b700ish icon indicating copy to clipboard operation
b700ish copied to clipboard

Keyboard functionality

Open unculcated opened this issue 6 months ago • 0 comments

I added keyboard functionality

( // This is b700ish // A vague attempt to implement a rough guess of the // Buchla 700 voice architecture in Supercollider // Aaron Lanterman, October 21, 2020

// Make sure you also have b700ish_patches.scd file. // Also, find the line that reads // "/Users/lanterma/buchla700_supercollider/b700ish_patches.scd".load; // and change it to an appropriate path on your machine.

// Execute this whole-file code block to start. // There's a bug where you need to manually select a patch from the patch menu before sound will start. // The Patch drop-down menu is in the upper right corner; I think any patch will do. // You can use a MIDI controller or the key buttons in the lower right corner of the GUI. s.boot; s.scope; FreqScope.new;

~trianglePink = Color(255/255,0,170/255,1); ~circleBlue = Color(0,170/255,255/255,1); ~squareOrange = Color(255/255,170/255,0,1); ~squareGreen = Color(0,255/255,170/255,1);

~envTitleStrings = ["Idx1","Idx2","Idx3","Idx4","Idx5","Idx6", "LvlA", "LvlB", "Filt", "Res"]; ~envTitleColors = (~trianglePink ! 6) ++ [~squareOrange] ++ [~squareGreen] ++ (Color.black ! 2); ~numberEnvs = ~envTitleStrings.size;

~idx1eno = 0; ~idx2eno = 1; ~idx3eno = 2; ~idx4eno = 3; ~idx5eno = 4; ~idx6eno = 5; ~levelAeno = 6; ~levelBeno = 7; ~filteno = 8; ~reseno = 9;

"/Users/ss/Downloads/b700ish-main/b700ish_patches.scd".load; if (~currentPatch.isNil,{~currentPatch=0}); ~instruments[~currentPatch].value;

~chebyNames = Array.new; ~chebyCoefs = Array.new; ~chebyNames = ~chebyNames.add("Default"); ~chebyCoefs = ~chebyCoefs.add([1]); ~chebyNames = ~chebyNames.add("True Triangle "); ~chebyCoefs = ~chebyCoefs.add([1,0] / ((1..32).squared)); ~chebyNames = ~chebyNames.add("Square-Compatible Triangle"); ~chebyCoefs = ~chebyCoefs.add([1,0,-1,0] / ((1..32).squared)); ~chebyNames = ~chebyNames.add("Jimmy Smith (All Positive)"); ~chebyCoefs = ~chebyCoefs.add([1,1,1]); ~chebyNames = ~chebyNames.add("Jimmy Smith"); ~chebyCoefs = ~chebyCoefs.add([1,1,-1]); ~chebyNames = ~chebyNames.add("Full Tonewheel"); ~chebyCoefs = ~chebyCoefs.add([1,1,-1,-1,0,1,0,-1,0,1,0,-1,0,0,0,1]); ~chebyNames = ~chebyNames.add("True Square"); ~chebyCoefs = ~chebyCoefs.add([1,0,-1,0] / (1..32)); ~chebyNames = ~chebyNames.add("Triangle-Compatible Square"); ~chebyCoefs = ~chebyCoefs.add([1,0] / (1..32)); ~chebyNames = ~chebyNames.add("Alt Saw"); ~chebyCoefs = ~chebyCoefs.add(0.25*[1,-1,-1,1] / (1..32)); ~chebyNames = ~chebyNames.add("Alt Impulse Train"); ~chebyCoefs = ~chebyCoefs.add(0.1*[1,-1,-1,1]Array.fill(32,1)); ~chebyNames = ~chebyNames.add("Alt Sign-Flipping Impulse Train"); ~chebyCoefs = ~chebyCoefs.add(0.1[1,0,-1,0]*Array.fill(32,1));

~wsdefaultsig = Signal.chebyFill(4096, [1], normalize: true, zeroOffset:false); ~wsdefault = ~wsdefaultsig.asWavetableNoWrap; ~wsdefaultbuf = Buffer.loadCollection(s, ~wsdefault);

SynthDef.new(\fbfm, { arg freq = 220, gate = 0, config = 1, offsets = #[0,0,10,0,0,10,10,10,10,0], numerators = #[1,1,1,1], denominators = #[1,1,1,1], cf = 0, wsAbuf = ~wsdefaultbuf, wsBbuf = ~wsdefaultbuf;

var cfreq = freq;
var po = LocalIn.ar(4);
var freqs = cfreq * numerators / denominators;

var out, oo, fmodinputs, tm2, tm5, wsAoutput, wsBoutput, wsMix;
var levelA, levelB, rawlevelA, rawlevelB;

var acousticVars,rawindexes,max_rawindex,io0,io1,io2,io3,io4,io5,io6;

var envidx1, envidx2, envidx3, envidx4, envidx5, envidx6;
var envfilt, envres, envlevelA, envlevelB;
var filtercontrol, rescontrol;

var envidx1trick = Env.newClear(8);
var envidx2trick  = Env.newClear(8);
var envidx3trick  = Env.newClear(8);
var envidx4trick  = Env.newClear(8);
var envidx5trick  = Env.newClear(8);
var envidx6trick  = Env.newClear(8);
var envfilttrick  = Env.newClear(8);
var envrestrick  = Env.newClear(8);
var envlevelAtrick  = Env.newClear(8);
var envlevelBtrick  = Env.newClear(8);

envidx1 = \env1.kr(envidx1trick.asArray);
envidx2 = \env2.kr(envidx2trick.asArray);
envidx3 = \env3.kr(envidx3trick.asArray);
envidx4 = \env4.kr(envidx4trick.asArray);
envidx5 = \env5.kr(envidx5trick.asArray);
envidx6 = \env6.kr(envidx6trick.asArray);
envlevelA = \envlA.kr(envlevelAtrick.asArray);
envlevelB = \envlB.kr(envlevelBtrick.asArray);
envfilt = \envf.kr(envfilttrick.asArray);
envres = \envr.kr(envrestrick.asArray);

acousticVars = Clip.kr(offsets.lag(0.5) +
   [EnvGen.kr(envidx1,gate), EnvGen.kr(envidx2,gate), EnvGen.kr(envidx3,gate),
    EnvGen.kr(envidx4,gate), EnvGen.kr(envidx5,gate), EnvGen.kr(envidx6,gate),
	EnvGen.kr(envlevelA,gate,doneAction: Done.freeSelf),
	EnvGen.kr(envlevelB,gate,doneAction: Done.freeSelf),
	EnvGen.kr(envfilt,gate), EnvGen.kr(envres,gate)],
	0,10);

filtercontrol = acousticVars[~filteno];
rescontrol = acousticVars[~reseno];
levelA = acousticVars[~levelAeno];
levelB = acousticVars[~levelBeno];

//rawindexes =
//   8*pi*(2**(((127*[acousticVars[~idx1eno],acousticVars[~idx2eno],acousticVars[~idx3eno],
//	                acousticVars[~idx4eno],acousticVars[~idx5eno],acousticVars[~idx6eno]]
 //             /10)-135)/8));

rawindexes = pi*(2**((33/16)-((100-(10*
	[acousticVars[~idx1eno],acousticVars[~idx2eno],acousticVars[~idx3eno],
     acousticVars[~idx4eno],acousticVars[~idx5eno],acousticVars[~idx6eno]]))/8)));

 max_rawindex = pi*(2**(33/16));

// index triangle outputs
 io0 = rawindexes[0] * Select.ar(config,
	    [po[2-1],po[2-1],po[2-1],po[1-1],po[1-1],po[1-1],
		 po[3-1],po[4-1],po[3-1],po[4-1],po[3-1],po[1-1]]);
 io1 = rawindexes[1] * Select.ar(config,
	    [po[4-1],po[2-1],po[3-1],po[3-1],po[2-1],po[4-1],
		 po[2-1],po[2-1],po[2-1],po[3-1],po[2-1],po[2-1]]);

 io3 = rawindexes[3] * Select.ar(config,
	    [po[2-1],po[4-1],po[4-1],po[2-1],po[3-1],po[3-1],
	     po[4-1],po[4-1],po[3-1],po[2-1],po[1-1],po[4-1]]);
 io4 = rawindexes[4] * Select.ar(config,
	    [po[4-1],po[4-1],po[1-1],po[3-1],po[4-1],po[2-1],
		 po[1-1],po[4-1],po[1-1],po[1-1],po[2-1],po[3-1]]);

// index triangles 3 and 6 (on images), i.e. 2 and 5 in code, only feed wavetables fmodinputs = [Select.ar(config,[io0,io0,io0,io1,io1,io1, io1,io1,io1,io0,io1,io4]), Select.ar(config,[Silent.ar,Silent.ar,io1,io4,io3,Silent.ar, io0,io0,io3,io1,Silent.ar,io0]), Select.ar(config,[io3,io3,io3,Silent.ar,Silent.ar,io4, Silent.ar,Silent.ar,Silent.ar,io3,io4,Silent.ar]), Select.ar(config,[Silent.ar,Silent.ar,io4,Silent.ar,Silent.ar,Silent.ar, Silent.ar,Silent.ar,Silent.ar,io4,Silent.ar,Silent.ar])];

fmodinputs[0] = fmodinputs[0] +
        Select.ar(config,[Silent.ar,Silent.ar,Silent.ar,Silent.ar,Silent.ar,Silent.ar,
                          io3,io3,Silent.ar,Silent.ar,Silent.ar,Silent.ar]);
fmodinputs[1] = fmodinputs[1] +
        Select.ar(config,[Silent.ar,Silent.ar,Silent.ar,Silent.ar,io4,Silent.ar,
                          Silent.ar,Silent.ar,Silent.ar,Silent.ar,Silent.ar,Silent.ar]);

// oscillator outputs
oo = SinOsc.ar(freqs,fmodinputs);

// index triangles 3 and 6 (on images), i.e. 2 and 5 in code, only feed wavetables
tm2 = Select.ar(config,[io1,io1,Silent.ar,io0,Silent.ar,io0,
	                    Silent.ar,Silent.ar,io0,Silent.ar,io0,io3]);
tm5 = Select.ar(config,[io4,io4,Silent.ar,io3,Silent.ar,io3,
	                    Silent.ar,io4,io4,Silent.ar,io3,io1]);

// Since the output of index triangles 3 and 6 (on images), i.e. 2 and 5 in code,
// doesn't involve any feedback mechanisms, we might as well use oo intead of po.
io2 = ((rawindexes[2] + tm2) / max_rawindex) *
      Select.ar(config,
     [oo[1-1],oo[1-1],oo[1-1],oo[4-1],oo[1-1],oo[2-1],
	  oo[1-1],oo[1-1],oo[1-1],oo[1-1],oo[1-1],oo[1-1]]);

io5 = ((rawindexes[5] + tm5) / max_rawindex) *
      Select.ar(config, [oo[3-1],oo[3-1],oo[3-1],oo[4-1],oo[1-1],oo[4-1],
	 	                 oo[1-1],oo[4-1],oo[4-1],oo[3-1],oo[3-1],oo[1-1]]);

LocalOut.ar(oo);
wsAoutput = Shaper.ar(wsAbuf, io2);
wsBoutput = Shaper.ar(wsBbuf, io5);

//rawlevelA = 8*pi*(2**(((127*levelA/10)-135)/8));
//rawlevelB = 8*pi*(2**(((127*levelB/10)-135)/8));

rawlevelA = pi*(2**((33/16)-((100-(10*levelA))/8)));
rawlevelB = pi*(2**((33/16)-((100-(10*levelB))/8)));

wsAoutput = (rawlevelA / max_rawindex) * wsAoutput;
wsBoutput = (rawlevelB / max_rawindex) * wsBoutput;
wsMix = (wsBoutput * cf) + (wsAoutput * (1 - cf));
out = wsMix;
out = MoogFF.ar(wsMix, 20*(2**filtercontrol), 4 * rescontrol/10);
Out.ar(0, 0.2*[out, out]);

}).add;

///////////////

MIDIClient.init; MIDIIn.connectAll;

~notes = Array.newClear(128); // array has one slot per possible MIDI note

~startNote = { arg velocity, noteNumber; ~vel = velocity / 127; ~nn = noteNumber; ~genvstr.do({arg item, i; var realenv = try {item.compile.value;} {arg error; }; if(realenv.isKindOf(Env), {~genv[i] = realenv; {~envtitles[i].stringColor = ~darkGreen;}.defer; }, {~genv[i] = nil; postln("Charm"); {~envtitles[i].stringColor = Color.red;}.defer;} ); }); ~notes[noteNumber] = Synth.new(\fbfm, [\freq, (noteNumber).midicps, \gate, 1, \config, ~gconfig, \offsets, ~goffsets, \numerators, ~gnumerators, \denominators, ~gdenominators, \cf, ~gcf, \wsAbuf, ~gwsAbuf, \wsBbuf, ~gwsBbuf, // it seems like SC doesn't want me to pass in ~genvinv all at once, // as an array; when compiling the synthdef, it complains that // the "message 'at' is not understood" -- not sure why... \env1, ~genv[~idx1eno], \env2, ~genv[~idx2eno], \env3, ~genv[~idx3eno], \env4, ~genv[~idx4eno], \env5, ~genv[~idx5eno], \env6, ~genv[~idx6eno], \envlA, ~genv[~levelAeno], \envlB, ~genv[~levelBeno], \envf, ~genv[~filteno], \envr, ~genv[~reseno] ]); ~notes[noteNumber].register; };

~stopNote = { arg velocity, noteNumber; ~notes[noteNumber].set(\gate, 0); ~notes[noteNumber] = nil; };

MIDIdef.noteOn(\noteOnTest,{ arg velocity, noteNumber, chan, src; ~startNote.value(velocity, noteNumber)});

MIDIdef.noteOff(\noteOffTest, { arg velocity, noteNumber, chan, src; ~stopNote.value(velocity, noteNumber); });

~updateGUI = { ~cfimages = Array.fill(12, {arg i; Image.new("/Users/ss/Downloads/b700ish-main/fm_config_c_" ++ i.asStringToBase(width: 2) ++ ".png"); });

if(w.notNil, {if(w.isClosed, {~currentBounds = Rect(0,540,700,600)}, {~currentBounds = w.bounds; ~currentBounds.top = ~currentBounds.top+22; });}, {~currentBounds = Rect(0,540,700,600)});

Window.closeAll; ~forceOnTop = false; w = Window("B700ish",~currentBounds,resizable: false).front.alwaysOnTop_(~forceOnTop);

~cfknob = Knob(w,Rect(330,110,32,32)) .action_({arg obj; ~notes.do({arg item; ~gcf = obj.value; if (item.isPlaying, {item.set(\cf,~gcf);}); }); }) .value = ~gcf;

StaticText(w, Rect(165,150,160,40)).string_("Frequency Ratios:");

~numtextboxes = Array.fill(4, {arg i; var tf = TextField(w,Rect(165+(40*i),182,37,22)) .action_({arg obj; var vaf = obj.value.asFloat; if(vaf != 0, {~gnumerators[i] = vaf; ~notes.do({arg item; if(item.isPlaying, {item.seti(\numerators,i,vaf);}); }) }); obj.value = ~gnumerators[i].asString; }); tf.string = ~gnumerators[i].asString; tf; });

~denomtextboxes = Array.fill(4, {arg i; var tf = TextField(w,Rect(165+(40*i),207,37,22)) .action_({arg obj; var vaf = obj.value.asFloat; if(vaf != 0, {~gdenominators[i] = vaf; ~notes.do({arg item; if(item.isPlaying, {item.seti(\denominators,i,vaf);}); }) }); obj.value = ~gdenominators[i].asString; }); tf.string = ~gdenominators[i].asString; tf; });

w.drawFunc_({~cfimages[~gconfig].tileInRect(Rect(10,182,128,128))}); w.refresh;

StaticText(w, Rect(10,150,160,40)).string_("Config:");

~configselect = PopUpMenu(w, Rect(60, 160, 40, 20)); ~configselect.items = Array.series(12); ~configselect.value = ~gconfig; ~configselect.action = { arg obj; ~gconfig = obj.value; w.drawFunc_({~cfimages[~gconfig].tileInRect(Rect(10,182,128,128))}); w.refresh; // need this so new image appears ~notes.do({arg item; if(item.isPlaying, {item.set(\config,~gconfig);}) }); };

~setupAbuf = { arg selection; ~wsAsig = Signal.chebyFill(4096, ~chebyCoefs[selection], normalize: true, zeroOffset:false); ~wsA = ~wsAsig.asWavetableNoWrap; ~gwsAbuf = Buffer.loadCollection(s, ~wsA); };

~setupBbuf = { arg selection; ~wsBsig = Signal.chebyFill(4096, ~chebyCoefs[selection], normalize: true, zeroOffset:false); ~wsB = ~wsBsig.asWavetableNoWrap; ~gwsBbuf = Buffer.loadCollection(s, ~wsB); };

~envtitles = Array.fill(~numberEnvs, {arg i; StaticText(w, Rect(5,310+(25*i),40,40)).string_(~envTitleStrings[i] ++ ":").align_(\left) });

~darkGreen = Color(0,0.5,0,1); ~genv = Array.fill(~numberEnvs); ~envtextboxes = Array.fill(~numberEnvs, {arg i; var realenv = nil; var tf = TextField(w,Rect(40,320+(25*i),300,22)) .action_({arg obj; var realenv = nil; ~vel = 1; ~nn = 60; realenv = try {obj.value.compile.value;} {arg error; }; ~genvstr[i] = obj.value; if(realenv.isKindOf(Env), {~genv[i] = realenv; ~envtitles[i].stringColor = ~darkGreen;}, {~genv[i] = nil; ~envtitles[i].stringColor = Color.red;} ); }); ~genvstr; tf.value = ~genvstr[i]; ~vel = 1; ~nn = 60; realenv = try {~genvstr[i].compile.value;} {arg error; }; if(realenv.isKindOf(Env), {~genv[i] = realenv; ~envtitles[i].stringColor = ~darkGreen; }, {~genv[i] = nil; ~envtitles[i].stringColor = Color.red;} ); tf; }); ~goffsets[1]; //8.4

~offsetSliders = Array.fill(~numberEnvs, {arg i; Slider(w,Rect(10+(32i),6,20,128)) .action_({arg obj; ~goffsets[i] = 10(obj.value); ~notes.do({arg item; if (item.isPlaying, {item.seti(\offsets,i,~goffsets[i]);} ); }); }).knobColor_(~envTitleColors[i]).value = (~goffsets[i] / 10); StaticText(w, Rect(0+(32*i),120,40,40)).string_(~envTitleStrings[i]).align_(\center); });

~setupAbuf.value(~gwsAselect); ~setupBbuf.value(~gwsBselect);

~makeAplots = { ~plotterA = Plotter("",Rect(350,182,190,100),w); ~plotterA.plotColor_(Color.red); ~plotterA.value = ~wsAsig; ~plotterAsinin = Plotter("",Rect(550,182,100,100),w); ~plotterAsinin.plotColor_(Color.red); // There has to be a more elegant way to do this... ~plotterAsinin.value = Array.fill(100,{arg n; ~wsAsig[(~wsAsig.size - 1) * (sin(2pin / 100)+1)/2]}); };

~makeBplots = { ~plotterB = Plotter("",Rect(350,310,190,100),w); ~plotterB.plotColor_(Color.green); ~plotterB.value = ~wsBsig; ~plotterBsinin = Plotter("",Rect(550,310,100,100),w); ~plotterBsinin.plotColor_(Color.green); // There has to be a more elegant way to do this... ~plotterBsinin.value = Array.fill(100,{arg n; ~wsBsig[(~wsBsig.size - 1) * (sin(2pin / 100)+1)/2]}); };

~makeAplots.value(); ~makeBplots.value();

StaticText(w, Rect(350,150,160,40)).string_("WaSh A:"); ~wsAselect = PopUpMenu(w, Rect(408, 160, 130,20)); ~wsAselect.items = ~chebyNames; ~wsAselect.value = ~gwsAselect; ~wsAselect.action = { arg obj; ~gwsAselect = obj.value; ~setupAbuf.value(~gwsAselect); ~makeAplots.value(); };

StaticText(w, Rect(350,278,160,40)).string_("WaSh B:"); ~wsBselect = PopUpMenu(w, Rect(408, 288, 130, 20)); ~wsBselect.items = ~chebyNames; ~wsBselect.value = ~gwsBselect; ~wsBselect.action = { arg obj; ~gwsBselect = obj.value; ~setupBbuf.value(~gwsBselect); ~makeBplots.value(); };

StaticText(w, Rect(350,50,160,40)).string_("Patch:"); ~patchselect = PopUpMenu(w, Rect(395, 60, 250,20)); ~patchselect.items = ~instrumentNames; ~patchselect.value = ~currentPatch; ~patchselect.action = { arg obj; ~currentPatch = obj.value; ~instruments[~currentPatch].value; ~updateGUI.value; };

~keysrow1 = Array.fill(13, {arg i; Button(w, Rect(350+(22*i), 430, 20, 40)) .states_([["", Color.white, Color.white]]) .mouseDownAction_({~startNote.value(127, 36+i)}) .mouseLeaveAction_({~stopNote.value(127, 36+i)}) .action_({~stopNote.value(127, 36+i)}); });

~keysrow2 = Array.fill(13, {arg i; Button(w, Rect(350+(22*i), 472, 20, 40)) .states_([["", Color.white, Color.white]]) .mouseDownAction_({~startNote.value(127, 12+36+i)}) .mouseLeaveAction_({~stopNote.value(127, 12+36+i)}) .action_({~stopNote.value(127, 12+36+i)}); });

~keysrow2 = Array.fill(13, {arg i; Button(w, Rect(350+(22*i), 514, 20, 40)) .states_([["", Color.white, Color.white]]) .mouseDownAction_({~startNote.value(127, 24+36+i)}) .mouseLeaveAction_({~stopNote.value(127, 24+36+i)}) .action_({~stopNote.value(127, 24+36+i)}); });

~keyboardActiveButton = Button(w, Rect(350, 560, 150, 30)) .states_([ ["Activate Keyboard", Color.black, Color.gray], ["Deactivate Keyboard", Color.white, Color.red] ]) .action_({ |butt| ~keyboardActive = butt.value == 1; });

~keyboardActive = false; ~keyboardActiveButton = Button(w, Rect(350, 560, 150, 30)) .states_([ ["Activate Keyboard", Color.black, Color.gray], ["Deactivate Keyboard", Color.white, Color.red] ]) .action_({ |butt| ~keyboardActive = butt.value == 1; });

~keyboardActive = false;

~keyboardNoteMap = Dictionary[
    $a -> 60, $w -> 61, $s -> 62, $e -> 63, $d -> 64, $f -> 65, $t -> 66,
    $g -> 67, $y -> 68, $h -> 69, $u -> 70, $j -> 71, $k -> 72, $o -> 73,
    $l -> 74, $p -> 75, $; -> 76
];

~keyboardNoteOn = { |char|
    var note = ~keyboardNoteMap[char];
    if(note.notNil, {
        ~startNote.value(127, note);
    });
};

~keyboardNoteOff = { |char|
    var note = ~keyboardNoteMap[char];
    if(note.notNil, {
        ~stopNote.value(0, note);
    });
};

w.view.keyDownAction = { |view, char, modifiers, unicode, keycode|
    if(~keyboardActive, {
        ~keyboardNoteOn.(char);
    });
};

w.view.keyUpAction = { |view, char, modifiers, unicode, keycode|
    if(~keyboardActive, {
        ~keyboardNoteOff.(char);
    });
};

};

~updateGUI.value; )

unculcated avatar Aug 22 '24 08:08 unculcated