-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmidi_master_clock.ino
323 lines (272 loc) · 8.02 KB
/
midi_master_clock.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// =====================================
// Midi Master Clock
// Modified code for Arduino Mega 2560, Sparkfun MIDI shield and
// Arducam Liquid Crystal display.
// Original at: https://github.com/DieterVDW/arduino-midi-clock
// 12/16/2016
// Source: https://github.com/freonirons409/midi_master_clock
// Author: Aaron Irons ([email protected])
// Website: https://www.aaronirons.net
// =====================================
#include <TimerOne.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(49, 45, 35, 33, 31, 29);
#define TAP_PIN 2
#define TAP_PIN_POLARITY RISING
#define MINIMUM_TAPS 3
#define EXIT_MARGIN 150 // If no tap after 150% of last tap interval -> measure and set
#define DIMMER_INPUT_PIN A0
#define DIMMER_CHANGE_MARGIN 2
//#define DIMMER_CHANGE_PIN A1
#define DEAD_ZONE 50
#define CHANGE_THRESHOLD 5000
#define RATE_DIVISOR 30
#define BLINK_OUTPUT_PIN 7
#define BLINK_PIN_POLARITY 0 // 0 = POSITIVE, 255 - NEGATIVE
#define BLINK_TIME 4 // How long to keep LED lit in CLOCK counts (so range is [0,24])
#define SYNC_OUTPUT_PIN 9 // Can be used to drive sync analog sequencer (Korg Monotribe etc ...)
#define SYNC_PIN_POLARITY 0 // 0 = POSITIVE, 255 - NEGATIVE
#define START_STOP_INPUT_PIN 3
#define START_STOP_PIN_POLARITY 0 // 0 = POSITIVE, 1024 = NEGATIVE
#define MIDI_START 0xFA
#define MIDI_STOP 0xFC
#define DEBOUNCE_INTERVAL 500L // Milliseconds
#define EEPROM_ADDRESS 0 // Where to save BPM
#ifdef EEPROM_ADDRESS
#include <EEPROM.h>
#endif
#define MIDI_FORWARD
#define MIDI_TIMING_CLOCK 0xF8
#define CLOCKS_PER_BEAT 24
#define MINIMUM_BPM 40 // Used for debouncing
#define MAXIMUM_BPM 300 // Used for debouncing
long intervalMicroSeconds;
int bpm;
boolean initialized = false;
long minimumTapInterval = 60L * 1000 * 1000 / MAXIMUM_BPM;
long maximumTapInterval = 60L * 1000 * 1000 / MINIMUM_BPM;
int tapButton = 0;
int startStopPressed = 0;
volatile long firstTapTime = 0;
volatile long lastTapTime = 0;
volatile long timesTapped = 0;
volatile int blinkCount = 0;
int lastDimmerValue = 0;
boolean playing = false;
long lastStartStopTime = 0;
#ifdef DIMMER_CHANGE_PIN
long changeValue = 0;
#endif
void setup() {
// Set MIDI baud rate:
Serial.begin(31250);
//Serial.begin(74880);
// Set pin modes
#ifdef BLINK_OUTPUT_PIN
pinMode(BLINK_OUTPUT_PIN, OUTPUT);
#endif
#ifdef SYNC_OUTPUT_PIN
pinMode(SYNC_OUTPUT_PIN, OUTPUT);
#endif
#ifdef DIMMER_INPUT_PIN
pinMode(DIMMER_INPUT_PIN, INPUT);
#endif
#ifdef START_STOP_INPUT_PIN
pinMode(START_STOP_INPUT_PIN, INPUT);
#endif
pinMode(TAP_PIN, INPUT_PULLUP);
pinMode(START_STOP_INPUT_PIN, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(51, OUTPUT); //input on LCD, receiving pot pin value
pinMode(53, OUTPUT); //LCD pin 2, power supply for logic operating - 5.0v
pinMode(49, OUTPUT);
pinMode(47, OUTPUT);
pinMode(45, OUTPUT);
pinMode(35, OUTPUT);
pinMode(33, OUTPUT);
pinMode(31, OUTPUT);
pinMode(29, OUTPUT);
pinMode(27, OUTPUT); //LCD pin 15, power supply for backlight - 5.0v
pinMode(25, OUTPUT); //LCD pin 16, backlight grnd
pinMode(A8, OUTPUT); //Ground for Display pot
pinMode(A10, OUTPUT); //5v for Display pot
digitalWrite(53, HIGH);
digitalWrite(47, LOW);
digitalWrite(35, HIGH);
digitalWrite(33, HIGH);
digitalWrite(31, HIGH);
digitalWrite(29, HIGH);
digitalWrite(27, HIGH);
digitalWrite(25, LOW);
analogWrite(A8, 0);
analogWrite(A10, 255);
//init LCD
lcd.begin(16, 2);
// Print a message to the LCD.
lcd.print("MIDI CLOCK! v666");
// Get the saved BPM value from 2 stored bytes: MSB LSB
bpm = EEPROM.read(EEPROM_ADDRESS) << 8;
bpm += EEPROM.read(EEPROM_ADDRESS + 1);
if (bpm < MINIMUM_BPM || bpm > MAXIMUM_BPM) {
bpm = 120;
}
// Interrupt for catching tap events
attachInterrupt(digitalPinToInterrupt(TAP_PIN), tapInput, TAP_PIN_POLARITY);
// Attach the interrupt to send the MIDI clock and start the timer
Timer1.initialize(intervalMicroSeconds);
Timer1.setPeriod(calculateIntervalMicroSecs(bpm));
Timer1.attachInterrupt(sendClockPulse);
// Initialize dimmer value
#ifdef DIMMER_INPUT_PIN
// Initialize dimmer value
lastDimmerValue = analogRead(DIMMER_INPUT_PIN);
#endif
lcd.setCursor(0, 1);
lcd.print("BPM:");
lcd.setCursor(5, 1);
setDisplayValue(bpm);
//Serial.write(MIDI_STOP);
}
void loop() {
long now = micros();
#ifdef TAP_PIN
/*
* Handle tapping of the tap tempo button
*/
if (timesTapped > 0 && timesTapped < MINIMUM_TAPS && (now - lastTapTime) > maximumTapInterval) {
// Single taps, not enough to calculate a BPM -> ignore!
//Serial.println("Ignoring lone taps!");
timesTapped = 0;
} else if (timesTapped >= MINIMUM_TAPS) {
long avgTapInterval = (lastTapTime - firstTapTime) / (timesTapped-1);
if ((now - lastTapTime) > (avgTapInterval * EXIT_MARGIN / 100)) {
bpm = 60L * 1000 * 1000 / avgTapInterval;
updateBpm(now);
blinkCount = ((now - lastTapTime) * 24 / avgTapInterval) % CLOCKS_PER_BEAT;
timesTapped = 0;
}
}
#endif
#ifdef DIMMER_INPUT_PIN
/*
* Handle change of the dimmer input
*/
int curDimValue = analogRead(DIMMER_INPUT_PIN);
if (curDimValue > lastDimmerValue + DIMMER_CHANGE_MARGIN
|| curDimValue < lastDimmerValue - DIMMER_CHANGE_MARGIN) {
bpm = map(curDimValue, 0, 1024, MINIMUM_BPM, MAXIMUM_BPM);
updateBpm(now);
lastDimmerValue = curDimValue;
}
#endif
#ifdef DIMMER_CHANGE_PIN
int curDimValue = analogRead(DIMMER_CHANGE_PIN);
if (bpm > MINIMUM_BPM && curDimValue < (512 - DEAD_ZONE)) {
int val = (512 - DEAD_ZONE - curDimValue) / RATE_DIVISOR;
changeValue += val * val;
} else if (bpm < MAXIMUM_BPM && curDimValue > (512 + DEAD_ZONE)) {
int val = (curDimValue - 512 - DEAD_ZONE) / RATE_DIVISOR;
changeValue += val * val;
} else {
changeValue = 0;
}
if (changeValue > CHANGE_THRESHOLD) {
bpm += curDimValue < 512 ? -1 : 1;
updateBpm(now);
changeValue = 0;
}
#endif
#ifdef START_STOP_INPUT_PIN
/*
* Check for start/stop button pressed
*/
startStopPressed = digitalRead(START_STOP_INPUT_PIN);
if (startStopPressed == 0 && (lastStartStopTime + (DEBOUNCE_INTERVAL * 1000)) < now) {
startOrStop();
lastStartStopTime = now;
}
#endif
#ifdef MIDI_FORWARD
/*
* Forward received serial data
*/
while (Serial.available()) {
int b = Serial.read();
Serial.write(b);
}
#endif
}
void tapInput() {
long now = micros();
if (now - lastTapTime < minimumTapInterval) {
return; // Debounce
}
if (timesTapped == 0) {
firstTapTime = now;
}
timesTapped++;
lastTapTime = now;
}
void startOrStop() {
if (!playing) {
Serial.write(MIDI_START);
} else {
Serial.write(MIDI_STOP);
}
playing = !playing;
}
void sendClockPulse() {
// Write the timing clock byte
Serial.write(MIDI_TIMING_CLOCK);
blinkCount = (blinkCount+1) % CLOCKS_PER_BEAT;
if (blinkCount == 0) {
// Turn led on
#ifdef BLINK_OUTPUT_PIN
analogWrite(BLINK_OUTPUT_PIN, 255 - BLINK_PIN_POLARITY);
#endif
#ifdef SYNC_OUTPUT_PIN
// Set sync pin to HIGH
analogWrite(SYNC_OUTPUT_PIN, 255 - SYNC_PIN_POLARITY);
#endif
} else {
#ifdef SYNC_OUTPUT_PIN
if (blinkCount == 1) {
// Set sync pin to LOW
analogWrite(SYNC_OUTPUT_PIN, 0 + SYNC_PIN_POLARITY);
}
#endif
#ifdef BLINK_OUTPUT_PIN
if (blinkCount == BLINK_TIME) {
// Turn led on
analogWrite(BLINK_OUTPUT_PIN, 0 + BLINK_PIN_POLARITY);
}
#endif
}
}
void updateBpm(long now) {
// Update the timer
long interval = calculateIntervalMicroSecs(bpm);
Timer1.setPeriod(interval);
// Save the BPM
#ifdef EEPROM_ADDRESS
EEPROM.write(EEPROM_ADDRESS, bpm - 40);
//EEPROM.write(EEPROM_ADDRESS + 1, bpm % 256);
#endif
lcd.setCursor(0, 1);
lcd.print("BPM:");
setDisplayValue(bpm);
}
void setDisplayValue(int value) {
// value >= 1000 ? lcd.print(value / 1000) : 0x00;
// value >= 100 ? lcd.print((value / 100) % 10) : 0x00;
lcd.setCursor(5, 1);
lcd.print(value);
if(value < 100) {
lcd.setCursor(7, 1);
lcd.print(" ");
}
}
long calculateIntervalMicroSecs(int bpm) {
// Take care about overflows!
return 60L * 1000 * 1000 / bpm / CLOCKS_PER_BEAT;
}