amiga-bootcamp/10_devices/audio.md
Ilia Sharin c4522ef2ed Add memory types article, expand blitter cookbook, update cross-references
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
2026-04-25 14:12:46 -04:00

8.2 KiB
Raw Blame History

← Home · Devices

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;           /* 064 */
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.device autodocs
  • See also: interrupts.md — interrupt server chain
  • See also: memory_types.md — audio sample buffers must reside in Chip RAM (Paula DMA)