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
use core::time::Duration;
use core::num::NonZeroU32;
use core::fmt;
use core::ops::Range;
use chrono::NaiveDateTime;

pub mod flags;
pub mod effects;
mod parse;
mod player;

use flags::*;
use effects::*;

pub const MAX_DD_SAMPLES: usize = 32;

pub const MFP_TIMER_FREQUENCY: u32 = 2_457_600;
const DEFAULT_CHIPSET_FREQUENCY: u32 = 2_000_000;
const DEFAULT_FRAME_FREQUENCY: u16 = 50;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum YmVersion {
    Ym2,
    Ym3,
    Ym4,
    Ym5,
    Ym6,
}

impl YmVersion {
    /// The YM version identifier tag as a string (4 ascii characters).
    pub fn tag(self) -> &'static str {
        match self {
            YmVersion::Ym2 => "YM2!",
            YmVersion::Ym3 => "YM3!",
            YmVersion::Ym4 => "YM4!",
            YmVersion::Ym5 => "YM5!",
            YmVersion::Ym6 => "YM6!",
        }
    }
}

impl fmt::Display for YmVersion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.tag().fmt(f)
    }
}

/// The **YM** music file.
///
/// The YM-file consist of [YmFrame]s that represent the state of the AY/YM chipset registers and
/// contain additional information about special effects.
///
/// Depending on the [YmSong::version] special effects are being encoded differently.
#[derive(Debug, Clone)]
pub struct YmSong {
    /// YM-file version.
    pub version: YmVersion,
    /// The last modification timestamp of the YM-file from the LHA envelope.
    pub created: Option<NaiveDateTime>,
    /// The song attributes.
    pub song_attrs: SongAttributes,
    /// The song title or a file name.
    pub title: String,
    /// The song author.
    pub author: String,
    /// The comment.
    pub comments: String,
    /// The number of cycles per second of the AY/YM chipset clock.
    pub chipset_frequency: u32,
    /// The number of frames played each second.
    pub frame_frequency: u16,
    /// The loop frame index.
    pub loop_frame: u32,
    /// The AY/YM state frames.
    pub frames: Box<[YmFrame]>,
    /// `DIGI-DRUM` samples.
    pub dd_samples: Box<[u8]>,
    /// `DIGI-DRUM` sample end indexes in [YmSong::dd_samples].
    pub dd_samples_ends: [usize;MAX_DD_SAMPLES],
        cursor: usize,
        voice_effects: [(SidVoice, SinusSid, DigiDrum); 3],
        buzzer: SyncBuzzer,
}

