At the end of October 2014 Ozzie and Chris spent a week working on the QI4Q box. We added a second Teensy to the tech. This was designed to handle the the processing power required for the delay from our PVDF mic. The new teensy would deal with this very memory-hungry effect and also read amplitude information from the PVDF. It would send this info down the hardware serial to the original teensy which would use it to control the sound of our three WAV files for the Serres page. Everything is detailed in the video below (along with a strange capacitive sensing effect which is fun, but probably too unstable to use in a final iteration of the work). Code for the two teensys (still lacking serial functions) is also below.
To Do:
Split stereo wire going into amp so that each teensy can use the shielded headphone out in mono (this is much less noisy than the line out on the board)
Test and write hardware serial communication code for both teensys
Calibrate Serres audio object (currently called flexFSR to work with incoming data from serial)
To Do:
Split stereo wire going into amp so that each teensy can use the shielded headphone out in mono (this is much less noisy than the line out on the board)
Test and write hardware serial communication code for both teensys
Calibrate Serres audio object (currently called flexFSR to work with incoming data from serial)
/*
CODE FOR TEENSY ATTACHED TO PVDF
this code takes mic input from pvdf, applies delay and sends sound to the amp
it also reads amplitude peak from the pvdf and sends that information down the hardware serial
Chris Wood & Assegid Kidane. October 2014
*/
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
AudioInputI2S audioInput;
AudioOutputI2S audioOutput; // audio shield: headphones & line-out
//wav files. we control the volume based on peak detection
AudioPlaySdWav painter;
AudioPlaySdWav muff;
AudioPlaySdWav clean;
AudioAnalyzePeak peak_L;
AudioEffectDelay delay1;
AudioEffectDelay delay2;
//mixer for controlling volume of wav files
AudioMixer4 mixer1;
//we seem to need 2 peaks for the mic.
//i don't understand why this is - it should just be peak_L
AudioConnection a1(audioInput,0,peak_L,0);
AudioConnection a2(peak_L, 0, mixer1, 3);
AudioConnection a3(audioInput,0,audioOutput,0);
AudioConnection a4(audioInput,1,audioOutput,1);
//delay lines - three lines into first delay. combined one into second.
//sent out of one channel only to give pan effect
AudioConnection b1(audioInput, delay1);
AudioConnection b2(delay1, 0, mixer1, 0);
AudioConnection b3(delay1, 1, mixer1, 1);
AudioConnection b4(delay1, 2, mixer1, 2);
AudioConnection b5(mixer1, 0, delay2, 0);
AudioConnection b6(delay2, 0, audioOutput, 1);
AudioConnection d1(painter, 0, mixer1, 0);
AudioConnection d2(muff, 0, mixer1, 1);
AudioConnection d3(clean, 0, mixer1, 2);
AudioConnection d4(mixer1,0,audioOutput,0);
AudioConnection d5(mixer1,0,audioOutput,1);
AudioControlSGTL5000 audioShield;
const int myInput = AUDIO_INPUT_MIC;
void setup() {
AudioMemory(200);
audioShield.enable();
audioShield.inputSelect(myInput);
audioShield.volume(0.4); //audio shield volume
Serial.begin(9600);
//first stage of delay (3 taps)
delay1.delay(0, 150);
delay1.delay(1, 250);
delay1.delay(3, 330);
//second stage of delay (1 tap)
delay2.delay(0, 330);
}
elapsedMillis fps;
uint8_t cnt=0;
void loop() {
//readPeak
//
uint8_t leftPeak;
if(fps > 24) {
if (peak_L.available()) {
fps=0;
//this multiplication was from example sketch. it's not strictly necessary
uint8_t leftPeak=peak_L.read() * 30.0;
//**************
// INSERT CODE TO SEND leftPeak DOWN THE SERIAL PORT TO OTHER TEENSY
//**************
/*
//this next bit is for testing wav playback. in the final version we will send leftPeak down the serial
//where the other teensy will perform the function to control wav file volume in the mixer object
//this will likely not work inthis sketch because the delay lines are eating all the memory
//Serial.println(leftPeak);
fsr(leftPeak);
delay(50);
*/
}
}
}
CODE FOR TEENSY ATTACHED TO PVDF
this code takes mic input from pvdf, applies delay and sends sound to the amp
it also reads amplitude peak from the pvdf and sends that information down the hardware serial
Chris Wood & Assegid Kidane. October 2014
*/
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
AudioInputI2S audioInput;
AudioOutputI2S audioOutput; // audio shield: headphones & line-out
//wav files. we control the volume based on peak detection
AudioPlaySdWav painter;
AudioPlaySdWav muff;
AudioPlaySdWav clean;
AudioAnalyzePeak peak_L;
AudioEffectDelay delay1;
AudioEffectDelay delay2;
//mixer for controlling volume of wav files
AudioMixer4 mixer1;
//we seem to need 2 peaks for the mic.
//i don't understand why this is - it should just be peak_L
AudioConnection a1(audioInput,0,peak_L,0);
AudioConnection a2(peak_L, 0, mixer1, 3);
AudioConnection a3(audioInput,0,audioOutput,0);
AudioConnection a4(audioInput,1,audioOutput,1);
//delay lines - three lines into first delay. combined one into second.
//sent out of one channel only to give pan effect
AudioConnection b1(audioInput, delay1);
AudioConnection b2(delay1, 0, mixer1, 0);
AudioConnection b3(delay1, 1, mixer1, 1);
AudioConnection b4(delay1, 2, mixer1, 2);
AudioConnection b5(mixer1, 0, delay2, 0);
AudioConnection b6(delay2, 0, audioOutput, 1);
AudioConnection d1(painter, 0, mixer1, 0);
AudioConnection d2(muff, 0, mixer1, 1);
AudioConnection d3(clean, 0, mixer1, 2);
AudioConnection d4(mixer1,0,audioOutput,0);
AudioConnection d5(mixer1,0,audioOutput,1);
AudioControlSGTL5000 audioShield;
const int myInput = AUDIO_INPUT_MIC;
void setup() {
AudioMemory(200);
audioShield.enable();
audioShield.inputSelect(myInput);
audioShield.volume(0.4); //audio shield volume
Serial.begin(9600);
//first stage of delay (3 taps)
delay1.delay(0, 150);
delay1.delay(1, 250);
delay1.delay(3, 330);
//second stage of delay (1 tap)
delay2.delay(0, 330);
}
elapsedMillis fps;
uint8_t cnt=0;
void loop() {
//readPeak
//
uint8_t leftPeak;
if(fps > 24) {
if (peak_L.available()) {
fps=0;
//this multiplication was from example sketch. it's not strictly necessary
uint8_t leftPeak=peak_L.read() * 30.0;
//**************
// INSERT CODE TO SEND leftPeak DOWN THE SERIAL PORT TO OTHER TEENSY
//**************
/*
//this next bit is for testing wav playback. in the final version we will send leftPeak down the serial
//where the other teensy will perform the function to control wav file volume in the mixer object
//this will likely not work inthis sketch because the delay lines are eating all the memory
//Serial.println(leftPeak);
fsr(leftPeak);
delay(50);
*/
}
}
}
////////////////////////////////////////////////////////////////////////////
/*
CODE FOR TEENSY WHICH PLAYS WAV FILES
this code is for the teensy which is responsible for launching wav files based on data from
reed sensor
magnet sensors (page detection)
pvdf amplitude data (via serial from other teensy)
Chris Wood & Assegid Kidane. October 2014
*/
//NOTE: STILL NEED TO ADD FUNCTIONS FOR LAUNCHING WAV FILES BASED ON MAGNET SENSE
//teensy audio shield libraries
#include <Audio.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>
//wav files for serres control
AudioPlaySdWav painter;
AudioPlaySdWav muff;
AudioPlaySdWav clean;
//wav files for magent sense
AudioPlaySdWav quicken;
AudioPlaySdWav lem;
//wav file for photocell in base
AudioPlaySdWav ligeti;
//wav file for reed sense
AudioPlaySdWav breath;
AudioOutputI2S dac;
AudioMixer4 mixer1;
//connections for serres control
AudioConnection c1(painter, 0, mixer1, 0);
AudioConnection c2(muff, 0, mixer1, 1);
AudioConnection c3(clean, 0, mixer1, 2);
AudioConnection c16(mixer1, 0, dac, 0);
AudioConnection c17(mixer1, 0, dac, 1);
//connections for other wav files
//functions still need to be defined below (as of 31/11/14)
AudioConnection c3(clean, 0, mixer1, 2);
AudioConnection c8(quicken, 0, dac, 1);
AudioConnection c9(quicken, 0, dac, 0);
AudioConnection c10(lem, 0, dac, 1);
AudioConnection c11(lem, 0, dac, 0);
AudioConnection c12(ligeti, 0, dac, 1);
AudioConnection c13(ligeti, 0, dac, 0);
AudioConnection c14(breath, 0, dac, 1);
AudioConnection c15(breath, 0, dac, 0);
// boolean operators to prevent repeatedly launching audio files
boolean breathPlayed = false;
boolean quickenLaunched = false;
boolean lemLaunched = false;
boolean bookmarkLaunched = false;
boolean serresLaunched = false;
boolean ligetiLaunched = false;
void setup() {
Serial.begin(9600);
// Audio connections require memory to work.
AudioMemory(5);
audioShield.enable();
audioShield.volume(0.4);
// start running SD card
SPI.setMOSI(7);
SPI.setSCK(14);
SD.begin(10);
// initialise gain on serres mixer at reasonable level
mixer1.gain(0, 0.5);
}
void loop(){
//*********
//CODE OR FUNCTION TO READ DATA FROM SERIAL
//*********
fsr(SERIAL DATA);
photocellLigeti(PHOTOCELL PIN);
reedBreath(REEDSENSE_PIN);
//THIS FUNCTION CONTROLS PLAYBACK OF THE SERRES PAGE WAV FILES
void fsr(float input){
//next few lines to calibrate incoming data to previous range
float offset1 = 4.5;
float flexFloat = (mapFloat (input, 0.3, 32.0, 0.0, 1023.0));
float flexFSR = flexFloat * offset1;
Serial.println(flexFSR);
// launch sound when page touched
if ((flexFSR > 3) && (serresLaunched == false)){
painter.play("voicesea.wav");
muff.play("reverb.wav");
clean.play("serrescl.wav");
serresLaunched = true;
}
// stop sound when page laid to rest
if ((flexFSR < 3) && (serresLaunched == true)){
painter.stop();
muff.stop();
clean.stop();
serresLaunched = false;
//audioShield.volume(0.5);
}
// FIRST LEVEL (PAINTERLY) - voicesea.wav
float vol1 = 0;
// light touch
if ((flexFSR > 250) && (flexFSR < 600)){
vol1 = mapFloat(flexFSR, 250.0, 600.0, 0.0, 0.6);
}
// envelope up
if ((flexFSR >= 276) && (flexFSR <= 800)){
vol1 = mapFloat(flexFSR, 276.0, 800.0, 0.6, 0.8);
}
// envelope down
if ((flexFSR > 801) && (flexFSR < 850)){
vol1 = mapFloat(flexFSR, 801.0, 850.0, 0.8, 0.0);
}
// silent
if (flexFSR >= 850){
vol1 = 0;
}
// SECOND LEVEL (MUFF) - seresm.wav
float vol2 = 0;
// silent below this threshold
if (flexFSR < 300){
vol2 = 0;
}
// ramp up
if ((flexFSR >= 300) && (flexFSR <= 900)){
vol2 = mapFloat(flexFSR, 300.0, 900.0, 0.0, 0.8);
}
// ramp down 1
if ((flexFSR >= 901) && (flexFSR <= 990)){
vol2 = mapFloat(flexFSR, 901.0, 990.0, 0.8, 0.5);
}
// ramp down 2
if ((flexFSR >= 991) && (flexFSR <= 1020)){
vol2 = mapFloat(flexFSR, 991.0, 1020.0, 0.5, 0.0);
}
//silent above this threshold
if (flexFSR > 1020){
vol2 = 0;
}
//THIRD LEVEL (CLEAN) - serres.wav
float vol3 = 0;
//silent below this threshold
if (flexFSR < 900){
vol3 = 0;
}
//ramp up
if ((flexFSR >= 950) && (flexFSR <= 1020)){
vol3 = mapFloat(flexFSR, 950.0, 1020.0, 0.0, 0.70);
}
//steady above threshold
if (flexFSR > 1021){
vol3 = 0.70;
}
mixer1.gain(0, vol1);
mixer1.gain(1, vol2);
mixer1.gain(2, vol3);
// loop files after they've been launched
if (serresLaunched == true){
if (painter.isPlaying() == false){
painter.play("voicesea.wav");
}
if (muff.isPlaying() == false){
muff.play("reverb.wav");
}
if (clean.isPlaying() == false){
clean.play("serrescl.wav");
}
}
}
// function for the box opening detection
void reedBreath(int pin){
pinMode(pin, INPUT);
audioShield.volume(0.5);
Serial.println(digitalRead(pin));
if ((digitalRead(pin) == LOW) && (breathPlayed == false)){
Serial.println("launch reed sense");
breath.play("inhale.wav");
breathPlayed = true;
}
}
void photocellLigeti(int input){
int photoVal = input;
//Serial.println(photoVal);
if ((photoVal >= 800) && (ligetiLaunched == false)){
//make sure fsr stuff is turned off
painter.stop();
muff.stop();
clean.stop();
ligeti.play("ligeti.wav");
ligetiLaunched = true;
}
if (ligeti.isPlaying() == true){
painter.stop();
muff.stop();
clean.stop();
}
}
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max){
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
CODE FOR TEENSY WHICH PLAYS WAV FILES
this code is for the teensy which is responsible for launching wav files based on data from
reed sensor
magnet sensors (page detection)
pvdf amplitude data (via serial from other teensy)
Chris Wood & Assegid Kidane. October 2014
*/
//NOTE: STILL NEED TO ADD FUNCTIONS FOR LAUNCHING WAV FILES BASED ON MAGNET SENSE
//teensy audio shield libraries
#include <Audio.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>
//wav files for serres control
AudioPlaySdWav painter;
AudioPlaySdWav muff;
AudioPlaySdWav clean;
//wav files for magent sense
AudioPlaySdWav quicken;
AudioPlaySdWav lem;
//wav file for photocell in base
AudioPlaySdWav ligeti;
//wav file for reed sense
AudioPlaySdWav breath;
AudioOutputI2S dac;
AudioMixer4 mixer1;
//connections for serres control
AudioConnection c1(painter, 0, mixer1, 0);
AudioConnection c2(muff, 0, mixer1, 1);
AudioConnection c3(clean, 0, mixer1, 2);
AudioConnection c16(mixer1, 0, dac, 0);
AudioConnection c17(mixer1, 0, dac, 1);
//connections for other wav files
//functions still need to be defined below (as of 31/11/14)
AudioConnection c3(clean, 0, mixer1, 2);
AudioConnection c8(quicken, 0, dac, 1);
AudioConnection c9(quicken, 0, dac, 0);
AudioConnection c10(lem, 0, dac, 1);
AudioConnection c11(lem, 0, dac, 0);
AudioConnection c12(ligeti, 0, dac, 1);
AudioConnection c13(ligeti, 0, dac, 0);
AudioConnection c14(breath, 0, dac, 1);
AudioConnection c15(breath, 0, dac, 0);
// boolean operators to prevent repeatedly launching audio files
boolean breathPlayed = false;
boolean quickenLaunched = false;
boolean lemLaunched = false;
boolean bookmarkLaunched = false;
boolean serresLaunched = false;
boolean ligetiLaunched = false;
void setup() {
Serial.begin(9600);
// Audio connections require memory to work.
AudioMemory(5);
audioShield.enable();
audioShield.volume(0.4);
// start running SD card
SPI.setMOSI(7);
SPI.setSCK(14);
SD.begin(10);
// initialise gain on serres mixer at reasonable level
mixer1.gain(0, 0.5);
}
void loop(){
//*********
//CODE OR FUNCTION TO READ DATA FROM SERIAL
//*********
fsr(SERIAL DATA);
photocellLigeti(PHOTOCELL PIN);
reedBreath(REEDSENSE_PIN);
//THIS FUNCTION CONTROLS PLAYBACK OF THE SERRES PAGE WAV FILES
void fsr(float input){
//next few lines to calibrate incoming data to previous range
float offset1 = 4.5;
float flexFloat = (mapFloat (input, 0.3, 32.0, 0.0, 1023.0));
float flexFSR = flexFloat * offset1;
Serial.println(flexFSR);
// launch sound when page touched
if ((flexFSR > 3) && (serresLaunched == false)){
painter.play("voicesea.wav");
muff.play("reverb.wav");
clean.play("serrescl.wav");
serresLaunched = true;
}
// stop sound when page laid to rest
if ((flexFSR < 3) && (serresLaunched == true)){
painter.stop();
muff.stop();
clean.stop();
serresLaunched = false;
//audioShield.volume(0.5);
}
// FIRST LEVEL (PAINTERLY) - voicesea.wav
float vol1 = 0;
// light touch
if ((flexFSR > 250) && (flexFSR < 600)){
vol1 = mapFloat(flexFSR, 250.0, 600.0, 0.0, 0.6);
}
// envelope up
if ((flexFSR >= 276) && (flexFSR <= 800)){
vol1 = mapFloat(flexFSR, 276.0, 800.0, 0.6, 0.8);
}
// envelope down
if ((flexFSR > 801) && (flexFSR < 850)){
vol1 = mapFloat(flexFSR, 801.0, 850.0, 0.8, 0.0);
}
// silent
if (flexFSR >= 850){
vol1 = 0;
}
// SECOND LEVEL (MUFF) - seresm.wav
float vol2 = 0;
// silent below this threshold
if (flexFSR < 300){
vol2 = 0;
}
// ramp up
if ((flexFSR >= 300) && (flexFSR <= 900)){
vol2 = mapFloat(flexFSR, 300.0, 900.0, 0.0, 0.8);
}
// ramp down 1
if ((flexFSR >= 901) && (flexFSR <= 990)){
vol2 = mapFloat(flexFSR, 901.0, 990.0, 0.8, 0.5);
}
// ramp down 2
if ((flexFSR >= 991) && (flexFSR <= 1020)){
vol2 = mapFloat(flexFSR, 991.0, 1020.0, 0.5, 0.0);
}
//silent above this threshold
if (flexFSR > 1020){
vol2 = 0;
}
//THIRD LEVEL (CLEAN) - serres.wav
float vol3 = 0;
//silent below this threshold
if (flexFSR < 900){
vol3 = 0;
}
//ramp up
if ((flexFSR >= 950) && (flexFSR <= 1020)){
vol3 = mapFloat(flexFSR, 950.0, 1020.0, 0.0, 0.70);
}
//steady above threshold
if (flexFSR > 1021){
vol3 = 0.70;
}
mixer1.gain(0, vol1);
mixer1.gain(1, vol2);
mixer1.gain(2, vol3);
// loop files after they've been launched
if (serresLaunched == true){
if (painter.isPlaying() == false){
painter.play("voicesea.wav");
}
if (muff.isPlaying() == false){
muff.play("reverb.wav");
}
if (clean.isPlaying() == false){
clean.play("serrescl.wav");
}
}
}
// function for the box opening detection
void reedBreath(int pin){
pinMode(pin, INPUT);
audioShield.volume(0.5);
Serial.println(digitalRead(pin));
if ((digitalRead(pin) == LOW) && (breathPlayed == false)){
Serial.println("launch reed sense");
breath.play("inhale.wav");
breathPlayed = true;
}
}
void photocellLigeti(int input){
int photoVal = input;
//Serial.println(photoVal);
if ((photoVal >= 800) && (ligetiLaunched == false)){
//make sure fsr stuff is turned off
painter.stop();
muff.stop();
clean.stop();
ligeti.play("ligeti.wav");
ligetiLaunched = true;
}
if (ligeti.isPlaying() == true){
painter.stop();
muff.stop();
clean.stop();
}
}
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max){
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}