From 5cf195d457ed40365e6dec50922db02bca2a4c5a Mon Sep 17 00:00:00 2001 From: Ales Tsurko Date: Sun, 27 Oct 2024 00:09:33 +0200 Subject: [PATCH] Add tempo control --- Cargo.lock | 139 ++++++++++++++++++++++++++++++++++++----- Cargo.toml | 1 + src/app/app.rs | 32 ++++++++-- src/app/midi_player.rs | 17 ++++- src/main.rs | 2 + 5 files changed, 171 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c702de7..535d42d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,6 +441,7 @@ dependencies = [ "cpal", "env_logger", "iced", + "iced_aw", "log", "midi-player", "quick-xml 0.31.0", @@ -523,7 +524,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.11.0", "proc-macro2", "quote", "regex", @@ -1523,6 +1524,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -2029,6 +2036,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "iced_aw" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e05df3019f20c6decea93d035b32a2afc7b329d89cc5a68cca097d0e0a1889" +dependencies = [ + "cfg-if", + "chrono", + "iced", + "iced_fonts", + "itertools 0.13.0", + "num-format", + "num-traits", +] + [[package]] name = "iced_core" version = "0.13.2" @@ -2049,6 +2071,15 @@ dependencies = [ "web-time", ] +[[package]] +name = "iced_fonts" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7deb0800a850ee25c8a42559f72c0f249e577feb3aad37b9b65dc1e517e52a" +dependencies = [ + "iced_core", +] + [[package]] name = "iced_futures" version = "0.13.2" @@ -2090,6 +2121,7 @@ dependencies = [ "iced_core", "iced_futures", "log", + "lyon_path", "once_cell", "raw-window-handle", "rustc-hash 2.0.0", @@ -2154,6 +2186,7 @@ dependencies = [ "iced_glyphon", "iced_graphics", "log", + "lyon", "once_cell", "resvg", "rustc-hash 2.0.0", @@ -2264,6 +2297,21 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "jni" version = "0.21.1" @@ -2506,6 +2554,58 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3bca95f9a4955b3e4a821fbbcd5edfbd9be2a9a50bb5758173e5358bfb4c623" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + [[package]] name = "lz4_flex" version = "0.11.3" @@ -2552,7 +2652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46059721011b0458b7bd6d9179be5d0b60294281c23320c207adceaecc54d13b" dependencies = [ "hashbrown 0.14.3", - "itertools", + "itertools 0.11.0", "libm", "ryu", ] @@ -2576,7 +2676,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1503b27e825cabd1c3d0ff1e95a39fb2ec9eab6fd3da6cfa41aec7091d273e78" dependencies = [ - "itertools", + "itertools 0.11.0", "libm", "malachite-base", ] @@ -2587,7 +2687,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a475503a70a3679dbe3b9b230a23622516742528ba614a7b2490f180ea9cb514" dependencies = [ - "itertools", + "itertools 0.11.0", "malachite-base", "malachite-nz", ] @@ -2895,6 +2995,16 @@ dependencies = [ "syn 2.0.80", ] +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2906,11 +3016,12 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3925,7 +4036,7 @@ dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "indexmap", - "itertools", + "itertools 0.11.0", "log", "num-complex", "num-traits", @@ -3944,7 +4055,7 @@ dependencies = [ "bitflags 2.6.0", "bstr", "cfg-if", - "itertools", + "itertools 0.11.0", "libc", "lock_api", "malachite-base", @@ -3980,7 +4091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd4e0c9fb7b3c70eb27b38d533edc0aa4875ea38cb06e12d76e234d00ef9766" dependencies = [ "bitflags 2.6.0", - "itertools", + "itertools 0.11.0", "lz4_flex", "malachite-bigint", "num-complex", @@ -4004,7 +4115,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd18fa95c71a08ecc9cce739a608f5bff38805963152e671b66f5f266f0e58d" dependencies = [ - "itertools", + "itertools 0.11.0", "maplit", "once_cell", "proc-macro2", @@ -4033,7 +4144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0389039b132ad8e350552d771270ccd03186985696764bcee2239694e7839942" dependencies = [ "bitflags 2.6.0", - "itertools", + "itertools 0.11.0", "malachite-bigint", "num-traits", "rustpython-literal", @@ -4060,7 +4171,7 @@ checksum = "868f724daac0caf9bd36d38caf45819905193a901e8f1c983345a68e18fb2abb" dependencies = [ "anyhow", "is-macro", - "itertools", + "itertools 0.11.0", "lalrpop-util", "log", "malachite-bigint", @@ -4141,7 +4252,7 @@ dependencies = [ "gethostname 0.2.3", "hex", "indexmap", - "itertools", + "itertools 0.11.0", "junction", "libc", "libsqlite3-sys", @@ -4212,7 +4323,7 @@ dependencies = [ "hex", "indexmap", "is-macro", - "itertools", + "itertools 0.11.0", "junction", "libc", "log", diff --git a/Cargo.toml b/Cargo.toml index 876eebe..e9be838 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ async-channel = "2.3.1" cpal = "0.15.3" env_logger = "0.11" iced = { version = "0.13.1", features = ["svg", "tokio", "wgpu"] } +iced_aw = "0.11" log = "0.4" midi-player = "0.2.1" quick-xml = "0.31.0" diff --git a/src/app/app.rs b/src/app/app.rs index 0de0297..86829e3 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -1,9 +1,10 @@ //! Application's GUI. -use iced::futures::{join, sink::SinkExt}; +use iced::futures::sink::SinkExt; use iced::stream; use iced::widget::{column, container, scrollable, text, text::Style as TextStyle, text_input}; -use iced::{time, Element, Font, Subscription}; +use iced::{time, widget::row, Element, Font, Subscription}; +use iced_aw::widget::number_input; use super::midi_player::{self, GlobalState as GlobalMidiPlayerState, State as MidiPlayerState}; use crate::interpreter; @@ -82,7 +83,11 @@ pub fn update(state: &mut State, message: Message) { state.output.push(Output::Error(e.to_string())); return; } - state.midi_player_state.controller.set_position(player.position); + state + .midi_player_state + .controller + .set_position(player.position); + state.midi_player_state.update_tempo(); state.midi_player_state.controller.play(); state.midi_player_state.playing_id = Some(id); } @@ -125,6 +130,9 @@ pub fn update(state: &mut State, message: Message) { } } } + Message::SetTempo(tempo) => { + state.midi_player_state.set_tempo(tempo); + } Message::InterpreterMessage(msg) => match msg { interpreter::Message::SendCmd(ref cmd) => { state.answer = "".to_owned(); @@ -199,6 +207,7 @@ pub fn view(state: &State) -> Element { .height(iced::Length::Fill) .anchor_bottom(), view_prompt(state), + view_bottom_panel(state), ]) .padding(40) .width(TERM_WIDTH * FONT_WIDTH) @@ -206,7 +215,7 @@ pub fn view(state: &State) -> Element { .into() } -fn view_output<'a>(output: &'a Output) -> Element<'a, Message> { +fn view_output(output: &Output) -> Element { match output { Output::Normal(msg) => container(text(msg)), Output::Command(msg) => { @@ -223,7 +232,7 @@ fn view_output<'a>(output: &'a Output) -> Element<'a, Message> { .into() } -fn view_prompt<'a>(state: &'a State) -> Element<'a, Message> { +fn view_prompt(state: &State) -> Element { use iced::widget::text_input::{Catalog, Status}; let normal_style = |theme: &iced::Theme, status: Status| { @@ -263,6 +272,18 @@ fn view_prompt<'a>(state: &'a State) -> Element<'a, Message> { container(text_input).height(40).into() } +fn view_bottom_panel(state: &State) -> Element { + row![ + text("Tempo:"), + number_input(state.midi_player_state.tempo(), 20..=600, Message::SetTempo,).step(1).width(60.0), + text("BPM") + ] + .spacing(10.0) + .align_y(iced::Alignment::Center) + .height(70.0) + .into() +} + /// The iced message type. #[allow(missing_docs)] #[derive(Debug, Clone)] @@ -275,6 +296,7 @@ pub enum Message { // id, position ChangePlayingPosition(usize, f64), Tick(time::Instant), + SetTempo(u16), } impl From for Message { diff --git a/src/app/midi_player.rs b/src/app/midi_player.rs index 75b5def..f06f654 100644 --- a/src/app/midi_player.rs +++ b/src/app/midi_player.rs @@ -18,6 +18,7 @@ pub(crate) struct GlobalState { pub(crate) controller: PlayerController, audio_stream: AudioStream, pub(crate) playing_id: Option, + pub(crate) tempo: u16, } impl GlobalState { @@ -31,6 +32,7 @@ impl GlobalState { controller, audio_stream, playing_id: None, + tempo: 120 } } @@ -75,6 +77,19 @@ impl GlobalState { stream } + + pub(crate) fn tempo(&self) -> u16 { + self.tempo + } + + pub(crate) fn set_tempo(&mut self, tempo: u16) { + self.tempo = tempo; + self.controller.set_tempo(tempo as f32); + } + + pub(crate) fn update_tempo(&mut self) { + self.set_tempo(self.tempo); + } } #[derive(Debug)] @@ -85,7 +100,7 @@ pub(crate) struct State { pub(crate) position: f64, } -pub(crate) fn view<'a>(state: &'a State) -> Element<'a, Message> { +pub(crate) fn view(state: &State) -> Element { let label = svg(if state.is_playing { "resources/img/pause.svg" } else { diff --git a/src/main.rs b/src/main.rs index dec760e..e7b0376 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ //! The executable. use athenacl::app; +use iced_aw::iced_fonts; fn main() -> iced::Result { iced::application("athenaCL", app::update, app::view) @@ -18,5 +19,6 @@ fn main() -> iced::Result { max_size: Some((800.0, f32::MAX).into()), ..Default::default() }) + .font(iced_fonts::REQUIRED_FONT_BYTES) .run() }