603 lines
21 KiB
Text
603 lines
21 KiB
Text
== Audio
|
|
|
|
Declared in `<joey/audio.h>`.
|
|
|
|
There are three layers, and on some platforms they share physical hardware:
|
|
|
|
* *Module playback* -- `jlAudioPlayMod`, four-channel Protracker `.MOD`.
|
|
* *Chip tracker* -- `jlMusicPlay`, a portable event stream driving three tone
|
|
voices plus noise at register-write cost on the machines' sound chips (DVX
|
|
and the hosted ports mix them in software). The recommended music path.
|
|
* *Voices and effects* -- `jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and
|
|
the streaming forms.
|
|
|
|
[WARNING]
|
|
====
|
|
On the Amiga the tone voices and the noise *are* the hardware the module
|
|
player drives: Paula's four channels. While a `.MOD` plays, `jlAudioVoice` and
|
|
`jlAudioNoise` writes fight the player's register writes and lose. On the
|
|
plain ST (not the STe build) digital sound -- a module, an effect, or a
|
|
stream -- plays through the sound chip's channel A, so tone voice 0 is silent
|
|
while it plays and sounds again when it stops; voices 1 and 2 play on. Choose
|
|
the module layer or the voice and tracker layer per scene, never both at once.
|
|
====
|
|
|
|
`jlInit` starts audio and `jlShutdown` stops it; a program that wants none
|
|
sets `jlConfigT.noAudio`, and every audio call is then silent. Sound that does
|
|
not start is not fatal: `jlInit` still succeeds, modules and sound effects are
|
|
silent, `jlAudioTickRegister` refuses, and the log says so. The tone and noise
|
|
voices, and so `jlMusicPlay`, still play where they drive a sound chip of
|
|
their own: on the Amiga, the ST, DOS, and the X68000.
|
|
|
|
The mix is the same on every port, within 3 dB: a sound effect at full scale,
|
|
and a module channel at full volume playing a full-scale sample, are each as
|
|
loud as a tone voice at attenuation 0 (a square wave sampled at full scale
|
|
peaks where the voice does), and the noise at attenuation 0 is as loud as a
|
|
voice, so modules, effects, voices, and `jlMusicPlay` keep their balance from
|
|
one machine to the next. The ports that mix in software put that peak at a
|
|
quarter of full scale, so the three voices and the noise together, or a
|
|
module's four channels, fit without clipping (the plain ST is the exception,
|
|
<<portsDifferAudio>>).
|
|
|
|
=== Constants
|
|
|
|
[cols="3,1,4", options="header"]
|
|
|===
|
|
|Name |Value |Meaning
|
|
|
|
|`JL_AUDIO_SFX_SLOTS` |5 |Sound effect slots, 0..4.
|
|
|`JL_AUDIO_VOICES` |3 |PSG-style tone voices, 0..2.
|
|
|`JL_SFX_RAW_SIGNED8` |0 |Raw stream format: `int8_t` PCM.
|
|
|`JL_SFX_RAW_UNSIGNED8_NOZERO` |1 |Raw stream format: `uint8_t` biased 0x80,
|
|
and 0x00 is forbidden (it is the DOC stop byte).
|
|
|===
|
|
|
|
[TIP]
|
|
====
|
|
Prefer *slot 4* for game sound effects when music is playing. The IIgs plays
|
|
tone voices 0..2 on sound effect slots 0..2 and the noise on slot 3: an effect
|
|
and a voice on the same slot cut each other off, and a voice still being held
|
|
takes its slot back at the next `jlAudioFrameTick`. On the Amiga effects, the
|
|
module, the voices, and the noise share Paula's four channels, so an effect can
|
|
take a channel a voice is using. On the other ports the slots are independent
|
|
of the voice layer.
|
|
====
|
|
|
|
=== jlAudioFrameTick
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioFrameTick(void);
|
|
----
|
|
|
|
Hooks the engine into your game loop. Every port does its sound work here: DOS,
|
|
the ST, the X68000, DVX, and the hosted ports mix or refill their sound buffers
|
|
in it, so without it modules and effects stop or repeat; the IIgs and Amiga
|
|
feed each streaming sound effect its next chunk, notice a module's end, and
|
|
(on the IIgs) keep held notes sounding; and on every port it advances
|
|
`jlMusicPlay`.
|
|
|
|
Call it once per frame, unconditionally.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlAudioPlayMod
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
|
|
----
|
|
|
|
Begins module playback. If a module is already playing it is replaced.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`data` |`const uint8_t *` |Platform-native module blob: `.mod` on every port
|
|
but the IIgs (an `.amod`, with its end marker, plays on the Amiga too), `.ntp`
|
|
on the IIgs. The asset pipeline produces the right form.
|
|
|`length` |`uint32_t` |Blob size in bytes.
|
|
|`loop` |`bool` |`true` plays forever.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
[NOTE]
|
|
====
|
|
`loop = false` plays the song once and stops it. The IIgs's replayer has a
|
|
play-once mode; the ports that mix modules in software stop where the song
|
|
comes back to a row it has played (its end, or a jump back), at an `F00`, or
|
|
at the Amiga's `E8F` stop marker; and the Amiga adds that marker as it copies
|
|
the module to chip RAM, sending the song's jumps back to it
|
|
(<<_music_protracker_modules>>). A module with no room for one, all 128 song
|
|
positions or all 128 patterns in use, loops there anyway, and so does one
|
|
whose jump back sits in a pattern it also plays where that jump goes
|
|
forward.
|
|
====
|
|
|
|
=== jlAudioStopMod
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioStopMod(void);
|
|
----
|
|
|
|
Stops the current module and resets the playhead, so the next `jlAudioPlayMod`
|
|
starts from the top. It goes silent at once on every port: the ports that mix
|
|
ahead of the sound chip (DOS and the X68000) take it back out of what they
|
|
have mixed, leaving the sound effects there playing on, as they start a new
|
|
module, or a new sound effect, in what is mixed already rather than after
|
|
it.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlAudioIsPlayingMod
|
|
|
|
[source,c]
|
|
----
|
|
bool jlAudioIsPlayingMod(void);
|
|
----
|
|
|
|
*Parameters:* none.
|
|
|
|
*Returns:* `true` while a module is producing output. `false` after a stop,
|
|
once a song played with `loop = false` has ended, before the first play, and on
|
|
platforms where audio init failed.
|
|
|
|
=== jlAudioPlaySfx
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioPlaySfx(uint8_t slot, const uint8_t *sample,
|
|
uint32_t length, uint16_t rateHz);
|
|
----
|
|
|
|
Triggers a one-shot digital sound effect. If the slot is already playing, the
|
|
new sample replaces it.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`slot` |`uint8_t` |Slot 0..`JL_AUDIO_SFX_SLOTS`-1. Prefer 4 alongside music.
|
|
|`sample` |`const uint8_t *` |Raw signed 8-bit PCM.
|
|
|`length` |`uint32_t` |Sample count in bytes.
|
|
|`rateHz` |`uint16_t` |The rate the sample was recorded at; the engine
|
|
re-pitches to its own output rate. Any rate plays at its pitch: one faster
|
|
than the Amiga's or the IIgs's chip skips samples there
|
|
(<<portsDifferAudio>>).
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
Most ports read the sample where it is while it plays, so keep it valid and
|
|
unchanged until it has finished or the slot has been stopped. The IIgs also
|
|
remembers the last sample each slot played by its address, length, and rate,
|
|
and plays its own copy when they match: changed bytes at the same address,
|
|
length, and rate are not heard there.
|
|
|
|
[NOTE]
|
|
====
|
|
The full -128..127 range is accepted everywhere, but the IIgs plays -128 as
|
|
-127: its DOC treats the corresponding byte as a wave-end marker and would halt
|
|
the voice. One level in 256, inaudible in practice.
|
|
====
|
|
|
|
=== jlAudioStreamFillT
|
|
|
|
[source,c]
|
|
----
|
|
typedef uint32_t (*jlAudioStreamFillT)(void *ctx, int8_t *dst, uint32_t count);
|
|
----
|
|
|
|
Stream refill callback. Write up to `count` signed 8-bit samples to `dst` and
|
|
return how many you wrote. Returning 0 signals end of stream and the slot
|
|
deactivates.
|
|
|
|
[WARNING]
|
|
====
|
|
On every port it is called in program time, from the call that starts the
|
|
stream and then from `jlAudioFrameTick` (on DVX, also while the desktop runs).
|
|
It must not block, and it should be quick: it runs inside those calls.
|
|
====
|
|
|
|
=== jlAudioPlaySfxStream
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill,
|
|
void *ctx, uint16_t rateHz);
|
|
----
|
|
|
|
Plays a streaming sound effect, pulling samples from your callback as the
|
|
slot's prefetch buffer drains. Sound effects can therefore be arbitrarily long
|
|
with no caller-side allocation.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`slot` |`uint8_t` |Slot 0..4. Replaces whatever is playing there.
|
|
|`fill` |`jlAudioStreamFillT` |Refill callback.
|
|
|`ctx` |`void *` |Opaque pointer handed back to the callback.
|
|
|`rateHz` |`uint16_t` |Rate your callback produces; the engine resamples with
|
|
linear interpolation. Any rate plays at its pitch, as for `jlAudioPlaySfx`.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
Every port feeds streams from `jlAudioFrameTick`, so *you must be calling
|
|
`jlAudioFrameTick` every frame* or the stream stops. On the IIgs the engine
|
|
plays one chunk at a time (7 KB) and fires the next from it, so a chunk seam
|
|
there can carry up to one frame of jitter. The Amiga loops an 8 KB ring that
|
|
`jlAudioFrameTick` tops up as it plays, with no seams; if the calls stop for
|
|
longer than the ring lasts (a third of a second at 22 kHz), it plays the
|
|
ring's old contents again.
|
|
====
|
|
|
|
=== jlAudioSfxRawFormat / jlAudioPlaySfxStreamRaw
|
|
|
|
[source,c]
|
|
----
|
|
uint8_t jlAudioSfxRawFormat(void);
|
|
void jlAudioPlaySfxStreamRaw(uint8_t slot, jlAudioStreamFillT fill,
|
|
void *ctx, uint16_t rateHz);
|
|
----
|
|
|
|
`jlAudioSfxRawFormat` returns this port's raw stream format, either
|
|
`JL_SFX_RAW_SIGNED8` or `JL_SFX_RAW_UNSIGNED8_NOZERO`.
|
|
|
|
`jlAudioPlaySfxStreamRaw` is `jlAudioPlaySfxStream` except your callback
|
|
delivers bytes *already in that format*, so the port skips its per-byte
|
|
conversion of every chunk. On the IIgs that conversion costs about 7
|
|
microseconds a byte, some 50 ms for each 7 KB chunk. DVX's raw format is the
|
|
IIgs's too, since its sound library's 8-bit PCM is unsigned, and it skips its
|
|
flip of every byte. Every other port's raw format is signed PCM, so there this
|
|
is simply the plain call.
|
|
|
|
If your producer cannot make the port's format, use `jlAudioPlaySfxStream`.
|
|
|
|
*Parameters:* as `jlAudioPlaySfxStream`. *Returns:* nothing.
|
|
|
|
=== jlAudioSetPan
|
|
|
|
[source,c]
|
|
----
|
|
typedef enum {
|
|
JL_AUDIO_PAN_LEFT = 0, JL_AUDIO_PAN_CENTER, JL_AUDIO_PAN_RIGHT, JL_AUDIO_PAN_COUNT
|
|
} jlAudioPanE;
|
|
|
|
void jlAudioSetPan(uint8_t slot, jlAudioPanE pan);
|
|
----
|
|
|
|
Places a sound effect slot in the stereo field. Slots start centered, and the
|
|
setting sticks to the *slot*, surviving retriggers -- so set it once for a
|
|
sound that should come from one side.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`slot` |`uint8_t` |Slot 0..4.
|
|
|`pan` |`jlAudioPanE` |Placement. Out-of-range values are ignored.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
What each port can actually do:
|
|
|
|
[cols="1,4", options="header"]
|
|
|===
|
|
|Platform |Behavior
|
|
|
|
|IIgs |All three. The DOC pans per oscillator, so a centered sound plays on two
|
|
oscillators, one per side. A stock IIgs sums to mono, so this shows up only on
|
|
a stereo card or an emulator.
|
|
|Amiga |Left and right by choosing the Paula channel side; a Paula channel plays
|
|
on one side only. A centered sound plays on a free channel each side when both
|
|
sides have one, so it reaches both ears; otherwise on the one channel
|
|
PTPlayer picks.
|
|
|X68000 |Ignored. Everything mixes into one ADPCM stream, so panning would move
|
|
all audio at once.
|
|
|DOS |All three on an SB16, which plays stereo: the module's channels on Paula's
|
|
sides, as on the Amiga, and each effect on its side. Ignored on the Sound
|
|
Blasters before it, which play mono.
|
|
|ST |Ignored. Output is mono.
|
|
|STe build |All three: the DMA chip plays stereo, the module's channels on
|
|
Paula's sides, as on the Amiga, and each effect on its side.
|
|
|DVX |All three, through DVX's stereo mixer (on a stereo card).
|
|
|SDL3, web, Win32, PSP, Haiku, BeOS |All three: the port mixes in stereo.
|
|
|Win16 |Ignored. Output is mono.
|
|
|===
|
|
|
|
=== jlAudioStopSfx
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioStopSfx(uint8_t slot);
|
|
----
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`slot` |`uint8_t` |Slot to silence. A no-op if already idle.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlAudioTone
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioTone(uint16_t freqHz);
|
|
----
|
|
|
|
A single tone that plays until the next call: `jlAudioVoice(0, freqHz, 0)`,
|
|
tone voice 0 at full volume, on every port. Designed for AGI-era music where
|
|
the engine programs a divisor and lets the chip play, so CPU cost during
|
|
playback is zero. A program that mixes it with `jlAudioVoice` or `jlMusicPlay`
|
|
shares voice 0 with them. On DOS without an OPL2 (AdLib) chip, voice 0, and so
|
|
this tone, is the PC speaker.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`freqHz` |`uint16_t` |Frequency in Hz. 0 silences the generator.
|
|
|===
|
|
|
|
*Returns:* nothing. Silent wherever voice 0 is (see `jlAudioVoice`).
|
|
|
|
=== jlAudioVoice
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
|
----
|
|
|
|
Three-voice PSG-style polyphonic tone generator.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`voice` |`uint8_t` |Voice 0, 1, or 2.
|
|
|`freqHz` |`uint16_t` |Frequency in Hz. 0 silences this voice.
|
|
|`atten` |`uint8_t` |AGI / SN76489-style 4-bit attenuation: 0 loudest,
|
|
15 silent, 2 dB per step as near as the chip comes (the ST's YM2149 has
|
|
sixteen levels about 3.5 dB apart, so a step there is within 1.6 dB).
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
Mapping to hardware: DOS uses AdLib OPL2 melodic channels 0..2 (with no OPL2,
|
|
voice 0 is the PC speaker and voices 1 and 2 are silent); the ST uses YM2149
|
|
channels A, B, and C (on the plain ST voice 0 is silent while digital sound
|
|
plays); the Amiga uses Paula channels 0..2 looping a two-byte square sample;
|
|
the IIgs uses Ensoniq DOC oscillators playing a resident square on sound
|
|
effect slots 0..2; the X68000 uses YM2151 channels 0..2, one operator each,
|
|
so its tones are sine waves; DVX and the hosted ports mix square waves in
|
|
software (DVX through a stream of its sound library's mixer; on a machine
|
|
whose only sound is the PC speaker, DVX's own square tones instead). The
|
|
OPL2's waves are not square either.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
A note sounds until the next call for that voice. The IIgs plays each note as
|
|
a sample a fraction of a second long and restarts the notes being held from
|
|
`jlAudioFrameTick`, so call that every frame, as every program should; a frame
|
|
much longer than 80 ms can leave a gap in a held note there.
|
|
====
|
|
|
|
=== jlAudioNoise
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioNoise(uint8_t pitch, uint8_t atten);
|
|
----
|
|
|
|
Noise and percussion generator -- the fourth logical voice of the tracker
|
|
layer.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`pitch` |`uint8_t` |0..31 in YM2149 noise-period style: 0 is the brightest
|
|
hiss (hats), 31 the lowest rumble (toms).
|
|
|`atten` |`uint8_t` |As `jlAudioVoice`. 15 or above silences.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
The noise runs at 125000 / `pitch` Hz (`pitch` 0 as 1), the YM2149's rate, on
|
|
every port as far as its chip goes. Mapping: the ST uses the YM2149 noise
|
|
generator mixed onto channel C; the X68000 the YM2151's noise, at the same
|
|
rates exactly; the Amiga Paula channel 3 looping an LFSR noise sample, whose
|
|
fastest rate, 28.6 kHz, pitches 0 to 4 all play at; the IIgs a resident noise
|
|
sample on slot 3, up to 26 kHz, likewise for pitches 0 to 4; and DVX and the
|
|
hosted ports mix the YM2149's own noise in software (its 17-bit generator),
|
|
so it sounds as on the ST (DVX with only the PC speaker: a noise tone of its
|
|
sound library). DOS mixes the same generator into the Sound Blaster's
|
|
stream, at about 22 kHz, so pitches 0 to 5 sound alike there; an AdLib with
|
|
no Sound Blaster plays the OPL2 rhythm-mode snare instead, leaving the three
|
|
tone channels untouched: a tuned drum, not free-running noise, whose pitch
|
|
spreads over the snare's range rather than following the YM2149's rate.
|
|
|
|
[WARNING]
|
|
====
|
|
On the ST, noise shares channel C's volume register with tone voice 2. Author
|
|
noise hits and voice-2 notes so they do not sound simultaneously; `songbake`
|
|
warns when they overlap.
|
|
====
|
|
|
|
=== jlMusicPlay
|
|
|
|
[source,c]
|
|
----
|
|
bool jlMusicPlay(const uint8_t *data, uint32_t length, bool loop);
|
|
----
|
|
|
|
Plays a JYM1 event stream baked by `songbake` through the tone and noise layer.
|
|
One authored song plays on every port, at register-write cost wherever a
|
|
sound chip makes it (DVX and the hosted ports mix it in software, and DOS
|
|
mixes its noise into the Sound Blaster's stream), so it is fully viable on
|
|
the ST and cheap on DOS, which `.MOD` playback is not.
|
|
|
|
Playback is pumped by `jlAudioFrameTick` and paced by `jlMillisElapsed`, so
|
|
tempo holds across 50, 60, and 70 Hz ports.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`data` |`const uint8_t *` |JYM1 stream. *Read in place* -- it must stay valid
|
|
until `jlMusicStop` or song end.
|
|
|`length` |`uint32_t` |Stream size in bytes.
|
|
|`loop` |`bool` |`true` restarts at the authored loop point, or the top if the
|
|
song has none. `false` silences the voices at song end.
|
|
|===
|
|
|
|
*Returns:* `true` on success; `false` on a malformed stream, in which case
|
|
nothing plays. With `jlConfigT.noAudio` the song runs silently, its time and
|
|
its end as usual.
|
|
|
|
=== jlMusicStop
|
|
|
|
[source,c]
|
|
----
|
|
void jlMusicStop(void);
|
|
----
|
|
|
|
Stops tracker playback and silences tones and noise. The playhead is discarded;
|
|
use `jlMusicPause` to keep it. Safe when nothing is playing.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlMusicIsPlaying
|
|
|
|
[source,c]
|
|
----
|
|
bool jlMusicIsPlaying(void);
|
|
----
|
|
|
|
*Parameters:* none.
|
|
|
|
*Returns:* `true` while a stream is loaded and active. Paused counts as active;
|
|
stopped or finished does not.
|
|
|
|
=== jlMusicPause / jlMusicResume
|
|
|
|
[source,c]
|
|
----
|
|
void jlMusicPause(void);
|
|
void jlMusicResume(void);
|
|
----
|
|
|
|
Pauses in place -- voices go silent, the playhead keeps its position -- and
|
|
picks up where it left off. The current row restarts, and wall-clock time spent
|
|
paused is not fast-forwarded through. Both are safe no-ops when there is no
|
|
active song.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
=== jlMusicSetAtten
|
|
|
|
[source,c]
|
|
----
|
|
void jlMusicSetAtten(uint8_t atten);
|
|
----
|
|
|
|
Global music attenuation, added to every authored voice and noise attenuation.
|
|
Applies immediately to sounding voices and to everything played afterwards.
|
|
Step it over successive frames for fades.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`atten` |`uint8_t` |0 plays as authored, 15 is silent.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
[NOTE]
|
|
====
|
|
`jlMusicPlay` does *not* reset this, so a fade-out carries into the next song
|
|
unless you clear it.
|
|
====
|
|
|
|
=== jlAudioTickRegister
|
|
|
|
[source,c]
|
|
----
|
|
typedef void (*jlAudioTickFnT)(void);
|
|
bool jlAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
|
|
----
|
|
|
|
Registers a periodic callback for sound schedulers needing better than
|
|
frame-rate resolution. PSG-era music drivers use this to write register changes
|
|
at exact note boundaries: frame-rate scheduling batches several notes into one
|
|
frame, so the chip only hears the last write and short notes vanish.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`fn` |`jlAudioTickFnT` |Callback, which may be fired from an interrupt. Pass
|
|
`NULL` to uninstall.
|
|
|`hz` |`uint16_t` |Firings per second.
|
|
|===
|
|
|
|
*Returns:* `true` if installed. `false` when `hz` is 0, when audio is off or
|
|
did not start, or when the port cannot take the rate (above the mixer's rate
|
|
on DVX and the hosted ports). Uninstalling always returns `true`.
|
|
|
|
[WARNING]
|
|
====
|
|
`fn` may run in an interrupt: it does on DOS, the Amiga, the ST, and the
|
|
X68000. Keep it to memory reads and direct hardware register writes -- no
|
|
allocation, no `printf`, nothing blocking -- and share data with it through
|
|
`jlAudioCriticalEnter` and `jlAudioCriticalExit`.
|
|
====
|
|
|
|
On every port the calls average exactly `hz` a second; where they fall
|
|
differs. DOS, the Amiga, DVX, and the hosted ports place each one at its own
|
|
time. The ST and X68000 deliver them from the system's own timer, 200 and 100
|
|
times a second, so the calls due between two of its ticks come together. The
|
|
IIgs delivers them in program time from `jlAudioFrameTick`, so a frame's calls
|
|
come together at it.
|
|
|
|
On DOS this reprograms PIT counter 0 and hooks INT 8, keeping the BIOS tick
|
|
counter advancing at the canonical 18.2 Hz through an accumulator so `time`
|
|
stays correct. `jlMillisElapsed` carries on from where it was when the tick
|
|
starts or stops; it never jumps back.
|
|
|
|
On the Amiga a `timer.device` request on the E-clock raises a software
|
|
interrupt at each tick. On the ST the tick rides TOS's 200 Hz Timer C
|
|
interrupt, and on the X68000 Human68k's 100 Hz Timer C interrupt.
|
|
|
|
On the IIgs `fn` runs in program time, at `jlAudioFrameTick`, as many times as
|
|
the display's beats since the last frame have made due, so the average is
|
|
exact whatever the program's frame rate.
|
|
|
|
On DVX and the hosted ports the sound is mixed in program time, a few frames
|
|
ahead (on DVX by its sound library's pump, which DVX also runs while the
|
|
desktop has the processor), and `fn` is called by the mixer between exactly
|
|
the two samples where its tick falls: in program time, so it may do anything,
|
|
yet a note it starts begins at the right place in the sound. `hz` may be
|
|
anything up to the mixer's rate: 44100 on the hosted ports (22222, or 22050
|
|
where the card refuses that, on Win16), and on DVX the sound card's mixing
|
|
rate. DVX's ticks keep time on a machine with no sound card too: its null
|
|
driver runs the mixer as a card would.
|
|
|
|
=== jlAudioCriticalEnter / jlAudioCriticalExit
|
|
|
|
[source,c]
|
|
----
|
|
void jlAudioCriticalEnter(void);
|
|
void jlAudioCriticalExit(void);
|
|
----
|
|
|
|
Critical section against the tick interrupt. If you mutate state that the
|
|
registered tick callback reads, wrap the mutation in these so the interrupt
|
|
cannot observe a half-written value. On DOS, the Amiga, the ST, and the
|
|
X68000 a section holds every interrupt off, so keep it short. Sections nest:
|
|
only the outermost pair does anything, and its Exit puts back the interrupt
|
|
state its Enter found. On the IIgs, DVX, and the hosted ports, whose
|
|
callbacks run in program time, they do nothing, and need not.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|