class

Tryst::SDL::Mixer

Inherits Reference < Object

An SDL3_mixer mixer: the thing audio is loaded into and played through. Two kinds, and the difference matters:

  • Mixer.new opens an audio device and mixes in real time on SDL's own audio thread. What an application wants.
  • Mixer.buffered has no device at all and produces audio only when #generate asks, as fast as it is asked. What a test wants: no hardware, no waiting, and the mixed samples in hand.

A mixer is an object you make and own; there is no implicit global one. Mixer.default is a settable convenience for code that does not want to name one, and nothing else reaches for it.

Constructors

buffered(spec : AudioSpec = AudioSpec.new) : Mixer

A mixer with no device behind it, producing audio only when #generate asks. The readable spelling of new(spec, buffered: true).

Source
default
Source
new(spec : AudioSpec | Nil = nil, buffered : Bool = false)

Opens an audio device and mixes on SDL's audio thread. A nil spec lets the device choose; the mixer converts everything to whatever it settled on either way, so naming one only saves conversion work.

buffered: true builds the device-less kind instead, where the spec is what the mixer produces rather than a request.

Source

Class methods

decoders

The decoders this build has, e.g. WAV, MP3, OGG. Decided during init, so the library has to be up for this to answer usefully.

Source
default=(mixer : Mixer | Nil) : Mixer | Nil

Nilable so it can be put back to "nothing yet", which is what a test needs to leave the next one a clean slate.

Source
init

Reference counted: repeated calls succeed and each needs its own quit. Every mixer takes a reference in its constructor and drops it in #destroy, so calling these directly is only needed to load the decoders before there is any mixer to load them for.

Source
quit
Source
version

The SDL3_mixer actually loaded into this process. Safe to call before init, unlike everything else here.

Source

Instance methods

active_capture

@api private - the AudioCapture currently tapping this mixer, if any. SDL allows one post-mix callback per mixer, so this is what lets a second capture say so instead of silently unhooking the first and leaving it writing to a file nothing feeds.

Source
active_capture=(active_capture : AudioCapture | Nil)

@api private - the AudioCapture currently tapping this mixer, if any. SDL allows one post-mix callback per mixer, so this is what lets a second capture say so instead of silently unhooking the first and leaving it writing to a file nothing feeds.

Source
buffered?

True for a mixer with no audio device - the only kind #generate works on.

Source
destroy

Frees the mixer and drops this object's reference on the library. SDL destroys every track and every loaded audio attached to it at the same time, so anything still holding those must not use them afterwards.

Source
destroyed?
Source
dispatch_stopped

Runs the on_stopped block of every track that has finished since the last call, and reports how many were delivered.

This exists because SDL fires its stopped callback ON THE AUDIO THREAD, where running arbitrary Crystal is not safe. The audio thread only bumps a counter; this is what turns those counters into calls, on whichever thread calls it. An application drives it from its own loop - in a Tk program, a timer:

session.every(50) { mixer.dispatch_stopped }

Cheap to call with nothing pending. A block that raises propagates and leaves the rest undelivered until the next call.

Source
format

The format this mixer settled on, which for a device mixer is the device's choice and not necessarily what was asked for. #generate produces bytes in this format.

Source
gain

Master gain over everything this mixer plays: 1.0 unchanged, 0.0 silent, above 1.0 amplifies. A multiplier, not a 0-100 volume - there is no upper bound, and it gets loud fast.

Source
gain=(value : Float32 | Float64) : Float32
Source
generate(into : Bytes) : Int32

Mixes into into and reports how many bytes of it are REAL audio. The whole buffer is always written; anything past the return value is silence appended because every track ran out, which is how a test tells "it played" from "it didn't".

Buffered mixers only - a device mixer generates on its own audio thread whenever the device asks, and MIX_Generate refuses it.

Source
generate(frames : Int32) : Bytes

Mixes frames sample frames and hands back the whole buffer, trailing silence included.

Source
lock

Stops the mixer running for the duration of the block, so its state can be changed without racing the audio thread. Nestable.

Source
pause_all
Source
pause_tag(tag : String) : Nil
Source
play_tag(tag : String, loops : Int32 = 0, fade_ms : Int32 = 0, start_ms : Int32 = 0) : Nil

Starts every track carrying tag, all at the same instant in the mix. Same options as Track#play, applied to each.

Source
resume_all
Source
resume_tag(tag : String) : Nil
Source
set_tag_gain(tag : String, gain : Float32 | Float64) : Float32

ASSIGNS the gain of every track carrying tag. It is a bulk write to each track's own gain, not a group fader layered over the top - SDL3_mixer has no such thing, and Track#gain reads back whatever was set here.

Which means a tag alone cannot be an effects slider that keeps sounds at their relative volumes: setting the tag flattens every tagged track to the same gain. An application that mixes a quiet footstep against a loud explosion has to keep each sound's base gain itself and set them individually, or re-tag by loudness.

No matching getter, because SDL keeps no per-tag value to read.

Source
stop_all(fade_ms : Int32 = 0) : Nil

Halts every track on this mixer, optionally fading out first.

Source
stop_tag(tag : String, fade_ms : Int32 = 0) : Nil
Source
to_unsafe

@api private - lets a Mixer be passed straight to a MIX_ call.

Source
unwatch_stopped(track : Track) : Nil

@api private

Source
watch_stopped(track : Track) : Nil

@api private - Track#on_stopped registers itself here.

Source