m8_file_parser/instruments/
sampler.rs

1use crate::instruments::common::*;
2use crate::reader::*;
3use crate::version::*;
4use crate::writer::Writer;
5use crate::SEND_COMMAND_NAMES;
6use crate::SEND_COMMAND_NAMES_6_2;
7use array_concat::concat_arrays;
8use num_enum::IntoPrimitive;
9use num_enum::TryFromPrimitive;
10
11use super::dests;
12use super::CommandPack;
13
14#[repr(u8)]
15#[allow(non_camel_case_types)]
16#[derive(IntoPrimitive, TryFromPrimitive, PartialEq, Copy, Clone, Default, Debug)]
17pub enum SamplePlayMode {
18    #[default]
19    FWD,
20    REV,
21    FWDLOOP,
22    REVLOOP,
23    FWD_PP,
24    REV_PP,
25    OSC,
26    OSC_REV,
27    OSC_PP,
28    REPITCH,
29    REP_REV,
30    REP_PP,
31    REP_BPM,
32    BPM_REV,
33    BPM_PP,
34}
35
36#[derive(PartialEq, Debug, Clone)]
37pub struct Sampler {
38    pub number: u8,
39    pub name: String,
40    pub transpose: bool,
41    pub table_tick: u8,
42    pub synth_params: SynthParams,
43
44    pub sample_path: String,
45    pub play_mode: SamplePlayMode,
46    pub slice: u8,
47    pub start: u8,
48    pub loop_start: u8,
49    pub length: u8,
50    pub degrade: u8,
51}
52
53#[rustfmt::skip] // Keep constants with important order vertical for maintenance
54const SAMPLER_FX_COMMANDS_BASE : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT - 3] = [
55    "VOL",
56    "PIT",
57    "FIN",
58    "PLY",
59    "STA",
60    "LOP",
61    "LEN",
62    "DEG",
63    "FLT",
64    "CUT",
65    "RES",
66    "AMP",
67    "LIM",
68    "PAN",
69    "DRY"
70];
71
72#[rustfmt::skip] // Keep constants with important order vertical for maintenance
73const SAMPLER_FX_COMMANDS_EXTRA : [&'static str; 2] = [
74    "SLI",
75    "ERR"
76];
77
78const SAMPLER_FX_COMMANDS : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
79    concat_arrays!(SAMPLER_FX_COMMANDS_BASE , SEND_COMMAND_NAMES, SAMPLER_FX_COMMANDS_EXTRA );
80
81const SAMPLER_FX_COMMANDS_6_2 : [&'static str; CommandPack::BASE_INSTRUMENT_COMMAND_COUNT + 2] =
82    concat_arrays!(SAMPLER_FX_COMMANDS_BASE , SEND_COMMAND_NAMES_6_2, SAMPLER_FX_COMMANDS_EXTRA );
83
84#[rustfmt::skip] // Keep constants with important order vertical for maintenance
85const DESTINATIONS : [&'static str; 14] = [
86    dests::OFF,
87    dests::VOLUME,
88    dests::PITCH,
89
90    "LOOP ST",
91    "LENGTH",
92    dests::DEGRADE,
93    dests::CUTOFF,
94    dests::RES,
95    dests::AMP,
96    dests::PAN,
97    dests::MOD_AMT,
98    dests::MOD_RATE,
99    dests::MOD_BOTH,
100    dests::MOD_BINV,
101];
102
103impl Sampler {
104    pub const MOD_OFFSET: usize = 29;
105
106    pub fn command_name(&self, ver: Version) -> &'static [&'static str] {
107        if ver.at_least(6, 1) {
108            &SAMPLER_FX_COMMANDS_6_2
109        } else {
110            &SAMPLER_FX_COMMANDS
111        }
112    }
113
114    pub fn destination_names(&self, _ver: Version) -> &'static [&'static str] {
115        &DESTINATIONS
116    }
117
118    /// List of all the applyable filter types for the instrument
119    pub fn filter_types(&self, _ver: Version) -> &'static [&'static str] {
120        &COMMON_FILTER_TYPES
121    }
122
123    pub fn write(&self, ver: Version, w: &mut Writer) {
124        let pos = w.pos();
125        w.write_string(&self.name, 12);
126        w.write(TranspEq::from(ver, self.transpose, self.synth_params.associated_eq).into());
127        w.write(self.table_tick);
128        w.write(self.synth_params.volume);
129        w.write(self.synth_params.pitch);
130        w.write(self.synth_params.fine_tune);
131
132        w.write(self.play_mode.into());
133        w.write(self.slice);
134        w.write(self.start);
135        w.write(self.loop_start);
136        w.write(self.length);
137        w.write(self.degrade);
138
139        self.synth_params.write(ver, w, Sampler::MOD_OFFSET);
140
141        w.seek(pos + 0x56);
142        w.write_string(&self.sample_path, 128);
143    }
144
145    pub fn from_reader(
146        ver: Version,
147        reader: &mut Reader,
148        start_pos: usize,
149        number: u8,
150        version: Version,
151    ) -> M8Result<Self> {
152        let name = reader.read_string(12);
153
154        let transp_eq = TranspEq::from_version(ver, reader.read());
155        let table_tick = reader.read();
156        let volume = reader.read();
157        let pitch = reader.read();
158        let fine_tune = reader.read();
159
160        let play_mode = reader.read();
161        let slice = reader.read();
162        let start = reader.read();
163        let loop_start = reader.read();
164        let length = reader.read();
165        let degrade = reader.read();
166
167        let synth_params = if version.at_least(3, 0) {
168            SynthParams::from_reader3(
169                ver,
170                reader,
171                volume,
172                pitch,
173                fine_tune,
174                transp_eq.eq,
175                Sampler::MOD_OFFSET,
176            )?
177        } else {
178            SynthParams::from_reader2(reader, volume, pitch, fine_tune)?
179        };
180
181        reader.set_pos(start_pos + 0x57);
182        let sample_path = reader.read_string(128);
183
184        Ok(Sampler {
185            number,
186            name,
187            transpose: transp_eq.transpose,
188            table_tick,
189            synth_params,
190
191            sample_path,
192            play_mode: play_mode
193                .try_into()
194                .map_err(|_| ParseError(format!("Invalid play mode")))?,
195            slice,
196            start,
197            loop_start,
198            length,
199            degrade,
200        })
201    }
202}