melody-bay 0.1.0

Kira-compatible generated synthesis sound sources.
Documentation
fn import_xm_impl(bytes: &[u8]) -> Result<ImportedSequence, ImportError> {
    if bytes.len() < 80 || &bytes[..17] != b"Extended Module: " || bytes[37] != 0x1a {
        return Err(ImportError::InvalidFormat("missing XM header"));
    }
    let title = trim_c_string(&bytes[17..37]);
    let header_size = le_u32(bytes, 60)? as usize;
    if header_size < 20 || 60 + header_size > bytes.len() {
        return Err(ImportError::InvalidFormat("truncated XM song header"));
    }
    let song_len = usize::from(le_u16(bytes, 64)?).max(1);
    let channels = usize::from(le_u16(bytes, 68)?).max(1);
    let patterns = usize::from(le_u16(bytes, 70)?);
    let instruments = usize::from(le_u16(bytes, 72)?);
    let speed = u32::from(le_u16(bytes, 76)?).max(1);
    let bpm = u32::from(le_u16(bytes, 78)?).max(1);
    let envelope_tick_seconds = 2.5 / f64::from(bpm);
    let orders = &bytes[80..80 + 256];
    let mut offset = 60 + header_size;
    let mut pattern_rows = Vec::<Vec<Vec<TrackerCell>>>::new();
    for _ in 0..patterns {
        let header_len = le_u32(bytes, offset)? as usize;
        if header_len < 9 || offset + header_len > bytes.len() {
            return Err(ImportError::InvalidFormat("truncated XM pattern header"));
        }
        let rows = usize::from(le_u16(bytes, offset + 5)?).max(1);
        let packed_len = usize::from(le_u16(bytes, offset + 7)?);
        offset += header_len;
        let end = offset
            .checked_add(packed_len)
            .filter(|end| *end <= bytes.len())
            .ok_or(ImportError::InvalidFormat("truncated XM pattern data"))?;
        pattern_rows.push(parse_xm_pattern(&bytes[offset..end], rows, channels)?);
        offset = end;
    }
    let mut samples = Vec::with_capacity(instruments);
    for _instrument_index in 0..instruments {
        let header_len = le_u32(bytes, offset)? as usize;
        if header_len < 29 || offset + header_len > bytes.len() {
            return Err(ImportError::InvalidFormat("truncated XM instrument header"));
        }
        let instrument_header_end = offset + header_len;
        let sample_count = usize::from(le_u16(bytes, offset + 27)?);
        let sample_header_size = if sample_count > 0 {
            le_u32(bytes, offset + 29)? as usize
        } else {
            0
        };
        let sample_map_start = offset + 33;
        let volume_env_start = sample_map_start + 96;
        let envelope_counts_start = volume_env_start + 96;
        let volume_type_offset = envelope_counts_start + 8;
        let volume_points = if sample_count > 0
            && volume_env_start + 48 <= offset + header_len
            && envelope_counts_start < offset + header_len
            && bytes.get(volume_type_offset).copied().unwrap_or(0) & 0x01 != 0
        {
            parse_xm_envelope(
                &bytes[volume_env_start..volume_env_start + 48],
                bytes.get(envelope_counts_start).copied().unwrap_or(0),
                envelope_tick_seconds,
            )
        } else {
            None
        };
        offset = instrument_header_end;
        let mut keymap = [0usize; 96];
        if sample_count > 0 && sample_map_start + 96 <= instrument_header_end {
            for (slot, value) in bytes[sample_map_start..sample_map_start + 96]
                .iter()
                .enumerate()
            {
                keymap[slot] = usize::from(*value);
            }
        }
        let mut instrument_samples = Vec::new();
        for _ in 0..sample_count {
            if sample_header_size < 40 || offset + sample_header_size > bytes.len() {
                return Err(ImportError::InvalidFormat("truncated XM sample header"));
            }
            let length = le_u32(bytes, offset)? as usize;
            let loop_start = le_u32(bytes, offset + 4)? as usize;
            let loop_len = le_u32(bytes, offset + 8)? as usize;
            let volume = f32::from(bytes[offset + 12].min(64)) / 64.0;
            let finetune = i8::from_ne_bytes([bytes[offset + 13]]);
            let sample_type = bytes[offset + 14];
            let is_16_bit = sample_type & 0x10 != 0;
            let loop_type = sample_type & 0x03;
            let pan = f32::from(bytes[offset + 15]) / 127.5 - 1.0;
            let relative_note = i8::from_ne_bytes([bytes[offset + 16]]);
            let name_start = offset + 18;
            let name_end = (name_start + 22).min(offset + sample_header_size);
            let name = trim_c_string(&bytes[name_start..name_end]);
            instrument_samples.push(TrackerSample {
                _name: name,
                length,
                is_16_bit,
                volume,
                finetune_cents: f32::from(finetune) * 100.0 / 128.0,
                loop_start,
                loop_len: if loop_type == 0 { 0 } else { loop_len },
                ping_pong_loop: loop_type == 0x02,
                data: Vec::new(),
                relative_note,
                pan,
                envelope: volume_points.clone(),
            });
            offset += sample_header_size;
        }
        for sample in &mut instrument_samples {
            let end = offset
                .checked_add(sample.length)
                .filter(|end| *end <= bytes.len())
                .ok_or(ImportError::MalformedSampleData("truncated XM sample data"))?;
            if sample.is_16_bit {
                if sample.length % 2 != 0 {
                    return Err(ImportError::MalformedSampleData(
                        "odd XM 16-bit sample byte length",
                    ));
                }
                let mut acc = 0i16;
                sample.data = bytes[offset..end]
                    .chunks_exact(2)
                    .map(|delta| {
                        let delta = i16::from_le_bytes([delta[0], delta[1]]);
                        acc = acc.wrapping_add(delta);
                        f32::from(acc) / 32768.0
                    })
                    .collect();
                sample.loop_start /= 2;
                sample.loop_len /= 2;
            } else {
                let mut acc = 0i8;
                sample.data = bytes[offset..end]
                    .iter()
                    .map(|delta| {
                        acc = acc.wrapping_add(i8::from_ne_bytes([*delta]));
                        f32::from(acc) / 128.0
                    })
                    .collect();
            }
            expand_ping_pong_loop(sample);
            offset = end;
        }
        samples.push(XmInstrument {
            keymap,
            samples: instrument_samples,
        });
    }

    let mut timing = TrackerTiming { speed, bpm };
    let mut sequence = IndexedSequence::new(24).title(title);
    sequence.tempo_at(0, f64::from(timing.bpm));
    let mut track_map = BTreeMap::<(usize, usize, usize, usize), IndexedTrack>::new();
    let mut active_notes = BTreeMap::<usize, ActiveTrackerNote>::new();
    let mut channel_instruments = vec![0usize; channels];
    let mut channel_volumes = vec![1.0f32; channels];
    let mut warnings = Vec::new();
    let mut output_tick = 0u64;
    let mut order_index = 0usize;
    let mut row_start = 0usize;
    let mut flow_guard = 0usize;
    let mix_gain = tracker_mix_gain(channels);
    while order_index < song_len && flow_guard < song_len.saturating_mul(256).max(1) {
        flow_guard += 1;
        let pattern_order = orders[order_index];
        let Some(rows) = pattern_rows.get(usize::from(pattern_order)) else {
            warnings.push(ImportWarning::new("XM order references missing pattern"));
            order_index += 1;
            continue;
        };
        let mut next_order = order_index + 1;
        let mut next_row_start = 0usize;
        let mut flow_changed = false;
        for row in rows.iter().skip(row_start) {
            let global_tick = output_tick;
            for (channel, cell) in row.iter().copied().enumerate() {
                if cell.effect == 0x0b {
                    next_order = usize::from(cell.effect_param);
                    next_row_start = 0;
                    flow_changed = true;
                } else if cell.effect == 0x0d {
                    next_order = order_index + 1;
                    next_row_start = tracker_pattern_break_row(cell.effect_param);
                    flow_changed = true;
                }
                handle_tracker_effects(
                    ImportedFormat::Xm,
                    &mut sequence,
                    &mut warnings,
                    &mut timing,
                    global_tick,
                    cell,
                );
                if cell.key_off {
                    close_tracker_channel_note(
                        &mut track_map,
                        &mut active_notes,
                        channel,
                        global_tick,
                    );
                }
                if cell.instrument > 0 {
                    channel_instruments[channel] = cell.instrument;
                }
                let Some(note) = cell.note else {
                    apply_tracker_channel_volume_effects(
                        &mut track_map,
                        &active_notes,
                        &mut channel_volumes,
                        TrackerVolumeContext {
                            channel,
                            row: global_tick,
                            cell,
                            timing,
                            mix_gain: tracker_mix_gain(channels),
                        },
                    );
                    continue;
                };
                close_tracker_channel_note(&mut track_map, &mut active_notes, channel, global_tick);
                let instrument_number = if cell.instrument > 0 {
                    cell.instrument
                } else {
                    channel_instruments[channel]
                };
                if instrument_number == 0 || instrument_number > samples.len() {
                    warnings.push(ImportWarning::new("XM note without valid instrument"));
                    continue;
                }
                let instrument = &samples[instrument_number - 1];
                let (sample_slot, sample) = xm_instrument_sample(instrument, note).ok_or(
                    ImportError::MalformedSampleData("XM instrument has no sample data"),
                )?;
                let sample_offset = tracker_sample_offset_frames(cell, sample);
                let track = track_map
                    .entry((channel, instrument_number, sample_slot, sample_offset))
                    .or_insert_with(|| {
                        IndexedTrack::new(tracker_instrument(
                            sample,
                            xm_channel_pan(channel, sample),
                            mix_gain,
                            sample_offset,
                        ))
                    });
                apply_tracker_cell_automation(
                    track,
                    global_tick,
                    cell,
                    sample.volume,
                    mix_gain,
                    timing,
                    1.0,
                );
                active_notes.insert(
                    channel,
                    ActiveTrackerNote {
                        start_index: global_tick,
                        instrument: instrument_number,
                        sample_slot,
                        sample_offset,
                        period: None,
                        playback_rate_correction: 1.0,
                        note,
                        velocity: cell.volume.unwrap_or(sample.volume),
                    },
                );
                channel_volumes[channel] = cell.volume.unwrap_or(sample.volume);
                apply_tracker_channel_volume_effects(
                    &mut track_map,
                    &active_notes,
                    &mut channel_volumes,
                    TrackerVolumeContext {
                        channel,
                        row: global_tick,
                        cell,
                        timing,
                        mix_gain,
                    },
                );
            }
            output_tick = output_tick.saturating_add(u64::from(timing.speed.max(1)));
            if flow_changed {
                break;
            }
        }
        order_index = next_order;
        row_start = next_row_start.min(255);
    }
    close_all_tracker_notes(&mut track_map, &mut active_notes, output_tick);
    for ((channel, instrument, sample_slot, sample_offset), track) in track_map {
        let mut track_id = if sample_slot == 0 {
            format!("xm-ch{:02}-inst{:02}", channel + 1, instrument)
        } else {
            format!(
                "xm-ch{:02}-inst{:02}-samp{:02}",
                channel + 1,
                instrument,
                sample_slot + 1
            )
        };
        if sample_offset > 0 {
            track_id.push_str(&format!("-off{sample_offset:06}"));
        }
        sequence.add_track(TrackId::named(track_id), track);
    }
    let metadata = sequence.metadata.clone();
    Ok(ImportedSequence {
        sequence,
        source_format: ImportedFormat::Xm,
        metadata,
        warnings,
    })
}

