pub struct PlayerState {Show 13 fields
pub samples: Vec<SampleBody>,
pub patterns: Vec<Pattern>,
pub order: Vec<u8>,
pub song_length: u8,
pub channels: Vec<Channel>,
pub speed: u8,
pub bpm: u8,
pub order_index: u8,
pub row: u8,
pub tick: u8,
pub tick_sample_cursor: u32,
pub sample_rate: u32,
pub ended: bool,
/* private fields */
}Expand description
Top-level player state. Owns samples, patterns, order, and the
per-channel mixer. Feeds render(dst) to fill an interleaved stereo
S16 buffer.
Fields§
§samples: Vec<SampleBody>§patterns: Vec<Pattern>§order: Vec<u8>§song_length: u8§channels: Vec<Channel>§speed: u8§bpm: u8§order_index: u8Current position in the order table (0..song_length).
row: u8Current row inside the current pattern (0..64).
tick: u8Current tick inside the current row (0..speed).
tick_sample_cursor: u32Samples emitted so far within the current tick.
sample_rate: u32§ended: boolFlag set when the song has wrapped past its last order.
Implementations§
Source§impl PlayerState
impl PlayerState
Sourcepub const DEFAULT_PAN_SEPARATION: f32 = 0.5
pub const DEFAULT_PAN_SEPARATION: f32 = 0.5
Default stereo pan separation (0.5 = 50 %).
See the pan_separation field doc-comment for the rationale.
We default to 0.5 — half-way between the
Protracker-effects-MODFIL12.txt §11 hard pan recommendation
(which itself warns against full hard pan “especially when
using headphones”) and full mono. Empirically this is the
value that minimises cross-correlation drift versus a
black-box reference render on real-world MODs (halluc.mod,
rhmst.mod) — a partial-bleed default is common practice.
An r14 trial used 0.7 but on these specific fixtures 0.5
yielded ~3 % better xcorr per 1 s window across the entire
song. Override with PlayerState::set_pan_separation.
Sourcepub const RAMP_FRAMES: u32 = 44
pub const RAMP_FRAMES: u32 = 44
Length of the per-trigger volume ramp, in output frames. 44 frames at 44.1 kHz ≈ 1 ms — enough to smooth the discontinuity at a fresh note-on without smearing percussive transients audibly.
Without this ramp, every note re-trigger emits a single-sample
step of magnitude (prev_mixed - new_mixed) into the post-mix
L/R bus. On real-world MODs (halluc.mod, rhmst.mod) those
steps were measured at ~5775 LSB and were the dominant
contributor to per-second cross-correlation drift versus a
black-box reference render. A 1 ms linear crossfade
corresponds to the Protracker-effects-MODFIL12.txt §1.6
note about Paula taking ~140 µs to settle on a fresh DMA
fetch (we round up to one full output millisecond so the
ramp is robust at any reasonable output sample rate).
Sourcepub const FIXED_RC_CUTOFF_HZ: f32 = 16_000.0
pub const FIXED_RC_CUTOFF_HZ: f32 = 16_000.0
Cutoff of the always-on first RC stage that sits between Paula’s DAC and the audio jacks.
Real-hardware schematic R/C values yield 1/(2πRC) ≈ 4400 Hz
(rounded to 5000 Hz in r14 of this module to model an
“authentic” Amiga sound). However, the prevailing modern
rendering convention effectively bypasses this stage in
the default render path — only the LED-gated stage is
modelled — because the real-hardware corner lops off audible
content that listeners expect to hear back from a MOD
render. With the strict 5 kHz value we measured
cross-correlation drift against a black-box reference render
on rhmst.mod of about 5 % (~0.94 vs ~0.99) — i.e. the
always-on filter was the dominant contributor to per-second
drift versus the modern reference.
We default to 16000 Hz to make the always-on stage audibly transparent (it still rolls off ultrasonic content past the resampler, which is the only PT-faithful purpose it serves at modern output rates).
Documentation references: paula-filter-notes.md,
docs/audio/trackers/mod/openmpt-module-formats.html
(Resampling).
Sourcepub const LED_FILTER_CUTOFF_HZ: f32 = 11_500.0
pub const LED_FILTER_CUTOFF_HZ: f32 = 11_500.0
Cutoff of the LED-controlled second RC stage (the one
toggled by E00 / E01).
The real Amiga “Power LED” filter is a 12 dB/oct Sallen-Key
cell with ~3.275 kHz nominal corner — that’s the
“spec-strict” value previously used here. However,
multimedia-cx-protracker.html documents an
11500 Hz 1-pole approximation that the modern PT
rendering convention converges on, and every
cross-correlation measurement against a black-box reference
render confirms 11500 Hz is much closer to the modern-player
ground truth than 3300 Hz is.
The Sallen-Key value lops off so much HF content that
real-world MOD files render audibly muffled compared to
what their authors were probably listening to in their
final mixdown — and this manifests as cross-correlation
dips of 0.05–0.10 across most of rhmst.mod and
halluc.mod.
pub fn new( header: &ModHeader, samples: Vec<SampleBody>, patterns: Vec<Pattern>, sample_rate: u32, ) -> Self
Sourcepub fn set_pan_separation(&mut self, sep: f32)
pub fn set_pan_separation(&mut self, sep: f32)
Override the stereo pan separation. 1.0 = full Amiga hard
pan (channels 0/3 → LEFT only, 1/2 → RIGHT only). 0.0 =
full mono. Values outside 0.0..=1.0 are clamped.
Sourcepub fn pan_separation(&self) -> f32
pub fn pan_separation(&self) -> f32
Read the current stereo pan separation.
Sourcepub fn samples_per_tick(&self) -> u32
pub fn samples_per_tick(&self) -> u32
Samples-per-tick rounded down. Classic formula is
sample_rate * 2.5 / BPM.
Sourcepub fn channel_is_left(i: usize) -> bool
pub fn channel_is_left(i: usize) -> bool
True if track index i is hard-panned left under the classic
Amiga convention (channels 0 & 3 left, 1 & 2 right; for >4 channels
the pattern repeats every 4).
Sourcepub fn render(&mut self, dst: &mut [i16]) -> usize
pub fn render(&mut self, dst: &mut [i16]) -> usize
Render n_frames stereo samples into dst (interleaved S16,
length = n_frames * 2). Returns samples actually rendered (may be
less than requested if song ends).
Sourcepub fn render_per_channel(
&mut self,
planes: &mut [&mut [i16]],
n_frames: usize,
) -> usize
pub fn render_per_channel( &mut self, planes: &mut [&mut [i16]], n_frames: usize, ) -> usize
Render into one S16 plane per MOD channel. planes.len() must
equal self.channels.len(); each plane receives the same number
of samples, and all planes must be at least n_frames long.