Created
April 5, 2013 08:26
-
-
Save spotco/5317556 to your computer and use it in GitHub Desktop.
Sound file note detection using JTransforms (java)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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