I'm working on a prototype for a lightsaber that changes the pitch and volume of its hum sound as it moves. The current solution only changes the volume (easy), and it doesn't sound realistic enough. I know I need to use .wav or .aiff (not lossy like mp3) to accomplish this. Ideally I'm looking for a lightweight solution, as I plan on running the final iteration of this on an Arduino Nano extra. So, the question is, how can I alter the speed a .wav or .aiff is read/output (thus changing the pitch of the sound)? Regardless of computing power, does anyone know ANY approaches that could accomplish this in real time?
2 Answers
To change the pitch of a stream of audio samples, you need to change its sample-rate. The "easy" way to do that would be to change the frequency of the audio hardware's sample-clock, e.g. if you wanted to increase the playback pitch by 10% you would set the DAC to convert 52,800 samples-per-second rather than its normal 48,000 samples per second.
On most platforms I've used, however, you aren't allowed that level of control over the output-sample-rate setting. Usually they only let you choose from a small number of "standard" output rates like 44100, 48000, 96000, etc. Perhaps Arduino allows fine-grained control over the DAC's sample rate, however; in that case you have an easy solution.
If not, the alternative approach is to instead change the contents of the sample-stream you are feeding to the audio device in such a way that it acts like it is playing samples at a different rate. This is called sample rate conversion, and doing it accurately is non-trivial. If you don't need accuracy (e.g. because you are just doing lightsaber sound effects and adding noise to the output isn't a problem) you could do a quick-and-dirty job by simply dropping every (nth) sample when you want to increase the pitch, or repeating every (nth) sample when you want to decrease the pitch. The value of n would be determined by the ratio of your desired "virtual sample rate" compared to the audio output's actual sample rate; you'd need to do some math to figure it out but it shouldn't be too hard.
If, OTOH, you want a decent-quality sample rate conversion that doesn't add artifacts and noise into the output, then you'll want to use a proper sample-rate-conversion library like libsamplerate to handle the necessary math for you. It does a very good job of avoiding added noise and artifacts, and you can specify varying levels of quality if you need to trade off quality to get reduced CPU usage. For a real-time effect, you'd probably want to use the callback interface so that you can change the pitch-ratio on the fly.
Note that either of the above two approaches change not only the pitch of the playback but also its duration -- e.g. if you increase the perceived pitch of the sound effect by 10%, you'll also end up with a sound effect that finishes playing back 10% faster than the original. If you want to be able to change the sound's pitch without changing its duration, there are algorithms out there that can do that, but I don't have any experience with them so I can't comment on their applicability to this use case.

- 70,199
- 15
- 131
- 234
-
Man, you're a legend. I don't know if the Arduino will let me use the first approach (changing the DAC), but if so, then this would solve all my problems hands down. For the second approach, you're exactly right--I don't need perfect sound quality, and because I'm using VERY limited hardware, the fact that it's (clearly) much faster is desirable. Regarding the last point--fortunately I'll simply be looping the sound effect, so how long it plays is irrelevant. Thanks for this, it's really helpful, and you have a great day :) – Jasmine May 11 '21 at 22:53
-
Btw, one more (very crude) way to handle the problem would be to pre-render a number of audio files, each containing sound with a different pitch, and playback samples from a different audio file based on what pitch you want to produce. You could cross-fade between files as necessary to avoid any obvious glitching when transitioning from one file to another. The downside would be that this would use up extra memory and disk space, and might be too crude to give the desired result. – Jeremy Friesner May 12 '21 at 00:48
-
1I’ll add for this purpose a very basic linear interpolation will do the job. So if you step through sample at a fractional rate e.g. x1.25 speed, when you ask for sample index `1.25` your output should be `(1 - 0.25) * sample[1] + 0.25 * sample[2]`. or more generally: `(1 - alpha) * sample[i] + alpha * sample[i + 1]; i = floor(float_index); alpha = float_index - i:` Very thorough answer – fdcpp May 12 '21 at 08:29
Hmm... I'm not entirely sure how this would work. It's my understanding that .wav and .aiff files are raw audio (maybe that's why you say you need to use them) I did find this answer Python change pitch of wav file but it uses python, which won't work for your Arduino, but its possible you could apply this technique using C++. Maybe someone else who knows more about this could take a look at that and explain how it might be done in C++

- 1
- 2