1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
use std::{
    fmt::Debug,
    fs::File,
    io::prelude::*,
    path::{Path, PathBuf},
};

use logos::Logos;

use crate::{
    error::{Error, Result},
    sfz::{types::OpcodeMap, Group, Header, Opcode, Region, SfzToken},
};

/// Represents the SFZ instrument parsed
///
/// All units in the sfz format are in real-world values:
/// Frequencies are expressed in Hertz, pitches in cents,
/// amplitudes in percentage and volumes in decibels.
///
/// Notes are expressed in MIDI Note Numbers, or in note names according to the
/// International Pitch Notation (IPN) convention. According to this rules,
/// middle C in the keyboard is C4 and the MIDI note number 60.
///
#[derive(Debug)]
pub struct Instrument {
    /// The default opcodes for this instrument.
    pub global: OpcodeMap,

    /// The list of groups.
    ///
    /// The opcodes in a group overrides those in global, for the associated region.
    pub groups: Vec<Group>,

    /// The list of regions.
    ///
    /// The opcodes in a region overrides those in global and in its group.
    pub regions: Vec<Region>, // these opcodes override global, and their group ones

    /// The default path.
    // maybe make this later a: struct Control
    // https://sfzformat.com/headers/control
    pub default_path: PathBuf,

    last_header_created: Header,
}

// constructors:
// - new
// - from_file
// - from_sfz
//
// - add_opcode
// - add_opcode_global
// - add_opcode_to_group
// - add_opcode_to_region
// - groups
// - regions
// - regions_in
// - new_group
// - new_region
// - set_region_group
//
impl Instrument {
    /// Creates an empty Instrument
    ///
    pub fn new() -> Instrument {
        Instrument {
            global: OpcodeMap::new(),
            groups: Vec::<Group>::new(),
            regions: Vec::<Region>::new(),
            default_path: PathBuf::new(),
            last_header_created: Header::Global,
        }
    }

    /// Creates an Instrument via loading and parsing some SFZ code in a file
    ///
    pub fn from_file(sfz_path: &Path) -> Result<Self> {
        // open sfz file, and read it into sfz_text
        let mut sfz_file = File::open(&sfz_path)?;
        let mut sfz_text = String::new();
        sfz_file.read_to_string(&mut sfz_text)?;

        Self::from_sfz(&sfz_text, sfz_path.parent().unwrap())
    }

    /// Creates an Instrument via parsing some SFZ code in a string
    ///
    /// sfz_path would be the root location from where to find the samples
    /// and default_path opcode value is appended to it.
    ///
    pub fn from_sfz(sfz: &str, sfz_path: &Path) -> Result<Self> {
        #[cfg(debug)]
        println!("DEBUG: Instrument::from_sfz()\n-----------------------------");

        // Initializes an instrument for construction
        let mut instrument = Instrument {
            global: OpcodeMap::new(),
            groups: Vec::<Group>::new(),
            regions: Vec::<Region>::new(),
            default_path: sfz_path.to_path_buf(),
            last_header_created: Header::Global, // not used in this constructor
        };

        // parser loop status
        let mut status = InstrumentParsingStatus::init();

        // parser loop
        let lex = SfzToken::lexer(&sfz);
        for t in lex {
            match &t {
                SfzToken::Header(h) => {
                    match h {
                        Header::Group => {
                            #[cfg(debug)]
                            println!("\nFound a new group:");

                            status.new_group();
                            instrument.groups.push(Group::new());
                        }
                        Header::Region => {
                            #[cfg(debug)]
                            println!("\nFound a new region:");

                            status.new_region();

                            // FIXME: SPEC: discard an empty region (or without a sample)
                            instrument.regions.push(Region::new());
                        }
                        Header::Control => {
                            status.new_control();
                        }
                        Header::Global => {
                            status.new_global();
                        }
                        // TBD
                        Header::Curve => println!("\n<curve>"),
                        // TBD
                        Header::Effect => println!("\n<effect>"),
                        _ => (),
                    }
                }

                SfzToken::Opcode(o) => {
                    // an opcode for <global>
                    if status.is_header_global {
                        #[cfg(debug)]
                        println!("global OP {:?}", o);

                        instrument.add_opcode_global(o);

                    // an opcode for <control>
                    } else if status.is_header_control {
                        match o {
                            Opcode::default_path(p) => instrument.default_path.push(p),
                            _ => (),
                        }
                    } else {
                        // an opcode for the <region>
                        if status.are_regions_in_current_group() {
                            #[cfg(debug)]
                            println!(
                                "  - new region opcode: {:?} (g{} r{})",
                                o,
                                status.group_counter.unwrap(),
                                status.region_counter.unwrap()
                            );

                            instrument.add_opcode_to_region(o, status.region_counter.unwrap())?;
                            instrument.set_region_group(
                                status.region_counter.unwrap(),
                                status.group_counter,
                            )?;

                        // an opcode for the <group>
                        } else if status.are_groups() {
                            #[cfg(debug)]
                            println!("  - new group opcode: {:?}", o);

                            instrument.add_opcode_to_group(o, status.group_counter.unwrap())?;
                        } else {
                            unreachable!();
                        }
                    }
                }
                _ => (),
            }
        }

        #[cfg(debug)]
        println!("-----------------------------------\n");

        Ok(instrument)
    }

