Using arduino as an interactive music piece

Hi,
I am fairly new to arduino as I used my board for a University project a while ago but haven't used it since.
That old project involved light dependent resistors and torches to trigger samples in Max/MSP.

I am starting a new University project now (my final project, so it must 'pack a punch') and need advice on what hardware to use and pointing in the right direction to learn how to use it.

I hope to build an interactive piece of music using my arduino board.
The format will be many bits of hardware connected to my arduino board (duemilanove) giving values in Max/MSP which will trigger various features (start/stop, filter sweeps, sine waves or other additional tones, key changes, possibly jittering).
I hope to include LDRs with a swinging torch like my old piece, which I know how to do, however the rest I have never attempted before. I also realise my board has six analog inputs so I must limit myself, or by another board.

I hope to have a cup/bowl of water which would either start or stop the piece or do something more interesting like create a sine wave.
As well as this I hope to use radio to create a filter sweep (either with a swinging radio or with the 'performer' simply having the radio emitter attached to them so with every movement they make it affects the piece.
Other ideas (not being too complicated to perform) are welcome as I hope to create a quirky interactive piece of music that is easily used by a general member of the public and that will mean the piece of music chosen (possibly just a drum beat to start with) will never be the same piece of music twice.

It is quite an ambitious piece, however I'm eager to learn and the hardest part is finding the information needed to know what hardware to buy and how to connect it.
Any and all help is appreciated,
Thanks.
Alan

I also realise my board has six analog inputs so I must limit myself, or by another board.

No you can use analogue multiplexers like in this project:-
http://www.thebox.myzen.co.uk/Hardware/MIDI_Footsteps.html

also take a look at this:-
http://www.thebox.myzen.co.uk/Hardware/Pendulum.html

I hope to have a cup/bowl of water which would either start or stop the piece

Yes I have seen that done, just use the bowl and electrodes to measure the resistance of the water, add a little salt to make things more conductive.

Hi Alan

I can't give much help myself.

But i can recomend Sebastian Tomczak's blog here:

He has done a ton of musical projects, many of them involving Arduino, and he also built an instrument that makes music based on waves / ripples on water in a glass bowl.

Good luck with your project.

Hey,

I am also doing a project similar to yours. I am trying to hook up an accelerometer and have it communicate with max/msp to create music. Perhaps you or somebody could help me out...

I have the code that reads information from my accelerometer

const int groundpin = 18;             // analog input pin 4 -- ground
const int powerpin = 19;              // analog input pin 5 -- voltage
const int xpin = A3;                  // x-axis of the accelerometer
const int ypin = A2;                  // y-axis
const int zpin = A1;                  // z-axis (only on 3-axis models)

void setup()
{
  // initialize the serial communications:
  Serial.begin(9600);
  
  // Provide ground and power by using the analog inputs as normal
  // digital pins.  This makes it possible to directly connect the
  // breakout board to the Arduino.  If you use the normal 5V and
  // GND pins on the Arduino, you can remove these lines.
  pinMode(groundpin, OUTPUT);
  pinMode(powerpin, OUTPUT);
  digitalWrite(groundpin, LOW); 
  digitalWrite(powerpin, HIGH);
}

void loop()
{
  // print the sensor values:
  Serial.print(analogRead(xpin));
  // print a tab between values:
  Serial.print("\t");
  Serial.print(analogRead(ypin));
  // print a tab between values:
  Serial.print("\t");
  Serial.print(analogRead(zpin));
  Serial.println();
  // delay before next reading:
  delay(100);
}

I also have the Standard Firmata Code that lets my Arduino communicate with Max/msp.

#include <Firmata.h>
#include <Servo.h>

/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

/* analog inputs */
int analogInputsToReport = 0; // bitwise array to store pin reporting
int analogPin = 0; // counter for reading analog pins

/* digital pins */
byte reportPINs[TOTAL_PORTS];   // PIN == input port
byte previousPINs[TOTAL_PORTS]; // PIN == input port
byte pinStatus[TOTAL_DIGITAL_PINS]; // store pin status, default OUTPUT
byte portStatus[TOTAL_PORTS];

/* timer variables */
unsigned long currentMillis;     // store the current value from millis()
unsigned long nextExecuteMillis; // for comparison with currentMillis
int samplingInterval = 19;      // how often to run the main loop (in ms)

Servo servos[MAX_SERVOS];

/*==============================================================================
 * FUNCTIONS
 *============================================================================*/

void outputPort(byte portNumber, byte portValue)
{
  portValue = portValue &~ portStatus[portNumber];
  if(previousPINs[portNumber] != portValue) {
    Firmata.sendDigitalPort(portNumber, portValue);
    previousPINs[portNumber] = portValue;
  }
}

/* -----------------------------------------------------------------------------
 * check all the active digital inputs for change of state, then add any events
 * to the Serial output queue using Serial.print() */
void checkDigitalInputs(void)
{
  byte i, tmp;
  for(i=0; i < TOTAL_PORTS; i++) {
    if(reportPINs[i]) {
      switch(i) {
      case 0:
        outputPort(0, PIND &~ B00000011); // ignore Rx/Tx 0/1
        break;
      case 1:
        outputPort(1, PINB);
        break;
      case ANALOG_PORT:
        outputPort(ANALOG_PORT, PINC);
        break;
      }
    }
  }
}

// -----------------------------------------------------------------------------
/* sets the pin mode to the correct state and sets the relevant bits in the
 * two bit-arrays that track Digital I/O and PWM status
 */
void setPinModeCallback(byte pin, int mode) {
  byte port = 0;
  byte offset = 0;

  // TODO: abstract for different boards
  if (pin < 8) {
    port = 0;
    offset = 0;
  } else if (pin < 14) {
    port = 1;
    offset = 8;
  } else if (pin < 22) {
    port = 2;
    offset = 14;
  }
  
  if(pin > 1) { // ignore RxTx (pins 0 and 1)
    if (isServoSupportedPin(pin) && mode != SERVO)
      if (servos[pin - FIRST_SERVO_PIN].attached())
        servos[pin - FIRST_SERVO_PIN].detach();
    if(pin > 13) 
      reportAnalogCallback(pin - 14, mode == ANALOG ? 1 : 0); // turn on/off reporting
    switch(mode) {
    case ANALOG:
      digitalWrite(pin, LOW); // disable internal pull-ups and fall thru to 'case INPUT:'
    case INPUT:
      pinStatus[pin] = mode;
      pinMode(pin, INPUT);
      portStatus[port] = portStatus[port] &~ (1 << (pin - offset));
      break;
    case OUTPUT:
      digitalWrite(pin, LOW); // disable PWM and fall thru to 'case PWM:' 
    case PWM:
      pinStatus[pin] = mode;
      pinMode(pin, OUTPUT);
      portStatus[port] = portStatus[port] | (1 << (pin - offset));
      break;
    case SERVO:
      // TODO: Support Arduino Mega
      if (isServoSupportedPin(pin)) {
        pinStatus[pin] = mode;
        if (!servos[pin - FIRST_SERVO_PIN].attached())
          servos[pin - FIRST_SERVO_PIN].attach(pin);
      } else
        Firmata.sendString("Servo only on pins from 2 to 13");
      break;
    case I2C:
      pinStatus[pin] = mode;
      Firmata.sendString("I2C mode not yet supported");
      break;
    default:
      Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM
    }
    // TODO: save status to EEPROM here, if changed
  }
}

void analogWriteCallback(byte pin, int value)
{
  switch(pinStatus[pin]) {
  case SERVO:
    if (isServoSupportedPin(pin))
      servos[pin - FIRST_SERVO_PIN].write(value);
    break;
  case PWM:
    analogWrite(pin, value);
    break;
  }
}

void digitalWriteCallback(byte port, int value)
{
  switch(port) {
  case 0: // pins 2-7 (don't change Rx/Tx, pins 0 and 1)
    // 0xFF03 == B1111111100000011    0x03 == B00000011
    PORTD = (value &~ 0xFF03) | (PORTD & 0x03);
    break;
  case 1: // pins 8-13 (14,15 are disabled for the crystal) 
    PORTB = (byte)value;
    break;
  case 2: // analog pins used as digital
    byte pin;
    byte pinModeMask;
    for(pin=0; pin<8; pin++)
      if(pinStatus[pin] == OUTPUT)
        pinModeMask += 1 << pin;
    PORTC = (byte)value & pinModeMask;
    break;
  }
}

// -----------------------------------------------------------------------------
/* sets bits in a bit array (int) to toggle the reporting of the analogIns
 */
//void FirmataClass::setAnalogPinReporting(byte pin, byte state) {
//}
void reportAnalogCallback(byte pin, int value)
{
  if(value == 0) {
    analogInputsToReport = analogInputsToReport &~ (1 << pin);
  }
  else { // everything but 0 enables reporting of that pin
    analogInputsToReport = analogInputsToReport | (1 << pin);
  }
  // TODO: save status to EEPROM here, if changed
}

void reportDigitalCallback(byte port, int value)
{
  reportPINs[port] = (byte)value;
  if(port == ANALOG_PORT) // turn off analog reporting when used as digital
    analogInputsToReport = 0;
}

/*============================================
 * SYSEX-BASED commands
 *==========================================*/

void sysexCallback(byte command, byte argc, byte *argv)
{
  switch(command) {
  case SERVO_CONFIG:
    if(argc > 4) {
      // these vars are here for clarity, they'll optimized away by the compiler
      byte pin = argv[0];
      int minPulse = argv[1] + (argv[2] << 7);
      int maxPulse = argv[3] + (argv[4] << 7);

      if (isServoSupportedPin(pin)) {
        // servos are pins from 2 to 13, so offset for array
        if (servos[pin - FIRST_SERVO_PIN].attached())
          servos[pin - FIRST_SERVO_PIN].detach();
        servos[pin - FIRST_SERVO_PIN].attach(pin, minPulse, maxPulse);
        setPinModeCallback(pin, SERVO);
      }
    }
    break;
  case SAMPLING_INTERVAL:
    if (argc > 1)
      samplingInterval = argv[0] + (argv[1] << 7);
    else
      Firmata.sendString("Not enough data");
    break;
  }
}

boolean isServoSupportedPin(byte pin)
{
  return ((FIRST_SERVO_PIN <= pin) && (pin <= (FIRST_SERVO_PIN + MAX_SERVOS)));
}

/*===========================================
 * SETUP()
 *===========================================*/
void setup() 
{
  byte i;

  Firmata.setFirmwareVersion(2, 1);

  Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
  Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
  Firmata.attach(REPORT_ANALOG, reportAnalogCallback);
  Firmata.attach(REPORT_DIGITAL, reportDigitalCallback);
  Firmata.attach(SET_PIN_MODE, setPinModeCallback);
  Firmata.attach(START_SYSEX, sysexCallback);

  portStatus[0] = B00000011;  // ignore Tx/RX pins
  portStatus[1] = B11000000;  // ignore 14/15 pins 
  portStatus[2] = B00000000;

  for(i=0; i < FIRST_ANALOG_PIN; ++i) {
    setPinModeCallback(i,OUTPUT);
  }
  // set all outputs to 0 to make sure internal pull-up resistors are off
  PORTB = 0; // pins 8-15
  PORTC = 0; // analog port
  PORTD = 0; // pins 0-7

  // TODO rethink the init, perhaps it should report analog on default
  for(i=0; i<TOTAL_PORTS; ++i) {
    reportPINs[i] = false;
  }
  // TODO: load state from EEPROM here

  /* send digital inputs here, if enabled, to set the initial state on the
   * host computer, since once in the loop(), this firmware will only send
   * digital data on change. */
  if(reportPINs[0]) outputPort(0, PIND &~ B00000011); // ignore Rx/Tx 0/1
  if(reportPINs[1]) outputPort(1, PINB);
  if(reportPINs[ANALOG_PORT]) outputPort(ANALOG_PORT, PINC);

  Firmata.begin(57600);
}

/

My question is how to I combine the two codes so that the information being recorded by the accelerometer is being sent to Max/msp?

Thanks!
Mike

My question is how to I combine the two codes so that the information being recorded by the accelerometer is being sent to Max/msp?

The first thing to ask yourself is what is the first sketch doing that you can not do using firmata commands.

A review of the 1st sketch reveals this:

const int xpin = A3;                  // x-axis of the accelerometer
const int ypin = A2;                  // y-axis
const int zpin = A1;                  // z-axis (only on 3-axis models)

A3, A2, and A1 are aliases for analog pins being used as digital pins. Are you really connecting the accelerometer to the first 3 analog pins and expecting to read the data as though they were digital pins?

  Serial.print(analogRead(xpin));

Apparently not.

Aside from this, though, the only commands performed in loop() are analogRead(), Serial.print(), and delay().

Presumably, the delay is there to prevent flooding the Serial Monitor with data faster than it can read it, and the Serial.print() statements are there so that you can verify that the data was obtained/valid.

Therefore, the only thing that loop() is doing is reading each of 3 analog pins, which you can already do from Max/msp.

Grumpy_Mike:
Taking a look at the MIDI footsteps piece where more inputs were attatched to a single analog pin, am I right in saying that this pin can still only be used for one job?

For example if I connected two LDRs to one analog pin in the way shown in that piece Max/MSP would not be able to distinguish between the two LDRs signals coming in so they would be trigger the same thing?

MikMo:
Thanks for that link to the blog. I've looked at quite a few of his projects and they're really useful.

m.mike:
Apologies but I have no experience in that area (very little overall anyway).

General question.
Am I right in thinking that when I load a sketch onto my arduino, if I want it to be able to receive signals from different objects I am definitely able to load multiple sketches in, but will have to specify in the sketch what pins work with what signal?

If that isn't clear I mean if I have one LDR connected to an analog pin and a radio circuit connected to a different pin (like this piece http://interface.khm.de/index.php/lab/experiments/theremin-as-a-capacitive-sensing-device/ ), I want the LDR to trigger a sample when a certain value is reached and I want the radio circuit to control a filter (not produce a sine wave like in the link) can I load more than one sketch to tell the arduino to deal with these two seperate jobs?
I am definitely still learning the basics of how to use an arduino.

can I load more than one sketch to tell the arduino to deal with these two seperate jobs?

No you can only load one sketch into an arduino at any time.
Your sketch can of course look at different pins and do different things with them but it musty be one sketch. However it all happens so fast it appears like several things are happening at once.

You can't connect two things to the same pin. That is why you can use a multiplexer like in the MIDI footsteps to switch different things into the same analogue input. As your sketch controls the switching you know what device you are reading.

You can't connect two things to the same pin. That is why you can use a multiplexer like in the MIDI footsteps to switch different things into the same analogue input. As your sketch controls the switching you know what device you are reading.

So the sketch can distinguish between two different objects being plugged into the same pin? Meaning one pin can do two jobs? or are you saying it would only work if every signal going into that pin were doing the same job?

So the sketch can distinguish between two different objects being plugged into the same pin?

No if the two objects are physically connected to the same pin it will not work.
If however you have a multiplexer that switches the signal from one of two devices to one pin on the arduino then your sketch can distinguish between them because your sketch is in charge of the switching and knows what device it as switched to.

So the sketch can distinguish between two different objects being plugged into the same pin?

No if the two objects are physically connected to the same pin it will not work.
If however you have a multiplexer that switches the signal from one of two devices to one pin on the arduino then your sketch can distinguish between them because your sketch is in charge of the switching and knows what device it as switched to.

Brilliant, thanks for explaining that to me.