Skip to main content

snd/
lib.rs

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/// A sound resource
28#[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,              /*sound manager 3.0 or later only*/
72    GetVolume = 47,           /*sound manager 3.0 or later only*/
73    ClockComponent = 50,      /*sound manager 3.2.1 or later only*/
74    GetClockComponent = 51,   /*sound manager 3.2.1 or later only*/
75    ScheduledSound = 52,      /*sound manager 3.3 or later only*/
76    LinkSoundComponents = 53, /*sound manager 3.3 or later only*/
77    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,              /*sound manager 3.0 or later only*/
118            47 => Self::GetVolume,           /*sound manager 3.0 or later only*/
119            50 => Self::ClockComponent,      /*sound manager 3.2.1 or later only*/
120            51 => Self::GetClockComponent,   /*sound manager 3.2.1 or later only*/
121            52 => Self::ScheduledSound,      /*sound manager 3.3 or later only*/
122            53 => Self::LinkSoundComponents, /*sound manager 3.3 or later only*/
123            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    ///48000.00000 in fixed-point
180    Rate48khz = 0xbb800000,
181    ///44100.00000 in fixed-point
182    Rate44khz = 0xac440000,
183    ///32000.00000 in fixed-point
184    Rate32khz = 0x7d000000,
185    ///22050.00000 in fixed-point
186    Rate22050hz = 0x56220000,
187    /// 22254.54545 in fixed-point
188    Rate22khz = 0x56ee8ba3,
189    /// 16000.00000 in fixed-point
190    Rate16khz = 0x3e800000,
191    /// 11127.27273 in fixed-point
192    Rate11khz = 0x2b7745d1,
193    /// 11025.00000 in fixed-point
194    Rate11025hz = 0x2b110000,
195    /// 8000.00000 in fixed-point
196    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    /// if NIL then samples are in sampleArea
226    pub sample_ptr: u32,
227    /// length of sound in bytes
228    pub length: u32,
229    /// sample rate for this sound
230    pub sample_rate: SampleRate,
231    ///start of looping portion
232    pub loop_start: u32,
233    ///end of looping portion
234    pub loop_end: u32,
235    ///header encoding
236    pub encoding: HeaderEncoding,
237    ///baseFrequency value
238    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}