Ticha-Voice Box Documentation

photo

Overview
The ‘Voice Box’ is a musical instrument (of sorts) that receives audio input from the microphone and performs real-time pitch changes with a custom glove-controller. It can be used as both a personal listening device and a means of communication: the user has the option to either speak directly into the microphone and have their altered voice projected from the speaker, or plug in headsets and listen to the distorted noises of the world around them.

Inspiration / Critical Reflection
The project was inspired by a number of things that were not necessarily directly related to each other. Initially when I wanted to make simple piano gloves I was actually inspired by my frequent practice of tapping on tables or chairs that I developed as a result of not having ready access to a piano. In order to manifest this habit, I decided to create a portable instrument that allowed other people to listen to the sounds I hear in my head. But soon I discovered that a number of people have made instruments like these in the past, so instead of being a personal project it turned into a re-implementation of what has already been done countless times. So I decided to refocus my scope of inspiration in an effort to create something that was more novel. When I stumbled across Adafruit’s Wave Shield and Voice Changer project I immediately had my heart set on making a device that distorted voices in some way. I was initially aiming to create gloves that allowed a person to autotune their voice in real-time and make them sound like Imogen Heap, but given the limited time I had and my lack of understanding of how sound frequencies work I had to keep things relatively simple. Thus instead of a real-time autotuner, I built a real-time pitch-shifter.

The Voice Box surprisingly became a device that had some personal value as well, as its concept revolves around the difficulty to understand others and their difficulty to understand me. As I was testing the final product, I become engrossed in puppeteering other people’s voices and speaking in voices that were hardly decipherable – and it was then that I realized these gloves had created a wall between myself and society. Using these gloves turned into a very self-reflective experience, as it caused me to exhibit strange control freak behaviors and made me think about why I was able to extract so much enjoyment out of exercising power over others.

Technical Details
Electrodes are placed around the joints of each of my fingers so that whenever I bend one of them I would cause the electrodes to make contact – triggering a switch that creates the voice pitch-shifting effect. Essentially the electrodes behave like normal momentary switches, but were specifically designed to function without having to make contact with an external surface/object. This allows for an ease of use and enables user to make the more natural gestures common in playing keyboard instruments and typing.

Some technical hurdles I had to overcome: Although using electrodes seems to be a conceptually simple idea, they were surprisingly difficult to implement properly. I initially only had a pull-up resistor for each finger (to prevent short circuiting), but when I tested it out I noticed that the Arduino was not correctly interpreting the digital input data; namely, when the electrodes made contact with each other the input was read as 1’s, but when they were separated the input was just a jumbled mess of 0’s and 1’s. To overcome this issue I had to add pull-down resistors to explicitly make the ‘open’ and ‘closed’ states distinct. But however annoying the resistor handling was, I think the greatest technical hurdle I overcame was getting the pitch shifting to actually work. Adafruit’s original voice changer project uses a potentiometer to make pitch shifts, and because that is an analog input it is not possible to change your voice in real-time (running two analog inputs concurrently is beyond the capacity of an Arduino). So I theorized that while it’s not possible to dynamically change pitch using an analog input, it could technically be possible with multiple digital inputs. Luckily my theory was correct, and making things work just required some simple modifications to Adafruit’s original code.

Images
(Sorry for not using Fritzing – there are too many parts to the device and I felt it would be much easier for me to show what’s going on with photos)

/* Code adapted from ADAVOICE, an Arduino-based voice pitch changer */

#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/WAV information.  Number of elements here should match the
// number of keypad rows times the number of columns, plus one:
const char *sound[] = {
  "startup" };                      // Extra item = boot sound

int button6State = 0; 
int button7State = 0; 
int button8State = 0; 
int button9State = 0; 
int button11State = 0; 

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

  //initialization
  for(int i = 6; i < = 9; i++) {
    pinMode(i, INPUT);
  }
  pinMode(11, INPUT);

  while(wave.isplaying); // Wait for startup sound to finish...
  startPitchShift(700);     // 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() {
  button6State = digitalRead(6);
  button7State = digitalRead(7);
  button8State = digitalRead(8);
  button9State = digitalRead(9);
  button11State = digitalRead(11);

  if (button6State == HIGH) { //thumb   
    startPitchShift(0);

  } 
  else if (button7State == HIGH) { //pointer   
    startPitchShift(350);
  } 
  else if (button8State == HIGH) { //middle    
    startPitchShift(700);
  } 
  else if (button9State == HIGH) { //ring   
    startPitchShift(820);

  } 
  if (button11State == HIGH) { //pinky
    startPitchShift(1000);

  } 
}


//////////////////////////////////// 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(int 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;
}

Ticha-Progress

Well, I’m technically in “progress limbo” at the moment because I am still waiting for my components from Adafruit, but I’ve done some research on how the code for the project should work and compiled useful code from a couple of sources. I have also modified my idea to something (hopefully) more feasible and (definitely) more interesting than my previous one.

EDIT: Got the voice changer to work and I figured out how to change pitch in realtime without having to worry about too many analog signals! No pictures / video, but it works! Now I just need to make the gloves + do a little bit of coding and I’ll be good to go. 🙂

