changing pitch of audio samples - TeensyVariablePlayback library

Moo

Well-known member
github.com/newdigate/teensy-variable-playback
I started this endeavour a few years back, I needed samples played at variable playback rate.

The initial implementation was a hack but it worked okay for non-interpolated resampling, a little buggy with linear interpolation - especially in reverse. (Sounds awful without interpolation, almost like 8-bit.. linear interpolation is slightly better)

There was a lot of interest in this code and I finally had a chance to iron out the wrinkles and implement quadratic interpolation. 300 commits later and it sounds much better and its stable.

It can play from array or from uSD card file (raw or wav)

I've made it available from the Arduino library manager, just search TeensyVariablePlayback - and have a look at the examples.

I'm going to look at implementing stereo, when I get a chance next.

I've tested on teensy 4.1 and teensy 3.6

Please feel free to use, develop, improve. I would be interested to know what you think.

Cheers
Nic
 
So far so good, I've got 4 streams playing the same file at different speeds, without any glitches or errors. I'm using a teensy 4.1, SD card and a UDA1334 dac. Not sure what the theoretical maximum is but Ill report what I learn.

I am getting a non critical stop compiler error regarding the filename variable.
 
Okay, 8 instances of the same file at once at different playback rates... which is so cacophonous I can't really tell how well it's working. I don't hear any distinct digital errors, no popping or ugly distortion. I'm impressed.
 
I've made a polyphonic sampler to go with the variable playback rate, Here's a video https://www.youtube.com/watch?v=qDfv6R2WrX4 and code here https://github.com/newdigate/teensy-polyphony

If you get latest of the variable playback v1.0.6 the interpolation sounds much better (using float) - but im not sure about the performance on a t3.6, as I dont think it has a hard floating point unit. T3.6 did manage to play a single voice with quadratic interpolation, but i didnt want to push it.
 
Updated, thank you.

I've lowered the number of streams in my project to 6 leaving overhead for filters + stuff. Works well.
 
Last edited:
I see you have a mathematical method of matching a midi note to a specific playback rate. My math-fu isn't that strong but I knew it was possible. Such solutions come to me better when my consciousness is out of the way, like asleep or under heavy caffeination. :)

I went the long way around, using a strobe tuner with a selected temperament to build a look-up-table...a method that allows for alternative temperaments and micro tuning. I'm not 100% certain of this, but I think it should actually be faster for the processor to fetch a variable from an array than it is to calculate a double.
 
I see you have a mathematical method of matching a midi note to a specific playback rate. My math-fu isn't that strong but I knew it was possible. Such solutions come to me better when my consciousness is out of the way, like asleep or under heavy caffeination. :)

yes, the rate can be calculated pretty easily given the original sample note and the desired note.

I went the long way around, using a strobe tuner with a selected temperament to build a look-up-table...a method that allows for alternative temperaments and micro tuning. I'm not 100% certain of this, but I think it should actually be faster for the processor to fetch a variable from an array than it is to calculate a double.

Since the calc only happens once when you play a sample, I don't think it'll make much difference.
 
Does the effect work for human voices, too?
Do you have an example mp3?

It works with any 16bit mono Little endian 44100Hz raw sample data. So it should be able to do voice. I'll make a demo....
 
Moo, just got my board today. Getting everything up and running tomorrow. Looking forward to testing out your library!

Interested to see how you get on...

I've been testing stereo, but somewhere between my sample loader, polyphonic mechanism, im getting a few tiny audible pops on the lower notes...
 
Moo, just got my board today. Getting everything up and running tomorrow. Looking forward to testing out your library!

I've managed to fix a few tiny issues and it sounds great in stereo now. I tested on teensy 4.1 with 8 stereo voices and it sounds good.

I merged the stereo branch and release TeensyVariablePlayback 1.0.7, it should be available through the Arduino library manager soon.

I also have a few little helpers if you are interested -
teensy-sample-flashloader to load samples from SD card to flash memory...
teensy-polyphony to play multiple voices simultaneously...
teensy-eurorack-audio-gui audio gui with resampling components under "newdigate"...

Screenshot 2021-07-13 at 11.53.14.png
 
