-
Notifications
You must be signed in to change notification settings - Fork 1
/
player.rs
136 lines (112 loc) · 4.28 KB
/
player.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#![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)?;
// cpal boilerplate
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();
// Read the file
let data = std::fs::read(filename)?;
// Create a seed for the PRNG
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]),
);
// Load a sonant song and create a synth
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>,
{
// Create a channel so the audio thread can request samples
let (audio_tx, audio_rx) = mpsc::sync_channel(10);
// Create the audio thread
let stream = device.build_output_stream(
config,
move |buffer: &mut [T], _: &cpal::OutputCallbackInfo| {
let (tx, rx) = mpsc::sync_channel(1);
// Request samples from the main thread
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);
// Send samples requested by the audio thread.
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(())
}