diff --git a/dsp/Params.cmajor b/dsp/Params.cmajor index 4986c9e..da17417 100644 --- a/dsp/Params.cmajor +++ b/dsp/Params.cmajor @@ -86,6 +86,10 @@ namespace Percupuff float cowbellPanning; float cowbellVelocity; float cowbellMidi; + float maracasLevel; + float maracasPanning; + float maracasVelocity; + float maracasMidi; float sideStickLevel; float sideStickPanning; float sideStickVelocity; @@ -173,6 +177,10 @@ namespace Percupuff p.cowbellPanning = 0.0f; p.cowbellVelocity = 100.0f; p.cowbellMidi = 56.0f; + p.maracasLevel = 50.0f; + p.maracasPanning = 0.0f; + p.maracasVelocity = 100.0f; + p.maracasMidi = 70.0f; p.sideStickLevel = 50.0f; p.sideStickPanning = 0.0f; p.sideStickVelocity = 100.0f; @@ -260,6 +268,10 @@ namespace Percupuff input event float cowbellPanning [[ name: "cowbellPanning", min: -100, max: 100, init: 0, step: 5 ]]; input event float cowbellVelocity [[ name: "cowbellVelocity", min: 0, max: 100, init: 100, step: 1 ]]; input event float cowbellMidi [[ name: "cowbellMidi", min: 0, max: 127, init: 56, step: 1 ]]; + input event float maracasLevel [[ name: "maracasLevel", min: 0, max: 100, init: 50, step: 1 ]]; + input event float maracasPanning [[ name: "maracasPanning", min: -100, max: 100, init: 0, step: 5 ]]; + input event float maracasVelocity [[ name: "maracasVelocity", min: 0, max: 100, init: 100, step: 1 ]]; + input event float maracasMidi [[ name: "maracasMidi", min: 0, max: 127, init: 70, step: 1 ]]; input event float sideStickLevel [[ name: "sideStickLevel", min: 0, max: 100, init: 50, step: 1 ]]; input event float sideStickPanning [[ name: "sideStickPanning", min: -100, max: 100, init: 0, step: 5 ]]; input event float sideStickVelocity [[ name: "sideStickVelocity", min: 0, max: 100, init: 100, step: 1 ]]; @@ -350,6 +362,10 @@ namespace Percupuff event cowbellPanning (float f) { params.cowbellPanning = f; update(); } event cowbellVelocity (float f) { params.cowbellVelocity = f; update(); } event cowbellMidi (float f) { params.cowbellMidi = f; update(); } + event maracasLevel (float f) { params.maracasLevel = f; update(); } + event maracasPanning (float f) { params.maracasPanning = f; update(); } + event maracasVelocity (float f) { params.maracasVelocity = f; update(); } + event maracasMidi (float f) { params.maracasMidi = f; update(); } event sideStickLevel (float f) { params.sideStickLevel = f; update(); } event sideStickPanning (float f) { params.sideStickPanning = f; update(); } event sideStickVelocity (float f) { params.sideStickVelocity = f; update(); } diff --git a/dsp/Percupuff.cmajor b/dsp/Percupuff.cmajor index eb74ecb..b3f1d84 100644 --- a/dsp/Percupuff.cmajor +++ b/dsp/Percupuff.cmajor @@ -39,6 +39,7 @@ namespace Percupuff bongos = Drums::Bongos; sideStick = Drums::SideStick; rideBell = Drums::RideBell; + maracas = Drums::Maracas; gainLimiter = std::levels::ConstantGain (float<2>, 1.0f); mpe = std::midi::MPEConverter; } @@ -57,6 +58,7 @@ namespace Percupuff p.paramsOut -> electricSnare.paramsIn; p.paramsOut -> sideStick.paramsIn; p.paramsOut -> rideBell.paramsIn; + p.paramsOut -> maracas.paramsIn; // Send the midi events so the sound processors know when to start playing. midiIn -> mpe; @@ -71,6 +73,7 @@ namespace Percupuff mpe -> bongos.eventIn; mpe -> sideStick.eventIn; mpe -> rideBell.eventIn; + mpe -> maracas.eventIn; mpe -> noteOn; // Some sounds use noise. @@ -95,6 +98,7 @@ namespace Percupuff bongos -> gainLimiter; sideStick -> gainLimiter; rideBell -> gainLimiter; + maracas -> gainLimiter; // Output the result. gainLimiter -> out; diff --git a/dsp/drums/Maracas.cmajor b/dsp/drums/Maracas.cmajor new file mode 100644 index 0000000..3838efb --- /dev/null +++ b/dsp/drums/Maracas.cmajor @@ -0,0 +1,122 @@ +namespace Percupuff +{ + namespace Drums + { + processor Maracas { + input event (std::notes::NoteOn) eventIn; + output stream float<2> out; + input event Params paramsIn; + + float triggerVelocity = 0.0f; + int midiNotePitch = 0; + float outputLevel = 0.5f; + float panning = 0.0f; + + event paramsIn(Params p) { + midiNotePitch = int(p.maracasMidi); + outputLevel = p.maracasLevel * 0.01f; + panning = p.maracasPanning; + } + + event eventIn(std::notes::NoteOn n) { + if (int(n.pitch) == midiNotePitch) + triggerVelocity = sqrt(n.velocity); + } + + node envelope = Envelope; + node noise = std::noise::White; + + // Two fixed filters for "dark" and "bright" tone blending + node hpfBright = std::filters::butterworth::Processor( + std::filters::butterworth::Mode::highPass, 5000.0f + ); + + node lpfBright = std::filters::butterworth::Processor( + std::filters::butterworth::Mode::lowPass, 9000.0f + ); + + node hpfDark = std::filters::butterworth::Processor( + std::filters::butterworth::Mode::highPass, 3200.0f + ); + + node lpfDark = std::filters::butterworth::Processor( + std::filters::butterworth::Mode::lowPass, 7000.0f + ); + + void main() + { + envelope.attackIn <- 0.0008f; // quicker attack for snappy bursts + + loop + { + while (triggerVelocity == 0) + advance(); + + let baseVel = triggerVelocity; + triggerVelocity = 0.0f; + + float burstCount = 4 + (baseVel * 25); + float burstSpacing = 0.00008f + (1.0f - baseVel) * 0.0004f; + + for (int i = 0; i < int(burstCount); ++i) + { + float velJitter = 0.8f + (noise.out * 0.2f); + float burstVel = baseVel * velJitter; + + // control brightness blend (0 = dark, 1 = bright) + float brightMix = (noise.out * 0.5f + 0.5f); + + // shorter envelope release for each burst + envelope.releaseIn <- 0.0012f + (noise.out * 0.01f); + envelope.triggerIn <- void; + envelope.advance(); + + float gain = envelope.gainOut; + + // Alternate stereo motion slightly + float altPan = ((i % 2 == 0) ? -0.4f : 0.4f) + (panning * 0.01f); + + while (gain > 0.001f) + { + // Slightly “grainy” random noise burst + float raw = noise.out * (2.2f + noise.out * 0.1f); + + // Process both bright and dark chains + hpfBright.in <- raw; + lpfBright.in <- hpfBright.out; + float bright = lpfBright.out; + + hpfDark.in <- raw; + lpfDark.in <- hpfDark.out; + float dark = lpfDark.out; + + // Blend between dark and bright versions + float filtered = dark * (1.0f - brightMix) + bright * brightMix; + + float outSample = filtered * gain * baseVel * outputLevel; + + float leftGain = 0.5f * (1.0f - altPan); + float rightGain = 0.5f * (1.0f + altPan); + out <- (outSample * leftGain, outSample * rightGain); + + noise.advance(); + hpfBright.advance(); + lpfBright.advance(); + hpfDark.advance(); + lpfDark.advance(); + envelope.advance(); + advance(); + + gain = envelope.gainOut; + } + + float jitter = (noise.out * 0.0003f); + int silentSamples = int((burstSpacing + jitter) * float(processor.frequency)); + for (int s = 0; s < silentSamples; ++s) + advance(); + } + } + } + } + } +} diff --git a/percupuff.cmajorpatch b/percupuff.cmajorpatch index c0bcca4..64b07c4 100644 --- a/percupuff.cmajorpatch +++ b/percupuff.cmajorpatch @@ -29,6 +29,7 @@ "dsp/drums/Bongos.cmajor", "dsp/drums/SideStick.cmajor", "dsp/drums/RideBell.cmajor", + "dsp/drums/Maracas.cmajor", "dsp/Percupuff.cmajor" ], "view": { diff --git a/view/src/params.ts b/view/src/params.ts index 82e33fd..47c0504 100644 --- a/view/src/params.ts +++ b/view/src/params.ts @@ -35,6 +35,8 @@ export const instruments = { cowbell: { name: "Cowbell", group: "🐮", midi: 56 }, + maracas: { name: "Maracas", group: null, midi: 70 }, + // Example of adding an instrument without having it show up in the UI yet. sideStick: { name: "Side Stick", group: null, midi: 37 }, } as const;