Some trouble with sample playback based on sampleRate

Hello!

I’m having some trouble with sample (wav file) playback. When I change Rack’s sample rate during playback, the pitch of my sample playback changes slightly.

When the sample rate changes, I’m calling:

  void onSampleRateChange(const SampleRateChangeEvent& e) override
  {
    sample_rate = e.sampleRate;
  }

Later on, I compute the amount that I need to step ahead in a sample array using:

step_amount = sample.sample_rate / sample_rate;
playback_position = playback_position + this->step_amount;

However, this doesn’t seem to be working. For example, if the sample rate is 44100 and I change it to a different rate, the pitch raises or lowers just a little bit. Has anyone run into this before?

Cheers,
Bret

What types are step_amount and playback_position? Do you use int, float or double?

Everything (step_amount, playback position) is float until I calculate the index into the array. Eventually the float gets converted to an unsigned int, which is used as an index to look up the value in an array of floats. Do you think that I should be using doubles?

Here’s my sample class:

#pragma once

#include "AudioFile.h"

struct SampleAudioBuffer
{
  std::vector<float> left_buffer;
  std::vector<float> right_buffer;
  
  void clear()
  {
    left_buffer.resize(0);
    right_buffer.resize(0);
  }
  
  void push_back(float audio_left, float audio_right)
  {
    left_buffer.push_back(audio_left);
    right_buffer.push_back(audio_right);
  }
  
  unsigned int size()
  {
    return(left_buffer.size());
  }
  
  void read(unsigned int index, float *left_audio_ptr, float *right_audio_ptr)
  {
    if((index >= left_buffer.size()) || (index >= right_buffer.size()))
    {
      *left_audio_ptr = 0;
      *right_audio_ptr = 0;
    }
    else
    {
      *left_audio_ptr = left_buffer[index];
      *right_audio_ptr = right_buffer[index];
    }
  }
};

struct Sample
{
  std::string path;
  std::string filename;
  std::string display_name;
  bool loading;
  bool loaded = false;
  bool queued_for_loading = false;
  std::string queued_path = "";
  unsigned int sample_length = 0;
  SampleAudioBuffer sample_audio_buffer;
  float sample_rate;
  unsigned int channels;
  AudioFile<float> audioFile; // For loading samples and saving samples
  
  Sample()
  {
    sample_audio_buffer.clear();
    loading = false;
    filename = "[ empty ]";
    path = "";
    sample_rate = 44100;
    channels = 0;
    
    audioFile.setNumChannels(2);
    audioFile.setSampleRate(44100);
  }
  
  ~Sample()
  {
    sample_audio_buffer.clear();
  }
  
  void load(std::string path)
  {
    this->loading = true;
    this->loaded = false;
    
    float left = 0;
    float right = 0;
    
    // If file fails to load, abandon operation
    if(! audioFile.load(path))
    {
      this->loading = false;
      this->loaded = false;
      return;
    }
    
    // Read details about the loaded sample
    uint32_t sampleRate = audioFile.getSampleRate();
    int numSamples = audioFile.getNumSamplesPerChannel();
    int numChannels = audioFile.getNumChannels();
    
    this->channels = numChannels;
    this->sample_rate = sampleRate;
    sample_audio_buffer.clear();
    
    for (int i = 0; i < numSamples; i++)
    {
      if(numChannels == 2)
      {
        left = audioFile.samples[0][i];  // [channel][sample_index]
        right = audioFile.samples[1][i];
      }
      else if(numChannels == 1)
      {
        left = audioFile.samples[0][i];
        right = left;
      }
      
      sample_audio_buffer.push_back(left, right);
    }
    
    // Store sample length and file information to this object for the rest
    // of the patch to reference.
    this->sample_length = sample_audio_buffer.size();
    this->filename = system::getFilename(path);
    this->display_name = filename;
    this->display_name.erase(this->display_name.length()-4); // remove the .wav extension
    this->path = path;
    
    this->loading = false;
    this->loaded = true;
  };
  
  // Where to put recording code and how to save it?
  void initialize_recording()
  {
    // Clear out audioFile data.  audioFile represents the .wav file information
    // that can be loaded or saved.  In this case, we're going to be saving incoming audio pretty soon.
    audioFile.samples[0].resize(0);
    audioFile.samples[1].resize(0);
    
    // Also clear out the sample audio information
    sample_audio_buffer.clear();
    sample_length = 0;
  }
  
  void record_audio(float left, float right)
  {
    // Store incoming audio both in the audioFile for saving and in the sample for playback
    audioFile.samples[0].push_back(left);
    audioFile.samples[1].push_back(right);
    
    sample_audio_buffer.push_back(left, right);
    sample_length = sample_audio_buffer.size();
  }
  
  void save_recorded_audio(std::string path)
  {
    if(audioFile.save(path) != true)
    {
      // DEBUG(("Voxglitch sample.hpp::save_recorded_audio() - issue saving file to: " + path).c_str());
    }
  }
    
  void read(unsigned int index, float *left_audio_ptr, float *right_audio_ptr)
  {
    sample_audio_buffer.read(index, left_audio_ptr, right_audio_ptr);
  }
  
  unsigned int size()
  {
    return(sample_length);
  }
  
};


In my TapeRecorder is switched playback_position to double because by using float, pitch changes only worked fine for the first 6 minutes of the sample and after 6 minutes, pitch changes were somewhat quantized.

Thanks, I’ll give that a shot!

I got it working! I’m still not 100% sure what was wrong – I think that I had changed some code that didn’t convert a float to an unsigned int in the right way. Phew! What a relief.

2 Likes