Ticha-Sketches

I Think I May Scratch Myself in my Sleep is a project by Marco de Mutiis that depicts the inner violence of man through animating a dismembered piano. Without the strings to produce melodic tones, the hammers of the piano are left to flail about and rap against the cloth. The movements of the hammers are triggered by data received from internet sex convos, making their rapid tapping a manifestation of the internal frustration we suppress on a daily basis. While I don’t think I plan to make something this sophisticated for my final project, I really like this concept of taking data and representing it in an quirky, poetic manner.

 

The Pong Playing Flexible Screen on a Shirt is a pretty self-explanatory project: it is a T-shirt that also serves as an interface for playing pong. Wearable electronics has become a recent topic of interest of mine because it raises the question of how an article of clothing can be both fashionable and functional.

 

Speaking of wearable electronics, this pressure-sensitive conductive sheet seems to be a handy sensor to have for a wearable technology project. I was mostly drawn to it because it is a lightweight, cheap alternative to bend/flex sensors. In the example on Adafruit, it was used to create shoes that lit up with each step a person took – and building off that for inspiration, I thought it would be interesting to also make a project that focused on walking behavior.

 

Ideas:

1. Groove Gloves

sketch I

I was thinking about creating gloves that play sound based on certain finger movements – an air piano, in other words. Each finger would have a sensor that gets triggered when that finger is bent, which causes the note corresponding to the respective finger to be played. Instead of buying 10 expensive flex/bend sensors, I was thinking about making my own simple ‘contact switches’ that can be used to determine whether a finger is bent or not. When the fingers are relaxed the contact strips will be disconnected, otherwise when the fingers are bent to a certain extent the contact strips will be connected, triggering the sound.

It would be interesting to incorporate a visual aspect into the gloves as well – perhaps an LED for each finger that lights up whenever a note is played. With these two attributes the ‘Grooving Gloves’ can be used as a visually appealing performance-art instrument.

 

2. Tread Carefully (backup plan)

sketch II

While footsteps have a nice percussive sound, they do get a little boring after a while. With Adafruit’s FLORA and a few Velostats, boots can be made for both walking and music-making. These shoes allow a person to add some more spring to their step, because with every step they take a random note is played. But the greater the force is applied to the sensor, the louder the sound becomes; so if the wearer wishes to be less conspicuous, they would have to tread carefully.

Swetha and Ticha-Bathroom Stall Counter

After hearing Golan’s story about the urinal timer in China, Swetha and I were inspired to do something that also involved bathrooms. Due to the awkwardness that surrounds the bathroom atmosphere and people’s acute concern for public hygiene, we decided to create a measuring device that interplays these two characteristics. Hence, the bathroom stall counter was created: this simple device tallies the number of people who have used a particular bathroom stall and displays the value on the SSD. To do this, we attached a magnet to the bathroom stall door and used a hall effect sensor to determine the magnet’s proximity, which allowed us to tell whether the stall was vacant or occupied.