    /// Add an opcode, depending on context, to either the last created region,
    /// the last created group, or the global header (in that priority order)
    ///
    pub fn add_opcode(&mut self, opcode: &Opcode) -> Result<()> {
        match self.last_header_created {
            Header::Global => Ok(self.add_opcode_global(opcode)),
            Header::Group => self.add_opcode_to_group(opcode, self.groups() - 1),
            Header::Region => self.add_opcode_to_region(opcode, self.regions() - 1),
            _ => Err(Error::Generic),
        }
    }

    /// Add an opcode to the global header
    ///
    pub fn add_opcode_global(&mut self, opcode: &Opcode) {
        self.global.insert(opcode.str_name(), opcode.clone());
    }

    /// Add an opcode to a group
    pub fn add_opcode_to_group(&mut self, opcode: &Opcode, group: usize) -> Result<()> {
        if group >= self.groups() {
            return Err(Error::OutOfBounds(format![
                "Tried to add an Opcode to Group `{0}`, but the last group is `{1}`",
                group,
                self.groups() - 1
            ]));
        }
        self.groups[group].add_opcode(opcode);
        Ok(())
    }

    /// Add an opcode to a region
    pub fn add_opcode_to_region(&mut self, opcode: &Opcode, region: usize) -> Result<()> {
        #[cfg(debug)]
        println!(
            "    status.add_opcode_to_region() opcode: {:?} region: {}",
            opcode, region
        );

        if region >= self.regions() {
            return Err(Error::OutOfBounds(format![
                "Tried to add an Opcode to Region `{0}`, but the last region is `{1}`",
                region,
                self.regions() - 1
            ]));
        }
        self.regions[region].add_opcode(opcode);
        Ok(())
    }

    /// Get the number of groups
    pub fn groups(&self) -> usize {
        self.groups.len()
    }

    /// Get the number of regions
    pub fn regions(&self) -> usize {
        self.regions.len()
    }

    /// Get the number of regions in a group
    pub fn regions_in(&self, group: usize) -> Result<usize> {
        if group >= self.groups() {
            return Err(Error::OutOfBounds(format![
                "There's no group `{0}`, the last group is `{1}`",
                group,
                self.groups() - 1
            ]));
        }

        let mut count = 0;
        for region in self.regions.iter() {
            if region.group() == Some(group) {
                count += 1;
            }
        }
        Ok(count)
    }

    /// Create a new empty group header in the Instrument
    pub fn new_group(&mut self) {
        self.groups.push(Group::new());
        self.last_header_created = Header::Group;
    }

    /// Create a new empty region header in the Instrument
    ///
    /// The region gets associated with the last group created (if any)
    pub fn new_region(&mut self) {
        let num_groups = self.groups();

        if num_groups > 0 {
            self.regions.push(Region::with_group(num_groups - 1));
        } else {
            self.regions.push(Region::new());
        }

        self.last_header_created = Header::Region;

        // NOTE: needs a Opcode::sample in order to be valid
        // maybe if the previous region doesn't have sample code, delete it?
        // I'm not sure that's a good idea. Maybe a sample can be added later.
        //
        // Maybe add a method to check and delete all the regions without a sample
    }

