From b6a9993c97f67de303aeb41aa085c79642ab6326 Mon Sep 17 00:00:00 2001 From: Paul Chaignon Date: Wed, 22 Oct 2014 10:16:30 -0400 Subject: [PATCH] Add .sc as a SuperCollider file extension --- lib/linguist/languages.yml | 1 + samples/SuperCollider/WarpPreset.sc | 19 ++ samples/SuperCollider/WarpTate.sc | 286 ++++++++++++++++++++++++++++ samples/SuperCollider/WarpTrack.sc | 263 +++++++++++++++++++++++++ samples/SuperCollider/WarpUtil.sc | 127 ++++++++++++ 5 files changed, 696 insertions(+) create mode 100644 samples/SuperCollider/WarpPreset.sc create mode 100644 samples/SuperCollider/WarpTate.sc create mode 100644 samples/SuperCollider/WarpTrack.sc create mode 100644 samples/SuperCollider/WarpUtil.sc diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 56032ebb..bf05f7b8 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -2299,6 +2299,7 @@ SuperCollider: lexer: Text only extensions: - .scd + - .sc Swift: type: programming diff --git a/samples/SuperCollider/WarpPreset.sc b/samples/SuperCollider/WarpPreset.sc new file mode 100644 index 00000000..93aed4e7 --- /dev/null +++ b/samples/SuperCollider/WarpPreset.sc @@ -0,0 +1,19 @@ +WarpPreset { + *new {|path| + if(path.notNil) { + ^Object.readArchive(path); + }; + + ^super.new.init(); + } + + init { + + } + + save { + Dialog.savePanel({|path| + this.writeArchive(path); + }); + } +} \ No newline at end of file diff --git a/samples/SuperCollider/WarpTate.sc b/samples/SuperCollider/WarpTate.sc new file mode 100644 index 00000000..73c09e6d --- /dev/null +++ b/samples/SuperCollider/WarpTate.sc @@ -0,0 +1,286 @@ +WarpTate { + classvar tempoChannel; + var <>tempoControl; + var sections; + var sensorMinAdj; + var <>sensorMaxAdj; + var 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); + } + } +} \ No newline at end of file diff --git a/samples/SuperCollider/WarpTrack.sc b/samples/SuperCollider/WarpTrack.sc new file mode 100644 index 00000000..d7521353 --- /dev/null +++ b/samples/SuperCollider/WarpTrack.sc @@ -0,0 +1,263 @@ +WarpTrack { + classvar argKey, + 'midiChannel' -> argMidiChannel, + 'notes' -> Set[], + 'params' -> IdentityDictionary[], + 'paramControls' -> IdentityDictionary[], + 'patternTrack' -> true, + 'sensorFuncs' -> IdentityDictionary[] + ]; + + if(argType.notNil) { + settings['type'] = argType; + if(WarpTrack.defaults.keys.includes(argType)) { + ['paramControls', 'params'].do {|key, i| + settings[key] = WarpTrack.defaults[argType][key]; + } + }; + + this.initParams(); + }; + } + + on {|note| + var clock = parent.clock; + var sub = 1 / (parent.clock.tempo * 16); // one sub division + + clock.schedAbs(clock.nextBar - sub, { + if(settings['patternTrack']) { + this.allOff(); + } { + this.off(note); + }; + }); + + parent.clock.playNextBar({ + settings['notes'].add(note); + parent.noteOn(settings['midiChannel'], note, 127); + }); + } + + off {|note| + // settings['notes'].remove(note); + parent.noteOff(settings['midiChannel'], note, 0); + } + + hit {|note=60, vel=127, dur=1, quant=0| + { + parent.noteOn(settings['midiChannel'], note, vel); + dur.wait; + parent.noteOff(settings['midiChannel'], note, vel); + }.fork(parent.clock, quant:quant); + } + + allOff { + settings['notes'].do {|note, i| + this.off(note); + }; + } + + assign {|paramKey, num, learn=false, init=true, checkAvailable=true| + if(num.notNil) { + this.assignAll(IdentityDictionary[paramKey -> num], learn, init, checkAvailable); + } { + parent.assign(settings['key'], paramKey, nil, learn); + }; + } + + assignAll {|paramControls, learn=false, init=true, checkAvailable=true| + var action = { + var channel = settings['midiChannel']; + + paramControls.keysValuesDo { |paramKey, num| + if(checkAvailable.not || + (checkAvailable && parent.isControlAvailable(channel, num))) { + settings['paramControls'][paramKey] = num; + if(init) { + settings['params'][paramKey] = 0; + }; + parent.setControl(channel, num, paramKey); + if(learn) { + parent.control(channel, num, 127); + 0.05.wait; + parent.control(channel, num, 0); + }; + paramKey ++ " assigned to controlNum " ++ num; + } { + ("this controlNum " ++ num ++ " is already assigned!").postln; + }; + }; + }; + + if(learn) { + action.fork; + } { + action.(); + }; + + } + + initParams { + settings['params'].keysValuesDo { |key, value| + this.setParam(key, value); + }; + settings['paramControls'].keysValuesDo { |key, value| + parent.setControl(settings['midiChannel'], value, key); + }; + } + + setParam {|paramKey, val, quant| + var func = { + parent.control( + settings['midiChannel'], + settings['paramControls'][paramKey], + val + ); + + settings['params'][paramKey] = val; + }; + + if(quant.notNil) { + { + func.(); + }.fork(parent.clock, quant:quant); + } { + func.(); + }; + } + + readPreset {|path, checkAvailable=true| + this.loadPreset(Object.readArchive(path), checkAvailable); + } + + loadPreset {|preset, checkAvailable=true| + // copy all settings except notes and paramControls + preset.keys.reject({|settingKey, i| + ['notes', 'paramControls'].includes(settingKey); + }).do {|presetKey, i| + settings[presetKey] = preset[presetKey]; + }; + + // copy notes if it's a patternTrack + if(preset['patternTrack']) { + settings['notes'] = preset['notes']; + }; + + // assign all without learn or init + this.assignAll( + preset['paramControls'], + false, + false, + checkAvailable + ); + + this.initParams(); + + // if(settings['notes'].size > 0) { + // this.on(settings['notes'].asArray[0]); + // }; + } + + sensor {|sensorKey, val| + settings['sensorFuncs'][sensorKey].do {|func, i| + func.(this, val); + } + } + + addFunc {|sensorKey, funcKey, func| + if(parent.sensorKeys.includes(sensorKey)) { + if(settings['sensorFuncs'].includesKey(sensorKey).not) { + settings['sensorFuncs'][sensorKey] = IdentityDictionary[]; + }; + + settings['sensorFuncs'][sensorKey][funcKey] = func; + } { + "parent doesn't have that sensor key".postln; + }; + } + + removeFunc {|sensorKey, funcKey| + settings['sensorFuncs'][sensorKey].removeAt(funcKey); + + if(settings['sensorFuncs'][sensorKey].isEmpty) { + settings['sensorFuncs'].removeAt(sensorKey); + }; + } + + availableControls { + ^parent.availableControls[settings['midiChannel']].copy; + } + + save { + Dialog.savePanel({|path| + settings.writeArchive(path); + }); + } + + play { + if(settings['patternTrack'] && settings['notes'].notEmpty) { + this.on(settings['notes'].choose); + }; + } +} diff --git a/samples/SuperCollider/WarpUtil.sc b/samples/SuperCollider/WarpUtil.sc new file mode 100644 index 00000000..2d5d8ff9 --- /dev/null +++ b/samples/SuperCollider/WarpUtil.sc @@ -0,0 +1,127 @@ +WarpUtil { + var <>parent; + var