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