New articles: - 01_hardware/common/memory_types.md: comprehensive Chip/Fast/Slow RAM reference with DMA accessibility matrix, per-model configurations (A500-A4000), accelerator memory expansion (classic + modern), adaptive software behavior, pitfalls with impact analysis, FPGA/emulation notes - AGENTS.md: documentation standards and methodology guidelines Blitter programming (08_graphics/blitter_programming.md): - Rewrote minterm truth table with narrative explanation and worked example - Added 7 advanced use cases with assembly/C code: shifted BOB, hardware scroll, area fill polygon, interleaved bitplane BOBs, double-buffered game loop, GUI window drag, tile map renderer - Added Good/Bad Patterns section with 5 named antipatterns - Added Practical Limitations table (10 constraints with workarounds) - Expanded Common Minterms table with Description and Real-World Use Case columns Cross-reference updates: - Root README: added memory types to coverage, quick start, section index - 01_hardware/README: updated common/ folder description - address_space.md: linked to memory_types.md and chip_ram_expansion.md - memory_management.md (exec): linked to hardware memory types reference - bitmap.md, sprites.md, animation.md, audio.md: linked to memory_types.md explaining DMA Chip RAM requirement for each subsystem - chip_ram_expansion.md: linked to comprehensive memory types article
8.2 KiB
audio.device — DMA Audio Channels
Overview
audio.device provides access to the Amiga's 4 hardware DMA audio channels. Each channel plays 8-bit signed PCM samples from Chip RAM at a programmable rate. The hardware supports independent volume, period (sample rate), and unlimited looping. Audio DMA is handled entirely by the custom chips — the CPU is not involved in the actual sample playback.
Audio Hardware Architecture
graph TD
subgraph "Custom Chips (Paula)"
AUD0["AUD0 (Left)"]
AUD1["AUD1 (Right)"]
AUD2["AUD2 (Right)"]
AUD3["AUD3 (Left)"]
end
subgraph "Chip RAM"
S0["Sample 0"]
S1["Sample 1"]
S2["Sample 2"]
S3["Sample 3"]
end
S0 -->|DMA| AUD0
S1 -->|DMA| AUD1
S2 -->|DMA| AUD2
S3 -->|DMA| AUD3
AUD0 --> LEFT["Left Output"]
AUD3 --> LEFT
AUD1 --> RIGHT["Right Output"]
AUD2 --> RIGHT
Channel Stereo Assignment
| Channel | Default Panning | Hardware Register Base |
|---|---|---|
| 0 | Left | $DFF0A0 |
| 1 | Right | $DFF0B0 |
| 2 | Right | $DFF0C0 |
| 3 | Left | $DFF0D0 |
This is fixed in hardware — you cannot reassign channels to different outputs. Software panning requires mixing samples before playback.
Channel Registers
Each channel has 5 registers (10 bytes):
| Offset | Register | Size | Description |
|---|---|---|---|
| +$00 | AUDxLCH |
WORD | Pointer high — bits 18:16 of sample address |
| +$02 | AUDxLCL |
WORD | Pointer low — bits 15:1 of sample address |
| +$04 | AUDxLEN |
WORD | Length in words (not bytes). Min=1, Max=65535 |
| +$06 | AUDxPER |
WORD | Period (DMA clock divider). Lower = faster = higher frequency |
| +$08 | AUDxVOL |
WORD | Volume: 0 (silent) to 64 (max). Values >64 produce distortion on some models |
Period ↔ Frequency Conversion
/* Period = clock_constant / desired_frequency */
/* PAL: clock = 3546895 Hz */
/* NTSC: clock = 3579545 Hz */
#define PAL_CLOCK 3546895
#define NTSC_CLOCK 3579545
UWORD period_from_hz(ULONG freq, BOOL isPAL) {
return (isPAL ? PAL_CLOCK : NTSC_CLOCK) / freq;
}
ULONG hz_from_period(UWORD period, BOOL isPAL) {
return (isPAL ? PAL_CLOCK : NTSC_CLOCK) / period;
}
| Frequency | Period (PAL) | Period (NTSC) | Quality |
|---|---|---|---|
| 8287 Hz | 428 | 432 | Amiga standard (MOD default) |
| 11025 Hz | 322 | 325 | Low-quality speech |
| 16726 Hz | 212 | 214 | CD/4 quality |
| 22050 Hz | 161 | 162 | Near-CD quality |
| 28867 Hz | 123 | 124 | Maximum safe rate |
Warning
Periods below ~124 cause DMA contention with other chip resources (sprites, bitplanes). The hardware minimum is period=1, but anything below ~124 steals so many DMA cycles that display and other I/O suffers.
Channel Allocation via audio.device
The OS manages channel allocation to prevent conflicts between applications:
/* Request channels: */
#include <devices/audio.h>
/* Allocation map: which channels we want, in preference order */
UBYTE allocMap[] = {
0x01, /* channel 0 only */
0x02, /* channel 1 only */
0x04, /* channel 2 only */
0x08, /* channel 3 only */
0x03, /* channels 0+1 */
0x05, /* channels 0+2 (both left) */
0x0A, /* channels 1+3 (both right) */
0x0F /* all four channels */
};
struct MsgPort *audioPort = CreateMsgPort();
struct IOAudio *aio = (struct IOAudio *)
CreateIORequest(audioPort, sizeof(struct IOAudio));
aio->ioa_Request.io_Message.mn_Node.ln_Pri = 0; /* priority */
aio->ioa_Data = allocMap;
aio->ioa_Length = sizeof(allocMap);
BYTE err = OpenDevice("audio.device", 0, (struct IORequest *)aio, 0);
if (err == 0)
{
/* aio->ioa_AllocKey = unique key for this allocation */
UWORD allocKey = aio->ioa_AllocKey;
/* ioa_Request.io_Unit bits indicate which channel was allocated */
}
Priority System
| Priority | User |
|---|---|
| +127 | System critical (alerts) |
| +100 | Music player (dedicated) |
| 0 | Normal application |
| −128 | Background / optional |
Higher-priority requests can steal channels from lower-priority holders. The displaced holder receives an ADCMD_ALLOCATE signal.
Playing a Sample
/* After successful OpenDevice: */
aio->ioa_Request.io_Command = CMD_WRITE;
aio->ioa_Request.io_Flags = ADIOF_PERVOL; /* set period and volume */
aio->ioa_Data = sampleData; /* MUST be in Chip RAM! */
aio->ioa_Length = sampleLength; /* in bytes (must be even) */
aio->ioa_Period = 428; /* ~8287 Hz (PAL) */
aio->ioa_Volume = 64; /* 0–64 */
aio->ioa_Cycles = 1; /* 0 = loop forever, N = play N times */
BeginIO((struct IORequest *)aio);
/* Returns immediately — DMA plays in background */
/* Wait for completion: */
WaitIO((struct IORequest *)aio);
Important
Sample data must be in Chip RAM (
MEMF_CHIP). The DMA hardware can only read from the first 2 MB of address space. Passing a Fast RAM pointer results in silence or random noise.
Double-Buffering (Continuous Playback)
/* Use two IOAudio requests for gapless audio: */
struct IOAudio *aio_a = /* ... */;
struct IOAudio *aio_b = /* ... */;
/* Start first buffer: */
aio_a->ioa_Data = buffer_a;
aio_a->ioa_Length = BUFFER_SIZE;
aio_a->ioa_Cycles = 1;
BeginIO((struct IORequest *)aio_a);
/* Queue second buffer immediately: */
aio_b->ioa_Data = buffer_b;
aio_b->ioa_Length = BUFFER_SIZE;
aio_b->ioa_Cycles = 1;
BeginIO((struct IORequest *)aio_b);
/* When aio_a completes, fill buffer_a with new data and re-queue: */
WaitIO((struct IORequest *)aio_a);
/* fill buffer_a... */
BeginIO((struct IORequest *)aio_a);
/* When aio_b completes, fill buffer_b... */
Audio Interrupts
Each channel generates an interrupt when its DMA period completes (all sample words played):
| Channel | Interrupt Bit | INTENA/INTREQ |
|---|---|---|
| AUD0 | 7 | INTF_AUD0 ($0080) |
| AUD1 | 8 | INTF_AUD1 ($0100) |
| AUD2 | 9 | INTF_AUD2 ($0200) |
| AUD3 | 10 | INTF_AUD3 ($0400) |
/* Set up audio interrupt: */
struct Interrupt audioInt;
audioInt.is_Node.ln_Type = NT_INTERRUPT;
audioInt.is_Node.ln_Pri = 0;
audioInt.is_Node.ln_Name = "MyAudioInt";
audioInt.is_Data = myData;
audioInt.is_Code = (APTR)AudioIntHandler;
AddIntServer(INTB_AUD0, &audioInt);
/* Handler — called when channel 0 finishes playing: */
__saveds void AudioIntHandler(void)
{
/* Reload next sample segment into AUD0LCH/AUD0LCL/AUD0LEN */
custom->aud[0].ac_ptr = nextSample;
custom->aud[0].ac_len = nextLength / 2; /* length in words */
}
Modulation Modes
The Amiga audio hardware supports two modulation modes for channels 0+1 and 2+3:
Amplitude Modulation
Channel N modulates the volume of channel N+1:
/* Channel 0 data controls channel 1's volume */
custom->adkcon = ADKF_USE0V1; /* enable AM: ch0 → ch1 volume */
Period Modulation (Frequency Modulation)
Channel N modulates the period of channel N+1:
/* Channel 0 data controls channel 1's period */
custom->adkcon = ADKF_USE0P1; /* enable PM: ch0 → ch1 period */
These modes are rarely used in practice — they reduce the effective channel count from 4 to 2.
Direct Hardware Access (Bypassing audio.device)
Games and demos often bypass audio.device entirely:
; Direct hardware — play a sample on channel 0:
LEA $DFF000, A5 ; custom chip base
MOVE.L #sample, $A0(A5) ; AUD0LC — sample pointer
MOVE.W #length/2, $A4(A5) ; AUD0LEN — length in words
MOVE.W #428, $A6(A5) ; AUD0PER — period (8287 Hz)
MOVE.W #64, $A8(A5) ; AUD0VOL — max volume
; Enable DMA for channel 0:
MOVE.W #$8201, $96(A5) ; DMACON — set DMAEN + AUD0EN
Warning
: Direct access conflicts with any OS audio. Always
OwnBlitter()/Forbid()and disable audio device first if mixing approaches.
References
- NDK39:
devices/audio.h,hardware/custom.h - HRM: Amiga Hardware Reference Manual — Audio DMA chapter
- ADCD 2.1:
audio.deviceautodocs - See also: interrupts.md — interrupt server chain
- See also: memory_types.md — audio sample buffers must reside in Chip RAM (Paula DMA)