/// This type represent the state of the AY/YM chipset registers and contain additional information
/// about special effects.
///
/// ```text
/// X - AY/YM register data.
/// S - Controls special effects.
/// P - Frequency pre-divisor.
/// F - Frequency divisor.
/// - - Unused.
/// ----------------------------------------------------------
///      b7 b6 b5 b4 b3 b2 b1 b0  Register description
///  0:  X  X  X  X  X  X  X  X   Fine period voice A
///  1:  S  S  S  S  X  X  X  X   Coarse period voice A
///  2:  X  X  X  X  X  X  X  X   Fine period voice B
///  3:  S  S  S  S  X  X  X  X   Coarse period voice B
///  4:  X  X  X  X  X  X  X  X   Fine period voice C
///  5:  -  -  -  -  X  X  X  X   Coarse period voice C
///  6:  P  P  P  X  X  X  X  X   Noise period
///  7:  X  X  X  X  X  X  X  X   Mixer control
///  8:  P  P  P  X  X  X  X  X   Volume voice A
///  9:  -  -  -  X  X  X  X  X   Volume voice B
/// 10:  -  -  -  X  X  X  X  X   Volume voice C
/// 11:  X  X  X  X  X  X  X  X   Envelope fine period
/// 12:  X  X  X  X  X  X  X  X   Envelope coarse period
/// 13:  x  x  x  x  X  X  X  X   Envelope shape
/// ----------------------------------------------------------
/// virtual registers to store extra data for special effects:
/// ----------------------------------------------------------
/// 14:  F  F  F  F  F  F  F  F   Frequency divisor for S in 1
/// 15:  F  F  F  F  F  F  F  F   Frequency divisor for S in 3
/// ```
///
/// The AY/YM `Envelope shape` register is modified only if the value of the 13 frame
/// register is not equal to `0xff`.
///
/// # Special effects
///
/// The frequency of a special effect is encoded as `(2457600 / P) / F`.
///
/// The divisor `F` is an unsigned 8-bit integer.
///
/// The pre-divisor `P` is encoded as:
/// 
/// |PPP|  pre-divisor value|
/// |-----------------------|
/// |000|         Timer off |
/// |001|                 4 |
/// |010|                10 |
/// |011|                16 |
/// |100|                50 |
/// |101|                64 |
/// |110|               100 |
/// |111|               200 |
/// 
/// * The pre-divisor `P` in register 6 matches effect controlled by register 1.
/// * The divisor `F` in register 14 matches effect controlled by register 1.
/// * The pre-divisor `P` in register 8 matches effect controlled by register 3.
/// * The divisor `F` in register 15 matches effect controlled by register 3.
///
/// If an effect is active, the additional data resides in `X` bits in the `Volume` register of
/// the relevant voice:
///
/// * For the [`SID voice`][SidVoice] and [`Sinus SID`][SinusSid] effects the 4 lowest `X` bits
///   determine the effect's volume.
/// * For the [`Sync Buzzer`][SyncBuzzer] the 4 lowest `X` bits determine the effect's `Envelope shape`.
/// * For the [`DIGI-DRUM`][DigiDrum] effect the 5 `X` bits determine the played sample number.
/// * The `DIGI-DRUM` sample plays until its end or if it's overridden by another effect.
/// * All other effects are active only for the duration of a single frame.
/// * When the `DIGI-DRUM` is active the volume register from the frame for the relevant voice is being
///   ignored and the relevant voice mixer tone and noise bits are forced to be set.
///
/// The control bits of special effects are interpreted differently depending on the YM-file verion.
///
/// ## YM6!
///
/// The `S` bits in registers 1 and 3 controls any two of the selectable effects:
/// ```text
/// b7 b6 b5 b4 
/// -  -  0  0  effect disabled
/// -  -  0  1  effect active on voice A
/// -  -  1  0  effect active on voice B
/// -  -  1  1  effect active on voice C
/// 0  0  -  -  select SID voice effect
/// 0  1  -  -  select DIGI-DRUM effect
/// 1  0  -  -  select Sinus SID effect
/// 1  1  -  -  select Sync Buzzer effect
/// ```
///
/// ## YM4!/YM5!
///
/// The `S` bits in register 1 controls the `SID voice` effect.
/// The `S` bits in register 3 controls the `DIGI-DRUM` effect.
/// ```text
/// b7 b6 b5 b4 
/// -  -  0  0  effect disabled
/// -  -  0  1  effect active on voice A
/// -  -  1  0  effect active on voice B
/// -  -  1  1  effect active on voice C
/// -  0  -  -  SID voice timer continues, ignored for DIGI-DRUM
/// -  1  -  -  SID voice timer restarts, ignored for DIGI-DRUM
///```
///
/// ## YM3!
///
/// There are no special effects in this version.
///
/// ## YM2!
///
/// Only the `DIGI-DRUM` effect is recognized in this format. It is being played on voice C, and
/// uses one of the 40 predefined samples.
///
/// * The effect starts when the highest bit (7) of the `Volume voice C` register (10) is 1.
/// * The sample number is taken from the lowest 7 bits of the `Volume voice C` register (10).
/// * The effect frequency is calculated by `(2457600 / 4) / X`, where `X` is the unsigned 8-bit
///   value stored in the register 12 of the frame.
/// * The value of AY/YM chipset registers 11, 12 and 13 is only written if the value of the
///   frame register 13 is not equal to `0xFF`.
/// * The register 12 of the AY/YM chipset is always being set to `0` in this format.
/// * The register 13 of the AY/YM chipset is always being set to `0x10` in this format.
#[derive(Default, Debug, Clone, Copy)]
pub struct YmFrame {
    /// Frame data.
    pub data: [u8;16]
}

impl YmSong {
    /// Creates a new instance of `YmSong` from the given `frames` and other meta data.
    pub fn new(
            version: YmVersion,
            frames: Box<[YmFrame]>,
            loop_frame: u32,
            title: String,
            created: Option<NaiveDateTime>
        ) -> YmSong
    {
        YmSong {
            version,
            created,
            song_attrs: SongAttributes::default(),
            title,
            author: String::new(),
            comments: String::new(),
            chipset_frequency: DEFAULT_CHIPSET_FREQUENCY,
            frame_frequency: DEFAULT_FRAME_FREQUENCY,
            loop_frame,
            frames,
            dd_samples: Box::new([]),
            dd_samples_ends: [0usize;MAX_DD_SAMPLES],
            cursor: 0,
            voice_effects: Default::default(),
            buzzer: Default::default()
        }
    }

