1#![doc=include_str!("../README.md")]
2use std::fmt::Display;
3
4use binrw::{BinRead, BinReaderExt, binread};
5use resource_fork::Resource;
6
7#[binread]
8#[derive(Debug)]
9struct LongArray<T: BinRead + 'static>
10where
11 for<'a> <T as BinRead>::Args<'a>: Default + Clone,
12{
13 #[br(temp)]
14 count: u16,
15
16 #[br(count(count))]
17 pub(crate) items: Vec<T>,
18}
19
20fn long_list<T: BinRead>(input: LongArray<T>) -> Vec<T>
21where
22 for<'a> <T as BinRead>::Args<'a>: Default + Clone,
23{
24 input.items
25}
26
27#[derive(Debug, Resource)]
29#[resource(code = "snd ")]
30pub struct Sound {
31 pub channels: u16,
32 pub sample_rate: SampleRate,
33 pub bits_per_sample: u16,
34 pub format: AudioFormat,
35 pub samples: Vec<i8>,
36}
37
38#[derive(Debug, BinRead)]
39#[br(repr=u16, big)]
40pub enum Format {
41 Standard = 1,
42 Hypercard = 2,
43}
44
45#[derive(Debug, BinRead)]
46#[br(repr=u16, big)]
47pub enum ModifierType {
48 SampledSynth = 5,
49}
50
51#[derive(Debug, BinRead)]
52#[br(big)]
53pub struct Modifier {
54 pub modifier_type: ModifierType,
55 pub init: i32,
56}
57
58#[derive(Debug)]
59pub enum Op {
60 Null = 0,
61 Quiet = 3,
62 Flush = 4,
63 ReInit = 5,
64 Wait = 10,
65 Pause = 11,
66 Resume = 12,
67 CallBack = 13,
68 Sync = 14,
69 Available = 24,
70 Version = 25,
71 Volume = 46, GetVolume = 47, ClockComponent = 50, GetClockComponent = 51, ScheduledSound = 52, LinkSoundComponents = 53, Sound = 80,
78 Buffer = 81,
79 RateMultiplier = 86,
80 GetRateMultiplier = 87,
81}
82
83#[derive(Debug, thiserror::Error)]
84pub enum ConversionError {
85 #[error("Unknown opcode {0} encountered")]
86 UnknownOpcode(u16),
87 #[error("Unknown sample rate {0} encountered")]
88 UnknownSampleRate(u32),
89 #[error("Unknown format")]
90 UnsupportedFormat,
91 #[error("Channel count {0} is not supported")]
92 UnsupportedChannelCount(u16),
93 #[error("Sample format {0} is not supported")]
94 UnsupportedSampleFormat(AudioFormat),
95 #[error("Header format {0} is not supported yet")]
96 UnsupportedHeaderEncoding(HeaderEncoding),
97 #[error("Missing functionality")]
98 NotImplementedYet,
99}
100
101impl TryFrom<u16> for Op {
102 type Error = ConversionError;
103
104 fn try_from(value: u16) -> Result<Self, Self::Error> {
105 Ok(match value {
106 0 => Self::Null,
107 3 => Self::Quiet,
108 4 => Self::Flush,
109 5 => Self::ReInit,
110 10 => Self::Wait,
111 11 => Self::Pause,
112 12 => Self::Resume,
113 13 => Self::CallBack,
114 14 => Self::Sync,
115 24 => Self::Available,
116 25 => Self::Version,
117 46 => Self::Volume, 47 => Self::GetVolume, 50 => Self::ClockComponent, 51 => Self::GetClockComponent, 52 => Self::ScheduledSound, 53 => Self::LinkSoundComponents, 80 => Self::Sound,
124 81 => Self::Buffer,
125 86 => Self::RateMultiplier,
126 87 => Self::GetRateMultiplier,
127 value => return Err(ConversionError::UnknownOpcode(value)),
128 })
129 }
130}
131
132impl From<ConversionError> for binrw::Error {
133 fn from(val: ConversionError) -> Self {
134 binrw::Error::Custom {
135 pos: 0,
136 err: Box::new(val),
137 }
138 }
139}
140
141const DATA_OFFSET_FLAG: u16 = 0x8000;
142#[derive(Debug)]
143pub struct OpCode {
144 has_data_offset_flag: bool,
145 op: Op,
146}
147
148impl BinRead for OpCode {
149 type Args<'a> = ();
150
151 fn read_options<R: std::io::Read + std::io::Seek>(
152 reader: &mut R,
153 _endian: binrw::Endian,
154 _args: Self::Args<'_>,
155 ) -> binrw::BinResult<Self> {
156 let data: u16 = reader.read_be()?;
157 let has_flag = (data & DATA_OFFSET_FLAG) != 0;
158 let cmd = Op::try_from(data & !DATA_OFFSET_FLAG).unwrap();
159
160 Ok(OpCode {
161 has_data_offset_flag: has_flag,
162 op: cmd,
163 })
164 }
165}
166
167#[derive(Debug, BinRead)]
168#[br(big)]
169pub struct Command {
170 pub opcode: OpCode,
171 pub param1: i16,
172 pub param2: i32,
173}
174
175#[derive(Debug, BinRead)]
176#[br(big)]
177#[repr(u32)]
178pub enum SampleRate {
179 Rate48khz = 0xbb800000,
181 Rate44khz = 0xac440000,
183 Rate32khz = 0x7d000000,
185 Rate22050hz = 0x56220000,
187 Rate22khz = 0x56ee8ba3,
189 Rate16khz = 0x3e800000,
191 Rate11khz = 0x2b7745d1,
193 Rate11025hz = 0x2b110000,
195 Rate8khz = 0x1f400000,
197
198 Other(u32),
199}
200
201impl TryFrom<SampleRate> for u32 {
202 type Error = ConversionError;
203
204 fn try_from(val: SampleRate) -> Result<Self, Self::Error> {
205 Ok(match val {
206 SampleRate::Rate44khz => 44100,
207 SampleRate::Rate48khz => 48000,
208 SampleRate::Rate32khz => 32000,
209 SampleRate::Rate22050hz => 22050,
210 SampleRate::Rate22khz => 22254,
211 SampleRate::Rate16khz => 16000,
212 SampleRate::Rate11khz => 11127,
213 SampleRate::Rate11025hz => 11025,
214 SampleRate::Rate8khz => 8000,
215 SampleRate::Other(value) => {
216 return Err(ConversionError::UnknownSampleRate(value));
217 }
218 })
219 }
220}
221
222#[derive(Debug, BinRead)]
223#[br(big)]
224pub struct Header {
225 pub sample_ptr: u32,
227 pub length: u32,
229 pub sample_rate: SampleRate,
231 pub loop_start: u32,
233 pub loop_end: u32,
235 pub encoding: HeaderEncoding,
237 pub base_frequency: u8,
239}
240
241#[derive(Debug, BinRead)]
242#[br(repr=u8)]
243pub enum HeaderEncoding {
244 Standard = 0,
245 Compressed = 0xFE,
246 Extended = 0xFF,
247}
248impl Display for HeaderEncoding {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 match self {
251 HeaderEncoding::Standard => f.write_str("Standard"),
252 HeaderEncoding::Compressed => f.write_str("Compressed"),
253 HeaderEncoding::Extended => f.write_str("Extended"),
254 }
255 }
256}
257
258#[derive(Debug, Clone, Copy)]
259pub enum AudioFormat {
260 Uncompressed,
261 EightBitOffsetBinary,
262 SixteenBitBigEndian,
263 SixteenBitLittleEndian,
264}
265
266impl Display for AudioFormat {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268 match self {
269 AudioFormat::Uncompressed => f.write_str("Uncompressed"),
270 AudioFormat::EightBitOffsetBinary => f.write_str("8bit Offset Binary"),
271 AudioFormat::SixteenBitBigEndian => f.write_str("16-bit Big Endian"),
272 AudioFormat::SixteenBitLittleEndian => f.write_str("16-bit Little Endian"),
273 }
274 }
275}
276
277impl BinRead for Sound {
278 type Args<'a> = ();
279
280 fn read_options<R: std::io::Read + std::io::Seek>(
281 reader: &mut R,
282 _endian: binrw::Endian,
283 _args: Self::Args<'_>,
284 ) -> binrw::BinResult<Self> {
285 match reader.read_be()? {
286 Format::Standard => {
287 let modifiers: Vec<Modifier> = long_list(reader.read_be()?);
288 let commands: Vec<Command> = long_list(reader.read_be()?);
289 if modifiers.len() != 1
290 || commands.len() != 1
291 || !commands.first().unwrap().opcode.has_data_offset_flag
292 || !matches!(commands.first().unwrap().opcode.op, Op::Buffer | Op::Sound)
293 {
294 return Err(ConversionError::UnsupportedFormat.into());
295 }
296
297 let header: Header = reader.read_be().unwrap();
298 let (channel_count, packet_count, format) = match header.encoding {
299 HeaderEncoding::Extended => {
300 return Err(
301 ConversionError::UnsupportedHeaderEncoding(header.encoding).into()
302 );
303 }
304 HeaderEncoding::Compressed => {
305 return Err(
306 ConversionError::UnsupportedHeaderEncoding(header.encoding).into()
307 );
308 }
309 HeaderEncoding::Standard => {
310 (1, header.length, AudioFormat::EightBitOffsetBinary)
311 }
312 };
313
314 if channel_count != 1 {
315 return Err(ConversionError::UnsupportedChannelCount(channel_count).into());
316 }
317
318 if !matches!(format, AudioFormat::EightBitOffsetBinary) {
319 return Err(ConversionError::UnsupportedSampleFormat(format).into());
320 }
321
322 let mut samples = Vec::new();
323 for _ in 0..packet_count {
324 let sample: i8 = reader.read_be().unwrap();
325 samples.push(sample)
326 }
327
328 Ok(Self {
329 format,
330 samples,
331 channels: channel_count,
332 sample_rate: header.sample_rate,
333 bits_per_sample: if matches!(format, AudioFormat::EightBitOffsetBinary) {
334 8
335 } else {
336 16
337 },
338 })
339 }
340 Format::Hypercard => {
341 let _ref_count: i16 = reader.read_be()?;
342 let commands: Vec<Command> = long_list(reader.read_be()?);
343 if commands.len() != 1
344 || !commands.first().unwrap().opcode.has_data_offset_flag
345 || !matches!(commands.first().unwrap().opcode.op, Op::Buffer | Op::Sound)
346 {
347 return Err(ConversionError::NotImplementedYet.into());
348 }
349
350 let header: Header = reader.read_be().unwrap();
351 let (channel_count, packet_count, format) = match header.encoding {
352 HeaderEncoding::Extended => {
353 return Err(ConversionError::NotImplementedYet.into());
354 }
355 HeaderEncoding::Compressed => {
356 return Err(ConversionError::NotImplementedYet.into());
357 }
358 HeaderEncoding::Standard => {
359 (1, header.length, AudioFormat::EightBitOffsetBinary)
360 }
361 };
362
363 if channel_count != 1 {
364 return Err(ConversionError::NotImplementedYet.into());
365 }
366
367 if !matches!(format, AudioFormat::EightBitOffsetBinary) {
368 return Err(ConversionError::NotImplementedYet.into());
369 }
370
371 let mut samples = Vec::new();
372 for _ in 0..packet_count {
373 let sample: i8 = reader.read_be().unwrap();
374 samples.push(sample)
375 }
376
377 Ok(Self {
378 format,
379 samples,
380 channels: channel_count,
381 sample_rate: header.sample_rate,
382 bits_per_sample: if matches!(format, AudioFormat::EightBitOffsetBinary) {
383 8
384 } else {
385 16
386 },
387 })
388 }
389 }
390 }
391}