A.N.A.L.O.G. ISSUE 15 / JANUARY 1984 / PAGE 83

Music Synthesizer

16K cassette 24K disk
Requires Music Composer Cartridge

by Ken Collier

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.

Typing the program.

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.

Cassette instructions.

  1. Type Listing 1 into your computer and verify your typing with C:CHECK (see page 70).
  2. With Listing 1 correctly entered, type in Listing 2. The program lines will automatically merge with Listing 1. Make sure these new lines were typed correctly! It’s a good idea to CSAVE the entire program at this point.
  3. Type RUN and press RETURN. The program will begin checking the DATA lines, printing each line number in turn. You will be alerted if there are any problems. Fix incorrect lines and re-RUN the program as necessary until all errors are eliminated.
  4. When all DATA is correct, you will be prompted to “Ready cassette and press RETURN.” Put a blank cassette into your recorder, press the RECORD and PLAY keys simultaneously and hit RETURN. The message “Writing file” will appear and the program will create a boot- tape version of Music Synthesizer, displaying each line number as it goes. When the READY prompt reappears, the Synthesizer is ready to use. Be sure you have CSAVEd the BASIC program on a separate tape.
  5. Whenever you want to use Music Synthesizer, do the following: Rewind the tape created by the BASIC program to the beginning. Turn your computer OFF and remove all cartridges. Press the PLAY key on your recorder and turn your computer back ON while holding down the START key. The computer will “beep” once. Press RETURN and the Synthesizer will load and run automatically.

Disk instructions.

  1. Type Listing 1 into your computer and verify your typing with D:CHECK2 (see page 70).
  2. With Listing 1 correctly entered, type in Listing 3. The program lines will automatically merge with Listing 1. Make sure these new lines were typed correctly! It’s a good idea to SAVE the entire program at this point.
  3. Type RUN and press RETURN. The program will begin checking the DATA lines, printing each line number in turn. You will be alerted if there are any problems. Fix incorrect lines and re-RUN the program as necessary until all errors are eliminated.
  4. When all DATA is correct, you will be prompted to “Insert disk with DOS, press RETURN.” Put a disk containing DOS 2.0S in drive #1 and hit RETURN. The message “Writing file” will appear and the program will create an AUTORUN.SYS version of Music Synthesizer, displaying each line number as it goes. When the READY prompt reappears, the Synthesizer is ready to use. Be sure you have SAVEd the BASIC program.
  5. Whenever you want to use Music Synthesizer, do the following: Turn your computer OFF and remove all cartridges. Insert the disk containing the AUTORUN.SYS file into drive #1 and turn the computer back ON. Music Synthesizer will load and run automatically.

Using the Synthesizer.

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.

