Sound Experimentor

Date: 198x
Type: Program
Platform(s): TS 2068
Tags: Sound

Sound Experimentor is an interactive editor for the AY-3-8912 (or compatible) programmable sound generator, allowing users to explore and set all 14 sound chip registers in real time.

A cursor navigates a grid-based screen layout using keys 5, 6, 7, and 8 for movement, with “1” and “0” toggling binary fields and ENTER prompting numeric input for frequency, amplitude, noise, and envelope period values. The SOUND keyword sends all register values to the chip at once via a single statement at line 40, using a user-defined function FN a(x) to map screen row and column to register array indices.

An envelope shape visualizer at the bottom of the screen draws waveforms using PLOT and DRAW commands, selecting among eight named shapes (attack, hold, alternate, continue, etc.) via a computed GO TO at line 910.

Register values are mirrored in both a numeric display array a() and a raw storage array b(), with binary enable fields managed as substrings of b$ evaluated via BIN literals. A printer dump routine at line 3000 reads register values from a DATA statement at line 6200 and outputs them via LPRINT.


Program Analysis

Program Structure

The program is organized into well-delineated functional regions:

  • Lines 10–40: Initialization dispatch and core SOUND output subroutine
  • Lines 100–130: Main cursor-movement and keypress loop
  • Lines 300–410: Input routing and binary envelope field editing
  • Lines 500–810: Context-sensitive value entry (enable bits, frequency, amplitude, noise)
  • Lines 900–935: Envelope shape chart renderer (computed GO TO dispatch)
  • Lines 1000–2090: Two-digit and five-digit value input handlers
  • Lines 3000–3030: LPRINT register dump
  • Lines 5000–5900: Screen initialization and register value display subroutines
  • Lines 6000–6200: Low-level cell display helpers and DATA statement
  • Line 9999: Save/verify routine

Register Mapping and Data Model

All 14 AY-3-8912 registers are controlled through two parallel data structures. The array a(11) holds cooked register values in order: tone periods for channels A/B/C (split into low/high bytes, occupying a(1) through a(6)), amplitude values a(7)a(9), and envelope period bytes a(10)a(11). Separate scalar variables hold the noise register (n), mixer enable byte (e), and envelope shape (v). A secondary numeric array b(8) stores raw user-entered values for screen display, while two 7-character string variables b$(1) and b$(2) store binary representations of the enable and envelope shape bit fields.

The user-defined function at line 30, DEF FN a(x)=(x-6)*3+y/8+1, maps the screen cursor row x (6–10) and column y (0, 8, 16, 24) to an index into b(), cleanly encoding the two-dimensional register grid into a one-dimensional array offset.

SOUND Statement Usage

Line 40 contains the entire register-write operation as a single, long SOUND statement addressing all 14 registers (0 through 13) simultaneously. This is called as a subroutine (GO SUB 40) whenever register state changes, ensuring the chip always reflects the current editing state. The statement uses FN a(x) inline to dereference array values, making it compact but dependent on the global cursor state variables x and y being set correctly before the call—which they are, since the cursor position is preserved across the main loop.

Cursor Navigation

The main loop at lines 100–130 implements cursor movement using arithmetic rather than conditionals where possible. Key “5” moves left (decrements y by 8), key “8” moves right (increments y by 8), with boundary clamping embedded in the expressions at lines 120–122. Line 121 enforces a row constraint when y reaches 24 (the last column, which has fewer valid rows), and line 122 handles downward movement with a special case for the last column. A flashing, white-paper highlight at the cursor cell is drawn using FLASH 1; PAPER 7; SCREEN$ to preserve the existing character.

ON ERR Usage

Input routines throughout the program use ON ERR GO TO to trap invalid INPUT entries—looping back to the INPUT prompt on error—then immediately follow with ON ERR RESET to restore normal error handling once a valid value is received. This pattern appears at lines 620/630, 700/710, 1020/1030, and 2010/2020, providing robust user input validation without crashing on non-numeric entry.

Envelope Shape Visualizer

Lines 900–935 implement a graphical waveform display for the eight AY envelope shapes. Line 910 clears the chart area and then executes GO TO 920+v, a computed GO TO where v is the envelope shape register value (0–15, though only shapes 8–15 are distinct). Since shapes 0–7 are aliases for shapes 8 and 11, lines 929 and 935 redirect to 923 and 927 respectively. Each shape subroutine draws the characteristic waveform (sawtooth, triangle, ramp, etc.) using PLOT and a sequence of DRAW commands with relative coordinates, then returns.

LineShape value(s)Waveform description
9230–7, 9 (via 929)Single decay ramp, then silence
92711, 15 (via 935)Single attack ramp, then silence
9288Repeating decay sawtooth
93010Triangle wave (decay-attack alternating)
93112Single decay, then hold high
93214Repeating attack sawtooth
93313Single attack, then hold high
93416Triangle wave (attack-decay alternating)

Enable Bits via BIN Strings

The mixer enable register (e) and envelope shape bits are stored as human-readable binary strings in b$(1) and b$(2), initialized to "BIN 000000". Individual characters within these strings are set by direct substring assignment (e.g., LET b$(1,7-y/8-3*(x=10))=a$), and the numeric value is recovered with VAL b$(1), which evaluates the BIN literal embedded in the string. This is a clever technique that avoids explicit bit-manipulation arithmetic while still storing the state compactly.

Printer Dump

The printer routine at lines 3000–3030 uses RESTORE followed by READ to iterate through the DATA statement at line 6200, which lists the variable names as string tokens. When READ assigns these to i, they are evaluated as expressions, yielding the current register values. This allows the DATA line to serve as a symbolic register manifest that is self-documenting. Output is formatted with tab-spaced columns via LPRINT.

Content

Appears On

Related Products

Related Articles

Related Content

Image Gallery

Sound Experimentor

