Welcome to the MIDI Sprout forum

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Topics - sam

Pages: 1 [2]
Development / MIDI Sprout Code
« on: March 17, 2016, 10:49:02 AM »
Below you will find the codebase for the MIDI Sprout.  You can easily edit the code using the Arduino IDE and upload to your sprout.  You can change the note range, apply a musical scale to the data, change the MIDI CC controller number, and even build your own change detection algorithms!

Download a zip file containing the Arduino sketch .ino files: MIDI Psychogalvanometer 328p v021

LEDFader library from jgillick, be sure to add this to your Arduino Libraries from the gitHub repository.

The onboard ATMEGA328 chip is clocked at 16Mhz, just like an Arduino Uno.  One easy way to reprogram your Sprout would be to upload your new Code to an Arduino Uno, carefully remove the IC from the Uno board and exchange with the chip on your Sprout!

The MIDI Sprout circuit board includes pads for ICSP (In Circuit Serial Programming), to facilitate copying code to the ATMEGA328 chip when using a programmer.

Below is the full codebase concatenated from the .ino files:
Code: [Select]
MIDI_PsychoGalvanometer v021
Accepts pulse inputs from a Galvanic Conductance sensor
consisting of a 555 timer set as an astablemultivibrator and two electrodes.
Through sampling pulse widths and identifying fluctuations, MIDI note and control messages
are generated.  Features include Threshold, Scaling, Control Number, and Control Voltage
using PWM through an RC Low Pass filter.

#include <LEDFader.h> //manage LEDs without delay() jgillick/arduino-LEDFader https://github.com/jgillick/arduino-LEDFader.git

//set scaled values, sorted array, first element scale length
int scaleMajor[]  = {7,1, 3, 5, 6, 8, 10, 12};
int scaleDiaMinor[]  = {7,1, 3, 4, 6, 8, 9, 11};
int scaleIndian[]  = {7,1, 2, 2, 5, 6, 9, 11};
int scaleMinor[]  = {7,1, 3, 4, 6, 8, 9, 11};
int scaleChrom[] = {12,1,2,3,4,5,6,7,8,9,10,11,12};
int *scaleSelect = scaleChrom; //initialize scaling
int root = 0; //initialize for root

const byte interruptPin = INT0; //galvanometer input
const byte knobPin = A0; //knob analog input

const byte samplesize = 10; //set sample array size
const byte analysize = samplesize - 1;  //trim for analysis array

const byte polyphony = 5; //above 8 notes may run out of ram
byte channel = 1;  //setting channel to 11 or 12 often helps simply computer midi routing setups
int noteMin = 36; //C2  - keyboard note minimum
int noteMax = 96; //C7  - keyboard note maximum
byte QY8= 0;  //sends each note out chan 1-4, for use with General MIDI like Yamaha QY8 sequencer
byte controlNumber = 80; //set to mappable control, low values may interfere with other soft synth controls!!
byte controlVoltage = 1; //output PWM CV on controlLED, pin 17, PB3, digital 11 *lowpass filter
long batteryLimit = 3000; //voltage check minimum, 3.0~2.7V under load; causes lightshow to turn off (save power)
byte checkBat = 1;

volatile unsigned long microseconds; //sampling timer
volatile byte index = 0;
volatile unsigned long samples[samplesize];

float threshold = 2.3;  //change threshold multiplier
float threshMin = 1.61; //scaling threshold min
float threshMax = 3.71; //scaling threshold max
float knobMin = 1;
float knobMax = 1023;

