MPX61: A MIDI-IN Project for Matrix Keyboards

Note: this circuit and assembly-language program are from a project I did in 1985. It is far from elegant, but it works--even today! I have since completely redesigned both the hardware and software, but as the redesign was done specifically for a Korg PolySix (adding patch change as well), the new design is not nearly as easy to adapt to other matrix keyboards. The material here is meant simply as a guideline for those interested in rolling their own; it is up to them to figure out how the wiring and note<>matrix table for their particular synth is configured (These changes also required in the software.)

The new design still uses a Z8 microcomputer, but also makes use of some sexy analog switches from Siliconix. Common-point switches with built-in latches and a serial interface are *much* easier to lay out on a circuit baord than eight 8-bit latches and sixteen 4066s.

Anyway, a quick circuit rundown:

The Z8603 is a Zilog Z8 NMOS CPU with a ROM socket attached. The CPU runs the program listed below to read in MIDI data, match the note on/off and sustain pedal data for a selected MIDI channel, then play the notes by storing them to a bank of latches (eight 8-bit latches = 64 bits), and using the state of each bit in the latches to drive the SPST analog switch sections in sixteen 4066 chips. Since a standard 5-octave keyboard has 61 keys, three of the latch bits and their corresponding switches and not used. The analog switches are simply wired (each with a matrix diode) in parallel with the switch matrix of the actual keyboard.

There are a few nice things about the circuit. Since it operates completely outside the knowledge of the synth's on-board key-assigner, the MIDI-IN circuit and the key-assigner do not have to keep track of one another. If the MIDI-IN circuit is holding down more keys than the synth has assignables voices, the synth will react just as if the performer was holding down the actual keys on the keyboard. In addition, the circuit can be installed and removed from most synths without making any tricky mods to the existing hardware (the Polysix has a 16-pin connector for the keyboard; that is the only connection to the synth). Note that the keyboard can be played at the same time MIDI-IN data is playing notes; the two switch matrices do not interfere with each other.

The nicest feature, however, is the MIDI pedal sustain, which will work on multi-voice synths even if the synth itself has no pedal sustain provision. Examine the code listing to see how it works. MPX61 Schematic

Code Listing

