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