Source Code

   10 REM sound experimentor
   20 GO SUB 5000: GO TO 100
   30 DEF FN a(x)=(x-6)*3+y/8+1
   40 SOUND 0,a(1);1,a(2);2,a(3);3,a(4);4,a(5);5,a(6);6,n;7,e;8,a(7);9,a(8);10,a(9);11,a(10);12,a(11);13,v: RETURN 
  100 LET x1=6: LET y1=0: LET x=x1: LET y=y1
  110 PRINT AT x1,y1;SCREEN$ (X1,Y1): LET x1=x: LET y1=y: PRINT AT x,y; FLASH 1; PAPER 7;SCREEN$ (x,y)
  115 LET a$=INKEY$: IF a$="" THEN GO TO 115
  120 LET y=y+(-8*(y>0 AND a$="5")+8*(y<24 AND a$="8"))
  121 LET x=x-(x=11 AND y<24)
  122 LET x=x+(x<10+(y=24) AND a$="6")-(x>6 AND a$="7")
  125 IF a$="r" OR a$="R" THEN GO SUB 5100: GO SUB 40: GO TO 100
  127 IF a$="f" OR a$="F" THEN GO SUB 40
  128 IF a$="p" OR a$="P" THEN GO SUB 3000
  130 IF a$<>"1" AND a$<>"0" AND a$<>CHR$ 13 THEN GO TO 110
  300 IF x=6 THEN GO TO 600
  310 IF x=7 THEN GO TO 1000
  315 IF a$=CHR$ 13 THEN GO TO 110
  320 IF x=8 AND y<24 THEN GO TO 500
  330 IF x>8 AND y<24 THEN GO TO 800
  400 REM env
  410 LET b$(2,15-x)=a$: LET v=VAL b$(2): PRINT AT x,y;a$: GO SUB 40: GO SUB 910: PRINT AT 17,11;"  ";AT 17,11;v: GO TO 110
  500 REM env enables
  510 PRINT AT x,y;a$: LET a(7+y/8)=a(7+y/8)-16*(a(7+y/8)>15)+16*(a$="1"): GO SUB 40: GO SUB 5220: GO TO 110
  600 REM 4 digit
  610 IF y>16 THEN GO TO 2000
  620 ON ERR GO TO 620: INPUT "Freq. (-4095 to 0) -";a: IF a>4095 OR a<-4095 THEN STOP 
  630 ON ERR RESET : LET a=ABS INT a: LET b(FN a(x))=0-a: GO SUB 6000: LET a(y/4+1)=a-256*INT (a/256): LET a(y/4+2)=INT (a/256): GO SUB 40: GO SUB 5200
  640 GO TO 110
  700 ON ERR GO TO 700: INPUT "Noise val. (0 to 31) ";a: IF a<0 OR a>31 THEN STOP 
  710 ON ERR RESET : LET n=31-INT a: LET b(FN a(x))=a: GO SUB 6100: GO SUB 40: GO SUB 5220: GO TO 110
  800 REM enables
  810 LET b$(1,7-y/8-3*(x=10))=a$: LET e=63-VAL b$(1): PRINT AT x,y;a$: GO SUB 40: PRINT AT 15,27;"  ";AT 15,27;e: GO TO 110
  900 REM chart
  910 PRINT AT 19,14; PAPER 1;TAB 32;AT 20,14;TAB 32: INK 7: GO TO 920+v
  923 PLOT 112,21: DRAW 23,-11: DRAW 117,0: INK 0: RETURN 
  927 PLOT 112,10: DRAW 23,11: DRAW 0,-11: DRAW 117,0: INK 0: RETURN 
  928 PLOT 112,21: FOR i=0 TO 5: DRAW 23,-11: DRAW 0,11: NEXT i: INK 0: RETURN 
  929 GO TO 923
  930 PLOT 112,21: FOR i=0 TO 2: DRAW 23,-11: DRAW 23,11: NEXT i: INK 0: RETURN 
  931 PLOT 112,21: DRAW 23,-11: DRAW 0,11: DRAW 117,0: INK 0: RETURN 
  932 PLOT 112,10: FOR i=0 TO 5: DRAW 23,11: DRAW 0,-11: NEXT i: INK 0: RETURN 
  933 PLOT 112,10: DRAW 23,11: DRAW 117,0: INK 0: RETURN 
  934 PLOT 112,10: FOR i=0 TO 2: DRAW 23,11: DRAW 23,-11: NEXT i: INK 0: RETURN 
  935 GO TO 927
 1000 REM 2 digit
 1010 IF y>16 THEN GO TO 700
 1020 ON ERR GO TO 1020: INPUT "Amplitude. (0 to 15) ";a: IF a<0 OR a>15 THEN STOP 
 1030 ON ERR RESET : LET a=INT a: LET b(FN a(x))=a: GO SUB 6100: LET a(7+y/8)=a+16*(a(7+y/8)>15): GO SUB 40: GO SUB 5220
 1900 GO TO 110
 2000 REM 5 digit
 2010 ON ERR GO TO 2010: INPUT "Period len.(0 to 65535)";a: IF a<0 OR a>65535 THEN STOP 
 2020 ON ERR RESET : LET a=INT a: LET b(FN a(x))=a: GO SUB 6000: LET a(10)=a-256*INT (a/256): LET a(11)=INT (a/256): GO SUB 40: GO SUB 5200
 2090 GO TO 110
 3000 REM Printer
 3010 LPRINT : LPRINT "RHEESWARE SOUND EXPERIMENTOR    REGISTER VALUES"
 3015 RESTORE 
 3020 LPRINT "Reg.#   Val.    Reg.#   Val.    ": FOR a=0 TO 13: READ i: LPRINT a;".......";i,: NEXT a
 3030 LPRINT : LPRINT : LPRINT : RETURN 
 5000 REM screen init
 5020 BORDER 0: PAPER 6: CLS 
 5030 PRINT TAB 2; INK 7; PAPER 2;"RHEESWARE Sound Experimentor": PLOT 0,141: DRAW 255,0
 5040 PRINT INVERSE 1; INK 1;"ARROW keys select parameter.    1=ON, 0=OFF.  R= reset, P= print""ENTER"" inputs vals.   F =refeed"
 5050 PRINT ' BRIGHT 1;"chan A  chan B  chan C  ": PRINT "0000 fr 0000 fr 0000 fr 00000env00 AMPL 00 AMPL 00 AMPL 00 N. fr0 ENVEL 0 ENVEL 0 ENVEL 0 HOLD  0 ENAB  0 ENAB  0 ENAB  0 ALTERN0 NOISE 0 NOISE 0 NOISE 0 ATTACK                        0 CONT  "
 5060 PLOT 0,76: DRAW 255,0: PRINT ' BRIGHT 1;"    Sound register values       "
 5070 PRINT AT 14,0;: FOR i=0 TO 13: PRINT TAB i*8; PAPER 2; INK 7;i;: NEXT i
 5080 PLOT 0,29: DRAW 255,0: PRINT '' BRIGHT 1;"Envelope Shape"'' BRIGHT 0;"  env. period>": PLOT 111,23: DRAW 0,-21
 5090 PLOT 111,3: DRAW 144,0: FOR i=135 TO 255 STEP 23: PLOT i,2: DRAW 0,5: NEXT i
 5100 REM reset
 5105 DIM a(11): DIM b$(2,7): LET n=31: LET b$(1)="BIN 000000": LET b$(2)="BIN 000000": LET e=63: DIM b(8)
 5110 LET v=0
 5120 LET x=6: FOR y=0 TO 24 STEP 8: GO SUB 6000: NEXT y
 5130 LET x=7: FOR y=0 TO 24 STEP 8: GO SUB 6100: NEXT y
 5140 FOR x=8 TO 10: FOR y=0 TO 24 STEP 8: PRINT AT x,y;"0": NEXT y: NEXT x
 5150 PRINT AT x,24;"0"
 5160 GO SUB 900
 5200 REM register write
 5210 FOR i=0 TO 5: PRINT AT INT (14+i/4),i*8+3-32*INT (i/4);"   ";AT INT (14+i/4),i*8+3-32*INT (i/4);a(i+1): NEXT i
 5220 PRINT AT 15,19;"   ";AT 15,19;n;AT 15,27;"   ";AT 15,27;e: FOR i=0 TO 3: PRINT AT 16,i*8+3;"   ";AT 16,i*8+3;a(i+7): NEXT i: PRINT AT 17,3;"   ";AT 17,3;a(11): PRINT AT 17,11;"   ";AT 17,11;v
 5900 RETURN 
 6000 PRINT AT x,y;"     ";AT x,y;b(FN a(x)): RETURN 
 6100 PRINT AT x,y;"  ";AT x,y;b(FN a(x)): RETURN 
 6200 DATA a(1),a(2),a(3),a(4),a(5),a(6),n,e,a(7),a(8),a(9),a(10),a(11),v
 9999 CLEAR : SAVE "sound": SAVE "sound": VERIFY "": VERIFY "": GO TO 9999
   10 REM sound experimentor
   20 GO SUB 5000: GO TO 100
   30 DEF FN a(x)=(x-6)*3+y/8+1
   40 SOUND 0,a(1);1,a(2);2,a(3);3,a(4);4,a(5);5,a(6);6,n;7,e;8,a(7);9,a(8);10,a(9);11,a(10);12,a(11);13,v: RETURN 
  100 LET x1=6: LET y1=0: LET x=x1: LET y=y1
  110 PRINT AT x1,y1;SCREEN$ (X1,Y1): LET x1=x: LET y1=y: PRINT AT x,y; FLASH 1; PAPER 7;SCREEN$ (x,y)
  115 LET a$=INKEY$: IF a$="" THEN GO TO 115
  120 LET y=y+(-8*(y>0 AND a$="5")+8*(y<24 AND a$="8"))
  121 LET x=x-(x=11 AND y<24)
  122 LET x=x+(x<10+(y=24) AND a$="6")-(x>6 AND a$="7")
  125 IF a$="r" OR a$="R" THEN GO SUB 5100: GO SUB 40: GO TO 100
  127 IF a$="f" OR a$="F" THEN GO SUB 40
  128 IF a$="p" OR a$="P" THEN GO SUB 3000
  130 IF a$<>"1" AND a$<>"0" AND a$<>CHR$ 13 THEN GO TO 110
  300 IF x=6 THEN GO TO 600
  310 IF x=7 THEN GO TO 1000
  315 IF a$=CHR$ 13 THEN GO TO 110
  320 IF x=8 AND y<24 THEN GO TO 500
  330 IF x>8 AND y<24 THEN GO TO 800
  400 REM env
  410 LET b$(2,15-x)=a$: LET v=VAL b$(2): PRINT AT x,y;a$: GO SUB 40: GO SUB 910: PRINT AT 17,11;"  ";AT 17,11;v: GO TO 110
  500 REM env enables
  510 PRINT AT x,y;a$: LET a(7+y/8)=a(7+y/8)-16*(a(7+y/8)>15)+16*(a$="1"): GO SUB 40: GO SUB 5220: GO TO 110
  600 REM 4 digit
  610 IF y>16 THEN GO TO 2000
  620 ON ERR GO TO 620: INPUT "Freq. (-4095 to 0) -";a: IF a>4095 OR a<-4095 THEN STOP 
  630 ON ERR RESET : LET a=ABS INT a: LET b(FN a(x))=0-a: GO SUB 6000: LET a(y/4+1)=a-256*INT (a/256): LET a(y/4+2)=INT (a/256): GO SUB 40: GO SUB 5200
  640 GO TO 110
  700 ON ERR GO TO 700: INPUT "Noise val. (0 to 31) ";a: IF a<0 OR a>31 THEN STOP 
  710 ON ERR RESET : LET n=31-INT a: LET b(FN a(x))=a: GO SUB 6100: GO SUB 40: GO SUB 5220: GO TO 110
  800 REM enables
  810 LET b$(1,7-y/8-3*(x=10))=a$: LET e=63-VAL b$(1): PRINT AT x,y;a$: GO SUB 40: PRINT AT 15,27;"  ";AT 15,27;e: GO TO 110
  900 REM chart
  910 PRINT AT 19,14; PAPER 1;TAB 32;AT 20,14;TAB 32: INK 7: GO TO 920+v
  923 PLOT 112,21: DRAW 23,-11: DRAW 117,0: INK 0: RETURN 
  927 PLOT 112,10: DRAW 23,11: DRAW 0,-11: DRAW 117,0: INK 0: RETURN 
  928 PLOT 112,21: FOR i=0 TO 5: DRAW 23,-11: DRAW 0,11: NEXT i: INK 0: RETURN 
  929 GO TO 923
  930 PLOT 112,21: FOR i=0 TO 2: DRAW 23,-11: DRAW 23,11: NEXT i: INK 0: RETURN 
  931 PLOT 112,21: DRAW 23,-11: DRAW 0,11: DRAW 117,0: INK 0: RETURN 
  932 PLOT 112,10: FOR i=0 TO 5: DRAW 23,11: DRAW 0,-11: NEXT i: INK 0: RETURN 
  933 PLOT 112,10: DRAW 23,11: DRAW 117,0: INK 0: RETURN 
  934 PLOT 112,10: FOR i=0 TO 2: DRAW 23,11: DRAW 23,-11: NEXT i: INK 0: RETURN 
  935 GO TO 927
 1000 REM 2 digit
 1010 IF y>16 THEN GO TO 700
 1020 ON ERR GO TO 1020: INPUT "Amplitude. (0 to 15) ";a: IF a<0 OR a>15 THEN STOP 
 1030 ON ERR RESET : LET a=INT a: LET b(FN a(x))=a: GO SUB 6100: LET a(7+y/8)=a+16*(a(7+y/8)>15): GO SUB 40: GO SUB 5220
 1900 GO TO 110
 2000 REM 5 digit
 2010 ON ERR GO TO 2010: INPUT "Period len.(0 to 65535)";a: IF a<0 OR a>65535 THEN STOP 
 2020 ON ERR RESET : LET a=INT a: LET b(FN a(x))=a: GO SUB 6000: LET a(10)=a-256*INT (a/256): LET a(11)=INT (a/256): GO SUB 40: GO SUB 5200
 2090 GO TO 110
 3000 REM Printer
 3010 LPRINT : LPRINT "RHEESWARE SOUND EXPERIMENTOR    REGISTER VALUES"
 3015 RESTORE 
 3020 LPRINT "Reg.#   Val.    Reg.#   Val.    ": FOR a=0 TO 13: READ i: LPRINT a;".......";i,: NEXT a
 3030 LPRINT : LPRINT : LPRINT : RETURN 
 5000 REM screen init
 5020 BORDER 0: PAPER 6: CLS 
 5030 PRINT TAB 2; INK 7; PAPER 2;"RHEESWARE Sound Experimentor": PLOT 0,141: DRAW 255,0
 5040 PRINT INVERSE 1; INK 1;"ARROW keys select parameter.    1=ON, 0=OFF.  R= reset, P= print""ENTER"" inputs vals.   F =refeed"
 5050 PRINT ' BRIGHT 1;"chan A  chan B  chan C  ": PRINT "0000 fr 0000 fr 0000 fr 00000env00 AMPL 00 AMPL 00 AMPL 00 N. fr0 ENVEL 0 ENVEL 0 ENVEL 0 HOLD  0 ENAB  0 ENAB  0 ENAB  0 ALTERN0 NOISE 0 NOISE 0 NOISE 0 ATTACK                        0 CONT  "
 5060 PLOT 0,76: DRAW 255,0: PRINT ' BRIGHT 1;"    Sound register values       "
 5070 PRINT AT 14,0;: FOR i=0 TO 13: PRINT TAB i*8; PAPER 2; INK 7;i;: NEXT i
 5080 PLOT 0,29: DRAW 255,0: PRINT '' BRIGHT 1;"Envelope Shape"'' BRIGHT 0;"  env. period>": PLOT 111,23: DRAW 0,-21
 5090 PLOT 111,3: DRAW 144,0: FOR i=135 TO 255 STEP 23: PLOT i,2: DRAW 0,5: NEXT i
 5100 REM reset
 5105 DIM a(11): DIM b$(2,7): LET n=31: LET b$(1)="BIN 000000": LET b$(2)="BIN 000000": LET e=63: DIM b(8)
 5110 LET v=0
 5120 LET x=6: FOR y=0 TO 24 STEP 8: GO SUB 6000: NEXT y
 5130 LET x=7: FOR y=0 TO 24 STEP 8: GO SUB 6100: NEXT y
 5140 FOR x=8 TO 10: FOR y=0 TO 24 STEP 8: PRINT AT x,y;"0": NEXT y: NEXT x
 5150 PRINT AT x,24;"0"
 5160 GO SUB 900
 5200 REM register write
 5210 FOR i=0 TO 5: PRINT AT INT (14+i/4),i*8+3-32*INT (i/4);"   ";AT INT (14+i/4),i*8+3-32*INT (i/4);a(i+1): NEXT i
 5220 PRINT AT 15,19;"   ";AT 15,19;n;AT 15,27;"   ";AT 15,27;e: FOR i=0 TO 3: PRINT AT 16,i*8+3;"   ";AT 16,i*8+3;a(i+7): NEXT i: PRINT AT 17,3;"   ";AT 17,3;a(11): PRINT AT 17,11;"   ";AT 17,11;v
 5900 RETURN 
 6000 PRINT AT x,y;"     ";AT x,y;b(FN a(x)): RETURN 
 6100 PRINT AT x,y;"  ";AT x,y;b(FN a(x)): RETURN 
 6200 DATA a(1),a(2),a(3),a(4),a(5),a(6),n,e,a(7),a(8),a(9),a(10),a(11),v
 9999 CLEAR : SAVE "sound": SAVE "sound": VERIFY "": VERIFY "": GO TO 9999

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

People

No people associated with this content.

Scroll to Top