Skip to main content

melody_bay/import/
api.rs

1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum ImportedFormat {
3    Midi,
4    Mod,
5    Xm,
6}
7
8#[derive(Debug, Clone)]
9pub struct ImportedSequence {
10    pub sequence: IndexedSequence,
11    pub source_format: ImportedFormat,
12    pub metadata: SequenceMetadata,
13    pub warnings: Vec<ImportWarning>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[derive(Default)]
18pub struct ImportWarning {
19    pub message: String,
20    pub kind: ImportWarningKind,
21}
22
23impl ImportWarning {
24    fn new(message: impl Into<String>) -> Self {
25        Self {
26            message: message.into(),
27            kind: ImportWarningKind::InvalidMetadata,
28        }
29    }
30
31    #[allow(dead_code)]
32    fn unsupported_event(format: ImportedFormat, event: impl Into<String>) -> Self {
33        let event = event.into();
34        Self {
35            message: format!("unsupported {format:?} event {event}"),
36            kind: ImportWarningKind::UnsupportedEvent { format, event },
37        }
38    }
39
40    fn unsupported_effect(format: ImportedFormat, effect: impl Into<String>) -> Self {
41        let effect = effect.into();
42        Self {
43            message: format!("unsupported {format:?} effect {effect}"),
44            kind: ImportWarningKind::UnsupportedEffect { format, effect },
45        }
46    }
47
48    fn approximated_timing(format: ImportedFormat, detail: impl Into<String>) -> Self {
49        let detail = detail.into();
50        Self {
51            message: detail.clone(),
52            kind: ImportWarningKind::ApproximatedTiming { format, detail },
53        }
54    }
55
56    fn dropped_controller(format: ImportedFormat, controller: impl Into<String>) -> Self {
57        let controller = controller.into();
58        Self {
59            message: format!("dropped {format:?} controller or automation {controller}"),
60            kind: ImportWarningKind::DroppedControllerOrAutomation { format, controller },
61        }
62    }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66#[derive(Default)]
67pub enum ImportWarningKind {
68    UnsupportedEvent {
69        format: ImportedFormat,
70        event: String,
71    },
72    UnsupportedEffect {
73        format: ImportedFormat,
74        effect: String,
75    },
76    ApproximatedTiming {
77        format: ImportedFormat,
78        detail: String,
79    },
80    UnsupportedSampleEncoding {
81        format: ImportedFormat,
82        encoding: String,
83    },
84    #[default]
85    InvalidMetadata,
86    DroppedControllerOrAutomation {
87        format: ImportedFormat,
88        controller: String,
89    },
90}
91
92
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub enum ImportError {
96    InvalidFormat(&'static str),
97    UnsupportedFormatFeature(&'static str),
98    MalformedTiming(&'static str),
99    MalformedSampleData(&'static str),
100    ParserFailure(String),
101}
102
103impl fmt::Display for ImportError {
104    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Self::InvalidFormat(message) => write!(formatter, "invalid format: {message}"),
107            Self::UnsupportedFormatFeature(message) => {
108                write!(formatter, "unsupported format feature: {message}")
109            }
110            Self::MalformedTiming(message) => write!(formatter, "malformed timing: {message}"),
111            Self::MalformedSampleData(message) => {
112                write!(formatter, "malformed sample data: {message}")
113            }
114            Self::ParserFailure(message) => write!(formatter, "parser failure: {message}"),
115        }
116    }
117}
118
119impl std::error::Error for ImportError {}
120
121pub struct MidiImport;
122pub struct ModImport;
123pub struct XmImport;
124
125impl MidiImport {
126    pub fn from_bytes(bytes: &[u8]) -> Result<ImportedSequence, ImportError> {
127        importers::midi::import(bytes)
128    }
129}
130
131impl ModImport {
132    pub fn from_bytes(bytes: &[u8]) -> Result<ImportedSequence, ImportError> {
133        importers::mod_file::import(bytes)
134    }
135}
136
137impl XmImport {
138    pub fn from_bytes(bytes: &[u8]) -> Result<ImportedSequence, ImportError> {
139        importers::xm::import(bytes)
140    }
141}
142
143pub fn import_midi(bytes: &[u8]) -> Result<ImportedSequence, ImportError> {
144    MidiImport::from_bytes(bytes)
145}
146
147pub fn import_mod(bytes: &[u8]) -> Result<ImportedSequence, ImportError> {
148    ModImport::from_bytes(bytes)
149}
150
151pub fn import_xm(bytes: &[u8]) -> Result<ImportedSequence, ImportError> {
152    XmImport::from_bytes(bytes)
153}
154
155#[must_use]
156pub fn gm_instrument(program: u8) -> Instrument {
157    importers::builtins::gm_instrument(program)
158}
159
160#[must_use]
161pub fn gm_drum(note: u8) -> Instrument {
162    importers::builtins::gm_drum(note)
163}
164
165#[must_use]
166fn gm_instrument_impl(program: u8) -> Instrument {
167    if matches!(program, 0..=15) {
168        return gm_piano_graph_instrument(program);
169    }
170    let mut graph = AudioContext::new();
171    let osc = graph.create_oscillator();
172    osc.set_type(match program {
173        16..=23 => Waveform::Square,
174        24..=39 => Waveform::Sawtooth,
175        40..=55 => Waveform::Triangle,
176        80..=103 => Waveform::Square,
177        _ => Waveform::Sine,
178    });
179    let _ = osc.frequency().set_value(440.0);
180    let gain = graph.create_gain();
181    let _ = graph.label_node(&gain, "output");
182    let _ = gain.gain().set_value_at_time(0.0, 0.0);
183    match program {
184        16..=23 => {
185            let _ = gain.gain().linear_ramp_to_value_at_time(0.18, 0.006);
186            let _ = gain.gain().linear_ramp_to_value_at_time(0.06, 0.20);
187            let _ = gain.gain().linear_ramp_to_value_at_time(0.0, 0.90);
188        }
189        40..=55 => {
190            let _ = gain.gain().linear_ramp_to_value_at_time(0.12, 0.08);
191            let _ = gain.gain().linear_ramp_to_value_at_time(0.10, 0.35);
192        }
193        56..=63 => {
194            let _ = gain.gain().linear_ramp_to_value_at_time(0.16, 0.018);
195            let _ = gain.gain().linear_ramp_to_value_at_time(0.10, 0.25);
196        }
197        _ => {
198            let _ = gain.gain().linear_ramp_to_value_at_time(0.14, 0.02);
199            let _ = gain.gain().linear_ramp_to_value_at_time(0.08, 0.25);
200        }
201    }
202    let pan = graph.create_stereo_panner();
203    let filter = graph.create_biquad_filter();
204    let _ = filter.frequency().set_value(match program {
205        32..=39 => 900.0,
206        40..=55 => 2_400.0,
207        56..=63 => 3_200.0,
208        _ => 1_800.0,
209    });
210    let _ = graph.connect(osc, &filter);
211    let _ = graph.connect(&filter, &gain);
212    let _ = graph.connect(&gain, &pan);
213    let _ = graph.connect(&pan, graph.destination());
214    Instrument::graph(graph).base_note(Note::from_midi(69))
215}
216
217#[must_use]
218fn gm_piano_graph_instrument(program: u8) -> Instrument {
219    let peak = match program {
220        8..=15 => 0.10,
221        _ => 0.13,
222    };
223    let mut graph = AudioContext::new();
224    let osc = graph.create_oscillator();
225    osc.set_type(match program {
226        8..=15 => Waveform::Triangle,
227        _ => Waveform::Sine,
228    });
229    let _ = osc.frequency().set_value(440.0);
230    let gain = graph.create_gain();
231    let _ = graph.label_node(&gain, "output");
232    let _ = gain.gain().set_value_at_time(0.0, 0.0);
233    let _ = gain.gain().linear_ramp_to_value_at_time(peak, 0.006);
234    let _ = gain.gain().linear_ramp_to_value_at_time(peak * 0.18, 0.04);
235    let _ = gain
236        .gain()
237        .linear_ramp_to_value_at_time(peak * 0.025, 0.075);
238    let _ = gain.gain().linear_ramp_to_value_at_time(0.0, 0.12);
239    let _ = graph.connect(osc, &gain);
240    let _ = graph.connect(&gain, graph.destination());
241    Instrument::graph(graph).base_note(Note::from_midi(69))
242}
243
244#[must_use]
245fn gm_drum_impl(note: u8) -> Instrument {
246    let mut graph = AudioContext::new();
247    let osc = graph.create_oscillator();
248    osc.set_type(match note {
249        35 | 36 | 41 | 43 | 45 => Waveform::Sine,
250        38 | 40 => Waveform::Triangle,
251        42 | 44 | 46 | 49 | 51 | 57 => Waveform::Square,
252        _ => Waveform::Sawtooth,
253    });
254    let _ = osc.frequency().set_value(match note {
255        35 | 36 => 80.0,
256        38 | 40 => 180.0,
257        42 | 44 | 46 => 7_000.0,
258        _ => 440.0,
259    });
260    let gain = graph.create_gain();
261    let _ = graph.label_node(&gain, "output");
262    let _ = gain.gain().set_value_at_time(0.0, 0.0);
263    let _ = gain.gain().linear_ramp_to_value_at_time(0.45, 0.003);
264    let _ = gain.gain().linear_ramp_to_value_at_time(0.0, 0.16);
265    let _ = graph.connect(osc, &gain);
266    let _ = graph.connect(&gain, graph.destination());
267    Instrument::graph(graph).base_note(Note::from_midi(note))
268}
269