Fixed AudioSynthWaveform. Uses LUT with linear interpolation for sinewav...dds
// Generated from Excel by =ROUND(2*440/32*(2^((x-9)/12)),0) for 0<x<128 | // Generated from Excel by =ROUND(2*440/32*(2^((x-9)/12)),0) for 0<x<128 | ||||
// The lowest notes might not work, depending on the Arduino clock frequency | // The lowest notes might not work, depending on the Arduino clock frequency | ||||
#ifdef ORIGINAL_TABLE | |||||
const unsigned int tune_frequencies2_PGM[128] = | const unsigned int tune_frequencies2_PGM[128] = | ||||
{ | { | ||||
16,17,18,19,21,22,23,24,26,28,29,31,33,35,37,39,41, | 16,17,18,19,21,22,23,24,26,28,29,31,33,35,37,39,41, | ||||
13290,14080,14917,15804,16744,17740,18795,19912,21096, | 13290,14080,14917,15804,16744,17740,18795,19912,21096, | ||||
22351,23680,25088 | 22351,23680,25088 | ||||
}; | }; | ||||
#else | |||||
// This is for the Teensy Audio library which specifies | |||||
// frequencies as floating point. See make_notetab.xlsx | |||||
const float tune_frequencies2_PGM[128] = | |||||
{ | |||||
8.1758, 8.6620, 9.1770, 9.7227, 10.3009, 10.9134, 11.5623, 12.2499, | |||||
12.9783, 13.7500, 14.5676, 15.4339, 16.3516, 17.3239, 18.3540, 19.4454, | |||||
20.6017, 21.8268, 23.1247, 24.4997, 25.9565, 27.5000, 29.1352, 30.8677, | |||||
32.7032, 34.6478, 36.7081, 38.8909, 41.2034, 43.6535, 46.2493, 48.9994, | |||||
51.9131, 55.0000, 58.2705, 61.7354, 65.4064, 69.2957, 73.4162, 77.7817, | |||||
82.4069, 87.3071, 92.4986, 97.9989, 103.8262, 110.0000, 116.5409, 123.4708, | |||||
130.8128, 138.5913, 146.8324, 155.5635, 164.8138, 174.6141, 184.9972, 195.9977, | |||||
207.6523, 220.0000, 233.0819, 246.9417, 261.6256, 277.1826, 293.6648, 311.1270, | |||||
329.6276, 349.2282, 369.9944, 391.9954, 415.3047, 440.0000, 466.1638, 493.8833, | |||||
523.2511, 554.3653, 587.3295, 622.2540, 659.2551, 698.4565, 739.9888, 783.9909, | |||||
830.6094, 880.0000, 932.3275, 987.7666, 1046.5023, 1108.7305, 1174.6591, 1244.5079, | |||||
1318.5102, 1396.9129, 1479.9777, 1567.9817, 1661.2188, 1760.0000, 1864.6550, 1975.5332, | |||||
2093.0045, 2217.4610, 2349.3181, 2489.0159, 2637.0205, 2793.8259, 2959.9554, 3135.9635, | |||||
3322.4376, 3520.0000, 3729.3101, 3951.0664, 4186.0090, 4434.9221, 4698.6363, 4978.0317, | |||||
5274.0409, 5587.6517, 5919.9108, 6271.9270, 6644.8752, 7040.0000, 7458.6202, 7902.1328, | |||||
8372.0181, 8869.8442, 9397.2726, 9956.0635, 10548.0818, 11175.3034, 11839.8215, 12543.8540 | |||||
}; | |||||
#endif | |||||
#define CMD_PLAYNOTE 0x90 /* play a note: low nibble is generator #, note is next byte */ | #define CMD_PLAYNOTE 0x90 /* play a note: low nibble is generator #, note is next byte */ | ||||
#define CMD_STOPNOTE 0x80 /* stop a note: low nibble is generator # */ | #define CMD_STOPNOTE 0x80 /* stop a note: low nibble is generator # */ |
// Implement the midi player inside the Audio library. | // Implement the midi player inside the Audio library. | ||||
// This uses the new version of the waveform generator code | // This uses the new version of the waveform generator code | ||||
// See PlayMidiTones for code which uses the old version | // See PlayMidiTones for code which uses the old version | ||||
#include <Audio.h> | #include <Audio.h> | ||||
#include <Wire.h> | #include <Wire.h> | ||||
&sine7, | &sine7, | ||||
}; | }; | ||||
// allocate a wave type to each channel. | |||||
// The types used and their order is purely arbitrary. | |||||
short wave_type[8] = { | |||||
TONE_TYPE_SINE, | |||||
TONE_TYPE_SQUARE, | |||||
TONE_TYPE_SAWTOOTH, | |||||
TONE_TYPE_TRIANGLE, | |||||
TONE_TYPE_SINE, | |||||
TONE_TYPE_SQUARE, | |||||
TONE_TYPE_SAWTOOTH, | |||||
TONE_TYPE_TRIANGLE, | |||||
}; | |||||
// Two mixers are needed to handle the 8 channels of music | |||||
AudioMixer4 mixer1; | AudioMixer4 mixer1; | ||||
AudioMixer4 mixer2; | AudioMixer4 mixer2; | ||||
AudioOutputI2S audioOut; | AudioOutputI2S audioOut; | ||||
// Mix the first four channels into mixer1 | |||||
AudioConnection c0(sine0, 0, mixer1, 0); | AudioConnection c0(sine0, 0, mixer1, 0); | ||||
AudioConnection c1(sine1, 0, mixer1, 1); | AudioConnection c1(sine1, 0, mixer1, 1); | ||||
AudioConnection c2(sine2, 0, mixer1, 2); | AudioConnection c2(sine2, 0, mixer1, 2); | ||||
AudioConnection c3(sine3, 0, mixer1, 3); | AudioConnection c3(sine3, 0, mixer1, 3); | ||||
// and the last 4 channels into mixer2 | |||||
AudioConnection c4(sine4, 0, mixer2, 0); | AudioConnection c4(sine4, 0, mixer2, 0); | ||||
AudioConnection c5(sine5, 0, mixer2, 1); | AudioConnection c5(sine5, 0, mixer2, 1); | ||||
AudioConnection c6(sine6, 0, mixer2, 2); | AudioConnection c6(sine6, 0, mixer2, 2); | ||||
AudioConnection c7(sine7, 0, mixer2, 3); | AudioConnection c7(sine7, 0, mixer2, 3); | ||||
// Output mixre1 to the left channel and mixer2 to the right | |||||
AudioConnection c11(mixer1, 0, audioOut, 0); | AudioConnection c11(mixer1, 0, audioOut, 0); | ||||
AudioConnection c12(mixer2, 0, audioOut, 1); | AudioConnection c12(mixer2, 0, audioOut, 1); | ||||
//AudioControl_WM8731 codec; | //AudioControl_WM8731 codec; | ||||
AudioControlSGTL5000 codec; | AudioControlSGTL5000 codec; | ||||
// Initial value of the volume control | |||||
int volume = 50; | int volume = 50; | ||||
// allocate a wave type to each channel. | |||||
// The types used and their order is purely arbitrary. | |||||
short wave_type[8] = { | |||||
TONE_TYPE_SINE, | |||||
TONE_TYPE_SQUARE, | |||||
TONE_TYPE_SAWTOOTH, | |||||
TONE_TYPE_TRIANGLE, | |||||
TONE_TYPE_SINE, | |||||
TONE_TYPE_SQUARE, | |||||
TONE_TYPE_SAWTOOTH, | |||||
TONE_TYPE_TRIANGLE, | |||||
}; | |||||
void setup() | void setup() | ||||
{ | { | ||||
Serial.begin(115200); | Serial.begin(115200); | ||||
while (!Serial) ; | while (!Serial) ; | ||||
delay(3000); | |||||
delay(2000); | |||||
// http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html | |||||
Serial.print("Begin "); | |||||
Serial.println(__FILE__); | |||||
// Proc = 12 (13), Mem = 2 (8) | |||||
// Audio connections require memory to work. | // Audio connections require memory to work. | ||||
// The memory usage code indicates that 8 is the maximum | // The memory usage code indicates that 8 is the maximum | ||||
// so give it 10 just to be sure. | // so give it 10 just to be sure. | ||||
// Comment this if you don't it | // Comment this if you don't it | ||||
codec.unmuteLineout(); | codec.unmuteLineout(); | ||||
// Set the ramp time for each wave object | // Set the ramp time for each wave object | ||||
for(int i = 0; i < 8;i++) { | for(int i = 0; i < 8;i++) { | ||||
waves[i]->set_ramp_length(88); | waves[i]->set_ramp_length(88); | ||||
} | } | ||||
Serial.println("Begin PlayMidiTones"); | |||||
Serial.println("setup done"); | Serial.println("setup done"); | ||||
// Initialize processor and memory measurements | // Initialize processor and memory measurements | ||||
unsigned char c,opcode,chan; | unsigned char c,opcode,chan; | ||||
unsigned long d_time; | unsigned long d_time; | ||||
// Change this to if(1) for measurement output | |||||
if(0) { | |||||
// Change this to if(1) for measurement output every 5 seconds | |||||
if(1) { | |||||
/* | /* | ||||
For PlaySynthMusic this produces: | For PlaySynthMusic this produces: | ||||
Proc = 20 (21), Mem = 2 (8) | Proc = 20 (21), Mem = 2 (8) | ||||
codec.volume((float)n / 10.23); | codec.volume((float)n / 10.23); | ||||
} | } | ||||
// read the next note from the table | |||||
c = *sp++; | c = *sp++; | ||||
opcode = c & 0xf0; | opcode = c & 0xf0; | ||||
// was 0x0f but I'm only handling 8 channels | // was 0x0f but I'm only handling 8 channels | ||||
return; | return; | ||||
} | } | ||||
// Play the note on 'chan' | |||||
// Play the note on 'chan' - divide the frequency by two because the | |||||
// table of frequencies has been doubled. | |||||
if(opcode == CMD_PLAYNOTE) { | if(opcode == CMD_PLAYNOTE) { | ||||
waves[chan]->begin(AMPLITUDE,tune_frequencies2_PGM[*sp++], | waves[chan]->begin(AMPLITUDE,tune_frequencies2_PGM[*sp++], | ||||
wave_type[chan]); | wave_type[chan]); |
/* | /* | ||||
c | |||||
- released | |||||
b | |||||
- Use FIR filters with fast_fft option | |||||
The audio board uses the following pins. | The audio board uses the following pins. | ||||
6 - MEMCS | 6 - MEMCS | ||||
#include <Bounce.h> | #include <Bounce.h> | ||||
#include "filters.h" | #include "filters.h" | ||||
// If this pin is grounded the FIR filter is turned which | |||||
// makes just pass through the audio | |||||
// If this pin is grounded the FIR filter is turned off | |||||
// which just passes the audio sraight through | |||||
// Don't use any of the pins listed above | // Don't use any of the pins listed above | ||||
#define PASSTHRU_PIN 1 | #define PASSTHRU_PIN 1 | ||||
// If this pin goes low the next FIR filter in the list | // If this pin goes low the next FIR filter in the list | ||||
// is switched in. | // is switched in. | ||||
#define FILTER_PIN 0 | #define FILTER_PIN 0 | ||||
// debounce the passthru and filter switching pins | |||||
Bounce b_passthru = Bounce(PASSTHRU_PIN,15); | Bounce b_passthru = Bounce(PASSTHRU_PIN,15); | ||||
Bounce b_filter = Bounce(FILTER_PIN,15); | Bounce b_filter = Bounce(FILTER_PIN,15); | ||||
AudioInputI2S audioInput; // audio shield: mic or line-in | AudioInputI2S audioInput; // audio shield: mic or line-in | ||||
AudioFilterFIR myFilter; | |||||
// Use the fast FIR filter for left and right channels | |||||
AudioFilterFIR myFilterL(true); | |||||
AudioFilterFIR myFilterR(true); | |||||
AudioOutputI2S audioOutput; // audio shield: headphones & line-out | AudioOutputI2S audioOutput; // audio shield: headphones & line-out | ||||
// Create Audio connections between the components | // Create Audio connections between the components | ||||
// Both channels of the audio input go to the FIR filter | |||||
AudioConnection c1(audioInput, 0, myFilter, 0); | |||||
AudioConnection c2(audioInput, 1, myFilter, 1); | |||||
// both channels from the FIR filter go to the audio output | |||||
AudioConnection c3(myFilter, 0, audioOutput, 0); | |||||
AudioConnection c4(myFilter, 1, audioOutput, 1); | |||||
// Route audio into the left and right filters | |||||
AudioConnection c1(audioInput, 0, myFilterL, 0); | |||||
AudioConnection c2(audioInput, 1, myFilterR, 0); | |||||
// Route the output of the filters to their respective channels | |||||
AudioConnection c3(myFilterL, 0, audioOutput, 0); | |||||
AudioConnection c4(myFilterR, 0, audioOutput, 1); | |||||
AudioControlSGTL5000 audioShield; | AudioControlSGTL5000 audioShield; | ||||
// index of current filter. Start with the low pass. | // index of current filter. Start with the low pass. | ||||
int fir_idx = 0; | int fir_idx = 0; | ||||
struct fir_filter fir_list[] = { | struct fir_filter fir_list[] = { | ||||
low_pass , 100, // low pass with cutoff at 1kHz and -60dB at 2kHz | |||||
band_pass, 100, // bandpass 1200Hz - 1700Hz | |||||
NULL, 0 | |||||
{low_pass , 100}, // low pass with cutoff at 1kHz and -60dB at 2kHz | |||||
{band_pass, 100}, // bandpass 1200Hz - 1700Hz | |||||
{NULL, 0} | |||||
}; | }; | ||||
Serial.println(") is grounded"); | Serial.println(") is grounded"); | ||||
} | } | ||||
// Initialize the filter | // Initialize the filter | ||||
myFilter.begin(fir_list[0].coeffs,fir_list[0].num_coeffs); | |||||
myFilterL.begin(fir_list[0].coeffs,fir_list[0].num_coeffs); | |||||
myFilterR.begin(fir_list[0].coeffs,fir_list[0].num_coeffs); | |||||
Serial.println("setup done"); | Serial.println("setup done"); | ||||
} | } | ||||
// audio volume | // audio volume | ||||
int volume = 0; | int volume = 0; | ||||
unsigned long last_time = millis(); | |||||
void loop() | void loop() | ||||
{ | { | ||||
// Volume control | // Volume control | ||||
b_passthru.update(); | b_passthru.update(); | ||||
b_filter.update(); | b_filter.update(); | ||||
// If the passthru button is pushed, save the current | // If the passthru button is pushed, save the current | ||||
// filter index and then switch the filter to passthru | // filter index and then switch the filter to passthru | ||||
if(b_passthru.fallingEdge()) { | if(b_passthru.fallingEdge()) { | ||||
old_idx = fir_idx; | old_idx = fir_idx; | ||||
myFilter.begin(FIR_PASSTHRU,0); | |||||
myFilterL.begin(FIR_PASSTHRU,0); | |||||
myFilterR.begin(FIR_PASSTHRU,0); | |||||
} | } | ||||
// If passthru button is released, restore previous filter | // If passthru button is released, restore previous filter | ||||
if(b_passthru.risingEdge()) { | if(b_passthru.risingEdge()) { | ||||
if(old_idx != -1)myFilter.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
if(old_idx != -1) { | |||||
myFilterL.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
myFilterR.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
} | |||||
old_idx = -1; | old_idx = -1; | ||||
} | } | ||||
if(b_filter.fallingEdge()) { | if(b_filter.fallingEdge()) { | ||||
fir_idx++; | fir_idx++; | ||||
if(fir_list[fir_idx].num_coeffs == 0)fir_idx = 0; | if(fir_list[fir_idx].num_coeffs == 0)fir_idx = 0; | ||||
myFilter.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
myFilterL.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
myFilterR.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
} | |||||
if(1) { | |||||
// With fast_fir | |||||
// Proc = 18 (18), Mem = 4 (6) | |||||
if(millis() - last_time >= 5000) { | |||||
Serial.print("Proc = "); | |||||
Serial.print(AudioProcessorUsage()); | |||||
Serial.print(" ("); | |||||
Serial.print(AudioProcessorUsageMax()); | |||||
Serial.print("), Mem = "); | |||||
Serial.print(AudioMemoryUsage()); | |||||
Serial.print(" ("); | |||||
Serial.print(AudioMemoryUsageMax()); | |||||
Serial.println(")"); | |||||
last_time = millis(); | |||||
} | } | ||||
} | } | ||||
} | |||||
// Initialize FIR instances for the left and right channels | // Initialize FIR instances for the left and right channels | ||||
if(coeff_p && (coeff_p != FIR_PASSTHRU)) { | if(coeff_p && (coeff_p != FIR_PASSTHRU)) { | ||||
arm_fir_init_q15(&l_fir_inst, n_coeffs, coeff_p, &l_StateQ15[0], AUDIO_BLOCK_SAMPLES); | arm_fir_init_q15(&l_fir_inst, n_coeffs, coeff_p, &l_StateQ15[0], AUDIO_BLOCK_SAMPLES); | ||||
arm_fir_init_q15(&r_fir_inst, n_coeffs, coeff_p, &r_StateQ15[0], AUDIO_BLOCK_SAMPLES); | |||||
} | } | ||||
} | } | ||||
transmit(block,0); | transmit(block,0); | ||||
release(block); | release(block); | ||||
} | } | ||||
block = receiveReadOnly(1); | |||||
if(block) { | |||||
transmit(block,1); | |||||
release(block); | |||||
} | |||||
return; | return; | ||||
} | } | ||||
// Left Channel | // Left Channel | ||||
// get a block for the FIR output | // get a block for the FIR output | ||||
b_new = allocate(); | b_new = allocate(); | ||||
if(block && b_new) { | if(block && b_new) { | ||||
arm_fir_q15(&l_fir_inst, (q15_t *)block->data, (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES); | |||||
if(arm_fast) | |||||
arm_fir_fast_q15(&l_fir_inst, (q15_t *)block->data, (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES); | |||||
else | |||||
arm_fir_q15(&l_fir_inst, (q15_t *)block->data, (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES); | |||||
// send the FIR output to the left channel | // send the FIR output to the left channel | ||||
transmit(b_new,0); | transmit(b_new,0); | ||||
} | } | ||||
if(block)release(block); | if(block)release(block); | ||||
if(b_new)release(b_new); | if(b_new)release(b_new); | ||||
// Right Channel | |||||
block = receiveReadOnly(1); | |||||
b_new = allocate(); | |||||
if(block && b_new) { | |||||
arm_fir_q15(&r_fir_inst, (q15_t *)block->data, (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES); | |||||
transmit(b_new,1); | |||||
} | |||||
if(block)release(block); | |||||
if(b_new)release(b_new); | |||||
} | } | ||||
#include "AudioStream.h" | #include "AudioStream.h" | ||||
#include "arm_math.h" | #include "arm_math.h" | ||||
#define USE_FAST_FIR true | |||||
#define USE_SLOW_FIR false | |||||
// Maximum number of coefficients in a FIR filter | // Maximum number of coefficients in a FIR filter | ||||
// The audio breaks up with 128 coefficients so a | // The audio breaks up with 128 coefficients so a | ||||
// maximum of 150 is more than sufficient | // maximum of 150 is more than sufficient | ||||
public AudioStream | public AudioStream | ||||
{ | { | ||||
public: | public: | ||||
AudioFilterFIR(void): | |||||
AudioStream(2,inputQueueArray), coeff_p(NULL) | |||||
AudioFilterFIR(const boolean a_f): | |||||
AudioStream(2,inputQueueArray), arm_fast(a_f), coeff_p(NULL) | |||||
{ | { | ||||
} | } | ||||
void begin(short *coeff_p,int f_pin); | |||||
void begin(short *coeff_p,int n_coeffs); | |||||
virtual void update(void); | virtual void update(void); | ||||
void stop(void); | void stop(void); | ||||
// the state arrays are defined to handle a maximum of MAX_COEFFS | // the state arrays are defined to handle a maximum of MAX_COEFFS | ||||
// coefficients in a filter | // coefficients in a filter | ||||
q15_t l_StateQ15[AUDIO_BLOCK_SAMPLES + MAX_COEFFS]; | q15_t l_StateQ15[AUDIO_BLOCK_SAMPLES + MAX_COEFFS]; | ||||
q15_t r_StateQ15[AUDIO_BLOCK_SAMPLES + MAX_COEFFS]; | |||||
arm_fir_instance_q15 l_fir_inst; | arm_fir_instance_q15 l_fir_inst; | ||||
arm_fir_instance_q15 r_fir_inst; | |||||
// pointer to current coefficients or NULL or FIR_PASSTHRU | // pointer to current coefficients or NULL or FIR_PASSTHRU | ||||
short *coeff_p; | short *coeff_p; | ||||
// Whether to use the fast arm FIR code | |||||
const boolean arm_fast; | |||||
}; | }; | ||||
#endif | #endif |
filter_fir 140418 | |||||
Filters the audio stream using FIR coefficients supplied by the user. | |||||
The ARM library has two q15 functions which perform an FIR filter. One uses | |||||
a 64-bit accumulator (arm_fir_q15) and the other uses a 32-bit accumulator | |||||
(arm_fir_fast_q15). When instantiating a filter a boolean argument specifies | |||||
which version to use. Specifying USE_FAST_FIR (defined as boolean true) uses | |||||
the fast code otherwise the slower code is used. For example: | |||||
AudioFilterFIR myFilterL(USE_FAST_FIR); | |||||
void begin(short *cp,int n_coeffs) | |||||
Starts the filter using coefficients at *cp and the number of coefficients | |||||
is n_coeffs. The special value FIR_PASSTHRU can be used in place of the | |||||
address of the coefficient array, in which case the function will just pass | |||||
audio samples through without filtering. | |||||
void stop(void) | |||||
Stops the filter. | |||||
#include "utility/dspinst.h" | #include "utility/dspinst.h" | ||||
#ifdef ORIGINAL_AUDIOSYNTHWAVEFORM | |||||
/******************************************************************/ | /******************************************************************/ | ||||
// PAH - add ramp-up and ramp-down to the onset of the wave | // PAH - add ramp-up and ramp-down to the onset of the wave | ||||
// the length is specified in samples | // the length is specified in samples | ||||
void AudioSynthWaveform::set_ramp_length(uint16_t r_length) | |||||
{ | |||||
if(r_length < 0) { | |||||
ramp_length = 0; | |||||
return; | |||||
} | |||||
// Don't set the ramp length longer than about 4 milliseconds | |||||
if(r_length > 44*4) { | |||||
ramp_length = 44*4; | |||||
return; | |||||
} | |||||
ramp_length = r_length; | |||||
} | |||||
void AudioSynthWaveform::update(void) | |||||
{ | |||||
audio_block_t *block; | |||||
uint32_t i, ph, inc, index, scale; | |||||
int32_t val1, val2, val3; | |||||
//Serial.println("AudioSynthWaveform::update"); | |||||
if (((magnitude > 0) || ramp_down) && (block = allocate()) != NULL) { | |||||
ph = phase; | |||||
inc = phase_increment; | |||||
for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | |||||
index = ph >> 24; | |||||
val1 = wavetable[index]; | |||||
val2 = wavetable[index+1]; | |||||
scale = (ph >> 8) & 0xFFFF; | |||||
val2 *= scale; | |||||
val1 *= 0xFFFF - scale; | |||||
val3 = (val1 + val2) >> 16; | |||||
// The value of ramp_up is always initialized to RAMP_LENGTH and then is | |||||
// decremented each time through here until it reaches zero. | |||||
// The value of ramp_up is used to generate a Q15 fraction which varies | |||||
// from [0 - 1), and multiplies this by the current sample | |||||
if(ramp_up) { | |||||
// ramp up to the new magnitude | |||||
// ramp_mag is the Q15 representation of the fraction | |||||
// Since ramp_up can't be zero, this cannot generate +1 | |||||
ramp_mag = ((ramp_length-ramp_up)<<15)/ramp_length; | |||||
ramp_up--; | |||||
block->data[i] = (val3 * ((ramp_mag * magnitude)>>15)) >> 15; | |||||
} else if(ramp_down) { | |||||
// ramp down to zero from the last magnitude | |||||
// The value of ramp_down is always initialized to RAMP_LENGTH and then is | |||||
// decremented each time through here until it reaches zero. | |||||
// The value of ramp_down is used to generate a Q15 fraction which varies | |||||
// from (1 - 0], and multiplies this by the current sample | |||||
// avoid RAMP_LENGTH/RAMP_LENGTH because Q15 format | |||||
// cannot represent +1 | |||||
ramp_mag = ((ramp_down - 1)<<15)/ramp_length; | |||||
ramp_down--; | |||||
block->data[i] = (val3 * ((ramp_mag * last_magnitude)>>15)) >> 15; | |||||
} else { | |||||
block->data[i] = (val3 * magnitude) >> 15; | |||||
} | |||||
//Serial.print(block->data[i]); | |||||
//Serial.print(", "); | |||||
//if ((i % 12) == 11) Serial.println(); | |||||
ph += inc; | |||||
} | |||||
//Serial.println(); | |||||
phase = ph; | |||||
transmit(block); | |||||
release(block); | |||||
} else { | |||||
// is this numerical overflow ok? | |||||
phase += phase_increment * AUDIO_BLOCK_SAMPLES; | |||||
} | |||||
} | |||||
#else | |||||
/******************************************************************/ | |||||
// PAH 140415 - change sin to use Paul's interpolation which is much | |||||
// faster than arm's sin function | |||||
// PAH 140316 - fix calculation of sample (amplitude error) | |||||
// PAH 140314 - change t_hi from int to float | |||||
// PAH - add ramp-up and ramp-down to the onset of the wave | |||||
// the length is specified in samples | |||||
void AudioSynthWaveform::set_ramp_length(int16_t r_length) | void AudioSynthWaveform::set_ramp_length(int16_t r_length) | ||||
{ | { | ||||
if(r_length < 0) { | if(r_length < 0) { | ||||
} | } | ||||
boolean AudioSynthWaveform::begin(float t_amp,int t_hi,short type) | |||||
boolean AudioSynthWaveform::begin(float t_amp,float t_hi,short type) | |||||
{ | { | ||||
tone_type = type; | tone_type = type; | ||||
// tone_amp = t_amp; | |||||
amplitude(t_amp); | amplitude(t_amp); | ||||
tone_freq = t_hi; | |||||
if(t_hi < 1)return false; | |||||
tone_freq = t_hi > 0.0; | |||||
if(t_hi <= 0.0)return false; | |||||
if(t_hi >= AUDIO_SAMPLE_RATE_EXACT/2)return false; | if(t_hi >= AUDIO_SAMPLE_RATE_EXACT/2)return false; | ||||
tone_phase = 0; | tone_phase = 0; | ||||
// tone_incr = (0x100000000LL*t_hi)/AUDIO_SAMPLE_RATE_EXACT; | |||||
tone_incr = (0x80000000LL*t_hi)/AUDIO_SAMPLE_RATE_EXACT; | |||||
frequency(t_hi); | |||||
if(0) { | if(0) { | ||||
Serial.print("AudioSynthWaveform.begin(tone_amp = "); | Serial.print("AudioSynthWaveform.begin(tone_amp = "); | ||||
Serial.print(t_amp); | Serial.print(t_amp); | ||||
return(true); | return(true); | ||||
} | } | ||||
// PAH - 140313 fixed the calculation of the tone so that its spectrum | |||||
// is much improved | |||||
// PAH - 140313 fixed a problem with ramping | // PAH - 140313 fixed a problem with ramping | ||||
void AudioSynthWaveform::update(void) | void AudioSynthWaveform::update(void) | ||||
{ | { | ||||
short *bp; | short *bp; | ||||
// temporary for ramp in sine | // temporary for ramp in sine | ||||
uint32_t ramp_mag; | uint32_t ramp_mag; | ||||
int32_t val1, val2, val3; | |||||
uint32_t index, scale; | |||||
// temporaries for TRIANGLE | // temporaries for TRIANGLE | ||||
uint32_t mag; | uint32_t mag; | ||||
short tmp_amp; | short tmp_amp; | ||||
if(tone_freq == 0)return; | if(tone_freq == 0)return; | ||||
// L E F T C H A N N E L O N L Y | // L E F T C H A N N E L O N L Y | ||||
switch(tone_type) { | switch(tone_type) { | ||||
case TONE_TYPE_SINE: | case TONE_TYPE_SINE: | ||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | ||||
// Calculate interpolated sin | |||||
index = tone_phase >> 23; | |||||
val1 = AudioWaveformSine[index]; | |||||
val2 = AudioWaveformSine[index+1]; | |||||
scale = (tone_phase >> 7) & 0xFFFF; | |||||
val2 *= scale; | |||||
val1 *= 0xFFFF - scale; | |||||
val3 = (val1 + val2) >> 16; | |||||
// The value of ramp_up is always initialized to RAMP_LENGTH and then is | // The value of ramp_up is always initialized to RAMP_LENGTH and then is | ||||
// decremented each time through here until it reaches zero. | // decremented each time through here until it reaches zero. | ||||
// The value of ramp_up is used to generate a Q15 fraction which varies | // The value of ramp_up is used to generate a Q15 fraction which varies | ||||
ramp_up--; | ramp_up--; | ||||
// adjust tone_phase to Q15 format and then adjust the result | // adjust tone_phase to Q15 format and then adjust the result | ||||
// of the multiplication | // of the multiplication | ||||
// calculate the sample | |||||
tmp_amp = (short)((arm_sin_q15(tone_phase>>16) * tone_amp) >> 17); | |||||
// calculate the sample | |||||
tmp_amp = (short)((val3 * tone_amp) >> 15); | |||||
*bp++ = (tmp_amp * ramp_mag)>>15; | *bp++ = (tmp_amp * ramp_mag)>>15; | ||||
} | } | ||||
else if(ramp_down) { | else if(ramp_down) { | ||||
// cannot represent +1 | // cannot represent +1 | ||||
ramp_mag = ((ramp_down - 1)<<15)/ramp_length; | ramp_mag = ((ramp_down - 1)<<15)/ramp_length; | ||||
ramp_down--; | ramp_down--; | ||||
// adjust tone_phase to Q15 format and then adjust the result | |||||
// of the multiplication | |||||
tmp_amp = (short)((arm_sin_q15(tone_phase>>16) * last_tone_amp) >> 17); | |||||
tmp_amp = (short)((val3 * last_tone_amp) >> 15); | |||||
*bp++ = (tmp_amp * ramp_mag)>>15; | *bp++ = (tmp_amp * ramp_mag)>>15; | ||||
} else { | } else { | ||||
// adjust tone_phase to Q15 format and then adjust the result | |||||
// of the multiplication | |||||
tmp_amp = (short)((arm_sin_q15(tone_phase>>16) * tone_amp) >> 17); | |||||
*bp++ = tmp_amp; | |||||
*bp++ = (short)((val3 * tone_amp) >> 15); | |||||
} | } | ||||
// phase and incr are both unsigned 32-bit fractions | // phase and incr are both unsigned 32-bit fractions | ||||
case TONE_TYPE_SQUARE: | case TONE_TYPE_SQUARE: | ||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | ||||
if(tone_phase & 0x80000000)*bp++ = -tone_amp; | |||||
if(tone_phase & 0x40000000)*bp++ = -tone_amp; | |||||
else *bp++ = tone_amp; | else *bp++ = tone_amp; | ||||
// phase and incr are both unsigned 32-bit fractions | // phase and incr are both unsigned 32-bit fractions | ||||
tone_phase += tone_incr; | tone_phase += tone_incr; | ||||
case TONE_TYPE_SAWTOOTH: | case TONE_TYPE_SAWTOOTH: | ||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | ||||
*bp = ((short)(tone_phase>>16)*tone_amp) >> 15; | |||||
bp++; | |||||
*bp++ = ((short)(tone_phase>>15)*tone_amp) >> 15; | |||||
// phase and incr are both unsigned 32-bit fractions | // phase and incr are both unsigned 32-bit fractions | ||||
tone_phase += tone_incr; | |||||
tone_phase += tone_incr; | |||||
} | } | ||||
break; | break; | ||||
mag = ~mag + 1; | mag = ~mag + 1; | ||||
} | } | ||||
*bp++ = ((short)(mag>>17)*tmp_amp) >> 15; | *bp++ = ((short)(mag>>17)*tmp_amp) >> 15; | ||||
tone_phase += tone_incr; | |||||
tone_phase += 2*tone_incr; | |||||
} | } | ||||
break; | break; | ||||
} | } | ||||
release(block); | release(block); | ||||
} | } | ||||
} | } | ||||
#endif | |||||
#if 0 | |||||
void AudioSineWaveMod::frequency(float f) | |||||
{ | |||||
if (f > AUDIO_SAMPLE_RATE_EXACT / 2 || f < 0.0) return; | |||||
phase_increment = (f / AUDIO_SAMPLE_RATE_EXACT) * 4294967296.0f; | |||||
} | |||||
void AudioSineWaveMod::update(void) | |||||
{ | |||||
audio_block_t *block, *modinput; | |||||
uint32_t i, ph, inc, index, scale; | |||||
int32_t val1, val2; | |||||
//Serial.println("AudioSineWave::update"); | |||||
modinput = receiveReadOnly(); | |||||
ph = phase; | |||||
inc = phase_increment; | |||||
block = allocate(); | |||||
if (!block) { | |||||
// unable to allocate memory, so we'll send nothing | |||||
if (modinput) { | |||||
// but if we got modulation data, update the phase | |||||
for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | |||||
ph += inc + modinput->data[i] * modulation_factor; | |||||
} | |||||
release(modinput); | |||||
} else { | |||||
ph += phase_increment * AUDIO_BLOCK_SAMPLES; | |||||
} | |||||
phase = ph; | |||||
return; | |||||
} | |||||
if (modinput) { | |||||
for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | |||||
index = ph >> 24; | |||||
val1 = sine_table[index]; | |||||
val2 = sine_table[index+1]; | |||||
scale = (ph >> 8) & 0xFFFF; | |||||
val2 *= scale; | |||||
val1 *= 0xFFFF - scale; | |||||
block->data[i] = (val1 + val2) >> 16; | |||||
//Serial.print(block->data[i]); | |||||
//Serial.print(", "); | |||||
//if ((i % 12) == 11) Serial.println(); | |||||
ph += inc + modinput->data[i] * modulation_factor; | |||||
} | |||||
release(modinput); | |||||
} else { | |||||
ph = phase; | |||||
inc = phase_increment; | |||||
for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | |||||
index = ph >> 24; | |||||
val1 = sine_table[index]; | |||||
val2 = sine_table[index+1]; | |||||
scale = (ph >> 8) & 0xFFFF; | |||||
val2 *= scale; | |||||
val1 *= 0xFFFF - scale; | |||||
block->data[i] = (val1 + val2) >> 16; | |||||
//Serial.print(block->data[i]); | |||||
//Serial.print(", "); | |||||
//if ((i % 12) == 11) Serial.println(); | |||||
ph += inc; | |||||
} | |||||
} | |||||
phase = ph; | |||||
transmit(block); | |||||
release(block); | |||||
} | |||||
#endif | |||||
#include "AudioStream.h" | #include "AudioStream.h" | ||||
#include "arm_math.h" | #include "arm_math.h" | ||||
//#define ORIGINAL_AUDIOSYNTHWAVEFORM | |||||
#ifdef ORIGINAL_AUDIOSYNTHWAVEFORM | |||||
// waveforms.c | |||||
extern "C" { | |||||
extern const int16_t AudioWaveformSine[257]; | |||||
extern const int16_t AudioWaveformTriangle[257]; | |||||
extern const int16_t AudioWaveformSquare[257]; | |||||
extern const int16_t AudioWaveformSawtooth[257]; | |||||
} | |||||
class AudioSynthWaveform : public AudioStream | |||||
{ | |||||
public: | |||||
AudioSynthWaveform(const int16_t *waveform) | |||||
: AudioStream(0, NULL), wavetable(waveform), magnitude(0), phase(0) | |||||
, ramp_down(0), ramp_up(0), ramp_mag(0), ramp_length(0) | |||||
{ } | |||||
void frequency(float freq) { | |||||
if (freq > AUDIO_SAMPLE_RATE_EXACT / 2 || freq < 0.0) return; | |||||
phase_increment = (freq / AUDIO_SAMPLE_RATE_EXACT) * 4294967296.0f; | |||||
} | |||||
void amplitude(float n) { // 0 to 1.0 | |||||
if (n < 0) n = 0; | |||||
else if (n > 1.0) n = 1.0; | |||||
// Ramp code | |||||
if(magnitude && (n == 0)) { | |||||
ramp_down = ramp_length; | |||||
ramp_up = 0; | |||||
last_magnitude = magnitude; | |||||
} | |||||
else if((magnitude == 0) && n) { | |||||
ramp_up = ramp_length; | |||||
ramp_down = 0; | |||||
} | |||||
// set new magnitude | |||||
magnitude = n * 32767.0; | |||||
} | |||||
virtual void update(void); | |||||
void set_ramp_length(uint16_t r_length); | |||||
private: | |||||
const int16_t *wavetable; | |||||
uint16_t magnitude; | |||||
uint16_t last_magnitude; | |||||
uint32_t phase; | |||||
uint32_t phase_increment; | |||||
uint32_t ramp_down; | |||||
uint32_t ramp_up; | |||||
uint32_t ramp_mag; | |||||
uint16_t ramp_length; | |||||
}; | |||||
#else | |||||
// waveforms.c | |||||
extern "C" { | |||||
extern const int16_t AudioWaveformSine[257]; | |||||
} | |||||
#define AUDIO_SAMPLE_RATE_ROUNDED (44118) | #define AUDIO_SAMPLE_RATE_ROUNDED (44118) | ||||
{ | { | ||||
} | } | ||||
void frequency(int t_hi) | |||||
void frequency(float t_hi) | |||||
{ | { | ||||
tone_incr = (0x80000000LL*t_hi)/AUDIO_SAMPLE_RATE_EXACT; | |||||
if (t_hi > AUDIO_SAMPLE_RATE_EXACT / 2 || t_hi < 0.0) return; | |||||
tone_incr = ((0x80000000LL*t_hi)/AUDIO_SAMPLE_RATE_EXACT) + 0.5; | |||||
} | } | ||||
// If ramp_length is non-zero this will set up | // If ramp_length is non-zero this will set up | ||||
tone_amp = n * 32767.0; | tone_amp = n * 32767.0; | ||||
} | } | ||||
boolean begin(float t_amp,int t_hi,short t_type); | |||||
boolean begin(float t_amp,float t_hi,short t_type); | |||||
virtual void update(void); | virtual void update(void); | ||||
void set_ramp_length(int16_t r_length); | void set_ramp_length(int16_t r_length); | ||||
uint32_t ramp_up; | uint32_t ramp_up; | ||||
uint16_t ramp_length; | uint16_t ramp_length; | ||||
}; | }; | ||||
#endif | |||||
#if 0 | |||||
class AudioSineWaveMod : public AudioStream | |||||
{ | |||||
public: | |||||
AudioSineWaveMod() : AudioStream(1, inputQueueArray) {} | |||||
void frequency(float freq); | |||||
//void amplitude(q15 n); | |||||
virtual void update(void); | |||||
private: | |||||
uint32_t phase; | |||||
uint32_t phase_increment; | |||||
uint32_t modulation_factor; | |||||
audio_block_t *inputQueueArray[1]; | |||||
}; | |||||
#endif | |||||
#endif | #endif |
synth_waveform 140404 | |||||
This synthesizes a waveform of the specified type with a given amplitude | |||||
and frequency. There are currently four types of waveform: | |||||
#define TONE_TYPE_SINE 0 | |||||
#define TONE_TYPE_SAWTOOTH 1 | |||||
#define TONE_TYPE_SQUARE 2 | |||||
#define TONE_TYPE_TRIANGLE 3 | |||||
Sine wave generation uses a lookup table and linear interpolation. | |||||
The other three waveforms are generated directly without using table lookup. | |||||
boolean begin(float t_amp,float t_freq,short t_type) | |||||
This starts generation of a waveform of given type, amplitude and frequency | |||||
Example: begin(0.8,440.0,TONE_TYPE_SINE) | |||||
void set_ramp_length(int16_t r_length) | |||||
When a tone starts, or ends, playing it can generate an audible "thump" which can | |||||
be very distracting, especially when playing musical notes. This function specifies | |||||
a "ramp" length (in number of samples) and the beginning of the generated waveform | |||||
will be ramped up in volume from zero to t_amp over the course of r_length samples. | |||||
When the tone is switched off, by changing its volume to zero, instead of ending | |||||
abruptly it will be ramped down to zero over the next r_length samples. | |||||
For example, if r_length is 44, the beginning and end of the wave will have a ramp | |||||
of approximately one millisecond. | |||||
void frequency(float t_freq) | |||||
Changes the frequency of the wave to the specified t_freq. This is done in a phase- | |||||
continuous manner which should allow generation of audio frequency shift keying and | |||||
other effects requiring a changing frequency. | |||||
If the frequency is set to zero sample generation is stopped. | |||||
void amplitude(float n) | |||||
Changes the amplitude to 'n'. If 'n' is zero the wave is turned off and any further | |||||
audio output will be zero. | |||||