Output audio downsampled to 12bit & 26040hz sample rate

Hello,

Using a Daisy Pod, I’m looking for a way to downsample the incoming audio to 12bit/26040hz. I tried…

  1. the bit crusher class, other values work, but I do not hear output with the above values

  2. the decimator class, which supports the ability to set the bit depth, but it’s unclear to me what settings to use for the setBitDepthFactor() and/or SetDownSampleFactor() parameters to get the 26040hz goal.

  3. the crush mode from the multi effect example works, again, it’s unclear what tweaks I’d need to make to get the above values.

Any assistance would be appreciated, thanks!

I’m trying to do something similar-ish as well only by trying to mess with the hardware rather than using the audio engine.

In my case I need a variable sample rate as I’m wanting to simulate how the GameBoy output audio on it’s WAVE channel. The GB used a timer that would be adjusted according to the desired pitch. The timer corresponded to the when to switch to the next sample in the WAVE. The clever thing about doing it this way is I don’t have to worry about interpolation. As long as the SPI bus is running fast enough, the precision lost is minimal and it’s appropriately chippy. You can read more up on what I’m trying to do here.

So far, I’ve been thwarted since the Daisy likes to use fixed sample rates for the Audio DAC as well as the CV OUTs (I’m building against the Daisy Patch MS). Technically I’m sure it’s possible to get around both but I’m currently trying the CV route since it seems like it might be easier to do. It does have me wondering about perhaps switching to a Teensy plus an SPI DAC since I’m it seems like I’m having to circumvent much of what makes the Daisy awesome.

While I was digging around the forums, I recall reading one of the devs said they are working on arbitrary sample rates at least in between 8Khz and 96Khz so that might be something to follow (I don’t see any support for that in the lib documentation yet though). This would be WONDERFUL though so I’m hoping we see it.

Not sure if any of that is helpful to you or not but I tried :slight_smile: I think were you not to do any of the above, you’re going to be stuck where I was, which is to resample and interpolate your wave up to whatever the current (fixed) sample rate of the DAC is set to.

Thanks for sharing what you’re working on and your progress, it sounds cool :slight_smile: I’ll dig around the forum, per your suggestion.

A bit more info…

Looking at the bit crush module source…

float Bitcrush::Process(float in)
{
    float bits    = pow(2, bit_depth_);
    float foldamt = sample_rate_ / crush_rate_;
    float out;

    out = in * 65536.0f;
    out += 32768;
    out *= (bits / 65536.0f);
    out = floor(out);
    out *= (65536.0f / bits) - 32768;

    fold.SetIncrement(foldamt);
    out = fold.Process(out);
    out /= 65536.0;

    return out;
}

I assume the middle section adjusts the bit depth, not sure if it’s also downsampling and the lower section, using the fold module adds distortion?

Looking online I found this http://www.willpirkle.com/forum/algorithm-design/bit-reduction/ bit reduction formula. I modified it as…

float myBitcrush(float in)
{
    // bit reduction
    /*
    y(n) = floor(x(n) * 2^(b-1)) / (2^(b-1))
    y(n) = out signal
    x(n) = in signal
    b = bit depth
    */
   
    float bits    = pow(2, (bit_depth - 1));
    float foldamt = sample_rate / crush_rate;
    float out;

    // step 1 - bit reduction
    out = floor(in * bits) / bits;
    
    // step 2 - distortion only?
    // fold distortion - does not
    //fold.SetIncrement(foldamt);
    //out = fold.Process(out);
    //out /= 65536.0;

    return out;

The first part of adjusting the bit depth seems to work, however the fold section does not, hence I commented it out.

So at this point it appears I can adjust the bit depth with the above code OR using the decimator with SetBitsToCrush(4) 16 - 4 = 12 (goal) and the other parameters set to 0.

Gonna keep investigating :smile:

If it helps, I’ve been working on my project basically all day haha going from Daisy to O_C to using a Teensy with a DAC. The latter has been simplest but for the Daisy, I did think about doing something kinda odd which might be helpful for you:

// Callback to update the wave index when our timer expires
// which, in turn, changes the value that is output in the
// audioCallback. This also resets the timer to the current
// sample_rate which we update outside of this interrupt callback.
void waveStepCallback() {
  noInterrupts();
  wave_index++;
  if(wave_index > WAVE_SLOTS)
    wave_index = 0;
  waveTimer->setOverflow(sample_rate);     
  interrupts();
}

// Surely this won't work. Output the same value constantly
// as the waveStepCallback is what is actually changing the 
// value.
void audioCallback(float**  in, float** out, size_t size) {
  for(size_t i = 0; i < size; i++)
    out[0][i] = out[1][i] = audio_wave[wave_index];  
}

void setup() { 
  // Wave Timer Init
  waveTimer = new HardwareTimer(WAVE_TIMER);
  waveTimer->setOverflow(sample_rate, HERTZ_FORMAT);
  waveTimer->attachInterrupt(waveStepCallback);
  waveTimer->resume();

  // Patch SM Init
  patchSM = DAISY.init(DAISY_PATCH_SM);
  DAISY.SetAudioSampleRate(SaiHandle::Config:: SampleRate::SAI_96KHZ);
  DAISY.SetAudioBlockSize(1);
  DAISY.StartAudio(audioCallback);
}

void loop() {
  updatePitch();
  updateSampleRate();
}

I removed stuff that wasn’t relevant to the explanation. Basically I’m not using the audioCallback to do anything special but write the current value of my wave. The waveCallback is what updates the output based on a timer. This lets me work around the fixed sample rate of the audio engine.

I’ve no idea if this works though :slight_smile: If the engine disables interrupts during the audioCallback then I don’t think it’ll work since I’m exploiting the idea that I can update the wave index separately (in my own callback where I do disable interrupts).

If this works, I’m sure it can cause artifacts but for my application that’s just fine (almost desired). This isn’t as clean as using the approach with the Teensy though. I dunno if that’ll work either though.