245 lines
7.6 KiB
Rust
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))
|
|
}
|
|
}
|