diff --git a/Cargo.lock b/Cargo.lock index 0e4f5dc..bbf0dcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,16 +3,19 @@ version = 4 [[package]] -name = "autocfg" -version = "1.4.0" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] [[package]] -name = "base64" -version = "0.22.1" +name = "autocfg" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" @@ -155,7 +158,6 @@ dependencies = [ "js-sys", "log", "pm64", - "ron", "serde", "serde-wasm-bindgen", "wasm-bindgen", @@ -212,7 +214,9 @@ dependencies = [ "lazy_static", "log", "midly", + "regex", "rmp-serde", + "ron", "serde", "serde_derive", "typescript-type-def", @@ -266,6 +270,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rmp" version = "0.8.14" @@ -290,14 +323,15 @@ dependencies = [ [[package]] name = "ron" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "base64", "bitflags", + "once_cell", "serde", "serde_derive", + "typeid", "unicode-ident", ] @@ -324,10 +358,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -342,11 +377,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -388,6 +432,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typescript-type-def" version = "0.5.13" diff --git a/mamar-wasm-bridge/Cargo.toml b/mamar-wasm-bridge/Cargo.toml index 7457214..89120a4 100644 --- a/mamar-wasm-bridge/Cargo.toml +++ b/mamar-wasm-bridge/Cargo.toml @@ -17,7 +17,6 @@ console_error_panic_hook = "0.1.6" console_log = { version = "0.2", features = ["color"] } pm64 = { path = "../pm64", features = ["midly"] } -ron = "0.10.1" [dev-dependencies] wasm-bindgen-test = "0.3.13" diff --git a/mamar-wasm-bridge/src/lib.rs b/mamar-wasm-bridge/src/lib.rs index 2365d01..65ab830 100644 --- a/mamar-wasm-bridge/src/lib.rs +++ b/mamar-wasm-bridge/src/lib.rs @@ -41,7 +41,9 @@ pub fn bgm_decode(data: &[u8]) -> JsValue { Err(e) => to_js(&e.to_string()), } } else { - match ron::from_str::(&String::from_utf8_lossy(data)) { + let input_string = String::from_utf8_lossy(data); + + match Bgm::from_ron_string(&input_string) { Ok(bgm) => to_js(&bgm), Err(e) => to_js(&e.to_string()), } @@ -68,8 +70,9 @@ pub fn bgm_encode(bgm: &JsValue) -> JsValue { #[wasm_bindgen] pub fn ron_encode(bgm: &JsValue) -> JsValue { let bgm: Bgm = from_js(bgm); - match ron::ser::to_string_pretty(&bgm, ron::ser::PrettyConfig::new().indentor(" ").depth_limit(5)) { - Ok(ron) => ron.to_string().into(), + + match bgm.to_ron_string() { + Ok(ron) => ron.into(), Err(e) => e.to_string().into(), } } diff --git a/mamar-web/src/app/doc/Ruler.tsx b/mamar-web/src/app/doc/Ruler.tsx index bb3880c..095bd75 100644 --- a/mamar-web/src/app/doc/Ruler.tsx +++ b/mamar-web/src/app/doc/Ruler.tsx @@ -110,7 +110,7 @@ export function useSegmentLengths(): number[] { return segments.map(segment => { if (bgm && segment.type === "Subseg") { const master = bgm.trackLists[segment.trackList].tracks[0] - return master.commands.vec.reduce((totalDelay, event) => { + return master.commands.reduce((totalDelay, event) => { if (event.type === "Delay") { return totalDelay + event.value } else { diff --git a/mamar-web/src/app/doc/SegmentMap.tsx b/mamar-web/src/app/doc/SegmentMap.tsx index e37b114..231f254 100644 --- a/mamar-web/src/app/doc/SegmentMap.tsx +++ b/mamar-web/src/app/doc/SegmentMap.tsx @@ -8,9 +8,15 @@ import { TimeProvider } from "./timectx" import TrackControls from "../emu/TrackControls" import { useBgm, useDoc, useVariation } from "../store" import useSelection, { SelectionProvider } from "../util/hooks/useSelection" +import { Track } from "pm64-typegen" const TRACK_HEAD_WIDTH = 100 // Match with $trackHead-width +function hasParentTrack(track: Track): boolean { + const { polyphony } = track + return typeof polyphony === "object" && "ConditionalTakeover" in polyphony +} + function PianoRollThumbnail({ trackIndex, trackListIndex }: { trackIndex: number, trackListIndex: number }) { const [doc, dispatch] = useDoc() const [bgm] = useBgm() @@ -18,7 +24,7 @@ function PianoRollThumbnail({ trackIndex, trackListIndex }: { trackIndex: number const isSelected = doc?.panelContent.type === "tracker" && doc?.panelContent.trackList === trackListIndex && doc?.panelContent.track === trackIndex const nameId = useId() - if (!track || track.commands.vec.length === 0) { + if (!track || track.commands.length === 0) { return <> } else { const handlePress = (evt: any) => { @@ -41,7 +47,7 @@ function PianoRollThumbnail({ trackIndex, trackListIndex }: { trackIndex: number [styles.pianoRollThumbnail]: true, [styles.drumRegion]: track.isDrumTrack, [styles.disabledRegion]: track.isDisabled, - [styles.hasInterestingParentTrack]: track.parentTrackIdx !== 0, + [styles.hasInterestingParentTrack]: hasParentTrack(track), [styles.selected]: isSelected, })} onClick={handlePress} diff --git a/mamar-web/src/app/doc/SubsegDetails.tsx b/mamar-web/src/app/doc/SubsegDetails.tsx index dbdee07..49081ae 100644 --- a/mamar-web/src/app/doc/SubsegDetails.tsx +++ b/mamar-web/src/app/doc/SubsegDetails.tsx @@ -6,33 +6,13 @@ import styles from "./SubsegDetails.module.scss" import Tracker from "./Tracker" import { useBgm } from "../store" +import { Polyphony } from "pm64-typegen" export interface Props { trackListId: number trackIndex: number } -function polyphonicIdxToVoiceCount(polyphonicIdx: number): number { - // player->unk_22A - switch (polyphonicIdx) { - case 1: return 1 - case 5: return 2 - case 6: return 3 - case 7: return 4 - default: return 0 - } -} - -function voiceCountToPolyphonicIdx(voiceCount: number): number { - switch (voiceCount) { - case 1: return 1 - case 2: return 5 - case 3: return 6 - case 4: return 7 - default: return 0 - } -} - export default function SubsegDetails({ trackListId, trackIndex }: Props) { const hid = useId() const [bgm, dispatch] = useBgm() @@ -65,8 +45,8 @@ export default function SubsegDetails({ trackListId, trackIndex }: Props) { dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, isDisabled: !v })}>Enabled {trackIndex !== 0 ? <> dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, isDrumTrack })}>Percussion - { - dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, polyphonicIdx, parentTrackIdx }) + { + dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, polyphony }) }} /> : <>} @@ -77,7 +57,7 @@ export default function SubsegDetails({ trackListId, trackIndex }: Props) { } -function PolyphonyForm({ polyphonicIdx, parentTrackIdx, maxParentTrackIdx, onChange }: { polyphonicIdx: number, parentTrackIdx: number, maxParentTrackIdx: number, onChange: (polyphonicIdx: number, parentTrackIdx: number) => void }) { +function PolyphonyForm({ polyphony, maxParentTrackIdx, onChange }: { polyphony: Polyphony, maxParentTrackIdx: number, onChange: (polyphony: Polyphony) => void }) { const polyphonyLabel = Polyphony @@ -119,9 +99,18 @@ function PolyphonyForm({ polyphonicIdx, parentTrackIdx, maxParentTrackIdx, onCha - let state = "manual" - if (polyphonicIdx === 255) state = "auto" - if (parentTrackIdx !== 0) state = "parent" + let state: "auto" | "manual" | "parent" = "manual" + let parentTrackIdx = 0 + let voiceCount = 1 + + if (polyphony === "Automatic") { + state = "auto" + } else if ("ConditionalTakeover" in polyphony) { + state = "parent" + parentTrackIdx = polyphony.ConditionalTakeover.parent + } else if ("Manual" in polyphony) { + voiceCount = polyphony.Manual.voices + } // Store parent track between states, e.g. so that parent->manual->parent doesn't forget which track it was const [recentNonZeroParentTrackIdx, setRecentNonZeroParentTrackIdx] = useState(1) @@ -136,11 +125,19 @@ function PolyphonyForm({ polyphonicIdx, parentTrackIdx, maxParentTrackIdx, onCha onChange={newState => { if (state === newState) return if (newState === "auto") { - onChange(255, 0) + onChange("Automatic") } else if (newState === "manual") { - onChange(1, 0) + onChange({ + Manual: { + voices: 1, + }, + }) } else if (newState === "parent") { - onChange(polyphonicIdx, Math.min(recentNonZeroParentTrackIdx, maxParentTrackIdx)) + onChange({ + ConditionalTakeover: { + parent: Math.min(recentNonZeroParentTrackIdx, maxParentTrackIdx), + }, + }) } }} > @@ -150,11 +147,15 @@ function PolyphonyForm({ polyphonicIdx, parentTrackIdx, maxParentTrackIdx, onCha {state === "manual" ? onChange(voiceCountToPolyphonicIdx(voiceCount), 0)} + onChange={voices => onChange({ + Manual: { + voices, + }, + })} /> : <>} {state === "parent" ? onChange(polyphonicIdx, parentTrackIdx)} + onChange={parentTrackIdx => onChange({ + ConditionalTakeover: { + parent: parentTrackIdx, + }, + })} /> : <>} } diff --git a/mamar-web/src/app/doc/Tracker.tsx b/mamar-web/src/app/doc/Tracker.tsx index ed41f6b..7108691 100644 --- a/mamar-web/src/app/doc/Tracker.tsx +++ b/mamar-web/src/app/doc/Tracker.tsx @@ -532,7 +532,7 @@ function CommandList({ width, height }: { const [bgm] = useBgm() const { trackListId, trackIndex } = useContext(trackListCtx)! const track = bgm?.trackLists[trackListId]?.tracks[trackIndex] - const commands = track?.commands?.vec ?? [] + const commands: pm64.Event[] = track?.commands ?? [] return { - const { commands } = draft.trackLists[action.trackList].tracks[action.track] - commands.vec = arrayMove(commands.vec, action.oldIndex, action.newIndex) + const track = draft.trackLists[action.trackList].tracks[action.track] + track.commands = arrayMove(track.commands, action.oldIndex, action.newIndex) }) case "update_track_command": return produce(bgm, draft => { - const { commands } = draft.trackLists[action.trackList].tracks[action.track] - for (let i = 0; i < commands.vec.length; i++) { - if (commands.vec[i].id === action.command.id) { - commands.vec[i] = action.command + const track = draft.trackLists[action.trackList].tracks[action.track] + for (let i = 0; i < track.commands.length; i++) { + if (track.commands[i].id === action.command.id) { + track.commands[i] = action.command } } }) case "delete_track_command": return produce(bgm, draft => { - const { commands } = draft.trackLists[action.trackList].tracks[action.track] - commands.vec.splice(action.index, 1) + const track = draft.trackLists[action.trackList].tracks[action.track] + track.commands.splice(action.index, 1) }) case "modify_track_settings": return produce(bgm, draft => { @@ -97,15 +96,12 @@ export function bgmReducer(bgm: Bgm, action: BgmAction): Bgm { if (action.isDisabled !== undefined) { track.isDisabled = action.isDisabled } - if (action.polyphonicIdx !== undefined) { - track.polyphonicIdx = action.polyphonicIdx + if (action.polyphony !== undefined) { + track.polyphony = action.polyphony } if (action.isDrumTrack !== undefined) { track.isDrumTrack = action.isDrumTrack } - if (action.parentTrackIdx !== undefined) { - track.parentTrackIdx = action.parentTrackIdx - } }) case "update_instrument": return produce(bgm, draft => { diff --git a/pm64/Cargo.toml b/pm64/Cargo.toml index 4626766..660ebc9 100644 --- a/pm64/Cargo.toml +++ b/pm64/Cargo.toml @@ -8,7 +8,9 @@ edition = "2018" lazy_static = "1" log = "0.4" midly = { version = "0.5", optional = true, default-features = false, features = ["std", "alloc"] } +regex = "1.12.2" rmp-serde = "1.3.0" +ron = "0.12.0" serde = "1" serde_derive = "1" typescript-type-def = "0.5" diff --git a/pm64/src/bgm/cmd.rs b/pm64/src/bgm/cmd.rs index 41e7a07..f74cee6 100644 --- a/pm64/src/bgm/cmd.rs +++ b/pm64/src/bgm/cmd.rs @@ -33,6 +33,7 @@ use crate::id::{gen_id, Id}; /// [HashMap](std::collections::HashMap) (i.e. a dictionary) with relative-time keys and [Command] values (for /// example, you cannot lookup by vector index, because ordering is undefined between [Delay] partitions). #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, TypeDef)] +#[serde(transparent)] pub struct CommandSeq { /// List of [Command]s in time order. Sets of [Command]s at the same time value have undefined ordering, so this /// is not a public field. Similarly, [CommandSeq] does not [Deref](std::ops::Deref) to the [Vec] it wraps diff --git a/pm64/src/bgm/de.rs b/pm64/src/bgm/de.rs index 3b76c86..205e44c 100644 --- a/pm64/src/bgm/de.rs +++ b/pm64/src/bgm/de.rs @@ -357,9 +357,8 @@ impl Track { Ok(Self { name: Default::default(), is_disabled, - polyphonic_idx, + polyphony: Polyphony::from_raw(polyphonic_idx, parent_track_idx), is_drum_track, - parent_track_idx, commands: if commands_offset == 0 { CommandSeq::with_capacity(0) } else { diff --git a/pm64/src/bgm/en.rs b/pm64/src/bgm/en.rs index 09414d0..23e75bc 100644 --- a/pm64/src/bgm/en.rs +++ b/pm64/src/bgm/en.rs @@ -227,9 +227,8 @@ impl Bgm { Track { name, is_disabled, - polyphonic_idx, + polyphony, is_drum_track, - parent_track_idx, commands, .. }, @@ -245,15 +244,17 @@ impl Bgm { } f.write_u16_be(0)?; // Replaced later if !null - let polyphonic_idx = match *polyphonic_idx { - POLYPHONIC_IDX_AUTO_MAMAR => polyphony_to_polyphonic_idx(commands.max_polyphony()), - n => n, + let polyphonic_idx = match *polyphony { + Polyphony::Automatic => polyphony_to_polyphonic_idx(commands.max_polyphony()), + Polyphony::Manual { voices } => polyphony_to_polyphonic_idx(voices), + Polyphony::ConditionalTakeover { parent: _ } => 1, + Polyphony::Other { priority } => priority, }; let flags = (*is_disabled as u16) << 8 | (polyphonic_idx as u16) << 0xD | if *is_drum_track { 0x0080 } else { 0 } - | (*parent_track_idx as u16) << 9; + | (polyphony.to_parent_idx() as u16) << 9; f.write_u16_be(flags)?; } diff --git a/pm64/src/bgm/midi.rs b/pm64/src/bgm/midi.rs index ae29b50..17bbb56 100644 --- a/pm64/src/bgm/midi.rs +++ b/pm64/src/bgm/midi.rs @@ -206,9 +206,8 @@ fn midi_track_to_bgm_track( let mut track = Track { name: "".into(), is_disabled: false, - polyphonic_idx: POLYPHONIC_IDX_AUTO_MAMAR, + polyphony: Polyphony::Automatic, is_drum_track: false, - parent_track_idx: 0, commands: CommandSeq::new(), }; @@ -424,14 +423,14 @@ fn midi_track_to_bgm_track( // Poly[phonic] mode on/off 126 => { if value == 0 { - track.polyphonic_idx = 0; + track.polyphony = Polyphony::Manual { voices: 0 }; } else { - track.polyphonic_idx = 1; + track.polyphony = Polyphony::Manual { voices: 1 }; } } // Poly[phonic] mode on 127 => { - track.polyphonic_idx = 2; + track.polyphony = Polyphony::Automatic; } _ => { pitch_range_cmd_state = PitchRangeCommandState::None; diff --git a/pm64/src/bgm/mod.rs b/pm64/src/bgm/mod.rs index 46140a5..145c2e0 100644 --- a/pm64/src/bgm/mod.rs +++ b/pm64/src/bgm/mod.rs @@ -10,9 +10,10 @@ pub mod mamar; #[cfg(feature = "midly")] pub mod midi; -use std::collections::HashMap; use std::ops::Range; +use std::{collections::HashMap, sync::LazyLock}; +use regex::Regex; use serde_derive::{Deserialize, Serialize}; use typescript_type_def::TypeDef; @@ -50,6 +51,9 @@ pub struct Bgm { #[derive(Clone, Default, Copy, PartialEq, Eq, Debug)] pub struct NoSpace; +static RON_COMMAND_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r#"name:\s*".*"(?:.|\n)*?commands:\s*(\[(?:.|\n)*?\])"#).unwrap()); + impl Bgm { pub fn new() -> Bgm { Bgm { @@ -151,6 +155,66 @@ impl Bgm { ) } } + + pub fn from_ron_string(input_string: &str) -> Result { + let matches: Vec> = RON_COMMAND_REGEX.captures_iter(&input_string).collect(); + let mut modified_input_string = input_string.to_string(); + + for captures in matches.into_iter().rev() { + let commands_group = captures.get(1).unwrap(); + let (_, [commands_str]) = captures.extract(); + + let commands: Vec = ron::de::from_str(commands_str)?; + let events: Vec = commands + .into_iter() + .map(|command| Event { id: gen_id(), command }) + .collect(); + + modified_input_string.replace_range( + commands_group.start()..commands_group.end(), + &ron::ser::to_string(&events)?, + ); + } + + Ok(ron::from_str::(&modified_input_string)?) + } + + pub fn to_ron_string(&self) -> Result { + let pretty_config = ron::ser::PrettyConfig::new().indentor(" ").depth_limit(5); + let bgm_string = ron::ser::to_string_pretty(&self, pretty_config.clone())?.to_string(); + + // strip commands of id field + let matches: Vec> = RON_COMMAND_REGEX.captures_iter(&bgm_string).collect(); + let mut modified_bgm_string = bgm_string.clone(); + + for captures in matches.into_iter().rev() { + let events_group = captures.get(1).unwrap(); + let (_, [events_str]) = captures.extract(); + + let events: Vec = ron::de::from_str(events_str)?; + if events.is_empty() { + continue; + } + + let commands: Vec = events.into_iter().map(|event| event.command).collect(); + + let mut commands_string = "[\n".to_owned(); + for line in ron::ser::to_string_pretty(&commands, pretty_config.clone().depth_limit(1))? + .lines() + .skip(1) + { + commands_string.push_str(" "); + commands_string.push_str(line); + commands_string.push('\n'); + } + modified_bgm_string.replace_range( + events_group.start()..events_group.end(), + &commands_string[..commands_string.len() - 1], + ); + } + + Ok(modified_bgm_string) + } } #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeDef)] @@ -238,24 +302,18 @@ pub struct Track { #[serde(default)] pub name: String, pub is_disabled: bool, - pub polyphonic_idx: u8, + pub polyphony: Polyphony, pub is_drum_track: bool, - /// Track index plus one. 0 means no parent. See au_bgm_load_subsegment - pub parent_track_idx: u8, pub commands: CommandSeq, } -/// 255 is never used in vanilla songs so we can repurpose it to mean 'please calculate a good polyphonic_idx for me' -pub const POLYPHONIC_IDX_AUTO_MAMAR: u8 = 255; - impl Default for Track { fn default() -> Self { Self { name: "".to_owned(), is_disabled: true, - polyphonic_idx: POLYPHONIC_IDX_AUTO_MAMAR, + polyphony: Polyphony::Automatic, is_drum_track: false, - parent_track_idx: 0, commands: Default::default(), } } @@ -266,14 +324,66 @@ impl Track { Track { name: self.name.clone(), is_disabled: self.is_disabled, - polyphonic_idx: self.polyphonic_idx, + polyphony: self.polyphony, is_drum_track: self.is_drum_track, - parent_track_idx: self.parent_track_idx, commands: self.commands.split_at(time), } } } +/// 255 is never used in vanilla songs so we can repurpose it to mean 'please calculate a good polyphonic_idx for me' +pub const POLYPHONIC_IDX_AUTO_MAMAR: u8 = 255; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, TypeDef)] +pub enum Polyphony { + Automatic, + Manual { voices: u8 }, + ConditionalTakeover { parent: u8 }, + Other { priority: u8 }, +} + +impl Polyphony { + pub fn from_raw(raw_priority: u8, raw_parent_track_idx: u8) -> Self { + if raw_parent_track_idx > 0 { + return Self::ConditionalTakeover { + parent: raw_parent_track_idx - 1, + }; + } + + match raw_priority { + 0 => Self::Manual { voices: 0 }, + 1 => Self::Manual { voices: 1 }, + 5 => Self::Manual { voices: 2 }, + 6 => Self::Manual { voices: 3 }, + 7 => Self::Manual { voices: 4 }, + POLYPHONIC_IDX_AUTO_MAMAR => Self::Automatic, + _ => Self::Other { priority: raw_priority }, + } + } + + pub fn to_polyphonic_idx(self) -> u8 { + match self { + Polyphony::Automatic => POLYPHONIC_IDX_AUTO_MAMAR, + Polyphony::Manual { voices } => match voices { + 1 => 1, + 2 => 5, + 3 => 6, + 4 => 7, + _ => 0, + }, + Polyphony::ConditionalTakeover { parent: _ } => 1, + Polyphony::Other { priority } => priority, + } + } + + pub fn to_parent_idx(self) -> u8 { + match self { + Polyphony::Automatic | Polyphony::Manual { .. } | Polyphony::Other { .. } => 0, + Polyphony::ConditionalTakeover { parent } => parent + 1, + } + } +} + #[derive(Clone, Default, PartialEq, Eq, Debug, Serialize, Deserialize, TypeDef)] #[serde(rename_all = "camelCase")] #[serde(default)]