On the Commodore 64, talking about SID DIGI (sample) play routines, there are many forms. What they all have in common is that they need to use a Timer routine to play samples at a certain rate. The time in cycles that it takes for that Timer interrupt driven sample playback determines the maximum sample rate.
On a PAL machine, one cycle equals 1.015 microsecond.
This means, if you wish to have a sample play at 8000 hz (update 8000 times a second), you need to call the sample play routine every 1/8000 = 0.000125 seconds. Or 125 microseconds. This is 125/1.015 = ~123 cycles. Meaning the Timer will need to be set to cause the interrupt at $7b cycles. It also means that the sample playback routine should not spend more than 123 cycles to complete in total, otherwise the next update will start when the previous did not finish yet. Also, if any other stuff needs to be done, like scrollers, graphical stuff, or even a whole game running (for example using other interrupts) things can get tricky if the sample play routine is taking too long, leaving no cycles for other things.
So let's take a look at some sample playback routines used in the Commodore 64 music scene.
Sample driver from: BMX Kidz
Year: 1987
Author: Rob Hubbard
Samples: 4-bit $D418
Size: 55 bytes
This is the FM-YAM adaptation, but same cycle and byte number.
Here's Rob's 6502 code:
org $b92d sample_driver sta sa+1 ; store Accu to pick up at rti (3,3) inc $fc ; $fc + 1 to toggle low/high nibble (5,8) lda $fc ; load the toggle (3,11) and #$01 ; and with bit 1 (set zero y/n) (2,12) php ; store processor status for later (3,15) samp lda $a3e5 ; load sample from address (4,19) cmp #$1f ; was it $1f? end of sample (2,21) bne lb947 ; if it wasn't then do next sample (3,24) plp ; it was. clean proc. status (4,28-1) lda #$00 ; stop timer A (2,29) sta $dd0e ; by setting dd0e to 0 (4,33) jmp en ; and leave the routine (3,36) lb947 plp ; restore proc. status (4,28) beq lown ; was zero set? then low nibble (3,31) hign and #$f0 ; no, high nibble. isolate that (2,33-1) jmp sid ; jmp to set the sid/fm-yam (3,36) lown asl ; shift left 4 times to make it (2,33) asl ; the high nibble for fm-yam (2,35) asl ; (2,37) asl ; (2,39) inc samp+1 ; increase the sample address (5,44) bne sid ; need to increase high byte? (3,47) inc samp+2 ; yes, do that (5,52-1) sid sta $df50 ; store at data register fm-yam (4,55,51,40) en lda $dd0d ; acknowledge timer interrupt (4,59,55,44) sa lda #$00 ; retrieve Accu value (2,61,57,46) rti ; back to location before interrupt (6,67,63,52)
So the maximum number of cycles is 67 when the low nibble needs to be played and the high byte of the sample address needs to be increased as well. Otherwise it takes 63 cycles for the low nibble play, and 52 cycles for the high nibble play.
So this routine will take an average number of 57.5 cycles. But the 67 cycles is the limiting factor. In the hypothetical situation that you would be able to call this routine at that rate, this would then allow a sample rate of 14705 Hz. Of course, in the game intro screen, other things are happening that take cycles. The sample rate for the samples playing never reaches that high maximum rate. Suffice it to say that the maximum sample rate will be determined by the sample routine above, but also cycles needed to do other things!
Sample driver from: Combat School
Year: 1987
Author: Martin Galway
Samples: 4-bit $D418
Size: Varies per instrument
; sample "instruments" of 256 bytes ; .C:b77d A2 00 LDX #$00 ; reset x 2, 2 ; .C:b77f BD 9F B9 LDA $B99F,X ; get sample 5, 7 ; .C:b782 4A LSR A ; get high nib 2, 9 ; .C:b783 4A LSR A ; 2, 11 ; .C:b784 4A LSR A ; 2, 13 ; .C:b785 4A LSR A ; 2, 15 ; .C:b786 20 9A B7 JSR $B79A ; play sample 6, 127 ; .C:b789 BD 9F B9 LDA $B99F,X ; get sample 5, 133 ; .C:b78c EA NOP ; time it 2, 135 ; .C:b78d EA NOP ; 2, 137 ; .C:b78e EA NOP ; 2, 139 ; .C:b78f 29 0F AND #$0F ; get low nib 2, 141 ; .C:b791 20 9A B7 JSR $B79A ; play sample 6, 256 ; .C:b794 E8 INX ; x + 1 2, 258 ; .C:b795 D0 E8 BNE $B77F ; 256 samples 3, 66559 ; .C:b797 4C 0B B7 JMP $B70B ; 3, 66562 ; .C:b79a 4C 83 09 STA $D418 ; play sample 4, 24 ; .C:b79d A0 12 LDY #$12 ; set delay 18 2, 26 ; .C:b79f 88 DEY ; y -1 2, 28 ; .C:b7a0 10 FD BPL $B79F ; if not -1 go 3, 115 ; .C:b7a2 60 RTS ; back. 6, 121 This sample takes around 67560 microseconds, 0.0676 seconds, when not interrupted.
Galway's sample driver is rather crude. Samples are not timed using Timer A or B, but instead each sample has a specific routine that is called to generate the $D418 wave. The downside of such a technique is that everything else has to wait many cycles before the instrument is done playing.
Above is an example of such an instrument. Each sample is played at a rate of 125 and 130 cycles, alternating (on a PAL machine that is fluctating around 8000 Hz). That rate is determined by the Y value in $b79d. The higher the number, the slower the playback rate.
If samples needed to be longer (like the snare drum) the routine was copied right after the previous one, with one page further for the sample addresses. I also suspect that these samples were "handdrawn" and not actually sampled instruments.
Sample driver from: Turbo Outrun
Year: 1987
Author: Maniacs of Noise
Samples: 4-bit $D418
Size: 69 bytes
;original ; .C:00a0 D8 CLD ; make sure no decimal 2,2 ; .C:00a1 85 0B STA $0B ; store A for later 3,5 ; .C:00a3 AD FE 80 LDA $80FE ; get sample 4,9 ; .C:00a6 C9 1F CMP #$1F ; is it 1f (end)? 2,11 ; .C:00a8 D0 0C BNE $00B6 ; if not, play 2/3,13/14 ; .C:00aa A9 00 LDA #$00 ; stop timer A 2,15 ; .C:00ac 8D 0E DD STA $DD0E ; 4,19 ; .C:00af A9 38 LDA #$38 ; set filter to enable 2,21 ; .C:00b1 8D 18 D4 STA $D418 ; low/band pass, vol 8 4,25 ; .C:00b4 D0 2A BNE $00E0 ; jump out of here 2/3 28 ; .C:00b6 24 0A BIT $0A ; check nibble toggle 3,17,3 ; .C:00b8 30 12 BMI $00CC ; if bit 7, do low 2/3,19,5 ; .C:00ba 4A LSR A ; high nibble sample 2,21,7 ; .C:00bb 4A LSR A ; get it into low 2,23,9 ; .C:00bc 4A LSR A ; nibble 2,25,11 ; .C:00bd 4A LSR A ; 2,27,13 ; .C:00be EA NOP ; timing cycles 2,29,15 ; .C:00bf EA NOP ; or placeholders 2,31,17 ; .C:00c0 18 CLC ; make sure to 2,33,19 ; .C:00c1 69 30 ADC #$30 ; keep enabling filter 2,35,21 ; .C:00c3 8D 18 D4 STA $D418 ; and play sample 4,39,25 ; .C:00c6 A9 80 LDA #$80 ; set nibble toggle 2,41,27 ; .C:00c8 85 0A STA $0A ; 3,44,30 ; .C:00ca D0 14 BNE $00E0 ; jump out of here 2/3,47,33 ; .C:00cc 29 0F AND #$0F ; low nibble, isolate 2,22 ; .C:00ce EA NOP ; placeholders/timers 2,24 ; .C:00cf EA NOP 2,26 ; .C:00d0 18 CLC ; play sample with 2,2,28 ; .C:00d1 69 30 ADC #$30 ; low and band filter 2,30 ; .C:00d3 8D 18 D4 STA $D418 ; on 4,34 ; .C:00d6 A9 00 LDA #$00 ; set nibble toggle 2,36 ; .C:00d8 85 0A STA $0A ; 3,39 ; .C:00da E6 A4 INC $A4 ; calculate address 5,44 ; .C:00dc D0 02 BNE $00E0 ; for next sample 2/3,46/47 ; .C:00de E6 A5 INC $A5 ; 5,51 ; .C:00e0 AD 0D DD LDA $DD0D ; ack timer interrupt 4,55,51,51 ; .C:00e3 A5 0B LDA $0B ; retrieve A 3,58,54,54 ; .C:00e5 40 RTI ; return from interrupt 6,64,60,60
60 cycles low nibble, 64 cycles high nibble max
This routine is placed at zero page, for speed, which is good, and timed using Timer A. It relies on a toggle to select which 4-bit nibble to play and sets low pass and bas pass filter to on state with each sample played. NOPs are used to time the two nibble routines so each of them takes the same time (to ensure stable sample rate), 60 cycles each. When the sample address crosses a page, 4 cycles are added to the low nibble routine. The theoretical max sample rate / play back speed is then 16420 Hz on a PAL machine. Of course, this will never be attainable, since a lot of other stuff needs to be taken care of as well, either on screen, or at the least playing the SID part of the tune.