A.N.A.L.O.G. ISSUE 15 / JANUARY 1984 / PAGE 83
In the two years I’ve owned my 400 computer, I have enjoyed experimenting with Atari’s Music Composer cartridge. Unfortunately, it only makes an organ-like tone that can’t be changed. Think how much nicer it would be if you could control the actual sound of the notes as well as their pitch!
I wrote Music Synthesizer for this very purpose. Used in conjunction with the Music Composer, I can orchestrate my own four-piece band that doesn’t take breaks. Sound changes are just a keystroke away. Anything from piano to organ to bongos to science fiction “weird” can be created, from a melodic ditty to a noisy dirge.
Atari’s Music Composer cartridge must be used to compose the music. After a music file has been saved on disk or cassette, it can be entered into the Synthesizer. Files containing up to approximately 5000 notes are accepted. All ten phrases are independently controllable, so each phrase can sound different if desired. All voices and all “arrange music” commands are supported during playback. Also available are adjustable tempo and the ability to repeat a song up to 255 times automatically.
Music Synthesizer works by altering four sound parameters: attack, decay, volume drop and vibrato. “Attack” is the time required for a note to rise from no volume to maximum volume. “Decay” is the time required for the note to drop to a steady (sustain) volume. In Music Synthesizer, a note remains at sustain volume until its duration is over. Then it drops instantly to zero volume. Sustain and release (zero-drop) times cannot be varied; the difference in sound is small.
“Volume drop” determines how far a note drops from maximum volume before sustaining, from no drop to a complete drop. “Vibrato” is a repeated shift to a new pitch, followed by a return to the original pitch. Both the vibrato range and speed are variable. The range determines how far from the original pitch the note travels before returning. The same range will create an apparently larger sound difference for high-pitched notes than it will for lower notes. The reason can be deduced from the chart of notes on page 58 of the Atari BASIC Reference Manual: the values for the lower pitches are farther apart. If the range you specify is large enough, the pitch will “wrap around” the scale. The result can sound rather like a crazed marimba player. Vibrato speed determines how fast this pitch exchange takes place.
The Atari BASIC program in Listing 1 is the main data and data checking routine. This listing is used to create both disk and cassette versions of Music Synthesizer. The DATA statements are listed in hexadecimal (base 16) to make the program fit in a 16K cassette-based system. It makes typing the program a little more difficult, but it’s a necessary evil.
Listing 2 must be added to Listing 1 if you’re using a 410 or 1010 cassette recorder.
Listing 3 must be added to Listing 1 if you’re using a disk drive.
Listing 4 is the assembly-language source code for Music Synthesizer, written with the Atari Assembler/Editor cartridge. You do not have to type in Listing 4 to use the Synthesizer! It’s provided for those readers who are interested in seeing how the program works.
If everything worked, Music Synthesizer’s main menu will be displayed. Type the letter of your choice. The options are:
Listen. Listen to a song. Push SELECT to stop a song before it ends. If no Music Composer files have been entered, the Synthesizer will print “No Data.”
Retrieve. Enter a Music Composer file from disk or cassette. When prompted for a device name, type “C” for cassette or “D(number):filename” for disk.
Number. Number of times to play a song. The program counts down to zero; the default value is one. Pushing SELECT during playback resets Number to one.
Phrase Number. Which phrase to change (0-9). Hitting RETURN here will bring you back to the top of the main menu. If the selected phrase wasn’t used in your file, the program will display a “No Data” message. Otherwise, it will prompt you for sound parameters:
Attack Rate. 1 is the fastest, 255 the slowest.
Decay Rate. Same as Attack.
Volume Drop. 0 is no drop, 7 is a complete drop (like a drum).
Vibrato Range. 0 is no vibrato, 1 is the smallest range, 255 the largest.
Vibrato Speed. 1 is the quickest, 255 the slowest.
Tempo. Speed to play the song. 1 is the quickest (useful for hyperkinetics), 255 is the slowest (for water torture).
Hitting RETURN over any parameter value will leave that value the same. Hitting SYSTEM RESET restores all default values.
The rest is up to you. Enjoy all the variations possible, but have some sympathy for your friends and neighbors, too. “99 Bottles of Beer on the Wall” might drive them crazy, no matter how nice it sounds.
1 REM *** MUSIC SYNTHESIZER *** 10 DATA 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0,10,11,12,13,14,15 20 DIM DAT$(91),HEX(22):FOR X=0 TO 22: READ N:HEX(X)=N:NEXT X:LINE=990:RESTOR E 1000:TRAP 60:? "CHECKING DATA" 25 LINE=LINE+10:? "LINE:";LINE:READ DA T$:IF LEN(DAT$)<>90 THEN 110 28 DATLIN=PEEK(183)+PEEK(184)*256:IF D ATLIN<>LINE THEN ? "LINE ";LINE;" MISS ING!":END 30 FOR X=1 TO 89 STEP 2:D1=ASC(DAT$(X, X))-48:D2=ASC(DAT$(X+1,X+1))-48:BYTE=H EX(D1)*16+HEX(D2) 35 IF PASS=2 THEN PUT #1,BYTE:NEXT X:R EAD CHKSUM:GOTO 25 40 TOTAL=TOTAL+BYTE:IF TOTAL>999 THEN TOTAL=TOTAL-1000 45 NEXT X:READ CHKSUM:IF TOTAL=CHKSUM THEN 25 50 GOTO 110 60 IF PEEK(195)<>6 THEN 110 100 ? "WRITING FILE":PASS=2:LINE=990:R ESTORE 1000:TRAP 60:GOTO 25 110 ? "BAD DATA: LINE ";LINE:END 1000 DATA D820E22020A32120A3264C3C25A2 30A97F9D4403A9289D450320B2263028A9079D 4203A9009D4403A92B9D4503,888 1010 DATA A9009D4803A92A9D49032056E4C0 88D008A90C9D42034C56E484D4208C26A2BAA0 27209624A230203E2068684C,359 1020 DATA C521A90085A0A02B84A1A8B1A0C9 AAD035C8D002E6A1B1A0300AAA9480A5A19581 4C8D20A90385A0B1A00A0A0A,11 1030 DATA 85C1A90085A0C8D002E6A1B1A0C9 FFF008C8D0F2E6A14C8D20C8D002E6A1B1A0C9 FFD0BAA206BDDA2085A6A92A,36 1040 DATA 85A7A9FFA00191A688B59485A0B5 9585A1F00EB1A091A6C8B1A091A6C8C9FFD0F2 CACA10D460002A402A802AC0,708 1050 DATA 2AA21BA9009580CA10FB60A203A9 0685A8B5B9F031C902D0034C3123202F21C907 B01FA8B9152185A6B91C2185,432 1060 DATA A7202F216CA600F245549D8EF297 20212121212021A90095B9CA8A0A85A810C460 FE1C06BD412185A0A92A85A1,136 1070 DATA BC1C06B1A060004080C038E9010A DE2006F0A49D1C064CF220A8B934069D0806B9 3E069D0C06B948069D1006B9,317 1080 DATA 52069D1406B95C069D1806980AA8 A90195B99D2C06B9800095B0B9810095B4F002 F6B94CF2200A09A09D28064C,861 1090 DATA F2209D20064CF2209D24064CF220 A9FD8D3002A9268D3102A9628DC80260204925 20E22020D325200D20205C20,892 1100 DATA 4C242220C6264820482468C94CF0 16C952F0DEC94EF031C950F06BC954F049208C 264CC521A59505970599059B,4 1110 DATA D0034C5324A265A02720712620CD 2420EC2020B1242038254C0A25A28FA0272071 262066251006209D264C0A22,348 1120 DATA D006AAF0F5CA86B82048244CC521 A5C1209024A212A027202B231006209D264C2A 22D0E4AAF0F585C14C2422A2,368 1130 DATA 1FA027202B23D0D3C90A9006209D 264C4822AAF0F785A80AA8B98100D0034C5324 204824A6A8BD3406209024A2,848 1140 DATA 2CA027202B231006208C264C6A22 D008AAF0F5A6A89D3406204824A6A8BD3E0620 9024A23AA027202B23100620,595 1150 DATA 8C264C8E22D008AAF0F5A6A89D3E 06204824A6A8A9AE38FD48064A209024A280A0 27202B233006D017C9089006,188 1160 DATA 208C264CB2220A85AFA9AE38E5AF A6A89D4806204824A6A8BD5206209024A247A0 27202B231006208C264CE322,450 1170 DATA D005A6A89D5206204824A6A8BD5C 06209024A256A027202B231006208C264C0423 D008AAF0F5A6A89D5C064C24,691 1180 DATA 222071264C6625A4A8BD2C06D012 DE3006F0034C2721B59CD0069900D29901D2F6 B0D002F6B4B59CD011A90195,804 1190 DATA C6BD08069D0006A9A29901D295A9 A900A885AE95BD9D2C06BD18069D0406B5B085 A0B5B485A1B1A0C955D00B98,255 1200 DATA 95C6A4A89901D24C1A24C956D005 A9244C0F24C97FD009F6B0D0ABF6B44C4A23C9 FFD010A90095C6A4A89901D2,117 1210 DATA A90195B94CF2204A26AE4A26AEA0 00C9079006E907C84CC02348B9012485AD68A8 B9082465ADA4AE79052485AD,985 1220 DATA BD2406C981B00B65ADC9259024E9 244CE723297F85AFA5ADE5AFC925901369234C F823000C182400FF01000204,488 1230 DATA 0507090BA8B9D82695C2A4A89900 D2F6B0D002F6B4B5B085A0B5B485A1A000949C B1A00A369C4AA8B93B249D30,276 1240 DATA 064C27210406080C101820304060 80C0A227A9009D5728CA10FA60204824A278A0 272071264CC521A203A006DE,941 1250 DATA 0406D021B5BDF00AB5C29900D2D6 BD4C8424B5C2F00F38FD14069900D2F6BDBD18 069D04068888CA10D56085D4,490 1260 DATA A2ABA02720682685D520AAD920E6 D8D8A0FFC8B1F3291F996628B1F310F460A5C1 85AFA90485A6A03088D0FD20,808 1270 DATA 1826C6A6D0F4206024C6AFD0E960 2029258D08D22065E4A203A006A900959C95B0 95B495C69D24069D20069D1C,975 1280 DATA 06A9019D2C0695B9A9AA9D2806A9 A095A9B99500D00295B9CA888810D160A5B905 BA05BB05BCD012A5B8F005C6,498 1290 DATA B84CE7212048242029254CC5214C FE21A206A9009D01D29D00D2CACA10F660A511 D029C61120292585B8204824,988 1300 DATA 4CC521A209A9409D3E069D5C06A9 019D3406A9009D5206A9A29D4806CA10E660A9 0085AFCE7A2820C626C99BF0,796 1310 DATA 39C97ED013A5AFF0F1C6AFA6AFA9 FF9D7A28FE7B284C6D25A6AFE003F0DCC93090 D8C93AB0D4A6AF9D6606E91F,478 1320 DATA 9D7A28A9FF9D7B28E6AF4C6D25A6 AFD003E6AF60A96685F3A90685F4A9009D6606 85F22000D820D2D9D8A5D4A6,695 1330 DATA D5F002A28060A29EA02720682685 AFCE6B2820C626A6AFC97ED0108AF0F4C6AFA9 FF9D6A28FE6B284CDF259D7F,793 1340 DATA 28C99BF01538E9209D6B28A9FF9D 6C28E6AFE8E011D0CE4CE825FE6B2860A203A0 06B5C6F042DE0006D03DC901,653 1350 DATA F007C902F0204C6226F6A9B5A999 01D2DD2806F006BD08064C4726F6C6BD0C069D 00064C6226B5A9DD1006F00C,488 1360 DATA 900AD6A9B5A99901D24C4426F6C6 8888CA10B560A95785A4A9284C7726A96B85A4 A92885A586A284A3A000B1A2,297 1370 DATA F01938E92091A4C84C7F26A020A2 7F8E1FD08E0AD4CA10F78810F260208C264C48 24A220203E20A9C49D4403A9,290 1380 DATA 269D4503A9039D4203A9049D4A03 A9009D4B034C56E44B9BA220A9009D48039D49 03A9079D42034C56E4F3E6D9,696 1390 DATA CCC1B6ADA29990888079726C6660 5B55514C4844403C3935322F2D2A282523211F 1D70707047CA270770700707,701 1400 DATA 0707077070070741FD2654454D50 4F20312D3235353A005048524153452023312D 393A0041545441434B20312D,338 1410 DATA 3235353A00444543415920312D32 35353A00562E52414E474520302D3235353A00 562E535045454420312D3235,816 1420 DATA 353A005055534820425245414B20 544F2053544F50004E4F204441544100564F4C 2E2044524F5020302D373A00,526 1430 DATA 5245504541545320312D3235353A 00444556494345204E414D453F004355525245 4E542056414C55453A00492F,352 1440 DATA 4F204552524F5220434F44453A00 00006D757369630073796E74686573697A6572 00000000627900006B656E00,536 1450 DATA 636F6C6C69657200000000002C69 7374656E000000000000000000000000000032 657472696576650000000000,679 1460 DATA 000000000000002E756D62657200 00000000000000000000000000306872617365 006368616E67650000000000,457 1470 DATA 000034656D706F00000000000000 00000000000000000000000000000000000000 000000000000000000000000,942
2 REM *** CASSETTE VERSION *** 65 IF PASS=2 THEN FOR X=1 TO 109:PUT #1,0:NEXT X:CLOSE #1:END 70 ? "READY CASSETTE AND PRESS RETURN" ;:OPEN #1,8,128,"C:":RESTORE 200:FOR X =1 TO 35:READ N:PUT #1,N:NEXT X 200 DATA 0,18,221,31,255,31,169,60,141 ,2,211,169,0,141,231,2,133,14,169,56,1 41,232,2 210 DATA 133,15,169,0,133,10,169,32,13 3,11,24,96
2 REM *** DISK VERSION *** 65 IF PASS=2 THEN PUT #1,224:PUT #1,2:PUT #1,225:PUT #1,2:PUT #1,0:PUT #1,32 :CLOSE #1:END 70 ? "INSERT DISK WITH DOS, PRESS RETU RN";:DIM IN$(1):INPUT IN$:OPEN #1,8,0, "D:SYN.OBJ" 90 PUT #1,255:PUT #1,255:PUT #1,0:PUT #1,32:PUT #1,111:PUT #1,40
; +-------------------+ ; | MUSIC SYNTHESIZER | ; | by Ken Collier | ; +-------------------+ ; ; Page Six variables. ; *= $0600 ; SYNCNT *= *+4 syn counter VIBCNT *= *+4 vibrato counter ATTACK *= *+4 attack counter DECAY *= *+4 decay counter DROP *= *+4 minimum volume VRANGE *= *+4 vibrato range VSPEED *= *+4 vibrato speed VCOUNT *= *+4 voice counter LINECNT *= *+4 line counter TRANSPOS *= *+4 transpose value HIGHVOL *= *+4 maximum volume GETNOTE *= *+4 need new note DURCNT *= *+4 duration count PATTACK *= *+10 phrase attack PDECAY *= *+10 phrase decay PDROP *= *+10 phrase min vol PVRANG *= *+10 phrase v.range PVSPEED *= *+10 phrase v.speed NMBUF *= *+4 numeric buffer ; ; Page Zero variables. ; *= $80 ; TABLE *= *+28 voices/phrases TIED *= *+4 tied note flags PTR *= *+2 temp index PINDX *= *+2 print index P2NDX *= *+2 print index 2 TMP2 *= *+2 temporary 2 XSTORE *= *+1 voice index VOLNOW *= *+4 current volume NOTE *= *+1 note value ACCID *= *+1 accidental TMP *= *+1 temporary PTRL *= *+4 phrase addr low PTRH *= *+4 phrase addr hi REPEAT *= *+1 repeat music STATUS *= *+4 voice status VSTAT *= *+4 vibrato status TEMPO *= *+1 music speed VNOTE *= *+4 vibrato sound SYNSTAT *= *+4 syn status ; FR0 = $D4 F.P. register 0 CIX = $F2 F.P. index INBUFF = $F3 F.P. buffer ; ; Miscellaneous labels. ; CIOV = $E456 CIO vector SIOINV = $E465 SIO init vector AFP = $D800 ASCII to F.P. FASC = $D8E6 F.P. to ASCII IFP = $D9AA INTEGER to F.P. FPI = $D9D2 F.P. to INTEGER ICCOM = $0342 command byte ICBAL = $0344 buffer addr lo ICBAH = $0345 buffer addr hi ICBLL = $0348 buffer len lo ICBLH = $0349 buffer len hi ICAX1 = $034A aux info 1 ICAX2 = $034B aux info 2 OPEN = $03 open command CLOSE = $0C close command READ = $07 get characters EOF = 136 end file flag IOCB2 = $20 IOCB #2 offset IOCB3 = $30 IOCB #3 offset WSPACE = $2A00 workspace addr BUFS = $2A00 buffer size AUDF1 = $D200 audio freq 1 AUDC1 = $D201 audio volume 1 SDLSTL = $0230 display list COLBK = $02C8 backgnd color BRKKEY = $11 break key flag CONSOL = $D01F console speaker SKCTL = $D20F serial I/O ctrl AUDCTL = $D208 audio control WSYNC = $D40A wait for sync RETURN = $9B return key BACKS = $7E backspace key VLEN = $40 voice buff len ; *= $2000 loads here! ; ; Setup the program. ; START CLD clear decimal JSR CLRTBL clear tables JSR SETDL set display JSR OPENKBD open keyboard JMP STOPIT continue ; ; Input MUSIC COMPOSER files. ; DATAIN LDX #IOCB3 use IOCB #3 LDA #NAME&$FF filename lo STA ICBAL,X buffer addr lo LDA #NAME/256 filename hi STA ICBAH,X buffer addr hi JSR OPENIT open file BMI ERROR error? LDA #READ get characters STA ICCOM,X command byte LDA #BUFF&$FF read buffer lo STA ICBAL,X buffer addr lo LDA #BUFF/256 read buffer hi STA ICBAH,X buffer addr hi LDA #BUFS&$FF read len lo STA ICBLL,X buffer len lo LDA #BUFS/256 read len hi STA ICBLH,X buffer len hi JSR CIOV read music data CPY #EOF end of file? BNE ERROR yes. CLOSEIT LDA #CLOSE close cmd STA ICCOM,X command byte JMP CIOV close file ; ; Input/output error handler. ; ERROR STY FR0 save error code JSR BELL ring bell LDX #ETX&$FF I/O err msg lo LDY #ETX/256 I/O err msg hi JSR CURNT2 print msg+code LDX #IOCB3 use IOCB 3 JSR CLOSEIT close file PLA pull two byte PLA return address JMP CHOICE choose option ; ; Read through data and find ; PHRASE data,VOICE data, and ; MISCELLANEOUS data and store ; location in table. ; POINTERS LDA #0 get zero STA PTR index lo LDY #BUFF/256 buff addr hi STY PTR+1 index hi TAY set Y to zero SET1 LDA (PTR),Y get byte CMP #170 header byte? BNE SET2 no. INY increment index BNE WHICH1 overflow? INC PTR+1 yes, inc high WHICH1 LDA (PTR),Y get byte BMI MISC misc. record? TAX use as index STY TABLE,X save address LDA PTR+1 of record in STA TABLE+1,X table JMP ENDMARK continue MISC LDA #3 offset by 3 STA PTR bytes in index LDA (PTR),Y get tempo ASL A times 2 ASL A times 4 ASL A times 8 STA TEMPO save tempo LDA #0 get zero to STA PTR reset index ENDMARK INY inc index BNE END1 overflow? INC PTR+1 yes. inc hi END1 LDA (PTR),Y get byte CMP #255 end of record? BEQ SET2 yes. INY inc index BNE ENDMARK overflow? INC PTR+1 yes. inc hi JMP ENDMARK continue SET2 INY inc index BNE SET3 overflow? INC PTR+1 yes. inc hi SET3 LDA (PTR),Y get byte CMP #255 end of file? BNE SET1 no. ; ; Transfer the VOICE records ; to storage areas set aside. ; LDX #6 set counter TR1 LDA TRVTBL,X voice addr lo STA TMP2 index lo LDA #VC1/256 voice addr hi STA TMP2+1 index hi LDA #255 end of rec flag LDY #1 record offset STA (TMP2),Y init record DEY zero Y register LDA TABLE+20,X voice rec lo STA PTR index lo LDA TABLE+21,X voice rec hi STA PTR+1 index hi BEQ TRN empty voice rec TR2 LDA (PTR),Y put voice byte STA (TMP2),Y in voice buf INY inc index LDA (PTR),Y put 2nd byte STA (TMP2),Y in voice buf INY inc index CMP #255 end of voice? BNE TR2 no. TRN DEX yes. dec count DEX index by 2 BPL TR1 all voices? RTS yes. continue ; TRVTBL .WORD VC1,VC2 voice addr .WORD VC3,VC4 table ; ; Erase old table before input. ; CLRTBL LDX #27 init index LDA #0 get zero CT1 STA TABLE,X store table DEX dec index BPL CT1 done? RTS yes. continue ; ; Check to see if a VOICE ; needs a new record. ; NEEDREC LDX #3 init index LDA #6 init voice index STA XSTORE save voice index NEXTREC LDA STATUS,X active? BEQ INCX no. CMP #2 need voice rec? BNE GETREC yes. JMP PLAYIT play note ; ; Get a new VOICE record ; and process the info. ; GETREC JSR FINDREC record byte CMP #7 valid commands? BCS STATUS1 no. end voice TAY make index LDA JMPTBL,Y jmp addr lo STA TMP2 jmp pointer lo LDA JMPTBH,Y jmp addr hi STA TMP2+1 jmp pointer hi JSR FINDREC operand byte JMP (TMP2) go to it ; JMPTBL .BYTE NEXTREC&$FF,GOTO1&$FF .BYTE PLAY1&$FF,TRANS1&$FF .BYTE VOLUME1&$FF,NEXTREC&$FF .BYTE COUNT1&$FF JMPTBH .BYTE NEXTREC/256,GOTO1/256 .BYTE PLAY1/256,TRANS1/256 .BYTE VOLUME1/256,NEXTREC/256 .BYTE COUNT1/256 ; ; Clear status. ; STATUS1 LDA #0 get zero STA STATUS,X stop voice ; ; Increment to next VOICE. ; INCX DEX dec index TXA put in Acc ASL A times 2 STA XSTORE voice index BPL NEXTREC done yet? RTS yes. continue ; ; Lookup next VOICE command ; in VOICE storage area. ; FINDREC INC VCOUNT,X voice cnt LDA FTBL,X voice addr lo STA PTR index lo LDA #VC1/256 voice addr hi STA PTR+1 index hi LDY VCOUNT,X voice count LDA (PTR),Y get byte RTS continue ; FTBL .BYTE VC1&$FF voice table .BYTE VC2&$FF .BYTE VC3&$FF .BYTE VC4&$FF ; ; If VOICE record is a GOTO ; then process the jump. ; GOTO1 SEC set carry SBC #1 subtract 1 ASL A multiply by 2 DEC LINECNT,X dec linecount BEQ NEXTREC done? yes. STA VCOUNT,X point to line JMP NEXTREC get next record ; ; Set sound parameters for ; PHRASE number and set index to ; PHRASE's location in memory. ; PLAY1 TAY use as index LDA PATTACK,Y attack shadow STA ATTACK,X attack count LDA PDECAY,Y decay shadow STA DECAY,X decay count LDA PDROP,Y min vol shadow STA DROP,X min vol value LDA PVRANG,Y f-range shadow STA VRANGE,X freq. range LDA PVSPEED,Y speed shadow STA VSPEED,X vibrato speed TYA index to Acc ASL A multiply by 2 TAY to index again LDA #1 get one STA STATUS,X get rec status STA GETNOTE,X need a note LDA TABLE,Y phrase addr lo STA PTRL,X phrase tbl lo LDA TABLE+1,Y phrase addr hi STA PTRH,X phrase tbl hi BEQ PLAY2 data in table? INC STATUS,X yes. play it PLAY2 JMP NEXTREC get next rec ; ; Set volume. ; VOLUME1 ASL A times 2 ORA #$A0 pure tone STA HIGHVOL,X save it JMP NEXTREC get next rec ; ; Store the PHRASE count. ; COUNT1 STA LINECNT,X save count JMP NEXTREC get next rec ; ; Do transposition. ; TRANS1 STA TRANSPOS,X transpose JMP NEXTREC get next rec ; ; Iinitialize screen. ; SETDL LDA #DLIST&$FF display lo STA SDLSTL DL pointer lo LDA #DLIST/256 display hi STA SDLSTL+1 DL pointer hi LDA #$62 dark blue STA COLBK background RTS continue ; ; Get new file. ; RETRIEVE JSR DEFAULT do defaults JSR CLRTBL clear table JSR NAMEIT get filename JSR DATAIN read data JSR POINTERS set pointers JMP NMBR1 back to menu ; ; Get user input from keyboard. ; CHOICE JSR GETKEY get character PHA push value JSR ERASE erase window PLA pull value CMP #'L play the music? BEQ LISTEN yes. CMP #'R load file? BEQ RETRIEVE yes. CMP #'N repeat count? BEQ NUMBER yes. CMP #'P phrase change? BEQ CPHRASE yes. CMP #'T tempo change? BEQ CTEMPO yes. JSR BELL ring bell JMP CHOICE try again ; ; LISTEN to the music. ; LISTEN LDA TABLE+21 OR together ORA TABLE+23 high bytes of ORA TABLE+25 voice pointers ORA TABLE+27 to see if any BNE LIS1 are active? JMP NODATA no. LIS1 LDX #QTX&$FF play msg lo LDY #QTX/256 play msg hi JSR PRINTD print msg JSR PDEFAULT set defaults PLAYING JSR NEEDREC voice rec JSR DELAY synthesizer JSR BRKCHK break key? JMP FINISHED all done? ; ; Store # times to repeat music. ; NUMBER LDX #NMTX&$FF number msg lo LDY #NMTX/256 number msg hi JSR PRINTD print msg JSR GETNUM get number BPL NMBR0 range error? NMBRE JSR RINGER yes. ring bell JMP NUMBER try again NMBR0 BNE NMBR1 return? TAX no. put in X BEQ NMBRE value zero? DEX no. 1-255 STX REPEAT store 0-254 NMBR1 JSR ERASE erase window JMP CHOICE goto menu ; ; Change TEMPO of playback. ; CTEMPO LDA TEMPO get tempo JSR CURRENT print it LDX #TTX&$FF tempo msg lo LDY #TTX/256 tempo msg hi JSR TXNUM ? msg / get num BPL TEM1 numeric error? TEME JSR RINGER yes. ring bell JMP CTEMPO try again TEM1 BNE NMBR1 return? TAX no. BEQ TEME zero value? STA TEMPO no. 1-255 JMP NMBR1 continue ; ; Change parameters of ; sound for one phrase. ; CPHRASE LDX #PTX&$FF phrase msg lo LDY #PTX/256 phrase msg hi JSR TXNUM print/get num BNE NMBR1 return? CMP #10 no. >9? BCC C1C no. C1E JSR RINGER ring bell JMP CPHRASE try again C1C TAX test zero flag BEQ C1E num = zero? STA XSTORE no. save number ASL A times 2 TAY use as index LDA TABLE+1,Y phrase addr hi BNE C3 active phrase? JMP NODATA no. C3 JSR ERASE erase window LDX XSTORE get phrase # LDA PATTACK,X attack value JSR CURRENT print it LDX #ATX&$FF attack msg lo LDY #ATX/256 attack msg hi JSR TXNUM print/get num BPL C3A numeric error? C3E JSR BELL no. ring bell JMP C3 try again C3A BNE C5A return? TAX no. test zero BEQ C3E zero value? LDX XSTORE no. get index STA PATTACK,X save attack C5A JSR ERASE erase window LDX XSTORE get index LDA PDECAY,X decay value JSR CURRENT print it LDX #DTX&$FF decay msg lo LDY #DTX/256 decay msg hi JSR TXNUM print/get num BPL C5B numeric error? C5E JSR BELL yes. ring bell JMP C5A try again C5B BNE C6A return? TAX no. test zero BEQ C5E zero? LDX XSTORE no. get index STA PDECAY,X new decay C6A JSR ERASE erase window LDX XSTORE get index LDA #$AE maximum volume SEC set carry flag SBC PDROP,X sub min volume LSR A 0..14 => 0..7 JSR CURRENT print it LDX #VTX&$FF v.drop msg lo LDY #VTX/256 v.drop msg hi JSR TXNUM print/get num BMI C6E numeric error? BNE C8A no. return? CMP #8 no. >7? BCC C8C no. continue C6E JSR BELL yes. ring bell JMP C6A try again C8C ASL A multiply by 2 STA TMP use later LDA #$AE maximum volume SEC set carry SBC TMP subtract drop LDX XSTORE get index STA PDROP,X minimum volume C8A JSR ERASE erase window LDX XSTORE get index LDA PVRANG,X vibrato range JSR CURRENT print it LDX #RTX&$FF v.range msg lo LDY #RTX/256 v.range msg hi JSR TXNUM print/get num BPL C8B numeric error? JSR BELL yes. ring bell JMP C8A try again C8B BNE C10A return? LDX XSTORE no. get index STA PVRANG,X vibrato range C10A JSR ERASE erase window LDX XSTORE get index LDA PVSPEED,X vibrato speed JSR CURRENT print it LDX #STX&$FF v.speed msg lo LDY #STX/256 v.speed msg hi JSR TXNUM print/get num BPL C10B numeric error? C10E JSR BELL yes. ring bell JMP C10A try again C10B BNE C11A return? TAX no. test zero BEQ C10E yes. error LDX XSTORE get index STA PVSPEED,X vibrato speed C11A JMP NMBR1 continue ; TXNUM JSR PRINTD print string JMP GETNUM get number ; ; Play the notes. ; PLAYIT LDY XSTORE get index LDA GETNOTE,X need note? BNE NEWNOTE yes. get a note DEC DURCNT,X dec note length BEQ NOTEDONE note done? yes. JMP INCX another voice NOTEDONE LDA TIED,X note tied? BNE NEWNOTE yes. skip STA AUDF1,Y poke ultrasonic STA AUDC1,Y zero volume NEWNOTE INC PTRL,X note pntr lo BNE NW2 overflow? no. INC PTRH,X note pntr hi NW2 LDA TIED,X note tied? BNE NW3A yes. skip LDA #1 attack token STA SYNSTAT,X set status LDA ATTACK,X attack count STA SYNCNT,X save count LDA #$A2 starting volume STA AUDC1,Y poke hardware STA VOLNOW,X save volume NW3A LDA #0 get zero TAY set Y to zero STA ACCID init accidental STA VSTAT,X vibrato status STA GETNOTE,X not any more LDA VSPEED,X vibrato speed STA VIBCNT,X speed count LDA PTRL,X phrase ptr lo STA PTR save pointer lo LDA PTRH,X phrase ptr hi STA PTR+1 save pointer hi LDA (PTR),Y get note CMP #85 rest? BNE NW3 no. skip TYA Acc = 0 STA SYNSTAT,X synthesizer off LDY XSTORE get index STA AUDC1,Y zero volume JMP GETDURA get duration NW3 CMP #86 Cf6? BNE NW4 no. skip LDA #36 C6 index JMP NW40 get frequency NW4 CMP #127 measure? BNE NW5 no. skip INC PTRL,X note pointer lo BNE NEWNOTE overflow? no. INC PTRH,X note pointer hi JMP NEWNOTE get a new note NW5 CMP #255 end of phrase? BNE NW6 no. skip LDA #0 get a zero STA SYNSTAT,X synthesizer off LDY XSTORE get index STA AUDC1,Y zero volume LDA #1 get voice token STA STATUS,X set status JMP NEXTREC next voice rec NW6 LSR A divide by 2 ROL ACCID carry into ACCID LSR A divide by 4 ROL ACCID carry into ACCID LDY #0 init octave # NW7 CMP #7 compare 0..7 BCC NW8 in range? yes. SBC #7 subtract 7 INY inc octave # JMP NW7 try again NW8 PHA save note # LDA OCTBL,Y octave offset STA NOTE save octave PLA get note # TAY use as index LDA NOTBL,Y note offset ADC NOTE add octave LDY ACCID accidental ADC ACTBL,Y accid offset STA NOTE save note LDA TRANSPOS,X transpose CMP #129 up/down point BCS TRANSDN down? ADC NOTE add note NW36 CMP #37 in range? BCC NW40 no. skip SBC #36 make in range JMP NW36 try again TRANSDN AND #$7F transpose STA TMP save it LDA NOTE get note SBC TMP transpose it NW37 CMP #37 in range? BCC NW40 yes. skip ADC #36-1 make in range JMP NW37 try again ; OCTBL .BYTE 0,12,24,36 octave table ACTBL .BYTE 0,$FF,1 accidental table NOTBL .BYTE 0,2,4,5 note table .BYTE 7,9,11 ; NW40 TAY use as index LDA NOTES,Y get actual note STA VNOTE,X save it LDY XSTORE get index STA AUDF1,Y poke hardware ; ; Compute note duration. ; GETDURA INC PTRL,X pointer tbl lo BNE GDUR1 overflow? INC PTRH,X yes.pointer hi GDUR1 LDA PTRL,X pointer tbl lo STA PTR pointer lo LDA PTRH,X pointer tbl hi STA PTR+1 pointer hi LDY #0 get zero STY TIED,X zero tied flag LDA (PTR),Y duration byte ASL A minus => carry ROL TIED,X carry => 1=tied LSR A rotate back TAY use as index LDA GDTBL-1,Y actual duration STA DURCNT,X save it JMP INCX next voice ; GDTBL .BYTE 4,6,8 duration table .BYTE 12,16,24 .BYTE 32,48,64 .BYTE 96,128,192 ; ; Erase text at screen bottom. ; ERASE LDX #39 40 bytes 0..39 LDA #0 display blank ERSC STA TX1,X store in window DEX decrement index BPL ERSC done? RTS yes. continue ; ; If no data in memory. ; NODATA JSR ERASE erase window LDX #NTX&$FF no data msg lo LDY #NTX/256 no data msg hi JSR PRINTD print it JMP CHOICE goto menu ; ; Add VIBRATO. ; VIBRATO LDX #3 init index LDY #6 hardware index VBRT DEC VIBCNT,X v.speed count BNE VNXTX done? yes. LDA VSTAT,X vibrato status BEQ RAISE 0=raise pitch LDA VNOTE,X get frequency STA AUDF1,Y poke hardware DEC VSTAT,X vstat=0 JMP PUTVIB continue RAISE LDA VNOTE,X get frequency BEQ VNXTX 0=no sound SEC set carry SBC VRANGE,X sub vib range STA AUDF1,Y poke hardware INC VSTAT,X vstat=1 PUTVIB LDA VSPEED,X vibrato speed STA VIBCNT,X v.speed count VNXTX DEY hardware index DEY do it twice DEX dec index BPL VBRT done? no. RTS continue ; ; Print current sound value. ; CURRENT STA FR0 save value LDX #CTX&$FF current msg lo LDY #CTX/256 current msg hi CURNT2 JSR PRINTU print in top STA FR0+1 zero in F.P. hi JSR IFP INTEGER to F.P. JSR FASC F.P. to ASCII CLD clear decimal LDY #$FF init index PNUM INY inc index LDA (INBUFF),Y get ASCII AND #$1F to display code STA TX1+15,Y put msg window LDA (INBUFF),Y get ASCII BPL PNUM end of string RTS yes. continue ; ; Add delay to create TEMPO. ; DELAY LDA TEMPO music tempo STA TMP save it DEL LDA #4 do SYN 4 times STA TMP2 save count DEL1 LDY #48 delay loop cnt DEL2 DEY dec count BNE DEL2 done? no. JSR SYN synthesizer DEC TMP2 dec 4 times cnt BNE DEL1 done? no. JSR VIBRATO vibrato effect DEC TMP dec tempo value BNE DEL done? no. RTS continue ; ; Plug in default values. ; PDEFAULT JSR SHUTOFF zero sounds STA AUDCTL zero audio ctrl JSR SIOINV init hardware LDX #3 init index LDY #6 init tbl index DEF1 LDA #0 get zero STA TIED,X zero tied STA PTRL,X phrase tbl lo STA PTRH,X phrase tbl hi STA SYNSTAT,X syn status STA TRANSPOS,X transpose STA LINECNT,X voice line STA VCOUNT,X voice rpt cnt LDA #1 get one STA GETNOTE,X need new note STA STATUS,X need new record LDA #$AA define high vol STA HIGHVOL,X new high vol LDA #$A0 current volume STA VOLNOW,X new volnow LDA TABLE+21,Y voice tbl hi BNE DEF2 active? yes. STA STATUS,X shut off voice DEF2 DEX dec index DEY dec table index DEY do it twice BPL DEF1 done? no. RTS continue ; ; Check if all VOICEs are done. ; FINISHED LDA STATUS OR status ORA STATUS+1 of all voices ORA STATUS+2 together. if ORA STATUS+3 result <> 0, BNE ALLP keep playing LDA REPEAT play again? BEQ ALL3 no. end it DEC REPEAT dec repeat val JMP LISTEN play it again ALL3 JSR ERASE erase window JSR SHUTOFF stop all sound JMP CHOICE goto menu ALLP JMP PLAYING cont playing ; ; Stop all sound. ; SHUTOFF LDX #6 init index LDA #0 zero sound val SHUT STA AUDC1,X zero volume STA AUDF1,X zero frequency DEX dec index DEX do it again BPL SHUT done? no. RTS continue ; ; Check if the BREAK key ; has been pushed. ; BRKCHK LDA BRKKEY get break key BNE BRKX key hit? no. STOPIT DEC BRKKEY reset break JSR SHUTOFF turn off sound STA REPEAT reset repeat JSR ERASE erase window JMP CHOICE goto menu ; ; Set sound values to default. ; DEFAULT LDX #9 do ten phrases SYND LDA #64 default STA PDECAY,X decay STA PVSPEED,X vibrato speed LDA #1 default STA PATTACK,X attack LDA #0 default STA PVRANG,X vibrato range LDA #$A2 default STA PDROP,X minimum volume DEX dec index BPL SYND done? no. BRKX RTS continue ; ; Input and print a number. ; GETNUM LDA #0 get zero STA TMP save as pointer DEC TX2+15 cursor $FF GN1 JSR GETKEY get a letter CMP #RETURN is it a return BEQ GN3 yes. goto exit CMP #BACKS backspace? BNE GN2 no. goto GN2 LDA TMP get pointer BEQ GN1 left margin=0? DEC TMP dec pointer LDX TMP use as index LDA #$FF cursor char STA TX2+15,X put cursor INC TX2+16,X make space JMP GN1 get a char GN2 LDX TMP get pointer CPX #3 three digits? BEQ GN1 yes. try again CMP #'0 .LT. ASCII 0? BCC GN1 yes. try again CMP #'9+1 .GT. ASCII 9? BCS GN1 yes. try again LDX TMP get index STA NMBUF,X put in buffer SBC #32-1 display code STA TX2+15,X put in window LDA #$FF cursor char STA TX2+16,X put cursor INC TMP inc position JMP GN1 get a char GN3 LDX TMP get pointer BNE GN4 any characters? INC TMP no. set <>zero RTS continue GN4 LDA #NMBUF&$FF buffer adr lo STA INBUFF inbuff pntr lo LDA #NMBUF/256 buffer adr hi STA INBUFF+1 inbuff pntr hi LDA #0 non numeric STA NMBUF,X number +1 STA CIX FP offset JSR AFP ASCII to F.P. JSR FPI F.P. to INTEGER CLD clear decimal LDA FR0 get value LDX FR0+1 test >255 BEQ GN5 no. ok. LDX #$80 set minus flag GN5 RTS continue ; ; Name the INPUT device. ; NAMEIT LDX #ITX&$FF device msg lo LDY #ITX/256 device msg hi JSR PRINTU print it STA TMP init index DEC TX2 init cursor $FF NMT1 JSR GETKEY get character LDX TMP get index CMP #BACKS backspace? BNE NMT2 no. on screen NMTD TXA yes. test index BEQ NMT1 left margin=0? DEC TMP position left LDA #$FF get cursor STA TX2-1,X put screen INC TX2,X turn off cursor JMP NMT1 try again NMT2 STA NAME,X ascii name buf CMP #RETURN return key? BEQ NMT5 yes. goto NMT5 SEC set carry SBC #32 display code STA TX2,X put screen LDA #$FF get cursor STA TX2+1,X put screen INC TMP inc index sav INX inc index CPX #17 18 char yet? BNE NMT1 no. get more JMP NMTD delete last NMT5 INC TX2,X delete cursor RTS continue ; ; Controls operation of ; the SYNTHESIZER section, ; attack and decay. ; SYN LDX #3 init index LDY #6 hardware index SYN2 LDA SYNSTAT,X voice active? BEQ NXTX no. do another DEC SYNCNT,X syn dur count BNE NXTX count done? no. CMP #1 do attack? BEQ ATTCK yes. CMP #2 do decay? BEQ DECY yes. JMP NXTX next voice ATTCK INC VOLNOW,X inc volume LDA VOLNOW,X current volume STA AUDC1,Y poke hardware CMP HIGHVOL,X at maximum? BEQ GODECAY yes. now decay LDA ATTACK,X attack duration JMP DEC3 continue GODECAY INC SYNSTAT,X make decay DEC2 LDA DECAY,X decay duration DEC3 STA SYNCNT,X syn dur count JMP NXTX next voice DECY LDA VOLNOW,X current volume CMP DROP,X compare low vol BEQ GOSUSTN =low volume? BCC GOSUSTN <low volume? DEC VOLNOW,X dec volume LDA VOLNOW,X current volume STA AUDC1,Y poke hardware JMP DEC2 continue GOSUSTN INC SYNSTAT,X stop voice NXTX DEY hardware index DEY do it twice DEX index BPL SYN2 all done? no. RTS continue ; ; Print text to screen. ; PRINTU LDA #TX1&$FF upper line lo STA P2NDX ? to pointer lo LDA #TX1/256 upper line hi JMP PR0 skip PRINTD LDA #TX2&$FF lower line lo STA P2NDX ? to pointer lo LDA #TX2/256 lower line hi PR0 STA P2NDX+1 ? to pointer hi STX PINDX X=text addr lo STY PINDX+1 Y=text addr hi LDY #0 init index PR1 LDA (PINDX),Y get ascii char BEQ PRX end of string? SEC no. set carry SBC #32 display code STA (P2NDX),Y put screen INY inc index JMP PR1 again ; ; Ring bell (CTRL/2). ; BELL LDY #$20 duration count BELL1 LDX #$7F inner loop BELL2 STX CONSOL poke speaker STX WSYNC wait for sync DEX dec inner loop BPL BELL2 done? no. DEY dec duration BPL BELL1 done? no. PRX RTS continue ; ; Ring bell and erase text. ; RINGER JSR BELL ring bell JMP ERASE erase text ; ; Open keyboard for INPUT. ; OPENKBD LDX #IOCB2 use IOCB #2 JSR CLOSEIT close #2 LDA #KBD&$FF file name lo STA ICBAL,X buffer addr lo LDA #KBD/256 file name hi STA ICBAH,X buffer addr hi OPENIT LDA #OPEN open command STA ICCOM,X command byte LDA #4 input option STA ICAX1,X aux1 byte LDA #0 zero STA ICAX2,X aux2 byte JMP CIOV open file ; ;Keyboard file name. ; KBD .BYTE "K",RETURN ; ; Get character from keyboard. ; GETKEY LDX #IOCB2 use IOCB #2 LDA #0 buffer length STA ICBLL,X buf length lo STA ICBLH,X buf length hi LDA #READ get character STA ICCOM,X command byte JMP CIOV get a character ; ; Values for NOTE pitches ; from low to high. ; NOTES .BYTE 243,230,217,204 .BYTE 193,182,173,162 .BYTE 153,144,136,128 .BYTE 121,114,108,102 .BYTE 96,91,85,81 .BYTE 76,72,68,64 .BYTE 60,57,53,50 .BYTE 47,45,42,40 .BYTE 37,35,33,31 .BYTE 29 ; ; Display list. ; DLIST .BYTE $70,$70,$70,$47 .WORD SCREEN .BYTE 7,$70,$70,7 .BYTE 7,7,7,7 .BYTE $70,$70,7,7 .BYTE $41 .WORD DLIST ; ; Words at screen bottom. ; TTX .BYTE "TEMPO 1-255:",0 PTX .BYTE "PHRASE #1-9:",0 ATX .BYTE "ATTACK 1-255:",0 DTX .BYTE "DECAY 1-255:",0 RTX .BYTE "V.RANGE 0-255:",0 STX .BYTE "V.SPEED 1-255:",0 QTX .BYTE "PUSH BREAK TO STOP",0 NTX .BYTE "NO DATA",0 VTX .BYTE "VOL. DROP 0-7:",0 NMTX .BYTE "REPEATS 1-255:",0 ITX .BYTE "DEVICE NAME?",0 CTX .BYTE "CURRENT VALUE:",0 ETX .BYTE "I/O ERROR CODE:",0 ; ; Data for screen menu. ; SCREEN .BYTE 0,0,"music",0 .BYTE "synthesizer",0 .BYTE 0,0,0,"by",0,0 .BYTE "ken",0,"collier",0,0 .BYTE 0,0,0,'L-32,"isten" .BYTE 0,0,0,0,0,0,0,0,0,0,0 .BYTE 0,0,0,'R-32,"etrieve" .BYTE 0,0,0,0,0,0,0,0,0 .BYTE 0,0,0,'N-32,"umber" .BYTE 0,0,0,0,0,0,0,0,0,0,0 .BYTE 0,0,0,'P-32,"hrase",0 .BYTE "change",0,0,0,0 .BYTE 0,0,0,'T-32,"empo" .BYTE 0,0,0,0,0,0 .BYTE 0,0,0,0,0,0 .BYTE 0 next line offset ; TX1 *= *+20 text window 1 TX2 *= *+20 text window 2 NAME *= *+20 file name buf ; *= WSPACE ; VC1 *= *+VLEN voice space 1 VC2 *= *+VLEN voice space 2 VC3 *= *+VLEN voice space 3 VC4 *= *+VLEN voice space 4 ; BUFF *= *+BUFS file space ; ; RUN address ; *= $02E0 file run addr .WORD START addr to goto ; .END