; ; MPX-61 Universal MIDI card, software version 0.3 ; Copyright (c) 1985, 1986 by CSR/DMI (Christopher S. Rider) ; ; This version supports standard AGO keyboards, ; transpositon is by octave, C1 ~ C5, common-anode ; switching matrix. ; ; Note: This was assembled using an ancient CP/M cross assembler which ; didn't like the indexed forms {LD INDEX(Rx),Ry} although the ; actual opcodes are fine. They were DB'ed in as needed. ; ; Note 2: This code is ten years old. I would do some things differently ; today. This works just fine. Don't complain. :) ; ; CSR 11/29/95 ; RDA: EQU %00001000 ;Receiver Data Available TBE: EQU %00010000 ;Transmitter Buffer Empty RDA_: EQU %11110111 TBE_: EQU %11101111 CHANMSK: EQU %00001111 ;MIDI channel is lower nibble MIDIMSK: EQU %11110000 ;MIDI command is upper nibble DEF CHAN R:$10 ;Register 16 holds DIP switch value DEF MIDI_R R:$11 ;Receive data register DEF MIDI_T R:$12 ;Transmit data register DEF SUSFLG R:$13 ;Sustain pedal flag DEF IRQ R:$FA DEF SIO R:$F0 KEY_ON: EQU %10010000 KEY_OFF: EQU %10000000 CONTROL: EQU %10110000 ;Control parameter change SUSTAIN: EQU 64 ;Control parm=64 for sustain pedal K_TABLE: EQU $30-1 ;Registers 30h to 37h are the key table S_TABLE: EQU $38-1 ;Registers 38h to 3Fh are sustained keys ; ; ; ORG $000C INIT: SRP #$F0 ;Select control group LD R4,#2 ;Set timer for 31,250 baud LD R5,#%00000101 ;/ (R4=T0, R5=PRE0) LD R6,#%00000000 ;Set I/O ports, #2 as output (P2M) LD R7,#%01000001 ;#3 as SIO, active pull-ups (P3M) LD R8,#%01000101 ;#0 as input, #1 as output, int. stack CLR R11 ;Use polling for SIO operations LD R15,#127 ;Top of internal RAM LD R1,#%00000011 ;Start the UART SRP #0 ;Select "main" working set ; ; Clear the latches of random data ; CLR R2 ;Clear the 74LS374s LD R1,#%11111111 ;/ CLR R1 ; ; Clear out the key and sustain tables ; LD R4,#16 ;Eight passes in two tables CLR R5 KT_CLR: DB $D7,K_TABLE,$45 ; LD K_TABLE(R4),R5 ;Clear a byte DJNZ R4,KT_CLR ; ; Enable serial communications ; EI ;Enable polling flags CALL FLUSH ;Clear out UART LD CHAN,R0 ;Get the MIDI channel we work with AND CHAN,#CHANMSK CLR SUSFLG ;Clear sustain flag MAIN: LD R7,#%11111111 ;Assume a key on / pedal down CALL MIDI_IN ;Get MIDI byte... LD R5,MIDI_R ;/ AND R5,#CHANMSK ;Check for the right channel CP R5,CHAN ; / JR NZ,MAIN ;/ LD R5,MIDI_R AND R5,#MIDIMSK ;Now check the command bits CP R5,#KEY_ON JR Z,K_IS_ON CP R5,#KEY_OFF JR Z,K_IS_OFF CP R5,#CONTROL JR NZ,MAIN CTL_MSG: CALL MIDI_IN CP MIDI_R,#SUSTAIN ;Check for sustain JR NZ,MAIN CALL MIDI_IN OR MIDI_R,MIDI_R ;Pedal up or down? JR NZ,SUST ; ; Pedal released, clear sustain table ; CLR SUSFLG CLR R9 LD R10,#%10000000 LD R8,#8 SUSLP: DB $D7,S_TABLE,$89 ; LD S_TABLE(R8),R9 ;Clear table entry ; ; Don't kill keys that are active in key table, silence all others ; DB $C7,K_TABLE,$B8 ; LD R11,K_TABLE(R8) LD R2,R11 ;Yeah!! LD R1,R10 CLR R1 RR R10 ;Next bank DJNZ R8,SUSLP JR MAIN ; ; Pedal down ; SUST: LD SUSFLG,#$FF JR MAIN ; ; Key release ; K_IS_OFF: CLR R7 ;If key off, remember it ; ; Key down ; K_IS_ON: CALL MIDI_IN ;Get key number data LD R6,MIDI_R ;/ CALL MIDI_IN ;Get key velocity data OR MIDI_R,MIDI_R ;If velocity=0, key off JR NZ,DO_0 CLR R7 DO_0: SUB R6,#36 ;C1 key is group 0, bit 0 JR ULT,MAIN ;Below a C1? Forget it. CP R6,#60 ;C6 key is group 8, bit 4 JR UGT,MAIN ; ; Valid key number, create two values (R8 and R9) that represent where ; from the LSB in a byte the desired group and key bits are located. ; LD R9,R6 ;Prepare to point at key flags AND R9,#%00000111 ;(binary bit/key value) LD R8,R6 RR R8 RR R8 RR R8 AND R8,#%00000111 ;(binary group value) ; ; Adjust for offset to make DJNZ instruction loop the correct number of ; times, also clear the result registers, R10 and R11 ; INC R8 INC R9 CLR R10 CLR R11 LD R12,R8 LD R13,R9 ; ; Group bit determination: shift a "1" from the carry through the byte "n" ; times, where "n" = R12; the "1" ends up in the position corresponding ; to which latch is to receive the new key bit. ; SCF G_BIT: RLC R10 ;All these shifts and masks...bleah DJNZ R12,G_BIT ; ; Same thing for the key bit as above. ; SCF K_BIT: RLC R11 DJNZ R13,K_BIT ; ; Save the status of the bit in the key table, also put it in the sustain ; table if sustain is flagged. ; DB $C7,S_TABLE,$F8 ; LD R15,S_TABLE(R8) ;Get the bits of the group OR SUSFLG,SUSFLG ;Sustain? JR Z,SET_KEY OR R15,R11 ;Accumulate bits if sustained DB $D7,S_TABLE,$8F ; LD S_TABLE(R8),R15 SET_KEY: DB $C7,K_TABLE,$E8 ; LD R14,K_TABLE(R8) ;Get the bits of the group OR R7,R7 ;Look at key on/off JR Z,K_OFF OR R14,R11 ;Set the requested bit PLAY: DB $D7,K_TABLE,$8E ; LD K_TABLE(R8),R14 ;Save it in the key table ; ; Make sure that sustained notes get played. ; OR SUSFLG,SUSFLG JR Z,NOSUS OR R14,R15 NOSUS: LD R2,R14 ;(R2=port 2) LD R1,R10 ;Clock it into the selected group latch CLR R1 JP MAIN ;Always loop for more K_OFF: COM R11 ;Turn the bit off AND R14,R11 ;Reset the bit in the key table JR PLAY ; ; ; Utility routines ; ; MIDI_IN: TM IRQ,#RDA ;Check for RDA JR Z,MIDI_IN LD MIDI_R,SIO ;Get byte RTRN: AND IRQ,#RDA_ ;Reset the RDA bit RET MIDI_OUT: TM IRQ,#TBE ;Check for TBE JR Z,MIDI_OUT LD SIO,MIDI_T ;Send byte AND IRQ,#TBE_ ;Reset TBE bit RET FLUSH: TM IRQ,#RDA JR Z,RTRN LD R4,SIO AND IRQ,#RDA_ JR FLUSH ; ; The End ;

MPX61 MIDI-IN / Christopher S. Rider / syzygy@oldcrows.net