#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::cast_precision_loss)]
#![forbid(unsafe_code)]
use arrayvec::ArrayVec;
use byteorder::{ByteOrder, NativeEndian};
use colored::Colorize;
use cpal::traits::{DeviceTrait as _, HostTrait as _, StreamTrait as _};
use cpal::{FromSample, SampleFormat, SizedSample};
use error_iter::ErrorIter as _;
use sonant::{Song, Synth};
use std::process::ExitCode;
use std::sync::mpsc;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Missing filename argument")]
MissingFilename,
#[error("Sonant error")]
Sonant(#[from] sonant::Error),
#[error("I/O error")]
Io(#[from] std::io::Error),
#[error("CPAL audio stream config error")]
AudioConfig(#[from] cpal::DefaultStreamConfigError),
#[error("CPAL audio stream builder error")]
AudioStream(#[from] cpal::BuildStreamError),
#[error("CPAL audio stream play error")]
AudioPlay(#[from] cpal::PlayStreamError),
}
fn main() -> ExitCode {
match player() {
Err(e) => {
eprintln!("{} {}", "error:".red(), e);
for cause in e.sources().skip(1) {
eprintln!("{} {}", "caused by:".bright_red(), cause);
}
ExitCode::FAILURE
}
Ok(()) => ExitCode::SUCCESS,
}
}
fn player() -> Result<(), Error> {
let mut args = std::env::args().skip(1);
let filename = args.next().ok_or(Error::MissingFilename)?;
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("no output device available");
let stream_config = device.default_output_config()?;
let sample_rate = stream_config.sample_rate();
let format = stream_config.sample_format();
let data = std::fs::read(filename)?;
let mut seed = [0_u8; 16];
getrandom::getrandom(&mut seed).expect("failed to getrandom");
let seed = (
NativeEndian::read_u64(&seed[0..8]),
NativeEndian::read_u64(&seed[8..16]),
);
let song = Song::from_slice(&data)?;
let synth = Synth::new(&song, seed, sample_rate.0 as f32);
match format {
SampleFormat::I8 => run::<i8>(&device, &stream_config.into(), synth),
SampleFormat::I16 => run::<i16>(&device, &stream_config.into(), synth),
SampleFormat::I32 => run::<i32>(&device, &stream_config.into(), synth),
SampleFormat::I64 => run::<i64>(&device, &stream_config.into(), synth),
SampleFormat::U8 => run::<u8>(&device, &stream_config.into(), synth),
SampleFormat::U16 => run::<u16>(&device, &stream_config.into(), synth),
SampleFormat::U32 => run::<u32>(&device, &stream_config.into(), synth),
SampleFormat::U64 => run::<u64>(&device, &stream_config.into(), synth),
SampleFormat::F32 => run::<f32>(&device, &stream_config.into(), synth),
SampleFormat::F64 => run::<f64>(&device, &stream_config.into(), synth),
sample_format => panic!("Unsupported sample format '{}'", sample_format),
}
}
fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig, synth: Synth) -> Result<(), Error>
where
T: SizedSample + FromSample<f32>,
{
let (audio_tx, audio_rx) = mpsc::sync_channel(10);
let stream = device.build_output_stream(
config,
move |buffer: &mut [T], _: &cpal::OutputCallbackInfo| {
let (tx, rx) = mpsc::sync_channel(1);
audio_tx.send((buffer.len(), tx)).unwrap();
let samples = rx.recv().unwrap();
for (elem, sample) in buffer.iter_mut().zip(samples) {
*elem = T::from_sample(sample);
}
},
|err| eprintln!("an error occurred on stream: {err}"),
None,
)?;
stream.play()?;
let mut synth = synth.flat_map(ArrayVec::from);
while let Ok((len, tx)) = audio_rx.recv() {
let samples = synth.by_ref().take(len).collect::<Vec<_>>();
let done = samples.is_empty();
tx.send(samples).unwrap();
if done {
break;
}
}
Ok(())
}