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);
 | 
						|
		}
 | 
						|
	}
 | 
						|
} |