Hi everyone,
I am trying to understand/use a project, that seems very interesting.
But not being too familiar with writing classes, I find myself drown in an inch of water.
To summarize the problem as much as possible, I can say that there are relationships between objects and therefore in order to use the system properly, you need to understand how to write the code where these objects are interconnected correctly.
The project is about a system to write carnatic music, specifically Konnakol but with the advantage of being able to exploit algorithmic composition techniques on those materials.
There 4 classes:
- KonaWord
- KonaTime
- KonaTani
- KonaGenerator
1 -------------------------------------------------- KONAWORD
/* ========================================================================= */
/* = KonaWord Class - Represents a a single Konakkol word made up of jatis = */
/* ========================================================================= */
KonaWord {
classvar words; //Lookup table for syllables to use
var <tani; //The Tani that this word belongs to
var <word; //The word the instance represents, an Array of symbols
var <jatis; //The number of syllables in the word
var <gati; //The subdivision of the beat.
var <karve; //The number of matras each jati should occupy.
var <matras; //The number of pulses/sub-divisions in the word;
var <speed; //The duration wait between syllables
var <dur; //The duration of the word (the jatis * karve)
var <val; //Jatis, dur and word in an array for comparison
var <rout; //The routine for playing this phrase
var konaSynth; //Symbol of the SynthDef to use
var <accent; //Additional accent on the first syllable
*initClass {
//Set up lookup table for syllables
words = Array.newClear(10);
words[0] = ['-'];
words[1] = ['Ta'];
words[2] = ['Ta', 'Ka'];
words[3] = ['Ta', 'Ki', 'Tah'];
words[4] = ['Ta', 'Ka', 'Di', 'Mi'];
words[5] = ['Da', 'Di', 'Gi', 'Na', 'Dom'];
words[6] = ['Ta', 'Ki', 'Tah', 'Ta', 'Ki', 'Tah'];
words[7] = ['Ta', 'Ka', 'Di', 'Mi', 'Ta', 'Ki', 'Tah'];
words[8] = ['Ta', 'Ka', 'Di', 'Mi', 'Ta', 'Ka', 'Ju', 'Na'];
words[9] = ['Da', 'Di', 'Gi', 'Na', 'Dom', 'Ta', 'Ka', 'Di', 'Mi'];
}
*new {|argSyls, argGati, argKarve=1, argTani, argSynth|
//Check arguments aren't nil
if( (argSyls==nil) || (argGati == nil),
{^"arguments not set\n Provide (numSyllables, gati)"}
);
//Check specified group is within bounds, the gati is legit
if( argSyls<=9 && ([4,3,5,7,9].includes(argGati)),
{^super.new.konaWordInit(argSyls, argGati, argKarve, argTani, argSynth)
},
{^"Bad Size or Gati"}
);
}
// Inizializzazione
konaWordInit { |argSyls, argGati, argKarve, argTani, argSynth|
tani = argTani;
word = words[argSyls];
jatis = word.size;
gati = argGati;
karve = argKarve;
matras = jatis*karve;
speed = ((1/gati)*karve);
dur = speed * jatis;
val = [jatis, dur, word];
accent = 0;
if(tani!=nil) {
konaSynth = tani.konaSynth
} {
if(argSynth!=nil) {
konaSynth = argSynth
} {
konaSynth = \beep // SynthDef di default \beep
};
};
this.setRoutine;
}
//Method to set the routine for this word. Stored in a function for re-use
setRoutine {
var ind;
var rate;
var amp;
//MIDI variables
var bOne; //MIDI note for first beat (always an open sound)
var bOthers; //Chosen MIDI notes for other beats;
var othersComplete; //Possible MIDI notes for other beats
var othersTemp; //Storage for next 'other beat' MIDI note.
var note; //Chosen MIDI pitch.
var val; //Temporary storage of chosen MIDI note.
var vel; //Chosen velocity for MIDI note.
switch (konaSynth)
{nil} {
rout = Routine {
word.size.do {|i|
amp = 0.2;
case
{word[i]=='-'} {amp=0}
{i==0} {amp=0.4};
tani.s.bind{Synth(konaSynth, [\amp,
(amp+(accent/10)).min(1)])};
word[i].postln;
speed.wait;
};
yieldAndReset(nil);
};
}
// Controlla quel synth ??
{\konaHit} {
rout = Routine {
word.size.do { |i|
//Index of the syllable to be played
ind = tani.syls.indexOf(word[i]);
if(i==0) {(amp=0.8+(accent/10)).min(1)} {amp=0.6};
if(word[i]!='-') {
tani.s.bind {
Synth(\konaHit, [\out, 0, \bufnum, tani.fftBuff,
\recBuf, tani.buffers[ind], \rate, ((tani.laya/60)*(0.25/speed)).max(1);]);
};
tani.fftRout.next;
if(i==0) {
word[i].post;
} {
word[i].asString.toLower.post;
};
" ".post;
speed.post; " ".post;
speed.wait;
} {
word[i].post; " ".post;
speed.post; " ".post;
speed.wait
};
};
"".postln;
yieldAndReset(nil);
};
} // Midi
{\MIDITranscribe} {
rout = Routine {
word.size.do { |i|
if(word[i]!='-') {
if(i==0)
{note = 48; vel = ((70..100).choose+accent).min(127)}
{note = 52; vel = (100+accent).min(127)};
tani.mOut.noteOn(0, note, vel);
if(i==0) {
word[i].post;
} {
word[i].asString.toLower.post;
};
" ".post;
speed.post; " ".post;
speed.wait;
tani.mOut.noteOff(0, note, vel);
} {
word[i].post; " ".post;
speed.post; " ".post;
speed.wait
};
};
yieldAndReset(nil);
};
}
//Automated mapping of strokes for Kanjira virtual instrument
{\MIDIPlay} {
bOne = [36, 37, 38, 39, 45, 46, 47].choose;
othersComplete = (48..55);
othersTemp = Array.newFrom(othersComplete);
bOthers = Array.newClear(word.size-1);
(word.size-1).do { |i|
val = othersTemp.choose;
bOthers[i] = val;
othersTemp = Array.newFrom(othersComplete);
othersTemp.remove(val);
};
rout = Routine {
word.size.do { |i|
if(word[i]!='-') {
if(i==0) {
note = bOne;
vel = (100+accent).min(127);
word[i].post; " ".post;
} {
note = bOthers[i-1];
vel = (70..100).choose;
word[i].asString.toLower.post; " ".post;
};
tani.mOut.noteOn(0, note, vel);
speed.post; " ".post;
speed.wait;
tani.mOut.noteOff(0, note, vel);
} {
word[i].post; " ".post;
speed.post; " ".post;
speed.wait
};
};
" ".postln;
yieldAndReset(nil);
};
};
}
play {
this.rout.play(tani.clock);
}
//Concatonation method to return a new KonaTime with both KonaItems
++ { |aKonaItem|
var newTime = KonaTime.new();
newTime.add(this).addAll(aKonaItem);
^newTime
}
//Printing method for timing information of the word.
postWord {|argW=true, argD=true, argF=true|
var words, decimals, fractions;
var maxItemLength;
words = Array.newClear(jatis);
decimals = Array.newClear(jatis);
fractions = Array.newClear(jatis);
jatis.do { |i|
words[i] = word[i].asString;
decimals[i] = speed.asString[0..4];
fractions[i] = (speed/4).asFraction(100,false).asString;
};
maxItemLength = [words.maxItem, decimals.maxItem, fractions.maxItem].maxItem.size;
jatis.do { |i|
var numSpaces;
numSpaces = maxItemLength - words[i].size;
numSpaces.do {
words[i] = words[i] ++ " ";
};
numSpaces = maxItemLength - decimals[i].size;
numSpaces.do {
decimals[i] = decimals[i] ++ " ";
};
numSpaces = maxItemLength - fractions[i].size;
numSpaces.do {
fractions[i] = fractions[i] ++ " ";
};
};
if(argW) {
words.postln;
};
if(argD) {
decimals.postln;
};
if(argF) {
fractions.postln;
};
^[words, decimals, fractions];
}
//For adding additional accents to the first syllable
accentFirst {
accent = accent + 10;
}
}
2 -------------------------------------------------- KONATIME
/* ======================================================================= */
/* = KonaTime Class - Collection class that represents phrases of music = */
/* ======================================================================= */
KonaTime : List {
var <tani; // The tani this belongs to
var <tala; // The tala of the tani
var <talaSum; // The sum of the tala beats
var dur; // The duration of this instance
var allDurs;
var word;
var jatis; // The total number of Jatis in this instance
var matras; // The total number of matras in this instance
var numTalas; // The number of talas this instance represents
var rout; // The playback routine
var gati; // The gati of the first object. In most cases
// the gati will be the same for all objects,
// but for experimental work this might be mixed.
*new {|argTani|
^super.new.konaTimeInit(argTani);
}
//Create a new instance from a given collection of KonaItems
*newFrom{|aCol, aTani|
var tani;
var ret;
tani = aTani;
ret = this.new(tani);
aCol.size.do { |i|
ret.add(aCol[i])
};
^ret;
}
*fill{ |size, function, argTani|
var newCollection = KonaTime.new(argTani);
size.do { |i|
newCollection.add(function.())
};
^newCollection;
}
konaTimeInit {|argTani|
tani = argTani;
if(tani!=nil,
{
tala = tani.tala;
talaSum = tala.sum;
}
);
}
//Add an item
add {|argItem|
//Ensure against non-KonaItems
if(argItem.class==KonaWord || (argItem.class==KonaTime)) {
if(argItem==this) {
super.add(KonaTime.newFrom(argItem, argItem.tani))
} {
super.add(argItem);
};
};
//Accent the first item in the phrase
if(this.size==1) {
this.accentFirst;
gati = this[0].gati;
};
}
//Add a collection
addAll { |argCollection|
super.addAll(argCollection);
}
//Duration getter method
dur {
var ret = 0;
this.do { |item, i|
ret = ret + item.dur;
};
^ret;
}
//Duration getter method for all contained objects
allDurs {
allDurs = Array.newClear(this.size);
this.do{ |item, i|
if(item.class==KonaWord) {
allDurs[i] = Array.fill(item.jatis, item.speed);
} {
allDurs[i] = item.allDurs
};
}
^allDurs;
}
//Jatis getter method
jatis {
jatis = 0;
this.size.do { |i|
jatis = jatis + this[i].jatis;
};
^jatis;
}
//Matras getter method
matras {
matras = 0;
this.size.do { |i|
matras = matras + this[i].matras;
};
^matras;
}
//Karve getter method, returns the mode karve of the instance.
karve {
var karves = this.collect({|item, i| item.karve});
^karves.maxItem({|item, i| karves.occurrencesOf(item)})
}
//Returns the number of cycles this instance occupies
numTalas {
numTalas = 0;
^numTalas = this.dur/this.talaSum;
}
//The playback routine for this instance
rout {
rout = Routine.new({});
this.do { |item, i|
rout = rout++item.rout
};
^rout;
}
//Play the instance routine to the parent KonaTani's clock.
play {
this.rout.play(tani.clock);
}
//Concatonation method for combining KonaItems
++ { |aKonaItem|
var newTime = KonaTime.new(tani);
newTime.addAll(this).addAll(aKonaItem);
^newTime
}
//Getter method for the word of all of the contained objects
word {
word = List[];
this.do({|item, i| word.add(item.word)});
^word.asArray;
}
//Method for printing the word and timing of all contained objects
postWord {|argW=true, argD=true, argF=true|
var words, decimals, fractions;
var get;
this.do {|item, i|
get = item.postWord(false, false, false);
words = words ++ get[0];
decimals = decimals ++ get[1];
fractions = fractions ++ get[2];
};
if(argW) {
words.postln;
};
if(argD) {
decimals.postln;
};
if(argF) {
fractions.postln;
};
"".postln;
^[words, decimals, fractions];
}
//Method to accent the first syllable of the first item in the KonaWord
//Works recursively on KonaTimes
accentFirst {
if(this[0]!=nil) {
this[0].accentFirst;
};
}
//Method to return the maximum speed in this KonaTime, works recursively
// on KonaTimes.
speed {
var ret = this[0].speed;
this.do { |item, i|
if(item.speed < ret) {
ret = item.speed;
};
};
^ret;
}
//Getter method, returns the gati of the first object.
gati {
^this[0].gati
}
//Method to return the largest word in number of jatis (works recursively
// for KonaTimes)
greatestJatis {
var ret = 0;
var val;
this.do { |item, i|
if(item.class==KonaWord) {
val = item.jatis;
} {
val = item.greatestJatis;
};
if(val>ret) {ret = val};
};
^ret;
}
//Overridden reverse method to include passing the tani into the resulting
// reversed KonaTime
reverse {
^this.class.newFrom(this.asArray.reverse, tani)
}
}
3 -------------------------------------------------- KONATANI
/* =========================================================================== */
/* = KonaTani Class - Class to contain and playback whole pieces of music = */
/* =========================================================================== */
KonaTani {
classvar allTalas; // A set of the common talas
var <laya; // Tempo
var <tala; // Beats in cycle
var <talaSym; // The tala in symbols
var <gati; // 'Default' sub-divisions per beats
var <otherGatis; // Sub-divisions to change to
var <gen; // Material generator
var <store; // Whole solo stored in a KonaTime
//Playback variables
var <s; // Server
var <konaSynth; // Synth to use
var <clock; // Playback Tempo Clock
var <fftBuffArray; // Array of buffers for PV_PlayBuf FFT
var <fftRout; // Routine to cycle through FFT buffers
var <fftBuff; // Next FFT buffer to use
var <syls; // Array of syllables for opening analysis file/directory
var <buffers; // Buffers for scpv files
var <talaRout; // Routine for tala clapping
var <pRout; // Routine for playback;
var <mOut; // MIDIOut
*initClass {
allTalas = [
["I4","O","O"], // Adi Tala
["U","O"], // Rupaka Tala
["U1", "R", "W", "W", "R"], // Khanda Capu
["W", "W", "R", "U", "U"] // Misra Capu
];
}
*new {|argLaya=60, argTala=#["I4","O","O"], argGati=4, argOtherGatis=#[3], argSynth=\konaHit|
^super.new.konaTaniInit(argLaya, argTala, argGati, argOtherGatis, argSynth);
}
*rand{|argLaya, argTala, argGati, argOtherGatis, argSynth=\konaHit|
var laya = argLaya ?? {rrand(60,140)};
var tala = argTala ?? {allTalas.choose};
var gati = argGati ?? {[4,3,5,7,9].choose};
var otherGatis = argOtherGatis ?? {[4,3,5,7,9].select({|item, i| item!=gati })};
^super.new.konaTaniInit(laya, tala, gati, otherGatis, argSynth);
}
konaTaniInit {|argLaya, argTala, argGati, argOtherGatis, argSynth|
var oneCycleDur;
laya = argLaya;
talaSym = argTala;
tala = this.setTala(talaSym);
gati = argGati;
otherGatis = argOtherGatis;
gen = KonaGenerator.new(this, laya, tala, gati);
oneCycleDur = tala.sum*(60/laya); //Duration of one cycle
store = KonaTime.new(this);
//Playback Variables
konaSynth = argSynth;
this.setPlayback;
//Setup tala and clapping Routine
this.makeTalaRout();
//Load SynthDefs
this.setSynthDefs;
}
//Convert the tala from symbols to numbers
setTala {|aTala|
var ret = List[];
if(aTala.every { |item, i| item.class==Integer}) {
^aTala;
} {
aTala.do { |item, i|
switch (item[0].asSymbol)
{'I'} {ret.add(item[1].digit)}
{'O'} {ret.add(2)}
{'U'} {
switch ((item[1]!=nil).and({item[1].digit}) )
{1} {ret.add(0.5)}
{false} {ret.add(1)};
}
{'W'} {ret.add(0.5)}
{'R'} {ret.add(0.5)};
};
};
^ret.asArray;
}
//Setup playback variables
setPlayback {
s = Server.default;
clock = TempoClock.new(laya/60);
//Array of buffers for FFT
fftBuffArray = Array.fill(10, {Buffer.alloc(s, 1024)});
//Syllables Array
syls = ['Tam', 'Ta', 'Ka', 'Ki', 'Tah', 'Di', 'Mi', 'Da', 'Gi', 'Na',
'Dom', '-', 'Ju', 'Lan', 'Gu', 'Tom', 'Nam', 'Ri', 'Du', 'Din'];
//Buffers for PV analysis files
buffers=Array.newClear(syls.size);
buffers.size.do({|i| buffers[i] = Buffer.read(s, "sounds/Solkattu/"++syls[i]++".wav.scpv")});
//FFT Buffers and Routine
fftBuff=fftBuffArray[0];
fftRout = Routine.new({
inf.do({|i|
fftBuff = fftBuffArray.wrapAt(i);
0.yield;
})
});
// //MIDI
// MIDIClient.init(1,1);
// mOut = MIDIOut.newByName("loopMIDI Port", "loopMIDI Port 1");
}
//Setup SynthDefs
setSynthDefs {
//Default SynthDef
SynthDef(\konaHit, { arg out=0, bufnum=0, recBuf=1, rate=1, amp=0.8;
var chain, signal;
chain = PV_PlayBuf(bufnum, recBuf, rate, 0, 0);
signal = IFFT(chain, 1)*amp;
DetectSilence.ar(signal, doneAction:2);
Out.ar(out, signal.dup);
}).load(s);
// Clapping SynthDef by Thor Magnusson
SynthDef(\clapping, {arg t_trig=1, amp=0.5, filterfreq=100, rq=0.1;
var env, signal, attack, noise, hpf1, hpf2;
noise = WhiteNoise.ar(1)+SinOsc.ar([filterfreq/2,filterfreq/2+4 ],pi*0.5, XLine.kr(1,0.01,4));
hpf1 = RLPF.ar(noise, filterfreq, rq);
hpf2 = RHPF.ar(noise, filterfreq/2, rq/4);
env = EnvGen.kr(Env.perc(0.003, 0.00035));
signal = (hpf1+hpf2) * env;
signal = CombC.ar(signal, 0.5, 0.03, 0.031)+CombC.ar(signal, 0.5,0.03016, 0.06);
//signal = FreeVerb.ar(signal, 0.23, 0.15, 0.2);
signal = Limiter.ar(signal, 0.7, 0.01);
Out.ar(0, Pan2.ar(signal*amp, 0));
DetectSilence.ar(signal, doneAction:2);
}).load(s)
}
//Playback the contained piece of music with the tala cycle
play {
talaRout.reset;
pRout.reset;
talaRout.play(clock);
{
((60/laya)*(tala.sum)).wait;
pRout.play(clock);
}.fork
}
//Stop routine playback
stop {
//this.clock.stop;
talaRout.stop;
pRout.stop;
}
//Generate the clapping routine for the Tala
makeTalaRout {
var func = {|amp1, amp2, freq1, freq2, rq| s.bind {Synth(\clapping, [\amp,rrand(amp1, amp2), \filterfreq, rrand(freq1, freq2), \rq, rq.rand]) }};
talaRout = Routine {
inf.do {
talaSym.do { |item, i|
switch (item[0].asSymbol)
{'I'} {
//Clap
func.(0.4, 0.5, 2000, 2500, 0.9);
1.wait;
//Finger Taps
(item[1].digit-1).do { |j|
func.(0.01, 0.05, 6000, 7000, 0.9);
1.wait;
};
}
{'O'} {
//Clap
func.(0.4, 0.5, 2000, 2500, 0.9);
1.wait;
//Back of hand / wave
func.(0.01, 0.03, 400, 600, 0.9);
1.wait;
}
{'U'} {
//Clap
func.(0.4, 0.5, 2000, 2500, 0.9);
switch ((item[1]!=nil).and({item[1].digit}) )
{1} {0.5.wait}
{false} {1.wait};
}
{'W'} {
//Wave
func.(0.3, 0.4, 400, 600, 0.9);
0.5.wait;
}
{'R'} {
//Rest
0.5.wait;
};
};
};
};
}
//Getter for the music routine
rout {
^store.rout;
}
//Add an item to this instance's KonaTime
add {|aKonaItem|
store.add(aKonaItem);
pRout = store.rout;
}
//Add a collection of items to this instance's KonaTime
addAll { |argCollection|
store.addAll(argCollection);
pRout = store.rout;
}
//Clear this instance's KonaTime
clear {
store = KonaTime.new(this);
pRout = store.rout;
}
//Setter method for the gati
gati_{|aGati|
gati = aGati;
gen.gati = aGati;
}
}