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}, 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    /// List all the command ID referencing an EQ as
37    /// value. Depend on the song version number.
38    pub eq_tracking_commands: Vec<u8>,
39
40    /// Mapping from the "from" song eq index to the "to" song
41    /// eq index
42    pub mapping: Vec<u8>,
43
44    /// Eqs to be moved during the remapping
45    /// index in the "from" song
46    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
88/// For every instrument, it's destination instrument
89pub struct InstrumentMapping {
90    /// List all the command ID referencing an instrument as
91    /// value. Depend on the song version number.
92    pub instrument_tracking_commands: Vec<u8>,
93
94    /// Mapping from the "from" song instrument index to the "to"
95    /// song instrument index
96    pub mapping: [u8; Song::N_INSTRUMENTS],
97
98    /// Instruments to be moved during the remapping
99    /// index in the "from" song
100    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    /// List all the command ID referencing a table as
133    /// value. Depend on the song version number.
134    pub table_tracking_commands: Vec<u8>,
135
136    /// Mapping from the "from" song index to the to
137    pub mapping: [u8; Song::N_TABLES],
138
139    /// Table to be moved during remapping
140    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    /// Mapping from the "from" song phrase index to
178    /// the "to" phrase index
179    pub mapping: [u8; Song::N_PHRASES],
180
181    /// Phrases to be moved during the remapping
182    /// index in the "from" song
183    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
257/// Iter on all instruments to find allocated Eqs
258fn find_referenced_eq(song: &Song) -> Vec<bool> {
259    // flags on eqs in "to"
260    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
338/// Try to allocate in the new song by keeping previous numbers
339fn 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            // we take a slot above the existing one
346            Some(p) => Some(p + prev),
347            // nothing else worked, just try to find any free slot
348            None => allocation_state.iter().position(|v| !v),
349        }
350    }
351}
352
353/// Try to allocate in the new song by keeping previous numbers
354fn 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        // nothing else worked, just try to find any free slot
364        None => allocation_state.iter().rev().position(|v| !v),
365    }
366}
367
368/// These commands track instruments and must include the
369/// target instrument to fully reconstruct the sound
370pub(crate) const INSTRUMENT_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["INS", "NXT"];
371
372/// This command an external "table", that also must be copied
373/// in order to properly reproduce the phrase
374pub(crate) const TABLE_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["TBX", "TBL"];
375
376/// These commands track EQs, that must be copied, yada yada.
377pub(crate) const EQ_TRACKING_COMMAND_NAMES: [&'static str; 2] = ["EQI", "EQM"];
378
379/// brief struture to hold structures used to allocate instruments
380struct InstrumentAllocatorState<'a> {
381    from_song: &'a Song,
382    to_song: &'a Song,
383
384    /// cycle detection, can happen if we follow
385    /// INS/NXT references
386    seen_instruments: HashSet<u8>,
387
388    /// cycle detection, can happen if we follow
389    /// INS/NXT/TBX references
390    seen_tables: HashSet<u8>,
391
392    /// flags on instruments in "from"
393    instrument_flags: [bool; Song::N_INSTRUMENTS],
394    /// flags on tables in "from"
395    table_flags: [bool; Song::N_TABLES],
396    /// flags on eqsin "from"
397    eq_flags: Vec<bool>,
398    /// flags on eqsin "to"
399    allocated_eqs: Vec<bool>,
400    /// flags on table "to"
401    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        // we are from an instrument using the same index and we're free,
439        // so we reserve the same
440        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        // try to find an already exisint Eq with same parameters
447        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        // out of bound instrument, dont bother or if already allocated
480        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(()); // Err(format!("Detected cycles in tables"));
486        }
487
488        self.seen_tables.insert(table_ix as u8);
489
490        // if the table contains NXT command, we need to track NXT'ed command
491        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        // ok so we are not tied to an instruments, we must
509        // allocate a slot for ourselves.
510        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        // out of bound instrument, dont bother or if already allocated
538        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            // return Err(format!("Detected cycles in instruments"));
545        }
546
547        self.seen_instruments.insert(instr_ix as u8);
548
549        let mut instr = from_song.instruments[instr_ix].clone();
550
551        // first we search the new EQ
552        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                // prior to version 5, only 32 Eqs were possible, no
557                // sense to try to keep them in sync with the instrument numbe
558                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            // 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}