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},
8 Instrument, Version, FX,
9};
10
11#[repr(u8)]
12#[allow(non_camel_case_types)]
13#[derive(IntoPrimitive, TryFromPrimitive, PartialEq, Copy, Clone, Default, Debug)]
14pub enum MoveKind {
15 #[default]
16 EQ,
17 INS,
18 PHR,
19 CHN,
20 TBL,
21}
22
23pub trait RemapperDescriptorBuilder {
24 fn moved(&mut self, kind: MoveKind, from: usize, to: usize);
25}
26
27fn make_mapping<const C: usize>(offset: u8) -> [u8; C] {
28 let mut arr = [0 as u8; C];
29 for i in 0..arr.len() {
30 arr[i] = i as u8 + offset;
31 }
32
33 arr
34}
35
36pub struct EqMapping {
37 pub eq_tracking_commands: Vec<u8>,
40
41 pub mapping: Vec<u8>,
44
45 pub to_move: Vec<u8>,
48}
49
50impl EqMapping {
51 pub fn default_ver(ver: Version) -> EqMapping {
52 let command_names = FX::fx_command_names(ver);
53 let eq_tracking_commands = command_names.find_indices(&EQ_TRACKING_COMMAND_NAMES);
54
55 if ver.at_least(4, 1) {
56 EqMapping {
57 eq_tracking_commands,
58 mapping: vec![0; V4_1_OFFSETS.instrument_eq_count],
59 to_move: vec![],
60 }
61 } else {
62 EqMapping {
63 eq_tracking_commands,
64 mapping: vec![0; V4_OFFSETS.instrument_eq_count],
65 to_move: vec![],
66 }
67 }
68 }
69
70 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
71 for ix in &self.to_move {
72 let ixu = *ix as usize;
73 builder.moved(MoveKind::EQ, ixu, self.mapping[ixu] as usize)
74 }
75 }
76
77 pub fn print(&self) -> String {
78 let mut acc = String::new();
79
80 for e in self.to_move.iter() {
81 let new_ix = self.mapping[*e as usize];
82 acc = format!("{acc} Eq {e} => {new_ix}\n");
83 }
84
85 acc
86 }
87}
88
89pub struct InstrumentMapping {
91 pub instrument_tracking_commands: Vec<u8>,
94
95 pub mapping: [u8; Song::N_INSTRUMENTS],
98
99 pub to_move: Vec<u8>,
102}
103
104impl InstrumentMapping {
105 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
106 for ix in &self.to_move {
107 let ixu = *ix as usize;
108 builder.moved(MoveKind::INS, ixu, self.mapping[ixu] as usize)
109 }
110 }
111
112 pub fn print(&self) -> String {
113 let mut acc = String::new();
114
115 for e in self.to_move.iter() {
116 let new_ix = self.mapping[*e as usize];
117 acc = format!("{acc} instr {e} => {new_ix}\n");
118 }
119
120 acc
121 }
122
123 pub fn new(instrument_tracking_commands: Vec<u8>) -> Self {
124 Self {
125 instrument_tracking_commands,
126 mapping: make_mapping(0),
127 to_move: vec![],
128 }
129 }
130}
131
132pub struct TableMapping {
133 pub table_tracking_commands: Vec<u8>,
136
137 pub mapping: [u8; Song::N_TABLES],
139
140 pub to_move: Vec<u8>,
142}
143
144impl TableMapping {
145 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
146 for ix in &self.to_move {
147 let ixu = *ix as usize;
148 builder.moved(MoveKind::TBL, ixu, self.mapping[ixu] as usize)
149 }
150 }
151
152 pub fn print(&self) -> String {
153 let mut acc = String::new();
154
155 for e in self.to_move.iter() {
156 let new_ix = self.mapping[*e as usize];
157 acc = format!("{acc} table {e} => {new_ix}\n");
158 }
159
160 acc
161 }
162
163 pub fn remap_table(&mut self, from: u8, to: u8) {
164 self.mapping[from as usize] = to;
165 self.to_move.push(from);
166 }
167
168 fn new(table_tracking_commands: Vec<u8>) -> Self {
169 Self {
170 table_tracking_commands,
171 mapping: make_mapping(Song::N_TABLES as u8),
172 to_move: vec![],
173 }
174 }
175}
176
177pub struct PhraseMapping {
178 pub mapping: [u8; Song::N_PHRASES],
181
182 pub to_move: Vec<u8>,
185}
186
187impl PhraseMapping {
188 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
189 for ix in &self.to_move {
190 let ixu = *ix as usize;
191 builder.moved(MoveKind::PHR, ixu, self.mapping[ixu] as usize)
192 }
193 }
194
195 pub fn print(&self) -> String {
196 let mut acc = String::new();
197
198 for e in self.to_move.iter() {
199 let new_ix = self.mapping[*e as usize];
200 acc = format!("{acc} phrase {e} => {new_ix}\n");
201 }
202
203 acc
204 }
205}
206
207impl Default for PhraseMapping {
208 fn default() -> Self {
209 Self {
210 mapping: make_mapping(0),
211 to_move: vec![],
212 }
213 }
214}
215
216pub struct ChainMapping {
217 pub mapping: [u8; Song::N_CHAINS],
218 pub to_move: Vec<u8>,
219}
220
221impl ChainMapping {
222 pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
223 for ix in &self.to_move {
224 let ixu = *ix as usize;
225 builder.moved(MoveKind::CHN, ixu, self.mapping[ixu] as usize)
226 }
227 }
228
229 pub fn print(&self) -> String {
230 let mut acc = String::new();
231
232 for e in self.to_move.iter() {
233 let new_ix = self.mapping[*e as usize];
234 acc = format!("{acc} chain {e} => {new_ix}\n");
235 }
236
237 acc
238 }
239}
240
241impl Default for ChainMapping {
242 fn default() -> Self {
243 Self {
244 mapping: make_mapping(0),
245 to_move: vec![],
246 }
247 }
248}
249
250pub struct Remapper {
251 pub eq_mapping: EqMapping,
252 pub instrument_mapping: InstrumentMapping,
253 pub table_mapping: TableMapping,
254 pub phrase_mapping: PhraseMapping,
255 pub chain_mapping: ChainMapping,
256}
257
258fn find_referenced_eq(song: &Song) -> Vec<bool> {
260 let mut allocated_eqs = vec![false; song.eqs.len()];
262
263 for instr in &song.instruments {
264 match instr.equ() {
265 None => {}
266 Some(eq) => {
267 let equ = eq as usize;
268 if equ < allocated_eqs.len() {
269 allocated_eqs[equ] = true
270 }
271 }
272 }
273 }
274
275 allocated_eqs
276}
277
278fn find_allocated_instruments(song: &Song) -> [bool; Song::N_INSTRUMENTS] {
279 let mut allocated_instr = arr![false; 128];
280
281 for (i, instr) in song.instruments.iter().enumerate() {
282 match instr {
283 Instrument::None => {}
284 _ => allocated_instr[i] = true,
285 }
286 }
287
288 allocated_instr
289}
290
291fn find_allocated_tables(song: &Song) -> [bool; Song::N_TABLES] {
292 let mut allocated_table = arr![false; 256];
293
294 for (i, table) in song.tables.iter().enumerate() {
295 allocated_table[i] = i < Song::N_INSTRUMENTS || !table.is_empty();
296 }
297
298 allocated_table
299}
300
301fn find_referenced_phrases(song: &Song) -> [bool; Song::N_PHRASES] {
302 let mut allocated_phrases = arr![false; 255];
303 for chain in &song.chains {
304 for step in &chain.steps {
305 let phrase = step.phrase as usize;
306 if phrase < Song::N_PHRASES {
307 allocated_phrases[phrase] = true;
308 }
309 }
310 }
311
312 for (phrase_id, phrase) in song.phrases.iter().enumerate() {
313 if !phrase.is_empty() {
314 allocated_phrases[phrase_id] = true;
315 }
316 }
317
318 allocated_phrases
319}
320
321fn find_referenced_chains(song: &Song) -> [bool; Song::N_CHAINS] {
322 let mut allocated_chains = arr![false; 255];
323 for chain in song.song.steps.iter() {
324 let chain = *chain as usize;
325 if chain < Song::N_CHAINS {
326 allocated_chains[chain] = true;
327 }
328 }
329
330 for (i, chain) in song.chains.iter().enumerate() {
331 if !chain.is_empty() {
332 allocated_chains[i] = true
333 }
334 }
335
336 allocated_chains
337}
338
339fn try_allocate(allocation_state: &[bool], previous_id: u8) -> Option<usize> {
341 let prev = previous_id as usize;
342 if !allocation_state[prev] {
343 Some(prev)
344 } else {
345 match allocation_state[prev..].iter().position(|v| !v) {
346 Some(p) => Some(p + prev),
348 None => allocation_state.iter().position(|v| !v),
350 }
351 }
352}
353
354fn try_allocate_rev(allocation_state: &[bool], previous_id: u8) -> Option<usize> {
356 let prev = previous_id as usize;
357 match allocation_state[prev..]
358 .iter()
359 .enumerate()
360 .rev()
361 .find(|(_p, v)| !*v)
362 {
363 Some((p, _)) => Some(p),
364 None => allocation_state.iter().rev().position(|v| !v),
366 }
367}
368
369pub(crate) const INSTRUMENT_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["INS", "NXT"];
372
373pub(crate) const TABLE_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["TBX", "TBL"];
376
377pub(crate) const EQ_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["EQI", "EQM"];
379
380struct InstrumentAllocatorState<'a> {
382 from_song: &'a Song,
383 to_song: &'a Song,
384
385 seen_instruments: HashSet<u8>,
388
389 seen_tables: HashSet<u8>,
392
393 instrument_flags: [bool; Song::N_INSTRUMENTS],
395 table_flags: [bool; Song::N_TABLES],
397 eq_flags: Vec<bool>,
399 allocated_eqs: Vec<bool>,
401 allocated_tables: [bool; Song::N_TABLES],
403 allocated_instruments: [bool; Song::N_INSTRUMENTS],
404 instrument_mapping: InstrumentMapping,
405 eq_mapping: EqMapping,
406 table_mapping: TableMapping,
407}
408
409impl<'a> InstrumentAllocatorState<'a> {
410 fn new(from_song: &'a Song, to_song: &'a Song) -> InstrumentAllocatorState<'a> {
411 let fx_commands_names = crate::FX::fx_command_names(from_song.version);
412 let instrument_tracking_commands =
413 fx_commands_names.find_indices(&INSTRUMENT_TRACKING_COMMAND_NAMES);
414 let table_tracking_commands = fx_commands_names.find_indices(&TABLE_TRACKING_COMMAND_NAMES);
415
416 InstrumentAllocatorState {
417 from_song,
418 to_song,
419
420 table_mapping: TableMapping::new(table_tracking_commands),
421 seen_instruments: HashSet::new(),
422 seen_tables: HashSet::new(),
423
424 allocated_eqs: find_referenced_eq(to_song),
425 allocated_instruments: find_allocated_instruments(to_song),
426 eq_flags: vec![false; from_song.eqs.len()],
427 instrument_flags: arr![false; 128],
428 table_flags: arr![false; 256],
429 allocated_tables: find_allocated_tables(to_song),
430 instrument_mapping: InstrumentMapping::new(instrument_tracking_commands),
431 eq_mapping: EqMapping::default_ver(to_song.version),
432 }
433 }
434
435 fn allocate_eq(&mut self, equ: usize, is_instrument_eq: bool) -> Result<(), String> {
436 self.eq_flags[equ as usize] = true;
437 let from_eq = &self.from_song.eqs[equ];
438
439 if is_instrument_eq && !self.allocated_eqs[equ] && !self.allocated_instruments[equ] {
442 self.allocated_eqs[equ] = true;
443 self.eq_mapping.mapping[equ] = equ as u8;
444 self.eq_mapping.to_move.push(equ as u8);
445 return Ok(());
446 }
447 match self.to_song.eqs.iter().position(|to_eq| to_eq == from_eq) {
449 Some(eq_idx) if (eq_idx as usize) < self.eq_mapping.mapping.len() => {
450 self.eq_mapping.mapping[equ] = eq_idx as u8
451 }
452 Some(_) | None => match try_allocate_rev(&self.allocated_eqs, equ as u8) {
453 None => return Err(format!("No more available eqs")),
454 Some(eq_slot) => {
455 self.allocated_eqs[eq_slot] = true;
456 self.eq_mapping.mapping[equ] = eq_slot as u8;
457 self.eq_mapping.to_move.push(equ as u8);
458 }
459 },
460 }
461
462 Ok(())
463 }
464
465 fn is_touching_instrument(&self, cmd: u8) -> bool {
466 self.instrument_mapping
467 .instrument_tracking_commands
468 .contains(&cmd)
469 }
470
471 fn is_touching_table(&self, cmd: u8) -> bool {
472 self.table_mapping.table_tracking_commands.contains(&cmd)
473 }
474
475 fn is_touching_eq(&self, cmd: u8) -> bool {
476 self.eq_mapping.eq_tracking_commands.contains(&cmd)
477 }
478
479 fn touch_table(&mut self, table_ix: usize) -> Result<(), String> {
480 if table_ix >= Song::N_TABLES || self.table_flags[table_ix] {
482 return Ok(());
483 }
484
485 if self.seen_tables.contains(&(table_ix as u8)) {
486 return Ok(()); }
488
489 self.seen_tables.insert(table_ix as u8);
490
491 let instrument_table = &self.from_song.tables[table_ix];
493 for table_step in instrument_table.steps.iter() {
494 for fx in table_step.all_fx() {
495 if self.is_touching_instrument(fx.command) {
496 self.touch_instrument(fx.value as usize)?;
497 }
498
499 if self.is_touching_table(fx.command) {
500 self.touch_table(fx.value as usize)?;
501 }
502
503 if self.is_touching_eq(fx.command) {
504 self.touch_eq(fx.value as usize, false)?;
505 }
506 }
507 }
508
509 if table_ix > Song::N_INSTRUMENTS {
512 match try_allocate(&self.allocated_tables, table_ix as u8) {
513 None => return Err(format!("No table slot available")),
514 Some(new_ix) => {
515 self.table_mapping.to_move.push(table_ix as u8);
516 self.table_mapping.mapping[table_ix] = new_ix as u8;
517 self.allocated_tables[new_ix] = true;
518 }
519 }
520 }
521
522 self.seen_tables.remove(&(table_ix as u8));
523
524 Ok(())
525 }
526
527 fn touch_eq(&mut self, eq_ix: usize, is_instrument_eq: bool) -> Result<(), String> {
528 if eq_ix < self.eq_flags.len() && !self.eq_flags[eq_ix] {
529 self.allocate_eq(eq_ix, is_instrument_eq)?;
530 }
531 Ok(())
532 }
533
534 fn touch_instrument(&mut self, instr_ix: usize) -> Result<(), String> {
535 let from_song = self.from_song;
536 let to_song = self.to_song;
537
538 if instr_ix >= Song::N_INSTRUMENTS || self.instrument_flags[instr_ix] {
540 return Ok(());
541 }
542
543 if self.seen_instruments.contains(&(instr_ix as u8)) {
544 return Ok(());
545 }
547
548 self.seen_instruments.insert(instr_ix as u8);
549
550 let mut instr = from_song.instruments[instr_ix].clone();
551
552 if let Some(equ) = instr.equ() {
554 let equ = equ as usize;
555
556 if equ < self.eq_flags.len() && !self.eq_flags[equ] {
557 let is_instrum_eq = self.from_song.version.at_least(5, 0) && (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}