Dual AY-3-8910 MIDI module
For the last couple years I've been fascinated by MIDI and its ability to both play music in real time and record the signalling for later playback and refinement. That's all well and good but wheres the fun in just buying off the shelf equipment when you could make your own? Specifically I'm calking about MIDI controlled "sound modules" that take a MIDI input, and output sound. Often you would use MIDI to control a synthesizer, electric piano, or some other purpose built instrument; but why not programmable sound generators (PSG) like an AY-3-8910 from General Instruments!
There are several guides and hundreds of other people that have done this previously with a single chip but I want to control two chips at once. A single 8910 has 3 output channels, so it can play 3 notes at once. With a bit of work, and the correct timing, multiple chips can be used together to increase the note count.
My work below is a derivative of the code and design by TheSpodShed on Instructables available here: https://www.instructables.com/Arduino-MIDI-Chiptune-Synthesizer/
Theory
The AY-3-9810 is relatively simple to work with requiring only a data bus, a couple control lines, a 1 MHz clock, and reset. The datasheet (available here) describes the connectivity requirements in detail. The code by TheSpodShed has a neat feature where if more than 3 notes are played at once one of the middle notes will be dropped while the highest and lowest notes continue to play. This does a pretty good job of getting around the note limitation but it isn't without its faults.
To control two chips the data bus, clock, and reset can be shared but we need two more I/O pins on the Arduino (more on this later).
The circuit
The entire circuit is powered by over USB and the Arduino generates the 1 MHz clock for simplicity's sake.
The AY-3-8910 datasheet provides a reduced control scheme to save an I/O pin but I was unable to get this work reliably. I instead came up with an alternative reduced control scheme.
The original control scheme can be reduced to a much simpler to use method where BC1 is grounded and READ is never used. This allows the Arduino to change one output at a time to transition between states removing the need for precise timing.
BDIR | BC2 | BC1 | Function |
---|---|---|---|
0 | 0 | 0 | INACTIVE |
1 | 1 | 0 | WRITE |
1 | 0 | 0 | LATCH |
Code
You will need the Arduino MIDI library installed as well as the USBMIDI library if you want to use USB.
1 /*
2 * [Header removed]
3 */
4
5 // Uncomment to enable traiditonal serial midi
6 #define SERIALMIDI
7
8 // Uncomment to enable USB midi
9 #define USBMIDI
10
11 // Uncomment to enable debugging output over USB serial
12 //#define DEBUG
13
14 #include <avr/io.h>
15
16 #ifdef USBMIDI
17 #include "MIDIUSB.h"
18 #endif
19
20 // We borrow one struct from MIDIUSB for traditional serial midi, so define it if were not using USBMIDI
21 #ifndef MIDIUSB_h
22 typedef struct
23 {
24 uint8_t header;
25 uint8_t byte1;
26 uint8_t byte2;
27 uint8_t byte3;
28 } midiEventPacket_t;
29 #endif
30
31 #ifdef SERIALMIDI
32 #include <MIDI.h>
33 MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);//Serial1 on pro micro
34 #endif
35
36 typedef unsigned short int ushort;
37
38 typedef unsigned char note_t;
39 #define NOTE_OFF 0
40
41 typedef unsigned char midictrl_t;
42
43 // Pin driver ---------------------------------------------
44
45 static const int dbus[8] = { 2, 3, 4, 5, 6, 7, 8, 10 };
46
47 static const ushort
48 BC2_A = 16,
49 BDIR_A = 14,
50 BC2_B = A0,
51 BDIR_B = A1,
52 nRESET = 15,
53 clkOUT = 9;
54
55 static const ushort DIVISOR = 7; // Set for 1MHz clock
56
57 static void clockSetup() {
58 // Timer 1 setup for Mega32U4 devices
59 //
60 // Use CTC mode: WGM13..0 = 0100
61 // Toggle OC1A on Compare Match: COM1A1..0 = 01
62 // Use ClkIO with no prescaling: CS12..0 = 001
63 // Interrupts off: TIMSK0 = 0
64 // OCR0A = interval value
65
66 TCCR1A = (1 << COM1A0);
67 TCCR1B = (1 << WGM12) | (1 << CS10);
68 TCCR1C = 0;
69 TIMSK1 = 0;
70 OCR1AH = 0;
71 OCR1AL = DIVISOR; // NB write high byte first
72 }
73
74 static void setData(unsigned char db) {
75 unsigned char bit = 1;
76 for (ushort i = 0; i < 8; i++) {
77 digitalWrite(dbus[i], (db & bit) ? HIGH : LOW);
78 bit <<= 1;
79 }
80 }
81
82 static void writeReg_A(unsigned char reg, unsigned char db) {
83 // This is a bit of an odd way to do it, BC1 is kept low and NACT, BAR, IAB, and DWS are used.
84 // BC1 is kept low the entire time.
85
86 // Inactive (BDIR BC2 BC1 0 0 0)
87 digitalWrite(BDIR_A, LOW);
88 digitalWrite(BC2_A, LOW);
89
90 //Set register address
91 setData(reg);
92
93 // BAR (Latch) (BDIR BC2 BC1 1 0 0)
94 digitalWrite(BDIR_A, HIGH);
95
96 // Inactive (BDIR BC2 BC1 0 0 0)
97 digitalWrite(BDIR_A, LOW);
98
99 //Set register contents
100 setData(db);
101
102 // Write (BDIR BC2 BC1 1 1 0)
103 digitalWrite(BC2_A, HIGH);
104 digitalWrite(BDIR_A, HIGH);
105
106 // Inactive (BDIR BC2 BC1 0 0 0)
107 digitalWrite(BC2_A, LOW);
108 digitalWrite(BDIR_A, LOW);
109 }
110
111 static void writeReg_B(unsigned char reg, unsigned char db) {
112 // This is a bit of an odd way to do it, BC1 is kept low and NACT, BAR, IAB, and DWS are used.
113 // BC1 is kept low the entire time.
114
115 // Inactive (BDIR BC2 BC1 0 0 0)
116 digitalWrite(BDIR_B, LOW);
117 digitalWrite(BC2_B, LOW);
118
119 //Set register address
120 setData(reg);
121
122 // BAR (Latch) (BDIR BC2 BC1 1 0 0)
123 digitalWrite(BDIR_B, HIGH);
124
125 // Inactive (BDIR BC2 BC1 0 0 0)
126 digitalWrite(BDIR_B, LOW);
127
128 //Set register contents
129 setData(db);
130
131 // Write (BDIR BC2 BC1 1 1 0)
132 digitalWrite(BC2_B, HIGH);
133 digitalWrite(BDIR_B, HIGH);
134
135 // Inactive (BDIR BC2 BC1 0 0 0)
136 digitalWrite(BC2_B, LOW);
137 digitalWrite(BDIR_B, LOW);
138 }
139
140 // AY-3-8910 driver ---------------------------------------
141
142 class PSGRegs {
143 public:
144 enum {
145 TONEALOW = 0,
146 TONEAHIGH,
147 TONEBLOW,
148 TONEBHIGH,
149 TONECLOW,
150 TONECHIGH,
151 NOISEGEN,
152 MIXER,
153
154 TONEAAMPL,
155 TONEBAMPL,
156 TONECAMPL,
157 ENVLOW,
158 ENVHIGH,
159 ENVSHAPE,
160 IOA,
161 IOB
162 };
163
164 unsigned char regs_A[16];
165 unsigned char regs_B[16];
166
167 unsigned char lastregs_A[16];
168 unsigned char lastregs_B[16];
169
170 void init() {
171 for (int i = 0; i < 16; i++) {
172 regs_A[i] = lastregs_A[i] = 0xFF;
173 writeReg_A(i, regs_A[i]);
174
175 regs_B[i] = lastregs_B[i] = 0xFF;
176 writeReg_B(i, regs_B[i]);
177 }
178 }
179
180 void update() {
181 for (int i = 0; i < 16; i++) {
182 if (regs_A[i] != lastregs_A[i]) {
183 writeReg_A(i, regs_A[i]);
184 lastregs_A[i] = regs_A[i];
185 }
186
187 if (regs_B[i] != lastregs_B[i]) {
188 writeReg_B(i, regs_B[i]);
189 lastregs_B[i] = regs_B[i];
190 }
191 }
192 }
193
194 void setTone(ushort ch, ushort divisor, ushort ampl) {
195 if (ch > 2) {
196 //reduce channel to usable range
197 ch = ch - 3;
198 //use regs_B
199 regs_B[TONEALOW + (ch<<1)] = (divisor & 0xFF);
200 regs_B[TONEAHIGH + (ch<<1)] = (divisor >> 8);
201 regs_B[TONEAAMPL + ch] = ampl;
202
203 ushort mask = (8+1) << ch;
204 regs_B[MIXER] = (regs_B[MIXER] | mask) ^ (1 << ch);
205
206 return;
207 }
208
209 regs_A[TONEALOW + (ch<<1)] = (divisor & 0xFF);
210 regs_A[TONEAHIGH + (ch<<1)] = (divisor >> 8);
211 regs_A[TONEAAMPL + ch] = ampl;
212
213 ushort mask = (8+1) << ch;
214 regs_A[MIXER] = (regs_A[MIXER] | mask) ^ (1 << ch);
215 }
216
217 void setToneAndNoise(ushort ch, ushort divisor, ushort noisefreq, ushort ampl) {
218 if (ch > 2) {
219 //reduce channel to usable range
220 ch = ch - 3;
221 //use regs_B
222 regs_B[TONEALOW + (ch<<1)] = (divisor & 0xFF);
223 regs_B[TONEAHIGH + (ch<<1)] = (divisor >> 8);
224 regs_B[NOISEGEN] = noisefreq;
225 regs_B[TONEAAMPL + ch] = ampl;
226
227 ushort mask = (8+1) << ch;
228 ushort bits = (noisefreq < 16 ? 8 : 0) + (divisor > 0 ? 1 : 0);
229 regs_B[MIXER] = (regs_B[MIXER] | mask) ^ (bits << ch);
230
231 return;
232 }
233
234 regs_A[TONEALOW + (ch<<1)] = (divisor & 0xFF);
235 regs_A[TONEAHIGH + (ch<<1)] = (divisor >> 8);
236 regs_A[NOISEGEN] = noisefreq;
237 regs_A[TONEAAMPL + ch] = ampl;
238
239 ushort mask = (8+1) << ch;
240 ushort bits = (noisefreq < 16 ? 8 : 0) + (divisor > 0 ? 1 : 0);
241 regs_A[MIXER] = (regs_A[MIXER] | mask) ^ (bits << ch);
242 }
243
244 void setEnvelope(ushort divisor, ushort shape) {
245 regs_A[ENVLOW] = (divisor & 0xFF);
246 regs_A[ENVHIGH] = (divisor >> 8);
247 regs_A[ENVSHAPE] = shape;
248
249 regs_B[ENVLOW] = (divisor & 0xFF);
250 regs_B[ENVHIGH] = (divisor >> 8);
251 regs_B[ENVSHAPE] = shape;
252 }
253
254 void setOff(ushort ch) {
255 if (ch > 2) {
256 //reduce channel to usable range
257 ch = ch - 3;
258 //use regs_B
259 ushort mask = (8+1) << ch;
260 regs_B[MIXER] = (regs_A[MIXER] | mask);
261 regs_B[TONEAAMPL + ch] = 0;
262 if (regs_B[ENVSHAPE] != 0) {
263 regs_B[ENVSHAPE] = 0;
264 update(); // Force flush
265 }
266
267 return;
268 }
269
270 ushort mask = (8+1) << ch;
271 regs_A[MIXER] = (regs_A[MIXER] | mask);
272 regs_A[TONEAAMPL + ch] = 0;
273 if (regs_A[ENVSHAPE] != 0) {
274 regs_A[ENVSHAPE] = 0;
275 update(); // Force flush
276 }
277 }
278 };
279
280 static PSGRegs psg;
281
282 // Voice generation ---------------------------------------
283
284 static const ushort
285 MIDI_MIN = 24,
286 MIDI_MAX = 96,
287 N_NOTES = (MIDI_MAX+1-MIDI_MIN);
288
289 static const ushort note_table[N_NOTES] = {
290 1911, // MIDI 24, 32.70 Hz
291 1804, // MIDI 25, 34.65 Hz
292 1703, // MIDI 26, 36.71 Hz
293 1607, // MIDI 27, 38.89 Hz
294 1517, // MIDI 28, 41.20 Hz
295 1432, // MIDI 29, 43.65 Hz
296 1351, // MIDI 30, 46.25 Hz
297 1276, // MIDI 31, 49.00 Hz
298 1204, // MIDI 32, 51.91 Hz
299 1136, // MIDI 33, 55.00 Hz
300 1073, // MIDI 34, 58.27 Hz
301 1012, // MIDI 35, 61.74 Hz
302 956, // MIDI 36, 65.41 Hz
303 902, // MIDI 37, 69.30 Hz
304 851, // MIDI 38, 73.42 Hz
305 804, // MIDI 39, 77.78 Hz
306 758, // MIDI 40, 82.41 Hz
307 716, // MIDI 41, 87.31 Hz
308 676, // MIDI 42, 92.50 Hz
309 638, // MIDI 43, 98.00 Hz
310 602, // MIDI 44, 103.83 Hz
311 568, // MIDI 45, 110.00 Hz
312 536, // MIDI 46, 116.54 Hz
313 506, // MIDI 47, 123.47 Hz
314 478, // MIDI 48, 130.81 Hz
315 451, // MIDI 49, 138.59 Hz
316 426, // MIDI 50, 146.83 Hz
317 402, // MIDI 51, 155.56 Hz
318 379, // MIDI 52, 164.81 Hz
319 358, // MIDI 53, 174.61 Hz
320 338, // MIDI 54, 185.00 Hz
321 319, // MIDI 55, 196.00 Hz
322 301, // MIDI 56, 207.65 Hz
323 284, // MIDI 57, 220.00 Hz
324 268, // MIDI 58, 233.08 Hz
325 253, // MIDI 59, 246.94 Hz
326 239, // MIDI 60, 261.63 Hz
327 225, // MIDI 61, 277.18 Hz
328 213, // MIDI 62, 293.66 Hz
329 201, // MIDI 63, 311.13 Hz
330 190, // MIDI 64, 329.63 Hz
331 179, // MIDI 65, 349.23 Hz
332 169, // MIDI 66, 369.99 Hz
333 159, // MIDI 67, 392.00 Hz
334 150, // MIDI 68, 415.30 Hz
335 142, // MIDI 69, 440.00 Hz
336 134, // MIDI 70, 466.16 Hz
337 127, // MIDI 71, 493.88 Hz
338 119, // MIDI 72, 523.25 Hz
339 113, // MIDI 73, 554.37 Hz
340 106, // MIDI 74, 587.33 Hz
341 100, // MIDI 75, 622.25 Hz
342 95, // MIDI 76, 659.26 Hz
343 89, // MIDI 77, 698.46 Hz
344 84, // MIDI 78, 739.99 Hz
345 80, // MIDI 79, 783.99 Hz
346 75, // MIDI 80, 830.61 Hz
347 71, // MIDI 81, 880.00 Hz
348 67, // MIDI 82, 932.33 Hz
349 63, // MIDI 83, 987.77 Hz
350 60, // MIDI 84, 1046.50 Hz
351 56, // MIDI 85, 1108.73 Hz
352 53, // MIDI 86, 1174.66 Hz
353 50, // MIDI 87, 1244.51 Hz
354 47, // MIDI 88, 1318.51 Hz
355 45, // MIDI 89, 1396.91 Hz
356 42, // MIDI 90, 1479.98 Hz
357 40, // MIDI 91, 1567.98 Hz
358 38, // MIDI 92, 1661.22 Hz
359 36, // MIDI 93, 1760.00 Hz
360 34, // MIDI 94, 1864.66 Hz
361 32, // MIDI 95, 1975.53 Hz
362 30, // MIDI 96, 2093.00 Hz
363 };
364
365 struct FXParams {
366 ushort noisefreq;
367 ushort tonefreq;
368 ushort envdecay;
369 ushort freqdecay;
370 ushort timer;
371 };
372
373 struct ToneParams {
374 ushort decay;
375 ushort sustain; // Values 0..32
376 ushort release;
377 };
378
379 static const ushort MAX_TONES = 4;
380 static const ToneParams tones[MAX_TONES] = {
381 { 30, 24, 10 },
382 { 30, 12, 8 },
383 { 5, 8, 7 },
384 { 10, 31, 30 }
385 };
386
387 class Voice {
388 public:
389 ushort m_chan; // Index to psg channel
390 ushort m_pitch;
391 int m_ampl, m_decay, m_sustain, m_release;
392 static const int AMPL_MAX = 1023;
393 ushort m_adsr;
394
395 void init (ushort chan) {
396 m_chan = chan;
397 m_ampl = m_sustain = 0;
398 kill();
399 }
400
401 void start(note_t note, midictrl_t vel, midictrl_t chan) {
402 const ToneParams *tp = &tones[chan % MAX_TONES];
403
404 m_pitch = note_table[note - MIDI_MIN];
405 if (vel > 127) {
406 m_ampl = AMPL_MAX;
407 }
408 else {
409 m_ampl = 768 + (vel << 1);
410 }
411 m_decay = tp->decay;
412 m_sustain = (m_ampl * tp->sustain) >> 5;
413 m_release = tp->release;
414 m_adsr = 'D';
415 psg.setTone(m_chan, m_pitch, m_ampl >> 6);
416 }
417
418 struct FXParams m_fxp;
419
420 void startFX(const struct FXParams &fxp) {
421 m_fxp = fxp;
422
423 if (m_ampl > 0) {
424 psg.setOff(m_chan);
425 }
426 m_ampl = AMPL_MAX;
427 m_adsr = 'X';
428 m_decay = fxp.timer;
429
430 psg.setEnvelope(fxp.envdecay, 9);
431 psg.setToneAndNoise(m_chan, fxp.tonefreq, fxp.noisefreq, 31);
432 }
433
434
435 void stop() {
436 if (m_adsr == 'X') {
437 return; // Will finish when ready...
438 }
439
440 if (m_ampl > 0) {
441 m_adsr = 'R';
442 }
443 else {
444 psg.setOff(m_chan);
445 }
446 }
447
448 void update100Hz() {
449 if (m_ampl == 0) {
450 return;
451 }
452
453 switch(m_adsr) {
454 case 'D':
455 m_ampl -= m_decay;
456 if (m_ampl <= m_sustain) {
457 m_adsr = 'S';
458 m_ampl = m_sustain;
459 }
460 break;
461
462 case 'S':
463 break;
464
465 case 'R':
466 if ( m_ampl < m_release ) {
467 m_ampl = 0;
468 }
469 else {
470 m_ampl -= m_release;
471 }
472 break;
473
474 case 'X':
475 // FX is playing.
476 if (m_fxp.freqdecay > 0) {
477 m_fxp.tonefreq += m_fxp.freqdecay;
478 psg.setToneAndNoise(m_chan, m_fxp.tonefreq, m_fxp.noisefreq, 31);
479 }
480
481 m_ampl -= m_decay;
482 if (m_ampl <= 0) {
483 psg.setOff(m_chan);
484 m_ampl = 0;
485 }
486 return;
487
488 default:
489 break;
490 }
491
492 if (m_ampl > 0) {
493 psg.setTone(m_chan, m_pitch, m_ampl >> 6);
494 }
495 else {
496 psg.setOff(m_chan);
497 }
498 }
499
500 bool isPlaying() {
501 return (m_ampl > 0);
502 }
503
504 void kill() {
505 psg.setOff(m_chan);
506 m_ampl = 0;
507 }
508 };
509
510
511 const ushort MAX_VOICES = 6;
512
513 static Voice voices[MAX_VOICES];
514
515 // MIDI synthesiser ---------------------------------------
516
517 // Deals with assigning note on/note off to voices
518
519 static const uint8_t PERC_CHANNEL = 9;
520
521 static const note_t
522 PERC_MIN = 35,
523 PERC_MAX = 50;
524
525 static const struct FXParams perc_params[PERC_MAX-PERC_MIN+1] = {
526 // Mappings are from the General MIDI spec at https://www.midi.org/specifications-old/item/gm-level-1-sound-set
527
528 // Params are: noisefreq, tonefreq, envdecay, freqdecay, timer
529
530 { 9, 900, 800, 40, 50 }, // 35 Acoustic bass drum
531 { 8, 1000, 700, 40, 50 }, // 36 (C) Bass Drum 1
532 { 4, 0, 300, 0, 80 }, // 37 Side Stick
533 { 6, 0, 1200, 0, 30 }, // 38 Acoustic snare
534
535 { 5, 0, 1500, 0, 90 }, // 39 (D#) Hand clap
536 { 6, 400, 1200, 11, 30 }, // 40 Electric snare
537 { 16, 700, 800, 20, 30 }, // 41 Low floor tom
538 { 0, 0, 300, 0, 80 }, // 42 Closed Hi Hat
539
540 { 16, 400, 800, 13, 30 }, // 43 (G) High Floor Tom
541 { 0, 0, 600, 0, 50 }, // 44 Pedal Hi-Hat
542 { 16, 800, 1400, 30, 25 }, // 45 Low Tom
543 { 0, 0, 800, 0, 40 }, // 46 Open Hi-Hat
544
545 { 16, 600, 1400, 20, 25 }, // 47 (B) Low-Mid Tom
546 { 16, 450, 1500, 15, 22 }, // 48 Hi-Mid Tom
547 { 1, 0, 1800, 0, 25 }, // 49 Crash Cymbal 1
548 { 16, 300, 1500, 10, 22 }, // 50 High Tom
549 };
550
551
552
553 static const int REQ_MAP_SIZE = (N_NOTES+7) / 8;
554 static uint8_t m_requestMap[REQ_MAP_SIZE];
555 // Bit is set for each note being requested
556 static midictrl_t m_velocity[N_NOTES];
557 // Requested velocity for each note
558 static midictrl_t m_chan[N_NOTES];
559 // Requested MIDI channel for each note
560 static uint8_t m_highest, m_lowest;
561 // Highest and lowest requested notes
562
563 static const uint8_t NO_NOTE = 0xFF;
564 static const uint8_t PERC_NOTE = 0xFE;
565 static uint8_t m_playing[MAX_VOICES];
566 // Which note each voice is playing
567
568 static const uint8_t NO_VOICE = 0xFF;
569 static uint8_t m_voiceNo[N_NOTES];
570 // Which voice is playing each note
571
572
573 static bool startNote(ushort idx) {
574 for (ushort i = 0; i < MAX_VOICES; i++) {
575 if (m_playing[i] == NO_NOTE) {
576 voices[i].start(MIDI_MIN + idx, m_velocity[idx], m_chan[idx]);
577 m_playing[i] = idx;
578 m_voiceNo[idx] = i;
579 return true;
580 }
581 }
582 return false;
583 }
584
585 static bool startPercussion(note_t note) {
586 ushort i;
587 for (i = 0; i < MAX_VOICES; i++) {
588 if (m_playing[i] == NO_NOTE || m_playing[i] == PERC_NOTE) {
589 if (note >= PERC_MIN && note <= PERC_MAX) {
590 voices[i].startFX(perc_params[note-PERC_MIN]);
591 m_playing[i] = PERC_NOTE;
592 }
593 return true;
594 }
595 }
596 return false;
597 }
598
599 static bool stopNote(ushort idx) {
600 uint8_t v = m_voiceNo[idx];
601 if (v != NO_VOICE) {
602 voices[v].stop();
603 m_playing[v] = NO_NOTE;
604 m_voiceNo[idx] = NO_VOICE;
605 return true;
606 }
607 return false;
608 }
609
610 static void stopOneNote() {
611 uint8_t v, chosen = NO_NOTE;
612
613 // At this point we have run out of voices.
614 // Pick a voice and stop it. We leave a voice alone
615 // if it's playing the highest requested note. If it's
616 // playing the lowest requested note we look for a 'better'
617 // note, but stop it if none found.
618
619 for (v = 0; v < MAX_VOICES; v++) {
620 uint8_t idx = m_playing[v];
621 if (idx == NO_NOTE) {// Uh? Perhaps called by mistake.
622 return;
623 }
624
625 if (idx == m_highest) {
626 continue;
627 }
628
629 if (idx == PERC_NOTE) {
630 continue;
631 }
632
633 chosen = idx;
634 if (idx != m_lowest) {
635 break;
636 }
637 // else keep going, we may find a better one
638 }
639
640 if (chosen != NO_NOTE) {
641 stopNote(chosen);
642 }
643 }
644
645 static void updateRequestedNotes() {
646 m_highest = m_lowest = NO_NOTE;
647 ushort i,j;
648
649 // Check highest requested note is playing
650 // Return true if note was restarted; false if already playing
651 for (i = 0; i < REQ_MAP_SIZE; i++) {
652 uint8_t req = m_requestMap[i];
653 if (req == 0) {
654 continue;
655 }
656
657 for (j = 0; j < 8; j++) {
658 if (req & (1 << j)) {
659 ushort idx = i*8 + j;
660 if (m_lowest == NO_NOTE || m_lowest > idx) {
661 m_lowest = idx;
662 }
663 if (m_highest==NO_NOTE || m_highest < idx) {
664 m_highest = idx;
665 }
666 }
667 }
668 }
669 }
670
671 static bool restartANote() {
672 if (m_highest != NO_NOTE && m_voiceNo[m_highest] == NO_VOICE) {
673 return startNote(m_highest);
674 }
675
676 if (m_lowest != NO_NOTE && m_voiceNo[m_lowest] == NO_VOICE) {
677 return startNote(m_lowest);
678 }
679
680 return false;
681 }
682
683 static void synth_init () {
684 ushort i;
685
686 for (i = 0; i < REQ_MAP_SIZE; i++) {
687 m_requestMap[i] = 0;
688 }
689
690 for (i = 0; i < N_NOTES; i++) {
691 m_velocity[i] = 0;
692 m_voiceNo[i] = NO_VOICE;
693 }
694
695 for (i = 0; i < MAX_VOICES; i++) {
696 m_playing[i] = NO_NOTE;
697 }
698
699 m_highest = m_lowest = NO_NOTE;
700 }
701
702 static void noteOff(midictrl_t chan, note_t note, midictrl_t vel) {
703 if (chan == PERC_CHANNEL || note < MIDI_MIN || note > MIDI_MAX) {
704 return; // Just ignore it
705 }
706
707 ushort idx = note - MIDI_MIN;
708
709 m_requestMap[idx/8] &= ~(1 << (idx & 7));
710 m_velocity[idx] = 0;
711 updateRequestedNotes();
712
713 if (stopNote(idx)) {
714 restartANote();
715 }
716 }
717
718 static void noteOn(midictrl_t chan, note_t note, midictrl_t vel) {
719 if (vel == 0) {
720 noteOff(chan, note, 0);
721 return;
722 }
723
724 if (chan == PERC_CHANNEL) {
725 if (!startPercussion(note)) {
726 stopOneNote();
727 startPercussion(note);
728 }
729 return;
730 }
731
732 // Regular note processing now
733
734 if (note < MIDI_MIN || note > MIDI_MAX) {
735 return; // Just ignore it
736 }
737
738 ushort idx = note - MIDI_MIN;
739
740 if (m_voiceNo[idx] != NO_VOICE) {
741 return; // Already playing. Ignore this request.
742 }
743
744 m_requestMap[idx/8] |= 1 << (idx & 7);
745 m_velocity[idx] = vel;
746 m_chan[idx] = chan;
747 updateRequestedNotes();
748
749 if (!startNote(idx)) {
750 stopOneNote();
751 startNote(idx);
752 }
753 }
754
755
756 static void update100Hz() {
757 for (ushort i = 0; i < MAX_VOICES; i++) {
758 voices[i].update100Hz();
759 if (m_playing[i] == PERC_NOTE && ! (voices[i].isPlaying())) {
760 m_playing[i] = NO_NOTE;
761 restartANote();
762 }
763 }
764 }
765
766 // Main code ----------------------------------------------
767
768 static unsigned long lastUpdate = 0;
769
770 void setup() {
771 // Hold in reset while we set up the reset
772 pinMode(nRESET, OUTPUT);
773 digitalWrite(nRESET, LOW);
774
775 pinMode(clkOUT, OUTPUT);
776 digitalWrite(clkOUT, LOW);
777 clockSetup();
778
779 pinMode(BC2_A, OUTPUT);
780 digitalWrite(BC2_A, LOW); // BC2 low
781 pinMode(BDIR_A, OUTPUT);
782 digitalWrite(BDIR_A, LOW); // BDIR low
783
784 pinMode(BC2_B, OUTPUT);
785 digitalWrite(BC2_B, LOW); // BC2 low
786 pinMode(BDIR_B, OUTPUT);
787 digitalWrite(BDIR_B, LOW); // BDIR low
788
789 for (ushort i = 0; i < 8; i++) {
790 pinMode(dbus[i], OUTPUT);
791 digitalWrite(dbus[i], LOW); // Set bus low
792 }
793
794 delay(100);
795 digitalWrite(nRESET, HIGH); // Release Reset
796 delay(10);
797
798 lastUpdate = millis();
799
800 psg.init();
801 for (ushort i = 0; i < MAX_VOICES; i++) {
802 voices[i].init(i);
803 }
804 synth_init();
805
806 #ifdef DEBUG
807 Serial.begin(115200);
808 #endif
809
810 #ifdef SERIALMIDI
811 // Initiate MIDI communications, listen to all channels
812 MIDI.begin(MIDI_CHANNEL_OMNI);
813 #endif
814 }
815
816 void handleMidiMessage(midiEventPacket_t &rx) {
817 if (rx.header==0x9) {// Note on
818 noteOn(rx.byte1 & 0xF, rx.byte2, rx.byte3);
819 }
820 else if (rx.header==0x8) {// Note off
821 noteOff(rx.byte1 & 0xF, rx.byte2, rx.byte3);
822 }
823 else if (rx.header==0xB) {// Control Change
824 if (rx.byte2 == 0x78 || rx.byte2 == 0x79 || rx.byte2 == 0x7B) {// AllSoundOff, ResetAllControllers, or AllNotesOff
825 // Kill Voices
826 for (ushort i = 0; i < MAX_VOICES; i++) {
827 voices[i].kill();
828 }
829 }
830 }
831 }
832
833 void loop() {
834 midiEventPacket_t rx;
835
836 #ifdef USBMIDI
837 rx = MidiUSB.read();
838
839 #ifdef DEBUG
840 //MIDI debugging
841 if (rx.header != 0) {
842 Serial.print("Received USB: ");
843 Serial.print(rx.header, HEX);
844 Serial.print("-");
845 Serial.print(rx.byte1, HEX);
846 Serial.print("-");
847 Serial.print(rx.byte2, HEX);
848 Serial.print("-");
849 Serial.println(rx.byte3, HEX);
850 }
851 #endif
852
853 handleMidiMessage(rx);
854 #endif
855
856 #ifdef SERIALMIDI
857 //Check for serial MIDI messages
858 //MIDI.read();
859 while (MIDI.read()) {
860 // Create midiEventPacket_t
861 rx =
862 {
863 byte(MIDI.getType() >>4),
864 byte(MIDI.getType() | ((MIDI.getChannel()-1) & 0x0f)), /* getChannel() returns values from 1 to 16 */
865 MIDI.getData1(),
866 MIDI.getData2()
867 };
868
869 #ifdef DEBUG
870 //MIDI debugging
871 if (rx.header != 0) {
872 Serial.print("Received MIDI: ");
873 Serial.print(rx.header, HEX);
874 Serial.print("-");
875 Serial.print(rx.byte1, HEX);
876 Serial.print("-");
877 Serial.print(rx.byte2, HEX);
878 Serial.print("-");
879 Serial.println(rx.byte3, HEX);
880 }
881 #endif
882
883 handleMidiMessage(rx);
884 }
885 #endif
886
887 unsigned long now = millis();
888 if ((now - lastUpdate) > 10) {
889 update100Hz();
890 lastUpdate += 10;
891 }
892
893 psg.update();
894 }