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

279 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../README.md) · [Devices](README.md)
# 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
```mermaid
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
```c
/* 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:
```c
/* 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
```c
/* 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)
```c
/* 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) |
```c
/* 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`:
```c
/* 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`:
```c
/* 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:
```asm
; 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](../06_exec_os/interrupts.md) — interrupt server chain
- See also: [memory_types.md](../01_hardware/common/memory_types.md) — audio sample buffers must reside in Chip RAM (Paula DMA)