100 REM AppleCrate Synthesizer (Broadcast) 110 REM MJM - Mar 26, 2005 120 REM (Apr 13, 2005: Default non-existant voices to v.0) 130 REM (Jul 03, 2008: Expand to 16 oscillator capacity) 140 REM (Jul 03, 2008: Add support for .vm files with voice mapping table) 150 REM (Jul 04, 2008: Added 'stereo split' option) 160 REM (Jul 10, 2008: Added support for non-contiguous oscillators) 165 REM (Oct 01, 2008: Changed to BCAST synth and voices) 170 : 200 NO = 16: REM Max number of oscillators 210 PFX$ = "/ap/merlin/work/nadanet/" 220 D$ = CHR$ (4) 230 DB$ = D$ + "bload" 240 : 250 GOSUB 60000: REM Initialize AmperNada 260 GOSUB 61000: REM Take census 270 : 280 FOR D = 2 TO 2 + NO - 1: REM Check Crate status 290 IF PEEK (ITBL + D) < > 2 THEN NB = 1: REM Need boot 300 NEXT D 310 IF NOT NB GOTO 530: REM Skip boot if not needed 320 : 330 MSRV = PEEK (ITBL + 2) = 3: REM Is Msg Server running? 340 IF MSRV THEN POKE ITBL + 2,0: POKE 512,0: & POKE (2,1012,1,512): & CALL (2,64098,0): REM Force re-boot/re-allocation of Message Server 350 : 360 REM Boot unbooted slaves 370 PBUF = 3 * 4096: REM Program buffer 380 PRINT DB$PFX$"nada.crate,A"PBUF 390 PSRT = PEEK (PBUF + 2) * 256: REM Prog start 400 PLNG = PEEK (48840) + 256 * PEEK (48841): REM Prog length 410 & BOOT(PSRT,PLNG,PBUF) 420 & SERVE(10): REM Boot Assign IDs 430 PRINT "Slave machines booted." 440 : 450 GOSUB 61000: REM Re-take census 460 : 470 NB = 0 480 FOR D = 2 TO 2 + NO - 1: REM Check Crate status 490 IF PEEK (ITBL + D) < > 2 THEN NB = NB + 1: REM Need boot 500 NEXT D 510 IF NB THEN PRINT NB" of "NO" oscillators not running.": STOP 520 : 530 REM Application code 540 : 550 QUIT = 999: REM Exit FOR loop 560 TSYN = 15 * 4096: REM SYNTH BCAST tag = $F000 570 TVOI = TSYN + 256: REM Voice xx BCAST tag = $F1xx 580 : 590 REM Master memory allocation 600 BLDR = 3 * 4096: REM SYNTH.LOADER buffer 610 BUF = BLDR - 2: REM 2-byte utility buffer 620 VBF = BLDR - 256: REM 254-byte merged voice table 630 MAP = BLDR + 512: REM Voice map 640 DIR = MAP + 256: REM Directory 650 VMUS = DIR + 256: REM Music buffer 660 : 670 REM Crate memory allocation 680 LD = 512: REM SYNTH.LOADER origin 690 ML = 1 * 256 + 12 * 16 + 12: REM Max loader length $1CC ($200..$3CB) 700 PG = BLDR + 3: REM First voice page 710 NL = BLDR + 4: REM Number of voices 720 LL = BLDR + 5: REM Voice list 730 SYNTH = 8 * 256: REM Address of Synthesizer $800 740 SL = 2 * 4096: REM Length of Synthesizer 750 MUSIC = SYNTH + SL: REM Music follows SYNTH 760 : 770 REM Directory defs 780 DL = 6: REM Dir entry size (bytes) 790 : 800 PRINT DB$PFX$"synth.loader,a"BLDR: REM Load SYNTH.LOADER 810 LN = PEEK (48840) + 256 * PEEK (48841): REM Loader length 820 IF LN > ML THEN PRINT "SYNTH.LOADER longer than "ML" bytes!": STOP 830 : 840 PRINT : REM Get the music file to play 850 INPUT "Music file: ";M$ 860 IF M$ = "" THEN PRINT D$"catalog,tbin": GOTO 840 870 IF RIGHT$ (M$,3) = ".vm" THEN LA = MAP: GOTO 900: REM Read voice map 880 IF RIGHT$ (M$,2) = ".m" THEN LA = DIR: GOTO 900: REM No voice map 890 PRINT M$" is not a music file.": GOTO 840 900 PRINT DB$M$",a"LA: REM Read music file 910 : 920 I = 0:OL = - 1 930 FOR DP = DIR TO DIR + PEEK (DIR + 1) - DL STEP DL 940 OU = ( PEEK (DP + 3) > 0): REM Oscillator used 950 OC = OC + OU: REM Count used oscillators 960 IF OL < 0 AND OU THEN OL = I: REM Lowest used osc 970 IF OU THEN OM = I: REM Highest used osc 980 I = I + 1 990 NEXT 1000 PRINT M$" uses "OC" oscillators." 1010 IF NO < OC THEN PRINT "Only "NO" oscilators available.": STOP 1020 : 1030 INPUT "Use pseudo-stereo? (y/n): ";A$: REM Split used osc across chans 1040 IF A$ = "y" OR A$ = "Y" THEN SS = NO / 2 - INT ((OC + 1) / 2) 1050 : 1060 IF LA = MAP GOTO 1170: REM Skip remap dialog if voice map present 1070 FOR I = 0 TO 255: POKE MAP + I,I: NEXT : REM Initial 1:1 re-map 1080 PRINT 1090 INPUT "Re-map voice number (RETURN to end): ";A$ 1100 IF A$ = "" GOTO 1170 1110 V1 = VAL (A$): IF V1 < 0 OR V1 > 255 GOTO 1090 1120 INPUT "To voice number: ";V2 1130 IF V2 < 0 OR V1 > 255 GOTO 1120 1140 POKE MAP + V1,V2 1150 GOTO 1090 1160 : 1170 PRINT "Sending music and loader to:"; 1180 OS = OL: REM For each used oscillator... 1190 : 1200 D = OS + 2 + SS: REM Crate machine ID 1210 PRINT " "D; 1220 DP = DIR + DL * OS: REM Ptr to osc dir entry 1230 FB = PEEK (DP + 3) * 256: REM Offset from DIR to Music stream 1240 IF FB = 0 GOTO 2300: REM Skip unused oscillators 1250 IF (OS = NO) THEN PRINT "More than "NO" osc.":OM = NO - 1: GOTO 2350 1260 VP = PEEK (DP + 1): REM Voice list ptr 1270 NV = PEEK (DIR + VP): REM Number of voices for this Osc 1280 POKE NL,NV: REM POKE number of voices into loader 1290 : 1300 FOR I = 1 TO NV: REM For each Voice... 1310 V = (VP > 0) * PEEK (DIR + VP + I): REM Voice number (default=0) 1320 V = PEEK (MAP + V): POKE DIR + VP + I,V: REM Re-map voice 1330 POKE NL + I,V: REM POKE voice into loader list 1340 IF VS = 0 THEN J = 0: GOTO 1440: REM Force first voice 1350 REM Add voice to merged sorted list 1360 FOR J = 1 TO VS: REM Scan sorted, merged voice buffer 1370 BV = PEEK (VBF + J) 1380 IF V = BV THEN J = QUIT: GOTO 1430: REM Skip if already present 1390 IF V > BV GOTO 1430: REM Keep scanning 1400 REM Move larger voices up in VBF 1410 FOR L = VBF + VS TO VBF + J STEP - 1: POKE L + 1, PEEK (L): NEXT 1420 POKE VBF + J,V:VS = VS + 1:J = QUIT: REM Insert voice and end scan 1430 NEXT J 1440 IF J < QUIT THEN VS = VS + 1: POKE VBF + VS,V: REM Add voice at end 1450 NEXT I 1460 : 1470 REM &POKE the music stream to the Crate 1480 MB = DIR + FB: REM Offset of music stream 1490 FL = PEEK (DP + 4) + 256 * PEEK (DP + 5): REM Music stream length 1500 & POKE (D,MUSIC,FL,MB): REM Transfer Music to crate 1510 POKE PG,(MUSIC + FL + 255) / 256: REM First voice page 1520 : 1530 REM &BRUN SYNTH.LOADER 1540 & B RUN (D,LD,LN,BLDR): REM BRUN SYNTH.LOADER 1550 : 1560 OS = OS + 1 1570 IF OS < = OM GOTO 1200 1580 : 1590 PRINT : PRINT "Broadcasting SYNTH to crate machines" 1600 PRINT DB$"/ap/merlin/work/synth/synth,a"VMUS 1610 & BCAST(TSYN,SL,VMUS) 1620 : 1630 REM &BCAST voices to crate machines 1640 PRINT "Broadcasting voice:"; 1650 FOR I = 1 TO VS 1660 V = PEEK (VBF + I) 1670 VV = - 1: ONERR GOTO 3000: REM Detect non-existant voice 1680 PRINT DB$"voices/v."V",a"VMUS: REM Load the Voice 1690 POKE 216,0: REM Cancel error jump 1700 IF VV > = 0 THEN PRINT DB$"voices/v."VV",a"VMUS: REM Substitute voice 1710 VL = ( PEEK (48841) + ( PEEK (48840) > 0)) * 256: REM Even page length 1720 PRINT " "V; 1730 & BCAST(TVOI + V,VL,VMUS): REM Broadcast voice to the crate 1740 NEXT I 1750 : 1760 PRINT : PRINT 1770 : 1780 INPUT "Return to start playing.";A$ 1790 & B POKE (768,0) 1800 GOTO 1780 1810 : 3000 REM Default non-existent voices 3010 POKE 216,0: REM Cancel error jump 3020 VV = 0: IF V > 127 THEN VV = 170 3030 PRINT "Substituting V."VV" for nonexistent V."V 3050 RESUME 3060 : 60000 REM NadaNet Definitions for Applesoft 60010 REM MJM - 12/20/08 60020 : 60030 NP = 3 * 256 + 12 * 16 + 15: REM NADANET page ($3CF) 60040 IF PEEK (NP - 2) < > 76 THEN PRINT "NadaNet not loaded.": STOP 60050 IF PEEK (NP) < > 145 THEN PRINT "Not ProDOS version of NadaNet.": STOP 60060 : 60070 MX = 20: REM Max machine ID 60080 BSLF = NP - 3: REM Boot ID in $3CC 60090 & IDTBL(ITBL): REM ID table 60100 RETURN 60110 : 61000 REM Take census of serving machines 61010 & TIMEOUT(2) 61020 QC = INT ( PEEK (33) / 13): REM Number of columns 61030 QL = INT ((MX + QC - 1) / QC): REM Number of lines 61040 SELF = PEEK (BSLF) 61050 FOR I = 1 TO QL 61060 FOR D = I TO MX STEP QL 61070 IF D = SELF THEN J = PEEK (975): GOTO 61120 61080 A$ = " " 61090 & PEEK #(D,975,1,512): REM Machine type 61100 IF PEEK (1) THEN K = 0: GOTO 61160 61110 J = PEEK (512) 61120 IF J = 184 THEN A$ = "CRATE ":K = 2 61130 IF J = 008 THEN A$ = "MSERVER ":K = 3 61140 IF J = 145 THEN A$ = "PRODOS ":K = 4 61150 IF J = 141 THEN A$ = "DOS ":K = 5 61160 POKE ITBL + D,K: REM Save type in IDTBL 61170 IF D = SELF THEN A$ = "==SELF==" 61180 IF D < 10 THEN PRINT " "; 61190 PRINT D":"A$;: PRINT " "; 61200 NEXT D 61210 PRINT 61220 NEXT I 61230 & TIMEOUT(): REM Reset retrys 61240 RETURN