Reduksi Noise Dari Rekaman Suara Pernapasan Menggunakan Wavelet Transform Based Filter

LAMPIRAN A: KODE PROGRAM

1. Preprocess.java

  public class Preprocess { public static final int SAMPLE_RATE = 44100; public static final double

  MAX_16_BIT = Short. MAX_VALUE ; /**

  • Baca audio samples dari wav file dan kembalikan sebagai larik bertipe double - * dengan nilai antara 1.0 dan +1.0.
  • @return d
  • /

  public static double [] baca(String namafile) { byte [] data = bacaByte(namafile); int N = data. length ; double [] d = new double [N/2]; for ( int i=0; i<N/2; i++) { d[i] = (( short ) (((data[2*i+1] & 0xFF) << 8) + (data[2*i] &0xFF))) / (( double ) MAX_16_BIT ); } return d; }

  /** * Baca data dan kembalikan sebagai larik bertipe byte.

  • @return data
  • /

  private static byte [] bacaByte(String namafile) { byte [] data = null ; AudioInputStream ais = null ; try {

  // Membaca file

  new File file = File(namafile); if (file.exists()) { ais = AudioSystem.getAudioInputStream(file); data = new byte [ais.available()]; ais.read(data); } } catch (Exception ex) { System. out .println(ex.getMessage()); throw new RuntimeException( "Tidak dapat membaca " + namafile); } return data; } }

2. SignalFilter.java

  public class SignalFilter {

  /** Koefisien untuk menandakan wavelet transform dengan berapa koefisien yang dipakai. */

  public static final int DB_NO = 4;

  /** Koefisien pertama Wavelet Transform Based Filter. */

  public static final double k0 = (1+sqrt(3))/(4*sqrt(2));

  /** Koefisien kedua Wavelet Transform Based Filter. */

  public static final double k1 = (3+sqrt(3))/(4*sqrt(2));

  /** Koefisien ketiga Wavelet Transform Based Filter. */

  public static final double k2 = (3-sqrt(3))/(4*sqrt(2));

  /** Koefisien keempat Wavelet Transform Based Filter. */

  public static final double k3 = (1-sqrt(3))/(4*sqrt(2));

  /**

  • Method decomposition melakukan dekomposisi sinyal input menjadi dua bagian
    • yang masing masing bagian memiliki panjang setengah dari panjang sinyal input.

  • Bagian pertama disebut low pass filter, sedangkan bagian kedua disebut high pass filter.
  • * Bagian low pass filter dapat didekomposisi lagi menjadi 2 bagian seperti

    sebelumnya.
  • Iterasi dekomposisi ini dilakukan sebanyak level yang ditentukan.
  • @param si sinyal input yang akan didekomposisi.
  • @param level jumlah dilakukannya dekomposisi.

  @return

  • sinyal yang didekomposisi yang dibangun oleh bagian low pass filter dan high pass filter, * panjangnya sama dengan sinyal input.
  • /

  public static double double int [] decomposition( [] si, level) { int lth = si. length ;

  // Panjang matriks yang didekomposisi di suatu level

  int currentLevelLength = lth; double [] result = new double [lth];

  // Lakukan proses dekomposisi sebanyak level

  for ( int i = 0; i < level; i++) { double [] tmp = result.clone(); int index = 0;

  // Lakukan dekomposisi

  for ( int j = 0; j < currentLevelLength; j+=2) { tmp[j] = k0 *si[index] + k1 *si[index+1] +

  

k2 *si[(index+2)%currentLevelLength] + k3 *si[(index+3)%currentLevelLength];

  tmp[j+1] = k3 *si[index] - k2 *si[index+1] +

  

k1 *si[(index+2)%currentLevelLength] - k0 *si[(index+3)%currentLevelLength]; index+=2; } index = 0; result = tmp.clone();

  // Atur output agar bagian low pass filter di setengah depan output // dan bagian high pass filter di setengah belakang output

  for ( int j = 0; j < currentLevelLength/2; j+=1) { result[index] = tmp[2*j]; result[index+currentLevelLength/2] = tmp[2*j+1]; index+=1; } currentLevelLength = currentLevelLength / 2; si = result; } return result; }

  /**

  • Method getvbs melakukan threshold terhadap sinyal input dan memisahkan * sinyal suara pernapasan dan noise.
  • @param si
  • @return sinyal suara pernapasan
  • /

  public static double double [] getvbs( [] si) { int lth = si. length ; double t, sum = 0, sum2 = 0; double [] tmp1 = si.clone(); double

  [] tmp2 = si.clone(); double [] tmp3 = si.clone(); double [] vbs = new double [lth]; double [] vn = new double [lth]; int x = 0, y = 0; for ( int i = 0; i < lth; i++) { sum += tmp1[i]; } double mean = sum / lth; for ( int i = 0; i < lth; i++) { tmp3[i] = (tmp2[i] - mean) * (tmp2[i] - mean); } for ( int i = 0; i < lth; i++) { sum2 += tmp3[i]; } double sd = Math.sqrt(sum2/lth);

  // Hitung threshold

  t = sd * sqrt((2 * Math.log(lth))); for ( int j = 0; j < lth; j++) { if (Math.abs(si[j]) >= t) { vn[x] = si[j]; x++; } else { vbs[y] = si[j]; y++; } } return vbs; }

  /**

  • Method getvn melakukan threshold terhadap sinyal input dan memisahkan * sinyal suara pernapasan dan noise.
  • @param si
  • @return noise
  • /

  public static double [] getvn( double [] si) { int lth = si. length ; double t, sum = 0, sum2 = 0; double [] tmp1 = si.clone(); double [] tmp2 = si.clone(); double [] tmp3 = si.clone(); double [] vbs = new double [lth]; double [] vn = new double [lth]; int x = 0, y = 0; for ( int i = 0; i < lth; i++) { sum += tmp1[i]; } double mean = sum / lth; for ( int i = 0; i < lth; i++) { tmp3[i] = (tmp2[i] - mean) * (tmp2[i] - mean); } for ( int i = 0; i < lth; i++) { sum2 += tmp3[i]; }

  double sd = Math.sqrt(sum2/lth);

  // Hitung threshold

  t = sd * sqrt((2 * Math.log(lth))); for ( int j = 0; j < lth; j++) { if (Math.abs(si[j]) >= t) { vn[x] = si[j]; x++; } else { vbs[y] = si[j]; y++; } } return vn; }

  /**

  • Method reconstruction melakukan rekonstruksi sinyal suara pernapasan * dan noise dari tahap threshold.
  • @param si sinyal input yang akan direkonstruksi.
  • @param level jumlah dilakukannya rekonstruksi.

  @return sinyal yang telah direkonstruksi. *

  • /

  public static double [] reconstrution( double [] si, int level) { int lth = si. length ; int currentLevelLength = lth / ( int )Math.pow(2, level-1); double

  [] result = si.clone();

  // Lakukan proses rekonstruksi sebanyak level

  for ( int i = 0; i < level; i++) { int index = 0; double [] tmp = result.clone();

  // Ubah urutan sinyal dari sebelumnya terbagi dua, // setengah low pass filter dan setengah high pass filter, menjadi // selang-seling low pass filter - high pass filter

  for ( int j = 0; j < currentLevelLength; j+=2) { tmp[j] = result[index]; tmp[j+1] = result[index+currentLevelLength/2]; index+=1; } index = currentLevelLength-2; result = tmp.clone();

  // Lakukan proses rekonstruksi

  for ( int j = 0; j < currentLevelLength; j+=2) { result[j] = k2 *tmp[index] + k1 *tmp[index+1] +

  k0 *tmp[(index+2)%currentLevelLength] + k3 *tmp[(index+3)%currentLevelLength];

  result[j+1] = k3 *tmp[index] - k0 *tmp[index+1] +

  k1 *tmp[(index+2)%currentLevelLength] - k2 *tmp[(index+3)%currentLevelLength]; index = (index + 2)%currentLevelLength; } currentLevelLength = currentLevelLength * 2; } return result; } }

3. Finalprocess.java

  public class Finalprocess { public static final int SAMPLE_RATE = 44100; public static final double MAX_16_BIT = Short. MAX_VALUE ;

  /** * Simpan larik bertipe double sebagai file sound.

  • /

  public static void simpan(String namafile, double [] input) {

  // 44,100 samples per second // Gunakan 16-bit audio, mono, signed PCM, little Endian

  AudioFormat format = new AudioFormat( SAMPLE_RATE , 16, 1, true , false ); byte [] data = new byte [2 * input. length ]; for ( int i = 0; i < input. length ; i++) { int temp = ( short ) (input[i] * MAX_16_BIT ); data[2*i + 0] = ( byte ) temp; byte data[2*i + 1] = ( ) (temp >> 8); }

  // Simpan file

  try { ByteArrayInputStream bais = new ByteArrayInputStream(data); AudioInputStream ais = new AudioInputStream(bais, format, input. length ); if (namafile.endsWith( ".wav" ) || namafile.endsWith( ".WAV" )) {

  AudioSystem.write(ais, AudioFileFormat.Type. WAVE , new File(namafile)); } else { throw new RuntimeException( "File format tidak dapat

  digunakan: " + namafile);

  } } catch (Exception e) {

  System. out .println(e); System.exit(1); } } }

4. MainWindow.java

  public class MainWindow extends JFrame implements ActionListener {

  /** * Aplikasi dijalankan.

  • @param args
  • /

  public static void main(String[] args) { EventQueue.invokeLater( new Runnable() {

  @Override

  public void run() { new MainWindow(); } }); } public MainWindow() {

  frmMain .setDefaultCloseOperation(JFrame. EXIT_ON_CLOSE ); series1 = new

  XYSeries( "Signal" );

  series2 = new

  XYSeries( "Signal" ); new

  dataset1 =

  XYSeriesCollection( series1 );

  dataset2 = new

  XYSeriesCollection( series2 );

  waveform1 = createWaveform( title1 , dataset1 ); waveform2 = createWaveform( title2 , dataset2 ); cpWaveform1 = new ChartPanel( waveform1 ); cpWaveform1 .setPreferredSize( new java.awt.Dimension(700, 200)); cpWaveform2 = new ChartPanel( waveform2 ); cpWaveform2 .setPreferredSize( new java.awt.Dimension(700, 200)); btnOpen = addButton( "Open" , tbButtons , true ); btnPlay = addButton( "Play" , tbButtons , false ); btnPause = addButton( "Pause" , tbButtons , false );

  false

  btnDenoise = addButton( "Denoise" , tbButtons , ); btnCalculateSNR = addButton( "Calculate SNR" , tbButtons , false ); btnCleanCanvas = addButton( "Clean Canvas" , tbButtons , true ); tbButtons .setFloatable( false ); tbButtons .setBackground(Color. WHITE ); tbButtons .add( btnOpen ); tbButtons .add( btnPlay ); tbButtons .add( btnPause ); tbButtons .add( btnDenoise ); tbButtons .add( btnCalculateSNR ); tbButtons .add( btnCleanCanvas ); lblFile .setHorizontalAlignment(JLabel. LEFT ); spnCalculation = new JScrollPane(); gbc_scrollPane = new GridBagConstraints(); gbc_scrollPane . fill = GridBagConstraints. BOTH ; gbc_scrollPane . gridx = 0; gbc_scrollPane . gridy = 2; txtCalculation .setLineWrap( true ); txtCalculation .setFont( new Font( "Arial" , Font. PLAIN , 12)); txtCalculation .setColumns(50); txtCalculation .setRows(5); spnCalculation .setViewportView( txtCalculation ); spnCalculation .setBorder( eBorder ); pnlCalculation .add( spnCalculation , gbc_scrollPane ); pnlCalculation .setBackground(Color. WHITE ); pnlMiddle2 .add( lblFile ); pnlMiddle2 .setBackground(Color. WHITE ); pnlMiddle3 .add( tbButtons ); pnlMiddle3 .setBackground(Color. WHITE ); pnlMiddle4 .add( lblCalculation ); pnlMiddle4 .setBackground(Color. WHITE ); pnlSub .setLayout( new BoxLayout( pnlSub , BoxLayout. Y_AXIS )); pnlSub .add( cpWaveform1 ); pnlSub .add( cpWaveform2 ); pnlSub .add( pnlMiddle2 ); pnlSub .add( pnlMiddle3 ); pnlSub .add( pnlMiddle4 ); pnlSub .add( pnlCalculation ); pnlContent .add( pnlSub ); frmMain .setContentPane( pnlContent ); frmMain .pack(); frmMain .setResizable( false ); frmMain .setLocationRelativeTo(getLayeredPane()); frmMain .setVisible( true );

  } private JFreeChart createWaveform(String title, XYDataset dataset) { JFreeChart jfreechart = ChartFactory.createXYLineChart(title, "Waktu" , "Amplitudo" , dataset, PlotOrientation.

  VERTICAL , false , false , false );

  jfreechart.setBackgroundPaint(Color. WHITE );

  XYPlot xyplot = (XYPlot) jfreechart.getPlot(); xyplot.setBackgroundPaint(Color. lightGray ); xyplot.setDomainGridlinePaint(Color. WHITE ); xyplot.setRangeGridlinePaint(Color. WHITE ); return jfreechart;

  } private JButton addButton(String name, JToolBar jtb, boolean state) {

  // TODO Auto-generated method stub

  JButton b = new JButton(name); b.addActionListener( this ); b.setEnabled(state); jtb.add(b); return b; }

  @Override

  public void actionPerformed(ActionEvent ae) {

  // TODO Auto-generated method stub

  Object obj = ae.getSource();

  if (obj.equals( btnOpen )) {

  player .stop(); loader .start(); btnPlay .setEnabled( false ); btnPause .setEnabled( false ); btnDenoise .setEnabled( false ); btnCalculateSNR .setEnabled( false );

  } else if (obj.equals( btnPlay )) { if ( btnPlay .getText().startsWith( "Play" )) {

  player .start(); btnPause .setEnabled( true ); btnPlay .setText( "Stop" );

  } else {

  player .stop(); btnPause .setEnabled( false ); btnPlay .setText( "Play" );

  } } else if (obj.equals( btnPause )) { if ( btnPause .getText().startsWith( "Pause" )) { if ( player . trdPlayer != null ) {

  player . sdLine .stop();

  }

  btnPause .setText( "Resume" );

  else } { if ( player . trdPlayer != null ) {

  player . sdLine .start();

  }

  btnPause .setText( "Pause" );

  } } else if (obj.equals( btnDenoise )) {

  denoise .start();

  } else if (obj.equals( btnCalculateSNR )) {

  ratio .start();

  } else if (obj.equals( btnCleanCanvas )) {

  player .stop(); btnPlay .setEnabled( false ); btnPause .setEnabled( false );

  false

  btnDenoise .setEnabled( ); btnCalculateSNR .setEnabled( false ); cleaner .start();

  } }

  /** * Loader class. Pengaturan membuka fie audio dan menggambar waveform.

  • @author Samuel Situmeang *
  • /

  public class Loader implements Runnable { private Thread trdLoader ; public void start() {

  trdLoader = new Thread( this ); trdLoader .setName( "Loader" ); trdLoader .start();

  } public void stop() {

  if ( trdLoader != null ) {

  trdLoader .interrupt();

  }

  trdLoader = null ;

  } public void run() {

  fc .setCurrentDirectory( new File(System.getProperty( "user.dir" )));

  int a = fc .showOpenDialog( null ); if (a == JFileChooser. APPROVE_OPTION ) { try {

  url = fc .getSelectedFile().getPath();

  File urlFile = new File( url ); String filename = urlFile.getName();

  lblFile .setText(filename);

  double [] signal = Preprocess.baca( url ); for ( int i = 0; i < signal. length ; i+=50) { double x = i; double y = signal[i];

  series1 .addOrUpdate(x, y);

  } } catch (Exception ex) { JOptionPane.showMessageDialog( null , "File tidak dapat

  dibuka." , "Pesan" ,JOptionPane. ERROR_MESSAGE ); lblFile .setText( "" );

  }

  btnPlay .setEnabled( true ); btnDenoise .setEnabled( true );

  } else {

  btnPlay .setEnabled( false );

  false

  btnDenoise .setEnabled( );

  } } }

  /** * Player class. Pengaturan memainkan file audio.

  @author Samuel Situmeang *

  • /

  public class Player implements Runnable { private SourceDataLine sdLine ; private Thread trdPlayer ; public void start() {

  errStr = null ; trdPlayer = new Thread( this ); trdPlayer .setName( "Player" ); trdPlayer .start();

  } public void stop() {

  trdPlayer = null ;

  } private void shutDown(String message) { if (( errStr = message) != null ) { System. err .println( errStr ); cpWaveform1 .repaint(); cpWaveform2 .repaint();

  } if ( trdPlayer != null ) {

  trdPlayer = null ; btnPause .setEnabled( false ); btnPlay .setText( "Play" );

  } } public void run() { try {

  audioFile = new File( url );

  } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } try {

  audioInputStream = AudioSystem.getAudioInputStream( audioFile );

  } catch (Exception ex) { ex.printStackTrace(); System.exit(1); }

  audioFormat = audioInputStream .getFormat();

  DataLine.Info info = new DataLine.Info(SourceDataLine. class , audioFormat ); try {

  sdLine = (SourceDataLine) AudioSystem.getLine(info); sdLine .open( audioFormat );

  } catch (LineUnavailableException lue) { lue.printStackTrace(); System.exit(1); } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } int frameSizeInBytes = audioFormat .getFrameSize(); int bufferLengthInFrames = sdLine .getBufferSize() / 8; int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; int nBytesRead = 0; byte [] abData = new byte [bufferLengthInBytes];

  sdLine .start();

  while ( trdPlayer != null ) { try { if ((nBytesRead = audioInputStream .read(abData)) == -1) { break ; } int nBytesRemaining = nBytesRead; while (nBytesRemaining > 0) { nBytesRemaining -= sdLine .write(abData, 0, nBytesRemaining); } } catch (Exception ex) { shutDown( "Error sewaktu dimainkan: " + ex); break ; } } if ( trdPlayer != null ) {

  sdLine .drain();

  }

  sdLine .stop(); sdLine .close(); sdLine = null ;

  shutDown( null ); } }

  /** * Denoise class. Pengaturan denoise file rekaman suara pernapasan.

  • @author Samuel Situmeang *
  • /

  public class Denoise implements Runnable { private Thread trdDenoise ; public void start() {

  trdDenoise = new Thread( this ); trdDenoise .setName( "Denoise" ); trdDenoise .start();

  } public void stop() { if ( trdDenoise != null ) {

  trdDenoise .interrupt();

  }

  trdDenoise = null ;

  } public void run() { double [] sinyalAudio = Preprocess.baca( url );

  //PrintArray.print(sinyalAudio, "sinyalAudio.txt");

  double [] dekomposisiSinyalAudio = SignalFilter.decomposition(sinyalAudio, 2);

  //PrintArray.print(dekomposisiSinyalAudio, "dekomposisiSinyalAudio.txt");

  double [] breathSignal = SignalFilter.getvbs(dekomposisiSinyalAudio);

  //PrintArray.print(breathSignal, "breathSignal.txt");

  double [] noise = SignalFilter.getvn(dekomposisiSinyalAudio);

  //PrintArray.print(noise, "noise.txt");

  double [] rekonstruksiSinyalAudio = SignalFilter.reconstrution(breathSignal, 2);

  

//PrintArray.print(rekonstruksiSinyalAudio, "rekonstruksiSinyalAudio.txt");

  double [] rekonstruksiNoise = SignalFilter.reconstrution(noise, 2);

  //PrintArray.print(rekonstruksiNoise, "rekonstruksiNoise.txt");

  Finalprocess.simpan( "src/Reducted.wav" , rekonstruksiSinyalAudio); Finalprocess.simpan( "src/Noise.wav" , rekonstruksiNoise); double [] signal = Preprocess.baca( "src/Reducted.wav" ); for ( int i = 0; i < signal. length ; i+=50) { double x = i; double y = signal[i];

  series2 .addOrUpdate(x, y);

  }

  btnCalculateSNR .setEnabled( true );

  } }

  /** * Ratio class. Pengaturan perhitungan nilai Signal to Noise Ratio (SNR).

  • @author Samuel Situmeang *
  • /

  public class Ratio implements Runnable { private Thread trdRatio ; public void start() {

  trdRatio = new Thread( this ); trdRatio .setName( "Ratio" ); trdRatio .start();

  } public void stop() { if null

  ( trdRatio != ) {

  trdRatio .interrupt();

  }

  trdRatio = null ;

  } public void run() { double

  [] signalReducted = Preprocess.baca( "src/Reducted.wav" ); double [] signalNoise = Preprocess.baca( "src/Noise.wav" );; double xisr = 1; double xrmssr = 1; double xisn = 1; double xrmssn = 1; double snr = 1.0; double snr_db = 1.0; int size = 0;

  // Tentukan Root Mean Square Amplitude Sinyal Tereduksi

  for ( int i = 0; i < signalReducted. length ; i++) { signalReducted[i] = signalReducted[i]*signalReducted[i]; xisr += signalReducted[i]; } xrmssr = xisr/(signalReducted. length );

  // Tentukan Root Mean Square Amplitude Noise

  for ( int i = 0; i < size; i++) { signalNoise[i] = signalNoise[i]*signalNoise[i]; xisn += signalNoise[i]; } xrmssn = xisn/(signalNoise. length ); snr = (xrmssr/xrmssn) * (xrmssr/xrmssn); snr_db = 10 * Math.log10(snr);

  • txtCalculation .setText( "Signal-to-noise ratio : " FormatDesimal.twoDF(snr) + "\n"

  "Signal-to-noise ratio_db : " + FormatDesimal.twoDF(snr_db) + " dB" ); +

  } }

  /** * Cleaner class. Pengaturan membersihkan panel dari gambar sinyal.

  • @author Samuel Situmeang *
  • /

  public class Cleaner implements Runnable { private Thread trdCleaner ; public void start() { new this

  trdCleaner = Thread( ); trdCleaner .setName( "Printer" ); trdCleaner .start();

  } public void stop() { if ( trdCleaner != null ) {

  trdCleaner .interrupt();

  }

  trdCleaner = null ;

  } public void run() {

  series1 .clear(); series2 .clear(); lblFile .setText( "No file Label" ); txtCalculation .setText( null ); btnPlay .setEnabled( false ); btnDenoise .setEnabled( false ); btnCalculateSNR .setEnabled( false ); btnPause .setEnabled( false );

  } } }