10 .TITLE "Atari Midi Interface" 20 .PAGE "C Terpin 1991" 30 ; created: 06/89 revised:03/91 40 ; 50 ; Midi 'V:' Handler 60 ; 70 ; Christopher Terpin 80 ; 144 Eggert Road 90 ; Buffalo, N.Y. 14215 0100 ; terpin@autarch.acsu.buffalo.edu 0110 ; or terpin@ubunix.acsu.buffalo.edu 0120 ; 0130 ; THIS SOURCE CODE IS IN THE 0140 ; PUBLIC DOMAIN. 0150 ; Please feel free to modify and 0160 ; improve this code. Please 0170 ; let me know about any major 0180 ; improvements or bugs you 0190 ; have discovered. Enjoy! 0200 ; 0210 ; 0220 ; 0230 ; execution from AtariBASIC: 0240 ;1. POKE 106,154:GR.0:DOS 0250 ;2. load D:MIDI.OBJ 0255 ;3. run BASIC cartridge 0260 ;4. I=USR(39168,cmd,[start, end],speed) (see below) 0270 ;------------------------------ 0280 ; to initialize V: handler: 0290 ; I=USR(39168,ASC("I")) 0300 ; CLOSE #1:OPEN #1,8,0,"V:" 0310 ; use ?#1; or PUT #1 to send midi codes (i.e. ?#1;".A@";) 0320 ;------------------------------ 0330 ; 0340 ; to reset normal serial i/o: 0350 ; I=USR(39168,ASC("C")) 0360 ;------------------------------- 0370 ; to record: 0380 ; I=USR(39168,ASC("R"),ADR(A$)) 0390 ; POKE 204,1 to start recording 0400 ; POKE 204,0 to stop recording 0410 ; L = PEEK(205)+256*PEEK(206) 0420 ; is # of bytes recorded 0430 ;------------------------------ 0440 ; to playback: 0450 ; I=USR(39168,ASC("P"),ADR(A$),ADR(A$)+L) 0460 ;------------------------------ 0470 ; to record B while playing A: 0480 ; I=USR(39168,ASC("R"),ADR(B$)) 0490 ; POKE 204,1 :I=USR(39168,ASC("P"),ADR(A$),ADR(A$)+L):POKE 204,0 0500 ;------------------------------ 0510 ; 0520 ; equates 0530 ; 0540 AUDCTL=$D208 ; pokey register 0550 OUTBUF=$CB ; character for output 0560 SWITCH= $CC ; 0==stop recording 0570 BUFADD = $CD ; pointer to buffer for recording 0580 PLAYBUF = $CF ; pointer to buffer for playback 0590 CH = $2FC ; last key pressed 0600 RECVDN = $39 ; receive flag 0610 SERIN = $D20D ; serial i/o 0620 SEROUT = SERIN 0630 ; 0640 VSERIN=$020A ; interrupt vector for serial input 0650 VSEROR = $20C ; interrupt vector for serial output ready 0660 VSEROC = $20E ; interrupt vector for serial output complete 0670 ; 0680 AUDF3=$D204 ; pokey ch3 :baudlo 0690 AUDF4=$D206 ; pokey ch4 :baudhi 0700 XMTDON=$3A ; transmit done flag 0710 SSKCTL=$232 ; serial port control shadow 0720 SKCTL=$D20F ; serial port control (W) 0730 SKSTAT=$D20F ; serial port status (R) 0740 SKRES= $D20A ; resets SKSTAT 0750 POKMSK=$10 ; shadow of IRQEN 0760 IRQEN=$D20E ; interrupt request 0770 HATABS=$31A ; pointer to handler table 0780 MIDIL= 22 ; midi baud rate 0790 MIDIH = 0 ; values 0800 RD = 4 ; CIO: open for read 0810 WR = 8 ; CIO: open for write 0820 CPUT = 11 ; CIO: PUT command 0830 COPEN = 3 ; CIO: OPEN command 0840 CCLOSE = 12 ; CIO: CLOSE command 0850 ; 0860 ; input-output control block (iocb) 0870 ; structure: 0880 ; 0890 ICCOM = $342 ; command 0900 ICAX1 = $34A ; aux1 0910 ICAX2 = $34B ; aux2 0920 ICBAH = $345 ; buffer address hi 0930 ICBAL = $344 ; buffer address lo 0940 ICBLL = $348 ; buffer length lo 0950 ICBLH = $349 ; buffer length hi 0960 CIOV = $E456 ; Central I/O vector 0970 ; 0980 ; 0990 ; countdown timers 1000 ; 1010 CDTMA1 = $0226 ; timer1 vector 1020 CDTMA2 = $0228 ; timer2 vector 1030 CDTMF5 = $022E ; timer5 flag 1040 SETVBV = $E45C ; set vblank 1050 CONSOL = $D01F ; used for keyboard click 1060 ; 1070 MARK = 255 ; buffer timing mark 1080 CTRLC = 146 ; control-c key 1090 ; 1100 *= $9900 1110 ; 1120 ; 1130 START 1140 PLA 1150 PLA 1160 PLA get command 1170 ; 1180 CMP #'I 1190 BNE OPT2 1200 JMP SETHAND ; setup CIO handler 1210 ; 1220 OPT2 CMP #'R 1230 BNE OPT3 1240 JMP RECORD ; midi record 1250 ; 1260 OPT3 CMP #'P 1270 BNE OPT4 1280 JMP PLAYBACK ; midi playback 1290 ; 1300 OPT4 CMP #'C 1310 BNE NOOPT 1320 JMP RESET ; reset ser port 1330 NOOPT SEC ; error 1340 RTS 1350 ;----------------------------- 1360 ; MIDI "V:" handler 1370 ; 1380 SETHAND 1390 LDY #0 ; add our handler 1400 LPHND LDA HATABS,Y 1410 CMP #0 ;free entry? 1420 BEQ FOUND ;yes.. 1430 INY ;no, skip 3 bytes 1440 INY 1450 INY 1460 CPY #34 ; end of table? 1470 BNE LPHND ; no...keep looking 1480 SEC ; yes..full table! 1490 RTS ; quit with error 1500 ; 1510 FOUND LDA #'V ; "V:" handler 1520 STA HATABS,Y ; save in table 1530 INY 1540 LDA #VTABL&255 ; store address 1550 STA HATABS,Y ; of vector 1560 INY ; table in 1570 LDA #VTABL/255 ; handler table 1580 STA HATABS,Y 1590 ; 1600 ; save interrupt vectors 1610 ; 1620 LDY #5 1630 SAVESER LDA VSERIN,Y 1640 STA SAVEVEC,Y 1650 DEY 1660 BPL SAVESER 1670 ; 1680 RTS 1690 ; 1700 SAVEVEC .BYTE 0,0,0,0,0,0 1710 ; 1720 ;-------------------------- 1730 ; begin MIDI Handler 1740 ; 1750 ; vector table: 1760 VTABL .WORD VOPEN-1 1770 .WORD VCLOSE-1 1780 .WORD NOFUNC-1 (get) 1790 .WORD VPUT-1 1800 .WORD NOFUNC-1 (status) 1810 .WORD NOFUNC-1 (special) 1820 JMP VINIT ; initialization 1830 ;----------------------------- 1840 VCLOSE 1850 VOPEN LDA #0 1860 STA MODE 1870 VINIT 1880 RETURN1 LDY #1 ; return success 1890 NOFUNC RTS 1900 ;----------------------------- 1910 ; 1920 ; 1930 ; 1940 ;---------------------------- 1950 ; PUT function 1960 ; 1970 VPUT STA OUTBUF ; save accumulator 1980 LDA MODE ; write mode ? 1990 CMP #WR 2000 BEQ WRMODE ; yes... 2010 LDA #WR ; no..set flag 2020 STA MODE ; and 2030 JSR SETIO ; set up for serial output 2040 ; 2050 WRMODE LDA OUTBUF ; retrieve byte 2060 STA SEROUT ; send it out & 2070 WAIT LDA XMTDON wait for irqs 2080 BEQ WAIT to do their 2090 LDA #0 stuff... 2100 STA XMTDON reset flag 2110 LDY #1 exit 2120 RTS with success 2130 ;---------------------------- 2140 ; 2150 ; 2160 ;---------------------------- 2180 ; enable serial I/O 2190 SETIO LDA #$73 ; ALLOW I & O concurrently! 2200 CONT STA SSKCTL ; set the serial port control, 2210 STA SKCTL and shadow 2220 LDA POKMSK ; get irqen mask 2230 AND #$CF ; no keyboard interrupts 2240 CLC 2250 ORA #$30 ;allow both types 2260 STA POKMSK of serial interrupts 2270 STA IRQEN 2280 LDA #MIDIL 2290 STA AUDF3 ;set 31.5k baud 2300 LDA #MIDIH rate for i/o 2310 STA AUDF4 in POKEY 2320 LDA #$28 ; clock ch3 with 1.79MHz 2330 STA AUDCTL ; & link ch3 to ch4 2340 ; 2350 LDA #0 ; reset transmit 2360 STA XMTDON done flag 2370 RTS 2380 ; 2390 ; 2400 MODE .BYTE 0 ; read/write mode flag 2410 ; 2420 ;------------------------------ 2430 ; reset serial port 2440 RESET 2450 LDA #$03 2460 STA SSKCTL ; disable 2470 STA SKCTL serial port 2480 LDA POKMSK interrupts 2490 AND #$C0 2500 STA POKMSK 2510 STA IRQEN 2520 LDA #0 2530 STA SWITCH 2540 STA XMTDON 2550 LDY #5 ; restore vectors 2560 RESTV LDA SAVEVEC,Y 2570 STA VSERIN,Y 2580 DEY 2590 BPL RESTV 2600 RTS 2610 ;---------------------------- 2620 ; 2630 ; 2640 ; interrupt service routine 2650 ; for serial input ready 2660 ; 2670 ;---------------------------- 2680 ISRSIR TYA 2690 PHA save y register 2700 LDA SKSTAT reset latches 2710 STA SKRES in case of overrun 2720 LDA SWITCH check software switch 2730 BEQ LEAVIT switch on? 2740 LDY #0 ; yes,save interval 2750 LDA INTERVAL 2755 CMP #2 ; 2 jiffies? 2760 BCC TOOSHORT ;time is too short 2770 LDY #1 2780 STA (BUFADD),Y 2790 LDA #MARK 2800 LDY #0 2810 STA (BUFADD),Y 2820 STY INTERVAL ; reset counter 2830 LDY #2 2840 TOOSHORT LDA SERIN ;load serial input 2850 STA (BUFADD),Y store input 2860 INY in buffer & 2870 TYA 2880 CLC 2890 ADC BUFADD 2900 STA BUFADD increment the 2910 LDA BUFADD+1 buffer pointer 2920 ADC #0 2930 STA BUFADD+1 2940 ; 2950 LEAVIT LDA #$FF set recvdn flag 2960 STA RECVDN 2970 PLA restore the 2980 TAY y register 2990 PLA and the accumulator 3000 RTI & return from interrupt. 3010 ;----------------------------- 3020 ; 3030 ; 3040 ;----------------------------- 3050 ; TIMER ROUTINE 3060 ; 3070 ; inserts timing marks 3080 ; to count time interval 3090 ; 3100 ;---------------------------- 3110 TIMER 3120 LDA SWITCH ;are we recording? 3130 BEQ SETTIM ;NO...reset this vbi 3150 INC INTERVAL ; count # of marks 3160 CMP #255 ; max count? 3170 BNE SETTIM ; no, continue 3190 LDY #0 ; write to buffer 3200 LDA #MARK ; timing mark 3210 STA (BUFADD),Y 3220 LDY #1 3230 LDA #255 3240 STA (BUFADD),Y 3260 LDA #0 ; reset counter 3270 STA INTERVAL 3280 ; 3290 CLC ;increment the 3300 LDA BUFADD ; buffer pointer 3310 ADC #2 ; by two 3320 STA BUFADD 3330 LDA BUFADD+1 3340 ADC #0 3350 STA BUFADD+1 3360 ; 3370 ; 3380 SETTIM 3390 LDA #TIMER&255 ; reset the 3400 STA CDTMA2 timer vector 3410 LDA #TIMER/256 3420 STA CDTMA2+1 3490 LDY #1 ; speed lo (1 jiffy) 3500 LDX #0 ; speed hi 3510 LDA #2 ; use timer 2 3520 JSR SETVBV ; start it! 3530 RTS ; done.. 3540 ;------------------------------ 3550 ; 3560 ; 3570 ; 3580 ;----------------------------- 3590 ; RECORD routine 3600 ; 3610 ; 3620 RECORD 3630 PLA 3640 STA BUFADD+1 ;address of 3650 PLA recording buffer 3660 STA BUFADD 3710 LDA #0 3720 STA INTERVAL ; reset counter 3730 STA RECVDN ; reset input flag 3740 JSR SETTIM ; start timer 3750 LDA #ISRSIR&255 3760 STA VSERIN ;setup serial 3770 LDA #ISRSIR/256 input interrupts 3780 STA VSERIN+1 3790 JSR SETIO ;enable serial input 3800 RTS ; done! 3820 INTERVAL .BYTE 0 3830 ;----------------------------- 3840 ; 3850 ; 3860 ; PLAYBACK routine 3870 ; 3880 PLAYBACK 3890 PLA 3900 STA PLAYBUF+1 ; playback buffer 3910 PLA 3920 STA PLAYBUF 3930 PLA 3940 STA ENDBUF+1 ; pointer to end of buffer 3950 PLA 3970 STA ENDBUF 4020 ; 4030 LDA #0 4040 STA DONE ; reset flag 4050 ; 4060 LDA #ISEROR&255 ; set up 4070 STA VSEROR serial output 4080 LDA #ISEROR/256 ready 4090 STA VSEROR+1 interrupt 4100 ; 4110 LDA #ISRTD&255 ; set up 4120 STA VSEROC serial output 4130 LDA #ISRTD/256 complete 4140 STA VSEROC+1 interrupt 4150 JSR SETIO 4160 ; 4170 ; 4180 ; 4190 ; 4200 ;------------------------------ 4210 ; output buffer 4220 PLAYLOOP 4230 LDY #0 4240 LDA (PLAYBUF),Y ; get note 4250 CMP #MARK ; is it a mark? 4260 BNE ISMIDI ; no... 4270 LDY #1 ; yes...wait 4280 LDA (PLAYBUF),Y 4290 TAY ; Y=jiffys to wait 4300 LDA #$10 4310 STA CDTMF5 4320 LDX #0 4330 LDA #5 4340 JSR SETVBV ; start timer 5 4350 WAITLOOP LDA CDTMF5 ; dumdedum.. 4360 BNE WAITLOOP 4370 ; 4380 LDA #2 4390 JSR INCPLAY ; inc buffer by 2 4400 LDA CH ; check for break 4410 CMP #CTRLC 4420 BNE PLAYLOOP 4430 RTS 4440 ; 4450 ISMIDI JSR VPUT ; otherwise send it 4460 LDA #1 4470 JSR INCPLAY ; incr. by 1 4480 LDA DONE ; finished? 4490 BEQ PLAYLOOP ; no.... 4500 RTS ; yes..quit 4510 ; 4520 ; 4530 ; 4540 INCPLAY 4550 CLC ; increment 4560 ADC PLAYBUF playbuf by A 4570 STA PLAYBUF 4580 LDA PLAYBUF+1 4590 ADC #0 4600 STA PLAYBUF+1 4610 ; 4620 CMP ENDBUF+1 ; end of song? 4630 BCC NOTEND 4640 LDA PLAYBUF 4650 CMP ENDBUF 4660 BCC NOTEND ; no.. 4670 LDA #1 ; yes... 4680 STA DONE set done flag 4690 NOTEND RTS 4700 ; 4710 ; 4720 ENDBUF .BYTE 0,0 4730 DONE .BYTE 0,0 4740 ; 4750 ; 4760 ; 4770 ; 4780 ;----------------------------- 4790 ; serial output ready 4800 ; interrupt service routine 4810 ; 4820 ISEROR 4830 ; 4840 LDA POKMSK ; enable 4850 ORA #$08 the transmit done 4860 STA POKMSK interrupt 4870 STA IRQEN 4880 PLA ; pop accumulator 4890 RTI 4900 ; 4910 ;--------------------------- 4920 ; Transmit Done Interrupt 4930 ; 4940 ISRTD 4950 LDA #$FF ;set transmit 4960 STA XMTDON done flag 4970 LDA POKMSK 4980 AND #$F7 ;disable tdi 4990 STA POKMSK 5000 STA IRQEN 5010 PLA ;restore accum. 5020 RTI 5030 ;----------------------------