| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| static arm_cfft_radix4_instance_q15 fft_inst; | |||||
| void AudioAnalyzeFFT256::init(void) | |||||
| { | |||||
| // TODO: replace this with static const version | |||||
| arm_cfft_radix4_init_q15(&fft_inst, 256, 0, 1); | |||||
| //for (int i=0; i<2048; i++) { | |||||
| //buffer[i] = i * 3; | |||||
| //} | |||||
| //__disable_irq(); | |||||
| //ARM_DEMCR |= ARM_DEMCR_TRCENA; | |||||
| //ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; | |||||
| //uint32_t n = ARM_DWT_CYCCNT; | |||||
| //arm_cfft_radix2_q15(&fft_inst, buffer); | |||||
| //n = ARM_DWT_CYCCNT - n; | |||||
| //__enable_irq(); | |||||
| //cycles = n; | |||||
| //arm_cmplx_mag_q15(buffer, buffer, 512); | |||||
| // each audio block is 278525 cycles @ 96 MHz | |||||
| // 256 point fft2 takes 65408 cycles | |||||
| // 256 point fft4 takes 49108 cycles | |||||
| // 128 point cmag takes 10999 cycles | |||||
| // 1024 point fft2 takes 125948 cycles | |||||
| // 1024 point fft4 takes 125840 cycles | |||||
| // 512 point cmag takes 43764 cycles | |||||
| //state = 0; | |||||
| //outputflag = false; | |||||
| } | |||||
| static void copy_to_fft_buffer(void *destination, const void *source) | |||||
| { | |||||
| const int16_t *src = (const int16_t *)source; | |||||
| int16_t *dst = (int16_t *)destination; | |||||
| // TODO: optimize this | |||||
| for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | |||||
| *dst++ = *src++; // real | |||||
| *dst++ = 0; // imaginary | |||||
| } | |||||
| } | |||||
| static void apply_window_to_fft_buffer(void *buffer, const void *window) | |||||
| { | |||||
| int16_t *buf = (int16_t *)buffer; | |||||
| const int16_t *win = (int16_t *)window;; | |||||
| for (int i=0; i < 256; i++) { | |||||
| int32_t val = *buf * *win++; | |||||
| //*buf = signed_saturate_rshift(val, 16, 15); | |||||
| *buf = val >> 15; | |||||
| buf += 2; | |||||
| } | |||||
| } | |||||
| void AudioAnalyzeFFT256::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| block = receiveReadOnly(); | |||||
| if (!block) return; | |||||
| if (!prevblock) { | |||||
| prevblock = block; | |||||
| return; | |||||
| } | |||||
| copy_to_fft_buffer(buffer, prevblock->data); | |||||
| copy_to_fft_buffer(buffer+256, block->data); | |||||
| //window = AudioWindowBlackmanNuttall256; | |||||
| //window = NULL; | |||||
| if (window) apply_window_to_fft_buffer(buffer, window); | |||||
| arm_cfft_radix4_q15(&fft_inst, buffer); | |||||
| // TODO: is this averaging correct? G. Heinzel's paper says we're | |||||
| // supposed to average the magnitude squared, then do the square | |||||
| // root at the end after dividing by naverage. | |||||
| arm_cmplx_mag_q15(buffer, buffer, 128); | |||||
| if (count == 0) { | |||||
| for (int i=0; i < 128; i++) { | |||||
| output[i] = buffer[i]; | |||||
| } | |||||
| } else { | |||||
| for (int i=0; i < 128; i++) { | |||||
| output[i] += buffer[i]; | |||||
| } | |||||
| } | |||||
| if (++count == naverage) { | |||||
| count = 0; | |||||
| for (int i=0; i < 128; i++) { | |||||
| output[i] /= naverage; | |||||
| } | |||||
| outputflag = true; | |||||
| } | |||||
| release(prevblock); | |||||
| prevblock = block; | |||||
| #if 0 | |||||
| block = receiveReadOnly(); | |||||
| if (state == 0) { | |||||
| //Serial.print("0"); | |||||
| if (block == NULL) return; | |||||
| copy_to_fft_buffer(buffer, block->data); | |||||
| state = 1; | |||||
| } else if (state == 1) { | |||||
| //Serial.print("1"); | |||||
| if (block == NULL) return; | |||||
| copy_to_fft_buffer(buffer+256, block->data); | |||||
| arm_cfft_radix4_q15(&fft_inst, buffer); | |||||
| state = 2; | |||||
| } else { | |||||
| //Serial.print("2"); | |||||
| arm_cmplx_mag_q15(buffer, output, 128); | |||||
| outputflag = true; | |||||
| if (block == NULL) return; | |||||
| copy_to_fft_buffer(buffer, block->data); | |||||
| state = 1; | |||||
| } | |||||
| release(block); | |||||
| #endif | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| static inline int32_t multiply_32x32_rshift30(int32_t a, int32_t b) __attribute__((always_inline)); | |||||
| static inline int32_t multiply_32x32_rshift30(int32_t a, int32_t b) | |||||
| { | |||||
| return ((int64_t)a * (int64_t)b) >> 30; | |||||
| } | |||||
| //#define TONE_DETECT_FAST | |||||
| void AudioAnalyzeToneDetect::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| int32_t q0, q1, q2, coef; | |||||
| const int16_t *p, *end; | |||||
| uint16_t n; | |||||
| block = receiveReadOnly(); | |||||
| if (!block) return; | |||||
| if (!enabled) { | |||||
| release(block); | |||||
| return; | |||||
| } | |||||
| p = block->data; | |||||
| end = p + AUDIO_BLOCK_SAMPLES; | |||||
| n = count; | |||||
| coef = coefficient; | |||||
| q1 = s1; | |||||
| q2 = s2; | |||||
| do { | |||||
| // the Goertzel algorithm is kinda magical ;-) | |||||
| #ifdef TONE_DETECT_FAST | |||||
| q0 = (*p++) + (multiply_32x32_rshift32_rounded(coef, q1) << 2) - q2; | |||||
| #else | |||||
| q0 = (*p++) + multiply_32x32_rshift30(coef, q1) - q2; | |||||
| // TODO: is this only 1 cycle slower? if so, always use it | |||||
| #endif | |||||
| q2 = q1; | |||||
| q1 = q0; | |||||
| if (--n == 0) { | |||||
| out1 = q1; | |||||
| out2 = q2; | |||||
| q1 = 0; // TODO: does clearing these help or hinder? | |||||
| q2 = 0; | |||||
| new_output = true; | |||||
| n = length; | |||||
| } | |||||
| } while (p < end); | |||||
| count = n; | |||||
| s1 = q1; | |||||
| s2 = q2; | |||||
| release(block); | |||||
| } | |||||
| void AudioAnalyzeToneDetect::set_params(int32_t coef, uint16_t cycles, uint16_t len) | |||||
| { | |||||
| __disable_irq(); | |||||
| coefficient = coef; | |||||
| ncycles = cycles; | |||||
| length = len; | |||||
| count = len; | |||||
| s1 = 0; | |||||
| s2 = 0; | |||||
| enabled = true; | |||||
| __enable_irq(); | |||||
| //Serial.printf("Tone: coef=%d, ncycles=%d, length=%d\n", coefficient, ncycles, length); | |||||
| } | |||||
| float AudioAnalyzeToneDetect::read(void) | |||||
| { | |||||
| int32_t coef, q1, q2, power; | |||||
| uint16_t len; | |||||
| __disable_irq(); | |||||
| coef = coefficient; | |||||
| q1 = out1; | |||||
| q2 = out2; | |||||
| len = length; | |||||
| __enable_irq(); | |||||
| #ifdef TONE_DETECT_FAST | |||||
| power = multiply_32x32_rshift32_rounded(q2, q2); | |||||
| power = multiply_accumulate_32x32_rshift32_rounded(power, q1, q1); | |||||
| power = multiply_subtract_32x32_rshift32_rounded(power, | |||||
| multiply_32x32_rshift30(q1, q2), coef); | |||||
| power <<= 4; | |||||
| #else | |||||
| int64_t power64; | |||||
| power64 = (int64_t)q2 * (int64_t)q2; | |||||
| power64 += (int64_t)q1 * (int64_t)q1; | |||||
| power64 -= (((int64_t)q1 * (int64_t)q2) >> 30) * (int64_t)coef; | |||||
| power = power64 >> 28; | |||||
| #endif | |||||
| return sqrtf((float)power) / (float)len; | |||||
| } | |||||
| AudioAnalyzeToneDetect::operator bool() | |||||
| { | |||||
| int32_t coef, q1, q2, power, trigger; | |||||
| uint16_t len; | |||||
| __disable_irq(); | |||||
| coef = coefficient; | |||||
| q1 = out1; | |||||
| q2 = out2; | |||||
| len = length; | |||||
| __enable_irq(); | |||||
| #ifdef TONE_DETECT_FAST | |||||
| power = multiply_32x32_rshift32_rounded(q2, q2); | |||||
| power = multiply_accumulate_32x32_rshift32_rounded(power, q1, q1); | |||||
| power = multiply_subtract_32x32_rshift32_rounded(power, | |||||
| multiply_32x32_rshift30(q1, q2), coef); | |||||
| power <<= 4; | |||||
| #else | |||||
| int64_t power64; | |||||
| power64 = (int64_t)q2 * (int64_t)q2; | |||||
| power64 += (int64_t)q1 * (int64_t)q1; | |||||
| power64 -= (((int64_t)q1 * (int64_t)q2) >> 30) * (int64_t)coef; | |||||
| power = power64 >> 28; | |||||
| #endif | |||||
| trigger = (uint32_t)len * thresh; | |||||
| trigger = multiply_32x32_rshift32(trigger, trigger); | |||||
| Serial.printf("bool: power=%d, trig=%d\n", power, trigger); | |||||
| return (power >= trigger); | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| /******************************************************************/ | |||||
| // A u d i o E f f e c t C h o r u s | |||||
| // Written by Pete (El Supremo) Jan 2014 | |||||
| // circular addressing indices for left and right channels | |||||
| short AudioEffectChorus::l_circ_idx; | |||||
| short AudioEffectChorus::r_circ_idx; | |||||
| short * AudioEffectChorus::l_delayline = NULL; | |||||
| short * AudioEffectChorus::r_delayline = NULL; | |||||
| int AudioEffectChorus::delay_length; | |||||
| // An initial value of zero indicates passthru | |||||
| int AudioEffectChorus::num_chorus = 0; | |||||
| // All three must be valid. | |||||
| boolean AudioEffectChorus::begin(short *delayline,int d_length,int n_chorus) | |||||
| { | |||||
| Serial.print("AudioEffectChorus.begin(Chorus delay line length = "); | |||||
| Serial.print(d_length); | |||||
| Serial.print(", n_chorus = "); | |||||
| Serial.print(n_chorus); | |||||
| Serial.println(")"); | |||||
| l_delayline = NULL; | |||||
| r_delayline = NULL; | |||||
| delay_length = 0; | |||||
| l_circ_idx = 0; | |||||
| r_circ_idx = 0; | |||||
| if(delayline == NULL) { | |||||
| return(false); | |||||
| } | |||||
| if(d_length < 10) { | |||||
| return(false); | |||||
| } | |||||
| if(n_chorus < 1) { | |||||
| return(false); | |||||
| } | |||||
| l_delayline = delayline; | |||||
| r_delayline = delayline + d_length/2; | |||||
| delay_length = d_length/2; | |||||
| num_chorus = n_chorus; | |||||
| return(true); | |||||
| } | |||||
| // This has the same effect as begin(NULL,0); | |||||
| void AudioEffectChorus::stop(void) | |||||
| { | |||||
| } | |||||
| void AudioEffectChorus::modify(int n_chorus) | |||||
| { | |||||
| num_chorus = n_chorus; | |||||
| } | |||||
| int iabs(int x) | |||||
| { | |||||
| if(x < 0)return(-x); | |||||
| return(x); | |||||
| } | |||||
| //static int d_count = 0; | |||||
| int last_idx = 0; | |||||
| void AudioEffectChorus::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| short *bp; | |||||
| int sum; | |||||
| int c_idx; | |||||
| if(l_delayline == NULL)return; | |||||
| if(r_delayline == NULL)return; | |||||
| // do passthru | |||||
| // It stores the unmodified data in the delay line so that | |||||
| // it isn't as likely to click | |||||
| if(num_chorus < 1) { | |||||
| // Just passthrough | |||||
| block = receiveWritable(0); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| l_circ_idx++; | |||||
| if(l_circ_idx >= delay_length) { | |||||
| l_circ_idx = 0; | |||||
| } | |||||
| l_delayline[l_circ_idx] = *bp++; | |||||
| } | |||||
| transmit(block,0); | |||||
| release(block); | |||||
| } | |||||
| block = receiveWritable(1); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| r_circ_idx++; | |||||
| if(r_circ_idx >= delay_length) { | |||||
| r_circ_idx = 0; | |||||
| } | |||||
| r_delayline[r_circ_idx] = *bp++; | |||||
| } | |||||
| transmit(block,1); | |||||
| release(block); | |||||
| } | |||||
| return; | |||||
| } | |||||
| // L E F T C H A N N E L | |||||
| block = receiveWritable(0); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| l_circ_idx++; | |||||
| if(l_circ_idx >= delay_length) { | |||||
| l_circ_idx = 0; | |||||
| } | |||||
| l_delayline[l_circ_idx] = *bp; | |||||
| sum = 0; | |||||
| c_idx = l_circ_idx; | |||||
| for(int k = 0; k < num_chorus; k++) { | |||||
| sum += l_delayline[c_idx]; | |||||
| if(num_chorus > 1)c_idx -= delay_length/(num_chorus - 1) - 1; | |||||
| if(c_idx < 0) { | |||||
| c_idx += delay_length; | |||||
| } | |||||
| } | |||||
| *bp++ = sum/num_chorus; | |||||
| } | |||||
| // send the effect output to the left channel | |||||
| transmit(block,0); | |||||
| release(block); | |||||
| } | |||||
| // R I G H T C H A N N E L | |||||
| block = receiveWritable(1); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| r_circ_idx++; | |||||
| if(r_circ_idx >= delay_length) { | |||||
| r_circ_idx = 0; | |||||
| } | |||||
| r_delayline[r_circ_idx] = *bp; | |||||
| sum = 0; | |||||
| c_idx = r_circ_idx; | |||||
| for(int k = 0; k < num_chorus; k++) { | |||||
| sum += r_delayline[c_idx]; | |||||
| if(num_chorus > 1)c_idx -= delay_length/(num_chorus - 1) - 1; | |||||
| if(c_idx < 0) { | |||||
| c_idx += delay_length; | |||||
| } | |||||
| } | |||||
| *bp++ = sum/num_chorus; | |||||
| } | |||||
| // send the effect output to the left channel | |||||
| transmit(block,1); | |||||
| release(block); | |||||
| } | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| extern "C" { | |||||
| extern const int16_t fader_table[256]; | |||||
| }; | |||||
| void AudioEffectFade::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| uint32_t i, pos, inc, index, scale; | |||||
| int32_t val1, val2, val, sample; | |||||
| uint8_t dir; | |||||
| pos = position; | |||||
| if (pos == 0) { | |||||
| // output is silent | |||||
| block = receiveReadOnly(); | |||||
| if (block) release(block); | |||||
| return; | |||||
| } else if (pos == 0xFFFFFFFF) { | |||||
| // output is 100% | |||||
| block = receiveReadOnly(); | |||||
| if (!block) return; | |||||
| transmit(block); | |||||
| release(block); | |||||
| return; | |||||
| } | |||||
| block = receiveWritable(); | |||||
| if (!block) return; | |||||
| inc = rate; | |||||
| dir = direction; | |||||
| for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { | |||||
| index = pos >> 24; | |||||
| val1 = fader_table[index]; | |||||
| val2 = fader_table[index+1]; | |||||
| scale = (pos >> 8) & 0xFFFF; | |||||
| val2 *= scale; | |||||
| val1 *= 0x10000 - scale; | |||||
| val = (val1 + val2) >> 16; | |||||
| sample = block->data[i]; | |||||
| sample = (sample * val) >> 15; | |||||
| block->data[i] = sample; | |||||
| if (dir > 0) { | |||||
| // output is increasing | |||||
| if (inc < 0xFFFFFFFF - pos) pos += inc; | |||||
| else pos = 0xFFFFFFFF; | |||||
| } else { | |||||
| // output is decreasing | |||||
| if (inc < pos) pos -= inc; | |||||
| else pos = 0; | |||||
| } | |||||
| } | |||||
| position = pos; | |||||
| transmit(block); | |||||
| release(block); | |||||
| } | |||||
| void AudioEffectFade::fadeBegin(uint32_t newrate, uint8_t dir) | |||||
| { | |||||
| __disable_irq(); | |||||
| uint32_t pos = position; | |||||
| if (pos == 0) position = 1; | |||||
| else if (pos == 0xFFFFFFFF) position = 0xFFFFFFFE; | |||||
| rate = newrate; | |||||
| direction = dir; | |||||
| __enable_irq(); | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| /******************************************************************/ | |||||
| // A u d i o E f f e c t F l a n g e | |||||
| // Written by Pete (El Supremo) Jan 2014 | |||||
| // 140207 - fix calculation of delay_rate_incr which is expressed as | |||||
| // a fraction of 2*PI | |||||
| // 140207 - cosmetic fix to begin() | |||||
| // circular addressing indices for left and right channels | |||||
| short AudioEffectFlange::l_circ_idx; | |||||
| short AudioEffectFlange::r_circ_idx; | |||||
| short * AudioEffectFlange::l_delayline = NULL; | |||||
| short * AudioEffectFlange::r_delayline = NULL; | |||||
| // User-supplied offset for the delayed sample | |||||
| // but start with passthru | |||||
| int AudioEffectFlange::delay_offset_idx = DELAY_PASSTHRU; | |||||
| int AudioEffectFlange::delay_length; | |||||
| int AudioEffectFlange::delay_depth; | |||||
| int AudioEffectFlange::delay_rate_incr; | |||||
| unsigned int AudioEffectFlange::l_delay_rate_index; | |||||
| unsigned int AudioEffectFlange::r_delay_rate_index; | |||||
| // fails if the user provides unreasonable values but will | |||||
| // coerce them and go ahead anyway. e.g. if the delay offset | |||||
| // is >= CHORUS_DELAY_LENGTH, the code will force it to | |||||
| // CHORUS_DELAY_LENGTH-1 and return false. | |||||
| // delay_rate is the rate (in Hz) of the sine wave modulation | |||||
| // delay_depth is the maximum variation around delay_offset | |||||
| // i.e. the total offset is delay_offset + delay_depth * sin(delay_rate) | |||||
| boolean AudioEffectFlange::begin(short *delayline,int d_length,int delay_offset,int d_depth,float delay_rate) | |||||
| { | |||||
| boolean all_ok = true; | |||||
| if(0) { | |||||
| Serial.print("AudioEffectFlange.begin(offset = "); | |||||
| Serial.print(delay_offset); | |||||
| Serial.print(", depth = "); | |||||
| Serial.print(d_depth); | |||||
| Serial.print(", rate = "); | |||||
| Serial.print(delay_rate,3); | |||||
| Serial.println(")"); | |||||
| Serial.print(" FLANGE_DELAY_LENGTH = "); | |||||
| Serial.println(d_length); | |||||
| } | |||||
| delay_length = d_length/2; | |||||
| l_delayline = delayline; | |||||
| r_delayline = delayline + delay_length; | |||||
| delay_depth = d_depth; | |||||
| // initial index | |||||
| l_delay_rate_index = 0; | |||||
| r_delay_rate_index = 0; | |||||
| l_circ_idx = 0; | |||||
| r_circ_idx = 0; | |||||
| delay_rate_incr = delay_rate/44100.*2147483648.; | |||||
| //Serial.println(delay_rate_incr,HEX); | |||||
| delay_offset_idx = delay_offset; | |||||
| // Allow the passthru code to go through | |||||
| if(delay_offset_idx < -1) { | |||||
| delay_offset_idx = 0; | |||||
| all_ok = false; | |||||
| } | |||||
| if(delay_offset_idx >= delay_length) { | |||||
| delay_offset_idx = delay_length - 1; | |||||
| all_ok = false; | |||||
| } | |||||
| return(all_ok); | |||||
| } | |||||
| boolean AudioEffectFlange::modify(int delay_offset,int d_depth,float delay_rate) | |||||
| { | |||||
| boolean all_ok = true; | |||||
| delay_depth = d_depth; | |||||
| delay_rate_incr = delay_rate/44100.*2147483648.; | |||||
| delay_offset_idx = delay_offset; | |||||
| // Allow the passthru code to go through | |||||
| if(delay_offset_idx < -1) { | |||||
| delay_offset_idx = 0; | |||||
| all_ok = false; | |||||
| } | |||||
| if(delay_offset_idx >= delay_length) { | |||||
| delay_offset_idx = delay_length - 1; | |||||
| all_ok = false; | |||||
| } | |||||
| l_delay_rate_index = 0; | |||||
| r_delay_rate_index = 0; | |||||
| l_circ_idx = 0; | |||||
| r_circ_idx = 0; | |||||
| return(all_ok); | |||||
| } | |||||
| void AudioEffectFlange::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| int idx; | |||||
| short *bp; | |||||
| short frac; | |||||
| int idx1; | |||||
| if(l_delayline == NULL)return; | |||||
| if(r_delayline == NULL)return; | |||||
| // do passthru | |||||
| if(delay_offset_idx == DELAY_PASSTHRU) { | |||||
| // Just passthrough | |||||
| block = receiveWritable(0); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| l_circ_idx++; | |||||
| if(l_circ_idx >= delay_length) { | |||||
| l_circ_idx = 0; | |||||
| } | |||||
| l_delayline[l_circ_idx] = *bp++; | |||||
| } | |||||
| transmit(block,0); | |||||
| release(block); | |||||
| } | |||||
| block = receiveWritable(1); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| r_circ_idx++; | |||||
| if(r_circ_idx >= delay_length) { | |||||
| r_circ_idx = 0; | |||||
| } | |||||
| r_delayline[r_circ_idx] = *bp++; | |||||
| } | |||||
| transmit(block,1); | |||||
| release(block); | |||||
| } | |||||
| return; | |||||
| } | |||||
| // L E F T C H A N N E L | |||||
| block = receiveWritable(0); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| l_circ_idx++; | |||||
| if(l_circ_idx >= delay_length) { | |||||
| l_circ_idx = 0; | |||||
| } | |||||
| l_delayline[l_circ_idx] = *bp; | |||||
| idx = arm_sin_q15( (q15_t)((l_delay_rate_index >> 16) & 0x7fff)); | |||||
| idx = (idx * delay_depth) >> 15; | |||||
| //Serial.println(idx); | |||||
| idx = l_circ_idx - (delay_offset_idx + idx); | |||||
| if(idx < 0) { | |||||
| idx += delay_length; | |||||
| } | |||||
| if(idx >= delay_length) { | |||||
| idx -= delay_length; | |||||
| } | |||||
| if(frac < 0) | |||||
| idx1 = idx - 1; | |||||
| else | |||||
| idx1 = idx + 1; | |||||
| if(idx1 < 0) { | |||||
| idx1 += delay_length; | |||||
| } | |||||
| if(idx1 >= delay_length) { | |||||
| idx1 -= delay_length; | |||||
| } | |||||
| frac = (l_delay_rate_index >> 1) &0x7fff; | |||||
| frac = (( (int)(l_delayline[idx1] - l_delayline[idx])*frac) >> 15); | |||||
| *bp++ = (l_delayline[l_circ_idx] | |||||
| + l_delayline[idx] + frac | |||||
| )/2; | |||||
| l_delay_rate_index += delay_rate_incr; | |||||
| if(l_delay_rate_index & 0x80000000) { | |||||
| l_delay_rate_index &= 0x7fffffff; | |||||
| } | |||||
| } | |||||
| // send the effect output to the left channel | |||||
| transmit(block,0); | |||||
| release(block); | |||||
| } | |||||
| // R I G H T C H A N N E L | |||||
| block = receiveWritable(1); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| r_circ_idx++; | |||||
| if(r_circ_idx >= delay_length) { | |||||
| r_circ_idx = 0; | |||||
| } | |||||
| r_delayline[r_circ_idx] = *bp; | |||||
| idx = arm_sin_q15( (q15_t)((r_delay_rate_index >> 16)&0x7fff)); | |||||
| idx = (idx * delay_depth) >> 15; | |||||
| idx = r_circ_idx - (delay_offset_idx + idx); | |||||
| if(idx < 0) { | |||||
| idx += delay_length; | |||||
| } | |||||
| if(idx >= delay_length) { | |||||
| idx -= delay_length; | |||||
| } | |||||
| if(frac < 0) | |||||
| idx1 = idx - 1; | |||||
| else | |||||
| idx1 = idx + 1; | |||||
| if(idx1 < 0) { | |||||
| idx1 += delay_length; | |||||
| } | |||||
| if(idx1 >= delay_length) { | |||||
| idx1 -= delay_length; | |||||
| } | |||||
| frac = (r_delay_rate_index >> 1) &0x7fff; | |||||
| frac = (( (int)(r_delayline[idx1] - r_delayline[idx])*frac) >> 15); | |||||
| *bp++ = (r_delayline[r_circ_idx] | |||||
| + r_delayline[idx] + frac | |||||
| )/2; | |||||
| r_delay_rate_index += delay_rate_incr; | |||||
| if(r_delay_rate_index & 0x80000000) { | |||||
| r_delay_rate_index &= 0x7fffffff; | |||||
| } | |||||
| } | |||||
| // send the effect output to the right channel | |||||
| transmit(block,1); | |||||
| release(block); | |||||
| } | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| void AudioFilterBiquad::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| int32_t a0, a1, a2, b1, b2, sum; | |||||
| uint32_t in2, out2, aprev, bprev, flag; | |||||
| uint32_t *data, *end; | |||||
| int32_t *state; | |||||
| block = receiveWritable(); | |||||
| if (!block) return; | |||||
| data = (uint32_t *)(block->data); | |||||
| end = data + AUDIO_BLOCK_SAMPLES/2; | |||||
| state = (int32_t *)definition; | |||||
| do { | |||||
| a0 = *state++; | |||||
| a1 = *state++; | |||||
| a2 = *state++; | |||||
| b1 = *state++; | |||||
| b2 = *state++; | |||||
| aprev = *state++; | |||||
| bprev = *state++; | |||||
| sum = *state & 0x3FFF; | |||||
| do { | |||||
| in2 = *data; | |||||
| sum = signed_multiply_accumulate_32x16b(sum, a0, in2); | |||||
| sum = signed_multiply_accumulate_32x16t(sum, a1, aprev); | |||||
| sum = signed_multiply_accumulate_32x16b(sum, a2, aprev); | |||||
| sum = signed_multiply_accumulate_32x16t(sum, b1, bprev); | |||||
| sum = signed_multiply_accumulate_32x16b(sum, b2, bprev); | |||||
| out2 = (uint32_t)sum >> 14; | |||||
| sum &= 0x3FFF; | |||||
| sum = signed_multiply_accumulate_32x16t(sum, a0, in2); | |||||
| sum = signed_multiply_accumulate_32x16b(sum, a1, in2); | |||||
| sum = signed_multiply_accumulate_32x16t(sum, a2, aprev); | |||||
| sum = signed_multiply_accumulate_32x16b(sum, b1, out2); | |||||
| sum = signed_multiply_accumulate_32x16t(sum, b2, bprev); | |||||
| aprev = in2; | |||||
| bprev = pack_16x16(sum >> 14, out2); | |||||
| sum &= 0x3FFF; | |||||
| aprev = in2; | |||||
| *data++ = bprev; | |||||
| } while (data < end); | |||||
| flag = *state & 0x80000000; | |||||
| *state++ = sum | flag; | |||||
| *(state-2) = bprev; | |||||
| *(state-3) = aprev; | |||||
| } while (flag); | |||||
| transmit(block); | |||||
| release(block); | |||||
| } | |||||
| void AudioFilterBiquad::updateCoefs(int *source, bool doReset) | |||||
| { | |||||
| int32_t *dest=(int32_t *)definition; | |||||
| int32_t *src=(int32_t *)source; | |||||
| __disable_irq(); | |||||
| for(uint8_t index=0;index<5;index++) | |||||
| { | |||||
| *dest++=*src++; | |||||
| } | |||||
| if(doReset) | |||||
| { | |||||
| *dest++=0; | |||||
| *dest++=0; | |||||
| *dest++=0; | |||||
| } | |||||
| __enable_irq(); | |||||
| } | |||||
| void AudioFilterBiquad::updateCoefs(int *source) | |||||
| { | |||||
| updateCoefs(source,false); | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| void AudioFilterFIR::begin(short *cp,int n_coeffs) | |||||
| { | |||||
| // pointer to coefficients | |||||
| coeff_p = cp; | |||||
| // 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); | |||||
| } | |||||
| } | |||||
| // This has the same effect as begin(NULL,0); | |||||
| void AudioFilterFIR::stop(void) | |||||
| { | |||||
| coeff_p = NULL; | |||||
| } | |||||
| void AudioFilterFIR::update(void) | |||||
| { | |||||
| audio_block_t *block,*b_new; | |||||
| // If there's no coefficient table, give up. | |||||
| if(coeff_p == NULL)return; | |||||
| // do passthru | |||||
| if(coeff_p == FIR_PASSTHRU) { | |||||
| // Just passthrough | |||||
| block = receiveWritable(0); | |||||
| if(block) { | |||||
| transmit(block,0); | |||||
| release(block); | |||||
| } | |||||
| block = receiveWritable(1); | |||||
| if(block) { | |||||
| transmit(block,1); | |||||
| release(block); | |||||
| } | |||||
| return; | |||||
| } | |||||
| // Left Channel | |||||
| block = receiveWritable(0); | |||||
| // 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); | |||||
| // 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 = receiveWritable(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 "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| void applyGain(int16_t *data, int32_t mult) | |||||
| { | |||||
| uint32_t *p = (uint32_t *)data; | |||||
| const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES); | |||||
| do { | |||||
| uint32_t tmp32 = *p; // read 2 samples from *data | |||||
| int32_t val1 = signed_multiply_32x16b(mult, tmp32); | |||||
| int32_t val2 = signed_multiply_32x16t(mult, tmp32); | |||||
| val1 = signed_saturate_rshift(val1, 16, 0); | |||||
| val2 = signed_saturate_rshift(val2, 16, 0); | |||||
| *p++ = pack_16x16(val2, val1); | |||||
| } while (p < end); | |||||
| } | |||||
| // page 133 | |||||
| void applyGainThenAdd(int16_t *data, const int16_t *in, int32_t mult) | |||||
| { | |||||
| uint32_t *dst = (uint32_t *)data; | |||||
| const uint32_t *src = (uint32_t *)in; | |||||
| const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES); | |||||
| if (mult == 65536) { | |||||
| do { | |||||
| uint32_t tmp32 = *dst; | |||||
| *dst++ = signed_add_16_and_16(tmp32, *src++); | |||||
| tmp32 = *dst; | |||||
| *dst++ = signed_add_16_and_16(tmp32, *src++); | |||||
| } while (dst < end); | |||||
| } else { | |||||
| do { | |||||
| uint32_t tmp32 = *src++; // read 2 samples from *data | |||||
| int32_t val1 = signed_multiply_32x16b(mult, tmp32); | |||||
| int32_t val2 = signed_multiply_32x16t(mult, tmp32); | |||||
| val1 = signed_saturate_rshift(val1, 16, 0); | |||||
| val2 = signed_saturate_rshift(val2, 16, 0); | |||||
| tmp32 = pack_16x16(val2, val1); | |||||
| uint32_t tmp32b = *dst; | |||||
| *dst++ = signed_add_16_and_16(tmp32, tmp32b); | |||||
| } while (dst < end); | |||||
| } | |||||
| } | |||||
| void AudioMixer4::update(void) | |||||
| { | |||||
| audio_block_t *in, *out=NULL; | |||||
| unsigned int channel; | |||||
| for (channel=0; channel < 4; channel++) { | |||||
| if (!out) { | |||||
| out = receiveWritable(channel); | |||||
| if (out) { | |||||
| int32_t mult = multiplier[channel]; | |||||
| if (mult != 65536) applyGain(out->data, mult); | |||||
| } | |||||
| } else { | |||||
| in = receiveReadOnly(channel); | |||||
| if (in) { | |||||
| applyGainThenAdd(out->data, in->data, multiplier[channel]); | |||||
| release(in); | |||||
| } | |||||
| } | |||||
| } | |||||
| if (out) { | |||||
| transmit(out); | |||||
| release(out); | |||||
| } | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| void AudioPlayMemory::play(const unsigned int *data) | |||||
| { | |||||
| uint32_t format; | |||||
| playing = 0; | |||||
| prior = 0; | |||||
| format = *data++; | |||||
| next = data; | |||||
| length = format & 0xFFFFFF; | |||||
| playing = format >> 24; | |||||
| } | |||||
| void AudioPlayMemory::stop(void) | |||||
| { | |||||
| playing = 0; | |||||
| } | |||||
| extern "C" { | |||||
| extern const int16_t ulaw_decode_table[256]; | |||||
| }; | |||||
| void AudioPlayMemory::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| const unsigned int *in; | |||||
| int16_t *out; | |||||
| uint32_t tmp32, consumed; | |||||
| int16_t s0, s1, s2, s3, s4; | |||||
| int i; | |||||
| if (!playing) return; | |||||
| block = allocate(); | |||||
| if (block == NULL) return; | |||||
| //Serial.write('.'); | |||||
| out = block->data; | |||||
| in = next; | |||||
| s0 = prior; | |||||
| switch (playing) { | |||||
| case 0x01: // u-law encoded, 44100 Hz | |||||
| for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 4) { | |||||
| tmp32 = *in++; | |||||
| *out++ = ulaw_decode_table[(tmp32 >> 0) & 255]; | |||||
| *out++ = ulaw_decode_table[(tmp32 >> 8) & 255]; | |||||
| *out++ = ulaw_decode_table[(tmp32 >> 16) & 255]; | |||||
| *out++ = ulaw_decode_table[(tmp32 >> 24) & 255]; | |||||
| } | |||||
| consumed = 128; | |||||
| break; | |||||
| case 0x81: // 16 bit PCM, 44100 Hz | |||||
| for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 2) { | |||||
| tmp32 = *in++; | |||||
| *out++ = (int16_t)(tmp32 & 65535); | |||||
| *out++ = (int16_t)(tmp32 >> 16); | |||||
| } | |||||
| consumed = 128; | |||||
| break; | |||||
| case 0x02: // u-law encoded, 22050 Hz | |||||
| for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 8) { | |||||
| tmp32 = *in++; | |||||
| s1 = ulaw_decode_table[(tmp32 >> 0) & 255]; | |||||
| s2 = ulaw_decode_table[(tmp32 >> 8) & 255]; | |||||
| s3 = ulaw_decode_table[(tmp32 >> 16) & 255]; | |||||
| s4 = ulaw_decode_table[(tmp32 >> 24) & 255]; | |||||
| *out++ = (s0 + s1) >> 1; | |||||
| *out++ = s1; | |||||
| *out++ = (s1 + s2) >> 1; | |||||
| *out++ = s2; | |||||
| *out++ = (s2 + s3) >> 1; | |||||
| *out++ = s3; | |||||
| *out++ = (s3 + s4) >> 1; | |||||
| *out++ = s4; | |||||
| s0 = s4; | |||||
| } | |||||
| consumed = 64; | |||||
| break; | |||||
| case 0x82: // 16 bits PCM, 22050 Hz | |||||
| for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 4) { | |||||
| tmp32 = *in++; | |||||
| s1 = (int16_t)(tmp32 & 65535); | |||||
| s2 = (int16_t)(tmp32 >> 16); | |||||
| *out++ = (s0 + s1) >> 1; | |||||
| *out++ = s1; | |||||
| *out++ = (s1 + s2) >> 1; | |||||
| *out++ = s2; | |||||
| s0 = s2; | |||||
| } | |||||
| consumed = 64; | |||||
| break; | |||||
| case 0x03: // u-law encoded, 11025 Hz | |||||
| for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 16) { | |||||
| tmp32 = *in++; | |||||
| s1 = ulaw_decode_table[(tmp32 >> 0) & 255]; | |||||
| s2 = ulaw_decode_table[(tmp32 >> 8) & 255]; | |||||
| s3 = ulaw_decode_table[(tmp32 >> 16) & 255]; | |||||
| s4 = ulaw_decode_table[(tmp32 >> 24) & 255]; | |||||
| *out++ = (s0 * 3 + s1) >> 2; | |||||
| *out++ = (s0 + s1) >> 1; | |||||
| *out++ = (s0 + s1 * 3) >> 2; | |||||
| *out++ = s1; | |||||
| *out++ = (s1 * 3 + s2) >> 2; | |||||
| *out++ = (s1 + s2) >> 1; | |||||
| *out++ = (s1 + s2 * 3) >> 2; | |||||
| *out++ = s2; | |||||
| *out++ = (s2 * 3 + s3) >> 2; | |||||
| *out++ = (s2 + s3) >> 1; | |||||
| *out++ = (s2 + s3 * 3) >> 2; | |||||
| *out++ = s3; | |||||
| *out++ = (s3 * 3 + s4) >> 2; | |||||
| *out++ = (s3 + s4) >> 1; | |||||
| *out++ = (s3 + s4 * 3) >> 2; | |||||
| *out++ = s4; | |||||
| s0 = s4; | |||||
| } | |||||
| consumed = 32; | |||||
| break; | |||||
| case 0x83: // 16 bit PCM, 11025 Hz | |||||
| for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 8) { | |||||
| tmp32 = *in++; | |||||
| s1 = (int16_t)(tmp32 & 65535); | |||||
| s2 = (int16_t)(tmp32 >> 16); | |||||
| *out++ = (s0 * 3 + s1) >> 2; | |||||
| *out++ = (s0 + s1) >> 1; | |||||
| *out++ = (s0 + s1 * 3) >> 2; | |||||
| *out++ = s1; | |||||
| *out++ = (s1 * 3 + s2) >> 2; | |||||
| *out++ = (s1 + s2) >> 1; | |||||
| *out++ = (s1 + s2 * 3) >> 2; | |||||
| *out++ = s2; | |||||
| s0 = s2; | |||||
| } | |||||
| consumed = 32; | |||||
| break; | |||||
| default: | |||||
| release(block); | |||||
| playing = 0; | |||||
| return; | |||||
| } | |||||
| prior = s0; | |||||
| next = in; | |||||
| if (length > consumed) { | |||||
| length -= consumed; | |||||
| } else { | |||||
| playing = 0; | |||||
| } | |||||
| transmit(block); | |||||
| release(block); | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| void AudioPlaySDcardRAW::begin(void) | |||||
| { | |||||
| playing = false; | |||||
| if (block) { | |||||
| release(block); | |||||
| block = NULL; | |||||
| } | |||||
| } | |||||
| bool AudioPlaySDcardRAW::play(const char *filename) | |||||
| { | |||||
| stop(); | |||||
| rawfile = SD.open(filename); | |||||
| if (!rawfile) { | |||||
| Serial.println("unable to open file"); | |||||
| return false; | |||||
| } | |||||
| Serial.println("able to open file"); | |||||
| playing = true; | |||||
| return true; | |||||
| } | |||||
| void AudioPlaySDcardRAW::stop(void) | |||||
| { | |||||
| __disable_irq(); | |||||
| if (playing) { | |||||
| playing = false; | |||||
| __enable_irq(); | |||||
| rawfile.close(); | |||||
| } else { | |||||
| __enable_irq(); | |||||
| } | |||||
| } | |||||
| void AudioPlaySDcardRAW::update(void) | |||||
| { | |||||
| unsigned int i, n; | |||||
| // only update if we're playing | |||||
| if (!playing) return; | |||||
| // allocate the audio blocks to transmit | |||||
| block = allocate(); | |||||
| if (block == NULL) return; | |||||
| if (rawfile.available()) { | |||||
| // we can read more data from the file... | |||||
| n = rawfile.read(block->data, AUDIO_BLOCK_SAMPLES*2); | |||||
| for (i=n/2; i < AUDIO_BLOCK_SAMPLES; i++) { | |||||
| block->data[i] = 0; | |||||
| } | |||||
| transmit(block); | |||||
| release(block); | |||||
| } else { | |||||
| rawfile.close(); | |||||
| playing = false; | |||||
| } | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #include "utility/dspinst.h" | |||||
| #define STATE_DIRECT_8BIT_MONO 0 // playing mono at native sample rate | |||||
| #define STATE_DIRECT_8BIT_STEREO 1 // playing stereo at native sample rate | |||||
| #define STATE_DIRECT_16BIT_MONO 2 // playing mono at native sample rate | |||||
| #define STATE_DIRECT_16BIT_STEREO 3 // playing stereo at native sample rate | |||||
| #define STATE_CONVERT_8BIT_MONO 4 // playing mono, converting sample rate | |||||
| #define STATE_CONVERT_8BIT_STEREO 5 // playing stereo, converting sample rate | |||||
| #define STATE_CONVERT_16BIT_MONO 6 // playing mono, converting sample rate | |||||
| #define STATE_CONVERT_16BIT_STEREO 7 // playing stereo, converting sample rate | |||||
| #define STATE_PARSE1 8 // looking for 20 byte ID header | |||||
| #define STATE_PARSE2 9 // looking for 16 byte format header | |||||
| #define STATE_PARSE3 10 // looking for 8 byte data header | |||||
| #define STATE_PARSE4 11 // ignoring unknown chunk | |||||
| #define STATE_STOP 12 | |||||
| void AudioPlaySDcardWAV::begin(void) | |||||
| { | |||||
| state = STATE_STOP; | |||||
| state_play = STATE_STOP; | |||||
| data_length = 0; | |||||
| if (block_left) { | |||||
| release(block_left); | |||||
| block_left = NULL; | |||||
| } | |||||
| if (block_right) { | |||||
| release(block_right); | |||||
| block_right = NULL; | |||||
| } | |||||
| } | |||||
| bool AudioPlaySDcardWAV::play(const char *filename) | |||||
| { | |||||
| stop(); | |||||
| wavfile = SD.open(filename); | |||||
| if (!wavfile) return false; | |||||
| buffer_remaining = 0; | |||||
| state_play = STATE_STOP; | |||||
| data_length = 0; | |||||
| state = STATE_PARSE1; | |||||
| return true; | |||||
| } | |||||
| void AudioPlaySDcardWAV::stop(void) | |||||
| { | |||||
| __disable_irq(); | |||||
| if (state != STATE_STOP) { | |||||
| audio_block_t *b1 = block_left; | |||||
| block_left = NULL; | |||||
| audio_block_t *b2 = block_right; | |||||
| block_right = NULL; | |||||
| state = STATE_STOP; | |||||
| __enable_irq(); | |||||
| if (b1) release(b1); | |||||
| if (b2) release(b2); | |||||
| wavfile.close(); | |||||
| } else { | |||||
| __enable_irq(); | |||||
| } | |||||
| } | |||||
| bool AudioPlaySDcardWAV::start(void) | |||||
| { | |||||
| __disable_irq(); | |||||
| if (state == STATE_STOP) { | |||||
| if (state_play == STATE_STOP) { | |||||
| __enable_irq(); | |||||
| return false; | |||||
| } | |||||
| state = state_play; | |||||
| } | |||||
| __enable_irq(); | |||||
| return true; | |||||
| } | |||||
| void AudioPlaySDcardWAV::update(void) | |||||
| { | |||||
| // only update if we're playing | |||||
| if (state == STATE_STOP) return; | |||||
| // allocate the audio blocks to transmit | |||||
| block_left = allocate(); | |||||
| if (block_left == NULL) return; | |||||
| if (state < 8 && (state & 1) == 1) { | |||||
| // if we're playing stereo, allocate another | |||||
| // block for the right channel output | |||||
| block_right = allocate(); | |||||
| if (block_right == NULL) { | |||||
| release(block_left); | |||||
| return; | |||||
| } | |||||
| } else { | |||||
| // if we're playing mono or just parsing | |||||
| // the WAV file header, no right-side block | |||||
| block_right = NULL; | |||||
| } | |||||
| block_offset = 0; | |||||
| //Serial.println("update"); | |||||
| // is there buffered data? | |||||
| if (buffer_remaining > 0) { | |||||
| // we have buffered data | |||||
| if (consume()) return; // it was enough to transmit audio | |||||
| } | |||||
| // we only get to this point when buffer[512] is empty | |||||
| if (state != STATE_STOP && wavfile.available()) { | |||||
| // we can read more data from the file... | |||||
| buffer_remaining = wavfile.read(buffer, 512); | |||||
| if (consume()) { | |||||
| // good, it resulted in audio transmit | |||||
| return; | |||||
| } else { | |||||
| // not good, no audio was transmitted | |||||
| buffer_remaining = 0; | |||||
| if (block_left) { | |||||
| release(block_left); | |||||
| block_left = NULL; | |||||
| } | |||||
| if (block_right) { | |||||
| release(block_right); | |||||
| block_right = NULL; | |||||
| } | |||||
| // if we're still playing, well, there's going to | |||||
| // be a gap in output, but we can't keep burning | |||||
| // time trying to read more data. Hopefully things | |||||
| // will go better next time? | |||||
| if (state != STATE_STOP) return; | |||||
| } | |||||
| } | |||||
| // end of file reached or other reason to stop | |||||
| wavfile.close(); | |||||
| if (block_left) { | |||||
| release(block_left); | |||||
| block_left = NULL; | |||||
| } | |||||
| if (block_right) { | |||||
| release(block_right); | |||||
| block_right = NULL; | |||||
| } | |||||
| state_play = STATE_STOP; | |||||
| state = STATE_STOP; | |||||
| } | |||||
| // https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ | |||||
| // Consume already buffered data. Returns true if audio transmitted. | |||||
| bool AudioPlaySDcardWAV::consume(void) | |||||
| { | |||||
| uint32_t len, size; | |||||
| uint8_t lsb, msb; | |||||
| const uint8_t *p; | |||||
| size = buffer_remaining; | |||||
| p = buffer + 512 - size; | |||||
| start: | |||||
| if (size == 0) return false; | |||||
| //Serial.print("AudioPlaySDcardWAV write, size = "); | |||||
| //Serial.print(size); | |||||
| //Serial.print(", data_length = "); | |||||
| //Serial.print(data_length); | |||||
| //Serial.print(", state = "); | |||||
| //Serial.println(state); | |||||
| switch (state) { | |||||
| // parse wav file header, is this really a .wav file? | |||||
| case STATE_PARSE1: | |||||
| len = 20 - data_length; | |||||
| if (size < len) len = size; | |||||
| memcpy((uint8_t *)header + data_length, p, len); | |||||
| data_length += len; | |||||
| if (data_length < 20) return false; | |||||
| // parse the header... | |||||
| if (header[0] == 0x46464952 && header[2] == 0x45564157 | |||||
| && header[3] == 0x20746D66 && header[4] == 16) { | |||||
| //Serial.println("header ok"); | |||||
| state = STATE_PARSE2; | |||||
| p += len; | |||||
| size -= len; | |||||
| data_length = 0; | |||||
| goto start; | |||||
| } | |||||
| //Serial.println("unknown WAV header"); | |||||
| break; | |||||
| // check & extract key audio parameters | |||||
| case STATE_PARSE2: | |||||
| len = 16 - data_length; | |||||
| if (size < len) len = size; | |||||
| memcpy((uint8_t *)header + data_length, p, len); | |||||
| data_length += len; | |||||
| if (data_length < 16) return false; | |||||
| if (parse_format()) { | |||||
| //Serial.println("audio format ok"); | |||||
| p += len; | |||||
| size -= len; | |||||
| data_length = 0; | |||||
| state = STATE_PARSE3; | |||||
| goto start; | |||||
| } | |||||
| //Serial.println("unknown audio format"); | |||||
| break; | |||||
| // find the data chunk | |||||
| case STATE_PARSE3: | |||||
| len = 8 - data_length; | |||||
| if (size < len) len = size; | |||||
| memcpy((uint8_t *)header + data_length, p, len); | |||||
| data_length += len; | |||||
| if (data_length < 8) return false; | |||||
| //Serial.print("chunk id = "); | |||||
| //Serial.print(header[0], HEX); | |||||
| //Serial.print(", length = "); | |||||
| //Serial.println(header[1]); | |||||
| p += len; | |||||
| size -= len; | |||||
| data_length = header[1]; | |||||
| if (header[0] == 0x61746164) { | |||||
| //Serial.println("found data chunk"); | |||||
| // TODO: verify offset in file is an even number | |||||
| // as required by WAV format. abort if odd. Code | |||||
| // below will depend upon this and fail if not even. | |||||
| leftover_bytes = 0; | |||||
| state = state_play; | |||||
| if (state & 1) { | |||||
| // if we're going to start stereo | |||||
| // better allocate another output block | |||||
| block_right = allocate(); | |||||
| if (!block_right) return false; | |||||
| } | |||||
| } else { | |||||
| state = STATE_PARSE4; | |||||
| } | |||||
| goto start; | |||||
| // ignore any extra unknown chunks (title & artist info) | |||||
| case STATE_PARSE4: | |||||
| if (size < data_length) { | |||||
| data_length -= size; | |||||
| return false; | |||||
| } | |||||
| p += data_length; | |||||
| size -= data_length; | |||||
| data_length = 0; | |||||
| state = STATE_PARSE3; | |||||
| goto start; | |||||
| // playing mono at native sample rate | |||||
| case STATE_DIRECT_8BIT_MONO: | |||||
| return false; | |||||
| // playing stereo at native sample rate | |||||
| case STATE_DIRECT_8BIT_STEREO: | |||||
| return false; | |||||
| // playing mono at native sample rate | |||||
| case STATE_DIRECT_16BIT_MONO: | |||||
| if (size > data_length) size = data_length; | |||||
| data_length -= size; | |||||
| while (1) { | |||||
| lsb = *p++; | |||||
| msb = *p++; | |||||
| size -= 2; | |||||
| block_left->data[block_offset++] = (msb << 8) | lsb; | |||||
| if (block_offset >= AUDIO_BLOCK_SAMPLES) { | |||||
| transmit(block_left, 0); | |||||
| transmit(block_left, 1); | |||||
| //Serial1.print('%'); | |||||
| //delayMicroseconds(90); | |||||
| release(block_left); | |||||
| block_left = NULL; | |||||
| data_length += size; | |||||
| buffer_remaining = size; | |||||
| if (block_right) release(block_right); | |||||
| return true; | |||||
| } | |||||
| if (size == 0) { | |||||
| if (data_length == 0) break; | |||||
| return false; | |||||
| } | |||||
| } | |||||
| // end of file reached | |||||
| if (block_offset > 0) { | |||||
| // TODO: fill remainder of last block with zero and transmit | |||||
| } | |||||
| state = STATE_STOP; | |||||
| return false; | |||||
| // playing stereo at native sample rate | |||||
| case STATE_DIRECT_16BIT_STEREO: | |||||
| if (size > data_length) size = data_length; | |||||
| data_length -= size; | |||||
| if (leftover_bytes) { | |||||
| block_left->data[block_offset] = header[0]; | |||||
| goto right16; | |||||
| } | |||||
| while (1) { | |||||
| lsb = *p++; | |||||
| msb = *p++; | |||||
| size -= 2; | |||||
| if (size == 0) { | |||||
| if (data_length == 0) break; | |||||
| header[0] = (msb << 8) | lsb; | |||||
| leftover_bytes = 2; | |||||
| return false; | |||||
| } | |||||
| block_left->data[block_offset] = (msb << 8) | lsb; | |||||
| right16: | |||||
| lsb = *p++; | |||||
| msb = *p++; | |||||
| size -= 2; | |||||
| block_right->data[block_offset++] = (msb << 8) | lsb; | |||||
| if (block_offset >= AUDIO_BLOCK_SAMPLES) { | |||||
| transmit(block_left, 0); | |||||
| release(block_left); | |||||
| block_left = NULL; | |||||
| transmit(block_right, 1); | |||||
| release(block_right); | |||||
| block_right = NULL; | |||||
| data_length += size; | |||||
| buffer_remaining = size; | |||||
| return true; | |||||
| } | |||||
| if (size == 0) { | |||||
| if (data_length == 0) break; | |||||
| leftover_bytes = 0; | |||||
| return false; | |||||
| } | |||||
| } | |||||
| // end of file reached | |||||
| if (block_offset > 0) { | |||||
| // TODO: fill remainder of last block with zero and transmit | |||||
| } | |||||
| state = STATE_STOP; | |||||
| return false; | |||||
| // playing mono, converting sample rate | |||||
| case STATE_CONVERT_8BIT_MONO : | |||||
| return false; | |||||
| // playing stereo, converting sample rate | |||||
| case STATE_CONVERT_8BIT_STEREO: | |||||
| return false; | |||||
| // playing mono, converting sample rate | |||||
| case STATE_CONVERT_16BIT_MONO: | |||||
| return false; | |||||
| // playing stereo, converting sample rate | |||||
| case STATE_CONVERT_16BIT_STEREO: | |||||
| return false; | |||||
| // ignore any extra data after playing | |||||
| // or anything following any error | |||||
| case STATE_STOP: | |||||
| return false; | |||||
| // this is not supposed to happen! | |||||
| //default: | |||||
| //Serial.println("AudioPlaySDcardWAV, unknown state"); | |||||
| } | |||||
| state_play = STATE_STOP; | |||||
| state = STATE_STOP; | |||||
| return false; | |||||
| } | |||||
| /* | |||||
| 00000000 52494646 66EA6903 57415645 666D7420 RIFFf.i.WAVEfmt | |||||
| 00000010 10000000 01000200 44AC0000 10B10200 ........D....... | |||||
| 00000020 04001000 4C495354 3A000000 494E464F ....LIST:...INFO | |||||
| 00000030 494E414D 14000000 49205761 6E742054 INAM....I Want T | |||||
| 00000040 6F20436F 6D65204F 76657200 49415254 o Come Over.IART | |||||
| 00000050 12000000 4D656C69 73736120 45746865 ....Melissa Ethe | |||||
| 00000060 72696467 65006461 746100EA 69030100 ridge.data..i... | |||||
| 00000070 FEFF0300 FCFF0400 FDFF0200 0000FEFF ................ | |||||
| 00000080 0300FDFF 0200FFFF 00000100 FEFF0300 ................ | |||||
| 00000090 FDFF0300 FDFF0200 FFFF0100 0000FFFF ................ | |||||
| */ | |||||
| // SD library on Teensy3 at 96 MHz | |||||
| // 256 byte chunks, speed is 443272 bytes/sec | |||||
| // 512 byte chunks, speed is 468023 bytes/sec | |||||
| bool AudioPlaySDcardWAV::parse_format(void) | |||||
| { | |||||
| uint8_t num = 0; | |||||
| uint16_t format; | |||||
| uint16_t channels; | |||||
| uint32_t rate; | |||||
| uint16_t bits; | |||||
| format = header[0]; | |||||
| //Serial.print(" format = "); | |||||
| //Serial.println(format); | |||||
| if (format != 1) return false; | |||||
| channels = header[0] >> 16; | |||||
| //Serial.print(" channels = "); | |||||
| //Serial.println(channels); | |||||
| if (channels == 1) { | |||||
| } else if (channels == 2) { | |||||
| num = 1; | |||||
| } else { | |||||
| return false; | |||||
| } | |||||
| bits = header[3] >> 16; | |||||
| //Serial.print(" bits = "); | |||||
| //Serial.println(bits); | |||||
| if (bits == 8) { | |||||
| } else if (bits == 16) { | |||||
| num |= 2; | |||||
| } else { | |||||
| return false; | |||||
| } | |||||
| rate = header[1]; | |||||
| //Serial.print(" rate = "); | |||||
| //Serial.println(rate); | |||||
| if (rate == AUDIO_SAMPLE_RATE) { | |||||
| } else if (rate >= 8000 && rate <= 48000) { | |||||
| num |= 4; | |||||
| } else { | |||||
| return false; | |||||
| } | |||||
| // we're not checking the byte rate and block align fields | |||||
| // if they're not the expected values, all we could do is | |||||
| // return false. Do any real wav files have unexpected | |||||
| // values in these other fields? | |||||
| state_play = num; | |||||
| return true; | |||||
| } | |||||
| #include "Audio.h" | |||||
| #include "arm_math.h" | |||||
| #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 - 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; | |||||
| } | |||||
| boolean AudioSynthWaveform::begin(float t_amp,int t_hi,short type) | |||||
| { | |||||
| tone_type = type; | |||||
| // tone_amp = t_amp; | |||||
| amplitude(t_amp); | |||||
| tone_freq = t_hi; | |||||
| if(t_hi < 1)return false; | |||||
| if(t_hi >= AUDIO_SAMPLE_RATE_EXACT/2)return false; | |||||
| tone_phase = 0; | |||||
| tone_incr = (0x100000000LL*t_hi)/AUDIO_SAMPLE_RATE_EXACT; | |||||
| if(0) { | |||||
| Serial.print("AudioSynthWaveform.begin(tone_amp = "); | |||||
| Serial.print(t_amp); | |||||
| Serial.print(", tone_hi = "); | |||||
| Serial.print(t_hi); | |||||
| Serial.print(", tone_incr = "); | |||||
| Serial.print(tone_incr,HEX); | |||||
| // Serial.print(", tone_hi = "); | |||||
| // Serial.print(t_hi); | |||||
| Serial.println(")"); | |||||
| } | |||||
| return(true); | |||||
| } | |||||
| void AudioSynthWaveform::update(void) | |||||
| { | |||||
| audio_block_t *block; | |||||
| short *bp; | |||||
| // temporary for ramp in sine | |||||
| uint32_t ramp_mag; | |||||
| // 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 | |||||
| block = allocate(); | |||||
| if(block) { | |||||
| bp = block->data; | |||||
| switch(tone_type) { | |||||
| case TONE_TYPE_SINE: | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| // 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--; | |||||
| // adjust tone_phase to Q15 format and then adjust the result | |||||
| // of the multiplication | |||||
| *bp = (short)((arm_sin_q15(tone_phase>>17) * tone_amp) >> 15); | |||||
| *bp++ = (*bp * ramp_mag)>>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--; | |||||
| // adjust tone_phase to Q15 format and then adjust the result | |||||
| // of the multiplication | |||||
| *bp = (short)((arm_sin_q15(tone_phase>>17) * last_tone_amp) >> 15); | |||||
| *bp++ = (*bp * ramp_mag)>>15; | |||||
| } else { | |||||
| // adjust tone_phase to Q15 format and then adjust the result | |||||
| // of the multiplication | |||||
| *bp++ = (short)((arm_sin_q15(tone_phase>>17) * tone_amp) >> 15); | |||||
| } | |||||
| // phase and incr are both unsigned 32-bit fractions | |||||
| tone_phase += tone_incr; | |||||
| } | |||||
| break; | |||||
| case TONE_TYPE_SQUARE: | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| if(tone_phase & 0x80000000)*bp++ = -tone_amp; | |||||
| else *bp++ = tone_amp; | |||||
| // phase and incr are both unsigned 32-bit fractions | |||||
| tone_phase += tone_incr; | |||||
| } | |||||
| break; | |||||
| case TONE_TYPE_SAWTOOTH: | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| *bp = ((short)(tone_phase>>16)*tone_amp) >> 15; | |||||
| bp++; | |||||
| // phase and incr are both unsigned 32-bit fractions | |||||
| tone_phase += tone_incr; | |||||
| } | |||||
| break; | |||||
| case TONE_TYPE_TRIANGLE: | |||||
| for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
| if(tone_phase & 0x80000000) { | |||||
| // negative half-cycle | |||||
| tmp_amp = -tone_amp; | |||||
| } | |||||
| else { | |||||
| // positive half-cycle | |||||
| tmp_amp = tone_amp; | |||||
| } | |||||
| mag = tone_phase << 2; | |||||
| // Determine which quadrant | |||||
| if(tone_phase & 0x40000000) { | |||||
| // negate the magnitude | |||||
| mag = ~mag + 1; | |||||
| } | |||||
| *bp++ = ((short)(mag>>17)*tmp_amp) >> 15; | |||||
| tone_phase += tone_incr; | |||||
| } | |||||
| break; | |||||
| } | |||||
| // send the samples to the left channel | |||||
| transmit(block,0); | |||||
| 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 | |||||