Tuesday, May 4, 2010

Gesture capture and playback code update


Number of improvements has been made; credits go to Sam Potter who helped me with managing arrays (thanks, Sam!). There is also change in calculating pitch change amount and some other bugfixes. Basic functionality seems to work - recording to a buffer constantly and recording information about musical events (pitch, duration, average amplitude, average loudness, pitch variation) in an array, pointing to specific time points in the buffer. Now I'll start analyzing that data to see, how can I recognize bigger phrases.
var numinchannels, samplerate, controlrate, masterVol, bufferArray, longbufferLength, fftbuflength, muteInput, in, out, st_out, timeOffset, pathprefix, inputBoost, oldstarttime, minimalEventDuration, currentEventNumber, onsetOffset; //general variables
var recorder, play, straightPlayer, warpPlayer, rec1, time, playCnt, checkPlayers, checkDuration, phrasePlay, clear; //synthdefs and functions
var longbuf, fftbuf; //buffers
var durationTriggerValue, dursMult, dursMultRand, stimes, stimesRand, timesEnv, timesRand, rates, ratesRand, pitches, pitchesRand, az, azRand, az2diff, el, elRand, el2diff, modAmts, modAmtsRand, modFreqs, modFreqsRand, atts, attsRand, rels, relsRand, amps, ampsRand; //envelopes
var window1, midiNoteKnob, midiAvKnob, hasFreqAvKnob, ampAvKnob, loudnessAvKnob, amplitudeKnob, starttimeNumber, durationNumber, recButton, playButton, onsetNumber, playNumber, clearButton, phraseNumber; //gui
var startTime, stopTime, midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, amplitude, duration; //continous values
var mainList, phraseList; //arrays with events and phrases

// ----------- variables to set ------------------------- //
in = 0; //number of the input channel
out = 42; //number of the first channel of Ambisonics B-format output
st_out = 0; //number of the first channel of stereo out
masterVol = 0; //in dB
inputBoost = 0; //input amplification in dB
minimalEventDuration = 0.2; //in seconds - to avoid unnecessary retriggering
longbufferLength = 90; //length of rec buffers in seconds
onsetOffset = -0.08; //compensate for onset delay; negative values if onsets are too late; -0.08 seems to be fine at the moment

// INIT

numinchannels = 1; //currently only one input channel is supported
inputBoost = inputBoost.dbamp; //convert to amp
currentEventNumber = 0; //init event numbering
startTime = 0; //init start time
samplerate = Server.default.sampleRate; //get samplerate
controlrate = samplerate / Server.default.options.blockSize; //get control rate
if (samplerate < 50000, {fftbuflength = 1024}, {if (samplerate < 100000, {fftbuflength = 2048}, {fftbuflength = 4096})}); // set the FFT buffer size // fftbuflength.postln;
longbuf = CtkBuffer.buffer(size: samplerate * longbufferLength).load(sync: true); //main recording buffer
mainList = Array.newClear(7) ! 1000; //set uo the global event list for the buffer; values for each array inside: startTime, midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, duration
timeOffset = thisThread.seconds; // get runtime time
time = thisThread.seconds - timeOffset; //get time relative to start

// SYNTHDEFS

