Skip to content

Instantly share code, notes, and snippets.

@spotco
Created April 5, 2013 08:26
Show Gist options
  • Save spotco/5317556 to your computer and use it in GitHub Desktop.
Save spotco/5317556 to your computer and use it in GitHub Desktop.
Sound file note detection using JTransforms (java)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;
import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;
/**
* Generates a chromatic scale starting at the note of an input file.
* Uses <https://sites.google.com/site/piotrwendykier/software/jtransforms> FFT library.
*
* @author spotco
*/
public class InputScaleGen {
public static int SAMPLERATE = 0;
public static boolean KARPLUS = false;
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: SoundEstimator INPUT.dat OUTPUT.dat KARPLUS(y/n default-n)");
return;
}
if (args.length >= 3) {
if (args[2].equals("y")) KARPLUS = true;
}
double[] audioData = parse_input_datfile(args[0]);
DoubleFFT_1D fft = new DoubleFFT_1D(audioData.length);
double[] fftData = new double[audioData.length * 2];
for (int i = 0; i < audioData.length; i++) {
fftData[2 * i] = audioData[i]; //make buffer for fft output, 2*i is real and 2*i+1 is imaginary
fftData[2 * i + 1] = 0;
}
fft.complexForward(fftData); //perform inplace fft on data
double max_hz = -1;
double max_fftval = -1;
for (int i = 0; i < fftData.length; i += 2) {
double hz = 2*((i / 2.0) / fftData.length) * SAMPLERATE;
double vlen = Math.sqrt(Math.pow(fftData[i], 2) + Math.pow(fftData[i + 1],2));
if (max_fftval < vlen) { //find frequency with max absolute value
max_fftval = vlen;
max_hz = hz;
}
}
PrintWriter fileOut = null;
try {
fileOut = new PrintWriter(new BufferedWriter(new FileWriter(args[1])));
} catch (IOException e) { e.printStackTrace();}
fileOut.println("; Sample Rate " + SAMPLERATE);
System.out.printf("Input file '%s' dominant frequency was:%f, writing output to '%s'\n",args[0],max_hz,args[1]);
int starting_n = (int) Math.round(n_from_fn(max_hz));
for(int i=0;i<12;i++) { //output scale starting from found input frequency
if (KARPLUS) {
output_karplus_sound(fileOut,fn_from_n(starting_n+i),0.25);
} else {
output_pure_sound(fileOut,fn_from_n(starting_n+i),0.25);
}
System.out.printf("Outputting note at %f Hz\n",fn_from_n(starting_n+i));
}
fileOut.close();
System.out.println("done");
System.exit(0);
}
/**
* @param n - target piano note
* @return - frequency of nth piano note
*/
public static double fn_from_n(double n) {
return 440*Math.pow(2,((n-49)/12)); //fn = 440*2^((n-49)/12)
}
/**
* Inverse of fn_from_n
* @param fn target frequency
* @return approximate place of piano note of frequency
* @see fn_from_n
*/
public static double n_from_fn(double fn) {
return 13+12*(Math.log(fn/55)/Math.log(2)); //n = 13+12*(log(fn/55)/log(2))
}
/**
* Given name of input .dat file, reads sample rate and returns array of intensity values
* @param file input .dat file
* @modifies SAMPLERATE
* @return array of intensity values of input file
*/
public static double[] parse_input_datfile(String file) {
ArrayList<Double> s = new ArrayList<Double>();
int sampleRate = 0;
try {
BufferedReader fileIn = new BufferedReader(new FileReader(file));
String oneLine = fileIn.readLine();
StringTokenizer str = new StringTokenizer(oneLine);
str.nextToken();
str.nextToken();
str.nextToken();
sampleRate = Integer.parseInt(str.nextToken());
while ((oneLine = fileIn.readLine()) != null) {
if (oneLine.charAt(0) == ';') {
continue;
}
str = new StringTokenizer(oneLine);
str.nextToken();
double data = Double.parseDouble(str.nextToken());
s.add(data);
}
} catch (Exception e) { e.printStackTrace(); }
SAMPLERATE = sampleRate;
return listToArray(s);
}
/**
* Outputs a karplus-effect note of target frequency and length
* @param fileOut output to write intensity values to
* @param tarfreq target frequency (in Hz) of note
* @param sndlen target length (in Sec) of note
*/
public static void output_karplus_sound(PrintWriter fileOut, double tarfreq, double sndlen) {
Queue<Double> q1 = new LinkedList<Double>();
Queue<Double> q2 = new LinkedList<Double>();
int N = (int) (SAMPLERATE/tarfreq);
int M = (int) (sndlen*SAMPLERATE);
for(int i = 0; i < N; i++) {
q1.add(Math.random()*2-1);
}
q2.add(0.0);
for(int numsteps = 0; numsteps < M; numsteps++) {
double a = q1.remove();
double b = q2.remove();
double c = 0.99 * (a+b)/2;
q1.add(c);
q2.add(a);
fileOut.println((double) numsteps / SAMPLERATE + "\t" + c);
}
}
/**
* Outputs a pure sinusoidal note of target frequency and length
* @param fileOut output to write intensity values to
* @param tarfreq target frequency (in Hz) of note
* @param sndlen target length (in Sec) of note
*/
public static void output_pure_sound(PrintWriter fileOut, double tarfreq, double sndlen) {
for(int numsteps = 0; numsteps < (sndlen*SAMPLERATE); numsteps++) {
double t = ((double) numsteps) / SAMPLERATE;
double ph = 0.75*Math.sin(2*Math.PI*tarfreq*t);
fileOut.println(t + "\t" + ph);
}
}
/**
* Converts a List<Double> to double[]. I don't know why java doesn't have this by default.
* @param arr
* @return
*/
public static double[] listToArray(List<Double> arr){
double[] result = new double[arr.size()];
int i = 0;
for(Double d : arr) {
result[i++] = d.doubleValue();
}
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment