I’m having some issues with code I made to emulate a Hammond for use with MIDI. After awhile, a note invariably seems to get stuck and something goes wrong with an EnvGen. I have added a rather poor emulation of the Hammond key clicks and when a note gets stuck, it seems to be holding the gate on the click envelope open, along with the other envelopes.
(
// (c) M. Josiah Sytsma 2022
s = Server.local;
s.newBusAllocators;
MIDIClient.init;
MIDIIn.connectAll;
s.waitForBoot{
var pedBuf = Buffer.alloc(s, 1024, 1, {arg buf; buf.sine1Msg(
(1..11).collect{
arg n;
if (n.odd)
{n.reciprocal}
{0}
}
)
});
var organOut = Bus.audio(s,1);
var vibOut = Bus.audio(s,1);
var defs = (
SynthDef(\organ, {
arg out, freqs=#[0,0,0,0,0,0,0,0,0], amps=#[0,0,0,0,0,0,0,0,0], amp=0.05, drawbarAmp=1, clickDur=0.01, clickAmp=0.03, gate=1, clickDec=1e-3, clickHold=0.003;
var sig, env, click;
env = Env.cutoff(clickDec).ar(0,gate);
click = WhiteNoise.ar(Hasher.kr(freqs).range(0.0,1.0))*amps;
click = OnePole.ar(click.sum, 0.8);
click = click*Env([1,1,0,1,1],[clickHold, clickDec, clickDec, clickHold], -4,2).ar(2,gate);
click = click*clickAmp;
sig = SinOsc.ar(freqs, 0, amps);
sig = sig.sum;
sig = sig*env*amp;
sig = [sig,click].sum;
Out.ar(out, sig);
}).add;
SynthDef(\perc, {
arg vol = 0.025, out, freq=261.63, dec, amp=1, gate=1;
var sig, env;
sig = SinOsc.ar(freq);
env = Env.perc(0.01, dec, 1, -4).ar(2, gate);
sig = sig*env*amp*vol/2;
Out.ar(out, sig);
}).add;
SynthDef(\pedal, {
arg out, freq=261.63, amp=0.03, drawbarAmp=1, clickDur=0.01, clickAmp=0.02, gate=1, clickDec=1e-3, clickHold=0.003, buf;
var sig, env, click;
env = Env.cutoff(clickHold+clickDec/2).ar(2,gate);
click = OnePole.ar(WhiteNoise.ar((Hasher.kr(freq).range(0,1/9))),0.9);
click = click*Env([1,1,0,1,1],[clickHold, clickDec, clickDec, clickHold], -4,2).ar(0,gate);
click = click*clickAmp;
sig = Osc.ar(buf, freq);
sig = sig*env*amp;
sig = [sig,click].sum;
Out.ar(out, sig);
}).add;
SynthDef(\vibrato, {
arg in, out, on=0, amp=1;
var dry, wet, sig, c1, c2, c3, v1, v2, v3, wet2, wet3, scanner;
amp = Lag.kr(amp, 0.2);
dry = In.ar(in, 1);
wet = (1..9).collect{arg i; DelayN.ar(dry, 0.01, i/9000)};
scanner = LFTri.ar(7,0).range(0,wet.size+1);
wet = SelectX.ar(scanner, [dry]++wet);
c3 = dry.blend(wet, MouseX.kr);
v3 = wet;
sig = SelectX.ar(on.varlag(0.1), [dry*(5/3), c3*5/3]);
sig = sig*amp;
Out.ar(out, sig!2);
}).add;
);
var sync = s.sync;
var
shadow = Color.grey(0.2, 0.5),
ivory = Color.new255(200,190,180),
brown = Color.fromHexString("654321"),
ebony = Color.grey(0.3),
light = Color.white.alpha_(0.2);
var win = Window("Hermond B5", Rect(300, 300, 680, 600), false, true, s, false).onClose_{pedBuf.free; CmdPeriod.run;}
.front
.background_(Color.grey(0.3));
var organNotes = Array.newClear(128);
var percNotes;
var switches = Dictionary.newFrom([
\vol, 0,
\vib, 0,
\perc, 0,
\percVol, 0,
\dec, 0,
\harm, 0
]);
var vib = Synth(\vibrato, [\in, organOut, \out, 0, \amp, [-3.dbamp,1.0][switches[\vol]]], s, \addToTail);
var switch = {
arg point, string, ctrl, bottomString, topString;
var rect = Rect(0, 0, 60, 120);
var bottomShade = Rect(0, rect.height*(5/6), rect.width, rect.height*(1/6));
var topShade = Rect(0, 0, rect.width, rect.height*(1/6));
var textBox;
var shade;
var val = switches[ctrl];
var selectorRect = [bottomShade.moveBy(0,bottomShade.height*1.25), topShade.moveBy(0,(topShade.height*1.25).neg)][val];
shade = [topShade,bottomShade][val];
Pen.use{
Pen.translate(point.x, point.y);
Pen.addRoundedRect(rect,4,4).width_(5).color_(Color.black).stroke;
Pen.color_(ivory);
Pen.addRoundedRect(rect,4,4);
Pen.use{
Pen.clip;
Pen.fillRect(rect);
};
textBox=Rect(0,rect.height*(6-val)/12,rect.width,20);
Pen.addRect(shade).fillAxialGradient(shade.center.x @ shade.top, shade.center.x @ shade.bottom, [Color.grey(0.1,0.3), Color.white.alpha_(0.2)][val], [Color.white.alpha_(0.2), Color.grey(0.1,0.3)][val]);
Pen.stringCenteredIn(string, textBox, Font("Futura", 12, false, false, false), Color.black);
Pen.stringCenteredIn(bottomString, bottomShade.moveBy(0,bottomShade.height*1.25), Font("Futura", 12, false, false, false), [Color.white, Color.grey(0.6)][val]);
Pen.stringCenteredIn(topString, topShade.moveBy(0,(topShade.height*1.25).neg), Font("Futura", 12, false, false, false), [Color.grey(0.6), Color.white][val]);
Pen.addRect(Rect.aboutPoint(selectorRect.center, selectorRect.width*2, selectorRect.height*2)).fillRadialGradient(selectorRect.center, selectorRect.center, 1, selectorRect.width/2, Color.white.alpha_(0.1), Color.clear);
};
};
/////////////////////////////////////the switches
var volx = 40;
var vibx = 115;
var percx = 350;
var percVolx = 425;
var decx = 500;
var harmx = 575;
var drawbarYs = Array.newClear(9);
var switchY = 40;
var switchGUI = UserView(win, win.view.bounds)
.drawFunc_{
switch.(volx @ switchY, "VOLUME", \vol, "SOFT", "NORMAL");
switch.(vibx @ switchY, "VIBRATO", \vib, "OFF", "ON");
switch.(percx @ switchY, "PERC", \perc, "OFF", "ON");
switch.(percVolx @ switchY, "PERC VOL", \percVol, "SOFT", "NORMAL");
switch.(decx @ switchY, "DECAY", \dec, "SLOW", "FAST");
switch.(harmx @ switchY, "HARM", \harm, "SECOND", "THIRD");
}
.mouseDownAction_{
arg view, x, y, modif, button, clickCount;
case
{Rect(volx,switchY,60,120).containsPoint(x@y)}{switches.add(\vol->(switches[\vol]+1).mod(2)); switchGUI.refresh}
{Rect(vibx,switchY,60,120).containsPoint(x@y)}{switches.add(\vib->(switches[\vib]+1).mod(2)); switchGUI.refresh}
{Rect(percx,switchY,60,120).containsPoint(x@y)}{switches.add(\perc->(switches[\perc]+1).mod(2)); switchGUI.refresh}
{Rect(percVolx,switchY,60,120).containsPoint(x@y)}{switches.add(\percVol->(switches[\percVol]+1).mod(2)); switchGUI.refresh}
{Rect(decx,switchY,60,120).containsPoint(x@y)}{switches.add(\dec->(switches[\dec]+1).mod(2)); switchGUI.refresh}
{Rect(harmx,switchY,60,120).containsPoint(x@y)}{switches.add(\harm->(switches[\harm]+1).mod(2)); switchGUI.refresh};
vib.set(\on, switches[\vib]);
vib.set(\amp, [0.5,1][switches[\vol]]);
if (switches[\perc]==0 && drawbarYs[8].isNil.not){amps[8]=drawbarYs[8].linlin(0,300,0,1)};
};
/////////////////////////////////////vibrato knob
var vkRect = Rect.aboutPoint(265 @ 100, 50, 50);
var vkRot = 0;
var vibKnob = UserView(win, vkRect)
.mouseMoveAction_{
arg view, x, y;
}
.drawFunc_{
arg view;
var squares = 20;
var center = vkRect.radiusH@vkRect.radiusV;
Pen.use{
Pen.color_(Color.grey(0.1));
//body
Pen.addOval(Rect.aboutPoint(center, 48, 48));
//decor
squares.do{
arg i;
var rad = 30;
var angle = 2pi/squares * i.neg,
x = rad*sin(angle) + vkRect.radiusH,
y = rad*cos(angle) + vkRect.radiusV;
Pen.use{
Pen.rotate(angle, vkRect.radiusH, vkRect.radiusV);
Pen.addRoundedRect(Rect.aboutPoint(center, vkRect.radiusH, 4), 2, 2)
.fill;
};
};
//gradient
//label
Pen.stringCenteredIn(
"VIBRATO\n"++"-&-\n"++"CHORUS",
Rect.aboutPoint(center.x@(center.y+3), 50, 50),
Font("Futura", 12),
Color.white;
);
//line
// Pen.color_(Color.white).width_(3).capStyle_(1).line(vkRect.radiusH @ 26, vkRect.radiusH @ 10).stroke;
Pen.color_(Color.white).fillOval(Rect.aboutPoint(center.x @ 10, 3, 3))
};
Pen.addOval(Rect.aboutPoint(center, 45, 45))
.fillRadialGradient(center-20, center-10, 1, 30, Color.grey(0.9,0.4), Color.clear);
};
/////////////////////////////////////the drawbars
var amps = (0!9);
var drawbarRatios = [-12,0,7,12,19,24,28,31,36];
var drawbarMin = 0;
var drawbarMax = 300;
var drawbarView = View(win, Rect(0, 200, win.view.bounds.width, win.view.bounds.height-200))
.background_(Color.grey(0.1));
var teethArray = (7.collect{arg i; 2.pow(i+1)!12}++(192!7)).flat;
var ratioArray = ((([85,71,67,105,103,84,74,98,96,88,67,108]/[104,82,73,108,100,77,64,80,74,64,46,70])!7)++([84,74,98,96,88,67,108]/[77,64,80,74,64,46,70])).flat;
var drawbar = {
arg point, color, ind;
var val;
var rect = Rect(0, 300, 40, 60);
UserView(drawbarView, Rect(point.x, 0, 40, 600))
.animate_(false)
.drawFunc_{
var numberRect = Rect(rect.left+5, rect.top, rect.width-10, -600);
var nums;
Pen.color_(color).fillRect(rect);
//bottom shadow
Pen.moveTo(rect.leftBottom)
.lineTo(rect.rightBottom)
.lineTo(rect.rightBottom - 8)
.lineTo(rect.left + 8 @ (rect.bottom-8))
.lineTo(rect.leftBottom)
.color_(Color.grey(0.1, 0.2))
.fill;
//right shadow
Pen.moveTo(rect.rightBottom)
.lineTo(rect.rightBottom - 8)
.lineTo(rect.right - 8 @ (rect.top+8))
.lineTo(rect.rightTop)
.lineTo(rect.rightBottom)
.fillAxialGradient(rect.rightBottom - 4, rect.right - 4 @ (rect.top+4), shadow, shadow.alpha_(0.3));
//grabby knobby
Pen.moveTo(rect.left + 8 @ (rect.bottom-8))
.lineTo(rect.left + 8 @ (rect.top+(rect.height*3/5)))
.lineTo(rect.right - 8 @ (rect.top+(rect.height*3/5)))
.lineTo(rect.right - 8 @ (rect.bottom-8))
.fillAxialGradient(rect.left @ (rect.top+(rect.height*4/5)), rect.right @ (rect.top+(rect.height*4/5)), light.alpha_(0.1), Color.grey(0.2,0.2));
//number line drawer thing
Pen.color_(Color.white).fillRect(numberRect);
Pen.color_(Color.black).fillRect(numberRect.insetAll(5,0,5,0));
//numbers
nums = 8.collect{arg i; Rect(numberRect.left, rect.top-((i+1)*drawbarMax/8), numberRect.width, 300/8)};
nums.do{arg r, i; Pen.stringCenteredIn((i+1).asString, r, Font("Futura", 20), Color.white)};
drawbarYs = drawbarYs.put(ind, rect.top);
val = rect.top.linlin(drawbarMin,drawbarMax,0,1);
amps.put(ind, val);
}
.mouseMoveAction_{
arg view, x, y;
var lim;
view.animate_(true);
lim = y.clip(drawbarMin,drawbarMax);
rect = Rect(0, lim, 40, 60);
val = lim.linlin(drawbarMin,drawbarMax,0,1);
if (ind == 8 && switches[\perc] == 1){val=0};
amps.put(ind, val);
organNotes.collect{arg item, i; if (item.isPlaying){organNotes[i].set(\amps, amps/9)}};
// amps.postln;
}
.mouseUpAction_{
arg view;
view.animate_(false);
};
};
var drawbarPoints =
9.collect{
arg i;
var offset=40, spacing = 50, y = 500, point;
point = offset+(i*spacing) @ y;
};
var drawbars = [brown, brown, ivory, ivory, ebony, ivory, ebony, ebony, ivory].collect{arg color, i; drawbar.(drawbarPoints[i], color, i)};
var freqsArray = (24..114).collect{
arg num;
var freqs, teeth;
freqs = num+drawbarRatios; //an array of midinums
freqs = freqs.collect{arg inval; //array of wheel numbers with foldback
w = inval-24;
while {w>90}{w=w-12};
while {w<0}{w=w+12};
w};
freqs = freqs.collect{arg inval;
ratioArray[inval] * teethArray[inval] * 20};
};
s.sync;
MIDIdef.noteOn(\onManuals, {
arg val, num, src, chan;
var freqs = freqsArray[num-24], perc;
perc = freqs[[2,3]];
// [freqs, amps/9].flop.collect{arg in; ("Num: "++num++" Freq: "++in[0].round(0.01)++" Drawbar: "++in[1]).postln};
if (switches[\perc]==1)
{
amps.put(8, 0);
if (organNotes.any{arg item; item.isPlaying}.not)
{percNotes = Synth(\perc, [
\freq, perc[switches[\harm]],
\dec, [0.5,0.125][switches[\dec]],
\out, organOut,
\amp, [-3.dbamp,0.dbamp][switches[\percVol]]
]).register;
};
};
organNotes.put(num, Synth(\organ, [\freqs, freqs, \amps, amps/9, \out, organOut]).register);
},
(24..114)
);
MIDIdef.noteOn(\onPeds, {
arg val, num, src, chan;
var freq;
freq = ratioArray[num] * teethArray[num] * 20;
organNotes.put(num, Synth(\pedal, [\freq, freq, \out, organOut, \buf, pedBuf]));
}, (0..23));
MIDIdef.noteOff(\oof, {
arg val, num, src, chan;
organNotes[num].set(\gate, 0);
if (percNotes.isPlaying){percNotes.set(\gate, -1)};
});
};
)