recorder = CtkSynthDef(\recorder, {arg mute = 0, onsetTreshold = 0.2, treshHi = -46, treshLo = -52, averageDelay = 0.1; //tresholds to be adjusted
var recTrigger, stopTrigger, amplitude, timer, hasFreq, freq, trigger, freqSlope, sig, hasFreqAv, ampAv, freqAv, counter, recTrigger0, inGate, chain, localbuf, onsets, midiNote, midiSlope, midiAv, loudness, loudnessAv;
inGate = Gate.ar(SoundIn.ar(in) * inputBoost, mute.neg + 1) * (mute.neg + 1); //incoming sound with mute
amplitude = Amplitude.kr(LagUD.ar(inGate, 0.01, 0.4), 0.01, 1.8); //amplitude tracking with "smoothing"
trigger = Schmidt.kr(amplitude, treshLo.dbamp, treshHi.dbamp); //general trigger
sig = Gate.ar(inGate, trigger);
localbuf = LocalBuf.new(fftbuflength, 1); //fft buffer
chain = FFT(localbuf, SoundIn.ar(in), 0.5); //start the chain
onsets = Gate.kr(Onsets.kr(chain, onsetTreshold), trigger) * trigger; //onset detection, gated and automaticaly "muted"
loudness = Loudness.kr(chain); //loudness analysis
// stopTrigger = onsets.neg + 1;
RecordBuf.ar(sig, longbuf, 0, run: 1, loop: 0, trigger: 1); //recorder
#freq, hasFreq = Tartini.kr(sig); //pitch analysis
midiNote = freq.cpsmidi; //convert to midi notes
// freqSlope = Slope.kr(freq).abs; //kind of amount of change of freq
midiSlope = (midiNote - Delay1.kr(midiNote)).abs; //kind of amount of change of midi note
midiAv = Gate.kr(AverageOutput.kr(midiSlope, DelayC.kr(onsets, 2, averageDelay)), trigger); //average of midi note change
// freqAv = Gate.kr(AverageOutput.kr(freqSlope, DelayC.kr(onsets, 2, averageDelay)), trigger); //average base freq in the sample
hasFreqAv = Gate.kr(AverageOutput.kr(hasFreq, DelayC.kr(onsets, 2, averageDelay)), trigger); //average amount of pitched sounds in the sample
ampAv = AverageOutput.kr(amplitude, DelayC.kr(onsets, 2, averageDelay)); //average amplitude of the sample
loudnessAv = AverageOutput.kr(loudness, DelayC.kr(onsets, 2, averageDelay)); //average lodness
timer = Phasor.kr(1, controlrate.reciprocal, 0, longbufferLength); //timer to report rec time
SendReply.kr(onsets, 'start' , [timer]); //send start times for events
SendReply.kr(Impulse.kr(10), 'cont' , [midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, amplitude]); //send continous values for events
SendReply.kr(trigger.neg + 1, 'stop' , [timer]); //send stop times for events
});

straightPlayer = CtkSynthDef(\straightPlayer, {arg dur, buffer = longbuf, startPos, amp = 0, rate = 1, az = 0, el = 0, dir=1, modAmt = 0, modFreq = 0, att = 0.001, rel = 0.001;
var src, env, enc_w, enc_x, enc_y, enc_z, decoded, mod, secondLeg;
secondLeg = dur - (dur * att) - (dur * rel);
env = EnvGen.kr(Env([0.001, 1, 1, 0.001], [att * dur, secondLeg, rel * dur], \exponential));
az = (az * pi) / 180; //to use az in degrees
el = (el * pi) / 180; //to use el in degrees
mod = SinOsc.ar(modFreq, modAmt);
// src = PlayBuf.ar(numinchannels, buffer, (rate + mod) * BufRateScale.kr(buffer), 1, startPos * numinchannels * samplerate, 0) * env;
src = PlayBuf.ar(numinchannels, buffer, rate * BufRateScale.kr(buffer), 1, startPos * samplerate, 0) * env;
// src = PlayBuf.ar(1, buffer);
# enc_w, enc_x, enc_y, enc_z = BFEncode1.ar(src, az, el, 1, 1, 0);
Out.ar(st_out, B2UHJ.ar(enc_w, enc_x, enc_y, enc_z) * amp.dbamp * masterVol.dbamp); //stereo out
// Out.ar(out, [enc_w, enc_x, enc_y, enc_z] * amp.dbamp * masterVol.dbamp); //ambisonics encoded out
});

// RECORDER CONTROL

rec1 = recorder.new; //initialize recorder
rec1.play; //start recorder


// FUNCTIONS

checkDuration = {arg type;
//type: 0 - stopTime, 1 = startTime
if(type==0,
{duration = stopTime - startTime; mainList.at(currentEventNumber).putEach(6, duration); if(mainList[currentEventNumber][0] == nil, {mainList.at(currentEventNumber).putEach(0, startTime)}); currentEventNumber = currentEventNumber + 1; {durationNumber.value = duration; recButton.value = 0}.defer},
{if(stopTime < oldstarttime,
{duration = startTime - oldstarttime;
// 'oldstarttime: '.post; oldstarttime.postln;
// 'startTime: '.post; startTime.postln;
// 'duration: '.post; duration.postln;
mainList.at(currentEventNumber).putEach(6, duration); if(mainList[currentEventNumber][0] == nil, {mainList.at(currentEventNumber).putEach(0, startTime)}); currentEventNumber = currentEventNumber + 1; {durationNumber.value = duration; recButton.value = 0}.defer})}
);
};
phrasePlay = {arg phraseNo;
var pos, dur;
'phrase no: '.post; phraseNo.postln;
pos = mainList[phraseNo][0];
dur = mainList[phraseNo][6];
'startPos: '.post; pos.post; ', duration: '.post; dur.postln;
straightPlayer.new(0, dur).dur_(dur).startPos_(pos).play;
};

clear = { // free whatever possible = stop execution
rec1.free;
longbuf.free;
AppClock.sched(0.4, {window1.close});
// Server.default.quit;
// Server.default.boot;
};

//var startTime, stopTime, midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, amplitude, duration;
// ------ a simple GUI --------
//starttimeNumber, durationNumber, recButton, playButton
window1 = Window.new('Improviser parameters').front;
window1.view.decorator=FlowLayout(window1.view.bounds);
midiNoteKnob = EZKnob(window1, 50@90,"midiNote", [0, 127]);
midiAvKnob = EZKnob(window1, 50@90,"midiSlAv", [0, 0.2]);
hasFreqAvKnob = EZKnob(window1, 50@90,"hasFreq", [0, 1]);
ampAvKnob = EZKnob(window1, 50@90,"ampAv", [0, 1]);
loudnessAvKnob = EZKnob(window1, 50@90,"loudAv", [0, 80]);
amplitudeKnob = EZKnob(window1, 50@90," amp ", [0, 1]);
recButton = Button(window1, Rect(20,20,50,30)).states_([["waiting", Color.gray, Color.black], ["phrase", Color.black, Color.red]]);
starttimeNumber = EZNumber(window1, 150@20,"s_time (s)", [0, longbufferLength]);
durationNumber = EZNumber(window1, 150@20,"dur (s)", [0, longbufferLength]);
onsetNumber = EZNumber(window1, 150@20,"onset no", [0, 10000]);
// phraseNumber = EZNumber(window1, 150@20,"phrase no", [0, 10000]);
playNumber = EZNumber(window1, 150@20,"play phrase no", [0, 10000], {|ez| phrasePlay.value(ez.value)}, labelWidth: 120);
clearButton = Button(window1, Rect(20,20,50,30)).states_([["kill me", Color.gray, Color.black], ["bye", Color.red, Color.black]]).action_({ arg butt; clear.value});

// RESPONDERS
OSCresponder(nil, 'start', { arg t, r, msg;// msg.postln;
oldstarttime = startTime; //remember previous start time
startTime = msg.at(3) + onsetOffset; //get actual start time
checkDuration.value(1); //check if we need to take duration from it
mainList.at(currentEventNumber).putEach(0, startTime); //put start time into the current phrase array
// 'starttime: '.post; startTime.postln;
{recButton.value = 1;
starttimeNumber.value = startTime;
onsetNumber.value = currentEventNumber;
}.defer; //gui control
}).add;

OSCresponder(nil, 'stop', { arg t, r, msg;// msg.postln;
// var stoptime;
stopTime = msg.at(3); //get stop time
checkDuration.value(0); //put duration in the array
// 'stoptime: '.post; stopTime.postln;
// {recButton.value = 0;
// durationNumber.value = duration;
// }.defer; //gui contol
}).add;
OSCresponder(nil, 'cont', { arg t, r, msg;// msg.postln;
var freqAv, hasFreqAv, ampAv, msgNew;
// [midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, amplitude]
{
midiNoteKnob.value = msg.at(3);
midiAvKnob.value = msg.at(4);
hasFreqAvKnob.value = msg.at(5);
ampAvKnob.value = msg.at(6);
loudnessAvKnob.value = msg.at(7);
amplitudeKnob.value = msg.at(8);
}.defer; //gui control
// 'org array: '.post; msg.postln;
msgNew = msg.rotate(-3).clipExtend(5); //extract only useful values from the array
// 'new array: '.post; msgNew.postln;
msgNew.do{arg val, i; mainList.at(currentEventNumber).putEach(i+1, val)};//put parameters into the main array
// 'current event no: '.post; currentEventNumber.postln;
}).add;
)

No comments:

Post a Comment