Sound effects has been notably absent in Rad Trails since its inception a few years ago. I finally took up the challenge to implement some sound effects such as engine sounds this weekend.

The approach to implementing engine sounds in Rad Trails was based on some basic principles. Most importantly I wanted find a solution using only rudimentary techniques. Instead of relying on actual audio samples, that could produce a more realistic effect, I wanted to put emphasis on Rad Trails lo-fi aesthetics.

Infact the new engine sounds in Rad Trails is so simple it’s done using only two parallel oscillators generating two different types of waveform frequencies.

Basic theory

The basic idea of replicating engine sound is to generate a frequency that goes from a low frequency to a rapidly higher frequency as the throttle is increased.

Up shifting gears will lower the base frequency and also decrease the rate at which the frequency increases, and vice versa.

The frequency should be aligned with the RPM of the motor. On a dirt bike the RPM will increase rapidly when applying the throttle and also decrease fast when the throttle is no longer applied.

Fugly psuedo graph to illustrate how the base frequency transforms when the throttle is applied.

Implementation

I used a fantastic audio generation framework for iOS, called AudioKit to generate the sounds in Rad Trails.

Oscillator setup:

let idleFrequency = 20.0 var oscillator: AKOscillator = { let waveform = AKTable(.sawtooth) let oscillator = AKOscillator(waveform: waveform) oscillator.frequency = self.idleFrequency oscillator.amplitude = 0.025 oscillator.rampDuration = 0.0 return oscillator }() var oscillator2: AKOscillator = { let waveform = AKTable(.sine) let oscillator = AKOscillator(waveform: waveform) oscillator.frequency = self.idleFrequency oscillator.rampDuration = 0.0 oscillator.amplitude = 0.2 return oscillator }()

variables:

var rpm = 0.0 let maxRpm = 1350.0 var gear = 0 let maxGear = 3 var frame: Double = 0 // frame count

Gear shift functions:

func upShift() { if gear < maxGear { gear += 1 rpm -= 100 } else { rpm -= 10 } } func downShift() { if gear > 0 { gear -= 1 rpm += 50 } else { rpm += 10 } }

As you can see the RPM is also adjusted when it hits the ceiling or the floor. This creates a little bit of a humming effect.

So, there is only two states in Rad Trails. Either the throttle is applied or it’s not. Whenever the throttle is applied I’m just increasing a variable called rpm according to the graph above.

The basic formula looks something like this:

// Game loop if throttleDown { rpm += (maxRpm - rpm) / (10.0 + (gear * 100)) if rpm > maxRpm - 100 { upShift() } } else { // Rapidly decreasing RPM rpm += -rpm / 20.0 if (oscillator.frequency < (idleFrequency + 5) + (gear * 25)) { downShift() } }

Setting the oscillator frequencies.

// Game loop frame += 1.0 let frequencyTransformation = sin(frame * gear) * 5 oscillator.frequency = (rpm * 0.25) - (gear * 25) + frequencyTransformation oscillator2.frequency = oscillator.frequency

So I’m just setting the same frequency on both oscillators, the sawtooth oscillator generates a intensive sound and the sine wave oscillator helps to balance out the sound a bit and generates a more nuanced effect.

To get a little bit more variance in the effect I added a sin transformation to the frequency so that the frequency fluctuates a bit as the RPM increases.

And that is basically it. Please note that this example is filled with arbitrary and numbers and assumptions.

Results

I’m somewhat satisfied with the result, as I think it resembles, albeit vaguely, how an engine sounds. With that said there is a lot that can be done to improve it. So please lower your expectations before playing the clip and keep in mind that one of the goals was to complete this using very basic techniques.

This is due to be released in the next version of Rad Trails that is awaiting App Store review at the time of writing. In the mean time please check out the current version of Rad Trails in the App Store 🤙

Daniel aka Crazy D