Go Synth — Reverb Effect

Find the related code in the branch 002-reverb-effect

On my way to have fun, a reverb is more important than the actual capability to play different notes 😀

So I gave it a try, without reading documentation. I want to challenge my capabilities to reverse engineer a reverb.

So a reverb effect is similar to the change of your voice you can hear when you are in a big hall or a church. It is not a “delay” — that echo of your voice from a distant mountain, you know? It is much shorter.

My first assumption was: If I can create a buffer and apply a small percent of the oldest part of this buffer to the sound, I would offset this small percentage and it should be working… Or is it?

After some tweaks to ensure the reverb effect is also applied in the reverb buffer, I got the expected result:

My main oscillator is a SAW, so it should normally just go up and then reset to -1. With the reverb effect applied, we can see an offset repetition and fade every time it gets repeated.

So visually, it is a WIN! 🎉

…well… until you realize it just does not sound like a reverb effect… at all! At best I got a short time vibrato when changing the frequency… Hum… NOT APPROVED!

My next tentative is to store in the buffer ONLY the reverberation (and not the final oscillation level). This way, I can have 2 different rates. I can apply a 20% rate on the first reverb calculation and then make a 95% rate on the reverb itself. This will enable the reverb effect to last longer.

Indeed, in the previous picture, we can see the reverb effect fade too quickly — that is my hypothesis why we just do not hear it much 🤔

That’s somehow a little bit better… But definitely does not sound like a reverb!

I will have to start documenting myself a little big I guess…

See ya for the next one!

Experience: my face became a meme | Life and style | The Guardian
Continue Reading

Go-Synth — First sound!

Find the related code in the branch blog/001-simple-saw-oscillator

The only rule I have set for this project was: “it has to sound good and since the beginning!”.

So instead of building a whole structure, UI, and other things, I will first build a sound and then extends its properties.

Normal developers would create a base architecture to welcome chromatic notes to adjust the frequency, a voltage control, the oscillator, and eventually an envelope system.

Well, that’s NOT how I will plan this project. I have 2 priorities:

  • A SAW oscillator — because saw shape is damn good!
  • A reverb effect — who want a synth with no reverb?!

Yesterday, I was so happy as I could code the Saw Oscillator right the first time! Or did I? Let’s see that.

How do we even design a sound oscilloscope?

func OscFunc(stat float64, delta float64) float64 {
	// Do something and return the current shape level
	return r
}

If you have experience in Game Development, this will sound familiar. As for game movements, to ensure the “speed” of rendering the sound (or the movement) is NOT affected by the speed of your CPU, we need to work with delta increments.

That is, instead of describing the next position every millisecond, we describe the next position after a delta time. This time is the time elapsed since the previous description.

You can imagine a loop that calls our OscFunc every time the CPU can afford to do so. As the frequency to call this function might vary, we need to integrate the “delta” of time elapsed when we shape our sound.

My first attempt was pretty basic, but the generated sounds immediately told me “IT WORKS”!

func SawFunc(stat float64, delta float64) float64 {
	_, r := math.Modf(stat + delta)
	return r
}

Yeah, it’s just an addition! The math.Modf ensures that we only take values between 0 and 1:
math.Modf(0.5) == 0.5

math.Modf(1.5) == 0.5

math.Modf(1.15) == 0.15

math.Modf(2) == 0

math.Modf(2.3) == 0.3

It was only after I observed a record of that sound, I realized that I still messed up a detail: Wave should oscillate from -1 to 1, not from 0 to 1!

It just required a minor code change to fix it and have a perfect SAW oscillator that would serve as base sound for the next challenges of this project!

func SawFunc(stat float64, delta float64) float64 {
	_, r := math.Modf(stat + delta)
	return r*2.0 - 1.0
}

Okay! Now it’s working like a charm! Time to write some tests and I can close this topic!

Many challenges on this project will NOT be test driven. Indeed, my aim is to have fun and to explore possibilities, I do not expect a predictable result for a given feature and I totally allow myself to change the expectations based on what I discover on my way. Tests enables to stay in a given way, so I would only write tests when I found which way I want the feature to take.

That’s it for today! See ya for the next one!

Continue Reading