Add files via upload
This commit is contained in:
parent
98aec7df53
commit
701ea1f2e4
17 changed files with 1131 additions and 0 deletions
25
AdaVoice/README.md
Normal file
25
AdaVoice/README.md
Normal file
|
|
@ -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.
|
||||||
|
|
||||||
|
<a href="http://learn.adafruit.com/wave-shield-voice-changer">Check out the full tutorial at http://learn.adafruit.com/wave-shield-voice-changer</a>
|
||||||
|
|
||||||
|
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
|
||||||
346
AdaVoice/adavoice/adavoice.ino
Normal file
346
AdaVoice/adavoice/adavoice.ino
Normal file
|
|
@ -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 <WaveHC.h>
|
||||||
|
#include <WaveUtil.h>
|
||||||
|
|
||||||
|
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<sizeof(rows); i++) {
|
||||||
|
pinMode(rows[i], OUTPUT);
|
||||||
|
digitalWrite(rows[i], HIGH);
|
||||||
|
}
|
||||||
|
// Set keypad columns to inputs, enable pull-up resistors:
|
||||||
|
for(i=0; i<sizeof(cols); i++) {
|
||||||
|
pinMode(cols[i], INPUT);
|
||||||
|
digitalWrite(cols[i], HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(wave.isplaying); // Wait for startup sound to finish...
|
||||||
|
startPitchShift(); // and start the pitch-shift mode by default.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////// LOOP
|
||||||
|
|
||||||
|
// As written here, the loop function scans a keypad to triggers sounds
|
||||||
|
// (stopping and restarting the voice effect as needed). If all you need
|
||||||
|
// is a couple of buttons, it may be easier to tear this out and start
|
||||||
|
// over with some simple digitalRead() calls.
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
uint8_t c, button;
|
||||||
|
|
||||||
|
// Set current row to LOW logic state...
|
||||||
|
digitalWrite(rows[r], LOW);
|
||||||
|
// ...then examine column buttons for a match...
|
||||||
|
for(c=0; c<sizeof(cols); c++) {
|
||||||
|
if(digitalRead(cols[c]) == LOW) { // First match.
|
||||||
|
button = r * sizeof(cols) + c; // Get button index.
|
||||||
|
if(button == prev) { // Same button as before?
|
||||||
|
if(++count >= 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;
|
||||||
|
}
|
||||||
|
|
||||||
479
AdaVoice/adavoice_face/adavoice_face.ino
Normal file
479
AdaVoice/adavoice_face/adavoice_face.ino
Normal file
|
|
@ -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 <WaveHC.h>
|
||||||
|
#include <WaveUtil.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
281
AdaVoice/dalek/dalek.ino
Normal file
281
AdaVoice/dalek/dalek.ino
Normal file
|
|
@ -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 <WaveHC.h>
|
||||||
|
#include <WaveUtil.h>
|
||||||
|
|
||||||
|
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<sizeof(rows); i++) {
|
||||||
|
pinMode(rows[i], OUTPUT);
|
||||||
|
digitalWrite(rows[i], HIGH);
|
||||||
|
}
|
||||||
|
// Set keypad columns to inputs, enable pull-up resistors:
|
||||||
|
for(i=0; i<sizeof(cols); i++) {
|
||||||
|
pinMode(cols[i], INPUT);
|
||||||
|
digitalWrite(cols[i], HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(wave.isplaying); // Wait for startup sound to finish...
|
||||||
|
startDalek(); // and start the Dalek effect
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////// LOOP
|
||||||
|
|
||||||
|
// As written here, the loop function scans a keypad to triggers sounds
|
||||||
|
// (stopping and restarting the voice effect as needed). If all you need
|
||||||
|
// is a couple of buttons, it may be easier to tear this out and start
|
||||||
|
// over with some simple digitalRead() calls.
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
uint8_t c, button;
|
||||||
|
|
||||||
|
// Set current row to LOW logic state...
|
||||||
|
digitalWrite(rows[r], LOW);
|
||||||
|
// ...then examine column buttons for a match...
|
||||||
|
for(c=0; c<sizeof(cols); c++) {
|
||||||
|
if(digitalRead(cols[c]) == LOW) { // First match.
|
||||||
|
button = r * sizeof(cols) + c; // Get button index.
|
||||||
|
if(button == prev) { // Same button as before?
|
||||||
|
if(++count >= 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);
|
||||||
|
}
|
||||||
|
|
||||||
BIN
AdaVoice/wavs/breath.wav
Normal file
BIN
AdaVoice/wavs/breath.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/burp.wav
Normal file
BIN
AdaVoice/wavs/burp.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/carhorn.wav
Normal file
BIN
AdaVoice/wavs/carhorn.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/crunch.wav
Normal file
BIN
AdaVoice/wavs/crunch.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/destroy.wav
Normal file
BIN
AdaVoice/wavs/destroy.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/door.wav
Normal file
BIN
AdaVoice/wavs/door.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/foghorn.wav
Normal file
BIN
AdaVoice/wavs/foghorn.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/hithere.wav
Normal file
BIN
AdaVoice/wavs/hithere.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/saber.wav
Normal file
BIN
AdaVoice/wavs/saber.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/smell.wav
Normal file
BIN
AdaVoice/wavs/smell.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/squirrel.wav
Normal file
BIN
AdaVoice/wavs/squirrel.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/startup.wav
Normal file
BIN
AdaVoice/wavs/startup.wav
Normal file
Binary file not shown.
BIN
AdaVoice/wavs/zilla.wav
Normal file
BIN
AdaVoice/wavs/zilla.wav
Normal file
Binary file not shown.
Loading…
Reference in a new issue