Skip to main content

snd/
lib.rs

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