    /// Returns `YmSong` with the `author` and `comments` set from the given arguments.
    pub fn with_meta(mut self, author: String, comments: String) -> YmSong {
        self.author = author;
        self.comments = comments;
        self
    }

    /// Returns `YmSong` with the `song_attrs`, `dd_samples` and `dd_samples_ends` set from the given arguments.
    pub fn with_samples(
            mut self,
            song_attrs: SongAttributes,
            dd_samples: Box<[u8]>,
            dd_samples_ends: [usize;MAX_DD_SAMPLES]
        ) -> YmSong
     {
        self.song_attrs = song_attrs;
        self.dd_samples = dd_samples;
        self.dd_samples_ends = dd_samples_ends;
        self
    }

    /// Returns `YmSong` with the `chipset_frequency` and `frame_frequency` set from the given arguments.
    pub fn with_frequency(mut self, chipset_frequency: u32, frame_frequency: u16) -> YmSong {
        self.chipset_frequency = chipset_frequency;
        self.frame_frequency = frame_frequency;
        self
    }

    /// Returns the song duration.
    pub fn song_duration(&self) -> Duration {
        let seconds = self.frames.len() as f64 / self.frame_frequency as f64;
        Duration::from_secs_f64(seconds)
    }

    /// Returns the AY/YM chipset clock frequency.
    #[inline]
    pub fn clock_frequency(&self) -> f32 {
        self.chipset_frequency as f32
    }

    /// Returns the number of AY/YM chipset clock cycles of a single music frame.
    pub fn frame_cycles(&self) -> f32 {
        self.clock_frequency() / self.frame_frequency as f32
    }

    /// Calculates the timer interval in clock cycles, from the given `divisor`.
    pub fn timer_interval(&self, divisor: NonZeroU32) -> f32 {
        let divisor = divisor.get() as f32;
        self.clock_frequency() as f32 * divisor / MFP_TIMER_FREQUENCY as f32
    }

    /// Returns the indicated sample data range in the [YmSong::dd_samples] for the given `sample`.
    ///
    /// # Panics
    /// Panics if `sample` value is not below [MAX_DD_SAMPLES].
    pub fn sample_data_range(&self, sample: usize) -> Range<usize> {
        let end = self.dd_samples_ends[sample];
        let start = match sample {
            0 => 0,
            index => self.dd_samples_ends[index - 1]
        };
        start..end
    }
}

impl YmFrame {
    /// Returns special effect control flags from the register 1.
    pub fn fx0(&self) -> FxCtrlFlags {
        FxCtrlFlags::from_bits_retain(self.data[1])
    }

    /// Returns special effect control flags from the register 3.
    pub fn fx1(&self) -> FxCtrlFlags {
        FxCtrlFlags::from_bits_retain(self.data[3])
    }

    /// Returns the value of the volume register for the indicated `chan`.
    ///
    /// The 2 lowest bits of `chan` indicate the voice channel:
    /// ```text
    ///  b1 b0 voice channel
    ///  0  0  A
    ///  0  1  B
    ///  1  0  C
    ///  1  1  invalid (panics in debug mode)
    /// ```
    pub fn vol(&self, chan: u8) -> u8 {
        let chan = chan & 3;
        debug_assert_ne!(chan, 3);
        self.data[(VOL_A_REG + chan) as usize] & 0x1f
    }

    /// Calculates the timer divsor for the special effect `fx0`.
    pub fn timer_divisor0(&self) -> Option<NonZeroU32> {
        calculate_timer_divisor(self.data[6], self.data[14])
    }

    /// Calculates the timer divsor for the special effect `fx1`.
    pub fn timer_divisor1(&self) -> Option<NonZeroU32> {
        calculate_timer_divisor(self.data[8], self.data[15])
    }
}

fn calculate_timer_divisor(prediv3: u8, div8: u8) -> Option<NonZeroU32> {
    let prediv = match prediv3 & 0b11100000 {
        0b00000000 => 0,
        0b00100000 => 4,
        0b01000000 => 10,
        0b01100000 => 16,
        0b10000000 => 50,
        0b10100000 => 64,
        0b11000000 => 100,
        0b11100000 => 200,
        _ => unreachable!()
    };
    NonZeroU32::new(prediv * div8 as u32)
}