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 );
} } }