Created
April 18, 2021 23:10
-
-
Save Aircoookie/deafeb9c0ee0c07d8ad60818300ba4a3 to your computer and use it in GitHub Desktop.
Aircoookie modification of MIDI Stepper motor driver Arduino sketch by jzkmath
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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