Sunday, June 6, 2010

Milestone 3

Hey, I finally made it to the third milestone! About time :)

I have THE task generating a (kinf of a) complex response, based on input parameters. It's one of the 6 to implement. They will be triggered depending on the values extracted from the input.
Currently this is basically how incoming music will influence response type:


- for long and steady pitches, high density - resp1: long granulated answer + harmonization (long notes or stretched short ones) OR - when incoming phrase becomse too stable - short, scattered texture
- for long unsteady pitches - resp2: long stretched answer OR many shorter notes
- for short events, low density - resp3: short/shortened scattered texture with filters OR long sounds
- for short events, high density - resp4: dense texture of short/shortened notes modulated OR many shorter notes
- for very loud events - resp5: many shorter notes + dense texture of short/shortened notes modulated;
- for long pause - generate romething - resp6

In this version of the code the first response is turned on constantly. Parameters controlling generated phrase in realtime are (among others): densityDir changes grain duration and freq, realtime loudness info changes harmonization scale and random panning for grains, realtime midi slope (rate of change of midi notes) modulates tempo of consecitive events in the phrase.



(
var numinchannels, samplerate, controlrate, masterVol, bufferArray, longbufferLength, fftbuflength, muteInput, in, out, st_out, timeOffset, pathprefix, inputBoost, minimalEventDuration, currentEventNumber, onsetOffset, soundInNo, inputOffset, continuousDataFreq; //general variables
var recorder, play, straightPlayer, mainPlayer, rec1, time, playCnt, checkPlayers, checkDuration, phrasePlay, clear, getPhrases, phraseAnalyze, dirPhraseAnalyze, phraseRespond, grainsynth, makeResponse, grainTask; //synthdefs and functions
var startResp, stopResp, contResp; //responders
var longbuf, fftbuf; //buffers
var window1, midiNoteKnob, midiAvKnob, hasFreqAvKnob, ampAvKnob, loudnessAvKnob, amplitudeKnob, starttimeNumber, durationNumber, recButton, playButton, onsetNumber, playNumber, clearButton, phraseNumber, ampTrigLoKnob, ampTrigHiKnob, onsetThreshKnob, noteChangeTrigLoKnob, noteChangeTrigHiKnob, noteChangeKnob, pauseNumber; //gui
var midiNotesAvKnob, midiNotesDirKnob, midiSlopeAvKnob, midiSlopeDirKnob, densityAvKnob, densityDirKnob, loudnAvKnob, loudnDirKnob, attitudeKnob, counterpointKnob, stabilityKnob, stablePhraseLengthKnob; //gui extracted params
//var midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, amplitude, duration, oldstarttime, oldstoptime, pause; //changing values
var mainList, phraseList, durPhrases, pitchPhrases, durPitchPhrases, extractedEvents; //arrays with events and phrases
var response1, response2, response3, response4, response5, response6, response7;//phrase responses
var midiNotesAv, midiNotesDir, midiSlopeAv, midiSlopeDir, densityAv, densityDir, loudnAv, loudnDir, regularityAv, regularityDir, durationsAv, durationsDir, stability; //realtime parameters
var densSTime, loudSTime, densStab, loudStab, stabilityThreshTime, stabilityLoudThresh, stabilityDensThresh, stablePhrasesLengths, stablePhrasesLengthsMemory, stablePhraseSTime; //stability and stable phrase length calculation
//var attitude, counterpoint; //attitude 1 = continue on your own, 0 = stop right after input, counterpoin1 1 = use opposite paramteres to the input, 0 = use similar
var complexSlopeAvTresh, complexLengthAvTresh; //thresholds

// ----------- variables to set ------------------------- // 
in = 0; //number of the input channel
st_out = 0; //number of the first channel of stereo out
masterVol = 0; //in dB
inputBoost = 0; //input amplification in dB
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
minimalEventDuration = 0.2; //in seconds - to avoid unnecessary retriggering
complexSlopeAvTresh = 2; //amount of midi slope average for the event above which event is treated as "complex" (multi-note)
complexLengthAvTresh = 1; //the shortest complex event
continuousDataFreq = 3; //Hz
stabilityThreshTime = 10; //time to consider the phrase too stable - seconds
stabilityLoudThresh = 0.3; //loudness variation +- above which the phrase isn't considered too stable anymore
stabilityDensThresh = 0.1; //density variation +- above which the phrase isn't considered too stable anymore
stablePhrasesLengthsMemory = 4; //number of stable phrases' lengths remembered back form the current
// ------------------------------------------------------ //



// INIT

//out = 42; //number of the first channel of Ambisonics B-format output
//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
//oldstoptime = 0; //init
//oldstarttime = 0; //init
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.new(size: samplerate * longbufferLength).load(sync: true); //main recording buffer
mainList = Array.newClear(8) ! 10000; //set up the global event list for the buffer; values for each array inside: startTime, midiNote, midiSlopeAv, hasFreqAv, ampAv, loudnessAv, duration, pause
stablePhrasesLengths = 0 ! stablePhrasesLengthsMemory; //init stable phrase length and stime
stablePhraseSTime = 0;
//phraseList = Array.newClear(8) ! 1000; //list of detected phrases
//durPhrases = Array.new; //list of phrases with similar dur
//pitchPhrases = Array.new; //list of phrases with similar pitch
timeOffset = thisThread.seconds; // get runtime time
inputOffset = in + Server.default.options.numOutputBusChannels;
densSTime = 0;
loudSTime = 0;
//stopTime = 0;
//controlrate.reciprocal.postln;
fftbuf = Buffer.alloc(Server.default, fftbuflength, 1);

// SYNTHDEFS

// TODO - as of Jun 3rd - deprecated!!! - add freq event triggering to the recorder; then add temporal ges ture analysis; then create meaningful answer from the system...

recorder = CtkSynthDef(\recorder, {arg mute = 0, onsetTreshold = 0.24, threshHi = -26, threshLo = -39, averageDelay = 0.07, noteChangeTrigLo = 120.1, noteChangeTrigHi = 400, relaxtime = 0.1, floor = -30; //tresholds to be adjusted
var recTrigger, stopTrigger, ampTrigger, onsetTrigger, noteChange, noteTrigger, sumTrigger, negSumTrigger, negAmpTrigger, amplitude, timer, pauseTimer, pauseTimerLatch, durTimer, hasFreq, freq, trigger, freqSlope, sig, hasFreqAv, ampAv, freqAv, counter, recTrigger0, inGate, chain, localbuf, onsets, midiNote, midiSlope, midiAv, loudness, loudnessAv, controlReci, noteNoteGate, midiGated, chain2, loud;
inGate = Gate.ar(In.ar(inputOffset) * inputBoost.dbamp, mute.neg + 1) * (mute.neg + 1); //incoming sound with mute
amplitude = Lag3UD.ar(RunningSum.rms(inGate), 0, 0.05); //Amplitude.kr(LagUD.ar(inGate, 0.01, 0.4), 0.01, 1.8); //amplitude tracking with "smoothing" TRYYYY!!!!
// amplitude.ampdb.poll(25, \amplitude);
ampTrigger = Schmidt.kr(amplitude, threshLo.dbamp, threshHi.dbamp); //amplitude trigger
negAmpTrigger = ampTrigger.neg + 1;
// ampTrigger.poll(125, \ampTri);
sig = inGate;//Gate.ar(inGate, trigger); //not gated for now
controlReci = controlrate.reciprocal;
chain = FFT(fftbuf, inGate, 0.5); //start the chain
loudness = Clip.kr(Loudness.kr(chain), 0, 64); //loudness analysis
onsetTrigger = Ramp.kr(Onsets.kr(chain, onsetTreshold), 0.01); //onset detection lagged
// onsetTrigger = Onsets.kr(chain, onsetTreshold);
// onsetTrigger.poll(125, \onsetT);
RecordBuf.ar(sig, longbuf, 0, run: 1, loop: 0, trigger: 1); //recorder
#freq, hasFreq = Tartini.kr(sig); //pitch analysis
// midiNote = freq.cpsmidi;
midiNote = LagUD.kr(freq.cpsmidi, 0.03, 0.03); //convert to midi notes - lagged -------- !params
noteNoteGate = Schmidt.kr(hasFreq, 0.85, 0.9);
midiGated = Gate.kr(midiNote, noteNoteGate);
midiSlope = (midiGated - DelayL.kr(midiGated, 2, averageDelay)).abs; //amount of change of midi note
noteChange = (midiSlope - DelayL.kr(midiSlope, 2, averageDelay)).abs * controlrate; //speed of pitch change
noteTrigger =  Ramp.kr(Schmidt.kr(noteChange, noteChangeTrigLo, noteChangeTrigHi), 0.01); //lagged
// noteTrigger.poll(125, \noteTrig);
sumTrigger = Clip.kr(Trig1.kr(noteTrigger + onsetTrigger, 0.11) * ampTrigger, 0, 1); //Trig1 to avoid double triggers?
// sumTrigger.poll(125, "sumTri----------");
negSumTrigger = SetResetFF.kr(sumTrigger, negAmpTrigger).neg + 1;
// negSumTrigger.poll(100, "negSumTrigger");
midiAv = AverageOutput.kr(midiSlope, DelayL.kr(sumTrigger, 2, averageDelay)); //average of midi note change
hasFreqAv = AverageOutput.kr(hasFreq, DelayL.kr(sumTrigger, 2, averageDelay)); //average amount of pitched sounds in the sample
ampAv = AverageOutput.kr(amplitude, DelayL.kr(sumTrigger, 2, averageDelay)).ampdb; //average amplitude of the sample
loudnessAv = AverageOutput.kr(loudness, DelayL.kr(sumTrigger, 2, averageDelay)); //average lodness
timer = Phasor.kr(1, controlrate.reciprocal, 0, longbufferLength); //timer to report rec time
// pauseTimer = Phasor.kr(negSumTrigger, controlrate.reciprocal, 0, longbufferLength) * DelayL.kr(negAmpTrigger, 2, 0.2); //send pause AFTER current event - with Delay2 it gets MOST pauses in tests...
pauseTimer = Phasor.kr(negAmpTrigger, controlrate.reciprocal, 0, longbufferLength) * DelayL.kr(negSumTrigger, 0.5, 0.02); //send pause AFTER current event - with Delay2 it gets MOST(?) pauses in tests...
pauseTimerLatch = pauseTimer + (Latch.kr(pauseTimer, Delay1.kr(sumTrigger)) * ampTrigger);
// pauseTimerLatch.poll(15, "timerlatch");
durTimer = Gate.kr(Phasor.kr(Delay1.kr(sumTrigger), controlrate.reciprocal, 0, longbufferLength), ampTrigger); //send dur of the current event
SendReply.kr(sumTrigger, 'start' , [timer, midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, durTimer, pauseTimer]); //send start times for events
SendReply.kr(Impulse.kr(continuousDataFreq), 'cont' , [midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, durTimer, pauseTimer, amplitude.ampdb, noteChange]); //test
SendReply.kr(negSumTrigger, 'stop' , [timer, midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, durTimer, pauseTimer]); //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
});

//temp synth
grainsynth = CtkSynthDef(\inPausesPlayer4, {arg dur, buffer, speed = 1, craziness = 0, amp = 0, pan = 0, modFreq = 0, modAmt = 0, soundToNoise = 1, output = 0, sendout, sendamp = -inf, sendout2, send2amp = -inf, sendout3, send3amp = -inf, perc = 0.95;
var src, snd, noise, mod, out, pointer, speedMod, trigger, startingPointMax = 0, startingPoint, env;
startingPointMax = BufDur.kr(buffer) - dur;
startingPoint = Rand(0.0, startingPointMax);
// env = EnvGen.kr(Env.new([0.0001, 1, 1, 0.0001], [dur * (1 - perc), dur * (perc * 0.3), dur * (perc * 0.7)], \exp)); //optional envelope... perc works better anyway I think
env = EnvGen.kr(Env.perc(dur * (1 - perc), dur * perc));
//speedMod = LFDNoise3.kr(modFreq, modAmt);
speedMod = SinOsc.ar(modFreq, 0, modAmt * 300);
//src = LPF.ar((PlayBuf.ar(1, buffer, (speed + speedMod) * BufRateScale.kr(buffer), 1, startingPoint * samplerate, 0) * soundToNoise) + BPF.ar(WhiteNoise.ar(soundToNoise.neg + 1), Clip.kr(speed * 330, 0, 20000), 0.2), (18000 - (12000 * modAmt))) * env;
src = SinOsc.ar(578 + speedMod) * env;
// src = PlayBuf.ar(1, buffer, speed + speedMod); //for testing
Out.ar(0, Pan2.ar(src, pan, amp.dbamp * masterVol.dbamp));
// Out.ar(sendout, src * sendamp.dbamp);
// Out.ar(sendout2, src * send2amp.dbamp);
// Out.ar(sendout3, src * send3amp.dbamp);
});
*/

//main player works! - mod: playback speed mod, filter type: 0 - LPF, 1 - BPF, 2 - HPF, 
mainPlayer = CtkSynthDef(\mainPlayer, {arg startPos = 0, dur = longbufferLength, grainDur = 0.05, warpToGrain = 1, grainFreq = 27, grainFreqRand = 4, grainPitchRand = 0.01,randPan = 1, filterAmt = 0, filterFreq = 4000, filterType = 2, buffer = longbuf, amp = 0, pan = 0, rate = 1, pitch = 1, modAmt = 0, modFreq = 0, att = 0.01, rel = 0.01;
var src, pointer, env, envGen, enc_w, enc_x, enc_y, enc_z, decoded, mod, secondLeg, scaledDur, warp, grain, trigger, triggeredPan, triggeredPitch, mix, filters;
scaledDur = dur * rate.reciprocal;
secondLeg = scaledDur - (scaledDur * att) - (scaledDur * rel);
envGen = EnvGen.kr(Env([0.001, 1, 1, 0.001], [att * scaledDur, secondLeg, rel * scaledDur], \exponential), doneAction: 2);
mod = pitch + SinOsc.ar(modFreq, 0, modAmt);
pointer = Phasor.kr(1, rate * controlrate.reciprocal / longbufferLength, startPos / longbufferLength, (startPos + dur) / longbufferLength);
// pointer.poll(20, "pointer");
trigger = Impulse.kr(grainFreq + TRand.kr(grainFreqRand.neg, grainFreqRand, Impulse.kr(grainFreq)));
triggeredPan = TRand.kr(randPan.neg, randPan, trigger);
triggeredPitch = TRand.kr(grainPitchRand.neg, grainPitchRand, trigger);
warp = Pan2.ar(Warp1.ar(1, buffer, pointer, mod, 0.14, -1, 4, 0.02, 4) * envGen, pan);
grain = GrainBuf.ar(2, trigger, grainDur, buffer, Clip.kr(pitch + triggeredPitch, -10, 10), pointer, 4, Clip.kr(triggeredPan + pan, -1, 1));
mix = (warp * warpToGrain) + (grain * (1 - warpToGrain));
filters = (LPF.ar(mix, filterFreq) * Clip.kr(1 - filterAmt, 0, 1)) + (BPF.ar(mix, filterFreq, 0.3) * Clip.kr(((filterAmt - 1).abs.neg + 1), 0, 1)) + (HPF.ar(mix, filterFreq) * Clip.kr(filterAmt - 1, 0, 1));
src = (mix * (1 - filterAmt)) + (filters * filterAmt);
// az = (az * pi) / 180; //to use az in degrees
// el = (el * pi) / 180; //to use el in degrees
// # 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(st_out, src * amp.dbamp * masterVol.dbamp); //stereo out
// Out.ar(out, [enc_w, enc_x, enc_y, enc_z] * amp.dbamp * masterVol.dbamp); //ambisonics encoded out
});
//mainPlayer.new(4).buffer_(longbuf).dur_(5).play;


// RECORDER CONTROL

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


// FUNCTIONS (~200 lines)

time = {thisThread.seconds - timeOffset}; //get time relative to start

checkDuration = {arg type, respTime; 
if((type == 1), { //if this is startTime
// "mainList[currentEventNumber - 2]: ".post; mainList[currentEventNumber - 2].postln;
// "mainList[currentEventNumber - 1]: ".post; mainList[currentEventNumber - 1].postln;
// "mainList[currentEventNumber]: ".post; mainList[currentEventNumber].postln;
if((mainList[currentEventNumber][0].notNil) && (respTime > minimalEventDuration), {currentEventNumber = currentEventNumber + 1; mainList[currentEventNumber][1] = 0; mainList[currentEventNumber][2] = 0; mainList[currentEventNumber][3] = 0; mainList[currentEventNumber][4] = 0; mainList[currentEventNumber][5] = 0; mainList[currentEventNumber][6] = 0; mainList[currentEventNumber][7] = 0; }); //advance, exception for the first one, ommit too short events, get rid of nils
mainList.at(currentEventNumber).putEach(0, respTime); //put the start time into array
// "mainList[currentEventNumber] after advance: ".post; mainList[currentEventNumber].postln;
{recButton.value = 1;
starttimeNumber.value = respTime;
onsetNumber.value = currentEventNumber;}.defer;
}, {//if this is stopTime
// currentEventNumber = currentEventNumber + 1; //advance on stoptime
{recButton.value = 0}.defer
});
phraseAnalyze.value; 
};

// -------- IDEA: 1. get realtime musical data, 2. provide list of available resources, 3. build synth-----------!!!!!!!!!!!!--------<<<<<<<<<----------

// ------------- -------- -------- NEXT --------- ---------- ---------

//gather remaining parameters (loudness, regularity, durs probably not), opt put them to the gui, move parts to the noteoff or realtime, then make playing func
//get time of unchanged events!!!
/*
*** plan as of June 3rd: ***
1. finish gathering realtime data - loudness, regularity - no, leaving regularity for now 
2. gather time data: density, loudness, regularity, pitch?
3. try decision-making mechanisms
4. build fynction & synth(s) for playing

*/

//array: startTime, midiNote, midiSlopeAv, hasFreqAv, ampAv, loudnessAv, duration, pause
/*
var midiNotesAv, midiNotesDir, midiSlopeAv, midiSlopeDir, densityAv, densityDir, loudnAv, loudnDir, regularityAv, regularityDir, durationsAv, durationsDir; //realtime parameters
var attitude, counterpoint; //attitude 1 = continue on your own, 0 = stop right after input, counterpoing 1 = use opposite paramteres to the input, 0 = use similar
var complexSlopeAvTresh; //thresholds
complexSlopeAvTresh = 2;
*/
//move part of that to realtime!!!
phraseAnalyze = {arg lookBackEvents = 10;//number of events to get current "character" and current "direction" for certain parameters
var farthestEvent, midiAr, midiNoComplexAr, slopeAr, lastSlopes, pausesAr, dursAr, pausesSum, dursSum, loudnesAr;
// "lookBackEvents before if: ".post; lookBackEvents.postln;
// "current ev before: ".post; currentEventNumber.postln;
if(currentEventNumber < lookBackEvents,  {lookBackEvents = currentEventNumber}); //decrease lookback at the beginning 
farthestEvent = (currentEventNumber - lookBackEvents - 1).thresh(0); //to not include the actual item in calculations
// "lookBackEvents: ".post; lookBackEvents.postln;
// "current ev: ".post; currentEventNumber.postln;
// "farthestEvent: ".post; farthestEvent.postln;
extractedEvents = Array.new;
lookBackEvents.do({arg i; //get last number of events into a separate array
if(mainList.at(i + farthestEvent).at(0).notNil, 
{extractedEvents = extractedEvents.add(mainList.at(i + farthestEvent))
});
});
// "extractedEvents: ".post; extractedEvents.postln;
// "extractedEvents[lookBackEvents - 1]: ".post; extractedEvents[lookBackEvents - 1].postln;
// "extractedEvents[lookBackEvents - 2]: ".post; extractedEvents[lookBackEvents - 2].postln;
// "extractedEvents[lookBackEvents - 3]: ".post; extractedEvents[lookBackEvents - 3].postln;
//create arrays
midiAr = Array.new;
slopeAr = Array.new;
midiNoComplexAr = Array.new;
lastSlopes = Array.new;
pausesAr = Array.new;
dursAr = Array.new;
loudnesAr = Array.new;
lookBackEvents.do({arg i; 
if(extractedEvents[i].notNil, {
midiAr = midiAr.add(extractedEvents[i][1]); //extract array of midi notes
pausesAr = pausesAr.add(if(extractedEvents[i][7].notNil, extractedEvents[i][7], 0)); //extract array of pauses
dursAr = dursAr.add(if(extractedEvents[i][6].notNil, extractedEvents[i][6], 0)); //extract array of durations
loudnesAr = loudnesAr.add(if(extractedEvents[i][5].notNil, extractedEvents[i][5], 0)); //extract array of loudnesses
if((extractedEvents[i][2].notNil) && (extractedEvents[i][5].notNil), {
if((extractedEvents[i][2] < complexSlopeAvTresh) && (extractedEvents[i][5] < complexLengthAvTresh), //and array of notes without complex events
{midiNoComplexAr = midiNoComplexAr.add(extractedEvents[i][1]);
if(midiNoComplexAr.size > 1, 
{slopeAr = slopeAr.add(absdif(midiNoComplexAr[midiNoComplexAr.size - 2], midiNoComplexAr[midiNoComplexAr.size - 1]))
});
}, { //add midiSlope from complex events
slopeAr = slopeAr.add(extractedEvents[i][2]);
};
);
});
});
});
// "midiAr: ".post; midiAr.postln;
midiNotesAv = midiAr.mean;
// "midiNotesAv: ".post; midiNotesAv.postln;
// "slopeAr: ".post; slopeAr.postln;
midiSlopeAv = slopeAr.mean;
// "midiSlopeAv: ".post; midiSlopeAv.postln;
// "pausesAr: ".post; pausesAr.postln;
// "dursAr: ".post; dursAr.postln;
if(pausesAr.includes(nil),  {pausesSum = 0}, {pausesSum = pausesAr.sum}); //for density: sum pauses
if(dursAr.includes(nil),  {dursSum = 0}, {dursSum = dursAr.sum}); //sum durs
// "pausesSum: ".post; pausesSum.postln;
// "dursSum: ".post; dursSum.postln;
if((pausesSum == 0) && (dursSum == 0), {densityAv = 0}, {densityAv = ((dursSum - pausesSum) / (dursSum + pausesSum)).clip(0, 1)}); //if both of pauses and durs are 0, then density is 0, if not, then it's calculated
// "densityAv: ".post; densityAv.postln;
loudnAv = loudnesAr.mean;
// "loudnAv: ".post; loudnAv.postln;
{midiNotesAvKnob.value = midiNotesAv;
midiSlopeAvKnob.value = midiSlopeAv;
densityAvKnob.value = densityAv;
loudnAvKnob.value = loudnAv;
}.defer;
};
dirPhraseAnalyze = {arg dirLookBack = 3;//number of events to get current "character" and current "direction" for certain parameters
var slopeDirAr, dirFarthestEvent, midiNoComplexDirAr, pausesDirAr, dursDirAr, pausesDirSum, dursDirSum;//for live
if(currentEventNumber < dirLookBack,  {dirLookBack = currentEventNumber}); //decrease dirlookback at the beginning 
dirFarthestEvent = (currentEventNumber - dirLookBack).thresh(0); //
midiNoComplexDirAr = Array.new;
slopeDirAr = Array.new;
pausesDirAr = Array.new;
dursDirAr = Array.new;
dirLookBack.do({arg i; //get last number of events into a separate array
if(mainList[i + dirFarthestEvent][7].notNil, {
pausesDirAr = pausesDirAr.add(mainList[i + dirFarthestEvent][7]);
dursDirAr = dursDirAr.add(mainList[i + dirFarthestEvent][6]);
});
if(mainList[i + dirFarthestEvent][2].notNil, {
if((mainList[i + dirFarthestEvent][2] < complexSlopeAvTresh) && (mainList[i + dirFarthestEvent][5] < complexLengthAvTresh), //and array of notes without complex events
{midiNoComplexDirAr = midiNoComplexDirAr.add(mainList[i + dirFarthestEvent][1]);
if(midiNoComplexDirAr.size > 1, 
{slopeDirAr = slopeDirAr.add(absdif(midiNoComplexDirAr[midiNoComplexDirAr.size - 2], midiNoComplexDirAr[midiNoComplexDirAr.size - 1])) //get slopes of those
})
}, { //add midiSlope from complex events
slopeDirAr = slopeDirAr.add(mainList[i + dirFarthestEvent][2]); //slope gets confused on long events!!! -------------- <<<<< ------ CHECK --------- !!!!!!!!! ------------
//"complex slope! ---------------".postln;
};
);
//lastSlopes = lastSlopes.add
//slopeDirAr = slopeDirAr.add(mainList[i + dirFarthestEvent][2]);
});
});
// "dursDirAr: ".post; dursDirAr.postln;
// "pausesDirAr: ".post; pausesDirAr.postln;
if(mainList[currentEventNumber][1].notNil, {midiNotesDir = mainList[currentEventNumber][1] - midiNotesAv});
// "midiNotesDir: ".post; midiNotesDir.postln;
midiSlopeDir = slopeDirAr.mean - midiSlopeAv;
// "midiSlopeDir: ".post; midiSlopeDir.postln;
if(pausesDirAr.includes(nil), {pausesDirSum = 0}, {pausesDirSum = pausesDirAr.sum}); //for density: sum pauses
if(dursDirAr.includes(nil), {dursDirSum = 0}, {dursDirSum = dursDirAr.sum}); //sum durs
if((dursDirSum == 0) && (pausesDirSum == 0), {densityDir = 0 - densityAv}, {densityDir = ((dursDirSum - pausesDirSum) / (dursDirSum + pausesDirSum)) - densityAv});
// "((dursDirSum - pausesDirSum) / (dursDirSum + pausesDirSum)): ".post; ((dursDirSum - pausesDirSum) / (dursDirSum + pausesDirSum)).postln;
// "densityDir: ".post; densityDir.postln;
if(mainList[currentEventNumber][5].notNil, {loudnDir = mainList[currentEventNumber][5] - loudnAv}, {loudnDir = 0});
// "loudnDir: ".post; loudnDir.postln;
//stability calc - will use density and loudness for now - var densSTime, loudSTime, densStab, loudStab; stabilityThreshTime, stabilityLoudThresh, stabilityDensThresh
if((loudnDir < stabilityLoudThresh) && (loudnDir > stabilityLoudThresh.neg), {loudStab = ((time.value - densSTime) / stabilityThreshTime).clip2(1)}, {loudSTime = time.value; loudStab = 0});
if((densityDir < stabilityDensThresh) && (densityDir > stabilityDensThresh.neg), {densStab = ((time.value - densSTime) / stabilityThreshTime).clip2(1)}, {densSTime = time.value; densStab = 0});
if(loudStab > densStab, {stability = loudStab}, {stability = densStab});
//stable phrase length
if(((loudnDir < stabilityLoudThresh) && (loudnDir > stabilityLoudThresh.neg) || (densityDir < stabilityDensThresh) && (densityDir > stabilityDensThresh.neg)),{stablePhrasesLengths[stablePhrasesLengthsMemory - 1] = (time.value - stablePhraseSTime)}, {stablePhraseSTime = time.value; stablePhrasesLengths.shift(-1)});
//GUI control
{midiNotesDirKnob.value = midiNotesDir;
midiSlopeDirKnob.value = midiSlopeDir;
densityDirKnob.value = densityDir;
loudnDirKnob.value = loudnDir;
stabilityKnob.value = stability;
stablePhraseLengthKnob.value = stablePhrasesLengths[stablePhrasesLengthsMemory - 1];
}.defer;
};

phrasePlay = {arg phraseNo;
var startPos, dur;
startPos = mainList[phraseNo][0];
dur = mainList[phraseNo][6];
'PLAYER phrase no: '.post; phraseNo.post; ', startPos: '.post; startPos.post; ', duration: '.post; dur.postln;
// straightPlayer.new(0, dur).dur_(dur).startPos_(startPos).play;
mainPlayer.new(0, dur).dur_(dur).startPos_(startPos).play;
};

clear = { // free whatever possible = stop execution
rec1.free;
longbuf.free;
fftbuf.free;
startResp.remove;
stopResp.remove;
contResp.remove;
makeResponse.stop;
response1.stop;
response2.stop;
response3.stop;
response4.stop;
response5.stop;
response6.stop;
AppClock.sched(0.3, {Task({51.do({arg i; {window1.alpha_(1 - (i * 0.02))}.defer; 0.007.wait;})}).play}); //fade out haha
AppClock.sched(1.03, {window1.close});
};

phraseRespond = { //find similar phrases and play them... to be rebuild to track directions in current playing and use buffer data to resynthesize (?) meaningful output
durPhrases = Array.new; //list of phrases with similar dur
pitchPhrases = Array.new; //list of phrases with similar pitch
durPitchPhrases = Array.new; //similar dur and pitch combined
(currentEventNumber - 1).do({arg i;
// "dur from phraseRespond: ".post; mainList.at(i).at(6).postln;
// "list at i: ".post; mainList.at(i).postln;
// if(mainList.at(currentEventNumber).at(6).notNil, {if(((mainList.at(i).at(6) / mainList.at(currentEventNumber).at(6)).abs > 0.9) && ((mainList.at(i).at(6) / mainList.at(currentEventNumber).at(6)).abs < 1.1), {durPhrases = durPhrases.add(i)})}); //find similar note durations
// if(mainList.at(currentEventNumber).at(1).notNil, {if(mainList.at(i).at(1).notNil, {if((mainList.at(i).at(1) - mainList.at(currentEventNumber).at(1)).abs < 1, {pitchPhrases = pitchPhrases.add(i)})})}); //find similar pitches
});
"durPhrases: ".post; durPhrases.postln;
"pitchPhrases: ".post; pitchPhrases.postln;
if(durPhrases.size>0, {phrasePlay.value(durPhrases.at(durPhrases.size.rand))}); //play for similar durations
if(pitchPhrases.size>0, {phrasePlay.value(pitchPhrases.at(pitchPhrases.size.rand))}); //play for similar pitches
// if((pitchPhrases.size>0) && (durPhrases.size>0), {
// durPitchPhrases = pitchPhrases.select({ arg item, i; durPhrases.includes(item) });
// "durPitchPhrases: ".post; durPitchPhrases.postln;
// if(durPitchPhrases.size>0, {phrasePlay.value(durPitchPhrases.at(durPitchPhrases.size.rand))}); //play for similar duration AND pitches
// });
};
/*
phrase responses:
loudness - influencing amp and amount of response
stablePhrasesLengths influencing responses' length - mean of last three values? or of all the values of any of the analyzed?
for long and steady pitches, high density - resp1: long granulated answer + harmony? (long notes or stretched) OR - when too stable - short, scattered texture, (realtime parameters influence)
for long unsteady pitches - resp2: long stretched answer OR many shorter notes
for short events, low density - resp3: short/shortened scattered texture with filters OR long notes with matching pitch (?)
for short events, high density - resp4: dense texture of short/shortened notes modulated OR many shorter notes
for very loud events - resp5: many shorter notes + dense texture of short/shortened notes modulated;
for long pause - generate random? - generate or not.. - resp6

//mainPlayer.new.startPos_(0).dur_(0).grainDur_(0).warpToGrain_(1).grainFreq_(27).grainFreqRand_(4).grainPitchRand_(0.01).randPan_(0).filterAmt_(0).filterFreq_(4000).filterType_(2).buffer_(longbuf).amp_(0).pan_(0).rate_(1).pitch_(1).modAmt_(0).modFreq_(0).att_(0.01).rel_(0.01); //synth will free automatically without supplying note length

realtime analyzed parameters: 
midiNotesAv, midiNotesDir, midiSlopeAv, midiSlopeDir, densityAv, densityDir, loudnAv, loudnDir, durationsAv, durationsDir, stability;
and use parameters from the mainList: startTime, midiNote, midiSlopeAv, hasFreqAv, ampAv, loudnessAv, duration, pause
*/

//main brain of the response generation
makeResponse = Task.new({
// var stime = 1, duration = 100, graindur = 0.1, graindurRand = 0, timeBetweenGrains = 0.05, timeBetweenGrainsRand = 0.02, perc = 0.7, speed = 1, modFreq = 2000, modAmt = 0.1, soundToNoise = 1, amp = 0, pan = 0, send1 = -90, send2 = -90, send3 = -90, buffer = longbuf;
// var now, dur, increment, ratio, grainSpeed, grainSoundToNoise, grainModAmt, currentGraindur, currentGraindurRand, currentTimeBetweenGrains, currentTimeBetweenGrainsRand, grainModFreq, grainAmp, grainPan, percussiveness, grainSend1, grainSend2, grainSend3, proc, phrNo;
var now; //task vars
// var resp1dur, resp2dur, resp3dur, resp4dur, resp5dur, resp6dur, resp7dur; //not necessary?
var longEventThresh, highDensity, highMidiSlope, highRegister, stabilityThresh, highLoudness, highLoudnessDif, longPauseLo, longPauseHi;//conditions
highDensity = 0.6;
longEventThresh = 1.2;
highMidiSlope = 2;
highRegister = 60;
stabilityThresh = 0.6;
highLoudness = 40;
highLoudnessDif = 10;
longPauseLo = 5;
longPauseHi = 10;
now = 0; //init time
// 1.wait; //give some time to start
"I'm in the Task!".postln;
//TASKS go here
response1 = Task.new({ //for long and steady pitches, high density - resp1: long granulated answer + harmony? (long notes or stretched) OR - when too stable - short, scattered texture, (realtime parameters influence)
var length, respNow, respIncr, chosenEvAr1, chosenEvAr2, stretchEvents, thisEvent, pitch, rate;
//mappings: densityDir to grain dur and grainFreq, realtime loudness to harmony size, and rand Pan, realtime midiSlopeDir to events speed in the phrase
"playing response1!".postln;
length = stablePhrasesLengths.mean * 2; //get initial length for the phrase
if(length < (longEventThresh * 2), {length = stablePhrasesLengths.sum}); //if it's too short, get the longer one
if(length < (longEventThresh * 2), {length = longEventThresh * 2}); //if it's STILL too short, get the longer one
"length :".post; length.postln;
respNow = 0; //start internal time
chosenEvAr1 = Array.new; //long events array
chosenEvAr2 = Array.new; //long events array
mainList.do({arg array, index; //get indexes of long events
if(array[6].notNil, {
if(array[6] > longEventThresh, 
{chosenEvAr1 = chosenEvAr1.add(index)}, {chosenEvAr2 = chosenEvAr2.add(index)}
)
}
)
});
if(chosenEvAr1.size < 1, {stretchEvents = 1}, {stretchEvents = 0}); //if there are no long events, stretch short ones
if((chosenEvAr1.size.notNil) && (chosenEvAr2.size.notNil), { //if they are not nil
if((chosenEvAr1.size > 0) || (chosenEvAr2.size > 0), { //proceed only, when we have something to play
while({respNow < length},
"I'm in while loop!".postln;
{
if(stability < stabilityThresh, //play longer notes unless the phrase isn't too stable
{
5.rand.round(1).do({arg i;
if(stretchEvents == 0, {thisEvent = chosenEvAr1.choose; rate = rrand(1, 2)}, {thisEvent = mainList.size.rand; rate = rrand(0.2, 0.7)}); //get this event number
if(i > 0, {pitch = rrand(0.5, (mainList[currentEventNumber][5] * 0.1) + 0.7)}; {pitch = 1});
mainPlayer.new.startPos_(mainList[thisEvent][0]).dur_(mainList[thisEvent][6]).grainDur_((densityDir * 0.02) + 0.1).warpToGrain_(0).grainFreq_((densityDir * 5) + 15).grainFreqRand_((densityDir * 8) + 8).grainPitchRand_((densityDir * 0.1) + 0.1).randPan_((mainList[currentEventNumber][5] * 0.02)).filterAmt_(0).filterFreq_(4000).filterType_(2).buffer_(longbuf).amp_((mainList[currentEventNumber][5] * 0.1) - 10).pan_(0.1.rand2).rate_(rate).pitch_(pitch).modAmt_(0).modFreq_(0).att_(0.2.rand).rel_(1.rand).play;
respIncr = 1.1 + (mainList[currentEventNumber][2] * 0.5);
if(respIncr<=0, {respIncr = 0.1});
respIncr.wait; //that's prett crucial...
respNow = respNow + respIncr;
"respNow from less stability: ".post; respNow.postln
})
}, {
thisEvent = chosenEvAr2.choose; rate = 1; //get this event number
if(thisEvent.isNil, {thisEvent = mainList.size.rand}); //if the array would be empty
pitch = rrand(0.7, (mainList[currentEventNumber][5] * 0.1) + 0.7); 
mainPlayer.new.startPos_(mainList[thisEvent][0]).dur_(mainList[thisEvent][6]).grainDur_((densityDir * 0.02) + 0.1).warpToGrain_(0.8).grainFreq_((densityDir * 5) + 15).grainFreqRand_((densityDir * 2) + 4).grainPitchRand_((densityDir * 0.05) + 0.05).randPan_((mainList[currentEventNumber][5] * 0.015)).filterAmt_(0).filterFreq_(4000).filterType_(2).buffer_(longbuf).amp_((mainList[currentEventNumber][5] * 0.1) - 8).pan_(0.1.rand2).rate_(rate).pitch_(pitch).modAmt_(0).modFreq_(0).att_(0.2.rand).rel_(1.rand).play;
respIncr = 0.1 + (mainList[currentEventNumber][2] * 0.05);
if(respIncr<=0, {respIncr = 0.1});
respIncr.wait; //that's prett crucial...
respNow = respNow + respIncr;
"respNow from more stability: ".post; respNow.postln;
});
// noteDur = 0.1;//for now
//mainPlayer.new.startPos_(0).dur_(0).grainDur_(0).warpToGrain_(1).grainFreq_(27).grainFreqRand_(4).grainPitchRand_(0.01).randPan_(0).filterAmt_(0).filterFreq_(4000).filterType_(2).buffer_(longbuf).amp_(0).pan_(0).rate_(1).pitch_(1).modAmt_(0).modFreq_(0).att_(0.2.rand).rel_(1.rand); //synth will free automatically without supplying note length
// respIncr = 0.1; //for now
// if(respIncr<=0, {respIncr = 0.1});
// respNow = respNow + respIncr;
});
}); //if arr size > 0 ending bracket
}); //if sizes are not nil ending bracket
});
response2 = Task.new({
});
response3 = Task.new({
});
response4 = Task.new({
});
response5 = Task.new({
});
//decision part
while({now < longbufferLength},
{
//check conditions for respo1
// if(
// (response1.isPlaying.not) && (durationsAv > longEventThresh) && (midiSlopeAv < highMidiSlope) && (densityAv > highDensity),
// {response1.start}
// );
// if(
// (response2.isPlaying.not) && (durationsAv > longEventThresh) && (midiSlopeAv > highMidiSlope),
// {response2.start}
// );
// if(
// (response3.isPlaying.not) && (durationsAv < longEventThresh) && (densityAv < highDensity),
// {response3.start}
// );
// if(
// (response4.isPlaying.not) && (durationsAv < longEventThresh) && (densityAv > highDensity),
// {response4.start}
// );
// if(
// (response5.isPlaying.not) && (mainList[currentEventNumber][5] > highLoudness) && ((mainList[currentEventNumber][5] - loudnAv) > highLoudnessDif),
// {response5.start}
// );
// if(
// (response3.isPlaying.not) && (mainList[currentEventNumber][7] > longPauseLo.longPauseHi.rand2),
// {response3.start}
// );
if( //test
(response1.isPlaying.not),
{response1.start}
);
now = now + continuousDataFreq.reciprocal;
continuousDataFreq.reciprocal.wait;
}
);
});

makeResponse.start; //run it!

//array: startTime, midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, duration, pause;

// ------ a simple GUI --------
//var midiNotesAvKnob, midiNotesDirKnob, midiSlopeAvKnob, midiSlopeDirKnob, densityAvKnob, densityDirKnob, loudnAvKnob, loudnDirKnob, attitudeKnob, counterpointKnob;
window1 = Window.new('Improviser parameters', Rect(128, 64, 560, 620)).front;
window1.alpha_(0);
window1.view.decorator=FlowLayout(window1.view.bounds, Point(10, 10), Point(10, 6));
midiNoteKnob = EZKnob(window1, 60@100,"midiNote", [0, 127]);
midiAvKnob = EZKnob(window1, 60@100,"midiSlAv", [0, 6]);
hasFreqAvKnob = EZKnob(window1, 60@100,"hasFreq", [0, 1]);
ampAvKnob = EZKnob(window1, 60@100,"ampAv", [-120, 0]);
loudnessAvKnob = EZKnob(window1, 60@100,"loudAv", [0, 64]);
// loudnessAvKnob = EZKnob(window1, 50@90,"loudAv", [0, 1100]);
amplitudeKnob = EZKnob(window1, 60@100," amp ", [-120, 0]);
noteChangeKnob = EZKnob(window1, 60@100,"noteChange", [0, 1200]);
window1.view.decorator.nextLine;
starttimeNumber = EZNumber(window1, 150@20,"startTime", [0, longbufferLength]);
durationNumber = EZNumber(window1, 150@20,"event dur", [0, longbufferLength]);
pauseNumber = EZNumber(window1, 150@20,"pause", [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 ev", [0, 10000], {|ez| phrasePlay.value(ez.value)});
window1.view.decorator.nextLine;
recButton = Button(window1, Rect(20,20,50,30)).states_([["waiting", Color.gray, Color.black], ["event", Color.black, Color.green]]);
window1.view.decorator.nextLine;
midiNotesAvKnob = EZKnob(window1, 60@100,"PhrAvNote", [0, 127]);
midiNotesDirKnob = EZKnob(window1, 60@100,"NoteDir", [-40, 40]);
midiSlopeAvKnob = EZKnob(window1, 60@100,"PhrSlope", [0, 6]);
midiSlopeDirKnob = EZKnob(window1, 60@100,"SlopeDir", [-6, 6]);
densityAvKnob = EZKnob(window1, 60@100,"PhrDensity", [0, 1]);
densityDirKnob = EZKnob(window1, 60@100,"DensityDir", [-1, 1]);
loudnAvKnob = EZKnob(window1, 60@100,"PhrLoudnss", [0, 64]);
loudnDirKnob = EZKnob(window1, 60@100,"LoudnssDir", [-40, 40]);
stabilityKnob = EZKnob(window1, 60@100,"Stability", [0, 1]);
stablePhraseLengthKnob = EZKnob(window1, 60@100,"StablePhrLgth", [0, longbufferLength]);
window1.view.decorator.nextLine;
ampTrigLoKnob = EZKnob(window1, 50@90,"AmpLo(dB)", [-120, 0], {|ez| rec1.threshLo_(ez.value)});
ampTrigHiKnob = EZKnob(window1, 50@90,"AmpHi(dB)", [-120, 0], {|ez| rec1.threshHi_(ez.value)});
onsetThreshKnob = EZKnob(window1, 50@90,"OnsetThr", [0, 1], {|ez| rec1.onsetTreshold_(ez.value)});
noteChangeTrigLoKnob = EZKnob(window1, 50@90,"NoteTLo", [0, 400], {|ez| rec1.noteChangeTrigLo_(ez.value)});
noteChangeTrigHiKnob = EZKnob(window1, 50@90,"NoteTHi", [0, 900], {|ez| rec1.noteChangeTrigHi_(ez.value)});
window1.view.decorator.nextLine;
clearButton = Button(window1, Rect(20,20,70,30)).states_([["kill me", Color.gray, Color.black], ["AAAHHH!!!", Color.red, Color.black]]).action_({ arg butt; clear.value});
//vars ampTrigLoKnob, ampTrigHiKnob, onsetThreshKnob, noteChangeTrigLoKnob, noteChangeTrigHiKnob
//rec1 mute = 0, onsetTreshold = 0.93, threshHi = -46, threshLo = -59, averageDelay = 0.1, noteChangeTrigLo = 20, noteChangeTrigHi = 40, relaxtime = 0.1, floor = -30

//window fade in...
AppClock.sched(0.05, {Task({26.do({arg i; {window1.alpha_(i * 0.04)}.defer; 0.003.wait;})}).play}); //fade in haha

// RESPONDERS  
startResp = OSCresponder(nil, 'start', { arg t, r, msg; // msg.postln;
var msgNew, startTime;
// oldstarttime = startTime; //remember previous start time - to func
startTime = msg.at(3) + onsetOffset; //get actual start time
msgNew = msg.rotate(-4).clipExtend(7); //extract only useful values from the array
// 'new array: '.post; msgNew.postln;
msgNew.do{arg val, i; mainList.at(currentEventNumber).putEach(i+1, val)};
// "mainList[currentEventNumber] startResp: ".post; mainList[currentEventNumber].postln;
// "pause from the list in startResp: ".post; mainList[currentEventNumber][7].postln;
checkDuration.value(1, startTime); //do operations to current event
}).add;

stopResp = OSCresponder(nil, 'stop', { arg t, r, msg;// msg.postln;
var msgNew, stopTime;
// oldstoptime = stopTime;
stopTime = msg.at(3); //get stop time
msgNew = msg.rotate(-4).clipExtend(7); //extract only useful values from the array
// 'new array: '.post; msgNew.postln;
msgNew.do{arg val, i; mainList.at(currentEventNumber).putEach(i+1, val)};
// "pause from the list in stopResp: ".post; mainList[currentEventNumber][7].postln;
checkDuration.value(0, stopTime); //put duration in the array - this also triggers response now
// 'stoptime: '.post; stopTime.postln;

}).add;
contResp = OSCresponder(nil, 'cont', { arg t, r, msg;// msg.postln;
var freqAv, hasFreqAv, ampAv, msgNew;
// [midiNote, midiAv, hasFreqAv, ampAv, loudnessAv, dur, pause, amplitude, noteChange]
// from v28 pause is added after current, adding before was to unreliable
{ 
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);
durationNumber.value = msg.at(8);
pauseNumber.value = msg.at(9);
amplitudeKnob.value = msg.at(10);
noteChangeKnob.value = msg.at(11);
}.defer; //gui control
// 'org array: '.post; msg.postln;
msgNew = msg.rotate(-3).clipExtend(7); //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
// "pause from the list: ".post; mainList[currentEventNumber][7].postln;
// "current event no: ".post; currentEventNumber.postln;
dirPhraseAnalyze.value; //analyze phrase directions etc
}).add;
)

No comments:

Post a Comment