Code module - Gemini Gem
in Drambo
Hi all, I made this for my own use and it’s been error free for a few days so figured I’d share it.
https://gemini.google.com/gem/1rHBbsL4HRJRlDwdYez1_YQVovmZDIdAy?usp=sharing
this gem is loaded with available code documentation, as well as all of the factory preset code, plus some generated content meant to prevent frequent errors IN LLM generated code.
have fun, and remember to share anything cool you make! Feel free to report frequent errors (along with associated code) and I can try and remediate.
Comments
That was pretty cool. It took several iterations and starting over from scratch once to get anything that works. I finally got smart and stopped gemini from trying to fix broken attempts. After starting over it succeeded in making basically the equivalent of the bit reducer to use on LFOs, but also asked if I wanted to add a smoothing control. That led to another series of failures, and some working attempts that weren't what I liked. In the end I ended up with something more useful than my original request.
So, not bad, but persistence required. The downside is I don't understand the code. The upside is now I can try to understand the code.
Argh! I had been using with consistent success (meaning no errors, not something totally revelatory).
I didn't expect success right off. Part of me was hoping for a fail. I had to wipe the smirk off my face when it actually ended up doing a good job and solving some things it would have taken me hours to figure out.
Will be trying this, thanks @bcrichards.
I’m kinda surprised that there don’t seem to have been any Code module creations shared on PatchStorage yet?
Certainly seems to work for me @bcrichards, it just built a physical modeling synth in seconds.
Question: what’s the best way to get it to just add every feature it can instead of the obligatory ‘would you like me to add xyz’ which seems to appear at the end of every one of its posts?
It works but then repeatedly fails when trying to make things more complex. Gave up eventually.
In such situations, where you’re going back and forth, copying and pasting code, it would be really helpful if there was a simple, quick way to select all text in the Code module’s text box. Worth considering @giku?
Long press and tap ‘select all’
Doh! Thank you @Intrepolicious.
Vector morph w/sub oscillator
#persample
// --- INPUTS & PARAMETERS ---
p = input(pitch, Pitch)
g = input(gate, Gate)
// Morphing & Timbre
morph = param(value, Morph)
richness = param(value, Richness)
detune = param(value, Detune)
// Sub & Saturation
subLevel = param(value, SubLevel)
drive = param(value, Drive)
// Standard Synth Controls
att = param(time, Attack)
dec = param(time, Decay)
cut = param(pitch, Cutoff)
res = param(value, Resonance)
// --- ENVELOPE & LFO ---
ampEnv = envAD(g, att, dec)
drift = oscSin(0.5) * 0.02
// --- OSCILLATORS ---
// Main Dual Stack
osc1 = oscSin(p + drift)
osc2 = oscSin(p * (1.0 + detune * 0.01))
sig = (osc1 + osc2) * 0.5
// Sub Oscillator (Exactly half the frequency of Pitch)
subOsc = oscSin(p * 0.5)
// --- VECTOR MORPHING ---
folder = sin(sig * (1.0 + richness * 12.0))
shaper = tanh(sig * (1.0 + richness * 20.0))
sigMorphed = mix(folder, shaper, morph)
// --- MIXING & FILTERING ---
// Blend the Sub in before the filter so it gets shaped too
combined = sigMorphed + (subOsc * subLevel)
// Filter stage
filtered = lpf(combined, cut, res)
// Final Drive: Glue the Sub and Morph together
// This prevents the sub from sounding "detached"
out = tanh(filtered * (1.0 + drive * 3.0))
// Apply Envelope
finalOut = out * ampEnv
output(finalOut, audio)
Very nice, thanks for sharing.
On iPhone to the best of my knowledge it’s double tap&hold on a blank space (no text), try several times until the popup finally says select, move to the big menu, press select all then copy
doesn’t matter if it’s on notes, or giku’s text windows, it the OS’s fault for sucking at text selection after so many versions still
btw, looking forward to try your vector thing
Here are two I like so far.
Pultec:
#persample
/*
Pultec EQP-1A Emulation
Architecture: Transformers -> Parallel EQ Summing -> Asymmetrical Tube Amplifier
*/
inputSig = input(audio)
// — Parameters —
// Parameter names are unified into single words to prevent parser errors.
pLowFreq = param(pitch, LowFreq) // LF Selector: 20, 30, 60, 100 Hz
pLowBst = param(value, LowBoost) // LF Boost Amount
pLowAtt = param(value, LowAtten) // LF Attenuate Amount
pHiFreq = param(pitch, HiFreq) // HF Boost Selector: 3k to 16k Hz
pHiBW = param(value, HiBandwidth)// HF Q: 0 = Broad, 1 = Sharp
pHiBst = param(value, HiBoost) // HF Boost Amount
pHiAttF = param(pitch, HiAttFreq) // HF Atten Selector: 5k, 10k, 20k Hz
pHiAtt = param(value, HiAtten) // HF Attenuate Amount
pDrive = param(value, TubeDrive) // Adds Asymmetrical Tube Saturation
// — 1. Transformer / Input Stage —
// Subtle roll-off at the extreme highs and lows (simulating analog hardware)
x = hpf6(inputSig, 15)
x = lpf6(x, 22000)
// — 2. Filter Math (Parallel Processing) —
// A. Low Shelf Boost
// Pultec boosts up to ~14dB. A linear multiplier of 5.0 equals roughly +14dB.
lowBoostSig = lpf6(x, pLowFreq) * (pLowBst * 5.0)
// B. Low Shelf Cut (Attenuate)
// THE PULTEC TRICK: The cut’s corner frequency is inherently shifted ~30% higher than the boost.
// Subtracting a Low Pass Filter from the dry signal creates a Low Shelf cut.
lowCutSig = lpf6(x, pLowFreq * 1.3) * (pLowAtt * 0.85)
// C. High Peak Boost (Bell)
// Convert Bandwidth (0-1) to SVF resonance (0-0.95 to avoid self-oscillation).
res = pHiBW * 0.95
hiBoostSig = bpf(x, pHiFreq, res) * (pHiBst * 5.0)
// D. High Shelf Cut (Attenuate)
// Subtracting a High Pass Filter from the dry signal creates a High Shelf cut.
hiCutSig = hpf6(x, pHiAttF) * (pHiAtt * 0.85)
// — 3. Parallel Summing —
// Sum the dry signal with all parallel bands
eq = x + lowBoostSig - lowCutSig + hiBoostSig - hiCutSig
// — 4. Output Stage (Asymmetrical Tube Amplifier) —
// Real vacuum tubes clip asymmetrically (the positive half of the waveform
// distorts differently than the negative half), generating warm even-order harmonics.
driveSig = eq * (1.0 + pDrive * 2.0)
posClip = tanh(driveSig)
// Compress the negative half slightly more for asymmetry
negClip = tanh(driveSig * 1.2) * 0.833
// Apply ternary logic for positive/negative halves
tube = driveSig > 0.0 ? posClip : negClip
// Blend the pure EQ and the Tube Character based on the Drive parameter
out = mix(eq, tube, pDrive)
output(out)
Glue Compressor
#persample
// 1. Inputs
In = input(audio, AudioIn)
// 2. Parameters (0.0 to 1.0 ranges)
ThreshKnob = param(value, Threshold)
RatioKnob = param(value, Ratio)
AttackKnob = param(value, Attack)
ReleaseKnob = param(value, Release)
MakeupKnob = param(value, Makeup)
ClipKnob = param(value, ClipAmount)
MixKnob = param(value, DryWet)
// 3. Parameter Mapping
// Threshold
threshold = pow(ThreshKnob, 3.0) + 0.0001
// Ratio (1:1 to 10:1)
ratio = 1.0 + (RatioKnob * 9.0)
// Attack Time
att_sec = 0.0001 + (AttackKnob * 0.03)
attCoef = 1.0 / (att_sec * sampleRate)
// Release Time
rel_sec = 0.05 + (ReleaseKnob * 1.15)
relCoef = 1.0 / (rel_sec * sampleRate)
// Makeup Gain
makeup = 1.0 + (MakeupKnob * 14.8)
// 4. Logarithmic VCA Sidechain
InLevel = abs(In)
InLog = log(InLevel + 0.000001)
ThreshLog = log(threshold)
// Calculate static Gain Reduction
StaticGR = max(0.0, InLog - ThreshLog) * (1.0 - 1.0 / ratio)
// Sidechain Envelope Smoothing
coef = StaticGR > SmoothGR ? attCoef : relCoef
SmoothGR = SmoothGR + (StaticGR - SmoothGR) * coef
// 5. Apply Compression
LinearGain = exp(-SmoothGR)
CompSignal = In * LinearGain * makeup
// 6. Variable Soft Clipping Stage
// Calculate the drive into the soft clipper (ranges from 1x to 5x drive)
Drive = 1.0 + (ClipKnob * 4.0)
// Apply the hyperbolic tangent (tanh) soft clip.
// We divide by ‘Drive’ at the end to automatically compensate for the volume
// boost, ensuring it acts as a peak-tamer rather than a volume raiser.
Saturated = tanh(CompSignal * Drive) / Drive
// Morph between the perfectly clean digital signal and the analog soft-clipped signal
GlueSignal = mix(CompSignal, Saturated, ClipKnob)
// 7. Parallel Mix (Dry/Wet)
Out = mix(In, GlueSignal, MixKnob)
output(Out, audio)
Wow, this looks amazing! Can’t wait until I get home and give it a go
This is so cool, thanks for sharing!
After quite a couple of prompts, it made me this cool delay with saturation, digital degradation and filtering in the feedback loop.
The initial settings are not very useful, maybe I figure out where to change them (hints welcome:).
#persample
// 1. Inputs and Parameters
sigIn = input(audio, AudioInput)
fbAmount = param(value, Feedback)
beatDiv = param(value, BeatDivision)
msOffset = param(value, MsOffset)
lpCutoff = param(pitch, LowPass)
hpCutoff = param(pitch, HighPass)
preDrive = param(value, InputDrive)
postDrive = param(value, FeedbackDrive)
bitCrush = param(value, BitCrush)
resample = param(value, Resample)
dryWet = param(value, DryWet)
// 2. Precise Timing Calculations
// Tempo is provided by the Drambo host
beatSec = 60 / tempo
// Scale 0-1 param to 0-4 beats (e.g., 0.25 = 1 beat)
targetBeats = beatDiv * 4
// Offset scaled to 0.1 seconds (100ms max)
offsetSec = msOffset * 0.1
dTime = (beatSec * targetBeats) + offsetSec
// 3. Input Drive (Limiter 1)
// Tanh provides soft-clipping before entering the delay line
drivenIn = tanh(sigIn * (1 + preDrive * 8))
// 4. The Feedback Loop
// delayedSig pulls the audio from the buffer based on dTime
delayedSig = delay(fbSig, dTime)
// --- LO-FI PROCESSING ---
// A. Sample Rate Reduction (Resampling)
// srCounter accumulates based on the Resample parameter
srCounter = srCounter + (resample * 0.5)
if (srCounter >= 1) {
heldSample = delayedSig
srCounter = 0
}
// If Resample is low, we use the clean signal; otherwise, the held one
lofiSig = (resample > 0.01) ? heldSample : delayedSig
// B. Bit Reduction (Quantization)
// Reduces the vertical resolution of the waveform
steps = 64 - (bitCrush * 62)
lofiSig = floor(lofiSig * steps) / steps
// C. Feedback Saturation & Filtering
// postDrive adds gain inside the loop for cumulative distortion
saturatedFb = tanh(lofiSig * (1 + postDrive * 8))
f1 = lpf(saturatedFb, lpCutoff, 0.2)
f2 = hpf(f1, hpCutoff, 0.1)
// — FEEDBACK LIMITER (Limiter 2) —
// Final safety check to prevent infinite volume buildup
limitedFb = clamp(tanh(f2), -1, 1)
// Sum Input + Processed Feedback into the loop variable
fbSig = drivenIn + (limitedFb * fbAmount)
// 5. Final Output
out = mix(sigIn, fbSig, dryWet)
output(out, audio)
What I would like to do, is put the code rack into a processor rack to give it more tweakable controls, but the code rack doesn’t produce (receive?) any signal once it’s in the rack and I’ve checked all the connections. Can I do anything about that,is it a known limitation or a bug?
@tyslothrop1 My advice is get it working outside a rack first. Then if it doesn’t work inside a rack where you connect the inputs right, it might be a bug (of the rack module, which I think deserves more love)
a project file always helps if you want feedback
Thanks for sharing your presets on PatchStorage @bcrichards, so much easier to download the presets than copying and pasting the code from here.
@Intrepolicious Your Vector Morph is easily interesting enough to put on PatchStorage too!
Thanks, I think the problem is pasting the code module. If I put an empty code module into a rack and paste code into the module, it works:) It just doesn't if I paste the code module with code inside.
I have pasted code modules (with code) several times without issues, except not in a rack scenario. I’ll try that to see if I can reproduce.
I have a vague feeling there are some quirks with the custom rack, that’s why I said it deserved some love and also suggested to try it outside the rack to remove it from the equation
I just had very quick test, and I can’t find the problem. The code module gets pasted correctly, of course I need to create inputs on the rack, and re-do the modulation connections but it seems to work
what exactly doesn’t work for you?
Thanks for looking into it @pedro. I wasn’t talking about the (just) rack, but a processor rack. But it turns out the problem isn’t the rack, but pasting in general. Although a session restart fixes that. Here’s an unlisted video, in which I add a code module, select a preset and it works, then I cut and paste it, and it doesn’t. Then I restart the session and it works again. And afterwards I do the same inside an instrument rack. It was an entirely fresh session, and the same happens with code modules, that have fx in them.
https://youtu.be/v6hiHkdLlN4
Hi everyone,
really like the code module and getting nice stuff done, but I just can’t seem to get a code for a delay to work. Teally would like some help. Thanks!
There’s two recipes at the end of the code docs that might help you, the karplus strong and the fb delay
but what exactly is your problem or what are you trying to achieve?
@pedro
thanks. I got a delay code module now. AI helped me and I understand it now.
Hi all, I’m trying to code a simple sinefold effect, but everything I try does not work. And I can code this easely in other apps. Anyone can provide me with a code?
thanks!
@Stanley I gave it a quick try, lots of room for improvement but looks close for starters
// Wavefolder
#persample
in = input(audio, Input)
fold = param(value, Fold) // Adjusts the folding intensity (0 to 1)
symm = param(value, Symmetry) // Shifts the center to create asymmetric folding (0 to 1)
gain = param(value, Gain) // Input drive (0 to 1)
// 1. Apply Gain to the input
// We scale the gain so 0-1 param covers a useful range (up to 4x boost)
drive = 1 + (gain * 3)
sig = in * drive
// 2. Apply Symmetry (DC Offset)
// We shift the signal by up to 1.0 before folding
offset = (symm * 2) - 1
sig = sig + (offset * 0.5)
// 3. Wavefolding Calculation
// Based on the sinusoidal folding formula: out = sin(in * fold_amount)
// We map the 'fold' parameter to a range that allows multiple folds
foldAmount = 1 + (fold * 10)
out = sin(sig * foldAmount)
// 4. Post-fold Normalization
// Wavefolding can reduce the perceived volume; we apply a slight
// makeup gain based on the fold amount to keep levels consistent
makeup = 1 + (fold * 0.5)
out = out * makeup
// 5. Basic anti-aliasing
out = lpf6(out, 15000)
output(out, audio, Output)
@pedro thanks!
I found out the link at the very start of this topic is the best way to go, but thank you so much.
Use what works for you. Ben's Gem is a great help. I've used it before but now I just find it overkill sometimes not because there's something wrong with it, but because I explained that to gemini already (under my google account) so there's no need to repeat.
For instance this is the prompt I used that got me something working at first try (did a bit of polishing later):
---
I want you to write me a Drambo code module audio FX using the pseudo language described here:
https://www.beepstreet.com/drambo-docs/code-module.html
What I want to build is a wavefolder effect.
Structure of the code should be like this:
#persample // <--- my bad, this wouldn't be needed as there's no feedback
in=input(audio, Input)
// out = ...generate wavefolded output
output(out, audio, Output)
There should also be at least these parameters (they range from 0 to 1):
fold=param(value, FoldAmount) // Adjusts how many times the wave is folded
symm=param(value, Symmetry) // Allows different folding of the top and bottom parts of the wave, creating varied timbres
but you can include more if you see fit
Use ideas from here
https://ccrma.stanford.edu/~jatin/ComplexNonlinearities/Wavefolder.html
---
Just a little krell patch, learned from @giku ‘s own “simple krell”
But I want to break out of sound generation inside the code module, so I’m building an EOC self-repeating random envelope, that you just get going but then at each EOC outputs random: attack, decay, velocity, pitch, PC (how can I do this last one?)
Pretty cool with a fb loop and an env follower, IMHO
Better