pub struct Channel {Show 31 fields
pub sample_index: u8,
pub sample_pos: f32,
pub period: u16,
pub volume: u8,
pub active: bool,
pub finetune: i8,
pub effect: u8,
pub effect_param: u8,
pub arp_base_period: u16,
pub mem_porta_up: u8,
pub mem_porta_down: u8,
pub tone_porta_target: u16,
pub tone_porta_speed: u8,
pub mem_vibrato: u8,
pub vib_pos: i8,
pub vib_wave: Waveform,
pub mem_tremolo: u8,
pub trem_pos: i8,
pub trem_wave: Waveform,
pub mem_sample_offset: u8,
pub mem_volslide: u8,
pub retrig_ticks: u8,
pub cut_tick: u8,
pub delay: Option<DelayedTrigger>,
pub glissando: bool,
pub pending_sample: u8,
pub pending_led: Option<bool>,
pub pan: u8,
pub last_mixed_sample: f32,
pub ramp_prev_sample: f32,
pub ramp_remaining_frames: u32,
}Expand description
Per-channel playback state.
Fields§
§sample_index: u81-based sample index (0 = no sample ever triggered).
sample_pos: f32Fractional read position into the sample’s pcm buffer.
period: u16Current period (0 = silent / not playing).
volume: u8Current volume 0..=64.
active: boolWhether this channel is currently sounding.
finetune: i8Current finetune for the most recently loaded sample.
effect: u8Current effect command (0..=0xF).
effect_param: u8§arp_base_period: u16Arpeggio base period — the un-modulated period for this row.
mem_porta_up: u8Last portamento-up param (1xx), used when param == 0.
mem_porta_down: u8Last portamento-down param (2xx), used when param == 0.
tone_porta_target: u16Tone portamento target period (set by a note on a 3xy / 5xy row).
tone_porta_speed: u8Tone portamento speed (3xy / 5xy param; 0 reuses the last value).
mem_vibrato: u8Last vibrato params — nibble format rate<<4 | depth.
vib_pos: i8Vibrato LFO position, signed -32..=31.
vib_wave: WaveformVibrato waveform control.
mem_tremolo: u8Last tremolo params — nibble format rate<<4 | depth.
trem_pos: i8Tremolo LFO position, signed -32..=31.
trem_wave: WaveformTremolo waveform control.
mem_sample_offset: u8Last 9xx sample-offset param.
mem_volslide: u8Last volume-slide param (A/5/6).
retrig_ticks: u8E9x retrigger period (ticks between retriggers).
cut_tick: u8ECx note-cut tick (0 = no cut pending).
delay: Option<DelayedTrigger>EDx note-delay state — Some(delay_tick) while pending, filled in on
row entry.
glissando: boolGlissando flag (E3x): if set, tone portamento snaps to nearest semitone each tick rather than sliding smoothly.
pending_sample: u8Sample number written on a row that did not also trigger a note —
the swap is deferred until the next note-on per Protracker quirk
(see enter_row for the citation). 0 means no pending swap.
pending_led: Option<bool>Pending LED-filter state from an E0x on this channel’s row.
Some(true) = filter ON (E00, LED on). Some(false) = filter
OFF (E01, LED off). None = no E0 this row. Resolved at the
end of enter_row after all four channels’ tick-0 effects are
processed: like Fxx, a later channel’s E0 wins on the same row.
pan: u8Per-channel pan position, 0..=255. 0 = hard LEFT, 255 =
hard RIGHT, 128 = centre. This is the FT-extension panning
state set by 8xx (full 8-bit pan, Protracker-effects- MODFIL12.txt lines 1201-1207: “Command 8: Set FINE Panning …
xxxxyyyy = panning position. (0=Most left, 255=most right.)”)
and by E8x (rough nibble pan, Protracker-effects- MODFIL12.txt lines 1503-1505: “Command $E8: Set (Rough)
Panning … yyyy = panning value. $0 = most left, $F = most
right.”) — the E8 nibble is replicated across both halves of
the byte so E80 → 0x00, E8F → 0xFF, E87 → 0x77, matching
the “rough” 16-step interpretation also documented in
multimedia-cx-protracker.html (“$0 is hard left, $F is hard
right”). Initial values follow the Amiga LRRL hard-pan
convention (channels 0 & 3 → 0, 1 & 2 → 255, pattern repeats
every 4) so a MOD with no panning commands renders identically
to the pre-r75 build.
last_mixed_sample: f32Last post-volume sample emitted by this channel’s mixer. Used
as the starting point for the crossfade ramp on the next
re-trigger (see ramp_prev_sample). Updated by mix_one
every output frame.
ramp_prev_sample: f32Volume that the previous note was being mixed at the moment
this channel was re-triggered. Captured by enter_row on
every fresh note-on (and on retrigger paths like E9x and the
EDx delayed trigger) so the mixer can crossfade from this old
value to the new note’s first sample over a short ramp,
instead of stepping discontinuously and producing the audible
pop that a hard re-trigger leaves behind.
Without the ramp, every note-on creates a discontinuity in the
mixed bus equal to (last sample of the old note) → (first
sample of the new note), which is typically a step of a few
hundred i16 LSBs and accumulates into the per-trigger HF drift
observed on real-world MODs (halluc.mod, rhmst.mod).
ramp_remaining_frames: u32Number of output frames remaining in the per-trigger
crossfade ramp. The ramp linearly interpolates from
ramp_prev_sample to the new note’s mixed sample. Set to
PlayerState::RAMP_FRAMES on every fresh trigger; counted
down by mix_one once per output frame.