This project is similar to the work of Adam Frelin ( http://adamfrelin.com/works/TapeFountains-11/ )  in which he dashes into public bathrooms and films himself creating interesting but inconvenient duck tape sculptures that change the function of the bathroom. We were inspired by his erratic but well-edited documentation and his way of interacting with this public space.

IMG_0856

 

Although Swetha did a wonderful job at laser-cutting boxes for our two circuits, our devices still ended up having a ghetto-looking appearance when we secured the parts with masking tape. So, we decided to overplay the ghetto-ness by adorning the boxes with graffiti-esque typography and doodles, which turned out pretty nicely and complemented the bathroom’s atmosphere. Unfortunately, we were not able to mount both of the boxes we made because the sensor would not work for one of them unless it was connected to the laptop.

 

Below are the fritzing diagram and the code:

arduino fritzing

 

 

const int hallPin = 2;
const int buttonPin = 9; 
int hallState = 0;        // value read from the pot

int buttonState = 0;
int Value = 0;
int prevValue = 0;

// Enable one of these two #includes and comment out the other.
// Conditional #include doesn't work due to Arduino IDE shenanigans.
#include  // Enable this line if using Arduino Uno, Mega, etc.
//#include  // Enable this line if using Adafruit Trinket, Gemma, etc.

#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"

Adafruit_7segment matrix = Adafruit_7segment();
uint16_t counter = 0;
uint16_t countPeeps = 0;

void setup() {
#ifndef __AVR_ATtiny85__ 
#endif
  matrix.begin(0x70);
  pinMode(hallPin, INPUT);
  pinMode(buttonPin, INPUT);
}

void loop() {
  hallState = digitalRead(hallPin);      
  buttonState = digitalRead(buttonPin);    
  // map it to the range of the analog out:
  // change the analog out value:
  if(buttonState == LOW){ 
   counter = 0; 
   countPeeps = 0;

  }  

  if (hallState == HIGH) {        
  	Value =10; 
  } 
  else {
    Value = 0; 
    if(prevValue == 10) { 
    	countPeeps++;
    }
  }

  prevValue = Value; 

  matrix.println(countPeeps);
  if(countPeeps == 0) {
  	matrix.writeDigitNum(4, 0, false);
  }
  matrix.writeDisplay();
  delay(10);
}

Ticha-Arduino Exercises

EDIT: NEVER MIND about the issue I had with #12 – it was really dumb…

In general, I found these exercises to be relatively enjoyable (albeit a little dull) and instructive in allowing me to get acquainted with the Arduino. I actually felt that the circuit diagrams were more useful than the pictorial representations because they clearly showed how the components related to each other.

Also I couldn’t find a way to properly document #12, so please assume that since it moved somehow, it must have worked…

Finally to prevent too many people from getting Rickrolled, I decided to use my own tune for #11. 😀

Ticha-LookingOutwards-Shields

1. Music Instrument Shield

Since I have been working a lot with sound lately, this shield would be useful for my audio-based projects in the future. The strength of this shield is that it “contains two large tonebanks including various piano, woodwinds, brass, synth, SFX and percussion sounds” and is “capable of playing several tones simultaneously” – giving the artist a great degree of freedom. The cost is also reasonable given the shield’s versatility.

2. Voice Recognition Shield

The voice recognition shield would be an effective complement to the music instrument shield above. This can create an interface between the viewer and the work, and coupled with the music instrument shield, can produce an engaging interactive piece relating to the dialogue between man and machine.

3. Gameduino

As someone who is an avid appreciator of video games I find this ‘gameduino’ to be an interesting shield I may use for future projects. The slightly primitive graphics is reminiscent of old-school Game Boy video games, which gives the shield a sense of charm.

Ticha-LookingOutwards-Sensors

1. Long Flex/Bend Sensor

According to the product description, the flex/bend sensor was popularized by Nintendo as a gaming interface for gloves. As the principal motion that our body makes is bending, the bend sensor offers many possibilities in which a device responds to body movements.

2. IR Distance Sensor

This sensor measures the proximity of objects. Despite its simplicity, it would be very useful for a number of projects – especially those involving robots or other machines that require methods of collision prevention.

3. Tilt Ball Switch

I was mostly drawn to this because it was dubbed “the poor man’s accelerometer” (and it’s only $2.00). Although it is not as powerful as a “rich man’s accelerometer”, its size makes it very convenient for small projects that require basic orientation/motion detection.

Ticha-Do you hear the birdie sing

My creature ended up not being an abstract or fantastical mystical one, but a relatively normal-looking robin that responds to the user’s voice. Lately, I’ve been doing a lot of work with the AudioInput library in processing because of the level of interactivity it offers between the user and the program. As someone who grew up in a musically oriented environment, I strongly believe that music is one of the best methods of bonding individuals. Thus, I created a robin that responds to singing but runs away when the input audio level exceeds a certain threshold. At the certain point, the robin will start to dance and even sing along once it has gained enough of the user’s trust.

I admit that I am still not completely satisfied with this project – I had spent so much time on trying to make the audio work and handling awkward boolean logic that I was not able to make the simulation as sophisticated as I wanted it to be. Nevertheless, this was a very valuable learning experience for me as it forced me to reason about my code more carefully and reacquainted me with good ol’ null pointer exceptions.

Dropbox link for the code because OpenProcessing hates me: https://www.dropbox.com/s/m1pbwk68zsvgkzz/singing_bird.zip

Ticha-Musical Stairs


(Note: as the audio is live, there is a little bit of background noise in the video that I was unable to edit out)

‘Musical Stairs’ is a project that examines the role of music in our mundane lives. It was partly inspired by ‘Casse’ by Andreas Gysin and Sidi Vanetti, which effectively employs sound to add appeal to their simple projection.

An old habit of mine is tapping my fingers on a desk or flat surface in a manner that imitates playing the piano. It is a habit I thought I had grown out of, but only recently resurfaced due to my frustration with not having easy access to a piano. Clearly, I am not the only one who enjoys using this ‘musical instrument surrogacy’ in the event of having idle hands – I have seen enough people drumming on chairs and playing table keyboards to know this for certain. My projection attempts to manifest this concept of using an everyday object as a surrogate for a specific musical instrument. In addition, it redefines the image of a staircase by recontextualizing it into a musical situation. The way the program works is simple: when the user clicks on an point on the screen a white ball is spawned, which changes color and plays a note pertaining to the step it makes contact with.

A potential extension of this would be to project on a variety of small areas and attributing different objects to different instruments. It would be interesting to use this notion to create an ‘interesting symphony of boring objects’.

I fortunately did not run into many problems during this assignment. Initially, my program could not play more than 7 notes before going completely mute. I had no idea what the problem was until Dave pointed out that I was creating a new AudioPlayer object each time I needed to play a sound; it was then that I realized I was doing a really really bad thing. A non-programming-related issue that I ran into involved particularly rude CS majors on the 9th floor who seemed to make a effort to disrupt my recording (even though I explicitly told them that I was working on a project).

Also thanks to Swetha and Jun for helping me with the awkward projector handling!

Improvements to be made:
– attribute a special event to colliding notes
– make notes more visually interesting