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
File file = new 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[] decomposition(double[] si, int 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] +
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[] getvbs(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); }
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] -
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;
data[2*i + 1] = (byte) (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");
dataset1 = new 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);
btnDenoise = addButton("Denoise", tbButtons, false);
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;
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
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);
btnDenoise.setEnabled(false);
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(); }
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);
btnDenoise.setEnabled(false); }
} }
/**
* 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) {
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);
}
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");
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(trdRatio != null) {
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++) {
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() {
trdCleaner = new Thread(this);
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); }