Skip to content

Instantly share code, notes, and snippets.

@jamshark70
Last active June 26, 2021 01:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamshark70/b6b23110b1ce4b28864c72afb0c35367 to your computer and use it in GitHub Desktop.
Save jamshark70/b6b23110b1ce4b28864c72afb0c35367 to your computer and use it in GitHub Desktop.
FFT + pitch analysis testing code
// 1. Prepare analysis function
(
///////////////////////////////////////
// Non-Realtime FFT Analysis To File //
///////////////////////////////////////
~analyseWav = {
arg wavIn, fftOut, pitchOut, action;
// Define function vars
var inBuf, fftBuf, pitchBuf, fftSize = ~fftSize, f, nrtServer, nrtScore, env;
var cond = Condition.new;
fork {
f = SoundFile.openRead(wavIn);
f.close;
// Define Non-RealTime server
nrtServer = Server(\nrt, NetAddr("127.0.0.1", 57110),
ServerOptions.new
.numOutputBusChannels_(2)
.numInputBusChannels_(2)
.sampleRate_(44100)
);
// Create buffer for input file
inBuf = Buffer(nrtServer, 65536, 1);
// Create output buffer for FFT analysis data
fftBuf = Buffer(nrtServer, f.duration.calcPVRecSize(fftSize, 0.5, nrtServer.options.sampleRate), 1);
pitchBuf = Buffer(nrtServer, (f.numFrames / nrtServer.options.blockSize).roundUp.asInteger, 2);
// Create score for recording of FFT analysis to WAV file
nrtScore = Score([
[0, inBuf.allocMsg],
[0, fftBuf.allocMsg],
[0, pitchBuf.allocMsg],
[0, inBuf.readMsg(wavIn, leaveOpen: true)],
[0, [\d_recv, SynthDef(\pv_ana, {
var sig = VDiskIn.ar(1, inBuf, f.sampleRate / SampleRate.ir);
var fft = FFT(LocalBuf(fftSize, 1), sig);
var pitch = Tartini.kr(sig);
RecordBuf.kr(pitch, pitchBuf, loop: 0);
fft = PV_RecordBuf(fft, fftBuf, run: 1);
Out.ar(0, sig);
}).asBytes]],
[0, Synth.basicNew(\pv_ana, nrtServer).newMsg],
[f.duration + (fftSize / nrtServer.options.sampleRate),
fftBuf.writeMsg(fftOut, "wav", "float");
],
[f.duration + (fftSize / nrtServer.options.sampleRate),
pitchBuf.writeMsg(pitchOut, "wav", "float");
]
]);
nrtScore.score.do(_.postln);
// Run score
nrtScore.recordNRT(
outputFilePath: if(thisProcess.platform.name == \windows) { "NUL" } { "/dev/null" },
headerFormat: "wav",
sampleRate: nrtServer.options.sampleRate,
options: nrtServer.options,
duration: f.duration + (fftSize / nrtServer.options.sampleRate),
action: { "done".postln; cond.unhang }
);
cond.hang;
// Free score
nrtScore.free;
// Free NRT server
nrtServer.remove;
// not really needed b/c their server is gone
// inBuf.free;
// fftBuf.free;
// pitchBuf.free;
action.value;
};
}
)
// 2. Prepare reference files
~fftSize = 2048;
// Default soundfile to be analysed
~wavPath = Platform.resourceDir +/+ "sounds/a11wlk01.wav";
// ^^ but, better to substitute a monophonic instrument or vocal recording
// Default location for analysis file
~fftPath = "~/fft.wav".standardizePath;
~pitchPath = "~/pitch.wav".standardizePath;
~fftRefPath = ~fftPath.splitext[0] ++ "-reference.wav";
~pitchRefPath = ~pitchPath.splitext[0] ++ "-reference.wav";
~analyseWav.value(~wavPath, ~fftRefPath, ~pitchRefPath);
// 3. 30x test
(
fork {
var cond = Condition.new;
var fftReference = ~fftRefPath;
var pitchReference = ~pitchRefPath;
var getData = { |path|
var f = SoundFile.openRead(path);
var result;
protect {
result = Signal.newClear(f.numFrames * f.numChannels);
f.readData(result);
} {
f.close;
};
result
};
~fftRefData = getData.(fftReference);
~pitchRefData = getData.(pitchReference);
30.do { |i|
var fpath = ~fftPath.splitext[0] ++ i.asPaddedString(2) ++ ".wav";
var ppath = ~pitchPath.splitext[0] ++ i.asPaddedString(2) ++ ".wav";
var fData, pData;
~analyseWav.value(~wavPath, fpath, ppath, { cond.unhang });
cond.hang;
fData = getData.(fpath);
if(fData != ~fftRefData) {
"Iteration '%' fft file is corrupt".format(i).warn;
};
pData = getData.(ppath);
if(pData != ~pitchRefData) {
"Iteration '%' pitch file is corrupt".format(i).warn;
};
};
};
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment