DACs and Sample Playback
This chapter is a placeholder. The full text is being written.
Synopsis
Square waves are fine for chip music. But the Next can also play actual sampled sound — speech, drums, wave-tables, anything you can put in memory as 8-bit PCM. The trick is the four built-in 8-bit DACs (left A, left B, right A, right B), each controlled by one I/O port. Write a byte → that byte becomes a voltage. Do it 8000 times a second and you have a wavetable synthesiser.
This chapter is where two earlier chapters pay off in a single application: we use the CTC to keep accurate sample-rate timing, and the zxnDMA to do the actual byte transfer with zero CPU overhead. We use both, side by side, and compare them.
Topics:
- The four DAC ports.
$FB(left A),$B3(left B),$0F(right A),$DF(right B), and the eight-bit unsigned PCM convention. - Routing. NextReg
$08bits — which DAC goes to which output, including “all four to both speakers” for max volume. - Sample formats. Raw 8-bit signed/unsigned, the 11025 Hz / 22050 Hz / 44100 Hz convention, and how to convert WAV in advance.
- CPU-driven playback. A CTC channel ticks at the sample rate; its ISR does an
OUT (DAC),Aand advances a pointer. Costs ~150 T-states per sample at 11 kHz. - DMA-driven playback. A CTC channel is wired to the DMA via NextReg
$CD. The DMA copies one byte from a sample buffer to the DAC port on every CTC tick. The CPU does literally nothing during playback. This is how the Next does professional-quality audio. - Looping, ping-pong, and end-of-buffer. DMA modes that automatically restart, plus status polling or a separate timing interrupt when you need to queue the next buffer.
- Mixing AY and DAC. Both go to the same output stage; the chip music continues underneath the samples.
What you should know first
Planned exercises
- CPU-paced sample. Play an 8 kHz speech sample using a CTC ISR.
- DMA-paced sample. Replay the same sample using DMA. Compare CPU load (use the CTC profiling utility from chapter 5 to measure how much main-loop time you actually win back).
- CTC-paced DMA streaming. Stream a small unsigned 8-bit sample buffer to a DAC port using burst-mode DMA paced by a CTC channel. The DMA transfers one byte per CTC trigger, releases the bus between samples, and lets the CPU continue running a small animation or counter in the foreground. The foreground activity proves the CPU is not trapped in the sample-output loop.
- Drum + chip music. AY tracker tune playing in the background, DMA-driven drum hits triggered on key press.
Planned Demo: Audio Streaming with CTC Timing
This demo belongs here rather than in the zxnDMA chapter because the interesting question is not how to upload a DMA table; it is how to produce a stable audio sample rate. The CTC supplies the sample clock, and zxnDMA moves one byte from the sample buffer to a DAC port on each CTC trigger.
The DMA program is close to an ordinary memory-to-port burst transfer, but WR2 has no prescaler. NextReg $CD connects a CTC channel’s ZC/TO pulse to the DMA trigger path:
CTC_CH3 equ $1B3B ; Use channel 3 for audio timing
NR_REG equ $243B
NR_DAT equ $253B
; --- Configure CTC Channel 3 as a timer at about 22 kHz.
; 28 MHz / 16 / 80 = 21,875 Hz
ld bc,CTC_CH3
ld a,%00000101 ; Timer, prescaler /16, TC follows
out (c),a
ld a,80
out (c),a
; --- Arm the CTC -> DMA trigger: NR $CD bit 3 = CTC channel 3.
ld a,$CD
ld bc,NR_REG
out (c),a
ld a,%00001000
ld bc,NR_DAT
out (c),a
; --- DMA program: burst mode, no WR2 prescaler.
audioCTCDMAProgram:
.dma reset
.dma wr0 a_to_b, transfer
audioCTCSrc: .dw 0 ; patch: source buffer address
audioCTCLen: .dw 0 ; patch: sample count
.dma wr1 memory, increment
.dma wr2 io, fixed ; no prescaler: CTC handles timing
.dma wr4 burst
.dw $00DF ; Port B = DAC port $DF
.dma wr5
.dma load
.dma enable
audioCTCDMAProgram_end:
; Kick off streaming: HL = buffer address, BC = sample count
ld (audioCTCSrc),hl
ld (audioCTCLen),bc
ld hl,audioCTCDMAProgram
ld b,audioCTCDMAProgram_end - audioCTCDMAProgram
ld c,$6B
otirThe CPU runs between CTC-triggered burst transfers. Each CTC tick delivers one sample to the DAC, and the foreground code can keep animating, polling input, or preparing the next buffer.