melody-bay 0.1.0

Kira-compatible generated synthesis sound sources.
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct SequencerParamTarget<'a> {
    label: Option<&'a str>,
    param: ParamKind,
    index: Option<usize>,
}

impl SequencerParamTarget<'_> {
    fn matches_label(&self, label: Option<&str>) -> bool {
        self.label
            .is_none_or(|target_label| label == Some(target_label))
    }
}

fn sequencer_param_target(target: &str) -> Option<SequencerParamTarget<'_>> {
    let (target, index) = match target.rsplit_once('#') {
        Some((name, index)) if !name.is_empty() => (name, Some(index.parse::<usize>().ok()?)),
        Some(_) => return None,
        None => (target, None),
    };
    let (label, name) = match target.split_once('.') {
        Some((label, name)) if !label.is_empty() && !name.is_empty() => (Some(label), name),
        Some(_) => return None,
        None => (None, target),
    };
    named_sequencer_param(name).map(|param| SequencerParamTarget {
        label,
        param,
        index,
    })
}

fn validate_automation_shape(
    track_id: &TrackId,
    automation: &TimedAutomationEvent,
) -> Result<(), SequencerValidationError> {
    match &automation.shape {
        AutomationShape::SetValue { value } | AutomationShape::LinearRamp { value } => {
            validate_automation_value(*value).map_err(|_| {
                SequencerValidationError::InvalidAutomationValue {
                    track_id: track_id.clone(),
                    target: automation.target.clone(),
                    value: *value,
                }
            })
        }
        AutomationShape::ValueCurve {
            values,
            duration_seconds,
        } => {
            if !duration_seconds.is_finite() || *duration_seconds <= 0.0 {
                return Err(SequencerValidationError::InvalidAutomationDuration {
                    track_id: track_id.clone(),
                    target: automation.target.clone(),
                    duration_seconds: *duration_seconds,
                });
            }
            for value in values {
                validate_automation_value(*value).map_err(|_| {
                    SequencerValidationError::InvalidAutomationValue {
                        track_id: track_id.clone(),
                        target: automation.target.clone(),
                        value: *value,
                    }
                })?;
            }
            Ok(())
        }
    }
}

fn named_sequencer_param(target: &str) -> Option<ParamKind> {
    match target {
        "gain" => Some(ParamKind::Gain),
        "frequency" => Some(ParamKind::Frequency),
        "detune" => Some(ParamKind::Detune),
        "offset" => Some(ParamKind::Offset),
        "playbackRate" | "playback_rate" => Some(ParamKind::PlaybackRate),
        "pan" => Some(ParamKind::Pan),
        "delayTime" | "delay_time" => Some(ParamKind::DelayTime),
        "q" | "Q" => Some(ParamKind::Q),
        "filterGain" | "filter_gain" => Some(ParamKind::FilterGain),
        "threshold" => Some(ParamKind::Threshold),
        "knee" => Some(ParamKind::Knee),
        "ratio" => Some(ParamKind::Ratio),
        "attack" => Some(ParamKind::Attack),
        "release" => Some(ParamKind::Release),
        _ => None,
    }
}