simple list menu
This commit is contained in:
parent
fc714de35c
commit
ad4a91e105
4 changed files with 259 additions and 79 deletions
182
src/main.rs
182
src/main.rs
|
@ -1,97 +1,121 @@
|
|||
use rodio::source::{Repeat, Source};
|
||||
use rodio::{Decoder, OutputStream};
|
||||
use std::fs::File;
|
||||
use std::time::Duration;
|
||||
use crossterm::{
|
||||
cursor::MoveTo,
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{self, Clear, ClearType},
|
||||
};
|
||||
use rodio::{source::Source, OutputStream, OutputStreamHandle};
|
||||
use std::{
|
||||
io::stdout,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
type Volume = f32;
|
||||
mod sound;
|
||||
use sound::Snoud;
|
||||
|
||||
fn main() {
|
||||
let files: Vec<String> = vec![
|
||||
"sound/rain.mp3".into(),
|
||||
"sound/thunder.mp3".into(),
|
||||
"sound/wind.mp3".into(),
|
||||
];
|
||||
let source = Snoud::new(&files);
|
||||
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
|
||||
stream_handle.play_raw(source.convert_samples()).unwrap();
|
||||
|
||||
// let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
// sink.append(source);
|
||||
// sink.play();
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
}
|
||||
App::new().run();
|
||||
}
|
||||
|
||||
struct Snoud {
|
||||
channels: Vec<SoundChannel>,
|
||||
sample_rate: u32,
|
||||
struct UIChannel {
|
||||
name: String,
|
||||
volume: f32,
|
||||
internal_volume: Arc<Mutex<f32>>,
|
||||
}
|
||||
|
||||
struct SoundChannel {
|
||||
source: Repeat<Decoder<File>>,
|
||||
paused: bool,
|
||||
volume: Volume,
|
||||
struct App {
|
||||
channels: Vec<UIChannel>,
|
||||
selected_channel: usize,
|
||||
_stream: OutputStream,
|
||||
stream_handle: OutputStreamHandle,
|
||||
quit: bool,
|
||||
}
|
||||
|
||||
impl SoundChannel {
|
||||
fn new(name: &String) -> Self {
|
||||
let file = File::open(name).expect("File not found");
|
||||
let source = Decoder::new(file)
|
||||
.expect("Could not decode file")
|
||||
.repeat_infinite();
|
||||
impl App {
|
||||
fn new() -> Self {
|
||||
let (_stream, stream_handle) = OutputStream::try_default() //
|
||||
.expect("Failed to create output stream");
|
||||
Self {
|
||||
source,
|
||||
paused: false,
|
||||
volume: 1.0,
|
||||
channels: Vec::new(),
|
||||
selected_channel: 0,
|
||||
_stream,
|
||||
stream_handle,
|
||||
quit: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Snoud {
|
||||
type Item = i16;
|
||||
fn run(&mut self) {
|
||||
// TODO scan directory instead
|
||||
let files: Vec<String> = vec![
|
||||
"sound/rain.mp3".into(),
|
||||
"sound/thunder.mp3".into(),
|
||||
"sound/wind.mp3".into(),
|
||||
];
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut out: Self::Item = 0;
|
||||
for c in &mut self.channels {
|
||||
if c.paused {
|
||||
continue;
|
||||
let mut snoud = Snoud::new();
|
||||
for filename in files {
|
||||
let internal_volume = snoud.add_channel(&filename);
|
||||
let ui_channel = UIChannel {
|
||||
name: filename,
|
||||
volume: 1.0,
|
||||
internal_volume,
|
||||
};
|
||||
self.channels.push(ui_channel);
|
||||
}
|
||||
|
||||
self.stream_handle
|
||||
.play_raw(snoud.convert_samples())
|
||||
.unwrap();
|
||||
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
while !self.quit {
|
||||
self.render();
|
||||
self.input();
|
||||
}
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
execute!(stdout(), Clear(ClearType::All), MoveTo(0, 0)).unwrap();
|
||||
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
println!("Snoud!!");
|
||||
println!("--------");
|
||||
for (i, channel) in self.channels.iter().enumerate() {
|
||||
println!(
|
||||
"{}{}:\n {:.0}",
|
||||
if i == self.selected_channel { ">" } else { " " },
|
||||
&channel.name,
|
||||
(channel.volume * 100.0)
|
||||
);
|
||||
}
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
}
|
||||
|
||||
fn input(&mut self) {
|
||||
if !event::poll(Duration::from_millis(50)).unwrap() {
|
||||
return;
|
||||
}
|
||||
|
||||
let event = if let Ok(Event::Key(keyevent)) = event::read() {
|
||||
keyevent
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
match event.code {
|
||||
KeyCode::Char('q') => self.quit = true,
|
||||
KeyCode::Up => {
|
||||
self.selected_channel = match self.selected_channel {
|
||||
0 => self.channels.len() - 1,
|
||||
n => n - 1,
|
||||
};
|
||||
}
|
||||
let mut sample: Self::Item = c.source.next().unwrap();
|
||||
sample = (f32::from(sample) * c.volume) as Self::Item;
|
||||
out = out.saturating_add(sample);
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for Snoud {
|
||||
fn channels(&self) -> u16 {
|
||||
2
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Snoud {
|
||||
fn new(filenames: &[String]) -> Self {
|
||||
let channels = filenames.iter().map(SoundChannel::new).collect();
|
||||
|
||||
Self {
|
||||
sample_rate: 48000,
|
||||
channels,
|
||||
KeyCode::Down => {
|
||||
self.selected_channel = (self.selected_channel + 1) % self.channels.len();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
81
src/sound.rs
Normal file
81
src/sound.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use rodio::source::{Repeat, Source};
|
||||
use rodio::Decoder;
|
||||
use std::fs::File;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Snoud {
|
||||
channels: Vec<SoundChannel>,
|
||||
sample_rate: u32,
|
||||
}
|
||||
|
||||
struct SoundChannel {
|
||||
source: Repeat<Decoder<File>>,
|
||||
paused: bool,
|
||||
volume: f32,
|
||||
volume_sync: Arc<Mutex<f32>>,
|
||||
}
|
||||
|
||||
impl SoundChannel {
|
||||
fn new(name: &str) -> Self {
|
||||
let file = File::open(name).expect("File not found");
|
||||
let source = Decoder::new(file)
|
||||
.expect("Could not decode file")
|
||||
.repeat_infinite();
|
||||
Self {
|
||||
source,
|
||||
paused: false,
|
||||
volume: 1.0,
|
||||
volume_sync: Arc::new(Mutex::new(1.0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Snoud {
|
||||
type Item = i16;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut out: Self::Item = 0;
|
||||
for c in &mut self.channels {
|
||||
if c.paused {
|
||||
continue;
|
||||
}
|
||||
let mut sample: Self::Item = c.source.next().unwrap();
|
||||
sample = (f32::from(sample) * c.volume) as Self::Item;
|
||||
out = out.saturating_add(sample);
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for Snoud {
|
||||
fn channels(&self) -> u16 {
|
||||
2
|
||||
}
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate
|
||||
}
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Snoud {
|
||||
pub fn new(/* filenames: &[String] */) -> Self {
|
||||
// let channels = filenames.iter().map(SoundChannel::new).collect();
|
||||
Self {
|
||||
sample_rate: 48000,
|
||||
channels: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_channel(&mut self, filename: &str) -> Arc<Mutex<f32>> {
|
||||
let new = SoundChannel::new(filename);
|
||||
let volume_sync = new.volume_sync.clone();
|
||||
self.channels.push(new);
|
||||
volume_sync
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue