Skip to content

Instantly share code, notes, and snippets.

@Aircoookie
Created April 18, 2021 23:10
Show Gist options
  • Save Aircoookie/deafeb9c0ee0c07d8ad60818300ba4a3 to your computer and use it in GitHub Desktop.
Save Aircoookie/deafeb9c0ee0c07d8ad60818300ba4a3 to your computer and use it in GitHub Desktop.
Aircoookie modification of MIDI Stepper motor driver Arduino sketch by jzkmath
#include <MIDI.h>
#include "pitches.h"
/*
* MIDI STEPPER +
*
* By Aircoookie
*
* Based on MIDI STEPPER V1 by Jonathan Kayne / jzkmath
* April 2018
* https://github.com/jzkmath/Arduino-MIDI-Stepper-Motor-Instrument
*
* Takes MIDI data and converts it to stepper music!
* Since the steppers only need to spin in one direction,
* you only need to control the STEP pin on the A4988 driver
* and pull DIR to either 5V or GND.
* You can add more stepper motors to the sketch by specifying more pins
* and expanding the array.
*/
#define NUM_STEPPERS 4
const byte stepPins[NUM_STEPPERS] = {2, 3, 4, 12};
const byte dirPins [NUM_STEPPERS] = {5, 6, 7, 13};
#define enPin 8 //Steppers are enabled when EN pin is pulled LOW
//If enough steppers are unused, one note can be assigned to multiple steppers based on velocity
#define MAX_STEPPERS_PER_NOTE 1 //supported values: 1, 2, 4, 8, recommended 2 for 4 steppers
//resonant pitch of stepper (recognizable horrible sound). Will be shifted by 1 octave up or down (0 to disable)
#define RESONANT_PITCH 50
unsigned long motorSpeeds[NUM_STEPPERS] = {0}; //holds the speeds of the motors.
unsigned long prevStepMicros[NUM_STEPPERS] = {0}; //last time
const bool motorDirection = LOW; //you can use this to change the motor direction, comment out if you aren't using it.
//bool disableSteppers = HIGH; //status of the enable pin. disabled when HIGH. Gets enabled when the first note on message is received.
MIDI_CREATE_DEFAULT_INSTANCE(); //use default MIDI settings
void setup() {
pinMode(enPin, OUTPUT);
digitalWrite(enPin, HIGH);
for (byte m = 0; m < NUM_STEPPERS; m++) {
pinMode(stepPins[m], OUTPUT);
pinMode(dirPins[m], OUTPUT);
digitalWrite(dirPins[m], motorDirection);
}
MIDI.begin(MIDI_CHANNEL_OMNI); //listen to all MIDI channels
MIDI.setHandleNoteOn(handleNoteOn); //execute function when note on message is recieved
MIDI.setHandleNoteOff(handleNoteOff); //execute function when note off message is recieved
//Serial.begin(115200); //allows for serial MIDI communication, comment out if using HIDUINO or LUFA
digitalWrite(enPin, LOW); //enable steppers.
delay(1);
p1s();
}
void p1s() {
/*for (uint32_t m = 0; m < NUM_STEPPERS; m++) {
int speed = 10000;
int start = micros();
int prev = micros();
while (micros() - start < 1000000) {
if (micros() - prev >= speed)
{ //step when correct time has passed and the motor is at a nonzero speed
digitalWrite(stepPins[m], HIGH);
prev = micros();
digitalWrite(stepPins[m], LOW);
}
}
}
digitalWrite(enPin, HIGH); //disable steppers.
delay(1);
*/
//motorSpeeds[0] = 15385; //C2
//motorSpeeds[1] = 12195; //E2
//motorSpeeds[2] = 10204; //G2
//motorSpeeds[3] = 7634; //C3
digitalWrite(enPin, HIGH);
}
void loop() {
MIDI.read(); //read MIDI messages
for (byte m = 0; m < NUM_STEPPERS; m++) {
if ((micros() - prevStepMicros[m] >= motorSpeeds[m]) && (motorSpeeds[m] != 0))
{ //step when correct time has passed and the motor is at a nonzero speed
digitalWrite(stepPins[m], HIGH);
prevStepMicros[m] += motorSpeeds[m];
digitalWrite(stepPins[m], LOW);
}
}
}
uint16_t fixResonance(byte pitch) {
if (pitch < RESONANT_PITCH -1 || pitch > RESONANT_PITCH +1) return pitch;
return (RESONANT_PITCH > 32) ? pitch - 12 : pitch + 12;
}
void handleNoteOn(byte channel, byte pitch, byte velocity) //MIDI Note ON Command
{
if (!velocity) handleNoteOff(0, pitch, 0);
#if RESONANT_PITCH > 0
pitch = fixResonance(pitch);
#endif
digitalWrite(enPin, LOW); //enable steppers.
#if MAX_STEPPERS_PER_NOTE == 1
byte numMotors = 1;
#elif MAX_STEPPERS_PER_NOTE == 2
byte numMotors = (velocity >> 6) + 1;
#elif MAX_STEPPERS_PER_NOTE == 4
byte numMotors = (velocity >> 5) + 1;
#elif MAX_STEPPERS_PER_NOTE == 8
byte numMotors = (velocity >> 4) + 1;
#else
//byte numMotors = (velocity / ((128/(MAX_STEPPERS_PER_NOTE -1))-1)) +1;
#error Unsupported value of MAX_STEPPERS_PER_NOTE (use 1, 2, 4, or 8)
#endif
byte motorsSet = 0;
//best case: find unused motor to play note, or any motor that already plays this note
for (byte m = 0; m < NUM_STEPPERS; m++) {
if (motorSpeeds[m] == 0) {
motorSpeeds[m] = pitchVals[pitch];
motorsSet++;
if (motorsSet == numMotors) return;
}
}
if (motorsSet) return; //do not "kill" other notes if at least one motor was set successfully
#if MAX_STEPPERS_PER_NOTE > 1
//next, check if two motors play the same note, switch one of them to the new note
for (byte m0 = 0; m0 < NUM_STEPPERS -1; m0++) {
for (byte m1 = m0; m1 < NUM_STEPPERS; m1++) {
if (motorSpeeds[m0] == motorSpeeds[m1]) {
motorSpeeds[m1] = pitchVals[pitch];
return;
}
}
}
#endif
//as a last resort, just replace the highest pitch sound, but only if new pitch is lower
uint16_t highest = UINT16_MAX, highestM = 0;
//search for lowest micros delay between steps
for (byte m = 0; m < NUM_STEPPERS; m++) {
if (motorSpeeds[m] > highest) {
highest = motorSpeeds[m];
highestM = m;
}
}
if (pitchVals[pitch] < highest) motorSpeeds[highestM] = pitchVals[pitch];
}
void handleNoteOff(byte channel, byte pitch, byte velocity) //MIDI Note OFF Command
{
#if RESONANT_PITCH > 0
pitch = fixResonance(pitch);
#endif
uint16_t speedVal = pitchVals[pitch];
bool disable = true; //disable steppers once the last stops playing
for (byte m = 0; m < NUM_STEPPERS; m++) {
if (motorSpeeds[m] == speedVal) motorSpeeds[m] = 0; //set motor speed to zero
if (motorSpeeds[m] > 0) disable = false;
}
digitalWrite(enPin, disable);
}
/*
pitches.h
Made By Jonathan Kayne
April 2018
This is an array of MIDI pitches and their respective speed values.
The index is the MIDI pitch value
The value stored is the time value between pulses on the stepper motor that produce the desired tone.
You can adjust these to tune your stepper motor.
Note that the duration is in microseconds, so 1 second = 1,000,000 microseconds.
Calculation:
pitchVal = 1000000/frequency
*/
const uint16_t pitchVals[] = {
0, //0, C-1
0, //1, C#-1
0, //2, D-1
0, //3, D#-1
0, //4, E-1
0, //5, F-1
0, //6, F#-1
0, //7, G-1
0, //8, G#-1
0, //9, A-1
0, //10, A#-1
0, //11, B-1
0, //12, C0
0, //13, C#0
0, //14, D0
0, //15, D#0
0, //16, E0
0, //17, F0
0, //18, F#0
0, //19, G0
0, //20, G#0
0, //21, A0
0, //22, A#0
32258, // 23 B0 31
30303, // 24 C1 33
28571, // 25 CS1 35
27027, // 26 D1 37
25641, // 27 DS1 39
24390, // 28 E1 41
22727, // 29 F1 44
21739, // 30 FS1 46
20408, // 31 G1 49
19230, // 32 GS1 52
18182, // 33 A1 55
17241, // 34 AS1 58
16129, // 35 B1 62
15385, // 36 C2 65
14493, // 37 CS2 69
13699, // 38 D2 73
12821, // 39 DS2 78
12195, // 40 E2 82
11494, // 41 F2 87
10753, // 42 FS2 93
10204, // 43 G2 98
9615, // 44 GS2 104
9091, // 45 A2 110
8547, // 46 AS2 117
8130, // 47 B2 123
7634, // 48 C3 131
7194, // 49 CS3 139
6803, // 50 D3 147
6410, // 51 DS3 156
6061, // 52 E3 165
5714, // 53 F3 175
5405, // 54 FS3 185
5102, // 55 G3 196
4808, // 56 GS3 208
4545, // 57 A3 220
4292, // 58 AS3 233
4049, // 59 B3 247
3817, // 60 C4 262
3610, // 61 CS4 277
3401, // 62 D4 294
3215, // 63 DS4 311
3030, // 64 E4 330
2865, // 65 F4 349
2703, // 66 FS4 370
2551, // 67 G4 392
2410, // 68 GS4 415
2273, // 69 A4 440
2146, // 70 AS4 466
2024, // 71 B4 494
1912, // 72 C5 523
1805, // 73 CS5 554
1704, // 74 D5 587
1608, // 75 DS5 622
1517, // 76 E5 659
1433, // 77 F5 698
1351, // 78 FS5 740
1276, // 79 G5 784
1203, // 80 GS5 831
1136, // 81 A5 880
1073, // 82 AS5 932
1012, // 83 B5 988
955, // 84 C6 1047
902, // 85 CS6 1109
851, // 86 D6 1175
803, // 87 DS6 1245
758, // 88 E6 1319
716, // 89 F6 1397
676, // 90 FS6 1480
638, // 91 G6 1568
602, // 92 GS6 1661
568, // 93 A6 1760
536, // 94 AS6 1865
506, // 95 B6 1976
478, // 96 C7 2093
451, // 97 CS7 2217
426, // 98 D7 2349
402, // 99 DS7 2489
379, // 100 E7 2637
358, // 101 F7 2794
338, // 102 FS7 2960
315, // 103 G7 3136
301, // 104 GS7 3322
284, // 105 A7 3520
268, // 106 AS7 3729
253, // 107 B7 3951
239, // 108 C8 4186
225, // 109 CS8 4435
213, // 110 D8 4699
201, // 111 DS8 4978
0, //112, E8
0, //113, F8
0, //114, F#8
0, //115, G8
0, //116, G#8
0, //117, A8
0, //118, A#8
0, //119, B8
0, //120, C9
0, //121, C#9
0, //122, D9
0, //123, D#9
0, //124, E9
0, //125, F9
0, //126, F#9
0, //127, G9
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment