simple list menu
This commit is contained in:
parent
fc714de35c
commit
ad4a91e105
4 changed files with 259 additions and 79 deletions
74
Cargo.lock
generated
74
Cargo.lock
generated
|
@ -174,6 +174,31 @@ dependencies = [
|
||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cty"
|
name = "cty"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -333,6 +358,18 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -684,6 +721,36 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slice-deque"
|
name = "slice-deque"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -705,6 +772,7 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
name = "snoud"
|
name = "snoud"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
"rodio",
|
"rodio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -814,6 +882,12 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.83"
|
version = "0.2.83"
|
||||||
|
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
crossterm = "0.25.0"
|
||||||
rodio = { version = "0.16.0", default_features = false, features = ["mp3"] }
|
rodio = { version = "0.16.0", default_features = false, features = ["mp3"] }
|
||||||
|
|
182
src/main.rs
182
src/main.rs
|
@ -1,97 +1,121 @@
|
||||||
use rodio::source::{Repeat, Source};
|
use crossterm::{
|
||||||
use rodio::{Decoder, OutputStream};
|
cursor::MoveTo,
|
||||||
use std::fs::File;
|
event::{self, Event, KeyCode},
|
||||||
use std::time::Duration;
|
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() {
|
fn main() {
|
||||||
let files: Vec<String> = vec![
|
App::new().run();
|
||||||
"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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Snoud {
|
struct UIChannel {
|
||||||
channels: Vec<SoundChannel>,
|
name: String,
|
||||||
sample_rate: u32,
|
volume: f32,
|
||||||
|
internal_volume: Arc<Mutex<f32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SoundChannel {
|
struct App {
|
||||||
source: Repeat<Decoder<File>>,
|
channels: Vec<UIChannel>,
|
||||||
paused: bool,
|
selected_channel: usize,
|
||||||
volume: Volume,
|
_stream: OutputStream,
|
||||||
|
stream_handle: OutputStreamHandle,
|
||||||
|
quit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SoundChannel {
|
impl App {
|
||||||
fn new(name: &String) -> Self {
|
fn new() -> Self {
|
||||||
let file = File::open(name).expect("File not found");
|
let (_stream, stream_handle) = OutputStream::try_default() //
|
||||||
let source = Decoder::new(file)
|
.expect("Failed to create output stream");
|
||||||
.expect("Could not decode file")
|
|
||||||
.repeat_infinite();
|
|
||||||
Self {
|
Self {
|
||||||
source,
|
channels: Vec::new(),
|
||||||
paused: false,
|
selected_channel: 0,
|
||||||
volume: 1.0,
|
_stream,
|
||||||
|
stream_handle,
|
||||||
|
quit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Snoud {
|
fn run(&mut self) {
|
||||||
type Item = i16;
|
// 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 snoud = Snoud::new();
|
||||||
let mut out: Self::Item = 0;
|
for filename in files {
|
||||||
for c in &mut self.channels {
|
let internal_volume = snoud.add_channel(&filename);
|
||||||
if c.paused {
|
let ui_channel = UIChannel {
|
||||||
continue;
|
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();
|
KeyCode::Down => {
|
||||||
sample = (f32::from(sample) * c.volume) as Self::Item;
|
self.selected_channel = (self.selected_channel + 1) % self.channels.len();
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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…
Reference in a new issue