To make the PC speaker work like a PWM device, we set the PIT's channel #2 to operating mode zero (interrupt-on-terminal-count).
MOV AL,90H OUT 43H,AL
Then we can output a 6-bit sample. Value 0 will be the silence, value 63 is the highest volume.
IRQ: PUSHA MOV AL,2 ; Sample [0...127] SHR AL,1 ; scaled to 6-bit JZ .1 ; zero means pause (no sound) OUT 42H,AL ; out the 6bit sample .1: ... POPA IRET
We set the timer counter to value 68 (later on called Divider), because we want 129 bpm for tempo.
MOV AL,68 OUT 40H,AL MOV AL,0 OUT 40H,AL
Then the player frequency will be 17,5 kHz.
To play musical notes, we need to multiply the carrier wave by some constant values.
We can compute these values depending on the frequencies, for example the A4 note computed like this: A_4=(32*256*44000/Hz+50)/100.
I have an inlcude file for notes: notes.inc
We had great fun with pc-speaker softsynth drums in a prod released earlier.
And this very simple drum pattern (bd - ht - sn/bd - ht) was used in another intro too, but with a different tempo: qblove
Bass drum is a simple division: A constant value divided by the time (or timer counter).
KICKDRUM: MOV AX,16128 ; constant value CWD ; extend the value to 2 words MOV CX,8191 ; get some lower bits of AND CX,SI ; the timer counter (SI) INC CX ; avoiding division by zero DIV CX ; get one bit only AND AL,64 ; 0 or 64 will be the volume (the sample)
Comparing an SN (on the left) to a BD (on the right), the snare is much longer, and more random.
Hihat also needs some random noise, so HT and SN starts with the same code snippet.
XCHG AX,DI ; DI: pointer to video memory, which is quite random here :) MUL SI ; SI: the timer counter XCHG AX,DX ; AX: random number depending on time AND AX,32 ; get only one random bit
One chord consists of four summed notes.
MOV BP,NOTES CHORDS: SALC ; AL: zero MOV AH,[BP+DI] ; AX: pitch F#, ... MUL SI ; SI: the timer counter AND DX,1FH ; DX: get a saw wave sample ADD BH,DL ; BH: sample (sum of the notes) INC DI ; next note TEST DI,3 ; loop 4x JNZ CHORDS
First, the idea of making a cover of a popular song in a restricted environment is great fun. That is why the length of the notes is other than power of 2.
The lengths of the chords are 7, 8 and 17. I had to convert this lengths to 8, 8 and 16. So I shifted the start, and put silence at the last beat.
TEST SI,1111000000000000B JZ SKIP ; no chords at the last beat ... ; chords here DRUMS: SUB SI,1000000000000B ; shifting back the drums by one beat
But after it sucked at Revision, we've changed the music. I believe ern0 found much better chords. I prefer the new song's mood.
The stored data for musical notes is almost always shrinkable. The raw data is 32 bytes long:
NOTES: DB A_3,B_3,D_4,Fs4 DB B_3,D_4,E_4,Gs4 DB Cs4,E_4,Fs4,A_4 DB Cs4,E_4,Fs4,A_4 DB B_3,D_4,Fs4,B_4 DB B_3,Cs4,E_4,Gs4 DB Cs4,E_4,Fs4,A_4 DB Cs4,E_4,Fs4,A_4
Here is the simple decoder:
MOV DI,SI SHRD DI,BX,2+16-5 AND DI,7*4
Look, we can save 12 bytes on the data...
NOTES: DB A_3,B_3,D_4,Fs4 DB B_3,D_4,E_4,Gs4 DB B_3,D_4,Fs4,B_4 DB B_3,Cs4,E_4,Gs4 DB Cs4,E_4,Fs4,A_4
and we lost only 7 bytes on the decoder.
MOV DI,4*4 SHR BL,1 JC .1 MOV DI,AX SHLD DI,SI,3 AND DI,3*4 .1:
We have two envelopes. Simple ADSR: no attack, just release.
Preparing CL and CH registers before the chord loop:
MOV AX,BX ; BL: pattern MOV CX,3 SHR AL,1 JC .1 MOV CH,128 .1: DEC AX ; skipping the drums-only intro SHR AL,1 AND CL,AL
One envelope for tone...
IMUL AX,SI,-16 OR AL,111B ROR AX,CL MUL BX ; DH: sample
and one for the volume...
IMUL AX,SI,-1 OR AH,CH ; CH: 128 or 0 MUL DX ; DH: sample
After adding together every instrument, drum, and chord, I test the maximum volume. I always store the debug information to memory, and print it using an external util.
if maxvol=1 CMP [FS:8000H],BH JAE .1 MOV [FS:8000H],BH .1: end if
This time the maximum sample value was 181. So I will scale down to 7 bits only.
MOV AL,180 ; vol 181 -> vol 127 MUL BH ; mastering XCHG BX,AX DONE: MOV [BP+IRQ.SAMPLE-1],BH
But at the cutoff part, I skip this downscaling, and the chords become even louder.
Here is the whole generated song:
If you liked this writeup, then leave a comment at the download page :)
And make sure you have also read the History of Chrome Revenge's born by TomCat