Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 5 of 5

Thread: Audio Expander for Teensy

  1. #1
    Member
    Join Date
    Nov 2016
    Location
    Rimini - Italy
    Posts
    37

    Audio Expander for Teensy

    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 by Sandro; 07-26-2018 at 04:14 PM.

  2. #2
    Member
    Join Date
    Nov 2016
    Location
    Rimini - Italy
    Posts
    37
    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!

  3. #3
    Member
    Join Date
    Nov 2016
    Location
    Rimini - Italy
    Posts
    37
    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;
            }
        }
    }

  4. #4
    Member
    Join Date
    Nov 2016
    Location
    Rimini - Italy
    Posts
    37
    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

  5. #5
    Member
    Join Date
    Nov 2016
    Location
    Rimini - Italy
    Posts
    37

    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..

    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!
    Sandro

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •