1use std::collections::HashMap;
2
3use agb_fixnum::Num;
4use agb_tracker_interop::{Jump, PatternEffect, RetriggerVolumeChange, Waveform};
5
6use xmrs::prelude::*;
7
8pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
9 let instruments = &module.instrument;
10 let mut instruments_map = HashMap::new();
11
12 struct SampleData {
13 data: Vec<u8>,
14 should_loop: bool,
15 fine_tune: f64,
16 relative_note: i8,
17 restart_point: u32,
18 volume: Num<i16, 8>,
19 envelope_id: Option<usize>,
20 fadeout: Num<i32, 8>,
21 }
22
23 let mut samples = vec![];
24 let mut envelopes: Vec<EnvelopeData> = vec![];
25 let mut existing_envelopes: HashMap<EnvelopeData, usize> = Default::default();
26
27 for (instrument_index, instrument) in instruments.iter().enumerate() {
28 let InstrumentType::Default(ref instrument) = instrument.instr_type else {
29 continue;
30 };
31
32 let envelope = &instrument.volume_envelope;
33 let envelope_id = if envelope.enabled {
34 let envelope = EnvelopeData::new(
35 envelope,
36 instrument,
37 module.frequency_type,
38 module.default_bpm as u32,
39 );
40 let id = existing_envelopes
41 .entry(envelope)
42 .or_insert_with_key(|envelope| {
43 envelopes.push(envelope.clone());
44 envelopes.len() - 1
45 });
46
47 Some(*id)
48 } else {
49 None
50 };
51
52 for (sample_index, sample) in instrument.sample.iter().enumerate() {
53 let should_loop = !matches!(sample.flags, LoopType::No);
54 let fine_tune = sample.finetune as f64 * 128.0;
55 let relative_note = sample.relative_note;
56 let restart_point = sample.loop_start;
57 let sample_len = if sample.loop_length > 0 {
58 (sample.loop_length + sample.loop_start) as usize
59 } else {
60 usize::MAX
61 };
62
63 let volume = Num::from_f32(sample.volume);
64
65 let sample = match &sample.data {
66 SampleDataType::Mono8(depth8) => depth8
67 .iter()
68 .map(|value| *value as u8)
69 .take(sample_len)
70 .collect::<Vec<_>>(),
71 SampleDataType::Mono16(depth16) => depth16
72 .iter()
73 .map(|sample| (sample >> 8) as i8 as u8)
74 .take(sample_len)
75 .collect::<Vec<_>>(),
76 _ => panic!("Stereo samples not supported"),
77 };
78
79 let fadeout = Num::from_f32(instrument.volume_fadeout);
80
81 instruments_map.insert((instrument_index, sample_index), samples.len());
82 samples.push(SampleData {
83 data: sample,
84 should_loop,
85 fine_tune,
86 relative_note,
87 restart_point,
88 volume,
89 envelope_id,
90 fadeout,
91 });
92 }
93 }
94
95 let mut patterns = vec![];
96 let mut pattern_data = vec![];
97
98 for pattern in &module.pattern {
99 let start_pos = pattern_data.len();
100 let mut effect_parameters: [u8; 255] = [0; u8::MAX as usize];
101 let mut tone_portamento_directions = vec![0; module.get_num_channels()];
102 let mut note_and_sample = vec![None; module.get_num_channels()];
103 let mut previous_retriggers: Vec<Option<(RetriggerVolumeChange, u8)>> =
104 vec![None; module.get_num_channels()];
105
106 for row in pattern.iter() {
107 let mut jump = None;
109
110 for (i, slot) in row.iter().enumerate() {
111 let channel_number = i % module.get_num_channels();
112
113 let sample = if slot.instrument == 0 {
114 0
115 } else {
116 let instrument_index = (slot.instrument - 1) as usize;
117
118 if let Some(InstrumentType::Default(instrument)) = module
119 .instrument
120 .get(instrument_index)
121 .map(|instrument| &instrument.instr_type)
122 {
123 let sample_slot = *instrument
124 .sample_for_note
125 .get(slot.note as usize)
126 .unwrap_or(&0) as usize;
127 instruments_map
128 .get(&(instrument_index, sample_slot))
129 .map(|sample_idx| sample_idx + 1)
130 .unwrap_or(0)
131 } else {
132 0
133 }
134 };
135
136 let mut effect1 = PatternEffect::None;
137
138 let previous_note_and_sample = note_and_sample[channel_number];
139 let maybe_note_and_sample = if matches!(slot.note, Note::KeyOff) {
140 effect1 = PatternEffect::Stop;
141
142 ¬e_and_sample[channel_number]
143 } else if !matches!(slot.note, Note::None) {
144 if sample != 0 {
145 note_and_sample[channel_number] = Some((slot.note, &samples[sample - 1]));
146 } else if let Some((note, _)) = &mut note_and_sample[channel_number] {
147 *note = slot.note;
148 }
149
150 ¬e_and_sample[channel_number]
151 } else {
152 ¬e_and_sample[channel_number]
153 };
154
155 if matches!(effect1, PatternEffect::None) {
156 effect1 = match slot.volume {
157 0x10..=0x50 => PatternEffect::Volume(
158 (Num::new((slot.volume - 0x10) as i16) / 64)
159 * maybe_note_and_sample
160 .map(|note_and_sample| note_and_sample.1.volume)
161 .unwrap_or(1.into()),
162 ),
163 0x60..=0x6F => PatternEffect::VolumeSlide(
164 -Num::new((slot.volume - 0x60) as i16) / 64,
165 false,
166 ),
167 0x70..=0x7F => PatternEffect::VolumeSlide(
168 Num::new((slot.volume - 0x70) as i16) / 64,
169 false,
170 ),
171 0x80..=0x8F => PatternEffect::FineVolumeSlide(
172 -Num::new((slot.volume - 0x80) as i16) / 128,
173 ),
174 0x90..=0x9F => PatternEffect::FineVolumeSlide(
175 Num::new((slot.volume - 0x90) as i16) / 128,
176 ),
177 0xC0..=0xCF => PatternEffect::Panning(
178 Num::new(slot.volume as i16 - (0xC0 + (0xCF - 0xC0) / 2)) / 8,
179 ),
180 _ => PatternEffect::None,
181 };
182 }
183
184 let effect_parameter = if slot.effect_parameter != 0 {
185 effect_parameters[slot.effect_type as usize] = slot.effect_parameter;
186 slot.effect_parameter
187 } else {
188 effect_parameters[slot.effect_type as usize]
189 };
190
191 let effect2 = match slot.effect_type {
192 0x0 => {
193 if slot.effect_parameter == 0 {
194 PatternEffect::None
195 } else if let Some((note, sample)) = maybe_note_and_sample {
196 let first_arpeggio = slot.effect_parameter >> 4;
197 let second_arpeggio = slot.effect_parameter & 0xF;
198
199 let first_arpeggio_speed = note_to_speed(
200 *note,
201 sample.fine_tune,
202 sample.relative_note + first_arpeggio as i8,
203 module.frequency_type,
204 );
205 let second_arpeggio_speed = note_to_speed(
206 *note,
207 sample.fine_tune,
208 sample.relative_note + second_arpeggio as i8,
209 module.frequency_type,
210 );
211
212 PatternEffect::Arpeggio(
213 first_arpeggio_speed
214 .try_change_base()
215 .expect("Arpeggio size too large"),
216 second_arpeggio_speed
217 .try_change_base()
218 .expect("Arpeggio size too large"),
219 )
220 } else {
221 PatternEffect::None
222 }
223 }
224 0x1 => {
225 let c4_speed: Num<u32, 12> =
226 note_to_speed(Note::C4, 0.0, 0, module.frequency_type).change_base();
227 let speed: Num<u32, 12> = note_to_speed(
228 Note::C4,
229 effect_parameter as f64 * 8.0,
230 0,
231 module.frequency_type,
232 )
233 .change_base();
234
235 let portamento_amount = speed / c4_speed;
236
237 PatternEffect::Portamento(portamento_amount.try_change_base().unwrap())
238 }
239 0x2 => {
240 let c4_speed = note_to_speed(Note::C4, 0.0, 0, module.frequency_type);
241 let speed = note_to_speed(
242 Note::C4,
243 effect_parameter as f64 * 8.0,
244 0,
245 module.frequency_type,
246 );
247
248 let portamento_amount = c4_speed / speed;
249
250 PatternEffect::Portamento(portamento_amount.try_change_base().unwrap())
251 }
252 0x3 => {
253 if let (Some((note, sample)), Some((prev_note, _))) =
254 (maybe_note_and_sample, previous_note_and_sample)
255 {
256 let target_speed = note_to_speed(
257 *note,
258 sample.fine_tune,
259 sample.relative_note,
260 module.frequency_type,
261 );
262
263 let direction = match (prev_note as usize).cmp(&(*note as usize)) {
264 std::cmp::Ordering::Less => 1,
265 std::cmp::Ordering::Equal => {
266 tone_portamento_directions[channel_number]
267 }
268 std::cmp::Ordering::Greater => -1,
269 };
270
271 tone_portamento_directions[channel_number] = direction;
272
273 let c4_speed = note_to_speed(Note::C4, 0.0, 0, module.frequency_type);
274 let speed = note_to_speed(
275 Note::C4,
276 effect_parameter as f64 * 8.0,
277 0,
278 module.frequency_type,
279 );
280
281 let portamento_amount = if direction > 0 {
282 speed / c4_speed
283 } else {
284 c4_speed / speed
285 };
286
287 PatternEffect::TonePortamento(
288 portamento_amount.try_change_base().unwrap(),
289 target_speed.try_change_base().unwrap(),
290 )
291 } else {
292 PatternEffect::None
293 }
294 }
295 0x4 => {
296 let vibrato_speed = effect_parameter >> 4;
297 let depth = effect_parameter & 0xF;
298
299 let c4_speed = note_to_speed(Note::C4, 0.0, 0, module.frequency_type);
300 let speed =
301 note_to_speed(Note::C4, depth as f64 * 16.0, 0, module.frequency_type);
302
303 let amount = speed / c4_speed - 1;
304
305 PatternEffect::Vibrato(
306 Waveform::Sine,
307 amount.try_change_base().unwrap(),
308 vibrato_speed,
309 )
310 }
311 0x8 => {
312 PatternEffect::Panning(Num::new(slot.effect_parameter as i16 - 128) / 128)
313 }
314 0x5 | 0x6 | 0xA => {
315 let first = effect_parameter >> 4;
316 let second = effect_parameter & 0xF;
317
318 if first == 0 {
319 PatternEffect::VolumeSlide(
320 -Num::new(second as i16) / 64,
321 slot.effect_type == 0x6,
322 )
323 } else {
324 PatternEffect::VolumeSlide(
325 Num::new(first as i16) / 64,
326 slot.effect_type == 0x6,
327 )
328 }
329 }
330 0x9 => PatternEffect::SampleOffset(effect_parameter as u16 * 256),
331 0xB => {
332 let pattern_idx = slot.effect_parameter;
333
334 jump = Some((
335 channel_number,
336 Jump::Position {
337 pattern: pattern_idx,
338 },
339 ));
340
341 PatternEffect::None
342 }
343 0xC => {
344 if let Some((_, sample)) = maybe_note_and_sample {
345 PatternEffect::Volume(
346 (Num::new(slot.effect_parameter as i16) / 64) * sample.volume,
347 )
348 } else {
349 PatternEffect::None
350 }
351 }
352 0xD => {
353 let first = slot.effect_parameter >> 4;
355 let second = slot.effect_parameter & 0xF;
356 let row_idx = first * 10 + second;
357
358 let pattern_break = Jump::PatternBreak { row: row_idx };
359
360 if let Some((idx, Jump::Position { pattern })) = jump {
362 jump = Some((
363 idx,
364 Jump::Combined {
365 pattern,
366 row: row_idx,
367 },
368 ))
369 } else {
370 jump = Some((channel_number, pattern_break));
371 }
372
373 PatternEffect::None
374 }
375 0xE => match slot.effect_parameter >> 4 {
376 0x1 => {
377 let c4_speed: Num<u32, 12> =
378 note_to_speed(Note::C4, 0.0, 0, module.frequency_type)
379 .change_base();
380 let speed: Num<u32, 12> = note_to_speed(
381 Note::C4,
382 effect_parameter as f64 * 8.0,
383 0,
384 module.frequency_type,
385 )
386 .change_base();
387
388 let portamento_amount = speed / c4_speed;
389
390 PatternEffect::FinePortamento(
391 portamento_amount.try_change_base().unwrap(),
392 )
393 }
394 0x2 => {
395 let c4_speed = note_to_speed(Note::C4, 0.0, 0, module.frequency_type);
396 let speed = note_to_speed(
397 Note::C4,
398 effect_parameter as f64 * 8.0,
399 0,
400 module.frequency_type,
401 );
402
403 let portamento_amount = c4_speed / speed;
404
405 PatternEffect::FinePortamento(
406 portamento_amount.try_change_base().unwrap(),
407 )
408 }
409
410 0x8 => PatternEffect::Panning(
411 Num::new(((slot.effect_parameter & 0xf) as i16) - 8) / 8,
412 ),
413 0x9 => {
414 let retrigger_amount = slot.effect_parameter & 0xf;
415 let modified_amount = if retrigger_amount == 0 {
416 if let Some((_, previous_retrigger)) =
417 previous_retriggers[channel_number]
418 {
419 previous_retrigger
420 } else {
421 1
422 }
423 } else {
424 previous_retriggers[channel_number] =
425 Some((RetriggerVolumeChange::NoChange, retrigger_amount));
426 retrigger_amount
427 };
428
429 PatternEffect::Retrigger(
430 RetriggerVolumeChange::NoChange,
431 modified_amount,
432 )
433 }
434 0xA => PatternEffect::FineVolumeSlide(
435 Num::new((slot.effect_parameter & 0xf) as i16) / 128,
436 ),
437 0xB => PatternEffect::FineVolumeSlide(
438 -Num::new((slot.effect_parameter & 0xf) as i16) / 128,
439 ),
440 0xC => PatternEffect::NoteCut((slot.effect_parameter & 0xf).into()),
441 0xD => PatternEffect::NoteDelay((slot.effect_parameter & 0xf).into()),
442 u => {
443 eprintln!("Unsupported extended effect E{u:X}y");
444 PatternEffect::None
445 }
446 },
447 0xF => match slot.effect_parameter {
448 0 => PatternEffect::SetTicksPerStep(u32::MAX),
449 1..=0x20 => PatternEffect::SetTicksPerStep(slot.effect_parameter as u32),
450 0x21.. => PatternEffect::SetFramesPerTick(bpm_to_frames_per_tick(
451 slot.effect_parameter as u32,
452 )),
453 },
454 0x10 => PatternEffect::SetGlobalVolume(
456 Num::new(slot.effect_parameter as i32) / 0x40,
457 ),
458 0x11 => {
460 let first = effect_parameter >> 4;
461 let second = effect_parameter & 0xF;
462
463 if first == 0 {
464 PatternEffect::GlobalVolumeSlide(-Num::new(second as i32) / 0x40)
465 } else {
466 PatternEffect::GlobalVolumeSlide(Num::new(first as i32) / 0x40)
467 }
468 }
469 0x1B => {
471 let first = effect_parameter >> 4;
472 let second = effect_parameter & 0xF;
473
474 let previous_retrigger = &mut previous_retriggers[channel_number];
475 let volume_type = match first {
476 0 => previous_retrigger
477 .map(|retrigger| retrigger.0)
478 .unwrap_or(RetriggerVolumeChange::NoChange),
479 1 => RetriggerVolumeChange::DecreaseByOne,
480 8 => RetriggerVolumeChange::NoChange,
481 _ => {
482 eprintln!("Unsupported retrigger effect volume {first}");
483 RetriggerVolumeChange::NoChange
484 }
485 };
486
487 let ticks_between_retriggers = if second == 0 {
488 if let Some((_, previous_retrigger)) = previous_retrigger {
489 *previous_retrigger
490 } else {
491 1
492 }
493 } else {
494 second
495 };
496
497 *previous_retrigger = Some((volume_type, ticks_between_retriggers));
498
499 PatternEffect::Retrigger(volume_type, ticks_between_retriggers)
500 }
501 e => {
502 let effect_char = char::from_digit(e as u32, 36)
503 .unwrap_or('?')
504 .to_ascii_uppercase();
505 eprintln!("Unsupported effect {effect_char}xy");
506
507 PatternEffect::None
508 }
509 };
510
511 if sample == 0
512 || matches!(effect2, PatternEffect::TonePortamento(_, _))
513 || matches!(effect1, PatternEffect::Stop)
514 {
515 pattern_data.push(agb_tracker_interop::PatternSlot {
516 speed: 0.into(),
517 sample: 0,
518 effect1,
519 effect2,
520 });
521 } else {
522 let sample_played = &samples[sample - 1];
523
524 let speed = note_to_speed(
525 slot.note,
526 sample_played.fine_tune,
527 sample_played.relative_note,
528 module.frequency_type,
529 );
530
531 pattern_data.push(agb_tracker_interop::PatternSlot {
532 speed: speed.try_change_base().unwrap(),
533 sample: sample as u16,
534 effect1,
535 effect2,
536 });
537 }
538 }
539 if let Some((jump_channel, jump)) = jump.take() {
542 let jump_effect = PatternEffect::Jump(jump);
543 let pattern_data_idx =
544 pattern_data.len() - module.get_num_channels() + jump_channel;
545 if let Some(data) = pattern_data.get_mut(pattern_data_idx) {
546 data.effect2 = jump_effect;
547 }
548 }
549 }
550
551 patterns.push(agb_tracker_interop::Pattern {
552 length: pattern.len(),
553 start_position: start_pos,
554 });
555 }
556
557 let samples: Vec<_> = samples
558 .iter()
559 .map(|sample| agb_tracker_interop::Sample {
560 data: sample.data.clone().into(),
561 should_loop: sample.should_loop,
562 restart_point: sample.restart_point,
563 volume: sample.volume,
564 volume_envelope: sample.envelope_id,
565 fadeout: sample.fadeout,
566 })
567 .collect();
568
569 let patterns_to_play = module.pattern_order.clone();
570
571 let envelopes = envelopes
572 .iter()
573 .map(|envelope| agb_tracker_interop::Envelope {
574 amount: envelope.amounts.clone().into(),
575 sustain: envelope.sustain,
576 loop_start: envelope.loop_start,
577 loop_end: envelope.loop_end,
578
579 vib_amount: envelope.vib_amount.try_change_base().unwrap(),
580 vib_waveform: envelope.vib_waveform,
581 vib_speed: envelope.vib_speed,
582 })
583 .collect::<Vec<_>>();
584
585 let frames_per_tick = bpm_to_frames_per_tick(module.default_bpm as u32);
586 let ticks_per_step = module.default_tempo;
587
588 agb_tracker_interop::Track {
589 samples: samples.into(),
590 pattern_data: pattern_data.into(),
591 patterns: patterns.into(),
592 num_channels: module.get_num_channels(),
593 patterns_to_play: patterns_to_play.into(),
594 envelopes: envelopes.into(),
595
596 frames_per_tick,
597 ticks_per_step: ticks_per_step.into(),
598 repeat: module.restart_position,
599 }
600}
601
602fn bpm_to_frames_per_tick(bpm: u32) -> Num<u32, 8> {
603 Num::<u32, 8>::new(150) / bpm
605}
606
607fn note_to_speed(
608 note: Note,
609 fine_tune: f64,
610 relative_note: i8,
611 frequency_type: FrequencyType,
612) -> Num<u32, 12> {
613 let frequency = match frequency_type {
614 FrequencyType::LinearFrequencies => {
615 note_to_frequency_linear(note, fine_tune, relative_note)
616 }
617 FrequencyType::AmigaFrequencies => note_to_frequency_amiga(note, fine_tune, relative_note),
618 };
619
620 let gba_audio_frequency = 32768f64;
621
622 let speed = frequency / gba_audio_frequency;
623 Num::from_f64(speed)
624}
625
626fn note_to_frequency_linear(note: Note, fine_tune: f64, relative_note: i8) -> f64 {
627 let real_note = (note as usize as f64) + (relative_note as f64) - 1.0; let period = 10.0 * 12.0 * 16.0 * 4.0 - real_note * 16.0 * 4.0 - fine_tune / 2.0;
629 8363.0 * 2.0f64.powf((6.0 * 12.0 * 16.0 * 4.0 - period) / (12.0 * 16.0 * 4.0))
630}
631
632fn note_to_frequency_amiga(note: Note, fine_tune: f64, relative_note: i8) -> f64 {
633 let note = (note as usize)
634 .checked_add_signed(relative_note as isize)
635 .expect("Note gone negative");
636 let pos = ((note % 12) * 8 + (fine_tune / 16.0) as usize).min(AMIGA_FREQUENCIES.len() - 2);
637 let frac = (fine_tune / 16.0) - (fine_tune / 16.0).floor();
638
639 let period = ((AMIGA_FREQUENCIES[pos] as f64 * (1.0 - frac))
640 + AMIGA_FREQUENCIES[pos + 1] as f64 * frac)
641 * 32.0 / (1 << ((note as i64) / 12)) as f64;
643
644 8363.0 * 1712.0 / period
645}
646
647static AMIGA_FREQUENCIES: &[u32] = &[
648 907, 900, 894, 887, 881, 875, 868, 862, 856, 850, 844, 838, 832, 826, 820, 814, 808, 802, 796,
649 791, 785, 779, 774, 768, 762, 757, 752, 746, 741, 736, 730, 725, 720, 715, 709, 704, 699, 694,
650 689, 684, 678, 675, 670, 665, 660, 655, 651, 646, 640, 636, 632, 628, 623, 619, 614, 610, 604,
651 601, 597, 592, 588, 584, 580, 575, 570, 567, 563, 559, 555, 551, 547, 543, 538, 535, 532, 528,
652 524, 520, 516, 513, 508, 505, 502, 498, 494, 491, 487, 484, 480, 477, 474, 470, 467, 463, 460,
653 457,
654];
655
656#[derive(PartialEq, Eq, Hash, Clone, Debug)]
657struct EnvelopeData {
658 amounts: Vec<Num<i16, 8>>,
659 sustain: Option<usize>,
660 loop_start: Option<usize>,
661 loop_end: Option<usize>,
662
663 vib_waveform: Waveform,
664 vib_speed: u8,
665 vib_amount: Num<i32, 12>,
666}
667
668impl EnvelopeData {
669 fn new(
670 e: &xmrs::envelope::Envelope,
671 instrument: &xmrs::instr_default::InstrDefault,
672 frequency_type: FrequencyType,
673 bpm: u32,
674 ) -> Self {
675 let mut amounts = vec![];
676
677 for frame in 0..=(Self::envelope_frame_to_gba_frame(e.point.last().unwrap().frame, bpm)) {
678 let xm_frame = Self::gba_frame_to_envelope_frame(frame, bpm);
679 let index = e
680 .point
681 .iter()
682 .rposition(|point| point.frame < xm_frame)
683 .unwrap_or(0);
684
685 let first_point = &e.point[index];
686 let second_point = &e.point[index + 1];
687
688 let amount = EnvelopePoint::lerp(first_point, second_point, xm_frame);
689 let amount = Num::from_f32(amount);
690
691 amounts.push(amount);
692 }
693
694 let sustain = if e.sustain_enabled {
695 Some(Self::envelope_frame_to_gba_frame(
696 e.point[e.sustain_point].frame,
697 bpm,
698 ))
699 } else {
700 None
701 };
702 let (loop_start, loop_end) = if e.loop_enabled {
703 (
704 Some(Self::envelope_frame_to_gba_frame(
705 e.point[e.loop_start_point].frame,
706 bpm,
707 )),
708 Some(Self::envelope_frame_to_gba_frame(
709 e.point[e.loop_end_point].frame,
710 bpm,
711 )),
712 )
713 } else {
714 (None, None)
715 };
716
717 let vib_waveform = match instrument.vibrato.waveform {
718 xmrs::instr_vibrato::Waveform::Sine => Waveform::Sine,
719 xmrs::instr_vibrato::Waveform::Square => Waveform::Square,
720 xmrs::instr_vibrato::Waveform::RampUp => Waveform::Saw,
721 xmrs::instr_vibrato::Waveform::RampDown => Waveform::Saw,
722 };
723
724 let vib_speed = (instrument.vibrato.speed * 64.0) as u8;
725 let vib_depth = instrument.vibrato.depth * 8.0;
726
727 let c4_speed = note_to_speed(Note::C4, 0.0, 0, frequency_type);
728 let mut vib_amount =
729 (note_to_speed(Note::C4, vib_depth.into(), 0, frequency_type) / c4_speed - 1)
730 .try_change_base()
731 .unwrap();
732
733 if matches!(
734 instrument.vibrato.waveform,
735 xmrs::instr_vibrato::Waveform::RampDown
736 ) {
737 vib_amount = -vib_amount;
738 }
739
740 EnvelopeData {
741 amounts,
742 sustain,
743 loop_start,
744 loop_end,
745
746 vib_waveform,
747 vib_speed,
748 vib_amount,
749 }
750 }
751
752 fn envelope_frame_to_gba_frame(envelope_frame: usize, bpm: u32) -> usize {
753 (envelope_frame as u32 * 250 / bpm) as usize
756 }
757
758 fn gba_frame_to_envelope_frame(gba_frame: usize, bpm: u32) -> usize {
759 (gba_frame as u32 * bpm / 250) as usize
760 }
761}