From 701ea1f2e43fb480d3571cc2ce5668208b5818bf Mon Sep 17 00:00:00 2001 From: Mike Barela Date: Thu, 6 Jun 2019 17:36:54 -0400 Subject: [PATCH] Add files via upload --- AdaVoice/README.md | 25 ++ AdaVoice/adavoice/adavoice.ino | 346 ++++++++++++++++ AdaVoice/adavoice_face/adavoice_face.ino | 479 +++++++++++++++++++++++ AdaVoice/dalek/dalek.ino | 281 +++++++++++++ AdaVoice/wavs/breath.wav | Bin 0 -> 44100 bytes AdaVoice/wavs/burp.wav | Bin 0 -> 48290 bytes AdaVoice/wavs/carhorn.wav | Bin 0 -> 6482 bytes AdaVoice/wavs/crunch.wav | Bin 0 -> 126832 bytes AdaVoice/wavs/destroy.wav | Bin 0 -> 61786 bytes AdaVoice/wavs/door.wav | Bin 0 -> 7742 bytes AdaVoice/wavs/foghorn.wav | Bin 0 -> 59044 bytes AdaVoice/wavs/hithere.wav | Bin 0 -> 26534 bytes AdaVoice/wavs/saber.wav | Bin 0 -> 89394 bytes AdaVoice/wavs/smell.wav | Bin 0 -> 64792 bytes AdaVoice/wavs/squirrel.wav | Bin 0 -> 23440 bytes AdaVoice/wavs/startup.wav | Bin 0 -> 75090 bytes AdaVoice/wavs/zilla.wav | Bin 0 -> 37146 bytes 17 files changed, 1131 insertions(+) create mode 100644 AdaVoice/README.md create mode 100644 AdaVoice/adavoice/adavoice.ino create mode 100644 AdaVoice/adavoice_face/adavoice_face.ino create mode 100644 AdaVoice/dalek/dalek.ino create mode 100644 AdaVoice/wavs/breath.wav create mode 100644 AdaVoice/wavs/burp.wav create mode 100644 AdaVoice/wavs/carhorn.wav create mode 100644 AdaVoice/wavs/crunch.wav create mode 100644 AdaVoice/wavs/destroy.wav create mode 100644 AdaVoice/wavs/door.wav create mode 100644 AdaVoice/wavs/foghorn.wav create mode 100644 AdaVoice/wavs/hithere.wav create mode 100644 AdaVoice/wavs/saber.wav create mode 100644 AdaVoice/wavs/smell.wav create mode 100644 AdaVoice/wavs/squirrel.wav create mode 100644 AdaVoice/wavs/startup.wav create mode 100644 AdaVoice/wavs/zilla.wav diff --git a/AdaVoice/README.md b/AdaVoice/README.md new file mode 100644 index 000000000..f54bc83af --- /dev/null +++ b/AdaVoice/README.md @@ -0,0 +1,25 @@ +Adafruit Voice Changer + Effects Box +======== + +Build your own custom voice changer and effects box. +Perfect for a costume, toy, art project, annoying the guy +in the next cubicle, etc. + +Check out the full tutorial at http://learn.adafruit.com/wave-shield-voice-changer + +To build this project you'll need: + + + * Adafruit Microphone Amp - http://www.adafruit.com/products/1063 + * Arduino Uno - http://www.adafruit.com/products/50 + * Adafruit Wave Shield Pack - http://www.adafruit.com/products/175 +(Comes with wave shield, SD card, Speaker, wire) + * Matrix Keypad - http://www.adafruit.com/products/419 + * 10K potentiometer - http://www.adafruit.com/products/562 + + +For portable use, a 6 x AA battery pack will last for many many hours + * http://www.adafruit.com/products/248 + +If you have a bigger speaker you want to power, check out our 3.7W +Class D amplifier board. http://www.adafruit.com/products/987 diff --git a/AdaVoice/adavoice/adavoice.ino b/AdaVoice/adavoice/adavoice.ino new file mode 100644 index 000000000..108d9139e --- /dev/null +++ b/AdaVoice/adavoice/adavoice.ino @@ -0,0 +1,346 @@ +/* +ADAVOICE is an Arduino-based voice pitch changer plus WAV playback. +Fun for Halloween costumes, comic convention getups and other shenanigans! + +Hardware requirements: + - Arduino Uno, Duemilanove or Diecimila (not Mega or Leonardo compatible). + - Adafruit Wave Shield + - Speaker attached to Wave Shield output + - Battery for portable use +If using the voice pitch changer, you will also need: + - Adafruit Microphone Breakout + - 10K potentiometer for setting pitch (or hardcode in sketch) +If using the WAV playback, you will also need: + - SD card + - Keypad, buttons or other sensor(s) for triggering sounds +Software requirements: + - WaveHC library for Arduino + - Demo WAV files on FAT-formatted SD card + +This example sketch uses a 3x4 keypad for triggering sounds...but with +some changes could be adapted to use several discrete buttons, Hall effect +sensors, force-sensing resistors (FSRs), I2C keypads, etc. (or if you just +want the voice effect, no buttons at all). + +Connections: + - 3.3V to mic amp+, 1 leg of potentiometer and Arduino AREF pin + - GND to mic amp-, opposite leg of potentiometer + - Analog pin 0 to mic amp output + - Analog pin 1 to center tap of potentiometer + - Wave Shield output to speaker or amplifier + - Matrix is wired to pins A2, A3, A4, A5 (rows) and 6, 7, 8 (columns) + - Wave shield is assumed wired as in product tutorial + +Potentiometer sets playback pitch. Pitch adjustment does NOT work in +realtime -- audio sampling requires 100% of the ADC. Pitch setting is +read at startup (or reset) and after a WAV finishes playing. + +POINT SPEAKER AWAY FROM MIC to avoid feedback. + +Written by Adafruit industries, with portions adapted from the +'PiSpeakHC' sketch included with WaveHC library. +*/ + +#include +#include + +SdReader card; // This object holds the information for the card +FatVolume vol; // This holds the information for the partition on the card +FatReader root; // This holds the information for the volumes root directory +FatReader file; // This object represent the WAV file for a pi digit or period +WaveHC wave; // This is the only wave (audio) object, -- we only play one at a time +#define error(msg) error_P(PSTR(msg)) // Macro allows error messages in flash memory + +#define ADC_CHANNEL 0 // Microphone on Analog pin 0 + +// Wave shield DAC: digital pins 2, 3, 4, 5 +#define DAC_CS_PORT PORTD +#define DAC_CS PORTD2 +#define DAC_CLK_PORT PORTD +#define DAC_CLK PORTD3 +#define DAC_DI_PORT PORTD +#define DAC_DI PORTD4 +#define DAC_LATCH_PORT PORTD +#define DAC_LATCH PORTD5 + +uint16_t in = 0, out = 0, xf = 0, nSamples; // Audio sample counters +uint8_t adc_save; // Default ADC mode + +// WaveHC didn't declare it's working buffers private or static, +// so we can be sneaky and borrow the same RAM for audio sampling! +extern uint8_t + buffer1[PLAYBUFFLEN], // Audio sample LSB + buffer2[PLAYBUFFLEN]; // Audio sample MSB +#define XFADE 16 // Number of samples for cross-fade +#define MAX_SAMPLES (PLAYBUFFLEN - XFADE) // Remaining available audio samples + +// Keypad information: +uint8_t + rows[] = { A2, A3, A4, A5 }, // Keypad rows connect to these pins + cols[] = { 6, 7, 8 }, // Keypad columns connect to these pins + r = 0, // Current row being examined + prev = 255, // Previous key reading (or 255 if none) + count = 0; // Counter for button debouncing +#define DEBOUNCE 10 // Number of iterations before button 'takes' + +// Keypad/WAV information. Number of elements here should match the +// number of keypad rows times the number of columns, plus one: +const char *sound[] = { + "breath" , "destroy", "saber" , // Row 1 = Darth Vader sounds + "zilla" , "crunch" , "burp" , // Row 2 = Godzilla sounds + "hithere", "smell" , "squirrel", // Row 3 = Dug the dog sounds + "carhorn", "foghorn", "door" , // Row 4 = Cartoon/SFX sound + "startup" }; // Extra item = boot sound + + +//////////////////////////////////// SETUP + +void setup() { + uint8_t i; + + Serial.begin(9600); + + // The WaveHC library normally initializes the DAC pins...but only after + // an SD card is detected and a valid file is passed. Need to init the + // pins manually here so that voice FX works even without a card. + pinMode(2, OUTPUT); // Chip select + pinMode(3, OUTPUT); // Serial clock + pinMode(4, OUTPUT); // Serial data + pinMode(5, OUTPUT); // Latch + digitalWrite(2, HIGH); // Set chip select high + + // Init SD library, show root directory. Note that errors are displayed + // but NOT regarded as fatal -- the program will continue with voice FX! + if(!card.init()) SerialPrint_P("Card init. failed!"); + else if(!vol.init(card)) SerialPrint_P("No partition!"); + else if(!root.openRoot(vol)) SerialPrint_P("Couldn't open dir"); + else { + PgmPrintln("Files found:"); + root.ls(); + // Play startup sound (last file in array). + playfile(sizeof(sound) / sizeof(sound[0]) - 1); + } + + // Optional, but may make sampling and playback a little smoother: + // Disable Timer0 interrupt. This means delay(), millis() etc. won't + // work. Comment this out if you really, really need those functions. + TIMSK0 = 0; + + // Set up Analog-to-Digital converter: + analogReference(EXTERNAL); // 3.3V to AREF + adc_save = ADCSRA; // Save ADC setting for restore later + + // Set keypad rows to outputs, set to HIGH logic level: + for(i=0; i= DEBOUNCE) { // Yes. Held beyond debounce threshold? + if(wave.isplaying) wave.stop(); // Stop current WAV (if any) + else stopPitchShift(); // or stop voice effect + playfile(button); // and play new sound. + while(digitalRead(cols[c]) == LOW); // Wait for button release. + prev = 255; // Reset debounce values. + count = 0; + } + } else { // Not same button as prior pass. + prev = button; // Record new button and + count = 0; // restart debounce counter. + } + } + } + + // Restore current row to HIGH logic state and advance row counter... + digitalWrite(rows[r], HIGH); + if(++r >= sizeof(rows)) { // If last row scanned... + r = 0; // Reset row counter + // If no new sounds have been triggered at this point, and if the + // pitch-shifter is not running, re-start it... + if(!wave.isplaying && !(TIMSK2 & _BV(TOIE2))) startPitchShift(); + } +} + + +//////////////////////////////////// HELPERS + +// Open and start playing a WAV file +void playfile(int idx) { + char filename[13]; + + (void)sprintf(filename,"%s.wav", sound[idx]); + Serial.print("File: "); + Serial.println(filename); + + if(!file.open(root, filename)) { + PgmPrint("Couldn't open file "); + Serial.print(filename); + return; + } + if(!wave.create(file)) { + PgmPrintln("Not a valid WAV"); + return; + } + wave.play(); +} + + +//////////////////////////////////// PITCH-SHIFT CODE + +void startPitchShift() { + + // Read analog pitch setting before starting audio sampling: + int pitch = analogRead(1); + Serial.print("Pitch: "); + Serial.println(pitch); + + // Right now the sketch just uses a fixed sound buffer length of + // 128 samples. It may be the case that the buffer length should + // vary with pitch for better results...further experimentation + // is required here. + nSamples = 128; + //nSamples = F_CPU / 3200 / OCR2A; // ??? + //if(nSamples > MAX_SAMPLES) nSamples = MAX_SAMPLES; + //else if(nSamples < (XFADE * 2)) nSamples = XFADE * 2; + + memset(buffer1, 0, nSamples + XFADE); // Clear sample buffers + memset(buffer2, 2, nSamples + XFADE); // (set all samples to 512) + + // WaveHC library already defines a Timer1 interrupt handler. Since we + // want to use the stock library and not require a special fork, Timer2 + // is used for a sample-playing interrupt here. As it's only an 8-bit + // timer, a sizeable prescaler is used (32:1) to generate intervals + // spanning the desired range (~4.8 KHz to ~19 KHz, or +/- 1 octave + // from the sampling frequency). This does limit the available number + // of speed 'steps' in between (about 79 total), but seems enough. + TCCR2A = _BV(WGM21) | _BV(WGM20); // Mode 7 (fast PWM), OC2 disconnected + TCCR2B = _BV(WGM22) | _BV(CS21) | _BV(CS20); // 32:1 prescale + OCR2A = map(pitch, 0, 1023, + F_CPU / 32 / (9615 / 2), // Lowest pitch = -1 octave + F_CPU / 32 / (9615 * 2)); // Highest pitch = +1 octave + + // Start up ADC in free-run mode for audio sampling: + DIDR0 |= _BV(ADC0D); // Disable digital input buffer on ADC0 + ADMUX = ADC_CHANNEL; // Channel sel, right-adj, AREF to 3.3V regulator + ADCSRB = 0; // Free-run mode + ADCSRA = _BV(ADEN) | // Enable ADC + _BV(ADSC) | // Start conversions + _BV(ADATE) | // Auto-trigger enable + _BV(ADIE) | // Interrupt enable + _BV(ADPS2) | // 128:1 prescale... + _BV(ADPS1) | // ...yields 125 KHz ADC clock... + _BV(ADPS0); // ...13 cycles/conversion = ~9615 Hz + + TIMSK2 |= _BV(TOIE2); // Enable Timer2 overflow interrupt + sei(); // Enable interrupts +} + +void stopPitchShift() { + ADCSRA = adc_save; // Disable ADC interrupt and allow normal use + TIMSK2 = 0; // Disable Timer2 Interrupt +} + +ISR(ADC_vect, ISR_BLOCK) { // ADC conversion complete + + // Save old sample from 'in' position to xfade buffer: + buffer1[nSamples + xf] = buffer1[in]; + buffer2[nSamples + xf] = buffer2[in]; + if(++xf >= XFADE) xf = 0; + + // Store new value in sample buffers: + buffer1[in] = ADCL; // MUST read ADCL first! + buffer2[in] = ADCH; + if(++in >= nSamples) in = 0; +} + +ISR(TIMER2_OVF_vect) { // Playback interrupt + uint16_t s; + uint8_t w, inv, hi, lo, bit; + int o2, i2, pos; + + // Cross fade around circular buffer 'seam'. + if((o2 = (int)out) == (i2 = (int)in)) { + // Sample positions coincide. Use cross-fade buffer data directly. + pos = nSamples + xf; + hi = (buffer2[pos] << 2) | (buffer1[pos] >> 6); // Expand 10-bit data + lo = (buffer1[pos] << 2) | buffer2[pos]; // to 12 bits + } if((o2 < i2) && (o2 > (i2 - XFADE))) { + // Output sample is close to end of input samples. Cross-fade to + // avoid click. The shift operations here assume that XFADE is 16; + // will need adjustment if that changes. + w = in - out; // Weight of sample (1-n) + inv = XFADE - w; // Weight of xfade + pos = nSamples + ((inv + xf) % XFADE); + s = ((buffer2[out] << 8) | buffer1[out]) * w + + ((buffer2[pos] << 8) | buffer1[pos]) * inv; + hi = s >> 10; // Shift 14 bit result + lo = s >> 2; // down to 12 bits + } else if (o2 > (i2 + nSamples - XFADE)) { + // More cross-fade condition + w = in + nSamples - out; + inv = XFADE - w; + pos = nSamples + ((inv + xf) % XFADE); + s = ((buffer2[out] << 8) | buffer1[out]) * w + + ((buffer2[pos] << 8) | buffer1[pos]) * inv; + hi = s >> 10; // Shift 14 bit result + lo = s >> 2; // down to 12 bits + } else { + // Input and output counters don't coincide -- just use sample directly. + hi = (buffer2[out] << 2) | (buffer1[out] >> 6); // Expand 10-bit data + lo = (buffer1[out] << 2) | buffer2[out]; // to 12 bits + } + + // Might be possible to tweak 'hi' and 'lo' at this point to achieve + // different voice modulations -- robot effect, etc.? + + DAC_CS_PORT &= ~_BV(DAC_CS); // Select DAC + // Clock out 4 bits DAC config (not in loop because it's constant) + DAC_DI_PORT &= ~_BV(DAC_DI); // 0 = Select DAC A, unbuffered + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_DI_PORT |= _BV(DAC_DI); // 1X gain, enable = 1 + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + for(bit=0x08; bit; bit>>=1) { // Clock out first 4 bits of data + if(hi & bit) DAC_DI_PORT |= _BV(DAC_DI); + else DAC_DI_PORT &= ~_BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + } + for(bit=0x80; bit; bit>>=1) { // Clock out last 8 bits of data + if(lo & bit) DAC_DI_PORT |= _BV(DAC_DI); + else DAC_DI_PORT &= ~_BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + } + DAC_CS_PORT |= _BV(DAC_CS); // Unselect DAC + + if(++out >= nSamples) out = 0; +} + diff --git a/AdaVoice/adavoice_face/adavoice_face.ino b/AdaVoice/adavoice_face/adavoice_face.ino new file mode 100644 index 000000000..9de146445 --- /dev/null +++ b/AdaVoice/adavoice_face/adavoice_face.ino @@ -0,0 +1,479 @@ +/* +This is a mash-up of the adavoice and wavface sketches. +Using an Arduino, Wave Shield and some supporting components, +creates a voice-changing mask with animated features. + +The BOM for the complete project is rather lengthy to put here, so +please see this tutorial for info (and parts) for the voice changer: +http://learn.adafruit.com/wave-shield-voice-changer +And see this tutorial for info/parts for the face animation: +http://learn.adafruit.com/animating-multiple-led-backpacks + +This version doesn't do WAV playback, as there's no keypad or +buttons. Just the voice. Some WAV-related hooks have been left +in place if you want to adapt it. This will be difficult though, +as RAM is VERY tight...we're using nearly the whole thing. +*/ + +#include +#include +#include +#include "Adafruit_LEDBackpack.h" +#include "Adafruit_GFX.h" + +// The SD card stuff is declared but not actually used in this sketch -- +// I wanted to keep the ability for WAVs here, even if they're not being +// exercised right now. RAM is VERY tight with all this in here...so if +// you adapt the sketch and run out of ram, and if you don't need WAV +// playback, I'd start by tearing this stuff out... +SdReader card; // This object holds the information for the card +FatVolume vol; // This holds the information for the partition on the card +FatReader root; // This holds the information for the volumes root directory +FatReader file; // This object represent the WAV file for a pi digit or period +WaveHC wave; // This is the only wave (audio) object, -- we only play one at a time +#define error(msg) error_P(PSTR(msg)) // Macro allows error messages in flash memory + +#define ADC_CHANNEL 0 // Microphone on Analog pin 0 + +// Wave shield DAC: digital pins 2, 3, 4, 5 +#define DAC_CS_PORT PORTD +#define DAC_CS PORTD2 +#define DAC_CLK_PORT PORTD +#define DAC_CLK PORTD3 +#define DAC_DI_PORT PORTD +#define DAC_DI PORTD4 +#define DAC_LATCH_PORT PORTD +#define DAC_LATCH PORTD5 + +uint16_t in = 0, out = 0, xf = 0, nSamples; // Audio sample counters +uint8_t adc_save; // Default ADC mode + +// WaveHC didn't declare it's working buffers private or static, +// so we can be sneaky and borrow the same RAM for audio sampling! +extern uint8_t + buffer1[PLAYBUFFLEN], // Audio sample LSB + buffer2[PLAYBUFFLEN]; // Audio sample MSB +#define XFADE 16 // Number of samples for cross-fade +#define MAX_SAMPLES (PLAYBUFFLEN - XFADE) // Remaining available audio samples + +// Used for averaging all the audio samples currently in the buffer +uint8_t oldsum = 0; +unsigned long newsum = 0L; + +#define MATRIX_EYES 0 +#define MATRIX_MOUTH_LEFT 1 +#define MATRIX_MOUTH_MIDDLE 2 +#define MATRIX_MOUTH_RIGHT 3 + +Adafruit_8x8matrix matrix[4] = { // Array of Adafruit_8x8matrix objects + Adafruit_8x8matrix(), Adafruit_8x8matrix(), + Adafruit_8x8matrix(), Adafruit_8x8matrix() }; + +static const uint8_t PROGMEM // Bitmaps are stored in program memory + blinkImg[][8] = { // Eye animation frames + { B11111100, // Fully open eye + B11111110, // The eye matrices are installed + B11111111, // in the mask at a 45 degree angle... + B11111111, // you can edit these bitmaps if you opt + B11111111, // for a rectilinear arrangement. + B11111111, + B01111111, + B00111111 }, + { B11110000, + B11111100, + B11111110, + B11111110, + B11111111, + B11111111, + B01111111, + B00111111 }, + { B11100000, + B11111000, + B11111100, + B11111110, + B11111110, + B01111111, + B00111111, + B00011111 }, + { B11000000, + B11110000, + B11111000, + B11111100, + B11111110, + B01111110, + B00111111, + B00011111 }, + { B10000000, + B11100000, + B11111000, + B11111100, + B01111100, + B01111110, + B00111110, + B00001111 }, + { B10000000, + B11000000, + B11100000, + B11110000, + B01111000, + B01111100, + B00111110, + B00001111 }, + { B10000000, + B10000000, + B11000000, + B01000000, + B01100000, + B00110000, + B00011100, + B00000111 }, + { B10000000, // Fully closed eye + B10000000, + B10000000, + B01000000, + B01000000, + B00100000, + B00011000, + B00000111 } }, + pupilImg[] = { // Pupil bitmap + B00000000, // (only top-left 7x7 is used) + B00011000, + B00111000, + B01110000, + B01100000, + B00000000, + B00000000, + B00000000 }, + landingImg[] = { // This is a bitmask of where + B01111000, // the pupil can safely "land" when + B11111100, // a new random position is selected, + B11111110, // so it doesn't run too far off the + B11111110, // edge and look bad. If you edit the + B11111110, // eye or pupil bitmaps, you may want + B01111110, // to adjust this...use '1' for valid + B00111100, // pupil positions, '0' for off-limits + B00000000 }, // points. + mouthImg[][24] = { // Mouth animation frames + { B00000000, B11000011, B00000000, // Mouth position 0 + B00000001, B01000010, B10000000, // Unlike the original 'roboface' + B00111110, B01111110, B01111100, // sketch (using AF animation), + B11000000, B00000000, B00000011, // the mouth positions here are + B00000000, B00000000, B00000000, // proportional to the current + B00000000, B00000000, B00000000, // sound amplitude. Position 0 + B00000000, B00000000, B00000000, // is closed, #7 is max open. + B00000000, B00000000, B00000000 }, + { B00000000, B00000000, B00000000, // Mouth position 1 + B00000000, B11000011, B00000000, + B00000011, B01111110, B11000000, + B00111110, B00000000, B01111100, + B11000000, B00000000, B00000011, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000 }, + { B00000000, B00000000, B00000000, // Mouth position 2 + B00000011, B11111111, B11000000, + B00011011, B01111110, B11011000, + B01111110, B01111110, B01111110, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000 }, + { B00000000, B00000000, B00000000, // Mouth position 3 + B00000011, B11111111, B11000000, + B01111011, B11111111, B11011110, + B00011111, B01111110, B11111000, + B00001110, B01111110, B01110000, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000 }, + { B00000000, B00000000, B00000000, // Mouth position 4 + B00000001, B11111111, B10000000, + B01111001, B11111111, B10011110, + B00111101, B11111111, B10111100, + B00011111, B01111110, B11111000, + B00000110, B01111110, B01100000, + B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000 }, + { B00000000, B00000000, B00000000, // Mouth position 5 + B00000001, B11111111, B10000000, + B00111001, B11111111, B10011100, + B00011101, B11111111, B10111000, + B00011111, B11111111, B11111000, + B00001111, B01111110, B11110000, + B00000110, B01111110, B01100000, + B00000000, B00000000, B00000000 }, + { B00000000, B00000000, B00000000, // Mouth position 6 + B00000001, B11111111, B10000000, + B00111001, B11111111, B10011100, + B00111101, B11111111, B10111100, + B00011111, B11111111, B11111100, + B00011111, B11111111, B11111000, + B00001111, B01111110, B11110000, + B00000110, B01111110, B01100000 }, + { B00000000, B01111110, B00000000, // Mouth position 7 + B00001001, B11111111, B10010000, + B00011001, B11111111, B10011000, + B00011101, B11111111, B10111000, + B00011111, B11111111, B11111000, + B00011111, B11111111, B11111000, + B00001111, B01111110, B11110000, + B00000110, B01111110, B01100000 } }, + blinkIndex[] = { 1, 2, 3, 4, 5, 6, 7, 6, 5, 3, 2, 1 }, // Blink bitmap sequence + matrixAddr[] = { 0x70, 0x71, 0x72, 0x73 }; // I2C addresses of 4 matrices + +uint8_t + blinkCountdown = 100, // Countdown to next blink (in frames) + gazeCountdown = 75, // Countdown to next eye movement + gazeFrames = 50, // Duration of eye movement (smaller = faster) + mouthPos = 0, // Current image number for mouth + mouthCountdown = 10; // Countdown to next mouth change +int8_t + eyeX = 3, eyeY = 3, // Current eye position + newX = 3, newY = 3, // Next eye position + dX = 0, dY = 0; // Distance from prior to new position + + +//////////////////////////////////// SETUP + +void setup() { + uint8_t i; + + Serial.begin(9600); + + // Seed random number generator from an unused analog input: + randomSeed(analogRead(A2)); + + // Initialize each matrix object: + for(i=0; i<4; i++) { + matrix[i].begin(pgm_read_byte(&matrixAddr[i])); + } + + // The WaveHC library normally initializes the DAC pins...but only after + // an SD card is detected and a valid file is passed. Need to init the + // pins manually here so that voice FX works even without a card. + pinMode(2, OUTPUT); // Chip select + pinMode(3, OUTPUT); // Serial clock + pinMode(4, OUTPUT); // Serial data + pinMode(5, OUTPUT); // Latch + digitalWrite(2, HIGH); // Set chip select high + + // Init SD library, show root directory. Note that errors are displayed + // but NOT regarded as fatal -- the program will continue with voice FX! + if(!card.init()) SerialPrint_P("Card init. failed!"); + else if(!vol.init(card)) SerialPrint_P("No partition!"); + else if(!root.openRoot(vol)) SerialPrint_P("Couldn't open dir"); + else { + PgmPrintln("Files found:"); + root.ls(); + } + + // Optional, but may make sampling and playback a little smoother: + // Disable Timer0 interrupt. This means delay(), millis() etc. won't + // work. Comment this out if you really, really need those functions. + TIMSK0 = 0; + + // Set up Analog-to-Digital converter: + analogReference(EXTERNAL); // 3.3V to AREF + adc_save = ADCSRA; // Save ADC setting for restore later + + startPitchShift(); // and start the pitch-shift mode by default. +} + + +//////////////////////////////////// LOOP + +void loop() { + // Draw eyeball in current state of blinkyness (no pupil). Note that + // only one eye needs to be drawn. Because the two eye matrices share + // the same address, the same data will be received by both. + matrix[MATRIX_EYES].clear(); + // When counting down to the next blink, show the eye in the fully- + // open state. On the last few counts (during the blink), look up + // the corresponding bitmap index. + matrix[MATRIX_EYES].drawBitmap(0, 0, + blinkImg[ + (blinkCountdown < sizeof(blinkIndex)) ? // Currently blinking? + pgm_read_byte(&blinkIndex[blinkCountdown]) : // Yes, look up bitmap # + 0 // No, show bitmap 0 + ], 8, 8, LED_ON); + // Decrement blink counter. At end, set random time for next blink. + if(--blinkCountdown == 0) blinkCountdown = random(5, 180); + + // Add a pupil atop the blinky eyeball bitmap. + // Periodically, the pupil moves to a new position... + if(--gazeCountdown <= gazeFrames) { + // Eyes are in motion - draw pupil at interim position + matrix[MATRIX_EYES].drawBitmap( + newX - (dX * gazeCountdown / gazeFrames) - 2, + newY - (dY * gazeCountdown / gazeFrames) - 2, + pupilImg, 7, 7, LED_OFF); + if(gazeCountdown == 0) { // Last frame? + eyeX = newX; eyeY = newY; // Yes. What's new is old, then... + do { // Pick random positions until one is within the eye circle + newX = random(7); newY = random(7); + } while(!((pgm_read_byte(&landingImg[newY]) << newX) & 0x80)); + dX = newX - eyeX; // Horizontal distance to move + dY = newY - eyeY; // Vertical distance to move + gazeFrames = random(3, 15); // Duration of eye movement + gazeCountdown = random(gazeFrames, 120); // Count to end of next movement + } + } else { + // Not in motion yet -- draw pupil at current static position + matrix[MATRIX_EYES].drawBitmap(eyeX - 2, eyeY - 2, pupilImg, 7, 7, LED_OFF); + } + + drawMouth(mouthImg[oldsum / 32]); + + // Refresh all of the matrices in one quick pass + for(uint8_t i=0; i<4; i++) matrix[i].writeDisplay(); + + delay(20); // ~50 FPS +} + +// Draw mouth image across three adjacent displays +void drawMouth(const uint8_t *img) { + for(uint8_t i=0; i<3; i++) { + matrix[MATRIX_MOUTH_LEFT + i].clear(); + matrix[MATRIX_MOUTH_LEFT + i].drawBitmap(i * -8, 0, img, 24, 8, LED_ON); + } +} + + +//////////////////////////////////// PITCH-SHIFT CODE + +void startPitchShift() { + + // Read analog pitch setting before starting audio sampling: + int pitch = analogRead(1); + Serial.print("Pitch: "); + Serial.println(pitch); + + // Right now the sketch just uses a fixed sound buffer length of + // 128 samples. It may be the case that the buffer length should + // vary with pitch for better results...further experimentation + // is required here. + nSamples = 128; + //nSamples = F_CPU / 3200 / OCR2A; // ??? + //if(nSamples > MAX_SAMPLES) nSamples = MAX_SAMPLES; + //else if(nSamples < (XFADE * 2)) nSamples = XFADE * 2; + + memset(buffer1, 0, nSamples + XFADE); // Clear sample buffers + memset(buffer2, 2, nSamples + XFADE); // (set all samples to 512) + + // WaveHC library already defines a Timer1 interrupt handler. Since we + // want to use the stock library and not require a special fork, Timer2 + // is used for a sample-playing interrupt here. As it's only an 8-bit + // timer, a sizeable prescaler is used (32:1) to generate intervals + // spanning the desired range (~4.8 KHz to ~19 KHz, or +/- 1 octave + // from the sampling frequency). This does limit the available number + // of speed 'steps' in between (about 79 total), but seems enough. + TCCR2A = _BV(WGM21) | _BV(WGM20); // Mode 7 (fast PWM), OC2 disconnected + TCCR2B = _BV(WGM22) | _BV(CS21) | _BV(CS20); // 32:1 prescale + OCR2A = map(pitch, 0, 1023, + F_CPU / 32 / (9615 / 2), // Lowest pitch = -1 octave + F_CPU / 32 / (9615 * 2)); // Highest pitch = +1 octave + + // Start up ADC in free-run mode for audio sampling: + DIDR0 |= _BV(ADC0D); // Disable digital input buffer on ADC0 + ADMUX = ADC_CHANNEL; // Channel sel, right-adj, AREF to 3.3V regulator + ADCSRB = 0; // Free-run mode + ADCSRA = _BV(ADEN) | // Enable ADC + _BV(ADSC) | // Start conversions + _BV(ADATE) | // Auto-trigger enable + _BV(ADIE) | // Interrupt enable + _BV(ADPS2) | // 128:1 prescale... + _BV(ADPS1) | // ...yields 125 KHz ADC clock... + _BV(ADPS0); // ...13 cycles/conversion = ~9615 Hz + + TIMSK2 |= _BV(TOIE2); // Enable Timer2 overflow interrupt + sei(); // Enable interrupts +} + +void stopPitchShift() { + ADCSRA = adc_save; // Disable ADC interrupt and allow normal use + TIMSK2 = 0; // Disable Timer2 Interrupt +} + +ISR(ADC_vect, ISR_BLOCK) { // ADC conversion complete + + // Save old sample from 'in' position to xfade buffer: + buffer1[nSamples + xf] = buffer1[in]; + buffer2[nSamples + xf] = buffer2[in]; + if(++xf >= XFADE) xf = 0; + + // Store new value in sample buffers: + buffer1[in] = ADCL; // MUST read ADCL first! + buffer2[in] = ADCH; + + newsum += abs((((int)buffer2[in] << 8) | buffer1[in]) - 512); + + if(++in >= nSamples) { + in = 0; + oldsum = (uint8_t)((newsum / nSamples) >> 1); // 0-255 + newsum = 0L; + } +} + +ISR(TIMER2_OVF_vect) { // Playback interrupt + uint16_t s; + uint8_t w, inv, hi, lo, bit; + int o2, i2, pos; + + // Cross fade around circular buffer 'seam'. + if((o2 = (int)out) == (i2 = (int)in)) { + // Sample positions coincide. Use cross-fade buffer data directly. + pos = nSamples + xf; + hi = (buffer2[pos] << 2) | (buffer1[pos] >> 6); // Expand 10-bit data + lo = (buffer1[pos] << 2) | buffer2[pos]; // to 12 bits + } if((o2 < i2) && (o2 > (i2 - XFADE))) { + // Output sample is close to end of input samples. Cross-fade to + // avoid click. The shift operations here assume that XFADE is 16; + // will need adjustment if that changes. + w = in - out; // Weight of sample (1-n) + inv = XFADE - w; // Weight of xfade + pos = nSamples + ((inv + xf) % XFADE); + s = ((buffer2[out] << 8) | buffer1[out]) * w + + ((buffer2[pos] << 8) | buffer1[pos]) * inv; + hi = s >> 10; // Shift 14 bit result + lo = s >> 2; // down to 12 bits + } else if (o2 > (i2 + nSamples - XFADE)) { + // More cross-fade condition + w = in + nSamples - out; + inv = XFADE - w; + pos = nSamples + ((inv + xf) % XFADE); + s = ((buffer2[out] << 8) | buffer1[out]) * w + + ((buffer2[pos] << 8) | buffer1[pos]) * inv; + hi = s >> 10; // Shift 14 bit result + lo = s >> 2; // down to 12 bits + } else { + // Input and output counters don't coincide -- just use sample directly. + hi = (buffer2[out] << 2) | (buffer1[out] >> 6); // Expand 10-bit data + lo = (buffer1[out] << 2) | buffer2[out]; // to 12 bits + } + + // Might be possible to tweak 'hi' and 'lo' at this point to achieve + // different voice modulations -- robot effect, etc.? + + DAC_CS_PORT &= ~_BV(DAC_CS); // Select DAC + // Clock out 4 bits DAC config (not in loop because it's constant) + DAC_DI_PORT &= ~_BV(DAC_DI); // 0 = Select DAC A, unbuffered + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_DI_PORT |= _BV(DAC_DI); // 1X gain, enable = 1 + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + for(bit=0x08; bit; bit>>=1) { // Clock out first 4 bits of data + if(hi & bit) DAC_DI_PORT |= _BV(DAC_DI); + else DAC_DI_PORT &= ~_BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + } + for(bit=0x80; bit; bit>>=1) { // Clock out last 8 bits of data + if(lo & bit) DAC_DI_PORT |= _BV(DAC_DI); + else DAC_DI_PORT &= ~_BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + } + DAC_CS_PORT |= _BV(DAC_CS); // Unselect DAC + + if(++out >= nSamples) out = 0; +} + diff --git a/AdaVoice/dalek/dalek.ino b/AdaVoice/dalek/dalek.ino new file mode 100644 index 000000000..a2ea5e1f4 --- /dev/null +++ b/AdaVoice/dalek/dalek.ino @@ -0,0 +1,281 @@ +/* +Dalek voice effect using Wave Shield. This is based on the adavoice sketch +with a lot of code & comments ripped out, so see that code for more insight, +parts list, etc. This sketch doesn't do any pitch bending, just the Dalek +modulation...you'll need to perform your own monotone & British accent. :) + +This should still let you play sounds off the SD card (voice effect stops +during playback), haven't tested, but worked in prior code this came from. + +Written by Adafruit industries, with portions adapted from the +'PiSpeakHC' sketch included with WaveHC library. +*/ + +#include +#include + +SdReader card; +FatVolume vol; +FatReader root; +FatReader file; +WaveHC wave; + +#define ADC_CHANNEL 0 // Microphone on Analog pin 0 + +// Wave shield DAC: digital pins 2, 3, 4, 5 +#define DAC_CS_PORT PORTD +#define DAC_CS PORTD2 +#define DAC_CLK_PORT PORTD +#define DAC_CLK PORTD3 +#define DAC_DI_PORT PORTD +#define DAC_DI PORTD4 +#define DAC_LATCH_PORT PORTD +#define DAC_LATCH PORTD5 + +uint8_t adc_save; + +// Keypad information: +uint8_t + rows[] = { A2, A3, A4, A5 }, // Keypad rows connect to these pins + cols[] = { 6, 7, 8 }, // Keypad columns connect to these pins + r = 0, // Current row being examined + prev = 255, // Previous key reading (or 255 if none) + count = 0; // Counter for button debouncing +#define DEBOUNCE 10 // Number of iterations before button 'takes' + +// Keypad/WAV information. Number of elements here should match the +// number of keypad rows times the number of columns, plus one: +const char *sound[] = { + "breath" , "destroy", "saber" , // Row 1 = Darth Vader sounds + "zilla" , "crunch" , "burp" , // Row 2 = Godzilla sounds + "hithere", "smell" , "squirrel", // Row 3 = Dug the dog sounds + "carhorn", "foghorn", "door" , // Row 4 = Cartoon/SFX sound + "startup" }; // Extra item = boot sound + + +//////////////////////////////////// SETUP + +void setup() { + uint8_t i; + + Serial.begin(9600); + + pinMode(2, OUTPUT); // Chip select + pinMode(3, OUTPUT); // Serial clock + pinMode(4, OUTPUT); // Serial data + pinMode(5, OUTPUT); // Latch + digitalWrite(2, HIGH); // Set chip select high + + if(!card.init()) Serial.print(F("Card init. failed!")); + else if(!vol.init(card)) Serial.print(F("No partition!")); + else if(!root.openRoot(vol)) Serial.print(F("Couldn't open dir")); + else { + Serial.println(F("Files found:")); + root.ls(); + // Play startup sound (last file in array). + playfile(sizeof(sound) / sizeof(sound[0]) - 1); + } + + // Optional, but may make sampling and playback a little smoother: + // Disable Timer0 interrupt. This means delay(), millis() etc. won't + // work. Comment this out if you really, really need those functions. + TIMSK0 = 0; + + // Set up Analog-to-Digital converter: + analogReference(EXTERNAL); // 3.3V to AREF + adc_save = ADCSRA; // Save ADC setting for restore later + + // Set keypad rows to outputs, set to HIGH logic level: + for(i=0; i= DEBOUNCE) { // Yes. Held beyond debounce threshold? + if(wave.isplaying) wave.stop(); // Stop current WAV (if any) + else stopDalek(); // or stop voice effect + playfile(button); // and play new sound. + while(digitalRead(cols[c]) == LOW); // Wait for button release. + prev = 255; // Reset debounce values. + count = 0; + } + } else { // Not same button as prior pass. + prev = button; // Record new button and + count = 0; // restart debounce counter. + } + } + } + + // Restore current row to HIGH logic state and advance row counter... + digitalWrite(rows[r], HIGH); + if(++r >= sizeof(rows)) { // If last row scanned... + r = 0; // Reset row counter + // If no new sounds have been triggered at this point, restart Dalek... + if(!wave.isplaying) startDalek(); + } +} + + +//////////////////////////////////// HELPERS + +// Open and start playing a WAV file +void playfile(int idx) { + char filename[13]; + + (void)sprintf(filename,"%s.wav", sound[idx]); + Serial.print("File: "); + Serial.println(filename); + + if(!file.open(root, filename)) { + Serial.print(F("Couldn't open file ")); + Serial.print(filename); + return; + } + if(!wave.create(file)) { + Serial.println(F("Not a valid WAV")); + return; + } + wave.play(); +} + + +//////////////////////////////////// DALEK MODULATION CODE + +void startDalek() { + + // Start up ADC in free-run mode for audio sampling: + DIDR0 |= _BV(ADC0D); // Disable digital input buffer on ADC0 + ADMUX = ADC_CHANNEL; // Channel sel, right-adj, AREF to 3.3V regulator + ADCSRB = 0; // Free-run mode + ADCSRA = _BV(ADEN) | // Enable ADC + _BV(ADSC) | // Start conversions + _BV(ADATE) | // Auto-trigger enable + _BV(ADIE) | // Interrupt enable + _BV(ADPS2) | // 128:1 prescale... + _BV(ADPS1) | // ...yields 125 KHz ADC clock... + _BV(ADPS0); // ...13 cycles/conversion = ~9615 Hz +} + +void stopDalek() { + ADCSRA = adc_save; // Disable ADC interrupt and allow normal use +} + +// Dalek sound is produced by a 'ring modulator' which multiplies microphone +// input by a 30 Hz sine wave. sin() is a time-consuming floating-point +// operation so instead a canned 8-bit integer table is used...the number of +// elements here takes into account the ADC sample rate (~9615 Hz) and the +// desired sine wave frequency (traditionally ~30 Hz for Daleks). +// This is actually abs(sin(x)) to slightly simplify some math later. + +volatile uint16_t ringPos = 0; // Current index into ring table below + +static const uint8_t PROGMEM ring[] = { + 0x00, 0x03, 0x05, 0x08, 0x0A, 0x0D, 0x0F, 0x12, + 0x14, 0x17, 0x19, 0x1B, 0x1E, 0x20, 0x23, 0x25, + 0x28, 0x2A, 0x2D, 0x2F, 0x32, 0x34, 0x37, 0x39, + 0x3C, 0x3E, 0x40, 0x43, 0x45, 0x48, 0x4A, 0x4C, + 0x4F, 0x51, 0x54, 0x56, 0x58, 0x5B, 0x5D, 0x5F, + 0x62, 0x64, 0x66, 0x68, 0x6B, 0x6D, 0x6F, 0x72, + 0x74, 0x76, 0x78, 0x7A, 0x7D, 0x7F, 0x81, 0x83, + 0x85, 0x87, 0x89, 0x8C, 0x8E, 0x90, 0x92, 0x94, + 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4, + 0xA6, 0xA8, 0xA9, 0xAB, 0xAD, 0xAF, 0xB1, 0xB3, + 0xB4, 0xB6, 0xB8, 0xBA, 0xBB, 0xBD, 0xBF, 0xC0, + 0xC2, 0xC4, 0xC5, 0xC7, 0xC8, 0xCA, 0xCB, 0xCD, + 0xCE, 0xD0, 0xD1, 0xD3, 0xD4, 0xD5, 0xD7, 0xD8, + 0xD9, 0xDB, 0xDC, 0xDD, 0xDE, 0xE0, 0xE1, 0xE2, + 0xE3, 0xE4, 0xE5, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, + 0xEC, 0xED, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, + 0xF3, 0xF3, 0xF4, 0xF5, 0xF5, 0xF6, 0xF7, 0xF7, + 0xF8, 0xF9, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFB, + 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFC, + 0xFC, 0xFB, 0xFB, 0xFB, 0xFA, 0xFA, 0xF9, 0xF9, + 0xF8, 0xF7, 0xF7, 0xF6, 0xF5, 0xF5, 0xF4, 0xF3, + 0xF3, 0xF2, 0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xED, + 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE5, 0xE4, + 0xE3, 0xE2, 0xE1, 0xE0, 0xDE, 0xDD, 0xDC, 0xDB, + 0xD9, 0xD8, 0xD7, 0xD5, 0xD4, 0xD3, 0xD1, 0xD0, + 0xCE, 0xCD, 0xCB, 0xCA, 0xC8, 0xC7, 0xC5, 0xC4, + 0xC2, 0xC0, 0xBF, 0xBD, 0xBB, 0xBA, 0xB8, 0xB6, + 0xB4, 0xB3, 0xB1, 0xAF, 0xAD, 0xAB, 0xA9, 0xA8, + 0xA6, 0xA4, 0xA2, 0xA0, 0x9E, 0x9C, 0x9A, 0x98, + 0x96, 0x94, 0x92, 0x90, 0x8E, 0x8C, 0x89, 0x87, + 0x85, 0x83, 0x81, 0x7F, 0x7D, 0x7A, 0x78, 0x76, + 0x74, 0x72, 0x6F, 0x6D, 0x6B, 0x68, 0x66, 0x64, + 0x62, 0x5F, 0x5D, 0x5B, 0x58, 0x56, 0x54, 0x51, + 0x4F, 0x4C, 0x4A, 0x48, 0x45, 0x43, 0x40, 0x3E, + 0x3C, 0x39, 0x37, 0x34, 0x32, 0x2F, 0x2D, 0x2A, + 0x28, 0x25, 0x23, 0x20, 0x1E, 0x1B, 0x19, 0x17, + 0x14, 0x12, 0x0F, 0x0D, 0x0A, 0x08, 0x05, 0x03 }; + +ISR(ADC_vect, ISR_BLOCK) { // ADC conversion complete + + uint8_t hi, lo, bit; + int32_t v; // Voice in + uint16_t r; // Ring in + uint32_t o; // Output + + lo = ADCL; + hi = ADCH; + + // Multiply signed 10-bit input by abs(sin(30 Hz)): + v = ((int32_t)hi << 8 | lo) - 512; // voice = -512 to +511 + r = (uint16_t)pgm_read_byte(&ring[ringPos]) + 1; // ring = 1 to 256 + o = v * r + 131072; // 0-261888 (18-bit) + hi = (o >> 14); // Scale 18- to 12-bit + lo = (o >> 16) | (o >> 6); + + if(++ringPos >= sizeof(ring)) ringPos = 0; // Cycle through table + + // Issue result to DAC: + DAC_CS_PORT &= ~_BV(DAC_CS); + DAC_DI_PORT &= ~_BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_DI_PORT |= _BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + for(bit=0x08; bit; bit>>=1) { + if(hi & bit) DAC_DI_PORT |= _BV(DAC_DI); + else DAC_DI_PORT &= ~_BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + } + for(bit=0x80; bit; bit>>=1) { + if(lo & bit) DAC_DI_PORT |= _BV(DAC_DI); + else DAC_DI_PORT &= ~_BV(DAC_DI); + DAC_CLK_PORT |= _BV(DAC_CLK); DAC_CLK_PORT &= ~_BV(DAC_CLK); + } + DAC_CS_PORT |= _BV(DAC_CS); +} + diff --git a/AdaVoice/wavs/breath.wav b/AdaVoice/wavs/breath.wav new file mode 100644 index 0000000000000000000000000000000000000000..352975a18982f1b972725c29f1884b7b49fab2d4 GIT binary patch literal 44100 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