changing pitch of audio samples - TeensyVariablePlayback library

When loading a new sample, the _bytesavailable variable doesn't seem to be reset. I've changed that in the code. Additionally, the framebuffer function for the display is disabled before loading a sample and enabled again after loading. Everything seems to be working now. Hope dies last ;-)

C:
// press patch list on page1 -----------------------------------
void press_patch_list_page1()
{
    uint8_t new_index = 0;
    static uint8_t old_index = 0;
    static uint8_t old_key_index = 1;

    new_index = touch_patch_list();
    if (new_index != old_index)
    {
        old_index = new_index;

        if (new_index >= 1 && new_index <= 7)
        {
            if (new_index != old_key_index)
            {
                old_key_index = new_index;

                // stop all samples
                for (size_t i = 0; i < 8; i++)
                {
                    sampleOsc[i].stop();
                }

                sample_busy_flag = false;
                extmem_free(Sample->sampledata);
                delete Sample;
                Sample = nullptr;

                print_file_List(new_index - 1);
                tft.updateScreenAsync(false); // update screen
                tft.useFrameBuffer(false);    // disabled Frame Buffer
                tft.updateChangedAreasOnly(false);

                // loading new Sample
                String Folder = "Samples/";
                String name_ = Folder + fileNames[new_index - 1];
                char *_filename = const_cast<char *>(name_.c_str());
                Sample = loader.loadSample(_filename);
                sample_busy_flag = true;

                // enabled frame buffer
                tft.useFrameBuffer(true);
                tft.updateChangedAreasOnly(true);
            }
        }
        else if (new_index == 0)
        {
            for (size_t i = 0; i < 7; i++)
            {
                TK_state_List_P1[i] = false; // clear touch state
            }
        }
    }

    if (new_index > 0)
    {
        Reset_screensaver();
    }
}

C:
#ifndef PSRAMLOADER_H
#define PSRAMLOADER_H

#include <Arduino.h>
#include <SD.h>

extern "C" uint8_t external_psram_size;

namespace newdigate {

    const uint32_t flashloader_default_sd_buffersize = 4 * 1024;
    
    struct audiosample {
        int16_t *sampledata;
        uint32_t samplesize;
    };

    class Psramloader {
    public:

        Psramloader() {
            _bytesavailable = external_psram_size * 1048576;
        }

        uint32_t _bytesavailable = 0;
        audiosample * loadSample(const char *filename);
    };
};
#endif

C:
#include "psramloader.h"
#include <Audio.h>

namespace newdigate {
    audiosample * Psramloader::loadSample(const char *filename ) {
        uint8_t *data = nullptr;
        _bytesavailable = external_psram_size * 1048576;
        Serial.printf("Reading %s\n", filename);
        File f = SD.open(filename, O_READ);
        if (f) {
            if (f.size() < _bytesavailable) {
               AudioNoInterrupts();
                uint32_t total_read = 0;
                data = (uint8_t*)extmem_malloc(f.size());
                uint8_t *index = data;
                while (f.available()) {
                    size_t bytesRead = f.read(index, flashloader_default_sd_buffersize);
                    if (bytesRead == 0)
                        break;
                    total_read += bytesRead;
                    index += bytesRead;
                }
                AudioInterrupts();
                _bytesavailable -= total_read;
                audiosample *sample = new audiosample();
                sample->sampledata = (int16_t*)data;
                sample->samplesize = f.size();
                Serial.printf("\tsample start %x\n", (uint32_t)data);
                Serial.printf("\tsample size %d\n", sample->samplesize);
                Serial.printf("\tavailable: %d\n", _bytesavailable);
                return sample;
            }
        }
        Serial.printf("not found %s\n", filename);
        return nullptr;
    }
}
 
Last edited:
Okay. The problem with loading samples seems to have been resolved. I no longer experienced crashes, even when quickly tapping the touchscreen. It seems to have only been related to the _bytesavailable variable. I deleted the lines for disabling the Display frame buffer function.
 
How can I stop the sample loop when a midi note off command is received ?
.setLoopType(looptype_none) doesn't work. The example loop continues?

A
I found the solution..
.setLoopType(looptype_none)
.setUseDualPlaybackHead(false)
end the loop
 
Last edited:
There’s a chunk of this thread starting here which worked out OK for effectively doing a preload using existing library capabilities.

In essence, you set the playback speed to 0.0, play the file, then when you’re ready for audio to start, change the speed to 1.0 (or whatever). It’s not quite as good as the preload for my buffered SD playback/record library, because you suffer the overhead of file opening before every play event, rather than during the playback of the preloaded audio.

There are a few points to note. Files take longer to open if they’re multiple directory levels down. If you’re doing something track-based like a looper or sequencer, it’s useful to have two playback objects per track; one is playing, the other can be used to get ready to play the next event on that track. PSRAM is very handy - 8 tracks with two objects with 32kB buffers needs 512kB of buffer space.
 
Thank you @h4yn0nnym0u5e for pointing me out that thread, I'm checking it. I definitely will try the two players approach. My goal is to create a sequencer POC with up to 64 voices polyphony reading from SD. I know it is ambitious given the limits of the board but I want to see how far can the Teensy 4.1 can go in this regard.
 
I’d think 64-poly is a bit of a stretch, but you never know until you try! I’ve benchmarked up to 16 open files at once, and with a reasonable read size (chunks of 4kB at a time) and a decent SD card, like a fast SanDisk, you can get around 10MB/s overall throughput. Performance definitely does drop off with more files open (slightly), and with smaller chunk sizes (significantly). 64 mono files would be 5.6MB/s, so you don't leave a huge margin for delays, the Teensy doing other stuff, etc. Also, playing back at faster than 1.0x will of course increase the data rate.

I'd strongly suggest you figure out how to instrument the code so you can see how critical areas are performing - happy to help on here if you want to know where to look in the library. An oscilloscope or logic analyser is massively helpful here, as you can toggle pins on entry/exit of the code you're looking at. I've not got one, but something like this would probably be enough.

Together with that, I'd encourage you to make it really easy to scale your polyphony. Ideally create a Voice class which does everything you need, then start with a small array, say four voices, and try to ensure you write your code so that if you change only the voice count, everything still works. Then it's easy to try 4, 5, 8, 16 ... checking your instrumentation as you go.

I forgot to mention that you must not try to play or stop from interrupt code, though I believe it's OK to change playback speed from an interrupt. A few people have used libraries that use timers and interrupts to get super-tight timing, and fallen foul of this. Note also that if you're doing other file I/O, such as reading or writing patches, sample lists etc., you need to wrap those accesses to prevent clashes: see this documentation.
 
Back
Top