korg_syro/
lib.rs

1//!
2//! Rust bindings for the [KORG SYRO](https://github.com/korginc/volcasample) library for the Volca Sample.
3//!
4//!
5//! Files for use with the [reset](SyroStream::reset) method
6//! can be found here:
7//!
8//! [https://github.com/korginc/volcasample/tree/master/alldata](https://github.com/korginc/volcasample/tree/master/alldata)
9//!
10//! # Examples
11//!
12//! Add/erase samples
13//!
14//! ```no_run
15//! use std::fs::File;
16//! use std::io::BufWriter;
17//! use korg_syro::SyroStream;
18//! use wav;
19//!
20//! let mut syro_stream = SyroStream::default();
21//!
22//! syro_stream
23//!     .add_sample(0, vec![], 44100, None)?
24//!     .erase_sample(1)?;
25//! let data = syro_stream.generate()?;
26//!
27//! // PCM data, 2 channels, 44.1kHz sample rate, 16 bit per sample
28//! let header = wav::Header::new(1, 2, 44100, 16);
29//!
30//! let output = File::create("output.wav").unwrap();
31//! wav::write(header, &wav::BitDepth::Sixteen(data), &mut BufWriter::new(output));
32//! # Ok::<(), korg_syro::SyroError>(())
33//! ```
34//!
35//! Reset from .alldata file
36//!
37//! ```no_run
38//! use std::fs::File;
39//! use std::io::BufWriter;
40//! use korg_syro::SyroStream;
41//! use wav;
42//!
43//! let input_data = std::fs::read("all_sample_preset.alldata").unwrap();
44//! let data = SyroStream::reset(input_data, Some(16))?;
45//!
46//! // PCM data, 2 channels, 44.1kHz sample rate, 16 bit per sample
47//! let header = wav::Header::new(1, 2, 44100, 16);
48//!
49//! let output = File::create("output.wav").unwrap();
50//! wav::write(header, &wav::BitDepth::Sixteen(data), &mut BufWriter::new(output));
51//! # Ok::<(), korg_syro::SyroError>(())
52//! ```
53use std::mem::MaybeUninit;
54
55use array_init;
56use byteorder::{ByteOrder, LittleEndian};
57use korg_syro_sys as syro;
58use thiserror::Error;
59
60#[macro_use]
61mod macros;
62use macros::*;
63
64pub mod pattern;
65
66#[derive(Error, Debug, PartialEq)]
67pub enum SyroError {
68    #[error("invalid value {val} for '{name}', expected at least {} and at most {}", .lo, .hi)]
69    OutOfBounds {
70        val: u32,
71        name: &'static str,
72        lo: usize,
73        hi: usize,
74    },
75
76    #[error("empty stream, provide at least one sample or pattern")]
77    EmptyStream,
78
79    #[error("unhandled SyroStatus {status:?}")]
80    SyroStatus { status: syro::SyroStatus },
81}
82
83fn check_syro_status(status: syro::SyroStatus) -> Result<(), SyroError> {
84    match status {
85        syro::SyroStatus::Status_Success => Ok(()),
86        // TODO probably implement individual errors for these
87        // SyroStatus::Status_IllegalDataType
88        // SyroStatus::Status_IllegalData
89        // SyroStatus::Status_IllegalParameter
90        // SyroStatus::Status_OutOfRange_Number
91        // SyroStatus::Status_OutOfRange_Quality
92        // SyroStatus::Status_NotEnoughMemory
93        // SyroStatus::Status_InvalidHandle
94        // SyroStatus::Status_NoData
95        _ => Err(SyroError::SyroStatus { status }),
96    }
97}
98
99max_check!(sample_index, 99);
100bounds_check!(bit_depth, 8, 16);
101
102// Encapsulates ownership of SyroData
103struct SyroDataBundle {
104    #[allow(dead_code)]
105    data: Vec<u8>,
106    syro_data: syro::SyroData,
107}
108
109impl SyroDataBundle {
110    fn sample(
111        index: u32,
112        data_type: syro::SyroDataType,
113        mut data: Vec<u8>,
114        sample_rate: u32,
115        bit_depth: u32,
116    ) -> Self {
117        let syro_data = syro::SyroData {
118            DataType: data_type,
119            pData: data.as_mut_ptr(),
120            // the sample (0-99) or sequence pattern (0-9) number
121            Number: index,
122            // size of data to be converted (in bytes)
123            Size: data.len() as u32,
124            // The conversion bit depth. It can be set to 8-16. Seems unused when DataType = Sample_liner
125            Quality: bit_depth,
126            Fs: sample_rate,
127            SampleEndian: korg_syro_sys::Endian::LittleEndian,
128        };
129
130        Self { data, syro_data }
131    }
132
133    fn erase(index: u32) -> Self {
134        let syro_data = syro::SyroData {
135            DataType: syro::SyroDataType::DataType_Sample_Erase,
136            pData: 0 as *mut u8,
137            Number: index,
138            Size: 0,
139            Quality: 0,
140            Fs: 0,
141            SampleEndian: korg_syro_sys::Endian::LittleEndian,
142        };
143
144        Self {
145            data: vec![],
146            syro_data,
147        }
148    }
149
150    fn reset(mut data: Vec<u8>) -> Self {
151        let syro_data = syro::SyroData {
152            DataType: syro::SyroDataType::DataType_Sample_All,
153            pData: data.as_mut_ptr(),
154            Size: data.len() as u32,
155            Number: 0,
156            Quality: 0,
157            Fs: 44100,
158            SampleEndian: korg_syro_sys::Endian::LittleEndian,
159        };
160
161        Self { data, syro_data }
162    }
163
164    fn reset_compressed(mut data: Vec<u8>, bit_depth: u32) -> Self {
165        let syro_data = syro::SyroData {
166            DataType: syro::SyroDataType::DataType_Sample_AllCompress,
167            pData: data.as_mut_ptr(),
168            Size: data.len() as u32,
169            Number: 0,
170            Quality: bit_depth,
171            Fs: 44100,
172            SampleEndian: korg_syro_sys::Endian::LittleEndian,
173        };
174
175        Self { data, syro_data }
176    }
177
178    fn pattern(index: u32, mut data: Vec<u8>) -> Self {
179        let syro_data = syro::SyroData {
180            DataType: syro::SyroDataType::DataType_Pattern,
181            pData: data.as_mut_ptr(),
182            Number: index,
183            Size: data.len() as u32,
184            Quality: 0,
185            Fs: 0,
186            SampleEndian: korg_syro_sys::Endian::LittleEndian,
187        };
188
189        Self { data, syro_data }
190    }
191
192    fn data(&self) -> syro::SyroData {
193        self.syro_data
194    }
195}
196
197/// Builder struct for syrostream data.
198///
199/// Output from the [generate](SyroStream::generate) or
200/// [reset](SyroStream::reset) methods is uncompressed PCM
201/// data that can be used to write a .wav file.
202pub struct SyroStream {
203    samples: [Option<SyroDataBundle>; 100],
204    patterns: [Option<SyroDataBundle>; 10],
205}
206
207impl Default for SyroStream {
208    fn default() -> Self {
209        Self {
210            samples: array_init::array_init(|_| None),
211            patterns: array_init::array_init(|_| None),
212        }
213    }
214}
215
216fn convert_data(data: Vec<i16>) -> Vec<u8> {
217    let mut new_data: Vec<u8> = vec![0; data.len() * 2];
218    LittleEndian::write_i16_into(data.as_slice(), new_data.as_mut_slice());
219    new_data
220}
221
222impl SyroStream {
223    /// Generate stream from a .alldata file
224    pub fn reset(data: Vec<u8>, compression: Option<u32>) -> Result<Vec<i16>, SyroError> {
225        let mut syro_stream = Self::default();
226        let syro_data_bundle = match compression {
227            Some(bit_depth) => {
228                check_bit_depth(bit_depth as u8)?;
229                SyroDataBundle::reset_compressed(data, bit_depth)
230            }
231            None => SyroDataBundle::reset(data),
232        };
233        match syro_stream.samples.get_mut(0) {
234            Some(elem) => {
235                *elem = Some(syro_data_bundle);
236            }
237            None => unreachable!(),
238        }
239        syro_stream.generate()
240    }
241
242    /// Add a sample at the given index
243    ///
244    /// The index must be in the range 0-99. If compression is desired it has to
245    /// be in the range of 8-16 bits.
246    ///
247    ///_**Note**: there are currently no guards against using samples that are too large._
248    pub fn add_sample(
249        &mut self,
250        index: u32,
251        data: Vec<i16>,
252        sample_rate: u32,
253        compression: Option<u32>,
254    ) -> Result<&mut Self, SyroError> {
255        check_sample_index(index as u8)?;
256        let data = convert_data(data);
257        let bundle = match compression {
258            Some(bit_depth) => {
259                check_bit_depth(bit_depth as u8)?;
260                SyroDataBundle::sample(
261                    index,
262                    syro::SyroDataType::DataType_Sample_Compress,
263                    data,
264                    sample_rate,
265                    bit_depth,
266                )
267            }
268            None => SyroDataBundle::sample(
269                index,
270                syro::SyroDataType::DataType_Sample_Liner,
271                data,
272                sample_rate,
273                0,
274            ),
275        };
276        match self.samples.get_mut(index as usize) {
277            Some(elem) => *elem = Some(bundle),
278            None => panic!("Index out of bounds, checking must have failed"),
279        }
280        Ok(self)
281    }
282
283    /// Erase the sample at the given index
284    ///
285    /// The index must be in the range 0-99
286    pub fn erase_sample(&mut self, index: u32) -> Result<&mut Self, SyroError> {
287        check_sample_index(index as u8)?;
288        // TODO maybe refactor to remove the check function and just throw on None
289        match self.samples.get_mut(index as usize) {
290            Some(elem) => *elem = Some(SyroDataBundle::erase(index)),
291            None => panic!("Index out of bounds, checking must have failed"),
292        }
293        Ok(self)
294    }
295
296    /// Add a Pattern at the given index
297    ///
298    /// The index must be in the range 0-9
299    pub fn add_pattern(
300        &mut self,
301        index: usize,
302        pattern: pattern::Pattern,
303    ) -> Result<&mut Self, SyroError> {
304        pattern::check_pattern_index(index as u8)?;
305        let data = SyroDataBundle::pattern(index as u32, pattern.to_bytes());
306        if let Some(elem) = self.patterns.get_mut(index) {
307            *elem = Some(data);
308        }
309        Ok(self)
310    }
311
312    /// Generates the syro stream
313    ///
314    /// Ouptut is uncompressed PCM data
315    pub fn generate(self) -> Result<Vec<i16>, SyroError> {
316        let mut data: Vec<syro::SyroData> = Vec::with_capacity(110);
317
318        for sample in self.samples.iter() {
319            if let Some(bundle) = sample {
320                data.push(bundle.data());
321            }
322        }
323
324        for pattern in self.patterns.iter() {
325            if let Some(bundle) = pattern {
326                data.push(bundle.data());
327            }
328        }
329
330        if data.len() == 0 {
331            return Err(SyroError::EmptyStream);
332        }
333
334        // unsafe territory
335        let syro_stream = {
336            let (handle, num_frames) = init_syro_handle(data)?;
337            let result = generate_syro_stream(handle, num_frames);
338            free_syro_handle(handle)?;
339            result
340        }?;
341        Ok(syro_stream)
342    }
343}
344
345fn init_syro_handle(mut data: Vec<syro::SyroData>) -> Result<(syro::SyroHandle, u32), SyroError> {
346    let mut num_frames = 0;
347
348    let handle: syro::SyroHandle = unsafe {
349        let mut handle: MaybeUninit<syro::SyroHandle> = MaybeUninit::uninit();
350
351        let status = syro::SyroVolcaSample_Start(
352            handle.as_mut_ptr(),
353            data.as_mut_ptr(),
354            data.len() as i32,
355            0,
356            &mut num_frames,
357        );
358        check_syro_status(status)?;
359
360        handle.assume_init()
361    };
362
363    Ok((handle, num_frames))
364}
365
366fn free_syro_handle(handle: syro::SyroHandle) -> Result<(), SyroError> {
367    unsafe {
368        let status = korg_syro_sys::SyroVolcaSample_End(handle);
369        check_syro_status(status)
370    }
371}
372
373fn generate_syro_stream(handle: syro::SyroHandle, num_frames: u32) -> Result<Vec<i16>, SyroError> {
374    let mut left: i16 = 0;
375    let mut right: i16 = 0;
376    let mut buffer = Vec::with_capacity(num_frames as usize * 2);
377    for _ in 0..num_frames {
378        unsafe {
379            let status = syro::SyroVolcaSample_GetSample(handle, &mut left, &mut right);
380            if status == syro::SyroStatus::Status_NoData {
381                // TODO investigate why GetSample keeps returning NoData and if it's ok
382            } else {
383                check_syro_status(status)?;
384            }
385        }
386        buffer.push(left);
387        buffer.push(right);
388    }
389
390    Ok(buffer)
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use pattern::*;
397    use waver;
398
399    // 0.5 second sine wave
400    fn sine_wave() -> Vec<i16> {
401        let mut wf = waver::Waveform::<i16>::new(44100.0);
402        wf.superpose(waver::Wave {
403            frequency: 440.0,
404            ..Default::default()
405        })
406        .normalize_amplitudes();
407        wf.iter().take(22050).collect()
408    }
409
410    #[test]
411    fn out_of_bounds() {
412        let mut syro_stream = SyroStream::default();
413        let result = syro_stream.add_sample(100, vec![], 44100, None);
414        assert!(result.is_err());
415        assert_eq!(
416            result.err().unwrap(),
417            SyroError::OutOfBounds {
418                val: 100,
419                name: "sample_index".into(),
420                lo: 0,
421                hi: 99
422            }
423        );
424    }
425
426    #[test]
427    fn empty_syrostream() {
428        let result = SyroStream::default().generate();
429        assert!(result.is_err());
430        assert_eq!(result.err().unwrap(), SyroError::EmptyStream);
431    }
432
433    #[test]
434    fn basic() -> anyhow::Result<()> {
435        let input_data: Vec<i16> = sine_wave();
436
437        let mut syro_stream = SyroStream::default();
438
439        syro_stream.add_sample(0, input_data, 44100, None)?;
440        syro_stream.erase_sample(1)?;
441        syro_stream.add_pattern(0, Pattern::default())?;
442
443        let _output = syro_stream.generate()?;
444        Ok(())
445    }
446}