Fixed AudioSynthWaveform. Uses LUT with linear interpolation for sinewav...dds
@@ -4,6 +4,7 @@ | |||
// 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 | |||
#ifdef ORIGINAL_TABLE | |||
const unsigned int tune_frequencies2_PGM[128] = | |||
{ | |||
16,17,18,19,21,22,23,24,26,28,29,31,33,35,37,39,41, | |||
@@ -18,7 +19,29 @@ const unsigned int tune_frequencies2_PGM[128] = | |||
13290,14080,14917,15804,16744,17740,18795,19912,21096, | |||
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_STOPNOTE 0x80 /* stop a note: low nibble is generator # */ |
@@ -1,7 +1,6 @@ | |||
// Implement the midi player inside the Audio library. | |||
// This uses the new version of the waveform generator code | |||
// See PlayMidiTones for code which uses the old version | |||
#include <Audio.h> | |||
#include <Wire.h> | |||
@@ -38,46 +37,57 @@ AudioSynthWaveform *waves[8] = { | |||
&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 mixer2; | |||
AudioOutputI2S audioOut; | |||
// Mix the first four channels into mixer1 | |||
AudioConnection c0(sine0, 0, mixer1, 0); | |||
AudioConnection c1(sine1, 0, mixer1, 1); | |||
AudioConnection c2(sine2, 0, mixer1, 2); | |||
AudioConnection c3(sine3, 0, mixer1, 3); | |||
// and the last 4 channels into mixer2 | |||
AudioConnection c4(sine4, 0, mixer2, 0); | |||
AudioConnection c5(sine5, 0, mixer2, 1); | |||
AudioConnection c6(sine6, 0, mixer2, 2); | |||
AudioConnection c7(sine7, 0, mixer2, 3); | |||
// Output mixre1 to the left channel and mixer2 to the right | |||
AudioConnection c11(mixer1, 0, audioOut, 0); | |||
AudioConnection c12(mixer2, 0, audioOut, 1); | |||
//AudioControl_WM8731 codec; | |||
AudioControlSGTL5000 codec; | |||
// Initial value of the volume control | |||
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() | |||
{ | |||
Serial.begin(115200); | |||
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. | |||
// The memory usage code indicates that 8 is the maximum | |||
// so give it 10 just to be sure. | |||
@@ -89,14 +99,11 @@ void setup() | |||
// Comment this if you don't it | |||
codec.unmuteLineout(); | |||
// Set the ramp time for each wave object | |||
for(int i = 0; i < 8;i++) { | |||
waves[i]->set_ramp_length(88); | |||
} | |||
Serial.println("Begin PlayMidiTones"); | |||
Serial.println("setup done"); | |||
// Initialize processor and memory measurements | |||
@@ -111,8 +118,8 @@ void loop() | |||
unsigned char c,opcode,chan; | |||
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: | |||
Proc = 20 (21), Mem = 2 (8) | |||
@@ -138,6 +145,7 @@ if(0) { | |||
codec.volume((float)n / 10.23); | |||
} | |||
// read the next note from the table | |||
c = *sp++; | |||
opcode = c & 0xf0; | |||
// was 0x0f but I'm only handling 8 channels | |||
@@ -163,7 +171,8 @@ if(0) { | |||
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) { | |||
waves[chan]->begin(AMPLITUDE,tune_frequencies2_PGM[*sp++], | |||
wave_type[chan]); |
@@ -1,4 +1,8 @@ | |||
/* | |||
c | |||
- released | |||
b | |||
- Use FIR filters with fast_fft option | |||
The audio board uses the following pins. | |||
6 - MEMCS | |||
@@ -25,14 +29,15 @@ The audio board uses the following pins. | |||
#include <Bounce.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 | |||
#define PASSTHRU_PIN 1 | |||
// If this pin goes low the next FIR filter in the list | |||
// is switched in. | |||
#define FILTER_PIN 0 | |||
// debounce the passthru and filter switching pins | |||
Bounce b_passthru = Bounce(PASSTHRU_PIN,15); | |||
Bounce b_filter = Bounce(FILTER_PIN,15); | |||
@@ -41,16 +46,18 @@ const int myInput = AUDIO_INPUT_LINEIN; | |||
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 | |||
// 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; | |||
@@ -62,9 +69,9 @@ struct fir_filter { | |||
// index of current filter. Start with the low pass. | |||
int fir_idx = 0; | |||
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} | |||
}; | |||
@@ -98,8 +105,8 @@ void setup() { | |||
Serial.println(") is grounded"); | |||
} | |||
// 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"); | |||
} | |||
@@ -108,7 +115,7 @@ int old_idx = -1; | |||
// audio volume | |||
int volume = 0; | |||
unsigned long last_time = millis(); | |||
void loop() | |||
{ | |||
// Volume control | |||
@@ -122,18 +129,20 @@ void loop() | |||
b_passthru.update(); | |||
b_filter.update(); | |||
// If the passthru button is pushed, save the current | |||
// filter index and then switch the filter to passthru | |||
if(b_passthru.fallingEdge()) { | |||
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(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; | |||
} | |||
@@ -141,9 +150,31 @@ void loop() | |||
if(b_filter.fallingEdge()) { | |||
fir_idx++; | |||
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(); | |||
} | |||
} | |||
} | |||
@@ -29,7 +29,6 @@ void AudioFilterFIR::begin(short *cp,int n_coeffs) | |||
// Initialize FIR instances for the left and right channels | |||
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(&r_fir_inst, n_coeffs, coeff_p, &r_StateQ15[0], AUDIO_BLOCK_SAMPLES); | |||
} | |||
} | |||
@@ -55,11 +54,6 @@ void AudioFilterFIR::update(void) | |||
transmit(block,0); | |||
release(block); | |||
} | |||
block = receiveReadOnly(1); | |||
if(block) { | |||
transmit(block,1); | |||
release(block); | |||
} | |||
return; | |||
} | |||
// Left Channel | |||
@@ -67,22 +61,15 @@ void AudioFilterFIR::update(void) | |||
// get a block for the FIR output | |||
b_new = allocate(); | |||
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 | |||
transmit(b_new,0); | |||
} | |||
if(block)release(block); | |||
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); | |||
} | |||
@@ -26,6 +26,10 @@ | |||
#include "AudioStream.h" | |||
#include "arm_math.h" | |||
#define USE_FAST_FIR true | |||
#define USE_SLOW_FIR false | |||
// Maximum number of coefficients in a FIR filter | |||
// The audio breaks up with 128 coefficients so a | |||
// maximum of 150 is more than sufficient | |||
@@ -39,12 +43,12 @@ class AudioFilterFIR : | |||
public AudioStream | |||
{ | |||
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); | |||
void stop(void); | |||
@@ -54,11 +58,11 @@ private: | |||
// the state arrays are defined to handle a maximum of MAX_COEFFS | |||
// coefficients in a filter | |||
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 r_fir_inst; | |||
// pointer to current coefficients or NULL or FIR_PASSTHRU | |||
short *coeff_p; | |||
// Whether to use the fast arm FIR code | |||
const boolean arm_fast; | |||
}; | |||
#endif |
@@ -0,0 +1,18 @@ | |||
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. | |||
@@ -29,9 +29,95 @@ | |||
#include "utility/dspinst.h" | |||
#ifdef ORIGINAL_AUDIOSYNTHWAVEFORM | |||
/******************************************************************/ | |||
// PAH - add ramp-up and ramp-down to the onset of the wave | |||
// 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) | |||
{ | |||
if(r_length < 0) { | |||
@@ -47,17 +133,15 @@ void AudioSynthWaveform::set_ramp_length(int16_t r_length) | |||
} | |||
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_amp = 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; | |||
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) { | |||
Serial.print("AudioSynthWaveform.begin(tone_amp = "); | |||
Serial.print(t_amp); | |||
@@ -72,8 +156,6 @@ boolean AudioSynthWaveform::begin(float t_amp,int t_hi,short type) | |||
return(true); | |||
} | |||
// PAH - 140313 fixed the calculation of the tone so that its spectrum | |||
// is much improved | |||
// PAH - 140313 fixed a problem with ramping | |||
void AudioSynthWaveform::update(void) | |||
{ | |||
@@ -81,10 +163,12 @@ void AudioSynthWaveform::update(void) | |||
short *bp; | |||
// temporary for ramp in sine | |||
uint32_t ramp_mag; | |||
int32_t val1, val2, val3; | |||
uint32_t index, scale; | |||
// temporaries for TRIANGLE | |||
uint32_t mag; | |||
short tmp_amp; | |||
if(tone_freq == 0)return; | |||
// L E F T C H A N N E L O N L Y | |||
@@ -94,6 +178,14 @@ void AudioSynthWaveform::update(void) | |||
switch(tone_type) { | |||
case TONE_TYPE_SINE: | |||
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 | |||
// decremented each time through here until it reaches zero. | |||
// The value of ramp_up is used to generate a Q15 fraction which varies | |||
@@ -106,8 +198,8 @@ void AudioSynthWaveform::update(void) | |||
ramp_up--; | |||
// adjust tone_phase to Q15 format and then adjust the result | |||
// 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; | |||
} | |||
else if(ramp_down) { | |||
@@ -120,15 +212,10 @@ void AudioSynthWaveform::update(void) | |||
// cannot represent +1 | |||
ramp_mag = ((ramp_down - 1)<<15)/ramp_length; | |||
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; | |||
} 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 | |||
@@ -140,7 +227,7 @@ void AudioSynthWaveform::update(void) | |||
case TONE_TYPE_SQUARE: | |||
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; | |||
// phase and incr are both unsigned 32-bit fractions | |||
tone_phase += tone_incr; | |||
@@ -149,10 +236,9 @@ void AudioSynthWaveform::update(void) | |||
case TONE_TYPE_SAWTOOTH: | |||
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 | |||
tone_phase += tone_incr; | |||
tone_phase += tone_incr; | |||
} | |||
break; | |||
@@ -173,7 +259,7 @@ void AudioSynthWaveform::update(void) | |||
mag = ~mag + 1; | |||
} | |||
*bp++ = ((short)(mag>>17)*tmp_amp) >> 15; | |||
tone_phase += tone_incr; | |||
tone_phase += 2*tone_incr; | |||
} | |||
break; | |||
} | |||
@@ -182,3 +268,88 @@ void AudioSynthWaveform::update(void) | |||
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 | |||
@@ -30,6 +30,64 @@ | |||
#include "AudioStream.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) | |||
@@ -51,9 +109,10 @@ public: | |||
{ | |||
} | |||
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 | |||
@@ -85,7 +144,7 @@ public: | |||
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); | |||
void set_ramp_length(int16_t r_length); | |||
@@ -102,4 +161,27 @@ private: | |||
uint32_t ramp_up; | |||
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 |
@@ -0,0 +1,36 @@ | |||
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. | |||