This is awesome, exactly what I need. My project is almost ready.
I only have a small issue. The sound I am playing in a loop is clipping a little, is that normal? Not happening before I started to use the library (I was using the granular effect).
What are the best practices to avoid clipping using your library?

Best regards,
Andy
 
What are the best practices to avoid clipping using your library?

Hi Andy!

First off, try padding the beginning and end of your sample with a couple of samples of silence. I think 4 samples of silence will probably be enough. Hopefully I will fix this someday.

Also, if you are playing multiple samples, make sure they are not full volume - two loud samples at full volume can cause an overflow which sounds awful.

If these don't work, then you can send me your sample and your sketch and I can try figure out whats wrong :)
 
Amazing work here, thanks for all of your efforts.

I've been having trouble returning lengthMillis and positionMillis using this library. They always return zero no matter what I try. Looking at playsdresmp.cpp I see AudioPlaySdResmp::lengthMillis() and AudioPlaySdResmp:: positionMillis() differs from play_sd_wav.cpp in the Audio library (I can get the Audio library to work as expected).

Has anyone had success returning millis? Sample sketch below.

Thanks for any ideas. I'm brand new to Teensy and having loads of fun so far.

Code:
// Plays a Wav (16-bit signed) PCM audio file at slower or faster rate
// this example requires an uSD-card inserted to teensy 3.6 with a file called DEMO.WAV
// https://github.com/newdigate/teensy-variable-playback

// Unable to return lentgthMillis and positionMillis? 

#include <Arduino.h>
#include <Audio.h>
#include <SD.h>
#include <Bounce.h>
#include <TeensyVariablePlayback.h>

int mode = 0;  // 0=stopped, 1=recording, 2=playing

// GUItool: begin automatically generated code
AudioPlaySdResmp         playSdWav1;     //xy=324,457
AudioOutputI2S           i2s2;           //xy=840.8571472167969,445.5714416503906
AudioConnection          patchCord1(playSdWav1, 0, i2s2, 0);
AudioConnection          patchCord2(playSdWav1, 0, i2s2, 1);
AudioControlSGTL5000     audioShield;
// GUItool: end automatically generated code

//#define A14 10

char* _filename = "SDTEST1.WAV";
const int analogInPin = A14;
unsigned long lastSamplePlayed = 0;

double getPlaybackRate(int16_t analog) { //analog: 0..1023
    return  (analog - 512.0) / 512.0;
}

Bounce buttonMillis = Bounce(29, 8);
Bounce buttonPlay = Bounce(31, 8);

void setup() {
    
    pinMode(29, INPUT_PULLUP);
    pinMode(31, INPUT_PULLUP);
  
    analogReference(0);
    pinMode(analogInPin, INPUT);   // i.e. Analog

    Serial.begin(57600);

    if (!(SD.begin(BUILTIN_SDCARD))) {
        // stop here if no SD card, but print a message
        while (1) {
            Serial.println("Unable to access the SD card");
            delay(500);
        }
    }

    AudioMemory(24);

    audioShield.enable();
    audioShield.volume(0.5);

    playSdWav1.enableInterpolation(true);
    int newsensorValue = analogRead(analogInPin);
    playSdWav1.setPlaybackRate(getPlaybackRate(newsensorValue));
}

void loop() {

    buttonMillis.update();
    buttonPlay.update();

    if (buttonMillis.fallingEdge()) {
    Serial.println("Millis Pressed");
    getMillis();
    }

    if (buttonPlay.fallingEdge()) {
    Serial.println("Play Pressed");
    if (mode == 0) startPlaying();
    }
  
    int newsensorValue = analogRead(analogInPin);
    playSdWav1.setPlaybackRate(getPlaybackRate(newsensorValue));
}

void startPlaying() {
  Serial.println("Start Playback");
  playSdWav1.playWav(_filename);
  mode = 2;

}

void getMillis(){
  Serial.print("positionMillis() = ");
  Serial.println(playSdWav1.positionMillis());
  Serial.print("lengthMillis()   = ");
  Serial.println(playSdWav1.lengthMillis());
  }

namespace std {
    void __throw_bad_function_call() {}
    void __throw_length_error(char const*) {}
}
 
Back
Top