Listing 1.

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:RESTORE 1000:TRAP 60:? "CHECKING DATA"
25 LINE=LINE+10:? "LINE:";LINE:READ DAT$:IF LEN(DAT$)<>90 THEN 110
28 DATLIN=PEEK(183)+PEEK(184)*256:IF DATLIN<>LINE THEN ? "LINE ";LINE;" MISSING!":END 
30 FOR X=1 TO 89 STEP 2:D1=ASC(DAT$(X,X))-48:D2=ASC(DAT$(X+1,X+1))-48:BYTE=HEX(D1)*16+HEX(D2)
35 IF PASS=2 THEN PUT #1,BYTE:NEXT X:READ 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:RESTORE 1000:TRAP 60:GOTO 25
110 ? "BAD DATA: LINE ";LINE:END 
1000 DATA D820E22020A32120A3264C3C25A230A97F9D4403A9289D450320B2263028A9079D4203A9009D4403A92B9D4503,888
1010 DATA A9009D4803A92A9D49032056E4C088D008A90C9D42034C56E484D4208C26A2BAA027209624A230203E2068684C,359
1020 DATA C521A90085A0A02B84A1A8B1A0C9AAD035C8D002E6A1B1A0300AAA9480A5A195814C8D20A90385A0B1A00A0A0A,11
1030 DATA 85C1A90085A0C8D002E6A1B1A0C9FFF008C8D0F2E6A14C8D20C8D002E6A1B1A0C9FFD0BAA206BDDA2085A6A92A,36
1040 DATA 85A7A9FFA00191A688B59485A0B59585A1F00EB1A091A6C8B1A091A6C8C9FFD0F2CACA10D460002A402A802AC0,708
1050 DATA 2AA21BA9009580CA10FB60A203A90685A8B5B9F031C902D0034C3123202F21C907B01FA8B9152185A6B91C2185,432
1060 DATA A7202F216CA600F245549D8EF29720212121212021A90095B9CA8A0A85A810C460FE1C06BD412185A0A92A85A1,136
1070 DATA BC1C06B1A060004080C038E9010ADE2006F0A49D1C064CF220A8B934069D0806B93E069D0C06B948069D1006B9,317
1080 DATA 52069D1406B95C069D1806980AA8A90195B99D2C06B9800095B0B9810095B4F002F6B94CF2200A09A09D28064C,861
1090 DATA F2209D20064CF2209D24064CF220A9FD8D3002A9268D3102A9628DC8026020492520E22020D325200D20205C20,892
1100 DATA 4C242220C6264820482468C94CF016C952F0DEC94EF031C950F06BC954F049208C264CC521A59505970599059B,4
1110 DATA D0034C5324A265A02720712620CD2420EC2020B1242038254C0A25A28FA0272071262066251006209D264C0A22,348
1120 DATA D006AAF0F5CA86B82048244CC521A5C1209024A212A027202B231006209D264C2A22D0E4AAF0F585C14C2422A2,368
1130 DATA 1FA027202B23D0D3C90A9006209D264C4822AAF0F785A80AA8B98100D0034C5324204824A6A8BD3406209024A2,848
1140 DATA 2CA027202B231006208C264C6A22D008AAF0F5A6A89D3406204824A6A8BD3E06209024A23AA027202B23100620,595
1150 DATA 8C264C8E22D008AAF0F5A6A89D3E06204824A6A8A9AE38FD48064A209024A280A027202B233006D017C9089006,188
1160 DATA 208C264CB2220A85AFA9AE38E5AFA6A89D4806204824A6A8BD5206209024A247A027202B231006208C264CE322,450
1170 DATA D005A6A89D5206204824A6A8BD5C06209024A256A027202B231006208C264C0423D008AAF0F5A6A89D5C064C24,691
1180 DATA 222071264C6625A4A8BD2C06D012DE3006F0034C2721B59CD0069900D29901D2F6B0D002F6B4B59CD011A90195,804
1190 DATA C6BD08069D0006A9A29901D295A9A900A885AE95BD9D2C06BD18069D0406B5B085A0B5B485A1B1A0C955D00B98,255
1200 DATA 95C6A4A89901D24C1A24C956D005A9244C0F24C97FD009F6B0D0ABF6B44C4A23C9FFD010A90095C6A4A89901D2,117
1210 DATA A90195B94CF2204A26AE4A26AEA000C9079006E907C84CC02348B9012485AD68A8B9082465ADA4AE79052485AD,985
1220 DATA BD2406C981B00B65ADC9259024E9244CE723297F85AFA5ADE5AFC925901369234CF823000C182400FF01000204,488
1230 DATA 0507090BA8B9D82695C2A4A89900D2F6B0D002F6B4B5B085A0B5B485A1A000949CB1A00A369C4AA8B93B249D30,276
1240 DATA 064C27210406080C10182030406080C0A227A9009D5728CA10FA60204824A278A0272071264CC521A203A006DE,941
1250 DATA 0406D021B5BDF00AB5C29900D2D6BD4C8424B5C2F00F38FD14069900D2F6BDBD18069D04068888CA10D56085D4,490
1260 DATA A2ABA02720682685D520AAD920E6D8D8A0FFC8B1F3291F996628B1F310F460A5C185AFA90485A6A03088D0FD20,808
1270 DATA 1826C6A6D0F4206024C6AFD0E9602029258D08D22065E4A203A006A900959C95B095B495C69D24069D20069D1C,975
1280 DATA 06A9019D2C0695B9A9AA9D2806A9A095A9B99500D00295B9CA888810D160A5B905BA05BB05BCD012A5B8F005C6,498
1290 DATA B84CE7212048242029254CC5214CFE21A206A9009D01D29D00D2CACA10F660A511D029C61120292585B8204824,988
1300 DATA 4CC521A209A9409D3E069D5C06A9019D3406A9009D5206A9A29D4806CA10E660A90085AFCE7A2820C626C99BF0,796
1310 DATA 39C97ED013A5AFF0F1C6AFA6AFA9FF9D7A28FE7B284C6D25A6AFE003F0DCC93090D8C93AB0D4A6AF9D6606E91F,478
1320 DATA 9D7A28A9FF9D7B28E6AF4C6D25A6AFD003E6AF60A96685F3A90685F4A9009D660685F22000D820D2D9D8A5D4A6,695
1330 DATA D5F002A28060A29EA02720682685AFCE6B2820C626A6AFC97ED0108AF0F4C6AFA9FF9D6A28FE6B284CDF259D7F,793
1340 DATA 28C99BF01538E9209D6B28A9FF9D6C28E6AFE8E011D0CE4CE825FE6B2860A203A006B5C6F042DE0006D03DC901,653
1350 DATA F007C902F0204C6226F6A9B5A99901D2DD2806F006BD08064C4726F6C6BD0C069D00064C6226B5A9DD1006F00C,488
1360 DATA 900AD6A9B5A99901D24C4426F6C68888CA10B560A95785A4A9284C7726A96B85A4A92885A586A284A3A000B1A2,297
1370 DATA F01938E92091A4C84C7F26A020A27F8E1FD08E0AD4CA10F78810F260208C264C4824A220203E20A9C49D4403A9,290
1380 DATA 269D4503A9039D4203A9049D4A03A9009D4B034C56E44B9BA220A9009D48039D4903A9079D42034C56E4F3E6D9,696
1390 DATA CCC1B6ADA29990888079726C66605B55514C4844403C3935322F2D2A282523211F1D70707047CA270770700707,701
1400 DATA 0707077070070741FD2654454D504F20312D3235353A005048524153452023312D393A0041545441434B20312D,338
1410 DATA 3235353A00444543415920312D3235353A00562E52414E474520302D3235353A00562E535045454420312D3235,816
1420 DATA 353A005055534820425245414B20544F2053544F50004E4F204441544100564F4C2E2044524F5020302D373A00,526
1430 DATA 5245504541545320312D3235353A00444556494345204E414D453F0043555252454E542056414C55453A00492F,352
1440 DATA 4F204552524F5220434F44453A0000006D757369630073796E74686573697A657200000000627900006B656E00,536
1450 DATA 636F6C6C69657200000000002C697374656E000000000000000000000000000032657472696576650000000000,679
1460 DATA 000000000000002E756D6265720000000000000000000000000000306872617365006368616E67650000000000,457
1470 DATA 000034656D706F0000000000000000000000000000000000000000000000000000000000000000000000000000,942

Listing 2.

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,141,232,2
210 DATA 133,15,169,0,133,10,169,32,133,11,24,96

Listing 3.

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 RETURN";: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

Listing 4.

; +-------------------+
; | 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