Audio Expander for Teensy

Status
Not open for further replies.

Sandro

Well-known member
Hi all,
thanks to the community Pjrc I've been able to develop this Library for an Expander with ADSR envelope filter integrated, which appears working very fine.
I started using Teensy 3.2, good for speed <= 2x"original speed"; than I moved to Teensy 3.6 which seems to ensure poliphony=4 (up to 4 keys simultaneously pressed) using up to 7x"original speed". Here's my code; I'm very proud of it but, since I expose it to this community, I apologize for my naive code: I'm a biginner with coding... Anyhow, I hope you guys will appreciate my work :).

This is Audio_expander_serialflash_raw.h:
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw") plays audio .RAW files from flash ROM memory chip.
 * - set_speed(speed_value, velocity_value) set speed (pitch) and velocity (in the MIDI sense).
 * - set_adsr(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory, and depends to "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 * Please send play command after set_speed and set_adsr commands.
 * Author: Sandro Grassia (Rimini, IT).
 *
 */

#ifndef Audio_expander_serialflash_raw_h_
#define Audio_expander_serialflash_raw_h_

#include "Arduino.h"
#include <AudioStream.h>
#include <SerialFlash.h>

class Audio_expander_serialflash_raw : public AudioStream
{
public:
    Audio_expander_serialflash_raw(void) : AudioStream(0, NULL)
    {
        begin();
    }
    void begin(void);
    bool play(const char *filename);
    void set_speed(float speed_value, float velocity_value);
    void stop(void);
    bool isPlaying(void)
    {
        return playing;
    }
    uint32_t positionMillis(void);
    uint32_t lengthMillis(void);
    virtual void update(void);
    int time_lapse(void);
    void set_adsr(float attack_time, float decay_time, float sustain_value, float release_time);
    void release_note(void);
    bool is_playing(void);

private:
    SerialFlashFile rawfile;
    uint32_t file_size;
    volatile uint32_t file_offset;
    volatile bool playing;
    int16_t ride; // update() cycle
    int32_t first_index_to_read, last_index_to_read, J1, J2, J3, J4, DJ3_4; // indexes of samples
    int16_t penultimate_sample, last_sample, last_value;  // values of samples
    float speed; // 0.5=half speed 1=original speed 2=double speed
    float velocity;	// velocity in MIDI sense; 0 <= velocity <= 1.0
    int64_t t, t_0; // t is the computational time, MUST be within 2902 microseconds (period of update() when 128 samples are required)
    float C1, C2, C3, gain, gain_0, sustain;    // coefficients for ADSR
    int8_t start_release;
    const float Alpha=1.0/44100.0;
};

#endif

This is Audio_expander_serialflash_raw.cpp. I included a linear ADSR envelope filter because I had problems (glitches) with the "envolope" filter available in Audio Library, that I could not remove. This is my code:
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw") plays audio .RAW files from flash ROM memory chip.
 * - set_speed(speed_value, velocity_value) set speed (pitch) and velocity (in the MIDI sense).
 * - set_adsr(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory, and depends to "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 * Please send play command after set_speed and set_adsr commands.
 * Author: Sandro Grassia (Rimini, IT).
 *
 */


#include <Arduino.h>
#include "Audio_expander_serialflash_raw.h"
#include "spi_interrupt.h"


void Audio_expander_serialflash_raw::begin(void)
{
    last_value=0;
    playing = false;
    file_offset = 0;
    file_size = 0;
}

void Audio_expander_serialflash_raw::set_speed(float speed_value, float velocity_value)
{
    speed = ((speed_value>=0.01)? ((speed_value<=7.0)? speed_value : 7.0) : 0.01) ; // set speed value within limits
    velocity = velocity_value;
}

bool Audio_expander_serialflash_raw::play(const char *filename)
{
    stop();
    ride = -1; // set "very first" ride!
    start_release=0; // allows ADRS to start
    AudioStartUsingSPI();
    rawfile = SerialFlash.open(filename);
    if (!rawfile)
    {
        AudioStopUsingSPI();
        return false;
    }
    file_size = rawfile.size();
    file_offset = 0;
    playing = true;
    return true;
}


void Audio_expander_serialflash_raw::stop(void)
{
    __disable_irq();
    if (playing)
    {
        playing = false;
        __enable_irq();
        rawfile.close();
        AudioStopUsingSPI();
    }
    else
    {
        __enable_irq();
    }
}


void Audio_expander_serialflash_raw::update(void)
{
    unsigned int i, n;
    audio_block_t *block;
    int16_t samples_vector[1280]; 	// current vector of samples read from file
    int32_t h, k, j; 				// indexes of samples
    float virtual_index; 			// likely not integer index
    float index_delta; 				// mantissa of virtual_index
    int16_t h_sample_value, k_sample_value; // samples value
    int f, g, w;
    int16_t samples_to_read;
    int16_t x_index_delta; 			// mantissa of virtual_index
    float Cx;

    // only update if we're playing
    if (!playing)
        return;

    // allocate the audio blocks to transmit
    block = allocate();
    if (block == NULL)
        return;

    if (rawfile.available())
    {

        // this first ride makes a linear ramp which connects the last sample sent to 0, which is (expected to be) the value of the first sample of the .RAW file to be played
        if (ride<0)
        {
            Cx=last_value>>7;
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++)
            {
                block->data[i]=last_value-(Cx*(i+1));
            }
            last_value=block->data[127];
        }

        // from this poin samples are taken from the .RAW file
        else if (ride>=0)
        {

            t_0 = micros(); // start counting the computational time
            first_index_to_read = ((ride==0)? 0 :last_index_to_read+1);
            last_index_to_read = ceil(((ride*AUDIO_BLOCK_SAMPLES)+AUDIO_BLOCK_SAMPLES-1)*speed); // last_index_to_read=ceil(((ride*128)+127)*speed);
            samples_to_read = last_index_to_read-first_index_to_read+1;

            // this part of code reads from file up to 128 samples per time; if we read more than 128 samples there are errors!. The result is the samples_vector[samples_to_read]
            f = samples_to_read % AUDIO_BLOCK_SAMPLES;
            g = samples_to_read >> 7;

            // read one group of "f" samples
            n = rawfile.read(block->data, f<<1);
            file_offset += n;
            for (i = n>>1; i < f; i++)
            {
                block->data[i] = 0;
            }
            for (i = 0; i < f; i++)
            {
                samples_vector[i]=block->data[i];
            }

            // read "g" groups of 128 samples
            for (w = 0; w<g; w++)
            {
                n = rawfile.read(block->data, AUDIO_BLOCK_SAMPLES << 1);
                file_offset += n;
                for (i = n/2; i < AUDIO_BLOCK_SAMPLES; i++)
                {
                    block->data[i] = 0;
                }
                for (i = 0; i < AUDIO_BLOCK_SAMPLES; i++)
                {
                    samples_vector[f+(w<<7)+i]=block->data[i];
                }
            }
            // samples_vector[samples_to_read] is ready!

            // calculating block->data[128] to be sent to the output Connection
            for (i = 0; i<AUDIO_BLOCK_SAMPLES; i++)
            {
                j = i+(AUDIO_BLOCK_SAMPLES*ride);
                virtual_index=(float)(j)*speed; // index of the needed sample, usually not-integer
                h = (int32_t)(virtual_index); // index of the lower sample needed for calculation
                k = ceil(virtual_index); // index of the upper sample needed for calculation
                index_delta = virtual_index-h; // mantissa
                x_index_delta = index_delta*1024.0;
                h = h-first_index_to_read;  // lower (relative) index of the sample needed for calculation
                k = k-first_index_to_read;  // higher (relative) index of the sample needed for calculation
                h_sample_value =((h==-2)? penultimate_sample: ((h==-1)? last_sample: samples_vector[h]));  // value of the lower sample needed for calculation
                k_sample_value =((k==-2)? penultimate_sample: ((k==-1)? last_sample: samples_vector[k]));  // value of the upper sample needed for calculation

                if(start_release == 0 && j<J1) // attack procedure
                    gain = C1*j;

                else if (start_release==0 && j<J2) // decay procedure
                    gain = (C2*(j-J1))+1.0;

                else if (start_release==1 && j<J4) // release procedure
                    gain = (C3*(j-J3))+gain_0;

                if (start_release==1 && j>=J4) // stop player!
                    stop();

                block->data[i]=gain*velocity*(h_sample_value + ((x_index_delta*(k_sample_value-h_sample_value))>>10));
            }
            last_value = block->data[127];
            penultimate_sample = samples_vector[k-1]; // value of the penultimate sample read from file will be used in the next ride
            last_sample = samples_vector[k]; // value of the last sample read from file will be used in the next ride
        }
        ride ++;
        t = micros()-t_0; // calculating the computational time "t"
        transmit(block);
    }

    else
    {
        rawfile.close();
        AudioStopUsingSPI();
        playing = false;
    }

    release(block);
}

#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592

uint32_t Audio_expander_serialflash_raw::positionMillis(void)
{
    return ((uint64_t)file_offset * B2M) >> 32;
}

uint32_t Audio_expander_serialflash_raw::lengthMillis(void)
{
    return ((uint64_t)file_size * B2M) >> 32;
}

int Audio_expander_serialflash_raw::time_lapse(void) // reporting the computational time "t"
{
    return t;
}

void Audio_expander_serialflash_raw::set_adsr(float attack_time, float decay_time, float sustain_value, float release_time) // ***** set ADSR parameters. Times are in seconds. 0 <= sustain_value <= 1.0
{
    attack_time=((attack_time>0.05)? attack_time : 0);
    C1 = ((attack_time >0.05)? Alpha/attack_time : 0);
    decay_time=((decay_time>0.05)? decay_time : 0.05);
    C2 = (sustain_value-1.0) * Alpha/decay_time;
    J1 = ((attack_time>0.05)? 1.0/C1: 0);
    J2 = (attack_time + decay_time)/Alpha;
    release_time = ((release_time>0.05)? release_time : 0.05);
    DJ3_4 = release_time/Alpha;
}

void Audio_expander_serialflash_raw::release_note(void)
{
    gain_0 = gain;
    J3 = ride*AUDIO_BLOCK_SAMPLES;
    J4 = J3+DJ3_4;
    C3 = -gain_0/DJ3_4;
    start_release = 1;
}

bool Audio_expander_serialflash_raw::is_playing(void) // checking if the object is playing
{
    return playing;
}

An example of code for a MIDI Expander with poliphony 4, which is running (fine!) on my Teensy 3.6 with Audio Adaptor and W25Q128FV flash memory chip.
In my application I use few external potentiometers to set ADSR:
Code:
/*
   MIDI Expander
   Teensy 3.6 + Audio Adaptor Board + W25Q128FV flash memory chip

*/

#include <MIDI.h> // https://www.pjrc.com/teensy/td_libs_MIDI.html
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <Audio_expander_serialflash_raw.h>

#define voices 4

Audio_expander_serialflash_raw  voice[voices];
AudioMixer4              mixer1;
AudioOutputI2S           audio_out;
AudioConnection          patchCord1(voice[0], 0, mixer1, 0);
AudioConnection          patchCord2(voice[1], 0, mixer1, 1);
AudioConnection          patchCord3(voice[2], 0, mixer1, 2);
AudioConnection          patchCord4(voice[3], 0, mixer1, 3);
AudioConnection          patchCord5(mixer1, 0, audio_out, 1);
AudioConnection          patchCord6(mixer1, 0, audio_out, 0);
AudioControlSGTL5000     board;

int note_number;
float velocity;
float note_number_to_pitch[128];
int i;
int pot_value[10][2];
unsigned long voice_timer[voices];
int key_played_by[voices];
float gain_0 = 1.0 / voices;
int w;
float attack_time, decay_time, sustain_value, release_time;

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
const int channel = 1; // canale MIDI

void setup() {
  MIDI.begin(MIDI_CHANNEL_OMNI);
  Serial.begin (9600);
  AudioMemory(30);
  /* AudioMemory: allocate the memory for all audio connections. The numberBlocks input specifies how much memory to reserve for audio data.
    Each block holds 128 audio samples, or approx 2.9 ms of sound. Usually an initial guess is made for numberBlocks and the actual
    usage is checked with AudioMemoryUsageMax().
  */

  // inizializzazione Audio Adaptor
  board.enable();
  board.volume(1.0);

  // inizializzazione mixer
  mixer1.gain(0, gain_0);
  mixer1.gain(1, gain_0);
  mixer1.gain(2, gain_0);
  mixer1.gain(3, gain_0);

  // set up SPI Teensy to SPI Flash....if you are using different pins set them here
  SPI.setMOSI(7);
  SPI.setMISO(12);
  SPI.setSCK(14);
  SerialFlash.begin(6);
  delay(100);

  for (i = 0; i < voices; i++) {
    voice[i].set_adsr(0, 2, 1, 0.2);
    voice_timer[i] = 0;
  }

  // note number to speed, conversion constants
  for (i = 0; i < 128; i++) {
    note_number_to_pitch[i] = pow(2.0, (i - 60.0) / 12.0); // vettore per la conversione da note number a pitch
  }
}


void loop() {

  if (MIDI.read()) {
    switch (MIDI.getType()) {
      case midi::NoteOn:
        note_number = MIDI.getData1();
        velocity = MIDI.getData2() / 127.0;
        // Serial.println (note_number);
        // Serial.println (note_number_to_pitch[note_number]);
        play_note(note_number, velocity);
        break;

      case midi::NoteOff:
        note_number = MIDI.getData1();
        velocity = MIDI.getData2() / 127.0;
        // Serial.println (note_number);
        release_voice(note_number);
        break;

      default:
        break;
    }
  }

  w++;
  if (w == 100000) {
    int i;
    int u = 0;
    for (i = 0; i < voices; i++) {
      u += voice[i].time_lapse();
    }
    Serial.println (u);
    w = 0;

    attack_time = 5 * analogRead(34) / 1023.0;
    decay_time = 2 * analogRead(35) / 1023.0;
    sustain_value = analogRead(36) / 1023.0;
    release_time = 10 * analogRead(37) / 1023.0;

  }
}

// play note
void play_note(int note_number, float velocity) {
  unsigned long minimum_time = voice_timer[0];
  int i;
  int ii = 0;
  for (i = 0; i < voices; i++) { // first test: if the same key is played we use the same voice
    if (key_played_by[i] == note_number) {
      ii = i;
      break;
    }
    if (voice[i].is_playing() == false) { // second test choice: look for a voice not busy
      ii = i;
      break;
    }
    if (voice_timer[i] < minimum_time ) { // final test choice: look for the voice playing for the longest time
      minimum_time = voice_timer[i];
      ii = i;
    }
  }
  key_played_by[ii] = note_number;
  voice[ii].set_speed(note_number_to_pitch[note_number], velocity);
  voice[ii].set_adsr(attack_time, decay_time, sustain_value, release_time);
  voice[ii].play("FILENAME.RAW");
  voice_timer[ii] = millis();
}

// stop note
void release_voice(int note_number) {
  int i;
  for (i = 0; i < 4; i++) {
    if (key_played_by[i] == note_number) {
      voice[i].release_note();
      break;
    }
  }
}
 
Last edited:
Hi all,
of course my code needs improvements. This part will be rewrited in a single reading (instead of f+g as now):

Code:
            // this part of code reads from file up to 128 samples per time; if we read more than 128 samples there are errors!. The result is the samples_vector[samples_to_read]
            f = samples_to_read % AUDIO_BLOCK_SAMPLES;
            g = samples_to_read >> 7;

            // read one group of "f" samples
            n = rawfile.read(block->data, f<<1);
            file_offset += n;
            for (i = n>>1; i < f; i++)
            {
                block->data[i] = 0;
            }
            for (i = 0; i < f; i++)
            {
                samples_vector[i]=block->data[i];
            }

            // read "g" groups of 128 samples
            for (w = 0; w<g; w++)
            {
                n = rawfile.read(block->data, AUDIO_BLOCK_SAMPLES << 1);
                file_offset += n;
                for (i = n/2; i < AUDIO_BLOCK_SAMPLES; i++)
                {
                    block->data[i] = 0;
                }
                for (i = 0; i < AUDIO_BLOCK_SAMPLES; i++)
                {
                    samples_vector[f+(w<<7)+i]=block->data[i];
                }
            }
            // samples_vector[samples_to_read] is ready!

As soon as a new important function, set_pitch_bend, will work fine, I'll publish an updated version of my object.
Bye!
 
hi all,
I've introduced a pitch_bend function.
Wishing that someone will help to improve the behave of the Expander object... there is a very light "click" each time pitch is changed, maybe due an approssimation at the beginning of the oversampling cycle. This is the code and an example:

Header
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw") plays audio .RAW files from flash ROM memory chip.
 * - set_note(speed_value, velocity_value) set speed (pitch) and velocity (in the MIDI sense).
 * - set_adsr(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - set_pitch_bend (pitch_bend_value) set the pitch bend value
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory, and depends to "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 * Please send play command after set_speed and set_adsr commands.
 *
 */

#ifndef Audio_expander_serialflash_raw_h_
#define Audio_expander_serialflash_raw_h_

#include "Arduino.h"
#include <AudioStream.h>
#include <SerialFlash.h>

class Audio_expander_serialflash_raw : public AudioStream
{
public:
	Audio_expander_serialflash_raw(void) : AudioStream(0, NULL) { begin(); }
	void begin(void);
	bool play(const char *filename);
	void set_note(float speed_value, float velocity_value);
	void stop(void);
	bool isPlaying(void) { return playing; }
	uint32_t positionMillis(void);
	uint32_t lengthMillis(void);
	virtual void update(void);
	int time_lapse(void);
    void set_adsr(float attack_time, float decay_time, float sustain_value, float release_time);
    void release_note(void);
    bool is_playing(void);
    void set_pitch_bend (float pitch_bend_value);

private:
	SerialFlashFile rawfile;
	uint32_t file_size;
	volatile uint32_t file_offset;
	volatile bool playing;
	int16_t ride;
	int32_t first_index_to_read, last_index_to_read, J1, J2, J3, J4, DJ3_4; 	// indexes of samples
	int16_t penultimate_sample, last_sample, last_value;  // values of samples
	float speed; // 0.5=half speed 1=original speed 2=double speed
	float velocity;	// 0 <= velocity <= 1.0
	int64_t t, t_0; // *t is the computational time, must be within 2902 microseconds (period of update() when 128 samples are required)
	float C1, C2, C3, gain, gain_0, sustain;    // coefficients for ADSR
	int8_t start_release;
	const float Alpha=1.0/44100.0;
	float pitch_bend=1.0;
	float bias_virtual_index=0;
	float bias_virtual_index_0;
	int32_t j_adsr;
};

#endif

CPP file
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw") plays audio .RAW files from flash ROM memory chip.
 * - set_note(speed_value, velocity_value) set speed (pitch) and velocity (in the MIDI sense).
 * - set_adsr(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - set_pitch_bend (pitch_bend_value) set the pitch bend value
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory, and depends to "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 * Please send play command after set_speed and set_adsr commands.
 *
 */


#include <Arduino.h>
#include "Audio_expander_serialflash_raw.h"
#include "spi_interrupt.h"

void Audio_expander_serialflash_raw::begin(void)
{
    last_value=0;
    playing = false;
    file_offset = 0;
    file_size = 0;
}

void Audio_expander_serialflash_raw::set_note(float speed_value, float velocity_value)
{
    speed = ((speed_value>=0.01)? ((speed_value<=7.0)? speed_value : 7.0) : 0.01) ; // set speed value within limits
    velocity = velocity_value;
}

bool Audio_expander_serialflash_raw::play(const char *filename)
{
    stop();
    ride = -1; // set "very first" ride!
    start_release=0; // allows ADRS to start
    j_adsr=0;
    bias_virtual_index=0;

    AudioStartUsingSPI();
    rawfile = SerialFlash.open(filename);
    if (!rawfile)
    {
        AudioStopUsingSPI();
        return false;
    }
    file_size = rawfile.size();
    file_offset = 0;
    playing = true;
    return true;
}


void Audio_expander_serialflash_raw::stop(void)
{
    __disable_irq();
    if (playing)
    {
        playing = false;
        __enable_irq();
        rawfile.close();
        AudioStopUsingSPI();
    }
    else
    {
        __enable_irq();
    }
}


void Audio_expander_serialflash_raw::update(void)
{
    unsigned int i, n;
    audio_block_t *block;
    int16_t samples_vector[1280]; 	// current vector of samples read from file
    int32_t h, k, j; 				// indexes of samples
    float virtual_index; 			// likely not integer index
    float index_delta; 				// mantissa of virtual_index
    int16_t h_sample_value, k_sample_value; // samples value
    int f, g, w;
    int16_t samples_to_read;
    int16_t x_index_delta; 			// mantissa of virtual_index
    float Cx;


    // only update if we're playing
    if (!playing)
        return;

    // allocate the audio blocks to transmit
    block = allocate();
    if (block == NULL)
        return;

    if (rawfile.available())
    {
        // this first ride generate a linear ramp which connects the last sample sent to 0, which is (MUST be) the value of the first sample of the .RAW file to be played
        t_0 = micros(); // start counting the computational time

        if (ride<0)
        {
            Cx=last_value>>7;
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++)
            {
                block->data[i]=last_value-(Cx*(i+1));
            }
            last_value=block->data[127];
        }

        // from this point samples are taken from the .RAW file
        else if (ride>=0)
        {
            first_index_to_read = ((ride==0)? 0 :last_index_to_read+1);
            last_index_to_read = ceil(((((ride+1)*AUDIO_BLOCK_SAMPLES)-1)*speed*pitch_bend) - bias_virtual_index) ;
            samples_to_read = last_index_to_read-first_index_to_read+1;

            n = rawfile.read(samples_vector, samples_to_read<<1);
            file_offset += n;
            for (i = n>>1; i < samples_to_read; i++)
            {
                samples_vector[i] = 0;
            }
            // samples_vector[samples_to_read] is ready!


            // calculating block->data[128] to be sent to the output Connection
            for (i = 0; i<AUDIO_BLOCK_SAMPLES; i++)
            {
                j = i+(AUDIO_BLOCK_SAMPLES*ride);
                virtual_index=(j*speed*pitch_bend)-bias_virtual_index; // index of the needed sample, usually not-integer
                h = floor (virtual_index);// // index of the lower sample needed for calculation
                k = ceil(virtual_index); // index of the upper sample needed for calculation
                index_delta = virtual_index-h; // mantissa
                x_index_delta = index_delta*1024.0;
                h = h-first_index_to_read;  // lower (relative) index of the sample needed for calculation
                k = k-first_index_to_read;  // higher (relative) index of the sample needed for calculation
                h_sample_value =((h==-2)? penultimate_sample: ((h==-1)? last_sample: samples_vector[h])); 	// value of the lower sample needed for calculation
                k_sample_value =((k==-2)? penultimate_sample: ((k==-1)? last_sample: samples_vector[k])); 	// value of the upper sample needed for calculation


                if(start_release == 0 && j_adsr<J1) // attack procedure
                    gain = C1*j_adsr;

                else if (start_release==0 && j_adsr<J2) // decay procedure
                    gain = (C2*(j_adsr-J1))+1.0;

                else if (start_release==1 && j_adsr<J4) // release procedure
                    gain = (C3*(j_adsr-J3))+gain_0;

                else if (start_release==1 && j_adsr>=J4) // stop player!
                    stop();

                j_adsr ++;

                block->data[i]=gain*velocity*(h_sample_value + ((x_index_delta*(k_sample_value-h_sample_value))>>10));
            }

            last_value = block->data[127];
            penultimate_sample = samples_vector[k-1]; // value of the penultimate sample read from file will be used in the next ride
            last_sample = samples_vector[k]; // value of the last sample read from file will be used in the next ride
            bias_virtual_index_0=last_index_to_read+1-virtual_index; //
        }
        ride ++;
        t = micros()-t_0; // calculating the computational time "t"
        transmit(block);
    }

    else
    {
        rawfile.close();
        AudioStopUsingSPI();
        playing = false;
    }

    release(block);
}

#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592

uint32_t Audio_expander_serialflash_raw::positionMillis(void)
{
    return ((uint64_t)file_offset * B2M) >> 32;
}

uint32_t Audio_expander_serialflash_raw::lengthMillis(void)
{
    return ((uint64_t)file_size * B2M) >> 32;
}

int Audio_expander_serialflash_raw::time_lapse(void) // reporting the computational time "t"
{
    return t;
}

void Audio_expander_serialflash_raw::set_adsr(float attack_time, float decay_time, float sustain_value, float release_time) // ***** set ADSR parameters. Times are in seconds. 0 <= sustain_value <= 1.0
{
    attack_time=((attack_time>0.05)? attack_time : 0);
    C1 = ((attack_time >0.05)? Alpha/attack_time : 0);
    decay_time=((decay_time>0.05)? decay_time : 0.05);
    C2 = (sustain_value-1.0) * Alpha/decay_time;
    J1 = ((attack_time>0.05)? 1.0/C1: 0);
    J2 = (attack_time + decay_time)/Alpha;
    release_time = ((release_time>0.05)? release_time : 0.05);
    DJ3_4 = release_time/Alpha;
}

void Audio_expander_serialflash_raw::release_note(void)
{
    gain_0 = gain;
    J3 = (j_adsr/AUDIO_BLOCK_SAMPLES)*AUDIO_BLOCK_SAMPLES; // J3 = ride*AUDIO_BLOCK_SAMPLES;
    J4 = J3+DJ3_4;
    C3 = -gain_0/DJ3_4;
    start_release = 1;
}

bool Audio_expander_serialflash_raw::is_playing(void) // checking if the object is playing
{
    return playing;
}

void Audio_expander_serialflash_raw::set_pitch_bend (float pitch_bend_value) // 0.5= half speed, 1.0= no change 2.0=double speed
{
    pitch_bend = ((pitch_bend_value>=0.1)? ((pitch_bend_value<=2.0)? pitch_bend_value : 2.0) : 0.1) ; // set pitch value within limits

    if (ride>0)
    {
        bias_virtual_index=bias_virtual_index_0; // when pitch bend is set, ride is set to 0, but we re-start from the last virtual_index
    }
    ride = 0;
}

Arduino example
Code:
/*
   MIDI Expander
   scheda: Teensy 3.6 + Audio Adaptor Board + W25Q128FV flash memory chip
*/

#include <MIDI.h> // https://www.pjrc.com/teensy/td_libs_MIDI.html
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <Audio_expander_serialflash_raw.h>

#define voices 4

Audio_expander_serialflash_raw  voice[voices];
AudioMixer4              mixer1;
AudioOutputI2S           audio_out;
AudioConnection          patchCord1(voice[0], 0, mixer1, 0);
AudioConnection          patchCord2(voice[1], 0, mixer1, 1);
AudioConnection          patchCord3(voice[2], 0, mixer1, 2);
AudioConnection          patchCord4(voice[3], 0, mixer1, 3);
AudioConnection          patchCord5(mixer1, 0, audio_out, 1);
AudioConnection          patchCord6(mixer1, 0, audio_out, 0);
AudioControlSGTL5000     board;

int note_number;
float velocity;
float note_number_to_pitch[128];
int i;
int u, w;
unsigned long voice_timer[voices];
int key_played_by[voices];
float gain_0 = 1.0 / voices;
float attack_time, decay_time, sustain_value, release_time, pitch_bend_value, pitch_bend_value_0;
int pot_value[50][2];

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
const int channel = 1; // canale MIDI

void setup()
{
    MIDI.begin(MIDI_CHANNEL_OMNI);
    Serial.begin (9600);
    AudioMemory(40); // valore critico
    /* AudioMemory: allocate the memory for all audio connections. The numberBlocks input specifies how much memory to reserve for audio data.
      Each block holds 128 audio samples, or approx 2.9 ms of sound. Usually an initial guess is made for numberBlocks and the actual
      usage is checked with AudioMemoryUsageMax().
    */

    // inizializzazione Audio Adaptor
    board.enable();
    board.volume(1.0);

    // inizializzazione mixer
    mixer1.gain(0, gain_0);
    mixer1.gain(1, gain_0);
    mixer1.gain(2, gain_0);
    mixer1.gain(3, gain_0);

    // set up SPI Teensy to SPI Flash....if you are using different pins set them here
    SPI.setMOSI(7);
    SPI.setMISO(12);
    SPI.setSCK(14);
    SerialFlash.begin(6);
    delay(100);

    for (i = 0; i < voices; i++)
    {
        voice[i].set_adsr(0, 2, 1, 1);
        voice_timer[i] = 0;
    }

    // conversion constants
    for (i = 0; i < 128; i++)
    {
        note_number_to_pitch[i] = pow(2.0, (i - 60.0) / 12.0); // vettore per la conversione da note number a pitch
    }
}


void loop()
{
    if (MIDI.read())
    {
        switch (MIDI.getType())
        {
        case midi::NoteOn:
            note_number = MIDI.getData1();
            velocity = MIDI.getData2() / 127.0;
            // Serial.println (note_number);
            // Serial.println (note_number_to_pitch[note_number]);
            play_note(note_number, velocity);
            break;

        case midi::NoteOff:
            note_number = MIDI.getData1();
            velocity = MIDI.getData2() / 127.0;
            // Serial.println (note_number);
            release_voice(note_number);
            break;

        case midi::PitchBend:
            // pitch_bend_value = ((MIDI.getData2() - 64) / 63.0) + 1.0;
            pitch_bend_value = (MIDI.getData2() / 127.0) + 0.496063;
            AudioNoInterrupts();
            for (i = 0; i < voices; i++)
            {
                voice[i].set_pitch_bend(pitch_bend_value);
            }
            AudioInterrupts();
            break;

        default:
            break;
        }
    }

    w++;
    if (w == 10000)
    {
        for (i = 0; i < voices; i++)
        {
            u += voice[i].time_lapse();
        }
        Serial.println (u);
        u = 0;
        w = 0;
    }

    attack_time = 2 * analogRead(34) / 1023.0;
    decay_time = 2 * analogRead(35) / 1023.0;
    sustain_value = analogRead(36) / 1023.0;
    release_time = 10 * analogRead(37) / 1023.0;
}

// play note
void play_note(int note_number, float velocity)
{
    unsigned long minimum_time = voice_timer[0];
    int i;
    int ii = -1;

    for (i = 0; i < voices; i++)   // first test: if the same key is played we use the same voice
    {
        if (key_played_by[i] == note_number)
        {
            ii = i;
            break;
        }
    }

    if (ii == -1) // second test choice: look for a voice not busy
    {
        for (i = 0; i < voices; i++)
        {
            if (voice[i].is_playing() == false)
            {
                ii = i;
                break;
            }
        }
    }

    if (ii == -1) // last test: choose the voice playing for the longest time
    {
        ii = 0;
        for (i = 0; i < voices; i++)
        {
            if (voice_timer[i] < minimum_time )   // final test choice: look for the voice playing for the longest time
            {
                minimum_time = voice_timer[i];
                ii = i;
            }
        }
    }
    key_played_by[ii] = note_number;
    voice[ii].set_note(note_number_to_pitch[note_number], velocity);
    voice[ii].set_adsr(attack_time, decay_time, sustain_value, release_time);
    voice[ii].play("FILENAME.RAW");
    voice_timer[ii] = millis();
}

// stop note
void release_voice(int note_number)
{
    int i;
    for (i = 0; i < 4; i++)
    {
        if (key_played_by[i] == note_number)
        {
            voice[i].release_note();
            break;
        }
    }
}
 
Hi all,
I found the bug, as I supposed was in the setup of the oversampling cycle. Now set_pitch_bend seems working very good!

The mistake is this:
Code:
        bias_virtual_index=bias_virtual_index_0; // when pitch bend is set, ride is set to 0, but we re-start from the last virtual_index

The new line is this:
Code:
        bias_virtual_index=bias_virtual_index_0 - (speed*pitch_bend); // when pitch bend is set, ride is set to 0, but we re-start from the last virtual_index


Finally, this is the new file:

CPP file
Code:
/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw") plays audio .RAW files from flash ROM memory chip.
 * - set_note(speed_value, velocity_value) set speed (pitch) and velocity (in the MIDI sense).
 * - set_adsr(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - set_pitch_bend (pitch_bend_value) set the pitch bend value
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory, and depends to "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 * Please: send set_speed and set_adsr commands BEFORE sending play
 * 
 *
 */


#include <Arduino.h>
#include "Audio_expander_serialflash_raw.h"
#include "spi_interrupt.h"

void Audio_expander_serialflash_raw::begin(void)
{
    last_value=0;
    playing = false;
    file_offset = 0;
    file_size = 0;
}

void Audio_expander_serialflash_raw::set_note(float speed_value, float velocity_value)
{
    speed = ((speed_value>=0.01)? ((speed_value<=7.0)? speed_value : 7.0) : 0.01) ; // set speed value within limits
    velocity = velocity_value;
}

bool Audio_expander_serialflash_raw::play(const char *filename)
{
    stop();
    ride = -1; // set "very first" ride!
    start_release=0; // allows ADRS to start
    j_adsr=0;
    bias_virtual_index=0;

    AudioStartUsingSPI();
    rawfile = SerialFlash.open(filename);
    if (!rawfile)
    {
        AudioStopUsingSPI();
        return false;
    }
    file_size = rawfile.size();
    file_offset = 0;
    playing = true;
    return true;
}


void Audio_expander_serialflash_raw::stop(void)
{
    __disable_irq();
    if (playing)
    {
        playing = false;
        __enable_irq();
        rawfile.close();
        AudioStopUsingSPI();
    }
    else
    {
        __enable_irq();
    }
}


void Audio_expander_serialflash_raw::update(void)
{
    unsigned int i, n;
    audio_block_t *block;
    int16_t samples_vector[1280]; 	// current vector of samples read from file
    int32_t h, k, j; 				// indexes of samples
    float virtual_index; 			// likely not integer index
    float index_delta; 				// mantissa of virtual_index
    int16_t h_sample_value, k_sample_value; // samples value
    int f, g, w;
    int16_t samples_to_read;
    int16_t x_index_delta; 			// mantissa of virtual_index
    float Cx;


    // only update if we're playing
    if (!playing)
        return;

    // allocate the audio blocks to transmit
    block = allocate();
    if (block == NULL)
        return;

    if (rawfile.available())
    {
        // this first ride generate a linear ramp which connects the last sample sent to 0, which is (MUST be) the value of the first sample of the .RAW file to be played
        t_0 = micros(); // start counting the computational time

        if (ride<0)
        {
            Cx=last_value>>7;
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++)
            {
                block->data[i]=last_value-(Cx*(i+1));
            }
            last_value=block->data[127];
        }

        // from this point samples are taken from the .RAW file
        else if (ride>=0)
        {
            first_index_to_read = ((ride==0)? 0 :last_index_to_read+1);
            last_index_to_read = ceil(((((ride+1)*AUDIO_BLOCK_SAMPLES)-1)*speed*pitch_bend) - bias_virtual_index) ;
            samples_to_read = last_index_to_read-first_index_to_read+1;

            n = rawfile.read(samples_vector, samples_to_read<<1);
            file_offset += n;
            for (i = n>>1; i < samples_to_read; i++)
            {
                samples_vector[i] = 0;
            }
            // samples_vector[samples_to_read] is ready!


            // calculating block->data[128] to be sent to the output Connection
            for (i = 0; i<AUDIO_BLOCK_SAMPLES; i++)
            {
                j = i+(AUDIO_BLOCK_SAMPLES*ride);
                virtual_index=(j*speed*pitch_bend)-bias_virtual_index; // index of the needed sample, usually not-integer
                h = floor (virtual_index);// // index of the lower sample needed for calculation
                k = ceil(virtual_index); // index of the upper sample needed for calculation
                index_delta = virtual_index-h; // mantissa
                x_index_delta = index_delta*1024.0;
                h = h-first_index_to_read;  // lower (relative) index of the sample needed for calculation
                k = k-first_index_to_read;  // higher (relative) index of the sample needed for calculation
                h_sample_value =((h==-2)? penultimate_sample: ((h==-1)? last_sample: samples_vector[h])); 	// value of the lower sample needed for calculation
                k_sample_value =((k==-2)? penultimate_sample: ((k==-1)? last_sample: samples_vector[k])); 	// value of the upper sample needed for calculation


                if(start_release == 0 && j_adsr<J1) // attack procedure
                    gain = C1*j_adsr;

                else if (start_release==0 && j_adsr<J2) // decay procedure
                    gain = (C2*(j_adsr-J1))+1.0;

                else if (start_release==1 && j_adsr<J4) // release procedure
                    gain = (C3*(j_adsr-J3))+gain_0;

                else if (start_release==1 && j_adsr>=J4) // stop player!
                    stop();

                j_adsr ++;

                block->data[i]=gain*velocity*(h_sample_value + ((x_index_delta*(k_sample_value-h_sample_value))>>10));
            }

            last_value = block->data[127];
            penultimate_sample = samples_vector[k-1]; // value of the penultimate sample read from file will be used in the next ride
            last_sample = samples_vector[k]; // value of the last sample read from file will be used in the next ride
            bias_virtual_index_0=last_index_to_read+1-virtual_index; //
        }
        ride ++;
        t = micros()-t_0; // calculating the computational time "t"
        transmit(block);
    }

    else
    {
        rawfile.close();
        AudioStopUsingSPI();
        playing = false;
    }

    release(block);
}

#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592

uint32_t Audio_expander_serialflash_raw::positionMillis(void)
{
    return ((uint64_t)file_offset * B2M) >> 32;
}

uint32_t Audio_expander_serialflash_raw::lengthMillis(void)
{
    return ((uint64_t)file_size * B2M) >> 32;
}

int Audio_expander_serialflash_raw::time_lapse(void) // reporting the computational time "t"
{
    return t;
}

void Audio_expander_serialflash_raw::set_adsr(float attack_time, float decay_time, float sustain_value, float release_time) // ***** set ADSR parameters. Times are in seconds. 0 <= sustain_value <= 1.0
{
    attack_time=((attack_time>0.05)? attack_time : 0);
    C1 = ((attack_time >0.05)? Alpha/attack_time : 0);
    decay_time=((decay_time>0.05)? decay_time : 0.05);
    C2 = (sustain_value-1.0) * Alpha/decay_time;
    J1 = ((attack_time>0.05)? 1.0/C1: 0);
    J2 = (attack_time + decay_time)/Alpha;
    release_time = ((release_time>0.05)? release_time : 0.05);
    DJ3_4 = release_time/Alpha;
}

void Audio_expander_serialflash_raw::release_note(void)
{
    gain_0 = gain;
    J3 = (j_adsr/AUDIO_BLOCK_SAMPLES)*AUDIO_BLOCK_SAMPLES; // J3 = ride*AUDIO_BLOCK_SAMPLES;
    J4 = J3+DJ3_4;
    C3 = -gain_0/DJ3_4;
    start_release = 1;
}

bool Audio_expander_serialflash_raw::is_playing(void) // checking if the object is playing
{
    return playing;
}

void Audio_expander_serialflash_raw::set_pitch_bend (float pitch_bend_value) // 0.5= half speed, 1.0= no change 2.0=double speed
{
    pitch_bend = ((pitch_bend_value>=0.1)? ((pitch_bend_value<=2.0)? pitch_bend_value : 2.0) : 0.1) ; // set pitch value within limits

    if (ride>0)
    {
        bias_virtual_index=bias_virtual_index_0-speed*pitch_bend; // when pitch bend is set, ride is set to 0, and we re-start from the next virtual_index
    }
    ride = 0;
}

Next stage cominig (hopefully) is the normal/reverse reading with set_direction function... Let's see :)
 
new code (ready for next features)

Hi all!
this is the new version of my code, with few improvements, but more linear and thus ready for new features. Of course, it's always naive, redundant and so on... But works fine! I learned to use seek function; a bit late, one could say. Ok, I was a little bit shy before.. :eek:

Header:
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw", speed_value, velocity_value) plays audio .RAW files from flash ROM memory chip with the required speed (pitch) and velocity (in the MIDI sense).
 * - set_ADSR(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - set_pitch_bend (pitch_bend_value) set the pitch bend value
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory: it depends from "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 *
 */

#ifndef Audio_expander_serialflash_raw_h_
#define Audio_expander_serialflash_raw_h_

#include "Arduino.h"
#include <AudioStream.h>
#include <SerialFlash.h>

class Audio_expander_serialflash_raw : public AudioStream
{
public:
	Audio_expander_serialflash_raw(void) : AudioStream(0, NULL) { begin(); }
	void begin(void);
	bool play(const char *filename, float speed_value, float velocity_value);
	void set_note(float speed_value, float velocity_value);
	void stop(void);
	bool isPlaying(void) { return playing; }
	uint32_t positionMillis(void);
	uint32_t lengthMillis(void);
	virtual void update(void);
	int time_lapse(void);
        void set_ADSR(float attack_time, float decay_time, float sustain_value, float release_time);
        void release_note(void);
        bool is_playing(void);
        void set_pitch_bend (float pitch_bend_value);

private:
	SerialFlashFile rawfile;
    volatile bool playing;
    uint32_t file_size;
	int32_t main_index=0;
    volatile uint32_t file_offset; // point the BYTE
    float file_sample_offset; // point the SAMPLE
    float base_file_sample_offset;
    int32_t j_adsr;
    int32_t samples_available;
    int8_t start_release;
    int8_t send_joint;
    int32_t J1, J2, J3, J4, DJ3_4; 	// indexes of samples
    int16_t last_value;  // values of samples
    float speed; // 0.5=half speed 1=original speed 2=double speed
    float velocity;	// 0 <= velocity <= 1.0
    int64_t t, t_0; // *t is the computational time, must be within 2902 microseconds (period of update() when 128 samples are required)
    float C1, C2, C3, gain, gain_0, sustain;    // coefficients for ADSR
    const float Alpha=1.0/44100.0;
    float pitch_bend=1.0;
    const int8_t min_speed=0.01;
    const int8_t max_speed=7.0;
};

#endif

CPP:
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw", speed_value, velocity_value) plays audio .RAW files from flash ROM memory chip with the required speed (pitch) and velocity (in the MIDI sense).
 * - set_ADSR(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - set_pitch_bend (pitch_bend_value) set the pitch bend value
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory: it depends from "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 *
 */


#include <Arduino.h>
#include "Audio_expander_serialflash_raw.h"
#include "spi_interrupt.h"

void Audio_expander_serialflash_raw::begin(void)
{
    last_value=0;
    playing = false;
    file_offset = 0;
    file_size = 0;
}

bool Audio_expander_serialflash_raw::play(const char *filename, float speed_value, float velocity_value)
{
    stop();
    AudioStartUsingSPI();
    rawfile = SerialFlash.open(filename);
    if (!rawfile)
    {
        AudioStopUsingSPI();
        return false;
    }
    // file_size = rawfile.size();
    speed = ((speed_value>=min_speed)? ((speed_value<=max_speed)? speed_value : max_speed) : min_speed); // set speed value within strong limits
    velocity = velocity_value;
    file_size = rawfile.size() - (rawfile.size()%256);
    send_joint=1; //
    main_index=0;
    file_offset=0; // points to BYTE in .RAW file
    file_sample_offset=0.0; // points to SAMPLE in .RAW file
    samples_available= file_size*2;
    base_file_sample_offset=0;
    rawfile.seek(file_offset);
    start_release=0; // allows ADRS to start
    j_adsr=0;

    playing = true;
    return true;
}


void Audio_expander_serialflash_raw::stop(void)
{
    __disable_irq();
    if (playing)
    {
        playing = false;
        __enable_irq();
        rawfile.close();
        AudioStopUsingSPI();
    }
    else
    {
        __enable_irq();
    }
}


void Audio_expander_serialflash_raw::update(void)
{
    int i;
    int n;
    audio_block_t *block;
    int16_t samples_basket[AUDIO_BLOCK_SAMPLES*11]; // current vector of samples read from file
    int32_t LOW_sample, HIGH_sample;  // indexes of samples in RAW file
    int32_t L_sample, H_sample;  // indexes of samples in sample_basket
    int16_t samples_to_read;
    float index_delta;
    int16_t x_index_delta; 	// mantissa of virtual_file_offset
    float Cx;
    float speed_bend;
    float basket_offset;


    // only update if we're playing
    if (!playing)
        return;

    // allocate the audio blocks to transmit
    block = allocate();
    if (block == NULL)
        return;

    if (rawfile.available())
    {
        // this first ride generate a linear ramp which connects the last sample sent to 0, which is (MUST be) the value of the first sample of the .RAW file to be played
        t_0 = micros(); // start counting the computational time

        if (send_joint==1)
        {
            Cx = last_value/128.0;
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++)
            {
                block->data[i]=last_value-(Cx*(i+1));
            }
            last_value=0;// last_value=block->data[127];
            send_joint=0;
        }

        // from this point samples are taken from the .RAW file
        else if (file_offset<file_size)
        {
            speed_bend=((pitch_bend<1)? speed*pitch_bend : ((speed<(0.5*max_speed))? speed*pitch_bend : speed));
            LOW_sample = floor((speed_bend*main_index)+base_file_sample_offset);
            HIGH_sample = ceil((speed_bend*(main_index+AUDIO_BLOCK_SAMPLES-1))+base_file_sample_offset);
            if (HIGH_sample>=samples_available)
            {
                stop();
            }

            samples_to_read = HIGH_sample-LOW_sample+1;
            file_offset=LOW_sample<<1;
            rawfile.seek(file_offset);
            n=rawfile.read(samples_basket, samples_to_read<<1);
            for (i=n/2; i < samples_to_read; i++)
            {
                samples_basket[i] = 0;
            }

            // calculating block->data[128] to be sent to the output Connection
            for (i = 0; i<AUDIO_BLOCK_SAMPLES; i++)
            {
                file_sample_offset=(speed_bend*main_index)+base_file_sample_offset; // index of the needed sample, usually not-integer
                basket_offset=file_sample_offset-LOW_sample;
                L_sample = floor(basket_offset);// // index of the lower sample needed for calculation
                H_sample = ceil(basket_offset); // index of the upper sample needed for calculation
                index_delta = basket_offset-L_sample;
                x_index_delta = index_delta*1024.0;

                //ADSR envelope filter uses the specific index j_adsr to be independent from main_index
                if(start_release == 0 && j_adsr<J1) // attack procedure
                {
                    gain = C1*j_adsr;
                }
                else if (start_release==0 && j_adsr<J2) // decay procedure
                {
                    gain = (C2*(j_adsr-J1))+ 1.0;
                }
                else if (start_release==1 && j_adsr<J4) // release procedure
                {
                    gain = (C3*(j_adsr-J3)) + gain_0;
                }
                else if (start_release==1 && j_adsr>=J4) // stop player!
                {
                    stop();
                }

                block->data[i]=gain*velocity*(samples_basket[L_sample] + ((x_index_delta*(samples_basket[H_sample]-samples_basket[L_sample]))>>10));
                main_index ++;
                j_adsr ++;
            }

            last_value = block->data[127];
            t = micros()-t_0; // calculating the computational time "t"
            transmit(block);
        }
        else
        {
            main_index=0;
            file_offset=0;
            stop();
        }

    }
    else
    {
        rawfile.close();
        AudioStopUsingSPI();
        playing = false;
    }

    release(block);
}

#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592

uint32_t Audio_expander_serialflash_raw::positionMillis(void)
{
    return ((uint64_t)file_offset * B2M) >> 32;
}

uint32_t Audio_expander_serialflash_raw::lengthMillis(void)
{
    return ((uint64_t)file_size * B2M) >> 32;
}

int Audio_expander_serialflash_raw::time_lapse(void) // reporting the computational time "t"
{
    return t;
}

void Audio_expander_serialflash_raw::set_ADSR(float attack_time, float decay_time, float sustain_value, float release_time) // ***** set ADSR parameters. Times are in seconds. 0 <= sustain_value <= 1.0
{
    attack_time=((attack_time>0.05)? attack_time : 0);
    C1 = ((attack_time >0.05)? Alpha/attack_time : 0);
    decay_time=((decay_time>0.05)? decay_time : 0.05);
    C2 = (sustain_value-1.0) * Alpha/decay_time;
    J1 = ((attack_time>0.05)? 1.0/C1: 0);
    J2 = (attack_time + decay_time)/Alpha;
    release_time = ((release_time>0.05)? release_time : 0.05);
    DJ3_4 = release_time/Alpha;
}

void Audio_expander_serialflash_raw::release_note(void)
{
    gain_0 = gain;
    J3 = (j_adsr/AUDIO_BLOCK_SAMPLES)*AUDIO_BLOCK_SAMPLES; // J3 = ride*AUDIO_BLOCK_SAMPLES;
    J4 = J3+DJ3_4;
    C3 = -gain_0/DJ3_4;
    start_release = 1;
}

bool Audio_expander_serialflash_raw::is_playing(void) // checking if the object is playing
{
    return playing;
}

void Audio_expander_serialflash_raw::set_pitch_bend (float pitch_bend_value) // 0.5= half speed, 1.0= no change 2.0=double speed
{
    pitch_bend = ((pitch_bend_value>=0.1)? ((pitch_bend_value<=2.0)? pitch_bend_value : 2.0) : 0.1) ; // set pitch value within limits
    if (playing)
    {
        base_file_sample_offset=file_sample_offset + (speed*pitch_bend); // when pitch bend is set, re-start from the next virtual_file_sample_offset
        main_index=0;
    }
}

This is my Arduino code:
Code:
/*
  Teensy MIDI Expander
  Board: Teensy 3.6 + Audio Adaptor Board + W25Q128FV flash memory chip
*/

#include <MIDI.h> // https://www.pjrc.com/teensy/td_libs_MIDI.html
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SerialFlash.h>
#include <Audio_expander_serialflash_raw.h>

#define voices 4

Audio_expander_serialflash_raw  voice[voices];
AudioMixer4              mixer1;
AudioOutputI2S           audio_out;
AudioConnection          patchCord1(voice[0], 0, mixer1, 0);
AudioConnection          patchCord2(voice[1], 0, mixer1, 1);
AudioConnection          patchCord3(voice[2], 0, mixer1, 2);
AudioConnection          patchCord4(voice[3], 0, mixer1, 3);
AudioConnection          patchCord5(mixer1, 0, audio_out, 1);
AudioConnection          patchCord6(mixer1, 0, audio_out, 0);
AudioControlSGTL5000     board;

int note_number;
float velocity;
float note_number_to_pitch[128];
int i;
int u, w;
unsigned long voice_timer[voices];
int key_played_by[voices];
float gain_0 = 1.0 / voices;
float attack_time, decay_time, sustain_value, release_time, pitch_bend_value, pitch_bend_value_0;
int pot_value[50][2];
int file=0;

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
const int channel = 1; // canale MIDI

void setup() {
  MIDI.begin(MIDI_CHANNEL_OMNI);
  Serial.begin (9600);
  AudioMemory(40); // valore critico
  /* AudioMemory: allocate the memory for all audio connections. The numberBlocks input specifies how much memory to reserve for audio data.
    Each block holds 128 audio samples, or approx 2.9 ms of sound. Usually an initial guess is made for numberBlocks and the actual
    usage is checked with AudioMemoryUsageMax().
  */

  // inizializzazione Audio Adaptor
  board.enable();
  board.volume(1.0);

  // inizializzazione mixer
  mixer1.gain(0, gain_0);
  mixer1.gain(1, gain_0);
  mixer1.gain(2, gain_0);
  mixer1.gain(3, gain_0);

  // set up SPI Teensy to SPI Flash....if you are using different pins set them here
  SPI.setMOSI(7);
  SPI.setMISO(12);
  SPI.setSCK(14);
  SerialFlash.begin(6);
  delay(100);

  for (i = 0; i < voices; i++) {
    voice[i].set_ADSR(0, 2, 1, 0.2);
    voice_timer[i] = 0;
  }

  // conversion constants
  for (i = 0; i < 128; i++) {
    note_number_to_pitch[i] = pow(2.0, (i - 60.0) / 12.0); // vettore per la conversione da note number a pitch
  }
}


void loop() {
  if (MIDI.read()) {
    switch (MIDI.getType()) {
      case midi::NoteOn:
        note_number = MIDI.getData1();
        velocity = MIDI.getData2() / 127.0;
        // Serial.println (note_number);
        // Serial.println (note_number_to_pitch[note_number]);
        play_note(note_number, velocity);
        break;

      case midi::NoteOff:
        note_number = MIDI.getData1();
        velocity = MIDI.getData2() / 127.0;
        // Serial.println (note_number);
        release_voice(note_number);
        break;

      case midi::PitchBend:
        // pitch_bend_value = ((MIDI.getData2() - 64) / 63.0) + 1.0;
        pitch_bend_value = (MIDI.getData2() / 127.0) + 0.496063;
        AudioNoInterrupts();
        for (i = 0; i < voices; i++)
        {
          voice[i].set_pitch_bend(pitch_bend_value);
        }
        AudioInterrupts();
        break;

      default:
        break;
    }
  }

  w++;
  if (w == 2000)
  {
    for (i = 0; i < voices; i++)
    {
      u += voice[i].time_lapse();
    }
    Serial.println (u);
    u = 0;
    w = 0;
  }

  attack_time = 2 * analogRead(34) / 1023.0;
  decay_time = 2 * analogRead(35) / 1023.0;
  sustain_value = analogRead(36) / 1023.0;
  release_time = 10 * analogRead(37) / 1023.0;

  if (read_pot(33, 1) == 1) {
    if (pot_value[33][1] <= 6)
      file = 0;
    else if (pot_value[33][1] <= 80)
      file = 1;
    else if (pot_value[33][1] <= 222)
      file = 2;
    else if (pot_value[33][1] <= 375)
      file = 3;
    else if (pot_value[33][1] <= 466)
      file = 4;
    else if (pot_value[33][1] <= 540)
      file = 5;
    else if (pot_value[33][1] <= 663)
      file = 6;
    else if (pot_value[33][1] <= 765)
      file = 7;
    else if (pot_value[33][1] <= 900)
      file = 8;
    else if (pot_value[33][1] <= 1016)
      file = 9;
    else
      file = 10;
  }
}

// play note
void play_note(int note_number, float velocity) {
  unsigned long minimum_time = voice_timer[0];
  int i;
  int ii = -1;

  for (i = 0; i < voices; i++) { // first test: if the same key is played we use the same voice
    if (key_played_by[i] == note_number) {
      ii = i;
      break;
    }
  }

  if (ii == -1) // second test choice: look for a voice not busy
  {
    for (i = 0; i < voices; i++)
    {
      if (voice[i].is_playing() == false) {
        ii = i;
        break;
      }
    }
  }

  if (ii == -1) // last test: choose the voice playing for the longest time
  {
    ii = 0;
    for (i = 0; i < voices; i++)
    {
      if (voice_timer[i] < minimum_time ) { // final test choice: look for the voice playing for the longest time
        minimum_time = voice_timer[i];
        ii = i;
      }
    }
  }
  key_played_by[ii] = note_number;
  voice[ii].set_ADSR(attack_time, decay_time, sustain_value, release_time);
  switch (file)
  {
  case 0:
  voice[ii].play("CAMPANA.RAW", note_number_to_pitch[note_number], velocity);
  break;
  
  case 1:
  voice[ii].play("SNAREV3.RAW", note_number_to_pitch[note_number], velocity);
  break;

  case 2:
  voice[ii].play("WAVE.RAW", note_number_to_pitch[note_number], velocity);
  break;
  
  default:
  voice[ii].play("METALS~1.RAW", note_number_to_pitch[note_number], velocity);
  break;
  }
  voice_timer[ii] = millis();
}

// stop note
void release_voice(int note_number) {
  int i;
  for (i = 0; i < 4; i++) {
    if (key_played_by[i] == note_number) {
      voice[i].release_note();
      break;
    }
  }
}

// reading pots
byte read_pot(byte pot, byte isteresi) {
  pot_value[pot][1] = analogRead(pot);
  if (abs(pot_value[pot][1] - pot_value[pot][0]) > isteresi) {
    pot_value[pot][0] = pot_value[pot][1];
    return 1;
  }
  else {
    return 0;
  }
}

Thank you for your attention! :eek:
Sandro
 
new code (semplification for update: function; bug fixed)

Hi all:

Header:
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw", speed_value, velocity_value) plays audio .RAW files from flash ROM memory chip with the required speed (pitch) and velocity (in the MIDI sense).
 * - set_ADSR(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - set_pitch_bend (pitch_bend_value) set the pitch bend value
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory: it depends from "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 */

#ifndef Audio_expander_serialflash_raw_h_
#define Audio_expander_serialflash_raw_h_

#include "Arduino.h"
#include <AudioStream.h>
#include <SerialFlash.h>

class Audio_expander_serialflash_raw : public AudioStream
{
public:
	Audio_expander_serialflash_raw(void) : AudioStream(0, NULL) { begin(); }
	void begin(void);
	bool play(const char *filename, float speed_value, float velocity_value);
	void set_note(float speed_value, float velocity_value);
	void stop(void);
	bool isPlaying(void) { return playing; }
	uint32_t positionMillis(void);
	uint32_t lengthMillis(void);
	virtual void update(void);
	int time_lapse(void);
    void set_ADSR(float attack_time, float decay_time, float sustain_value, float release_time);
    void release_note(void);
    bool is_playing(void);
    void set_pitch_bend (float pitch_bend_value);

private:
	SerialFlashFile rawfile;
    volatile bool playing;
    uint32_t file_size;
	volatile uint32_t file_offset; // point the BYTE
    float file_sample_offset; // point the SAMPLE
    float base_file_sample_offset;
    int32_t j_adsr;
    int32_t samples_available;
    int8_t start_release;
    int8_t send_joint;
	int32_t J1, J2, J3, J4, DJ3_4; 	// indexes of samples
	int16_t last_value;  // values of samples
	float speed; // 0.5=half speed 1=original speed 2=double speed
	float velocity;	// 0 <= velocity <= 1.0
	int64_t t, t_0; // *t is the computational time, must be within 2902 microseconds (period of update() when 128 samples are required)
	float C1, C2, C3, gain, gain_0, sustain;    // coefficients for ADSR
	const float Alpha = 1.0/44100.0;
	float pitch_bend = 1.0;
	const int8_t min_speed = 0.01;
	const int8_t max_speed = 7.0;
};

#endif

CPP file:
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified to use SerialFlash instead of SD library by Wyatt Olson <wyatt@digitalcave.ca>
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


/* Audio Expander Serialflash raw
 *
 * Functions/commands:
 * - play("filename.raw", speed_value, velocity_value) plays audio .RAW files from flash ROM memory chip with the required speed (pitch) and velocity (in the MIDI sense).
 * - set_ADSR(attack_time, decay_time, sustain_value, release_time) set ADSR envelope filter.
 * - set_pitch_bend (pitch_bend_value) set the pitch bend value
 * - release_note() is the release command.
 * - time_lapse() allows to check the computation time of each update() cycle.
 * - is_playing() useful to monitor the state of the instance.

 * The computation time of each update() cycle MUST be within 2,9 ms; computation depends on how
 * many samples have to be read from flash memory: it depends from "speed" value (high speeds require
 * many samples to be read each update() cycle. I experimented that up to 4 simultaneously-playing instances
 * can run on Teensy 3.6, overclock 240MHz, if speed is limited to 7.0.
 *
 *
 */


#include <Arduino.h>
#include "Audio_expander_serialflash_raw.h"
#include "spi_interrupt.h"

void Audio_expander_serialflash_raw::begin(void)
{
    last_value = 0;
    playing = false;
    file_offset = 0;
    file_size = 0;
}

bool Audio_expander_serialflash_raw::play(const char *filename, float speed_value, float velocity_value)
{
    stop();
    AudioStartUsingSPI();
    rawfile = SerialFlash.open(filename);
    if (!rawfile)
    {
        AudioStopUsingSPI();
        return false;
    }
    // file_size = rawfile.size();
    speed = ((speed_value>=min_speed)? ((speed_value<=max_speed)? speed_value : max_speed) : min_speed); // set speed value within limits
    velocity = velocity_value;
    file_size = rawfile.size() - (rawfile.size()%256);
    send_joint = 1; //
    file_offset = 0; // punta il BYTE
    file_sample_offset = 0.0; // punta in SAMPLE
    samples_available = file_size/2;
    base_file_sample_offset = 0;
    rawfile.seek(file_offset);
    start_release = 0; // allows ADRS to start
    j_adsr=0;

    playing = true;
    return true;
}


void Audio_expander_serialflash_raw::stop(void)
{
    __disable_irq();
    if (playing)
    {
        playing = false;
        __enable_irq();
        rawfile.close();
        AudioStopUsingSPI();
    }
    else
    {
        __enable_irq();
    }
}


void Audio_expander_serialflash_raw::update(void)
{
    int i;
    int n;
    audio_block_t *block;
    int16_t samples_basket[AUDIO_BLOCK_SAMPLES*11]; // current vector of samples read from file
    int32_t LOW_sample, HIGH_sample;  // indexes of samples in RAW file
    int32_t L_sample, H_sample;  // indexes of samples in sample_basket
    int16_t samples_to_read;
    float index_delta;
    int16_t x_index_delta; 	// mantissa of virtual_file_offset
    float Cx;
    float speed_bend;
    float basket_offset;


    // only update if we're playing
    if (!playing)
        return;

    // allocate the audio blocks to transmit
    block = allocate();
    if (block == NULL)
        return;

    if (rawfile.available())
    {
        // this first ride generate a linear ramp which connects the last sample sent to 0, which is (MUST be) the value of the first sample of the .RAW file to be played
        t_0 = micros(); // start counting the computational time

        if (send_joint == 1)
        {
            Cx = last_value/129.0;
            for (i=0; i<AUDIO_BLOCK_SAMPLES; i++)
            {
                block->data[i] = last_value-(Cx*(i+1));
            }
            transmit(block);
            last_value = block->data[127];
            send_joint = 0;
        }

        // from this point samples are taken from the .RAW file
        else if (file_offset<file_size)
        {
            speed_bend = ((pitch_bend<1)? speed*pitch_bend : ((speed<(0.5*max_speed))? speed*pitch_bend : speed));
            LOW_sample = floor(base_file_sample_offset);
            HIGH_sample = ceil((speed_bend*(AUDIO_BLOCK_SAMPLES-1))+base_file_sample_offset);
            if (HIGH_sample>=samples_available)
            {
                stop();
            }

            samples_to_read = HIGH_sample-LOW_sample+1;
            file_offset=LOW_sample<<1;
            rawfile.seek(file_offset);
            n=rawfile.read(samples_basket, samples_to_read<<1);
            for (i=n/2; i < samples_to_read; i++)
            {
                samples_basket[i] = 0;
            }

            // calculating block->data[128] to be sent to the output Connection
            for (i = 0; i<AUDIO_BLOCK_SAMPLES; i++)
            {
                file_sample_offset=base_file_sample_offset+(i*speed_bend); // index of the needed sample, usually not-integer
                basket_offset=file_sample_offset-LOW_sample;
                L_sample = floor(basket_offset);// // index of the lower sample needed for calculation
                H_sample = ceil(basket_offset); // index of the upper sample needed for calculation
                index_delta = basket_offset-L_sample;
                x_index_delta = index_delta*1024.0;

                //ADSR envelope filter uses the specific index j_adsr to be independent from main_index
                if(start_release==0 && j_adsr<J1) // attack procedure
                {
                    gain = C1*j_adsr;
                }
                else if (start_release==0 && j_adsr<J2) // decay procedure
                {
                    gain = (C2*(j_adsr-J1))+ 1.0;
                }
                else if (start_release==1 && j_adsr<J4) // release procedure
                {
                    gain = (C3*(j_adsr-J3)) + gain_0;
                }
                else if (start_release==1 && j_adsr>=J4) // stop player!
                {
                    stop();
                }

                block->data[i] = gain*velocity*(samples_basket[L_sample] + ((x_index_delta*(samples_basket[H_sample]-samples_basket[L_sample]))>>10));
                j_adsr ++;
            }
            base_file_sample_offset = file_sample_offset+speed_bend;
            last_value = block->data[127];
            t = micros()-t_0; // calculating the computational time "t"
            transmit(block);
        }
        else
        {
            file_offset = 0;
            stop();
        }

    }
    else
    {
        rawfile.close();
        AudioStopUsingSPI();
        playing = false;
    }

    release(block);
}

#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592

uint32_t Audio_expander_serialflash_raw::positionMillis(void)
{
    return ((uint64_t)file_offset * B2M)>>32;
}

uint32_t Audio_expander_serialflash_raw::lengthMillis(void)
{
    return ((uint64_t)file_size * B2M)>>32;
}

int Audio_expander_serialflash_raw::time_lapse(void) // reporting the computational time "t"
{
    return t;
}

void Audio_expander_serialflash_raw::set_ADSR(float attack_time, float decay_time, float sustain_value, float release_time) // ***** set ADSR parameters. Times are in seconds. 0 <= sustain_value <= 1.0
{
    attack_time = ((attack_time>0.05)? attack_time : 0);
    C1 = ((attack_time >0.05)? Alpha/attack_time : 0);
    decay_time = ((decay_time>0.05)? decay_time : 0.05);
    C2 = (sustain_value-1.0) * Alpha/decay_time;
    J1 = ((attack_time>0.05)? 1.0/C1: 0);
    J2 = (attack_time + decay_time)/Alpha;
    release_time = ((release_time>0.05)? release_time : 0.05);
    DJ3_4 = release_time/Alpha;
}

void Audio_expander_serialflash_raw::release_note(void)
{
    gain_0 = gain;
    J3 = (j_adsr/AUDIO_BLOCK_SAMPLES)*AUDIO_BLOCK_SAMPLES; // J3 = ride*AUDIO_BLOCK_SAMPLES;
    J4 = J3+DJ3_4;
    C3 = -gain_0/DJ3_4;
    start_release = 1;
}

bool Audio_expander_serialflash_raw::is_playing(void) // checking if the object is playing
{
    return playing;
}

void Audio_expander_serialflash_raw::set_pitch_bend (float pitch_bend_value) // 0.5= half speed, 1.0= no change 2.0=double speed
{
    pitch_bend = ((pitch_bend_value>=0.1)? ((pitch_bend_value<=2.0)? pitch_bend_value : 2.0) : 0.1) ; // set pitch value within limits
}

Arduino patch:
SAME AS LAST POST

Bye
Sandro
 
So I've been trying to get this working and have been unsuccessful. Is there a way to make this work from internal memory? I have my files encoded as .h files and they work fine when playing via AudioPlayMemory.

Thanks!
 
So I've been trying to get this working and have been unsuccessful. Is there a way to make this work from internal memory? I have my files encoded as .h files and they work fine when playing via AudioPlayMemory.

Thanks!

Hi, I don't know AudioPlayMemory deeply, but I belive it's possible to do what you need. First you need a function which can fill samples_basket[] giving a "memory_offset" and "samples_to_read". If we refer to the case 16 bit PCM, 44100 Hz, the reading funcion in AudioPlayMemory is:

Code:
case 0x81: // 16 bit PCM, 44100 Hz
		for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 2) {
			tmp32 = *in++;
			*out++ = (int16_t)(tmp32 & 65535);
			*out++ = (int16_t)(tmp32 >> 16);
		}
		consumed = AUDIO_BLOCK_SAMPLES;
		break;

So, I think that you could begin writing something like this in the update() of your new AudioPlayMemory_NEW function,

Code:
	int16_t samples_to_read;
        int16_t samples_basket[AUDIO_BLOCK_SAMPLES*11]; // current vector of samples read from file
	out = samples_basket;  // instead of "out = block->data;"


	  case 0x81: // 16 bit PCM, 44100 Hz
		for (i=0; i < samples_to_read; i += 2) {   // instead of     for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 2) {
			tmp32 = *memory_offset ++; // instead of   tmp32 = *in++  Notice "memory_offset" is a pointer to the start point, declared in AudioPlayMemory_NEW.h (because it's needed by other functions in the object).
			*out++ = (int16_t)(tmp32 & 65535);
			*out++ = (int16_t)(tmp32 >> 16);
		}
		consumed = samples_to_read;  //  instead of     consumed = AUDIO_BLOCK_SAMPLES;
		break;

Bye
 
Wouldn‘t it be more efficient to share your code on github and posting just one single link to the repository instead of copying/pasting multiple variants of your code here? With that, people would always get the most recent release.
 
Wouldn‘t it be more efficient to share your code on github and posting just one single link to the repository instead of copying/pasting multiple variants of your code here? With that, people would always get the most recent release.

Theremingenieur I agree! Few moths ago I tried using github, but I felt frustrated that I was not able to use README.md in a decent way. I should read some tutorial first.
 
Ok.. so what I think you're saying is to:
1: create a NEW play_memory.cpp file. (for example: play_memory_pitched.cpp)
2: Keep everything the same except the changes you have above and change the function's name itself within play_memory_pitched.cpp .
3: create a play_memory_pitched.h file as well and change it to reference play_memory_pitched.cpp.
4: also change the name of the function in play_memory_pitched.h as well.
5: call this new function in my .ino file.

is that right?
Thanks!
 
Ok.. so what I think you're saying is to:
1: create a NEW play_memory.cpp file. (for example: play_memory_pitched.cpp)
2: Keep everything the same except the changes you have above and change the function's name itself within play_memory_pitched.cpp .
3: create a play_memory_pitched.h file as well and change it to reference play_memory_pitched.cpp.
4: also change the name of the function in play_memory_pitched.h as well.
5: call this new function in my .ino file.

is that right?
Thanks!

Yes :). Also notice that my object works with .RAW audio files: I belive that some mods are required if you use other audio formats.
Bye
 
So I have my new .cpp file where I've changed update as follows:
Code:
void AudioPlayMemoryPitched::update(void)
{
	audio_block_t *block;
	const unsigned int *in;
	int16_t *out;
	uint32_t tmp32, consumed;
	int16_t s0, s1, s2, s3, s4;
	int i;

	if (!playing) return;
	block = allocate();
	if (block == NULL) return;

	//Serial.write('.');
	int16_t samples_to_read;
    int16_t samples_basket[AUDIO_BLOCK_SAMPLES*11]; // current vector of samples read from file
	out = samples_basket;  // instead of "out = block->data;"
	//out = block->data;
	in = next;
	s0 = prior;

	switch (playing) {
	 /* everything other then 16bit 44100 hz commented out...*/

		case 0x81: // 16 bit PCM, 44100 Hz
		for (i=0; i < samples_to_read; i += 2) {   // instead of     for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 2) {
			tmp32 = *memory_offset ++; // instead of   tmp32 = *in++  Notice "memory_offset" is a pointer to the start point, declared in AudioPlayMemory_NEW.h (because it's needed by other functions in the object).
			*out++ = (int16_t)(tmp32 & 65535);
			*out++ = (int16_t)(tmp32 >> 16);
		}
		consumed = samples_to_read;  //  instead of     consumed = AUDIO_BLOCK_SAMPLES;
		break;

	   /* everything other then 16bit 44100 hz commented out...*/

	  default:
		release(block);
		playing = 0;
		return;
	}
	prior = s0;
	next = in;
	if (length > consumed) {
		length -= consumed;
	} else {
		playing = 0;
	}
	transmit(block);
	release(block);
}

and I have my new .h file:
Code:
#ifndef play_memory_pitched_h_
#define play_memory_pitched_h_

#include "Arduino.h"
#include "AudioStream.h"

class AudioPlayMemoryPitched : public AudioStream {
public:
	AudioPlayMemoryPitched(void) : AudioStream(0, NULL), playing(0) { }
	void play(const unsigned int *data);
	void stop(void);
	bool isPlaying(void) { return playing; }
	uint32_t positionMillis(void);
	uint32_t lengthMillis(void);
	virtual void update(void);
private:
	const unsigned int *next;
	const unsigned int *beginning;
	uint32_t length;
	int16_t prior;
	volatile uint8_t playing;
};

#endif

But I don't see how you get the pitch change input over to the play/update function. I feel like I missed a step somewhere?

Thanks so much for your help on this, btw.
 
Hi SpeakingInTones,
looking at your .h file, I understand that your .cpp file (even if not fully published) is still missing many lines; modifying update() is the FIRST step, than you should merge/add my code (or part of it) in AudioPlayMemoryPitched in order to get what you need.
Bye
Sandro
 
Hi SpeakingInTones,
looking at your .h file, I understand that your .cpp file (even if not fully published) is still missing many lines; modifying update() is the FIRST step, than you should merge/add my code (or part of it) in AudioPlayMemoryPitched in order to get what you need.
Bye
Sandro

That makes a lot more sense. :) I'll take another whack at it later this week and update if I find anything.
 
Sounds really cool! Do you have a video available to show what it sounds like? I’ll need to order some parts soon :)
 
Status
Not open for further replies.
Back
Top