m8_files/
remapper.rs

1use arr_macro::arr;
2use num_enum::{IntoPrimitive, TryFromPrimitive};
3
4use crate::{
5    songs::{Song, V4_1_OFFSETS, V4_OFFSETS},
6    Instrument, Version,
7};
8
9#[repr(u8)]
10#[allow(non_camel_case_types)]
11#[derive(IntoPrimitive, TryFromPrimitive, PartialEq, Copy, Clone, Default, Debug)]
12pub enum MoveKind {
13    #[default]
14    EQ,
15    INS,
16    PHR,
17    CHN,
18    TBL,
19}
20
21pub trait RemapperDescriptorBuilder {
22    fn moved(&mut self, kind: MoveKind, from: usize, to: usize);
23}
24
25fn make_mapping<const C: usize>(offset: u8) -> [u8; C] {
26    let mut arr = [0 as u8; C];
27    for i in 0..arr.len() {
28        arr[i] = i as u8 + offset;
29    }
30
31    arr
32}
33
34pub struct EqMapping {
35    /// Mapping from the "from" song eq index to the "to" song
36    /// eq index
37    pub mapping: Vec<u8>,
38
39    /// Eqs to be moved during the remapping
40    /// index in the "from" song
41    pub to_move: Vec<u8>,
42}
43
44impl EqMapping {
45    pub fn default_ver(ver: Version) -> EqMapping {
46        if ver.at_least(4, 1) {
47            EqMapping {
48                mapping: vec![0; V4_1_OFFSETS.instrument_eq_count],
49                to_move: vec![],
50            }
51        } else {
52            EqMapping {
53                mapping: vec![0; V4_OFFSETS.instrument_eq_count],
54                to_move: vec![],
55            }
56        }
57    }
58
59    pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
60        for ix in &self.to_move {
61            let ixu = *ix as usize;
62            builder.moved(MoveKind::EQ, ixu, self.mapping[ixu] as usize)
63        }
64    }
65
66    pub fn print(&self) -> String {
67        let mut acc = String::new();
68
69        for e in self.to_move.iter() {
70            let new_ix = self.mapping[*e as usize];
71            acc = format!("{acc} Eq {e} => {new_ix}\n");
72        }
73
74        acc
75    }
76}
77
78/// For every instrument, it's destination instrument
79pub struct InstrumentMapping {
80    /// Mapping from the "from" song instrument index to the "to"
81    /// song instrument index
82    pub mapping: [u8; Song::N_INSTRUMENTS],
83
84    /// Instruments to be moved during the remapping
85    /// index in the "from" song
86    pub to_move: Vec<u8>,
87}
88
89impl InstrumentMapping {
90    pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
91        for ix in &self.to_move {
92            let ixu = *ix as usize;
93            builder.moved(MoveKind::INS, ixu, self.mapping[ixu] as usize)
94        }
95    }
96
97    pub fn print(&self) -> String {
98        let mut acc = String::new();
99
100        for e in self.to_move.iter() {
101            let new_ix = self.mapping[*e as usize];
102            acc = format!("{acc} instr {e} => {new_ix}\n");
103        }
104
105        acc
106    }
107}
108
109impl Default for InstrumentMapping {
110    fn default() -> Self {
111        Self {
112            mapping: make_mapping(0),
113            to_move: vec![],
114        }
115    }
116}
117
118pub struct TableMapping {
119    /// Mapping from the "from" song index to the to
120    pub mapping: [u8; Song::N_TABLES],
121
122    /// Table to be moved during remapping
123    pub to_move: Vec<u8>,
124}
125
126impl TableMapping {
127    pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
128        for ix in &self.to_move {
129            let ixu = *ix as usize;
130            builder.moved(MoveKind::TBL, ixu, self.mapping[ixu] as usize)
131        }
132    }
133
134    pub fn remap_table(&mut self, from: u8, to: u8) {
135        self.mapping[from as usize] = to;
136        self.to_move.push(from);
137    }
138}
139
140impl Default for TableMapping {
141    fn default() -> Self {
142        Self {
143            mapping: make_mapping(Song::N_TABLES as u8),
144            to_move: vec![],
145        }
146    }
147}
148
149pub struct PhraseMapping {
150    /// Mapping from the "from" song phrase index to
151    /// the "to" phrase index
152    pub mapping: [u8; Song::N_PHRASES],
153
154    /// Phrases to be moved during the remapping
155    /// index in the "from" song
156    pub to_move: Vec<u8>,
157}
158
159impl PhraseMapping {
160    pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
161        for ix in &self.to_move {
162            let ixu = *ix as usize;
163            builder.moved(MoveKind::PHR, ixu, self.mapping[ixu] as usize)
164        }
165    }
166
167    pub fn print(&self) -> String {
168        let mut acc = String::new();
169
170        for e in self.to_move.iter() {
171            let new_ix = self.mapping[*e as usize];
172            acc = format!("{acc} phrase {e} => {new_ix}\n");
173        }
174
175        acc
176    }
177}
178
179impl Default for PhraseMapping {
180    fn default() -> Self {
181        Self {
182            mapping: make_mapping(0),
183            to_move: vec![],
184        }
185    }
186}
187
188pub struct ChainMapping {
189    pub mapping: [u8; Song::N_CHAINS],
190    pub to_move: Vec<u8>,
191}
192
193impl ChainMapping {
194    pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
195        for ix in &self.to_move {
196            let ixu = *ix as usize;
197            builder.moved(MoveKind::CHN, ixu, self.mapping[ixu] as usize)
198        }
199    }
200
201    pub fn print(&self) -> String {
202        let mut acc = String::new();
203
204        for e in self.to_move.iter() {
205            let new_ix = self.mapping[*e as usize];
206            acc = format!("{acc} chain {e} => {new_ix}\n");
207        }
208
209        acc
210    }
211}
212
213impl Default for ChainMapping {
214    fn default() -> Self {
215        Self {
216            mapping: make_mapping(0),
217            to_move: vec![],
218        }
219    }
220}
221
222pub struct Remapper {
223    pub eq_mapping: EqMapping,
224    pub instrument_mapping: InstrumentMapping,
225    pub table_mapping: TableMapping,
226    pub phrase_mapping: PhraseMapping,
227    pub chain_mapping: ChainMapping,
228}
229
230/// Iter on all instruments to find allocated Eqs
231fn find_referenced_eq(song: &Song) -> Vec<bool> {
232    // flags on eqs in "to"
233    let mut allocated_eqs = vec![false; song.eqs.len()];
234
235    for instr in &song.instruments {
236        match instr.equ() {
237            None => {}
238            Some(eq) => {
239                let equ = eq as usize;
240                if equ < allocated_eqs.len() {
241                    allocated_eqs[equ] = true
242                }
243            }
244        }
245    }
246
247    // TODO: track eqi command....
248
249    allocated_eqs
250}
251
252fn find_allocated_instruments(song: &Song) -> [bool; Song::N_INSTRUMENTS] {
253    let mut allocated_instr = arr![false; 128];
254
255    for (i, instr) in song.instruments.iter().enumerate() {
256        match instr {
257            Instrument::None => {}
258            _ => allocated_instr[i] = true,
259        }
260    }
261
262    allocated_instr
263}
264
265fn find_referenced_phrases(song: &Song) -> [bool; Song::N_PHRASES] {
266    let mut allocated_phrases = arr![false; 255];
267    for chain in &song.chains {
268        for step in &chain.steps {
269            let phrase = step.phrase as usize;
270            if phrase < Song::N_PHRASES {
271                allocated_phrases[phrase] = true;
272            }
273        }
274    }
275
276    for (phrase_id, phrase) in song.phrases.iter().enumerate() {
277        if !phrase.is_empty() {
278            allocated_phrases[phrase_id] = true;
279        }
280    }
281
282    allocated_phrases
283}
284
285fn find_referenced_chains(song: &Song) -> [bool; Song::N_CHAINS] {
286    let mut allocated_chains = arr![false; 255];
287    for chain in song.song.steps.iter() {
288        let chain = *chain as usize;
289        if chain < Song::N_CHAINS {
290            allocated_chains[chain] = true;
291        }
292    }
293
294    for (i, chain) in song.chains.iter().enumerate() {
295        if !chain.is_empty() {
296            allocated_chains[i] = true
297        }
298    }
299
300    allocated_chains
301}
302
303/// Try to allocate in the new song by keeping previous numbers
304fn try_allocate(allocation_state: &[bool], previous_id: u8) -> Option<usize> {
305    let prev = previous_id as usize;
306    if !allocation_state[prev] {
307        Some(prev)
308    } else {
309        match allocation_state[prev..].iter().position(|v| !v) {
310            // we take a slot above the existing one
311            Some(p) => Some(p + prev),
312            // nothing else worked, just try to find any free slot
313            None => allocation_state.iter().position(|v| !v),
314        }
315    }
316}
317
318impl Remapper {
319    pub fn default_ver(ver: Version) -> Self {
320        Self {
321            eq_mapping: EqMapping::default_ver(ver),
322            instrument_mapping: Default::default(),
323            table_mapping: Default::default(),
324            phrase_mapping: Default::default(),
325            chain_mapping: Default::default(),
326        }
327    }
328
329    pub fn describe<T: RemapperDescriptorBuilder>(&self, builder: &mut T) {
330        self.eq_mapping.describe(builder);
331        self.instrument_mapping.describe(builder);
332        self.table_mapping.describe(builder);
333        self.phrase_mapping.describe(builder);
334        self.chain_mapping.describe(builder);
335    }
336
337    pub fn out_chain(&self, chain_id: u8) -> u8 {
338        self.chain_mapping.mapping[chain_id as usize]
339    }
340
341    pub fn print(&self) -> String {
342        let eq = self.eq_mapping.print();
343        let instr = self.instrument_mapping.print();
344        let phrase = self.phrase_mapping.print();
345        let chain = self.chain_mapping.print();
346        format!("{eq}\n{instr}\n{phrase}\n{chain}")
347    }
348
349    fn allocate_chains<'a, IT>(
350        from_song: &Song,
351        to_song: &Song,
352        phrase_mapping: &PhraseMapping,
353        from_chains_ids: IT,
354    ) -> Result<ChainMapping, String>
355    where
356        IT: Iterator<Item = &'a u8>,
357    {
358        let mut seen_chain: [bool; Song::N_CHAINS] = arr![false; 255];
359        let mut allocated_chains = find_referenced_chains(to_song);
360        let mut mapping: [u8; Song::N_CHAINS] = make_mapping(0);
361        let mut to_move = vec![];
362
363        for chain_id in from_chains_ids {
364            let chain_id = *chain_id as usize;
365            if chain_id >= Song::N_CHAINS || seen_chain[chain_id] {
366                continue;
367            }
368
369            seen_chain[chain_id] = true;
370            let to_chain = from_song.chains[chain_id].map(phrase_mapping);
371
372            match to_song
373                .chains
374                .iter()
375                .position(|c| c.steps == to_chain.steps)
376            {
377                Some(c) => mapping[chain_id] = c as u8,
378                None => match try_allocate(&allocated_chains, chain_id as u8) {
379                    None => {
380                        return Err(format!(
381                            "No more available chain slots for chain {chain_id}"
382                        ))
383                    }
384                    Some(free_slot) => {
385                        allocated_chains[free_slot] = true;
386                        mapping[chain_id] = free_slot as u8;
387                        to_move.push(chain_id as u8);
388                    }
389                },
390            }
391        }
392
393        Ok(ChainMapping { mapping, to_move })
394    }
395
396    fn allocate_phrases<'a, IT>(
397        from_song: &Song,
398        to_song: &Song,
399        instrument_mapping: &InstrumentMapping,
400        from_chains_ids: IT,
401    ) -> Result<PhraseMapping, String>
402    where
403        IT: Iterator<Item = &'a u8>,
404    {
405        let mut allocated_phrases = find_referenced_phrases(to_song);
406
407        let mut seen_phrase: [bool; Song::N_PHRASES] = arr![false; 0xFF];
408        let mut phrase_mapping: [u8; Song::N_PHRASES] = arr![0 as u8; 0xFF];
409
410        let mut to_move = vec![];
411
412        for chain_id in from_chains_ids {
413            let from_chain = &from_song.chains[*chain_id as usize];
414
415            for chain_step in from_chain.steps.iter() {
416                let phrase_ix = chain_step.phrase as usize;
417
418                if phrase_ix >= Song::N_PHRASES || seen_phrase[phrase_ix] {
419                    continue;
420                }
421
422                seen_phrase[phrase_ix] = true;
423                let phrase = from_song.phrases[phrase_ix].map_instruments(&instrument_mapping);
424                match to_song.phrases.iter().position(|p| p.steps == phrase.steps) {
425                    Some(known) => phrase_mapping[phrase_ix] = known as u8,
426                    None => match try_allocate(&allocated_phrases, phrase_ix as u8) {
427                        None => {
428                            return Err(format!(
429                                "No more available phrase slots for phrase {phrase_ix}"
430                            ))
431                        }
432                        Some(slot) => {
433                            to_move.push(phrase_ix as u8);
434                            allocated_phrases[slot] = true;
435                            phrase_mapping[phrase_ix] = slot as u8;
436                        }
437                    },
438                }
439            }
440        }
441
442        Ok(PhraseMapping {
443            mapping: phrase_mapping,
444            to_move,
445        })
446    }
447
448    /// Find location in destination song for EQ and instruments
449    fn allocate_eq_and_instruments<'a, IT>(
450        from_song: &Song,
451        to_song: &Song,
452        from_chains_ids: IT,
453    ) -> Result<(EqMapping, InstrumentMapping), String>
454    where
455        IT: Iterator<Item = &'a u8>,
456    {
457        // flags on instruments in "from"
458        let mut instrument_flags: [bool; Song::N_INSTRUMENTS] = arr![false; 128];
459        // flags on eqsin "from"
460        let mut eq_flags = vec![false; from_song.eqs.len()];
461        // flags on eqs in "to"
462        let mut allocated_eqs = find_referenced_eq(to_song);
463        let mut allocated_instruments = find_allocated_instruments(to_song);
464        let mut instrument_mapping = InstrumentMapping::default();
465        // eqs from "from" to "to"
466        let mut eq_mapping = EqMapping::default_ver(to_song.version);
467
468        for chain_id in from_chains_ids {
469            let from_chain = &from_song.chains[*chain_id as usize];
470
471            for chain_step in &from_chain.steps {
472                let phrase_id = chain_step.phrase as usize;
473                if phrase_id >= Song::N_PHRASES {
474                    continue;
475                }
476
477                let phrase = &from_song.phrases[phrase_id];
478
479                for step in &phrase.steps {
480                    let instr_ix = step.instrument as usize;
481
482                    // out of bound instrument, dont bother or if already allocated
483                    if instr_ix >= Song::N_INSTRUMENTS || instrument_flags[instr_ix] {
484                        continue;
485                    }
486
487                    let mut instr = from_song.instruments[instr_ix].clone();
488
489                    // first we search the new EQ
490                    if let Some(equ) = instr.equ() {
491                        let equ = equ as usize;
492
493                        if equ < eq_flags.len() && !eq_flags[equ] {
494                            eq_flags[equ as usize] = true;
495                            let from_eq = &from_song.eqs[equ];
496                            // try to find an already exisint Eq with same parameters
497                            match to_song.eqs.iter().position(|to_eq| to_eq == from_eq) {
498                                Some(eq_idx) if (eq_idx as usize) < eq_mapping.mapping.len() => {
499                                    eq_mapping.mapping[equ] = eq_idx as u8
500                                }
501                                Some(_) | None => match try_allocate(&allocated_eqs, equ as u8) {
502                                    None => {
503                                        return Err(format!(
504                                            "No more available eqs for instrument {instr_ix}"
505                                        ))
506                                    }
507                                    Some(eq_slot) => {
508                                        allocated_eqs[eq_slot] = true;
509                                        eq_mapping.mapping[equ] = eq_slot as u8;
510                                        eq_mapping.to_move.push(equ as u8);
511                                    }
512                                },
513                            }
514
515                            // finally update our Eq in our local copy
516                            instr.set_eq(eq_mapping.mapping[equ]);
517                        }
518                    }
519
520                    instrument_flags[instr_ix] = true;
521                    match to_song.instruments.iter().position(|i| i == &instr) {
522                        // horray we have a matching instrument, reuse it
523                        Some(to_instr_ix) => {
524                            instrument_mapping.mapping[instr_ix] = to_instr_ix as u8
525                        }
526                        // no luck, allocate a fresh one
527                        None => match try_allocate(&allocated_instruments, instr_ix as u8) {
528                            None => {
529                                return Err(format!(
530                                    "No more available instrument slots for instrument {instr_ix}"
531                                ))
532                            }
533                            Some(to_instr_ix) => {
534                                instrument_mapping.mapping[instr_ix] = to_instr_ix as u8;
535                                allocated_instruments[to_instr_ix] = true;
536                                instrument_mapping.to_move.push(instr_ix as u8)
537                            }
538                        },
539                    }
540                }
541            }
542        }
543
544        Ok((eq_mapping, instrument_mapping))
545    }
546
547    pub fn create<'a, IT>(from_song: &Song, to_song: &Song, chains: IT) -> Result<Remapper, String>
548    where
549        IT: Iterator<Item = &'a u8>,
550    {
551        let chain_vec: Vec<u8> = chains.map(|v| *v).collect();
552
553        // eqs from "from" to "to"
554        let (eq_mapping, instrument_mapping) =
555            Remapper::allocate_eq_and_instruments(from_song, to_song, chain_vec.iter())?;
556        let phrase_mapping =
557            Remapper::allocate_phrases(from_song, to_song, &instrument_mapping, chain_vec.iter())?;
558
559        let chain_mapping =
560            Remapper::allocate_chains(from_song, to_song, &phrase_mapping, chain_vec.iter())?;
561
562        let table_mapping = Default::default();
563
564        Ok(Self {
565            eq_mapping,
566            instrument_mapping,
567            table_mapping,
568            phrase_mapping,
569            chain_mapping,
570        })
571    }
572
573    /// Same as apply but the same song is the source and destination
574    pub fn renumber(&self, song: &mut Song) {
575        // move eq
576        for equ in self.eq_mapping.to_move.iter() {
577            let equ = *equ as usize;
578            let to_index = self.eq_mapping.mapping[equ];
579            song.eqs[to_index as usize] = song.eqs[equ].clone();
580            song.eqs[equ].clear();
581        }
582
583        // move instr
584        for instr_id in self.instrument_mapping.to_move.iter() {
585            let instr_id = *instr_id as usize;
586            let to_index = self.instrument_mapping.mapping[instr_id] as usize;
587            let instr = song.instruments[instr_id].clone();
588
589            song.tables[to_index] = song.tables[instr_id].clone();
590            song.instruments[to_index] = instr;
591            song.instruments[instr_id] = Instrument::None;
592        }
593
594        // move table
595        for table_id in self.table_mapping.to_move.iter() {
596            let table_id = *table_id as usize;
597            let to_index = self.table_mapping.mapping[table_id] as usize;
598            let table = song.tables[table_id].clone();
599
600            song.tables[to_index] = table;
601            song.tables[table_id].clear();
602        }
603
604        // remap eq in instr
605        let eq_count = song.eq_count();
606        for instr_id in 0..Song::N_INSTRUMENTS {
607            let instr = &mut song.instruments[instr_id];
608
609            if let Some(eq) = instr.equ() {
610                let eq = eq as usize;
611                if eq < eq_count {
612                    instr.set_eq(self.eq_mapping.mapping[eq]);
613                }
614            }
615        }
616
617        // move phrases
618        for phrase_id in self.phrase_mapping.to_move.iter() {
619            let phrase_id = *phrase_id as usize;
620            let to_index = self.phrase_mapping.mapping[phrase_id];
621            song.phrases[to_index as usize] = song.phrases[phrase_id].clone();
622            song.phrases[phrase_id].clear()
623        }
624
625        // remap instr in phrases
626        for phrase_id in 0..Song::N_PHRASES {
627            song.phrases[phrase_id] =
628                song.phrases[phrase_id].map_instruments(&self.instrument_mapping);
629        }
630
631        // move chain
632        for chain_id in self.chain_mapping.to_move.iter() {
633            let chain_id = *chain_id as usize;
634            let to_index = self.chain_mapping.mapping[chain_id];
635            song.chains[to_index as usize] = song.chains[chain_id].clone();
636            song.chains[chain_id].clear();
637        }
638
639        // remap chain
640        for chain_id in 0..Song::N_CHAINS {
641            song.chains[chain_id] = song.chains[chain_id].map(&self.phrase_mapping)
642        }
643    }
644
645    /// apply the reampping, cannot fail once mapping has been created
646    pub fn apply(&self, from: &Song, to: &mut Song) {
647        for equ in self.eq_mapping.to_move.iter() {
648            let equ = *equ as usize;
649            let to_index = self.eq_mapping.mapping[equ];
650            to.eqs[to_index as usize] = from.eqs[equ].clone();
651        }
652
653        for instr_id in self.instrument_mapping.to_move.iter() {
654            let instr_id = *instr_id as usize;
655            let to_index = self.instrument_mapping.mapping[instr_id] as usize;
656            let mut instr = from.instruments[instr_id].clone();
657
658            if let Some(eq) = instr.equ() {
659                let eq = eq as usize;
660                if eq < to.eq_count() {
661                    instr.set_eq(self.eq_mapping.mapping[eq]);
662                }
663            }
664
665            to.tables[to_index] = from.tables[instr_id].clone();
666            to.instruments[to_index] = instr;
667        }
668
669        // move table
670        for table_id in self.table_mapping.to_move.iter() {
671            let table_id = *table_id as usize;
672            let to_index = self.table_mapping.mapping[table_id] as usize;
673            to.tables[to_index] = from.tables[table_id].clone();
674        }
675
676        for phrase_id in self.phrase_mapping.to_move.iter() {
677            let phrase_id = *phrase_id as usize;
678            let to_index = self.phrase_mapping.mapping[phrase_id];
679            to.phrases[to_index as usize] =
680                from.phrases[phrase_id].map_instruments(&self.instrument_mapping);
681        }
682
683        for chain_id in self.chain_mapping.to_move.iter() {
684            let chain_id = *chain_id as usize;
685            let to_index = self.chain_mapping.mapping[chain_id];
686            to.chains[to_index as usize] = from.chains[chain_id].map(&self.phrase_mapping);
687        }
688    }
689}