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
use alloc::string::String;
use alloc::string::ToString;
use bincode::error::DecodeError;
use serde::Deserialize;
use serde_big_array::BigArray;
use super::serde_helper::deserialize_string_26;
use super::serde_helper::deserialize_string_4;
/// IT file header.
#[derive(Deserialize, Debug)]
#[repr(C)]
pub struct ItHeader {
/// Identifier ("IMPM").
#[serde(deserialize_with = "deserialize_string_4")]
id: String,
/// Song name
#[serde(deserialize_with = "deserialize_string_26")]
pub song_name: String,
/// Pattern information: Represents the number of rows per beat.
pub rows_per_beat: u8,
/// Pattern information: Represents the number of rows per measure.
pub rows_per_measure: u8,
/// Number of sequenced patterns in the song.
pub order_number: u16,
/// Number of instruments in the song.
pub instrument_number: u16,
/// Number of samples in the song.
pub sample_number: u16,
/// Number of patterns in the song.
pub pattern_number: u16,
/// ID of the tracker that made this file (1.0 to 2.17, encoded as hexadecimal).
pub created_with_tracker: u16,
/// Version of the tracker compatible with this file (1.0 to 2.17, encoded as hexadecimal).
pub compatible_with_tracker: u16,
/// Configuration flags (16 bits).
/// - Bit 0: Stereo if on, mono if off.
/// - Bit 1: Mixing (obsolete since version 1.04).
/// - Bit 2: Use instruments if on, use samples if off.
/// - Bit 3: Use linear slides if on, use Amiga slides if off.
/// - Bit 4: Use old effects if on, use IT effects if off.
/// - Bit 5: Link G effect with E and F memory if on.
/// - Bit 6: MIDI pitch controlled if on.
/// - Bit 7: Request Embedded MIDI Macros if on.
/// - Bits 8-15 are reserved.
pub flags: u16,
/// Special configuration flags (16 bits).
/// - Bit 0: Song message attached if on.
/// - Bit 1: Edit history embedded if on (Some versions of Schism tracker set this off even if there is embedded history).
/// - Bit 2: Highlight embedded if on.
/// - Bit 3: Embedded MIDI Macro.
/// - Bits 4-15 are reserved.
pub special_flags: u16,
/// Global volume of the song (0-128).
pub global_volume: u8,
/// Mixing volume of the song (0-128).
pub mix_volume: u8,
/// Starting tick speed of the song.
pub initial_speed: u8,
/// Starting beats per minute of the song.
pub initial_bpm: u8,
/// Panning separation between channels (0-128).
pub pan_separation: u8,
/// Pitch wheel depth for MIDI controllers.
pub pitch_wheel_depth: u8,
/// Length of the attached message.
pub message_length: u16,
/// Offset of the message in the file.
pub message_offset: u32,
/// Reserved field ("OMPT" for interpreted Mod Plug files).
#[serde(deserialize_with = "deserialize_string_4")]
pub reserved: String,
/// Initial pan of the channels
/// Each byte is a pan value (examples: 0 is left pan, 32 is center pan and 64 is right pan).
#[serde(with = "BigArray")]
pub initial_channel_pan: [u8; 64],
/// Initial volume of the channels
/// Each byte is a channel volume (0-64).
#[serde(with = "BigArray")]
pub initial_channel_volume: [u8; 64],
}
impl ItHeader {
pub fn load(data: &[u8]) -> Result<(Self, usize), DecodeError> {
let header_de =
bincode::serde::decode_from_slice::<ItHeader, _>(data, bincode::config::legacy());
match header_de {
Ok(header_de_ok) => {
if header_de_ok.0.is_it_header() {
Ok(header_de_ok)
} else {
Err(DecodeError::OtherString("Not an IT Module?".to_string()))
}
}
Err(e) => Err(e),
}
}
pub fn get_size() -> usize {
core::mem::size_of::<ItHeader>()
}
pub fn is_it_header(&self) -> bool {
self.id == "IMPM"
}
/// Mod Plug file?
pub fn is_ompt(&self) -> bool {
self.reserved == "OMPT"
}
pub fn is_post20(&self) -> bool {
(self.created_with_tracker >> 8) >= 2
}
// === Configuration Flags ===
/// Bit 0: Checks if Stereo is on.
pub fn is_stereo(&self) -> bool {
(self.flags & (1 << 0)) != 0
}
/// Bit 1: Checks if Mixing is on (obsolete).
pub fn is_mixing(&self) -> bool {
(self.flags & (1 << 1)) != 0
}
/// Bit 2: Checks if Instruments are used.
pub fn is_instruments_used(&self) -> bool {
(self.flags & (1 << 2)) != 0
}
/// Bit 3: Checks if Linear slides are used.
pub fn is_linear_slides(&self) -> bool {
(self.flags & (1 << 3)) != 0
}
/// Bit 4: Checks if Old effects are used.
pub fn is_old_effects(&self) -> bool {
(self.flags & (1 << 4)) != 0
}
/// Bit 5: Checks if G effect is linked with E and F memory.
pub fn is_g_linked_with_e_f(&self) -> bool {
(self.flags & (1 << 5)) != 0
}
/// Bit 6: Checks if MIDI pitch is controlled.
pub fn is_midi_pitch_controlled(&self) -> bool {
(self.flags & (1 << 6)) != 0
}
/// Bit 7: Checks if Embedded MIDI Macros are requested.
pub fn is_embedded_midi_macros(&self) -> bool {
(self.flags & (1 << 7)) != 0
}
// === Special Configuration Flags ===
/// Bit 0: Checks if Song message is attached.
pub fn is_song_message_attached(&self) -> bool {
(self.special_flags & (1 << 0)) != 0
}
/// Bit 1: Checks if Edit history is embedded.
pub fn is_edit_history_embedded(&self) -> bool {
(self.special_flags & (1 << 1)) != 0
}
/// Bit 2: Checks if Highlight is embedded.
pub fn is_highlight_embedded(&self) -> bool {
(self.special_flags & (1 << 2)) != 0
}
/// Bit 3: Checks if Embedded MIDI Macro is present.
pub fn is_embedded_midi_macro(&self) -> bool {
(self.special_flags & (1 << 3)) != 0
}
}