mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 17:50:22 +00:00
286 lines
6.0 KiB
Python
286 lines
6.0 KiB
Python
WarpTate {
|
|
classvar <numSections = 8;
|
|
// classvar <sectionDur = 3 * 60;
|
|
classvar <sectionDur = 60;
|
|
|
|
var <sensorKeys;
|
|
var <clock;
|
|
var <tempo;
|
|
var <>tempoChannel;
|
|
var <>tempoControl;
|
|
var <out;
|
|
var <tracks;
|
|
var <>sections;
|
|
var <availableControls;
|
|
var <controls;
|
|
var <sensorVals;
|
|
var <sensorPrevs;
|
|
var <sensorMins;
|
|
var <sensorMaxs;
|
|
var <>sensorMinAdj;
|
|
var <>sensorMaxAdj;
|
|
var <doAdjusts;
|
|
var <playRout;
|
|
|
|
*new {
|
|
^super.new.init;
|
|
}
|
|
|
|
init {
|
|
tempo = 120;
|
|
tempoChannel = 15;
|
|
tempoControl = 3;
|
|
clock = TempoClock.default
|
|
.tempo_(2)
|
|
.permanent_(true);
|
|
|
|
MIDIClient.init;
|
|
out = MIDIOut.newByName("IAC Driver", "Bus 1");
|
|
out.latency = 0;
|
|
|
|
tracks = IdentityDictionary[];
|
|
sections = Array.newClear(WarpTate.numSections);
|
|
// sections is a List of IdentityDictionary to be mapped to
|
|
// WarpTrack settings var
|
|
// e.g. List[IdentityDictionary[
|
|
// '303_1' -> IdentityDictionary['notes' -> List[42]],
|
|
// '808_1' -> IdentityDictionary['notes' -> List[24]]
|
|
// ]
|
|
// ]
|
|
sensorKeys = ['303a', '303b', '808a', '808b'];
|
|
|
|
// channel 16 reserved for tempo changes
|
|
availableControls = 15.collect {|channel|
|
|
(0..120).reject({|item, i|
|
|
[ 0, 1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 64, 65, 66, 67, 68,
|
|
69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 84, 91, 92, 93, 94,
|
|
95, 96, 97, 98, 99, 100, 102 ].includes(item)
|
|
});
|
|
};
|
|
|
|
controls = (0..127).collect { IdentityDictionary[] };
|
|
|
|
this.addOSCdefs();
|
|
|
|
CmdPeriod.add({this.stop});
|
|
}
|
|
|
|
tempo_ {|argTempo|
|
|
if(argTempo >= 50 && argTempo <= 177) {
|
|
tempo = argTempo;
|
|
clock.tempo = tempo / 60;
|
|
|
|
out.control(tempoChannel, tempoControl, tempo - 50);
|
|
} {
|
|
"Tempo out of Logic's range :(".postln;
|
|
};
|
|
}
|
|
|
|
addTrack {|trackKey, channel, type|
|
|
tracks[trackKey] = WarpTrack(this, trackKey, channel, type);
|
|
^tracks[trackKey];
|
|
}
|
|
|
|
loadTrack {|preset, checkAvailable|
|
|
var track = WarpTrack.load(this, preset, checkAvailable),
|
|
trackKey = track.settings['key'];
|
|
|
|
tracks[trackKey] = track;
|
|
^tracks[trackKey];
|
|
}
|
|
|
|
readTrack {|path|
|
|
var track = WarpTrack.read(this, path),
|
|
trackKey = track.settings['key'];
|
|
|
|
tracks[trackKey] = track;
|
|
^tracks[trackKey];
|
|
}
|
|
|
|
removeTrack {|trackKey|
|
|
tracks[trackKey].allOff();
|
|
tracks[trackKey] = nil;
|
|
}
|
|
|
|
removeAllTracks {
|
|
tracks = IdentityDictionary[];
|
|
}
|
|
|
|
on {|trackKey, note|
|
|
tracks[trackKey].on(note);
|
|
}
|
|
|
|
off {|trackKey, note|
|
|
tracks[trackKey].off(note);
|
|
}
|
|
|
|
hit {|trackKey, note=60, vel=127, dur=1|
|
|
tracks[trackKey].hit(note, vel, dur);
|
|
}
|
|
|
|
noteOn {|midiChannel, note, vel|
|
|
out.noteOn(midiChannel, note, vel);
|
|
}
|
|
|
|
noteOff {|midiChannel, note, vel|
|
|
out.noteOff(midiChannel, note, vel);
|
|
}
|
|
|
|
control {|midiChannel, num, val|
|
|
out.control(midiChannel, num, val);
|
|
}
|
|
|
|
isControlAvailable {|channel, controlNum|
|
|
^controls[channel].keys.includes(controlNum.asSymbol).not;
|
|
}
|
|
|
|
setControl {|channel, num, key|
|
|
controls[channel][num.asSymbol] = key;
|
|
availableControls[channel].remove(num);
|
|
}
|
|
|
|
assign {|trackKey, paramKey, controlNum, learn=false|
|
|
var channel = tracks[trackKey].settings['midiChannel'];
|
|
|
|
if(controlNum.notNil && this.isControlAvailable(channel, controlNum)) {
|
|
tracks[trackKey].assign(paramKey, controlNum, learn);
|
|
availableControls[channel].removeAt(0);
|
|
|
|
} {
|
|
if(availableControls[channel].size > 0) {
|
|
tracks[trackKey].assign(paramKey, availableControls[channel][0], learn);
|
|
availableControls[channel].removeAt(0);
|
|
} {
|
|
"no controls left!".postln;
|
|
};
|
|
};
|
|
|
|
"Don't forget to turn off MIDI learn".postln
|
|
}
|
|
|
|
setParam {|trackKey, paramKey, val|
|
|
var track, param;
|
|
|
|
if((track = tracks[trackKey]).notNil) {
|
|
if(track.params[paramKey].notNil) {
|
|
track.setParam(paramKey, val);
|
|
} {
|
|
"paramKey doesn't exist".postln;
|
|
};
|
|
} {
|
|
"track key doesn't exist".postln;
|
|
};
|
|
}
|
|
|
|
// sec takes IdentityDictionarys of WarpTrack settings
|
|
addSection {|index, tempo=120, presets|
|
|
// Add section with tempo
|
|
sections[index] = IdentityDictionary[
|
|
'tempo' -> tempo,
|
|
'tracks' -> presets
|
|
];
|
|
|
|
presets.do {|preset, i|
|
|
// create track if there ain't one with this key
|
|
if(tracks.includesKey(preset['key']).not) {
|
|
this.loadTrack(preset, true);
|
|
};
|
|
|
|
// store the preset
|
|
// sections[index]['tracks'][preset['key']] = preset;
|
|
};
|
|
|
|
^sections;
|
|
}
|
|
|
|
play {
|
|
|
|
if(sections.any {|item, i| item.notNil; }) {
|
|
playRout = Routine {
|
|
inf.do {|i|
|
|
sections.do {|section|
|
|
if(section.notNil) {
|
|
if(i !== 0) {
|
|
tracks.do {|track|
|
|
track.allOff();
|
|
};
|
|
this.removeAllTracks();
|
|
};
|
|
|
|
this.tempo = section['tempo'];
|
|
|
|
section['tracks'].do {|track, i|
|
|
var newTrack = this.loadTrack(track, false);
|
|
newTrack.play();
|
|
};
|
|
sectionDur.wait;
|
|
};
|
|
};
|
|
}
|
|
};
|
|
clock.playNextBar(playRout);
|
|
} {
|
|
"no sections added!".postln;
|
|
};
|
|
|
|
}
|
|
|
|
stop {
|
|
playRout.stop;
|
|
|
|
tracks.do {|track|
|
|
track.allOff();
|
|
out.allNotesOff(track.settings['midiChannel']);
|
|
}
|
|
}
|
|
|
|
addOSCdefs {
|
|
sensorVals = 0!sensorKeys.size;
|
|
sensorPrevs = 0!sensorKeys.size;
|
|
sensorMins = 9999!sensorKeys.size;
|
|
sensorMaxs = 0!sensorKeys.size;
|
|
sensorMinAdj = sensorMinAdj ?? { 0.005 };
|
|
sensorMaxAdj = sensorMaxAdj ?? { 0.01 };
|
|
doAdjusts = false!sensorKeys.size;
|
|
|
|
sensorKeys.do {|sensorKey, i|
|
|
OSCdef(("sensor_" ++ sensorKey).asSymbol, {|msg, time, addr, recvPort|
|
|
var val = msg[1];
|
|
|
|
|
|
sensorPrevs[i] = sensorVals[i];
|
|
sensorVals[i] = val;
|
|
|
|
if(doAdjusts[i]) {
|
|
sensorMins[i] = min(val, sensorMins[i]);
|
|
sensorMaxs[i] = max(val, sensorMaxs[i]);
|
|
|
|
if(val < sensorMaxs[i]) {
|
|
sensorMaxs[i] = sensorMaxs[i] - sensorMaxAdj;
|
|
};
|
|
|
|
if(val > sensorMins[i]) {
|
|
sensorMins[i] = sensorMins[i] + sensorMinAdj;
|
|
};
|
|
} {
|
|
val = val.clip(sensorMins[i], sensorMaxs[i]);
|
|
};
|
|
|
|
|
|
tracks.do {|track, j|
|
|
if(track.settings['sensorFuncs'].includesKey(sensorKey)) {
|
|
track.sensor(
|
|
sensorKey,
|
|
val.linlin(
|
|
sensorMins[i],
|
|
sensorMaxs[i],
|
|
127,
|
|
0
|
|
)
|
|
);
|
|
};
|
|
}
|
|
}, ("/prox/" ++ sensorKey).asSymbol);
|
|
}
|
|
}
|
|
} |