    /// Set the group of a region (group can be None)
    // FIXME: rethink receiving Option for group
    pub fn set_region_group(&mut self, region: usize, group: Option<usize>) -> Result<()> {
        #[cfg(debug)]
        println!(
            "    status.set_region_group() region: {} group: {:?}",
            region, group
        );
        if region >= self.regions() {
            return Err(Error::OutOfBounds(format![
                "Tried set the group of Region `{0}`, but the last region is `{1}`",
                region,
                self.regions() - 1
            ]));
        }
        if let Some(g) = group {
            if g >= self.groups() {
                return Err(Error::OutOfBounds(
                    format!["Tried to set Region `{0}` to have the Group `{1}`, but the last group is `{2}`",
                    region, g, self.groups()-1]
                ));
            }
        }
        // FIXME
        self.regions[region].set_group(group);
        Ok(())
    }

    // TODO:
    pub fn groups_iter(&self) -> () {}
}

/// The current status of the parsing of the instrument
#[derive(Debug)]
struct InstrumentParsingStatus {
    is_header_control: bool,
    is_header_global: bool,
    // counts groups (first one is 0, valid as index)
    group_counter: Option<usize>,
    // counts regions inside last group (first one is 0, valid as index)
    region_counter_in_group: Option<usize>,
    // counts all regions
    region_counter: Option<usize>,
}
impl InstrumentParsingStatus {
    pub fn init() -> Self {
        Self {
            is_header_control: false,
            is_header_global: false,
            group_counter: None,
            region_counter_in_group: None,
            region_counter: None,
        }
    }

    /// A new group header appears
    ///
    pub fn new_group(&mut self) {
        #[cfg(debug)]
        println!("  status.new_group()");
        // ensure we are out of the <control> header
        self.is_header_control = false;
        // ensure we are out of the <global> header
        self.is_header_global = false;
        // ensure we reset the region counter for the current group
        self.region_reset_in_current_group();
        // increment the group counter
        self.group_increment();
    }

    /// A new region header appears
    ///
    pub fn new_region(&mut self) {
        #[cfg(debug)]
        println!("  status.new_region()");
        // ensure we are out of the <control> header
        self.is_header_control = false;
        // ensure we are out of the <global> header
        self.is_header_global = false;
        // increment the region counter for the current group
        self.region_increment();
    }

    /// A new control header appears
    ///
    /// There can only be one, and must appear
    /// before the first global, group & region headers.
    ///
    // TODO: if incorrectly placed, following opcodes should be ignored
    pub fn new_control(&mut self) {
        if !self.is_header_control
            && !self.is_header_global
            && self.group_counter == None
            && self.region_counter == None
        {
            // enter the <control> header
            self.is_header_control = true;
        }
    }

    /// A new global header appears
    ///
    /// There can only be one, and must appear
    /// before the first group & region headers.
    ///
    // TODO: if incorrectly placed, following opcodes should be ignored
    pub fn new_global(&mut self) {
        if !self.is_header_global && self.group_counter == None && self.region_counter == None {
            // ensure we are out of the <control> header
            self.is_header_control = false;
            // enter the <global> header
            self.is_header_global = true;
        }
    }

    /// Increments the region counter
    fn region_increment(&mut self) {
        match self.region_counter {
            Some(c) => self.region_counter = Some(c + 1),
            None => self.region_counter = Some(0),
        }
        match self.region_counter_in_group {
            Some(c) => self.region_counter_in_group = Some(c + 1),
            None => self.region_counter_in_group = Some(0),
        }
        #[cfg(debug)]
        println!(
            "  status.region_increment() g{}→rig{} (r{})",
            self.group_counter.unwrap(),
            self.region_counter_in_group.unwrap(),
            self.region_counter.unwrap()
        );
    }

    /// Resets the region counter for the group
    fn region_reset_in_current_group(&mut self) {
        #[cfg(debug)]
        println!("  status.region_reset_in_current_group()");
        self.region_counter_in_group = None;
    }

    /// Increments the group counter
    fn group_increment(&mut self) {
        match self.group_counter {
            Some(c) => self.group_counter = Some(c + 1),
            None => self.group_counter = Some(0),
        }
        #[cfg(debug)]
        println!(
            "  status.group_increment() (→g{})",
            self.group_counter.unwrap()
        );
    }

    /// Are there any groups already defined?
    pub fn are_groups(&self) -> bool {
        if self.group_counter == None {
            return false;
        }
        true
    }

    /// Are there any regions already defined for the current group?
    pub fn are_regions_in_current_group(&self) -> bool {
        if self.region_counter_in_group == None {
            return false;
        }
        true
    }
}