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