unsigned long previousMillis = 0;
unsigned long currentMillis = 1;
unsigned long batteryCheck = 0; //battery check delay timer
#define LED_NUM 6
LEDFader leds[LED_NUM] = { // 6 LEDs (perhaps 2 RGB LEDs)
  LEDFader(11)  //Control Voltage output or controlLED
int ledNums[LED_NUM] = {3,5,6,9,10,11};
byte controlLED = 5; //array index of control LED (CV out)
byte noteLEDs = 1;  //performs lightshow set at noteOn event

typedef struct _MIDImessage { //build structure for Note and Control MIDImessages
  unsigned int type;
  int value;
  int velocity;
  long duration;
  long period;
  int channel;
MIDImessage noteArray[polyphony]; //manage MIDImessage data as an array with size polyphony
int noteIndex = 0;
MIDImessage controlMessage; //manage MIDImessage data for Control Message (CV out)

void setup()
  pinMode(knobPin, INPUT);
  randomSeed(analogRead(0)); //seed for QY8
  Serial.begin(31250);  //initialize at MIDI rate
  controlMessage.value = 0;  //begin CV at 0
  //MIDIpanic(); //dont panic, unless you are sure it is nessisary
  checkBattery(); // shut off lightshow if power is too low
  if(noteLEDs) bootLightshow(); //a light show to display on system boot
  attachInterrupt(interruptPin, sample, RISING);  //begin sampling from interrupt

void loop()
  currentMillis = millis();   //manage time
  checkBattery(); //on low power, shutoff lightShow, continue MIDI operation
  checkKnob(); //check knob value
  if(index >= samplesize)  { analyzeSample(); }  //if samples array full, also checked in analyzeSample(), call sample analysis   
  checkNote();  //turn off expired notes
  checkControl();  //update control value
  //checkButton();  //not implemented in this build
  checkLED();  //LED management without delay()
  previousMillis = currentMillis;   //manage time

void setNote(int value, int velocity, long duration, int notechannel)
  //find available note in array (velocity = 0);
  for(int i=0;i<polyphony;i++){
      //if velocity is 0, replace note in array
      noteArray[i].type = 0;
      noteArray[i].value = value;
      noteArray[i].velocity = velocity;
      noteArray[i].duration = currentMillis + duration;
      noteArray[i].channel = notechannel;
        if(QY8) { midiSerial(144, notechannel, value, velocity); }
        else { midiSerial(144, channel, value, velocity); }

          for(byte j=0; j<(LED_NUM-1); j++) {   //find available LED and set
            if(!leds[j].is_fading()) { rampUp(i, 255, duration);  break; }


void setControl(int type, int value, int velocity, long duration)
  controlMessage.type = type;
  controlMessage.value = value;
  controlMessage.velocity = velocity;
  controlMessage.period = duration;
  controlMessage.duration = currentMillis + duration; //schedule for update cycle

void checkControl()
  //need to make this a smooth slide transition, using high precision
  //distance is current minus goal
  signed int distance =  controlMessage.velocity - controlMessage.value;
  //if still sliding
  if(distance != 0) {
    //check timing
    if(currentMillis>controlMessage.duration) { //and duration expired
        controlMessage.duration = currentMillis + controlMessage.period; //extend duration
        //update value
       if(distance > 0) { controlMessage.value += 1; } else { controlMessage.value -=1; }
       //send MIDI control message after ramp duration expires, on each increment
       midiSerial(176, channel, controlMessage.type, controlMessage.value);
        //send out control voltage message on pin 17, PB3, digital 11
        if(controlVoltage) { if(distance > 0) { rampUp(controlLED, map(controlMessage.value, 0, 127, 0 , 255), 5); }
                                            else { rampDown(controlLED, map(controlMessage.value, 0, 127, 0 , 255), 5); }

void checkNote()
  for (int i = 0;i<polyphony;i++) {
    if(noteArray[i].velocity) {
      if (noteArray[i].duration <= currentMillis) {
        //send noteOff for all notes with expired duration   
          if(QY8) { midiSerial(144, noteArray[i].channel, noteArray[i].value, 0); }
          else { midiSerial(144, channel, noteArray[i].value, 0); }
        noteArray[i].velocity = 0;
        rampDown(i, 0, 225);


void MIDIpanic()
  //120 - all sound off
  //123 - All Notes off
 // midiSerial(21, panicChannel, 123, 0); //123 kill all notes
  //brute force all notes Off
  for(byte i=1;i<128;i++) {
    delay(1); //don't choke on note offs!
    midiSerial(144, channel, i, 0); //clear notes on main channel

       if(QY8){ //clear on all four channels
         for(byte k=1;k<5;k++) {
          delay(1); //don't choke on note offs!
          midiSerial(144, k, i, 0);

void midiSerial(int type, int channel, int data1, int data2) {

  cli(); //kill interrupts, probably unnessisary
    //  Note type = 144
    //  Control type = 176
    // remove MSBs on data
data1 &= 0x7F;  //number
data2 &= 0x7F;  //velocity

byte statusbyte = (type | ((channel-1) & 0x0F));

  sei(); //enable interrupts

void checkKnob() {
  //float knobValue
  threshold = analogRead(knobPin); 
  //set threshold to knobValue mapping
  threshold = mapfloat(threshold, knobMin, knobMax, threshMin, threshMax);

void knobMode() {
  //scroll through menus and select values using only a single knob
  //keep dreamin' kid,

void rampUp(int ledPin, int value, int time) {
LEDFader *led = &leds[ledPin];
// led->set_value(0);
  led->fade(value, time); 

void rampDown(int ledPin, int value, int time) {     
  LEDFader *led = &leds[ledPin];
 // led->set_value(255); //turn on
  led->fade(value, time); //fade out

void checkLED(){
//iterate through LED array and call update 
 for (byte i = 0; i < LED_NUM; i++) {
    LEDFader *led = &leds[i];

void checkButton() {
  //no button in this build...

long readVcc() {  //https://code.google.com/p/tinkerit/wiki/SecretVoltmeter
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;

void checkBattery(){
  //check battery voltage against internal 1.1v reference
  //if below the minimum value, turn off the light show to save power
  //don't check on every loop, settle delay in readVcc() slows things down a bit
 if(batteryCheck < currentMillis){
  batteryCheck = currentMillis+10000; //reset for next battery check
  if(readVcc() < batteryLimit) {   //if voltage > valueV
    //battery failure 
    if(checkBat) { //first battery failure
      for(byte j=0;j<LED_NUM;j++) { leds[j].stop_fade(); leds[j].set_value(0); }  //reset leds, power savings
      noteLEDs = 0;  //shut off lightshow set at noteOn event, power savings
      checkBat = 0; //update, first battery failure identified
    } else { //not first low battery cycle
      //do nothing, lights off indicates low battery
      //MIDI continues to flow, MIDI data eventually garbles at very low voltages
      //some USB-MIDI interfaces may crash due to garbled data

void bootLightshow(){
 //light show to be displayed on boot
  for (byte i = 5; i > 0; i--) {
    LEDFader *led = &leds[i-1];
//    led->set_value(200); //set to max

    led->fade(200, 150); //fade up
    while(led->is_fading()) checkLED();

    led->fade(0,150+i*17);  //fade down
    while(led->is_fading()) checkLED();
   //move to next LED

//provide float map function
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;

//debug SRAM memory size
int freeRAM() {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
} // print free RAM at any point

//interrupt timing sample array
void sample()
  if(index < samplesize) {
    samples[index] = micros() - microseconds;
    microseconds = samples[index] + microseconds; //rebuild micros() value w/o recalling
    //micros() is very slow
    //try a higher precision counter
    //samples[index] = ((timer0_overflow_count << 8) + TCNT0) - microseconds;
    index += 1;

void analyzeSample()
  //eating up memory, one long at a time!
  unsigned long averg = 0;
  unsigned long maxim = 0;
  unsigned long minim = 100000;
  float stdevi = 0;
  unsigned long delta = 0;
  byte change = 0;

  if (index = samplesize) { //array is full
    unsigned long sampanalysis[analysize];
    for (byte i=0; i<analysize; i++){
      //skip first element in the array
      sampanalysis[i] = samples[i+1];  //load analysis table (due to volitle)
      //manual calculation
      if(sampanalysis[i] > maxim) { maxim = sampanalysis[i]; }
      if(sampanalysis[i] < minim) { minim = sampanalysis[i]; }
      averg += sampanalysis[i];
      stdevi += sampanalysis[i] * sampanalysis[i];  //prep stdevi

    //manual calculation
    averg = averg/analysize;
    stdevi = sqrt(stdevi / analysize - averg * averg); //calculate stdevu
    if (stdevi < 1) { stdevi = 1.0; } //min stdevi of 1
    delta = maxim - minim;
    //**********perform change detection
    if (delta > (stdevi * threshold)){
      change = 1;
    if(change){// set note and control vector
       int dur = 150+(map(delta%127,1,127,100,2500)); //length of note
       int ramp = 3 + (dur%100) ; //control slide rate, min 25 (or 3 ;)
       int notechannel = random(1,5); //gather a random channel for QY8 mode
       //set scaling, root key, note
       int setnote = map(averg%127,1,127,noteMin,noteMax);  //derive note, min and max note
       setnote = scaleNote(setnote, scaleSelect, root);  //scale the note
       // setnote = setnote + root; // (apply root?)
       if(QY8) { setNote(setnote, 100, dur, notechannel); } //set for QY8 mode
       else { setNote(setnote, 100, dur, channel); }
       //derive control parameters and set   
       setControl(controlNumber, controlMessage.value, delta%127, ramp); //set the ramp rate for the control
     //reset array for next sample
    index = 0;

int scaleSearch(int note, int scale[], int scalesize) {
 for(byte i=1;i<scalesize;i++) {
  if(note == scale[i]) { return note; }
  else { if(note < scale[i]) { return scale[i]; } } //highest scale value less than or equal to note
  //otherwise continue search
 //didn't find note and didn't pass note value, uh oh!
 return 6;//give arbitrary value rather than fail

int scaleNote(int note, int scale[], int root) {
  //input note mod 12 for scaling, note/12 octave
  //search array for nearest note, return scaled*octave
  int scaled = note%12;
  int octave = note/12;
  int scalesize = (scale[0]);
  //search entire array and return closest scaled note
  scaled = scaleSearch(scaled, scale, scalesize);
  scaled = (scaled + (12 * octave)) + root; //apply octave and root
  return scaled;

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.


Kit Instructions / MIDI Sprout Kit Assembly Instructions
« on: March 15, 2016, 09:45:57 PM »
    Instructions for assembling your MIDI Sprout Kit can be found at this

(you can find a super high resolution version of the instructions here!)

The assembly instructions provide step by step directions to build your MIDI Sprout Kit, showing component placement and wire connections on a breadboard. 

Please find below, three videos detailing the assembly of the MIDI Sprout Kit:
1. MIDI Sprout Kit Parts (Part 1) -

2. MIDI Sprout Kit Galvanometer (Part 2) -

3. MIDI Sprout Kit Final Build (Part 3) -

Parts List:

  • ATMEGA 328-PU preProgrammed with the MIDI Sprout software
  • LMC555 Timer
  • 10k Potentiometer (linear)
  • 47uF Electrolytic Capacitor
  • 0.1uF Capacitor (yellow)
  • 0.0042uF Capacitor (blue)
  • 100k Resistor (Brown Black Yellow)
  • 220 ohm Resistors x 7 (Red Red Purple)
  • 16 Mhz Oscillator
  • LEDs (2 Red, Yellow, Green, Blue, White)
  • MIDI 5 pin Connector
  • 1/8 input Jack (with 2 pin Header)
  • Solderless Breadboard
  • Snap Electrode Leads
  • Snap Electrode Pad Pairs x 3
  • Battery Pack (3 AA)
  • Jumper Wires x 20 (assorted)

Each kit comes with a preprogrammed ATMEGA328 microprocessor running at 16Mhz - more information about the MIDI Sprout Code can be found here.


Development / Battery Life
« on: March 21, 2015, 04:46:40 PM »
MIDIsprout Battery Life Testing

When experiencing low battery power, the MIDIsprout goes into a 'Low Power Mode' in order to ensure uninterrupted performance.  Low Power mode turns off the LED Light show, saving power normally used as a part of the display.  At low power levels, it is increasingly possible for microcontrollers to produce MIDI data errors, by conserving power we can ensure that the MIDIsprout functions for many additional hours.

When using the sprout, if MIDI data is seen streaming out of the device (to a computer or synth)  but the LED light show does not illuminate, this indicates that the batteries are running low and that the sprout has kicked into Low Power mode.  Utilizing 3 AA batteries, full power provides 4.5V to the sprout. This voltage varies under load as the sprout detects microcurrent fluctations, outputs MIDI, and performs the light show.  The MIDIsprout can self detect its internal voltage (very cool SecretVoltmeter), when the system detects a sag below 2.7V under load the Low Power mode is engaged shutting off the LED Lights.

Normal MIDIsprout Usage: Attaching a MIDI sprout to a plant's leaves (Snake Plant, Philodentron, etc) using electrodes and connecting the sprout to a computer or synthesizer is considered 'Normal Usage'.  While MIDIsprout probes can be attached to people, plants, apples, or anything that conducts, the sprout is tuned specifically for sonifiying biodata from plants.  Battery life will vary depending on your application and usage. 

All values listed in this exploration of Battery Life refer to 'Normal' usage consisting of a Snake Plant and USB MIDI interface to a laptop.  All times are listed in Hours.

(0.00) MIDIsprout connected to snake plant using a pair of gel Tens electrodes and macbook pro with USB-MIDI interface.  I am monitoring MIDI data using a custom build of MIDIsprout_Tools, keeping an eye on the activity of note data and watching out for any MIDI data errors.  The USB-MIDI interface utilized doesn't appear to be ground isolated, which may present some noise and loading issues.  I've also seen artifacts in the past present in the MIDI data when 'touching' the body of a macbook due to non-optoisolated MIDI interfaces (particularly during periods of low plant activity).

(64.4)  The interface has been rock solid over the first few days of spring, with good temperatures and sunshine.  No MIDI data errors have been identified.  The plant has shown normal high/low activity shifts from day thru evening.  Touching the plant elicits the normal 'flourish' response.

(86.5) The system has now been operational for over 3 days of uninterrupted activity!

(90.3) Power Saving Mode ... engaged
In the later evening of day 4, power saving mode finally kicks in.  While the plant had been showing 'low' activity levels (appearing as few infrequent notes - a flat line), upon activation of Low Power Mode I am also noting an odd reaction when touching the laptop (USB MIDI grounding).  Unsure if this is at all related to Low Power mode, or if the plant is simply sensitive to the grounding change in the later evening hours.

As this was the first detection of low power, I decided to cycle the power (turn it off and back on).  The LED light show turns back on and activity is seen, still relatively low activity (normal for evening)

(91.56) Power Saving Mode ... Again!
After about two hours from the first activiation of Power Saving Mode, the sprout has again detected a critical voltage level and shut off the LED light show.  At this point, its rather late at night, and i noticed the data was producing a very small number of notes tightly clustered. When touching the laptop body, flourshes are seen in the note data.  I got up to check/adjust the electrodes, and saw normal activity before I touched the plant.
    - I am thinking that since the voltage has steadily decreased over days with the leds on, the voltage was sagging way low on average.  Then when the LEDs are turned off the average available voltage increases.  This would result in a net increase in voltage to the electrodes.  The plant may react to this sudden change and result in low activity. It has become obvious, from touching the laptop and seeing a reaction in the data that the USB MIDI interface is not completely isolated.

(92.21) power saving mode
    flatline same as above,rest again - leds illuminate, normal activity
(95.96) still in low power, normal activity ...
   -obviously a change when in low power mode that the plant reacts to, gets used to...

(96.56) a return to moderation, touching laptop body results in diverse signal.  note i had been using the keyboard for a while.  perhaps my touching creates a disturbance in the current.  again MIDI in ground must be coupled to usb ground ... should test!

(109.68) after all night alone in low power mode, normal activity seen in morning.  no errors.  amazing!

... not much activity, its the first day of spring, and its snowing ...

(161.0) the plant has been flatlined for about two days.  MIDI still flows and there have been no errors.  when touching the plant, normal excitation is seen, suggesting that the MIDIsprout is still working correctly.  I have noticed a steady increase in ground sensitivity, touching the macbook body does not consistently make an effect.  unplugging the laptop increases sensitivity, but the plant still shows flat.  I have decided to remove the electrodes and take the plant outside for some spring sun. 

this experiment is concluded.

    after leaving the plant outside for the first day of spring, i have again attached the trodes to the plant and usb interface, using the same low level batteries.  some activity with the laptop unplugged.  flatline slow notes when charging.  touching plugged in laptop has minimal effect.
     now i tried new batteries to see how the plant reacts with the difference in charge: lots of activity and lightshow.  Perhaps the charge, the galvanometer, and the threshold have a relationship.  i should test low power mode (no leds) with full power batteries to see if the change in activity is related to charge, or perhaps pumping from the LEDs introduces noise (tickle).

Battery Life - Low Power Mode and isolation considerations

Development / Experience Level
« on: March 21, 2015, 11:39:19 AM »
Please tell us about your experience building and using Electronics, as well as your comfort level with MIDI synthesizers and computer based music!

Development / Forum Categories
« on: March 19, 2015, 04:14:27 PM »
General Discussion

Kit Assembly
   Breadboard kit
   Solder Dev Kit
      -board files
      -code base
   Arduino Shield
   Galvanometer circuit and build instructions
   Kit Purchasing – solder circuit board purchasing – parts purchasing
        enclosures - Shapeways 3d print (battery connects), laser cut designs

   Hardware Modifications
      CV, MIDI pins

   Instructions for MIDIsprout usage
   Explanation of common Sprout outputs
      Plant type, time of day, electrode type, proximity and movement
      MIDI opto-isolation (coupled vs isolated)
   User Support Group (User Questions)
   User Stories, experiences, audio, events, and images from Users
   Uses Sonifying Plants, apples, animals, and other systems   

   Biodata sonification history

   MIDI education
   Circuit education
   PD/Max education
   Live education
   OSx MIDI and IAC
   Windows MIDI and Ox/Yolk
   MIDIsprout Tools  - options, configuration, feedback

Kit Instructions / MIDIsprout Kit Build Discussion
« on: March 18, 2015, 09:45:19 PM »
Use this part of the forum to discuss you experiences building your MIDI Sprout kit.  If you encounter any challenges, the forum is the best place to look for help!

Pages: 1 [2]