Arduino Code
Arduino Code
MIDI_PsychoGalvanometer v026
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.
MIDIsprout.com
-------------*/
//******************************
//set scaled values, sorted array, first element scale length
const int scaleCount = 5;
const int scaleLen = 13; //maximum scale length plus 1 for 'used length'
int currScale = 0; //current scale, default Chrom
int scale[scaleCount][scaleLen] = {
{12,1,2,3,4,5,6,7,8,9,10,11,12}, //Chromatic
{7,1, 3, 5, 6, 8, 10, 12}, //Major
{7,1, 3, 4, 6, 8, 9, 11}, //DiaMinor
{7,1, 2, 2, 5, 6, 9, 11}, //Indian
{7,1, 3, 4, 6, 8, 9, 11} //Minor
};
byte timeout = 0;
int value = 0;
int prevValue = 0;
typedef struct _MIDImessage { //build structure for Note and Control MIDImessages
unsigned int type;
int value;
int velocity;
long duration;
long period;
int channel;
}
MIDImessage;
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);
pinMode(buttonPin, INPUT_PULLUP);
button.attach(buttonPin);
button.interval(5);
randomSeed(analogRead(0)); //seed for QY8 4 channel mode
Serial.begin(31250); //initialize at MIDI rate
//Serial.begin(9600); //for debugging
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
checkButton(); //its about to get really funky in here
if(index >= samplesize) { analyzeSample(); } //if samples array full, also checked
in analyzeSample(), call sample analysis
checkNote(); //turn off expired notes
checkControl(); //update control value
checkLED(); //LED management without delay()
if(currMenu>0) checkMenu(); //allow main loop by checking current menu mode, and
updating millis
break;
}
}
}
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;
if(noteLEDs == 1) rampDown(i, 0, 225);
if(noteLEDs == 2) rampDown(i+1, 0, 225); //special threshold display mode
}
}
}
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
void knobMode() {
//scroll through menus and select values using only a single knob
//keep dreamin' kid,
}
void checkLED(){
//iterate through LED array and call update
for (byte i = 0; i < LED_NUM; i++) {
LEDFader *led = &leds[i];
led->update();
}
}
void checkButton() {
button.update();
//read button, debounce if possible, and set menuMode
if(button.fell()) { //on button release
noteLEDs = 0; //turn off normal light show for menu modes
for(byte j=0;j<LED_NUM;j++) { leds[j].stop_fade(); leds[j].set_value(0); } //off
LEDs
switch(currMenu) {
case 0: //this is the main sprout program
prevValue = 0;
currMenu = 1;
previousMillis = currentMillis;
break;
case 1: //this is the main selection menu
//value is menu selected
switch(value) {
case 0:
thresholdMode(); //set change threshold multiplier
return;
break;
case 1:
scaleMode(); //set note scale
return;
break;
case 2:
channelMode(); //set MIDI output channel
return;
break;
case 3:
brightnessMode(); //set LED max brightness
return;
break;
case 4:
return;
break;
case 5:
return;
break;
default:
break;
}
break;
default:
break;
}
}
}
void checkMenu(){
//read the knob value, update LEDs, and wait for button press
value = analogRead(knobPin);
//scale knob value against number of menus
value = map(value, knobMin, knobMax, 0, menus); //value is now menu index
}
else { //no value change
}
switch(currMenu) {
case 0:
//this is the main sprout program
break;
case 1: //this is the main selection menu
noteLEDs = 0;
pulse(value,maxBrightness,pulseRate); //pulse for current menu
break;
case 2: //this is the submenu
noteLEDs = 0;
pulse(value,maxBrightness,(pulseRate/2)); //pulse for current menu
break;
default:
break;
}
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
button.update();
if(button.fell()) runMode = 0;
currentMillis = millis();
} //after button press retain threshold setting
currMenu = 0; //return to main program
noteLEDs = 1; //normal light show
leds[prevValue].stop_fade();
leds[prevValue].set_value(0);
}
void scaleMode() {
int runMode = 1;
int prevScale = 0;
while(runMode) {
currScale = analogRead(knobPin);
//set current Scale choice
currScale = map(currScale, knobMin, knobMax, 0, scaleCount);
pulse(value,maxBrightness,(pulseRate/2)); //pulse for current menu
pulse(currScale,maxBrightness,(pulseRate/4)); //display selected scale if
scaleCount <= 5
button.update();
if(button.fell()) runMode = 0;
currentMillis = millis();
} //after button press retain threshold setting
currMenu = 0; //return to main program
noteLEDs = 1; //normal light show
leds[prevValue].stop_fade();
leds[prevValue].set_value(0);
leds[currScale].stop_fade();
leds[currScale].set_value(0);
}
void channelMode() {
int runMode = 1;
while(runMode) {
channel = analogRead(knobPin);
//set current MIDI Channel between 1 and 16
channel = map(channel, knobMin, knobMax, 1, 17);
pulse(value,maxBrightness,(pulseRate/4)); //pulse for current menu
checkLED();
if(index >= samplesize) { analyzeSample(); } //keep samples running
checkNote(); //turn off expired notes
checkControl(); //update control value
button.update();
if(button.fell()) runMode = 0;
currentMillis = millis();
} //after button press retain threshold setting
currMenu = 0; //return to main program
noteLEDs = 1; //normal light show
leds[prevValue].stop_fade();
leds[prevValue].set_value(0);
}
void brightnessMode() {
int runMode = 1;
while(runMode) {
maxBrightness = analogRead(knobPin);
//set led maxBrightness
maxBrightness = map(maxBrightness, knobMin, knobMax, 1, 255);
button.update();
if(button.fell()) runMode = 0;
currentMillis = millis();
} //after button press retain threshold setting
currMenu = 0; //return to main program
if(maxBrightness > 1) noteLEDs = 1; //normal light show, unles lowest value
leds[prevValue].stop_fade();
leds[prevValue].set_value(0);
}
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;
//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, scale[currScale], 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
}