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