I have a little app in SC that has a Keyboard at the top.
It is cumbersome to add effects and corresponding sliders and other gui widgets. I thought maybe going the way of Jit with its auto Gui ability would be a better way. I just don’t know of a way to add keyboard control.
this is the app I have going currently. It’s nice for making sounds for sampling. Or just banging out little melodies for loops. But Id like to be able to add effects or remove them at will, and it’s hard the way it is set up now to do that.
I am using the PortedPlugins for some things.
(
// Define global variables for buses and synths
~samplerBus = Bus.audio(s, 2);
~bitCrusherBus = Bus.audio(s, 2);
~chorusBus = Bus.audio(s, 2);
~reverbBus = Bus.audio(s, 2);
// Define SynthDefs
SynthDef(\samplerSynth, { |out=0, bufnum, rate=1, amp=0.5, gate=1, filterFreq=500, filterRes=0.1, filterOverdrive=0|
var sig, env;
env = EnvGen.kr(Env.adsr(0.01, 3, 4, 7), gate, doneAction: 2);
sig = PlayBuf.ar(2, bufnum, rate * BufRateScale.kr(bufnum), loop: 0) * env * amp;
sig = VADiodeFilter.ar(sig, filterFreq, filterRes, filterOverdrive);
Out.ar(out, sig);
}).add;
SynthDef(\bitCrusher, { |in, out, wet=1, amp=0.8, bits=32, resampleRate=22050, lag=0|
var sig = In.ar(in, 2);
sig = sig.round(2 ** (1 - bits));
sig = Latch.ar(sig, Impulse.ar(resampleRate));
sig = sig.lag(lag * 0.001);
sig = XFade2.ar(In.ar(in, 2), sig, wet * 2 - 1);
Out.ar(out, sig * amp);
}).add;
SynthDef(\chorus, { |in, out, wet=0.5, rate=0.4, depth=0.03, amp=0.9|
var sig = In.ar(in, 2);
var modulation = SinOsc.kr(rate, 0, depth, depth + 0.004);
var effect = AllpassL.ar(sig, 0.1, modulation, 0.1);
sig = XFade2.ar(sig, effect, wet * 2 - 1);
Out.ar(out, sig * amp);
}).add;
SynthDef(\reverb, { |in, out, roomSize=10, revTime=3, damping=0.5, inputBW=0.5, dry=1, early=0.7, tail=0.5, wet=0.5, amp=0.7|
var sig = In.ar(in, 2);
var verb = GVerb.ar(sig, roomSize, revTime, damping, inputBW, spread: 15, drylevel: dry, earlyreflevel: early, taillevel: tail);
sig = XFade2.ar(sig, verb, wet * 2 - 1);
Out.ar(out, sig * amp);
}).add;
SynthDef(\limiter, { |in, out, level=0.99, dur=0.01|
var sig = In.ar(in, 2);
sig = Limiter.ar(sig, level, dur);
Out.ar(out, sig);
}).add;
~createKeyboard = {
var buffer, bitCrusherSynth, chorusSynth, reverbSynth, limiterSynth;
var startTime, currentOctave = 0;
var keyboardActive = false;
var chordNotes = Set.new;
var looping = false, playRoutine;
var tempoClock;
var filterFreq = 500, filterRes = 0.1, filterOverdrive = 0;
var bcWet = 1, bcBits = 32, bcRs = 22050, bcLag = 0;
var chorusWet = 0.2, chorusRate = 0.01, chorusDepth = 0.0004;
var gverbRoomSize = 10, gverbRevTime = 3, gverbDamping = 0.5, gverbInputBW = 0.5, gverbDry = 1, gverbEarly = 0.7, gverbTail = 0.5, gverbWet = 0.5;
buffer = Buffer.read(s, "/Users/ss/Documents/sounds/Pmoog.wav");
tempoClock = TempoClock.new(2);
// Create effect synths
bitCrusherSynth = Synth(\bitCrusher, [\in, ~samplerBus, \out, ~bitCrusherBus]);
chorusSynth = Synth(\chorus, [\in, ~bitCrusherBus, \out, ~chorusBus], addAction: \addToTail);
reverbSynth = Synth(\reverb, [\in, ~chorusBus, \out, ~reverbBus], addAction: \addToTail);
limiterSynth = Synth(\limiter, [\in, ~reverbBus, \out, 0], addAction: \addToTail);
~win = Window("Sampler Keyboard with Effects", Rect(100, 100, 1000, 990)).front;
~scrollView = ScrollView(~win, Rect(0, 0, 1000, 600));
~contentView = View.new(~scrollView, Rect(0, 0, 980, 1200));
~notes = [\C, \Cs, \D, \Ds, \E, \F, \Fs, \G, \Gs, \A, \As, \B,
\C, \Cs, \D, \Ds, \E, \F, \Fs, \G, \Gs, \A, \As, \B];
~scaleDegrees = (0..23);
~keyboardMap = Dictionary[
$a -> 0, $w -> 1, $s -> 2, $e -> 3, $d -> 4, $f -> 5, $t -> 6, $g -> 7, $y -> 8, $h -> 9, $u -> 10, $j -> 11,
$k -> 12, $o -> 13, $l -> 14, $p -> 15, $; -> 16, $' -> 17,
$z -> 0, $x -> 2, $c -> 4, $v -> 5, $b -> 7, $n -> 9, $m -> 11, $, -> 12, $. -> 14, $/ -> 16
];
~sequence = List[];
~activeSynths = Dictionary.new;
~playNote = { |degree|
var adjustedDegree = degree + (currentOctave * 12);
var rate = 2.pow(adjustedDegree / 12);
var synth;
~activeSynths[adjustedDegree].do({ |syn| syn.set(\gate, 0) });
synth = Synth(\samplerSynth, [
\out, ~samplerBus,
\bufnum, buffer, \rate, rate, \amp, 0.5,
\filterFreq, filterFreq, \filterRes, filterRes, \filterOverdrive, filterOverdrive
]);
~activeSynths[adjustedDegree] = synth;
chordNotes.add(adjustedDegree);
if(~capturingTime, {
var elapsedTime = Main.elapsedTime - startTime;
if(chordNotes.size > 1, {
~sequence.add([chordNotes.asArray.sort, elapsedTime.round(0.001)]);
}, {
~sequence.add([adjustedDegree, elapsedTime.round(0.001)]);
});
}, {
if(chordNotes.size > 1, {
~sequence.add([chordNotes.asArray.sort, nil]);
}, {
~sequence.add([adjustedDegree, nil]);
});
});
if(degree < 24, {
~buttons[degree].states = [[~notes[degree].asString, Color.white, Color.black]];
AppClock.sched(0.2, {
~buttons[degree].states = [[~notes[degree].asString, Color.black, Color.white]];
nil
});
});
};
~stopNote = { |degree|
var adjustedDegree = degree + (currentOctave * 12);
~activeSynths[adjustedDegree].do({ |syn| syn.set(\gate, 0) });
~activeSynths[adjustedDegree] = nil;
chordNotes.remove(adjustedDegree);
};
~buttons = ~notes.collect({ |note, i|
Button(~win, Rect(10 + (i * 40), 10, 35, 100))
.states_([[note.asString, Color.black, Color.white]])
.mouseDownAction_({ ~playNote.(i) })
.mouseUpAction_({ ~stopNote.(i) });
});
~seqDisplay = TextView(~win, Rect(10, 120, 980, 100))
.string_("Played sequence: ")
.editable_(false);
Button(~win, Rect(10, 230, 100, 30))
.states_([["Clear Sequence"]])
.action_({
~sequence.clear;
~seqDisplay.string_("Played sequence: ");
});
Button(~win, Rect(120, 230, 100, 30))
.states_([["Print Sequence"]])
.action_({
var noteArray, durArray;
noteArray = ~sequence.collect({ |item| item[0] });
durArray = ~sequence.collect({ |item, i|
if(item[1].isNil, {
0.5
}, {
if(i == 0, {
0.5
}, {
var prevTime = ~sequence[i-1][1];
if(prevTime.isNil, {
0.5
}, {
(item[1] - prevTime).max(0.01)
});
});
});
});
"Note/Chord Array:".postln;
noteArray.postln;
"Duration Array:".postln;
durArray.postln;
"Pbind pattern:".postln;
("Pbind(\\degree, " ++ noteArray.collect({|item| item.asArray}).asCompileString ++
", \\dur, " ++ durArray.asCompileString ++ ")").postln;
});
Button(~win, Rect(230, 230, 100, 30))
.states_([
["Play Sequence", Color.black, Color.green],
["Stop Sequence", Color.white, Color.red]
])
.action_({ |but|
if(but.value == 1, {
looping = true;
playRoutine = Routine({
while { looping } {
var prevTime = 0;
~sequence.do({ |item|
var degrees = item[0].asArray;
var time = item[1];
var synths;
if(time.notNil, {
(time - prevTime).wait;
prevTime = time;
}, {
0.5.wait;
});
synths = degrees.collect({ |degree|
var rate = 2.pow(degree / 12);
Synth(\samplerSynth, [
\out, ~samplerBus,
\bufnum, buffer, \rate, rate, \amp, 0.5,
\filterFreq, filterFreq, \filterRes, filterRes, \filterOverdrive, filterOverdrive
]);
});
AppClock.sched(0.2, { synths.do(_.set(\gate, 0)); nil });
});
0.1.wait;
}
}).play(tempoClock);
}, {
looping = false;
playRoutine.stop;
});
});
~timingButton = Button(~win, Rect(340, 230, 150, 30))
.states_([
["Start Timing Capture", Color.black, Color.green],
["Stop Timing Capture", Color.white, Color.red]
])
.action_({ |but|
if(but.value == 1, {
~capturingTime = true;
startTime = Main.elapsedTime;
}, {
~capturingTime = false;
});
});
Button(~win, Rect(500, 230, 100, 30))
.states_([["Octave Down"]])
.action_({
currentOctave = (currentOctave - 1).clip(-1, 7);
~updateOctaveDisplay.value;
});
Button(~win, Rect(610, 230, 100, 30))
.states_([["Octave Up"]])
.action_({
currentOctave = (currentOctave + 1).clip(-1, 7);
~updateOctaveDisplay.value;
});
~octaveDisplay = StaticText(~win, Rect(720, 230, 100, 30))
.string_("Octave: 0")
.align_(\center);
~updateOctaveDisplay = {
~octaveDisplay.string_("Octave: " ++ currentOctave);
};
Button(~win, Rect(830, 230, 150, 30))
.states_([
["Activate Keyboard", Color.black, Color.green],
["Deactivate Keyboard", Color.white, Color.red]
])
.action_({ |but|
keyboardActive = but.value == 1;
if(keyboardActive, {
~win.view.focus(true);
});
});
Slider(~win, Rect(10, 270, 300, 20))
.action_({ |sl|
var tempo = sl.value.linexp(0, 1, 0.5, 4);
tempoClock.tempo = tempo;
~tempoDisplay.string = "Tempo: " ++ (tempo * 60).round(0.1) ++ " BPM";
})
.value_(0.6349);
~tempoDisplay = StaticText(~win, Rect(320, 270, 150, 20))
.string_("Tempo: 120.0 BPM");
// Filter controls
StaticText(~win, Rect(10, 330, 100, 20)).string_("Filter Freq:");
Slider(~win, Rect(10, 350, 300, 20))
.action_({ |sl|
filterFreq = sl.value.linexp(0, 1, 20, 20000);
~filterFreqDisplay.string = "Filter Freq: " ++ filterFreq.round(0.1) ++ " Hz";
})
.value_(0.5);
~filterFreqDisplay = StaticText(~win, Rect(320, 350, 150, 20))
.string_("Filter Freq: 500.0 Hz");
StaticText(~win, Rect(10, 380, 100, 20)).string_("Filter Res:");
Slider(~win, Rect(10, 400, 300, 20))
.action_({ |sl|
filterRes = sl.value.linlin(0, 1, 0.0, 1.0);
~filterResDisplay.string = "Filter Res: " ++ filterRes.round(0.001);
})
.value_(0.1);
~filterResDisplay = StaticText(~win, Rect(320, 400, 150, 20))
.string_("Filter Res: 0.1");
StaticText(~win, Rect(10, 430, 100, 20)).string_("Filter Overdrive:");
Slider(~win, Rect(10, 450, 300, 20))
.action_({ |sl|
filterOverdrive = sl.value.linlin(0, 1, 0.0, 1.0);
~filterOverdriveDisplay.string = "Filter Overdrive: " ++ filterOverdrive.round(0.001);
})
.value_(0);
~filterOverdriveDisplay = StaticText(~win, Rect(320, 450, 150, 20))
.string_("Filter Overdrive: 0.0");
// Bitcrusher controls
StaticText(~win, Rect(10, 480, 100, 20)).string_("BC Wet:");
Slider(~win, Rect(10, 500, 300, 20))
.action_({ |sl|
bcWet = sl.value;
bitCrusherSynth.set(\wet, bcWet);
~bcWetDisplay.string = "BC Wet: " ++ bcWet.round(0.001);
})
.value_(1);
~bcWetDisplay = StaticText(~win, Rect(320, 500, 150, 20))
.string_("BC Wet: 1.0");
StaticText(~win, Rect(10, 530, 100, 20)).string_("BC Bits:");
Slider(~win, Rect(10, 550, 300, 20))
.action_({ |sl|
bcBits = sl.value.linlin(0, 1, 1, 32).round;
bitCrusherSynth.set(\bits, bcBits);
~bcBitsDisplay.string = "BC Bits: " ++ bcBits;
})
.value_(1);
~bcBitsDisplay = StaticText(~win, Rect(320, 550, 150, 20))
.string_("BC Bits: 32");
StaticText(~win, Rect(10, 580, 100, 20)).string_("BC Rs:");
Slider(~win, Rect(10, 600, 300, 20))
.action_({ |sl|
bcRs = sl.value.linexp(0, 1, 100, 44100).round;
bitCrusherSynth.set(\resampleRate, bcRs);
~bcRsDisplay.string = "BC Rs: " ++ bcRs ++ " Hz";
})
.value_(0.7);
~bcRsDisplay = StaticText(~win, Rect(320, 600, 150, 20))
.string_("BC Rs: 22050 Hz");
StaticText(~win, Rect(10, 630, 100, 20)).string_("BC Lag:");
Slider(~win, Rect(10, 650, 300, 20))
.action_({ |sl|
bcLag = sl.value * 100;
bitCrusherSynth.set(\lag, bcLag);
~bcLagDisplay.string = "BC Lag: " ++ bcLag.round(0.001) ++ " ms";
})
.value_(0);
~bcLagDisplay = StaticText(~win, Rect(320, 650, 150, 20))
.string_("BC Lag: 0 ms");
// Chorus controls
StaticText(~win, Rect(10, 680, 100, 20)).string_("Chorus Wet:");
Slider(~win, Rect(10, 700, 300, 20))
.action_({ |sl|
chorusWet = sl.value;
chorusSynth.set(\wet, chorusWet);
~chorusWetDisplay.string = "Chorus Wet: " ++ chorusWet.round(0.001);
})
.value_(0.1);
~chorusWetDisplay = StaticText(~win, Rect(320, 700, 150, 20))
.string_("Chorus Wet: 0.5");
StaticText(~win, Rect(10, 730, 100, 20)).string_("Chorus Rate:");
Slider(~win, Rect(10, 750, 300, 20))
.action_({ |sl|
chorusRate = sl.value.linexp(0, 1, 0.01, 0.5);
chorusSynth.set(\rate, chorusRate);
~chorusRateDisplay.string = "Chorus Rate: " ++ chorusRate.round(0.001) ++ " Hz";
})
.value_(0.01);
~chorusRateDisplay = StaticText(~win, Rect(320, 750, 150, 20))
.string_("Chorus Rate: 0.01 Hz");
StaticText(~win, Rect(10, 780, 100, 20)).string_("Chorus Depth:");
Slider(~win, Rect(10, 800, 300, 20))
.action_({ |sl|
chorusDepth = sl.value * 0.1;
chorusSynth.set(\depth, chorusDepth);
~chorusDepthDisplay.string = "Chorus Depth: " ++ chorusDepth.round(0.001);
})
.value_(0.004);
~chorusDepthDisplay = StaticText(~win, Rect(320, 800, 150, 20))
.string_("Chorus Depth: 0.03");
// Reverb controls
StaticText(~win, Rect(500, 330, 100, 20)).string_("Room Size:");
Slider(~win, Rect(500, 350, 300, 20))
.action_({ |sl|
gverbRoomSize = sl.value.linlin(0, 1, 1, 10);
reverbSynth.set(\roomSize, gverbRoomSize);
~gverbRoomSizeDisplay.string = "Room Size: " ++ gverbRoomSize.round(0.1);
})
.value_(0.03);
~gverbRoomSizeDisplay = StaticText(~win, Rect(810, 350, 150, 20))
.string_("Room Size: 10.0");
StaticText(~win, Rect(500, 380, 100, 20)).string_("Rev Time:");
Slider(~win, Rect(500, 400, 300, 20))
.action_({ |sl|
gverbRevTime = sl.value.linlin(0, 1, 0.1, 22);
reverbSynth.set(\revTime, gverbRevTime);
~gverbRevTimeDisplay.string = "Rev Time: " ++ gverbRevTime.round(0.1) ++ " s";
})
.value_(0.029);
~gverbRevTimeDisplay = StaticText(~win, Rect(810, 400, 150, 20))
.string_("Rev Time: 3.0 s");
StaticText(~win, Rect(500, 430, 100, 20)).string_("Damping:");
Slider(~win, Rect(500, 450, 300, 20))
.action_({ |sl|
gverbDamping = sl.value;
reverbSynth.set(\damping, gverbDamping);
~gverbDampingDisplay.string = "Damping: " ++ gverbDamping.round(0.001);
})
.value_(0.5);
~gverbDampingDisplay = StaticText(~win, Rect(810, 450, 150, 20))
.string_("Damping: 0.5");
StaticText(~win, Rect(500, 480, 100, 20)).string_("Input BW:");
Slider(~win, Rect(500, 500, 300, 20))
.action_({ |sl|
gverbInputBW = sl.value;
reverbSynth.set(\inputBW, gverbInputBW);
~gverbInputBWDisplay.string = "Input BW: " ++ gverbInputBW.round(0.001);
})
.value_(0.5);
~gverbInputBWDisplay = StaticText(~win, Rect(810, 500, 150, 20))
.string_("Input BW: 0.5");
StaticText(~win, Rect(500, 530, 100, 20)).string_("Dry Level:");
Slider(~win, Rect(500, 550, 300, 20))
.action_({ |sl|
gverbDry = sl.value;
reverbSynth.set(\dry, gverbDry);
~gverbDryDisplay.string = "Dry Level: " ++ gverbDry.round(0.001);
})
.value_(1);
~gverbDryDisplay = StaticText(~win, Rect(810, 550, 150, 20))
.string_("Dry Level: 1.0");
StaticText(~win, Rect(500, 580, 100, 20)).string_("Early Ref Level:");
Slider(~win, Rect(500, 600, 300, 20))
.action_({ |sl|
gverbEarly = sl.value;
reverbSynth.set(\early, gverbEarly);
~gverbEarlyDisplay.string = "Early Ref Level: " ++ gverbEarly.round(0.001);
})
.value_(0.7);
~gverbEarlyDisplay = StaticText(~win, Rect(810, 600, 150, 20))
.string_("Early Ref Level: 0.7");
StaticText(~win, Rect(500, 630, 100, 20)).string_("Tail Level:");
Slider(~win, Rect(500, 650, 300, 20))
.action_({ |sl|
gverbTail = sl.value;
reverbSynth.set(\tail, gverbTail);
~gverbTailDisplay.string = "Tail Level: " ++ gverbTail.round(0.001);
})
.value_(0.5);
~gverbTailDisplay = StaticText(~win, Rect(810, 650, 150, 20))
.string_("Tail Level: 0.5");
StaticText(~win, Rect(500, 680, 100, 20)).string_("Reverb Wet/Dry:");
Slider(~win, Rect(500, 700, 300, 20))
.action_({ |sl|
gverbWet = sl.value;
reverbSynth.set(\wet, gverbWet);
~gverbWetDisplay.string = "Reverb Wet/Dry: " ++ gverbWet.round(0.001);
})
.value_(0.5);
~gverbWetDisplay = StaticText(~win, Rect(810, 700, 150, 20))
.string_("Reverb Wet/Dry: 0.5");
~updateSeqDisplay = {
~seqDisplay.string_("Played sequence: " ++ ~sequence.collect({ |item|
var notes = item[0];
var time = item[1];
var noteStr = if(notes.isArray, {
"[" ++ notes.collect(_.asString).join(", ") ++ "]"
}, {
notes.asString
});
if(time.isNil, {
noteStr
}, {
noteStr ++ "@" ++ time.round(0.001).asString
});
}).join(", "));
};
Routine({
loop {
~updateSeqDisplay.();
0.1.wait;
}
}).play(AppClock);
~win.view.keyDownAction = { |view, char, modifiers, unicode, keycode|
if(keyboardActive, {
var degree = ~keyboardMap[char.toLower];
if(degree.notNil, {
~playNote.(degree);
});
if(char.isDecDigit, {
var num = char.digit;
currentOctave = num - 2;
currentOctave = currentOctave.clip(-1, 7);
~updateOctaveDisplay.value;
});
});
};
~win.view.keyUpAction = { |view, char, modifiers, unicode, keycode|
if(keyboardActive, {
var degree = ~keyboardMap[char.toLower];
if(degree.notNil, {
~stopNote.(degree);
});
});
};
~win.onClose = {
buffer.free;
~samplerBus.free;
~bitCrusherBus.free;
~chorusBus.free;
~reverbBus.free;
bitCrusherSynth.free;
chorusSynth.free;
reverbSynth.free;
limiterSynth.free;
tempoClock.stop;
};
};
// Call createKeyboard in a way that allows for server synchronization
s.waitForBoot({
~createKeyboard.value;
});
)