/
player.rs
142 lines (123 loc) · 4.08 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
137
138
139
140
141
142
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_sign_loss)]
use std::fs::File;
use std::io::{self, Read};
use std::process;
use arrayvec::ArrayVec;
use byteorder::{ByteOrder, NativeEndian};
use colored::Colorize;
use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait};
use cpal::{StreamData, UnknownTypeOutputBuffer};
use thiserror::Error;
use sonant::{errors::iter_sources, Error as SonantError, Song, Synth};
#[derive(Debug, Error)]
pub enum Error {
#[error("Missing filename argument")]
MissingFilename,
#[error("Sonant error")]
Sonant(#[from] SonantError),
#[error("I/O error")]
IO(#[from] io::Error),
}
fn main() {
handle_errors(player());
}
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 event_loop = host.event_loop();
let device = host
.default_output_device()
.expect("no output device available");
let mut supported_formats_range = device
.supported_output_formats()
.expect("error while querying formats");
let format = supported_formats_range
.next()
.expect("no supported format?!")
.with_max_sample_rate();
let stream_id = event_loop.build_output_stream(&device, &format).unwrap();
event_loop
.play_stream(stream_id)
.expect("failed to play_stream");
// Read the file
let mut file = File::open(filename)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
// 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 mut synth = Synth::new(&song, seed, format.sample_rate.0 as f32)
.flat_map(ArrayVec::from)
.peekable();
// cpal event loop; this is the actual audio player
event_loop.run(move |stream_id, stream_result| {
let stream_data = match stream_result {
Ok(data) => data,
Err(err) => {
eprintln!("an error occurred on stream {:?}: {}", stream_id, err);
return;
}
};
match stream_data {
StreamData::Output {
buffer: UnknownTypeOutputBuffer::U16(mut buffer),
} => {
let max = f32::from(i16::max_value());
for (elem, sample) in buffer.iter_mut().zip(synth.by_ref()) {
*elem = sample.mul_add(max, max).round() as u16;
}
if synth.peek() == None {
process::exit(0);
}
}
StreamData::Output {
buffer: UnknownTypeOutputBuffer::I16(mut buffer),
} => {
for (elem, sample) in buffer.iter_mut().zip(synth.by_ref()) {
*elem = (sample * f32::from(i16::max_value())).round() as i16;
}
if synth.peek() == None {
process::exit(0);
}
}
StreamData::Output {
buffer: UnknownTypeOutputBuffer::F32(mut buffer),
} => {
for (elem, sample) in buffer.iter_mut().zip(synth.by_ref()) {
*elem = sample;
}
if synth.peek() == None {
process::exit(0);
}
}
_ => (),
}
});
}
pub fn handle_errors<E>(result: Result<(), E>)
where
E: std::error::Error + 'static,
{
match result {
Err(e) => {
eprintln!("{} {}", "error:".red(), e);
for cause in iter_sources(&e) {
eprintln!("{} {}", "caused by:".bright_red(), cause);
}
process::exit(1);
}
Ok(()) => (),
};
}