1use std::collections::HashSet;
2
3use arr_macro::arr;
4use num_enum::{IntoPrimitive, TryFromPrimitive};
5
6use crate::{
7 songs::{Song, V4_1_OFFSETS, V4_OFFSETS}, Instrument, Version, FIRMWARE_5_0_SONG_VERSION, FX
8};
9
10#[repr(u8)]
11#[allow(non_camel_case_types)]
12#[derive(IntoPrimitive, TryFromPrimitive, PartialEq, Copy, Clone, Default, Debug)]
13pub enum MoveKind {
14 #[default]
15 EQ,
16 INS,
17 PHR,
18 CHN,
19 TBL,
20}
21
22pub trait RemapperDescriptorBuilder {
23 fn moved(&mut self, kind: MoveKind, from: usize, to: usize);
24}
25
26fn make_mapping<const C: usize>(offset: u8) -> [u8; C] {
27 let mut arr = [0 as u8; C];
28 for i in 0..arr.len() {
29 arr[i] = i as u8 + offset;
30 }
31
32 arr
33}
34
35pub struct EqMapping {
36 pub eq_tracking_commands: Vec<u8>,
39
40 pub mapping: Vec<u8>,
43
44 pub to_move: Vec<u8>,
47}
48
49impl EqMapping {
50 pub fn default_ver(ver: Version) -> EqMapping {
51 let command_names = FX::fx_command_names(ver);
52 let eq_tracking_commands = command_names.find_indices(&EQ_TRACKING_COMMAND_NAMES);
53
54 if ver.after(&FIRMWARE_5_0_SONG_VERSION) {
55 EqMapping {
56 eq_tracking_commands,
57 mapping: vec![0; V4_1_OFFSETS.instrument_eq_count],
58 to_move: vec![],
59 }
60 } else {
61 EqMapping {
62 eq_tracking_commands,
63 mapping: vec![0; V4_OFFSETS.instrument_eq_count],
64 to_move: vec![],
65 }
66 }
67 }
68
69 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
70 for ix in &self.to_move {
71 let ixu = *ix as usize;
72 builder.moved(MoveKind::EQ, ixu, self.mapping[ixu] as usize)
73 }
74 }
75
76 pub fn print(&self) -> String {
77 let mut acc = String::new();
78
79 for e in self.to_move.iter() {
80 let new_ix = self.mapping[*e as usize];
81 acc = format!("{acc} Eq {e} => {new_ix}\n");
82 }
83
84 acc
85 }
86}
87
88pub struct InstrumentMapping {
90 pub instrument_tracking_commands: Vec<u8>,
93
94 pub mapping: [u8; Song::N_INSTRUMENTS],
97
98 pub to_move: Vec<u8>,
101}
102
103impl InstrumentMapping {
104 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
105 for ix in &self.to_move {
106 let ixu = *ix as usize;
107 builder.moved(MoveKind::INS, ixu, self.mapping[ixu] as usize)
108 }
109 }
110
111 pub fn print(&self) -> String {
112 let mut acc = String::new();
113
114 for e in self.to_move.iter() {
115 let new_ix = self.mapping[*e as usize];
116 acc = format!("{acc} instr {e} => {new_ix}\n");
117 }
118
119 acc
120 }
121
122 pub fn new(instrument_tracking_commands: Vec<u8>) -> Self {
123 Self {
124 instrument_tracking_commands,
125 mapping: make_mapping(0),
126 to_move: vec![],
127 }
128 }
129}
130
131pub struct TableMapping {
132 pub table_tracking_commands: Vec<u8>,
135
136 pub mapping: [u8; Song::N_TABLES],
138
139 pub to_move: Vec<u8>,
141}
142
143impl TableMapping {
144 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
145 for ix in &self.to_move {
146 let ixu = *ix as usize;
147 builder.moved(MoveKind::TBL, ixu, self.mapping[ixu] as usize)
148 }
149 }
150
151 pub fn print(&self) -> String {
152 let mut acc = String::new();
153
154 for e in self.to_move.iter() {
155 let new_ix = self.mapping[*e as usize];
156 acc = format!("{acc} table {e} => {new_ix}\n");
157 }
158
159 acc
160 }
161
162 pub fn remap_table(&mut self, from: u8, to: u8) {
163 self.mapping[from as usize] = to;
164 self.to_move.push(from);
165 }
166
167 fn new(table_tracking_commands: Vec<u8>) -> Self {
168 Self {
169 table_tracking_commands,
170 mapping: make_mapping(Song::N_TABLES as u8),
171 to_move: vec![],
172 }
173 }
174}
175
176pub struct PhraseMapping {
177 pub mapping: [u8; Song::N_PHRASES],
180
181 pub to_move: Vec<u8>,
184}
185
186impl PhraseMapping {
187 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
188 for ix in &self.to_move {
189 let ixu = *ix as usize;
190 builder.moved(MoveKind::PHR, ixu, self.mapping[ixu] as usize)
191 }
192 }
193
194 pub fn print(&self) -> String {
195 let mut acc = String::new();
196
197 for e in self.to_move.iter() {
198 let new_ix = self.mapping[*e as usize];
199 acc = format!("{acc} phrase {e} => {new_ix}\n");
200 }
201
202 acc
203 }
204}
205
206impl Default for PhraseMapping {
207 fn default() -> Self {
208 Self {
209 mapping: make_mapping(0),
210 to_move: vec![],
211 }
212 }
213}
214
215pub struct ChainMapping {
216 pub mapping: [u8; Song::N_CHAINS],
217 pub to_move: Vec<u8>,
218}
219
220impl ChainMapping {
221 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
222 for ix in &self.to_move {
223 let ixu = *ix as usize;
224 builder.moved(MoveKind::CHN, ixu, self.mapping[ixu] as usize)
225 }
226 }
227
228 pub fn print(&self) -> String {
229 let mut acc = String::new();
230
231 for e in self.to_move.iter() {
232 let new_ix = self.mapping[*e as usize];
233 acc = format!("{acc} chain {e} => {new_ix}\n");
234 }
235
236 acc
237 }
238}
239
240impl Default for ChainMapping {
241 fn default() -> Self {
242 Self {
243 mapping: make_mapping(0),
244 to_move: vec![],
245 }
246 }
247}
248
249pub struct Remapper {
250 pub eq_mapping: EqMapping,
251 pub instrument_mapping: InstrumentMapping,
252 pub table_mapping: TableMapping,
253 pub phrase_mapping: PhraseMapping,
254 pub chain_mapping: ChainMapping,
255}
256
257fn find_referenced_eq(song: &Song) -> Vec<bool> {
259 let mut allocated_eqs = vec![false; song.eqs.len()];
261
262 for instr in &song.instruments {
263 match instr.equ() {
264 None => {}
265 Some(eq) => {
266 let equ = eq as usize;
267 if equ < allocated_eqs.len() {
268 allocated_eqs[equ] = true
269 }
270 }
271 }
272 }
273
274 allocated_eqs
275}
276
277fn find_allocated_instruments(song: &Song) -> [bool; Song::N_INSTRUMENTS] {
278 let mut allocated_instr = arr![false; 128];
279
280 for (i, instr) in song.instruments.iter().enumerate() {
281 match instr {
282 Instrument::None => {}
283 _ => allocated_instr[i] = true,
284 }
285 }
286
287 allocated_instr
288}
289
290fn find_allocated_tables(song: &Song) -> [bool; Song::N_TABLES] {
291 let mut allocated_table = arr![false; 256];
292
293 for (i, table) in song.tables.iter().enumerate() {
294 allocated_table[i] = i < Song::N_INSTRUMENTS || !table.is_empty();
295 }
296
297 allocated_table
298}
299
300fn find_referenced_phrases(song: &Song) -> [bool; Song::N_PHRASES] {
301 let mut allocated_phrases = arr![false; 255];
302 for chain in &song.chains {
303 for step in &chain.steps {
304 let phrase = step.phrase as usize;
305 if phrase < Song::N_PHRASES {
306 allocated_phrases[phrase] = true;
307 }
308 }
309 }
310
311 for (phrase_id, phrase) in song.phrases.iter().enumerate() {
312 if !phrase.is_empty() {
313 allocated_phrases[phrase_id] = true;
314 }
315 }
316
317 allocated_phrases
318}
319
320fn find_referenced_chains(song: &Song) -> [bool; Song::N_CHAINS] {
321 let mut allocated_chains = arr![false; 255];
322 for chain in song.song.steps.iter() {
323 let chain = *chain as usize;
324 if chain < Song::N_CHAINS {
325 allocated_chains[chain] = true;
326 }
327 }
328
329 for (i, chain) in song.chains.iter().enumerate() {
330 if !chain.is_empty() {
331 allocated_chains[i] = true
332 }
333 }
334
335 allocated_chains
336}
337
338fn try_allocate(allocation_state: &[bool], previous_id: u8) -> Option<usize> {
340 let prev = previous_id as usize;
341 if !allocation_state[prev] {
342 Some(prev)
343 } else {
344 match allocation_state[prev..].iter().position(|v| !v) {
345 Some(p) => Some(p + prev),
347 None => allocation_state.iter().position(|v| !v),
349 }
350 }
351}
352
353fn try_allocate_rev(allocation_state: &[bool], previous_id: u8) -> Option<usize> {
355 let prev = previous_id as usize;
356 match allocation_state[prev..]
357 .iter()
358 .enumerate()
359 .rev()
360 .find(|(_p, v)| !*v)
361 {
362 Some((p, _)) => Some(p),
363 None => allocation_state.iter().rev().position(|v| !v),
365 }
366}
367
368pub(crate) const INSTRUMENT_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["INS", "NXT"];
371
372pub(crate) const TABLE_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["TBX", "TBL"];
375
376pub(crate) const EQ_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["EQI", "EQM"];
378
379struct InstrumentAllocatorState<'a> {
381 from_song: &'a Song,
382 to_song: &'a Song,
383
384 seen_instruments: HashSet<u8>,
387
388 seen_tables: HashSet<u8>,
391
392 instrument_flags: [bool; Song::N_INSTRUMENTS],
394 table_flags: [bool; Song::N_TABLES],
396 eq_flags: Vec<bool>,
398 allocated_eqs: Vec<bool>,
400 allocated_tables: [bool; Song::N_TABLES],
402 allocated_instruments: [bool; Song::N_INSTRUMENTS],
403 instrument_mapping: InstrumentMapping,
404 eq_mapping: EqMapping,
405 table_mapping: TableMapping,
406}
407
408impl<'a> InstrumentAllocatorState<'a> {
409 fn new(from_song: &'a Song, to_song: &'a Song) -> InstrumentAllocatorState<'a> {
410 let fx_commands_names = crate::FX::fx_command_names(from_song.version);
411 let instrument_tracking_commands =
412 fx_commands_names.find_indices(&INSTRUMENT_TRACKING_COMMAND_NAMES);
413 let table_tracking_commands = fx_commands_names.find_indices(&TABLE_TRACKING_COMMAND_NAMES);
414
415 InstrumentAllocatorState {
416 from_song,
417 to_song,
418
419 table_mapping: TableMapping::new(table_tracking_commands),
420 seen_instruments: HashSet::new(),
421 seen_tables: HashSet::new(),
422
423 allocated_eqs: find_referenced_eq(to_song),
424 allocated_instruments: find_allocated_instruments(to_song),
425 eq_flags: vec![false; from_song.eqs.len()],
426 instrument_flags: arr![false; 128],
427 table_flags: arr![false; 256],
428 allocated_tables: find_allocated_tables(to_song),
429 instrument_mapping: InstrumentMapping::new(instrument_tracking_commands),
430 eq_mapping: EqMapping::default_ver(to_song.version),
431 }
432 }
433
434 fn allocate_eq(&mut self, equ: usize, is_instrument_eq: bool) -> Result<(), String> {
435 self.eq_flags[equ as usize] = true;
436 let from_eq = &self.from_song.eqs[equ];
437
438 if is_instrument_eq && !self.allocated_eqs[equ] && !self.allocated_instruments[equ] {
441 self.allocated_eqs[equ] = true;
442 self.eq_mapping.mapping[equ] = equ as u8;
443 self.eq_mapping.to_move.push(equ as u8);
444 return Ok(());
445 }
446 match self.to_song.eqs.iter().position(|to_eq| to_eq == from_eq) {
448 Some(eq_idx) if (eq_idx as usize) < self.eq_mapping.mapping.len() => {
449 self.eq_mapping.mapping[equ] = eq_idx as u8
450 }
451 Some(_) | None => match try_allocate_rev(&self.allocated_eqs, equ as u8) {
452 None => return Err(format!("No more available eqs")),
453 Some(eq_slot) => {
454 self.allocated_eqs[eq_slot] = true;
455 self.eq_mapping.mapping[equ] = eq_slot as u8;
456 self.eq_mapping.to_move.push(equ as u8);
457 }
458 },
459 }
460
461 Ok(())
462 }
463
464 fn is_touching_instrument(&self, cmd: u8) -> bool {
465 self.instrument_mapping
466 .instrument_tracking_commands
467 .contains(&cmd)
468 }
469
470 fn is_touching_table(&self, cmd: u8) -> bool {
471 self.table_mapping.table_tracking_commands.contains(&cmd)
472 }
473
474 fn is_touching_eq(&self, cmd: u8) -> bool {
475 self.eq_mapping.eq_tracking_commands.contains(&cmd)
476 }
477
478 fn touch_table(&mut self, table_ix: usize) -> Result<(), String> {
479 if table_ix >= Song::N_TABLES || self.table_flags[table_ix] {
481 return Ok(());
482 }
483
484 if self.seen_tables.contains(&(table_ix as u8)) {
485 return Ok(()); }
487
488 self.seen_tables.insert(table_ix as u8);
489
490 let instrument_table = &self.from_song.tables[table_ix];
492 for table_step in instrument_table.steps.iter() {
493 for fx in table_step.all_fx() {
494 if self.is_touching_instrument(fx.command) {
495 self.touch_instrument(fx.value as usize)?;
496 }
497
498 if self.is_touching_table(fx.command) {
499 self.touch_table(fx.value as usize)?;
500 }
501
502 if self.is_touching_eq(fx.command) {
503 self.touch_eq(fx.value as usize, false)?;
504 }
505 }
506 }
507
508 if table_ix > Song::N_INSTRUMENTS {
511 match try_allocate(&self.allocated_tables, table_ix as u8) {
512 None => return Err(format!("No table slot available")),
513 Some(new_ix) => {
514 self.table_mapping.to_move.push(table_ix as u8);
515 self.table_mapping.mapping[table_ix] = new_ix as u8;
516 self.allocated_tables[new_ix] = true;
517 }
518 }
519 }
520
521 self.seen_tables.remove(&(table_ix as u8));
522
523 Ok(())
524 }
525
526 fn touch_eq(&mut self, eq_ix: usize, is_instrument_eq: bool) -> Result<(), String> {
527 if eq_ix < self.eq_flags.len() && !self.eq_flags[eq_ix] {
528 self.allocate_eq(eq_ix, is_instrument_eq)?;
529 }
530 Ok(())
531 }
532
533 fn touch_instrument(&mut self, instr_ix: usize) -> Result<(), String> {
534 let from_song = self.from_song;
535 let to_song = self.to_song;
536
537 if instr_ix >= Song::N_INSTRUMENTS || self.instrument_flags[instr_ix] {
539 return Ok(());
540 }
541
542 if self.seen_instruments.contains(&(instr_ix as u8)) {
543 return Ok(());
544 }
546
547 self.seen_instruments.insert(instr_ix as u8);
548
549 let mut instr = from_song.instruments[instr_ix].clone();
550
551 if let Some(equ) = instr.equ() {
553 let equ = equ as usize;
554
555 if equ < self.eq_flags.len() && !self.eq_flags[equ] {
556 let is_instrum_eq =
559 self.from_song.version.after(&FIRMWARE_5_0_SONG_VERSION) && (equ == instr_ix);
560
561 self.allocate_eq(equ, is_instrum_eq)?;
562 }
563
564 if equ < self.eq_mapping.mapping.len() {
566 instr.set_eq(self.eq_mapping.mapping[equ]);
567 }
568 }
569
570 self.touch_table(instr_ix)?;
571
572 self.instrument_flags[instr_ix] = true;
573 match to_song.instruments.iter().position(|i| i == &instr) {
574 Some(to_instr_ix) => self.instrument_mapping.mapping[instr_ix] = to_instr_ix as u8,
576 None => match try_allocate(&self.allocated_instruments, instr_ix as u8) {
578 None => {
579 return Err(format!(
580 "No more available instrument slots for instrument {instr_ix}"
581 ))
582 }
583 Some(to_instr_ix) => {
584 self.instrument_mapping.mapping[instr_ix] = to_instr_ix as u8;
585 self.allocated_instruments[to_instr_ix] = true;
586 self.instrument_mapping.to_move.push(instr_ix as u8)
587 }
588 },
589 };
590
591 self.seen_instruments.remove(&(instr_ix as u8));
592 Ok(())
593 }
594}
595
596impl Remapper {
597 pub fn default_ver(ver: Version) -> Self {
598 let command_names = crate::FX::fx_command_names(ver);
599 let instrument_tracking_commands =
600 command_names.find_indices(&INSTRUMENT_TRACKING_COMMAND_NAMES);
601 let table_tracking_commands = command_names.find_indices(&TABLE_TRACKING_COMMAND_NAMES);
602
603 Self {
604 eq_mapping: EqMapping::default_ver(ver),
605 instrument_mapping: InstrumentMapping::new(instrument_tracking_commands),
606 table_mapping: TableMapping::new(table_tracking_commands),
607 phrase_mapping: Default::default(),
608 chain_mapping: Default::default(),
609 }
610 }
611
612 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
613 self.eq_mapping.describe(builder);
614 self.instrument_mapping.describe(builder);
615 self.table_mapping.describe(builder);
616 self.phrase_mapping.describe(builder);
617 self.chain_mapping.describe(builder);
618 }
619
620 pub fn out_chain(&self, chain_id: u8) -> u8 {
621 self.chain_mapping.mapping[chain_id as usize]
622 }
623
624 pub fn print(&self) -> String {
625 let eq = self.eq_mapping.print();
626 let instr = self.instrument_mapping.print();
627 let phrase = self.phrase_mapping.print();
628 let chain = self.chain_mapping.print();
629 let table = self.table_mapping.print();
630 format!("{eq}\n{instr}\n{phrase}\n{chain}\n{table}")
631 }
632
633 fn allocate_chains<'a, IT>(
634 from_song: &Song,
635 to_song: &Song,
636 phrase_mapping: &PhraseMapping,
637 from_chains_ids: IT,
638 ) -> Result<ChainMapping, String>
639 where
640 IT: Iterator<Item = &'a u8>,
641 {
642 let mut seen_chain: [bool; Song::N_CHAINS] = arr![false; 255];
643 let mut allocated_chains = find_referenced_chains(to_song);
644 let mut mapping: [u8; Song::N_CHAINS] = make_mapping(0);
645 let mut to_move = vec![];
646
647 for chain_id in from_chains_ids {
648 let chain_id = *chain_id as usize;
649 if chain_id >= Song::N_CHAINS || seen_chain[chain_id] {
650 continue;
651 }
652
653 seen_chain[chain_id] = true;
654 let to_chain = from_song.chains[chain_id].map(phrase_mapping);
655
656 match to_song
657 .chains
658 .iter()
659 .position(|c| c.steps == to_chain.steps)
660 {
661 Some(c) => mapping[chain_id] = c as u8,
662 None => match try_allocate(&allocated_chains, chain_id as u8) {
663 None => {
664 return Err(format!(
665 "No more available chain slots for chain {chain_id}"
666 ))
667 }
668 Some(free_slot) => {
669 allocated_chains[free_slot] = true;
670 mapping[chain_id] = free_slot as u8;
671 to_move.push(chain_id as u8);
672 }
673 },
674 }
675 }
676
677 Ok(ChainMapping { mapping, to_move })
678 }
679
680 fn allocate_phrases<'a, IT>(
681 from_song: &Song,
682 to_song: &Song,
683 instr_mapping: &InstrumentMapping,
684 table_mapping: &TableMapping,
685 eq_mapping: &EqMapping,
686 from_chains_ids: IT,
687 ) -> Result<PhraseMapping, String>
688 where
689 IT: Iterator<Item = &'a u8>,
690 {
691 let mut allocated_phrases = find_referenced_phrases(to_song);
692
693 let mut seen_phrase: [bool; Song::N_PHRASES] = arr![false; 0xFF];
694 let mut phrase_mapping: [u8; Song::N_PHRASES] = arr![0 as u8; 0xFF];
695
696 let mut to_move = vec![];
697
698 for chain_id in from_chains_ids {
699 let from_chain = &from_song.chains[*chain_id as usize];
700
701 for chain_step in from_chain.steps.iter() {
702 let phrase_ix = chain_step.phrase as usize;
703
704 if phrase_ix >= Song::N_PHRASES || seen_phrase[phrase_ix] {
705 continue;
706 }
707
708 seen_phrase[phrase_ix] = true;
709 let phrase = from_song.phrases[phrase_ix].map_instruments(
710 instr_mapping,
711 table_mapping,
712 eq_mapping,
713 );
714 match to_song.phrases.iter().position(|p| p.steps == phrase.steps) {
715 Some(known) => phrase_mapping[phrase_ix] = known as u8,
716 None => match try_allocate(&allocated_phrases, phrase_ix as u8) {
717 None => {
718 return Err(format!(
719 "No more available phrase slots for phrase {phrase_ix}"
720 ))
721 }
722 Some(slot) => {
723 to_move.push(phrase_ix as u8);
724 allocated_phrases[slot] = true;
725 phrase_mapping[phrase_ix] = slot as u8;
726 }
727 },
728 }
729 }
730 }
731
732 Ok(PhraseMapping {
733 mapping: phrase_mapping,
734 to_move,
735 })
736 }
737
738 fn allocate_eq_and_instruments<'a, IT>(
740 from_song: &'a Song,
741 to_song: &'a Song,
742 from_chains_ids: IT,
743 ) -> Result<InstrumentAllocatorState<'a>, String>
744 where
745 IT: Iterator<Item = &'a u8>,
746 {
747 let mut alloc_state = InstrumentAllocatorState::new(from_song, to_song);
748
749 for chain_id in from_chains_ids {
750 let from_chain = &from_song.chains[*chain_id as usize];
751
752 for chain_step in &from_chain.steps {
753 let phrase_id = chain_step.phrase as usize;
754 if phrase_id >= Song::N_PHRASES {
755 continue;
756 }
757
758 let phrase = &from_song.phrases[phrase_id];
759
760 for step in &phrase.steps {
761 alloc_state.touch_instrument(step.instrument as usize)?;
762
763 for fx in step.all_fx() {
764 if alloc_state.is_touching_instrument(fx.command) {
765 alloc_state.touch_instrument(fx.value as usize)?;
766 }
767
768 if alloc_state.is_touching_table(fx.command) {
769 alloc_state.touch_table(fx.value as usize)?;
770 }
771
772 if alloc_state.is_touching_eq(fx.command) {
773 alloc_state.touch_eq(fx.value as usize, false)?;
774 }
775 }
776 }
777 }
778 }
779
780 Ok(alloc_state)
781 }
782
783 pub fn create<'a, IT>(from_song: &Song, to_song: &Song, chains: IT) -> Result<Remapper, String>
784 where
785 IT: Iterator<Item = &'a u8>,
786 {
787 let chain_vec: Vec<u8> = chains.map(|v| *v).collect();
788
789 let alloc_state =
791 Remapper::allocate_eq_and_instruments(from_song, to_song, chain_vec.iter())?;
792
793 let phrase_mapping = Remapper::allocate_phrases(
794 from_song,
795 to_song,
796 &alloc_state.instrument_mapping,
797 &alloc_state.table_mapping,
798 &alloc_state.eq_mapping,
799 chain_vec.iter(),
800 )?;
801
802 let chain_mapping =
803 Remapper::allocate_chains(from_song, to_song, &phrase_mapping, chain_vec.iter())?;
804
805 Ok(Self {
806 eq_mapping: alloc_state.eq_mapping,
807 instrument_mapping: alloc_state.instrument_mapping,
808 table_mapping: alloc_state.table_mapping,
809 phrase_mapping,
810 chain_mapping,
811 })
812 }
813
814 pub fn renumber(&self, song: &mut Song) {
816 for equ in self.eq_mapping.to_move.iter() {
818 let equ = *equ as usize;
819 let to_index = self.eq_mapping.mapping[equ];
820 song.eqs[to_index as usize] = song.eqs[equ].clone();
821 song.eqs[equ].clear();
822 }
823
824 for instr_id in self.instrument_mapping.to_move.iter() {
826 let instr_id = *instr_id as usize;
827 let to_index = self.instrument_mapping.mapping[instr_id] as usize;
828 let instr = song.instruments[instr_id].clone();
829
830 song.tables[to_index] = song.tables[instr_id].clone();
831 song.instruments[to_index] = instr;
832 song.instruments[instr_id] = Instrument::None;
833 }
834
835 for table_id in self.table_mapping.to_move.iter() {
837 let table_id = *table_id as usize;
838 let to_index = self.table_mapping.mapping[table_id] as usize;
839 let table = song.tables[table_id].map_instr(
840 &self.instrument_mapping,
841 &self.table_mapping,
842 &self.eq_mapping,
843 );
844
845 song.tables[to_index] = table;
846 song.tables[table_id].clear();
847 }
848
849 let eq_count = song.eq_count() - 4;
851 for instr_id in 0..Song::N_INSTRUMENTS {
852 let instr = &mut song.instruments[instr_id];
853
854 if let Some(eq) = instr.equ() {
855 let eq = eq as usize;
856 if eq < eq_count {
857 instr.set_eq(self.eq_mapping.mapping[eq]);
858 }
859 }
860 }
861
862 for phrase_id in self.phrase_mapping.to_move.iter() {
864 let phrase_id = *phrase_id as usize;
865 let to_index = self.phrase_mapping.mapping[phrase_id];
866 song.phrases[to_index as usize] = song.phrases[phrase_id].clone();
867 song.phrases[phrase_id].clear()
868 }
869
870 for phrase_id in 0..Song::N_PHRASES {
872 song.phrases[phrase_id] = song.phrases[phrase_id].map_instruments(
873 &self.instrument_mapping,
874 &self.table_mapping,
875 &self.eq_mapping,
876 );
877 }
878
879 for chain_id in self.chain_mapping.to_move.iter() {
881 let chain_id = *chain_id as usize;
882 let to_index = self.chain_mapping.mapping[chain_id];
883 song.chains[to_index as usize] = song.chains[chain_id].clone();
884 song.chains[chain_id].clear();
885 }
886
887 for chain_id in 0..Song::N_CHAINS {
889 song.chains[chain_id] = song.chains[chain_id].map(&self.phrase_mapping)
890 }
891 }
892
893 pub fn apply(&self, from: &Song, to: &mut Song) {
895 for equ in self.eq_mapping.to_move.iter() {
896 let equ = *equ as usize;
897 let to_index = self.eq_mapping.mapping[equ];
898 to.eqs[to_index as usize] = from.eqs[equ].clone();
899 }
900
901 for instr_id in self.instrument_mapping.to_move.iter() {
902 let instr_id = *instr_id as usize;
903 let to_index = self.instrument_mapping.mapping[instr_id] as usize;
904 let mut instr = from.instruments[instr_id].clone();
905
906 if let Some(eq) = instr.equ() {
907 let eq = eq as usize;
908 if eq < to.eq_count() && eq < self.eq_mapping.mapping.len() {
909 instr.set_eq(self.eq_mapping.mapping[eq]);
910 }
911 }
912
913 to.tables[to_index] = from.tables[instr_id].map_instr(
914 &self.instrument_mapping,
915 &self.table_mapping,
916 &self.eq_mapping,
917 );
918 to.instruments[to_index] = instr;
919 }
920
921 for table_id in self.table_mapping.to_move.iter() {
923 let table_id = *table_id as usize;
924 let to_index = self.table_mapping.mapping[table_id] as usize;
925 to.tables[to_index] = from.tables[table_id].map_instr(
926 &self.instrument_mapping,
927 &self.table_mapping,
928 &self.eq_mapping,
929 );
930 }
931
932 for phrase_id in self.phrase_mapping.to_move.iter() {
933 let phrase_id = *phrase_id as usize;
934 let to_index = self.phrase_mapping.mapping[phrase_id];
935 to.phrases[to_index as usize] = from.phrases[phrase_id].map_instruments(
936 &self.instrument_mapping,
937 &self.table_mapping,
938 &self.eq_mapping,
939 );
940 }
941
942 for chain_id in self.chain_mapping.to_move.iter() {
943 let chain_id = *chain_id as usize;
944 let to_index = self.chain_mapping.mapping[chain_id];
945 to.chains[to_index as usize] = from.chains[chain_id].map(&self.phrase_mapping);
946 }
947 }
948}
949
950#[cfg(test)]
951mod tests {
952 use crate::songs::*;
953 use std::fs::File;
954
955 use super::Remapper;
956
957 fn track_eq() -> Song {
958 let mut f = File::open("./examples/songs/TRACKEQ.m8s").expect("Could not open TRACKEQ");
959 Song::read(&mut f).expect("Could not parse TRACKEQ")
960 }
961
962 fn empty_6() -> Song {
963 let mut f = File::open("./examples/songs/V6EMPTY.m8s").expect("Could not open V6EMPTY");
964 Song::read(&mut f).expect("Could not parse V6EMPTY")
965 }
966
967 fn do_copy(chain_number: u8) -> Remapper {
968 let track_eq = track_eq();
969 let mut empty_song = empty_6();
970 let remapper = Remapper::create(&track_eq, &empty_song, [chain_number].iter())
971 .expect("Mapping failure");
972
973 remapper.apply(&track_eq, &mut empty_song);
974 remapper
975 }
976
977 #[test]
978 fn copy_chain_0() {
979 do_copy(0x0);
980 }
981
982 #[test]
983 fn copy_chain_1() {
984 do_copy(0x1);
985 }
986
987 #[test]
988 fn copy_chain_2() {
989 do_copy(0x2);
990 }
991
992 #[test]
993 fn copy_chain_10() {
994 do_copy(0x10);
995 }
996
997 #[test]
998 fn copy_chain_20() {
999 let remap = do_copy(0x20);
1000 assert!(remap.eq_mapping.to_move.contains(&0x01));
1001 }
1002
1003 #[test]
1004 fn copy_chain_21() {
1005 do_copy(0x21);
1006 }
1007
1008 #[test]
1009 fn copy_chain_30() {
1010 do_copy(0x30);
1011 }
1012
1013 #[test]
1014 fn copy_chain_40() {
1015 let remap = do_copy(0x40);
1016 assert!(remap.table_mapping.to_move.contains(&0x81))
1017 }
1018}