fn parse_xm_pattern(
    bytes: &[u8],
    rows: usize,
    channels: usize,
) -> Result<Vec<Vec<TrackerCell>>, ImportError> {
    let mut offset = 0usize;
    let mut parsed = Vec::with_capacity(rows);
    for _ in 0..rows {
        let mut row = Vec::with_capacity(channels);
        for _ in 0..channels {
            let first = read_u8(bytes, &mut offset)?;
            let (note, instrument, volume, effect, effect_param) = if first & 0x80 != 0 {
                let note = if first & 0x01 != 0 {
                    read_u8(bytes, &mut offset)?
                } else {
                    0
                };
                let instrument = if first & 0x02 != 0 {
                    read_u8(bytes, &mut offset)?
                } else {
                    0
                };
                let volume = if first & 0x04 != 0 {
                    read_u8(bytes, &mut offset)?
                } else {
                    0
                };
                let effect = if first & 0x08 != 0 {
                    read_u8(bytes, &mut offset)?
                } else {
                    0
                };
                let param = if first & 0x10 != 0 {
                    read_u8(bytes, &mut offset)?
                } else {
                    0
                };
                (note, instrument, volume, effect, param)
            } else {
                (
                    first,
                    read_u8(bytes, &mut offset)?,
                    read_u8(bytes, &mut offset)?,
                    read_u8(bytes, &mut offset)?,
                    read_u8(bytes, &mut offset)?,
                )
            };
            row.push(TrackerCell {
                note: xm_note_to_midi(note),
                period: None,
                instrument: usize::from(instrument),
                volume: xm_volume_to_velocity(volume),
                pan: xm_volume_to_pan(volume),
                effect,
                effect_param,
                key_off: note == 97 || effect == 0x14,
            });
        }
        parsed.push(row);
    }
    Ok(parsed)
}

fn parse_xm_envelope(bytes: &[u8], count: u8, tick_seconds: f64) -> Option<SampleEnvelope> {
    let count = usize::from(count).min(12);
    if count == 0 {
        return None;
    }
    let tick_seconds = if tick_seconds.is_finite() && tick_seconds > 0.0 {
        tick_seconds
    } else {
        0.02
    };
    let mut points = Vec::with_capacity(count);
    for index in 0..count {
        let offset = index * 4;
        let x = u16::from_le_bytes([bytes[offset], bytes[offset + 1]]) as f64 * tick_seconds;
        let y = u16::from_le_bytes([bytes[offset + 2], bytes[offset + 3]]) as f32 / 64.0;
        points.push((x, y));
    }
    Some(SampleEnvelope { points })
}