Skip to content

Add tape modulation to delay module#80

Merged
xconverge merged 6 commits intobkshepherd:mainfrom
chattob:feat/tapeModulation
Jan 11, 2026
Merged

Add tape modulation to delay module#80
xconverge merged 6 commits intobkshepherd:mainfrom
chattob:feat/tapeModulation

Conversation

@chattob
Copy link
Copy Markdown
Contributor

@chattob chattob commented Jan 10, 2026

Add tape modulation to delay module

Adds a new "Tape" waveform option that simulates analog tape wow and flutter using Perlin noise.

Changes

  • New TapeModulator class using 1D Perlin noise
  • Separate wow (slow) and flutter (fast) modulation components
  • When using Tape modulation on delay time: dynamic minimum delay calculation prevents buffer underruns, allowing the delay to work as a real-time tape effect (delay time = 0, feedback = 0, mix = 1)

chattob added 6 commits December 25, 2025 23:29
Wrap time accumulators to prevent floating-point precision loss.
Without wrapping, t_wow_ and t_flutter_ grow indefinitely, causing
precision issues in the Perlin noise calculations that result in
getTapeSpeed returning constant values.
@chattob chattob changed the title Feat/tape modulation Add tape modulation to delay module Jan 10, 2026
@xconverge
Copy link
Copy Markdown
Collaborator

Right on! I had a chase bliss lost and found and sent it back because it wasn't doing it for me. My board is missing a lot of modulation options and this was definitely something I was interested in! Thanks!

@xconverge xconverge merged commit f2031eb into bkshepherd:main Jan 11, 2026
1 check passed
@chattob chattob deleted the feat/tapeModulation branch January 12, 2026 06:48
@chattob
Copy link
Copy Markdown
Contributor Author

chattob commented Jan 12, 2026

Great! I am glad you like it! Thanks for all the work you have been doing on the project!

Some optimizations I realized are possible only after the PR:

  • In tape_modulator: perm_[512] doesn't actually need to be so big since we wraparound X at 255. We only access up to index 256, so perm_[257] would be enough (saves 255 bytes). We could even have just 1 compile-time constexpr array instead of both p_ and perm_, eliminating the initialization loop.
  • In delay_module: The wow_rate and flutter_rate magic numbers might not be the best choice. You might want to try different rates for yourself.

@GuitarML
Copy link
Copy Markdown
Collaborator

GuitarML commented Jan 17, 2026

@chattob I just tried this out and I love it!! I'll dig into the code soon, but is there an easy way to apply this to the live guitar stream, not just the delay? I would really like it as a stand alone effect separate from the Delay module. Or is the delayline part of how it operates and it can't really be separated out. Either way thanks for the contribution and hard work!

Edit: I tried turning the delay time all the way down, and the mix all the way to the right (full delay) and it gets close to what I'm imagining.

Nevermind, I see in your initial PR that also turning the feedback to 0 will give the effect I'm looking for. Very cool!

@chattob
Copy link
Copy Markdown
Contributor Author

chattob commented Jan 18, 2026

@GuitarML Hi Keith! Yes, my intent when I coded this was to apply it as a live effect. I believe some latency is unavoidable because when the virtual tape speed goes above 1×, the reader has to consume samples faster than they arrive. So we need to keep a small buffer to read from. Maybe there is some smart way around it, but I didn't find it!

To get as close as possible to real-time, I don't use m_delaySamplesMin and set the min delay samples as low as possible instead (just making sure delayTarget is always >= 1 after applying modulation): see lines 273 to 288. With delay time all the way down the latency I measured is around 40ms, so not too bad ;)

@GuitarML
Copy link
Copy Markdown
Collaborator

GuitarML commented Feb 10, 2026

@chattob I'm looking at implementing your tape class in a separate effect with a simple delay line, not all the extra stuff in this particular module. I was wondering what the expected range of values is for the "speed" output of the tape class, I noticed you have some additional scaling (depth of 500, other multipliers) and I didn't follow exactly what's going on there. In this instance I was looking at a delay line with a minimum of 1 sample, maximum of 48000 samples for 1 second total. Also are the expected wow/flutter rate and depth inputs to the function 0 to 1?

@chattob
Copy link
Copy Markdown
Contributor Author

chattob commented Feb 10, 2026

@GuitarML the expected range for speed depends on wow_depth and flutter_depth. in TapeModulator::GetTapeSpeed you can find this line: float speed = wow_depth * wow + (flutter_depth * 0.2f) * flutter where wow and flutter are normalized between +/- 1.0f. So the range for speed is eventually wow_depth + 0.2f * flutter_depth. I have here a jupyter notebook where you can play with depth and rates values and visualize the resulting curve.

For the scaling, I basically started with the 500 samples depth which is also hard-coded for other types of modulations (l.290 in delay_module.cpp) and then adjusted wow_depth and flutter_depth to my taste.

@chattob
Copy link
Copy Markdown
Contributor Author

chattob commented Feb 10, 2026

@GuitarML if you are planning to build a custom tape effect module you might also find some inspiration on the TapeScam repo. It was shared on the daisy discord some time ago.
Here are some sound examples.

@GuitarML
Copy link
Copy Markdown
Collaborator

@chattob that all makes sense, thanks for the explanation and I'll check out that repo!

@GuitarML
Copy link
Copy Markdown
Collaborator

GuitarML commented Feb 12, 2026

@chattob one last question, in my delay I use fonepole to smooth out changes in the target delay time. Is this intended to be used with the tape modulation or does the perlin noise handle smoothing already? I guess I could try both ways and see what sounds better, maybe only smooth changes coming from the knob and not the tape, but what do you think?

Also wanted to say I tried this effect in stereo while making each tape rate slightly different, and it sounds awesome! I tried with both left and right rates the same, but it sounded like mono, I guess the perlin noise is deterministic? No randomness in it

@chattob
Copy link
Copy Markdown
Contributor Author

chattob commented Feb 16, 2026

@GuitarML Awesome! I definitely have to try it stereo! Indeed with the current implementation the perlin noise is deterministic. To make it random we would need to randomly shuffle the permutation table p_. This could be done once at init.

About the smoothing: perlin noise is smooth by itself. But maybe a smoothing would still be useful when modulating delay time directly... I'm honestly not sure. Never had any weird artifacts until now.

@chattob
Copy link
Copy Markdown
Contributor Author

chattob commented Feb 17, 2026

Actually even easier: just randomize t_wow and t_flutter at init to start at a different point in the pattern. I will implement and test it before doing a pull request

@GuitarML
Copy link
Copy Markdown
Collaborator

@chattob that sounds great! On smoothing the delay time parameter, I was more thinking that the additional smoothing by the delay was making it sound different than the intended tape effect, but I can try with and without to see if it makes any difference. The smoothing is necessary for changing the overall delay time, otherwise you get clicks and pops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants