The Archimedes sound system is a highly sophisticated one, but the nine resident voices do little justice to its power or flexibility. The accompanying program is intended to put some of that power more readily into the user's hands. It creates a relocatable module containing any number of user-defined voices up to the operating system maximum of 32. Each is defined prior to the creation of the module in terms of a formula, which may contain any number of sine waves, random elements etc, plus volume and pitch data. This enables the generation of virtually any sound. Once the module has been created, and loaded into the RMA, the user need only allocate the new voices to a sound channel number before obtaining full control over each voice via the usual SOUND command. You can even use the new voices in conjunction with the Welcome Music Editor.
You should begin by carefully typing in the program in listing 1. This should be saved away before it is run. If it runs successfully you should see messages on the screen indicating the assembly of each of the 7 new voices. The newly created module is then automatically saved to disc (under the name UserVoices). To test it out, issue the following commands directly after assembling the module:
*RMLoad UserVoices
*ChannelVoice 2 2
VOICES 4
SOUND 2,-15,100,50
You should now hear a warbling sound which dies away after a second or so.
To test the whole set of voices, you could type in listing 2. This allocates and displays the 7 voices, then plays a sort of rhythm by cycling through the different voices at increasing frequency. If you press Escape (the crashing noise is caused by what appears to be a bug in the OS), you will see that function keys f1 to f7 play the seven new sounds at a SOUND pitch of 100.
The program has been written so that it is relatively easy to customise to meet individual requirements. The data for each sound is given in the early lines of the program between sets of dashed lines. After setting the variable TotalVoices% on line 90 to the number of voices to be created, you should give the formula for your first sound and its name as the two parameters of the procedure PROCfill.
Line 200 is an easy example to follow. It sets up the voice named "Ping" with the formula "SIN(Z)". You will see that the formula used for "Warble" on line 240 contains an element of third harmonic (using SIN(Z*3)), and that scaling has been applied to give the harmonic one quarter of the volume of the fundamental. The actual numbers used here are not important, only their relative values, since they are automatically scaled. Line 310 uses a random component in the voice formula, and you should note here that formulae containing randoms are not automatically scaled, and that in such cases the result of the formula must always be within the range -125 to +125. The variable Z ranges from 0 to 2*PI, and X from 0 to 255, both in a total of 256 steps for each voice.
The amplitude and frequency envelopes for each voice are created by making one or more calls to the procedures PROCaenv and PROCfenv. Both procedures have two parameters: the number of steps and the size of step; and the final call to each of the two procedures for any given voice must contain -1 as the second parameter. The Ping voice provides a very simple example. PROCaenv is called twice, once with parameters 127 and 1, giving a single step up to maximum volume (127=maximum, 0=minimum). The second call (-4,-1) gives a decay down to zero in steps of -4 units. The unit of time, or step width, for both frequency and amplitude is one centi-second. Since we require the pitch of the Ping voice to be steady, PROCfenv is called just once with parameters 0,-1.
The data for Warble is more complex. PROCaenv is called three times with parameters (127,1), (0,32) and (-1,-1). The first takes the sound up to full volume in a single step. The second holds that volume for 32 steps, and the third releases it to zero at one step per centi-second. PROCfenv, as you can see, is called 41 times. The FOR loop repeatedly calls the procedure to give 2 steps which drop the pitch by 24, followed by 2 which raise it by 16. The final call completes the envelope with a further drop in pitch.
As you can appreciate, there is virtually no limit to the sounds which can be produced using this system. The only problem likely to arise is that with a very complex voice, you might run out of allocated memory. This is easily resolved by increasing the two constants on lines 140 and 150 from their current value of 500 to something greater.
Square wave | (X>127)+.5 |
Sawtooth wave | X-128 |
10 REM >Voice2Gen4
20 REM Program User Voice Generator
30 REM Version A 1.54
40 REM Author Mark J Davis
50 REM RISC User March 1988
60 REM Program Subject to Copyright
70 :
80 DIM start% &10000
90 TotalVoices%=7 :REM User-defined
100 :
110 code2%=start%+300+20*TotalVoices%
120 wavetable%=code2%+TotalVoices%*600
130 aenvtable%=wavetable%+TotalVoices%*256
140 fenvtable%=aenvtable%+500
150 end%=fenvtable%+500
160 voice%=0:E%=0:F%=0
170 :
180 MODE0:PROCRS:PROCmodass
190 REM-------------------------------
200 PROCfill("SIN(Z)","Ping")
210 PROCaenv(127,1):PROCaenv(-4,-1)
220 PROCfenv(0,-1)
230 REM-------------------------------
240 PROCfill("80*SIN(Z)+20*SIN(Z*3)","Warble")
250 PROCaenv(127,1)
260 PROCaenv(0,32):PROCaenv(-1,-1)
270 FORX=1TO20
280 PROCfenv(-24,2):PROCfenv(16,2)
290 NEXT:PROCfenv(-8,-1)
300 REM-------------------------------
310 PROCfill("RND(250)-125","Bash")
320 PROCaenv(127,1):PROCaenv(-4,-1)
330 PROCfenv(0,-1)
340 REM-------------------------------
350 PROCfill("80*SIN(Z)+20*SIN(Z*3)+20*SIN(Z*7)","Pow")
360 PROCaenv(127,1):PROCaenv(-2,-1)
370 PROCfenv(-64,-1)
380 REM-------------------------------
390 PROCfill("80*SIN(Z)+20*SIN(Z*3)+20*SIN(Z*7)","Trem")
400 PROCaenv(127,1)
410 FORX=1TO10:PROCaenv(-4,5):PROCaenv(4,5):NEXT
420 PROCaenv(-4,-1)
430 PROCfenv(0,-1)
440 REM-------------------------------
450 PROCfill("(X>127)+.5","Trill")
460 PROCaenv(127,1):PROCaenv(-1,-1)
470 FORX=1TO30:PROCfenv(-100,1):PROCfenv(100,1):NEXT
480 PROCfenv(-1,-1)
490 REM-------------------------------
500 PROCfill("SIN(Z)","Rise")
510 PROCaenv(127,1):PROCaenv(-1,-1)
520 PROCfenv(100,-1)
530 REM-------------------------------
540 OSCLI"SAVE UserVoices "+STR$~start%+" "+STR$~end%
550 *SETTYPE UserVoices &FFA
560 *STAMP UserVoices
570 END
580 :
590 DEFPROCmodass
600 FORPASS=0TO2STEP2
610 P%=start%
620 [OPT PASS
630 .mstart
640 EQUD0:EQUD init-mstart
650 EQUD fin-mstart:EQUD0:EQUD title-mstart
660 EQUD help-mstart:EQUD0
670 .title EQUS"UserVoiceGenerator"
680 EQUB0:ALIGN
690 .help EQUS"Voices 1.00 (16 Jan 1988)"
700 EQUB0:ALIGN
710 .nos EQUD0:EQUD0:EQUD0:EQUD0
720 EQUD0:EQUD0:EQUD0:EQUD0
730 .init
740 STMFD R13!,{R14}
750 ADR R0,code2%:MOV R2,R0:MOV R1,#0
760 ADR R3,nos:SWI &40183:STRB R1,[R3,#1]
770 ]
780 IF TotalVoices%>1 THEN
790 FORX=2TOTotalVoices%:[OPTPASS
800 ADD R2,R2,#600:MOV R0,R2:MOV R1,#0
810 SWI &40183:STRB R1,[R3,#1]
820 ]:NEXT
830 ENDIF
840 :
850 [OPTPASS
860 LDMFD R13!,{R15}
870 .fin
880 STMFD R13!,{R14}
890 MOV R2,#0:ADR R3,nos
900 .flp LDRB R1,[R3,R2]:SWI &40184
910 ADD R2,R2,#1:CMP R2,#32:BCC flp
920 LDMFD R13!,{R15}
930 ]
940 NEXT
950 ENDPROC
960 :
970 DEFPROCwavass(name$,w%)
980 FORPASS=0TO2STEP2
990 P%=code2%+w%*600
1000 [OPT PASS
1010 .voicecode
1020 B fill:B fill:B gateon:B gateoff
1030 B instance:LDMFD R13!,{PC}
1040 LDMFD R13!,{PC}
1050 EQUD voicename-voicecode
1060 .logampptr EQUD0
1070 .wavebase EQUD wavetable%+w%*256-wavebase
1080 .aenvbase EQUD aenvtable%+E%
1090 .fenvbase EQUD fenvtable%+F%
1100 .waveinc EQUD wavetable%+w%*256-wavebase
1110 .aenvinc EQUD aenvtable%+E%-aenvbase
1120 .fenvinc EQUD fenvtable%+F%-fenvbase
1130 .instance
1140 STMFD R13!,{R0-R4}
1150 MOV R0,#0:MOV R1,#0
1160 MOV R2,#0:MOV R3,#0
1170 MOV R4,#0:SWI "Sound_Configure"
1180 LDR R0,[R3,#12]:STR R0,logampptr
1190 LDMFD R13!,{R0-R4,PC}
1200 .aenvaddr EQUD 0
1210 .fenvaddr EQUD 0
1220 .gateon
1230 ADR R0,wavebase:LDR R1,waveinc
1240 ADD R1,R1,R0:STR R1,[R0]
1250 ADR R0,aenvbase:LDR R1,aenvinc
1260 ADD R1,R1,R0:STR R1,[R0]
1270 ADR R0,fenvbase:LDR R1,fenvinc
1280 ADD R1,R1,R0:STR R1,[R0]
1290 LDR R0,wavebase:STR R0,[R9,#16]
1300 LDR R0,logampptr:STR R0,[R9,#20]
1310 LDR R0,aenvbase:STR R0,aenvaddr
1320 LDR R1,[R0]:STR R1,[R9,#28]
1330 LDR R0,fenvbase:STR R0,fenvaddr
1340 LDR R1,[R0]:STR R1,[R9,#24]
1350 LDR R0,[R9]:BIC R0,R0,#&7F
1360 STR R0,[R9,#0]
1370 .fill
1380 LDMIA R9,{R1-R8}
1390 AND R1,R1,#&7F
1400 AND R0,R8,#&FF:TST R8,#&100
1410 ADDEQ R1,R1,R0:CMP R1,#&7F
1420 MOVHI R1,#&7F:TST R8,#&100
1430 SUBNE R1,R1,R0:CMP R1,#&7F
1440 MOVHI R1,#0:SUBS R8,R8,#&10000
1450 LDRMI R0,aenvaddr
1460 LDRMI R8,[R0,#4]!
1470 STRMI R0,aenvaddr
1480 AND R0,R7,#&FF:TST R7,#&100
1490 ADDEQ R2,R2,R0:SUBNE R2,R2,R0
1500 SUBS R7,R7,#&10000
1510 LDRMI R0,fenvaddr
1520 LDRMI R7,[R0,#4]!:STRMI R0,fenvaddr
1530 LDR R0,[R9]:BIC R0,R0,#&7F
1540 ORR R1,R0,R1:STMIA R9,{R1-R8}
1550 AND R1,R1,#&7F
1560 LDRB R1,[R6,R1,LSL#1]
1570 MOV R1,R1,LSR#1:RSB R1,R1,#127
1580 .fillloop
1590 ADD R2,R2,R2,LSL#16
1600 LDRB R0,[R5,R2,LSR#24]
1610 SUBS R0,R0,R1,LSL#1
1620 MOVMI R0,#0:STRB R0,[R12],R11
1630 ADD R2,R2,R2,LSL#16
1640 LDRB R0,[R5,R2,LSR#24]
1650 SUBS R0,R0,R1,LSL#1
1660 MOVMI R0,#0:STRB R0,[R12],R11
1670 ADD R2,R2,R2,LSL#16
1680 LDRB R0,[R5,R2,LSR#24]
1690 SUBS R0,R0,R1,LSL#1
1700 MOVMI R0,#0:STRB R0,[R12],R11
1710 ADD R2,R2,R2,LSL#16
1720 LDRB R0,[R5,R2,LSR#24]
1730 SUBS R0,R0,R1,LSL#1
1740 MOVMI R0,#0:STRB R0,[R12],R11
1750 CMP R12,R10:BLT fillloop
1760 SUBS R4,R4,#1:LDR R1,[R9]
1770 STMIA R9,{R1-R8}
1780 MOVPL R0,#%00001000
1790 MOVMI R0,#%00000010
1800 LDMFD R13!,{PC}
1810 .gateoff MOV R0,#0
1820 .floop
1830 STRB R0,[R12],R11
1840 STRB R0,[R12],R11
1850 STRB R0,[R12],R11
1860 STRB R0,[R12],R11
1870 CMP R12,R10:BLT floop
1880 MOV R0,#%00000001
1890 LDMFD R13!,{PC}
1900 .voicename EQUS name$
1910 EQUB0:ALIGN
1920 ]
1930 NEXT PASS:ENDPROC
1940 :
1950 DEFPROCRS:FORX=1TO32
1960 SYS "Sound_RemoveVoice",0,X:NEXT
1970 ENDPROC
1980 DEFPROCfill(A$,name$):addr=voice%:PROCwavass(name$,voice%):voice%+=1
1990 PRINT"Assembling Voice ";addr
2000 addr=wavetable%+addr*256
2010 N=FNblock(1):IFINSTR(A$,"RND")>0 THENENDPROC
2020 Q=FNblock(N):ENDPROC
2030 :
2040 DEFFNblock(M):N=0
2050 FORX=0TO255:Z=RAD(X*360/256):Q=EVAL(A$)*M
2060 SYS "Sound_SoundLog",&FFFFFF*Q TO N%
2070 SYS "Sound_LogScale",N% TO X?addr
2080 IF(ABSQ) >N THENN=ABSQ
2090 NEXTX:=127/N
2100 :
2110 DEFPROCaenv(step,number)
2120 E%!aenvtable%=(ABSstep AND&FF)-&100*(step<0)+(number AND&7FFF)*&10000
2130 E%+=4:ENDPROC
2140 :
2150 DEFPROCfenv(step,number)
2160 F%!fenvtable%=(ABSstep AND&FF)-&100*(step<0)+(number AND&7FFF)*&10000
2170 F%+=4:ENDPROC
* * * * *
10 REM >Effects4
20 PROCclear
30 *RMLOAD UserVoices
40 PROCchannels
50 PROCkeys
60 PROCplay
70 END
80 :
90 DEFPROCplay
100 REPEAT
110 FOR N=50 TO 200 STEP 20
120 FOR C=7 TO 1 STEP -1
130 SOUND C,-15,N,50
140 X=INKEY(30):NEXT:NEXT:UNTIL FALSE
150 ENDPROC
160 :
170 DEFPROCclear
180 FOR X=1 TO 32
190 SYS "Sound_RemoveVoice",0,X:NEXT
200 ENDPROC
210 :
220 DEFPROCchannels
230 FOR A=1 TO 7
240 OSCLI("CHANNELVOICE "+STR$(A)+" "+STR$(A))
250 NEXT:VOICES 8:*VOICES
260 ENDPROC
270 :
280 DEFPROCkeys
290 FOR A=1 TO 7
300 OSCLI("KEY"+STR$(A)+" SOUND "+STR$(A)+",-15,100,50|M")
310 NEXT
320 ENDPROC