Dual AY-3-8910 MIDI module

From Doge Microsystems
Revision as of 05:27, 22 December 2020 by DogeMicrosystems (talk | contribs) (Created page with "For the last couple years I've been fascinated by [https://en.wikipedia.org/wiki/MIDI MIDI] and its ability to both play music in real time and record the signalling for later...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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

Dual AY-3-8910 circuit.png

AY-3-8910 control.png

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 }