carl_waagner/src/main.rs
2020-10-26 20:12:21 +01:00

245 lines
7.6 KiB
Rust

use serial::SerialPort;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Result as IoResult;
use walkdir::WalkDir;
fn main() {
let helptext = "Argument 1 is serial port (for example /dev/ttyACM0, Argument 2 is the directory with the music, no stars.";
let mut args = std::env::args_os();
let _selfname = args.next();
let serialport = args.next().expect(helptext);
let musicdir = args.next().expect(helptext);
println!("opening serial port");
let mut port = serial::open(&serialport).expect("could not open serial port");
port.reconfigure(&|settings| {
settings.set_baud_rate(serial::Baud115200)?;
settings.set_char_size(serial::Bits8);
settings.set_parity(serial::ParityNone);
settings.set_stop_bits(serial::Stop1);
settings.set_flow_control(serial::FlowNone);
Ok(())
})
.expect("could not configure port");
port.set_timeout(std::time::Duration::from_secs(30))
.expect("could not set timeout");
println!("serial port opened");
println!("skimming music dir");
let walker = WalkDir::new(musicdir).into_iter();
let walker = walker.filter_entry(|e| {
e.file_name()
.to_str()
.map(|s| !s.starts_with('.'))
.unwrap_or(true)
});
let mut music = Vec::new();
for entry in walker {
match entry {
Err(e) => eprintln!("error when reading music dir, continuing: {:?}", e),
Ok(entry) => {
if entry.file_type().is_file()
&& entry.path().extension() != Some(std::ffi::OsStr::new("jpg"))
{
music.push(Some(entry.path().to_owned()));
}
}
}
}
println!("music dir skimmed");
use rand::seq::SliceRandom;
use rand::SeedableRng;
let mut rng = rand::rngs::SmallRng::seed_from_u64(0);
music.shuffle(&mut rng);
println!("loaded {} \"songs\"", music.len());
println!("checked all songs");
println!("opening audio output");
let (_stream, stream_handle) = rodio::OutputStream::try_default()
.expect("could not open audio output, or could not find any");
let mut sink = rodio::Sink::try_new(&stream_handle).expect("could not create sink");
println!("audio output opened");
#[derive(Clone, Copy, Debug)]
struct Hyst {
max: usize,
prechoice: usize,
/// value when the choice was last changed
prechoice_val: usize,
/// value of last read
preval: usize,
/// number of times the same (but changed) value has been read
stability: usize,
}
let equality_range = 1;
let stability_tresh = 7;
let hyst = Hyst {
// wage has valid range 0-5000
max: 5000,
prechoice: 0,
prechoice_val: 0,
preval: 0,
stability: 0,
};
let musiclen = music.len();
let read_delay = std::time::Duration::from_millis(100);
// first remove noise
let iter = PortIter::new(port, read_delay)
.unwrap()
.map(|e| e.unwrap())
.scan(hyst, |hyst, el| {
// 8888 is empty screen, 69696969 is EEEE
if el == 8888 || el == 69_69_69_69 {
return Some(None)
}
hyst.max = hyst.max.max(el);
let absdiff = el.max(hyst.prechoice_val) - el.min(hyst.prechoice_val);
if absdiff > equality_range {
// build stability if value has not changed since last time
if hyst.preval == el {
hyst.stability += 1;
} else {
hyst.stability = 0;
}
hyst.preval = el;
// once we are stable
if hyst.stability > stability_tresh {
let choice = (el * (musiclen - 1)) / hyst.max;
// make sure the choice actually changed
if choice != hyst.prechoice {
hyst.prechoice = choice;
hyst.stability = 0;
hyst.prechoice_val = el;
println!("pushing choice {}", choice);
return Some(Some((el, hyst.max)));
}
}
}
Some(None)
});
// next: turn de-noised into a decoder
let iter = iter
// skip thing the denoiser denoised
.flatten()
.map(|(e, max)| {
let decoder;
// re-run until we hit actually valid music files
loop {
let base = (e * (music.len() - 1)) / max;
let mut delta: isize = 0;
let choicef;
let choice;
// search for non-deleted paths in a cyclic pattern around base choice
// (0, -1, 1, -2, 2...)
loop {
let modchoice = if delta > 0 {
let c = base + (delta as usize);
delta = -delta;
c
} else {
let c = base - (delta as usize);
delta = (-delta) + 1;
c
};
println!("trying {} next delta {}", modchoice, delta);
if let Some(Some(c)) = music.get(modchoice) {
choicef = c;
choice = modchoice;
break;
}
}
// try to decode
let c = || -> Result<rodio::Decoder<_>, ()> {
let f = std::fs::File::open(choicef).map_err(|_| ())?;
let rdr = BufReader::new(f);
rodio::Decoder::new(rdr).map_err(|_| ())
};
match (c)() {
Ok(d) => {
decoder = d;
println!("playing {:?}", choicef);
break;
}
Err(()) => {
eprintln!("{:?} was not actually music", choicef);
music[choice] = None;
}
}
}
decoder
});
// now just play the decoder
for decoder in iter {
let newsink = rodio::Sink::try_new(&stream_handle).expect("could not create sink");
newsink.append(decoder);
sink.stop();
sink = newsink;
}
}
#[derive(Debug)]
enum PortIterErr {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
impl From<std::io::Error> for PortIterErr {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl From<std::num::ParseIntError> for PortIterErr {
fn from(e: std::num::ParseIntError) -> Self {
Self::Parse(e)
}
}
struct PortIter<S: SerialPort> {
port: BufReader<S>,
delay: std::time::Duration,
}
impl<S: SerialPort> PortIter<S> {
fn new(mut p: S, delay: std::time::Duration) -> IoResult<Self> {
p.write_all(b"r")?;
Ok(Self {
port: BufReader::new(p),
delay,
})
}
}
impl<S: SerialPort> Iterator for PortIter<S> {
type Item = Result<usize, PortIterErr>;
fn next(&mut self) -> Option<Result<usize, PortIterErr>> {
let mut buf = String::new();
let r = self.port.read_line(&mut buf);
match r {
Ok(0) => return None,
Err(e) => return Some(Result::Err(e.into())),
Ok(_) => (),
};
let num = match buf.trim().parse::<usize>() {
Err(e) => return Some(Result::Err(e.into())),
Ok(num) => num,
};
match self.port.get_mut().write_all(b"r") {
Ok(_) => (),
Err(e) => return Some(Result::Err(e.into())),
}
std::thread::sleep(self.delay);
Some(Result::Ok(num))
}
}