m8_file_parser/
remapper.rs

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    /// List all the command ID referencing an EQ as
38    /// value. Depend on the song version number.
39    pub eq_tracking_commands: Vec<u8>,
40
41    /// Mapping from the "from" song eq index to the "to" song
42    /// eq index
43    pub mapping: Vec<u8>,
44
45    /// Eqs to be moved during the remapping
46    /// index in the "from" song
47    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
89/// For every instrument, it's destination instrument
90pub struct InstrumentMapping {
91    /// List all the command ID referencing an instrument as
92    /// value. Depend on the song version number.
93    pub instrument_tracking_commands: Vec<u8>,
94
95    /// Mapping from the "from" song instrument index to the "to"
96    /// song instrument index
97    pub mapping: [u8; Song::N_INSTRUMENTS],
98
99    /// Instruments to be moved during the remapping
100    /// index in the "from" song
101    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    /// List all the command ID referencing a table as
134    /// value. Depend on the song version number.
135    pub table_tracking_commands: Vec<u8>,
136
137    /// Mapping from the "from" song index to the to
138    pub mapping: [u8; Song::N_TABLES],
139
140    /// Table to be moved during remapping
141    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    /// Mapping from the "from" song phrase index to
179    /// the "to" phrase index
180    pub mapping: [u8; Song::N_PHRASES],
181
182    /// Phrases to be moved during the remapping
183    /// index in the "from" song
184    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
258/// Iter on all instruments to find allocated Eqs
259fn find_referenced_eq(song: &Song) -> Vec<bool> {
260    // flags on eqs in "to"
261    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
339/// Try to allocate in the new song by keeping previous numbers
340fn 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            // we take a slot above the existing one
347            Some(p) => Some(p + prev),
348            // nothing else worked, just try to find any free slot
349            None => allocation_state.iter().position(|v| !v),
350        }
351    }
352}
353
354/// Try to allocate in the new song by keeping previous numbers
355fn 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        // nothing else worked, just try to find any free slot
365        None => allocation_state.iter().rev().position(|v| !v),
366    }
367}
368
369/// These commands track instruments and must include the
370/// target instrument to fully reconstruct the sound
371pub(crate) const INSTRUMENT_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["INS", "NXT"];
372
373/// This command an external "table", that also must be copied
374/// in order to properly reproduce the phrase
375pub(crate) const TABLE_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["TBX", "TBL"];
376
377/// These commands track EQs, that must be copied, yada yada.
378pub(crate) const EQ_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["EQI", "EQM"];
379
380/// brief struture to hold structures used to allocate instruments
381struct InstrumentAllocatorState<'a> {
382    from_song: &'a Song,
383    to_song: &'a Song,
384
385    /// cycle detection, can happen if we follow
386    /// INS/NXT references
387    seen_instruments: HashSet<u8>,
388
389    /// cycle detection, can happen if we follow
390    /// INS/NXT/TBX references
391    seen_tables: HashSet<u8>,
392
393    /// flags on instruments in "from"
394    instrument_flags: [bool; Song::N_INSTRUMENTS],
395    /// flags on tables in "from"
396    table_flags: [bool; Song::N_TABLES],
397    /// flags on eqsin "from"
398    eq_flags: Vec<bool>,
399    /// flags on eqsin "to"
400    allocated_eqs: Vec<bool>,
401    /// flags on table "to"
402    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        // we are from an instrument using the same index and we're free,
440        // so we reserve the same
441        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        // try to find an already exisint Eq with same parameters
448        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        // out of bound instrument, dont bother or if already allocated
481        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(()); // Err(format!("Detected cycles in tables"));
487        }
488
489        self.seen_tables.insert(table_ix as u8);
490
491        // if the table contains NXT command, we need to track NXT'ed command
492        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        // ok so we are not tied to an instruments, we must
510        // allocate a slot for ourselves.
511        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        // out of bound instrument, dont bother or if already allocated
539        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            // return Err(format!("Detected cycles in instruments"));
546        }
547
548        self.seen_instruments.insert(instr_ix as u8);
549
550        let mut instr = from_song.instruments[instr_ix].clone();
551
552        // first we search the new EQ
553        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                // prior to version 5, only 32 Eqs were possible, no
558                // sense to try to keep them in sync with the instrument numbe
559                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            // finally update our Eq in our local copy
565            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            // horray we have a matching instrument, reuse it
575            Some(to_instr_ix) => self.instrument_mapping.mapping[instr_ix] = to_instr_ix as u8,
576            // no luck, allocate a fresh one
577            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    /// Find location in destination song for EQ and instruments
739    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        // eqs from "from" to "to"
790        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    /// Same as apply but the same song is the source and destination
815    pub fn renumber(&self, song: &mut Song) {
816        // move eq
817        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        // move instr
825        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        // move table
836        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        // remap eq in instr
850        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        // move phrases
863        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        // remap instr in phrases
871        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        // move chain
880        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        // remap chain
888        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    /// apply the reampping, cannot fail once mapping has been created
894    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        // move table
922        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}