├── dune-project ├── gramotocaml ├── test │ ├── dune │ └── test.ml └── src │ ├── gramotor.mli │ ├── gramotor_stubs.h │ ├── gramotor.ml │ ├── dune │ ├── gramotor_stubs.c │ └── lib.rs ├── frigo └── src │ ├── playco.ml │ ├── lv2Talker.ml │ └── ladspaTalker.ml ├── scale ├── src │ ├── lib.rs │ └── pitch_fetcher.rs └── Cargo.toml ├── src ├── configuration.ml ├── gen_gui ├── curveControler.ml ├── curve_controler.rs ├── ui │ ├── mod.rs │ ├── talker_object │ │ ├── mod.rs │ │ └── imp.rs │ ├── session_saving_dialog.rs │ ├── session_opening_dialog.rs │ ├── general_settings.rs │ ├── plugin_ui.rs │ └── bounded_float_entry.rs ├── css │ └── style.css ├── dune ├── util.rs ├── mixer_presenter.rs ├── undo_redo_list.rs ├── settings.rs ├── main.rs ├── track_control.rs ├── mixer_control.rs ├── output_presenter.rs ├── style.rs └── graph_control.rs ├── talker ├── dune ├── Cargo.toml ├── src │ ├── lib.rs │ ├── atom_talker.rs │ ├── cv_talker.rs │ ├── audio_talker.rs │ ├── control_talker.rs │ ├── talker_handler.rs │ ├── hidden_constant_talker.rs │ ├── dsp.rs │ ├── audio_format.rs │ ├── identifier.rs │ ├── lv2_handler.rs │ ├── voice.rs │ ├── data.rs │ └── horn.rs └── META ├── plugins ├── dune ├── META ├── inversion.ml └── temporalInversion.ml ├── audiofile ├── Cargo.toml └── src │ ├── lib.rs │ └── reader.rs ├── session ├── src │ ├── talkers │ │ ├── tseq │ │ │ ├── mod.rs │ │ │ ├── envelope.rs │ │ │ └── pitch.rs │ │ ├── mod.rs │ │ ├── round.rs │ │ ├── parabolic.rs │ │ ├── hub.rs │ │ ├── abs_sine.rs │ │ ├── second_degree_frequency_progression.rs │ │ ├── fuzz.rs │ │ ├── static_sine.rs │ │ ├── sinusoidal.rs │ │ ├── damped_sinusoidal.rs │ │ ├── sinusoidal_fptg.rs │ │ ├── table_talker.rs │ │ ├── bounded_sinusoidal.rs │ │ ├── audiofile_input.rs │ │ ├── damped_round.rs │ │ ├── dynamic_modulator.rs │ │ ├── square.rs │ │ ├── damper.rs │ │ ├── regulator.rs │ │ ├── accumulator.rs │ │ ├── bounded_square.rs │ │ └── audio_switch.rs │ ├── gp_error.rs │ ├── lib.rs │ ├── state.rs │ ├── audio_data.rs │ ├── track.rs │ ├── output.rs │ ├── event_bus.rs │ ├── factory.rs │ ├── audiofile_output.rs │ ├── channel.rs │ ├── session.rs │ └── tables │ │ └── mod.rs └── Cargo.toml ├── au2tseq └── Cargo.toml ├── playone ├── Cargo.toml └── src │ └── main.rs ├── .gitignore ├── configure.sh ├── graffophone.opam ├── Makefile ├── README.md └── Cargo.toml /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | -------------------------------------------------------------------------------- /gramotocaml/test/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name test) 3 | (libraries gramotor) 4 | ) 5 | -------------------------------------------------------------------------------- /frigo/src/playco.ml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gndl/graffophone/HEAD/frigo/src/playco.ml -------------------------------------------------------------------------------- /scale/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate failure; 2 | 3 | pub mod pitch_fetcher; 4 | pub mod scale; 5 | -------------------------------------------------------------------------------- /src/configuration.ml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gndl/graffophone/HEAD/src/configuration.ml -------------------------------------------------------------------------------- /src/gen_gui: -------------------------------------------------------------------------------- 1 | lablgladecc2 -embed -hide-default graffophoneGui.glade > graffophoneGui.ml 2 | -------------------------------------------------------------------------------- /frigo/src/lv2Talker.ml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gndl/graffophone/HEAD/frigo/src/lv2Talker.ml -------------------------------------------------------------------------------- /src/curveControler.ml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gndl/graffophone/HEAD/src/curveControler.ml -------------------------------------------------------------------------------- /frigo/src/ladspaTalker.ml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gndl/graffophone/HEAD/frigo/src/ladspaTalker.ml -------------------------------------------------------------------------------- /talker/dune: -------------------------------------------------------------------------------- 1 | 2 | (library 3 | (name graffophone_plugin) 4 | (synopsis "Graffophone plugin library") 5 | ) 6 | -------------------------------------------------------------------------------- /scale/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scale" 3 | version = "0.1.0" 4 | authors = ["gndl"] 5 | 6 | [dependencies] 7 | failure = "0.1.8" 8 | -------------------------------------------------------------------------------- /plugins/dune: -------------------------------------------------------------------------------- 1 | 2 | (library 3 | (name graffophone_plugins) 4 | (libraries graffophone_plugin) 5 | (synopsis "Graffophone plugin example") 6 | ) 7 | -------------------------------------------------------------------------------- /src/curve_controler.rs: -------------------------------------------------------------------------------- 1 | pub struct CurveControler {} 2 | 3 | impl CurveControler { 4 | pub fn new() -> CurveControler { 5 | Self {} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /audiofile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "audiofile" 3 | version = "0.1.0" 4 | authors = ["gndl"] 5 | 6 | [dependencies] 7 | failure = "0.1.8" 8 | ffmpeg-next = "7.0.2" 9 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bounded_float_entry; 2 | pub mod session_settings; 3 | pub mod general_settings; 4 | pub mod plugin_ui; 5 | pub mod session_opening_dialog; 6 | pub mod session_saving_dialog; 7 | pub mod talker_object; 8 | -------------------------------------------------------------------------------- /session/src/talkers/tseq/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod audio_event; 2 | pub mod sequence; 3 | pub mod binder; 4 | pub mod envelope; 5 | pub mod midi_seq; 6 | pub mod parser; 7 | pub mod pitch; 8 | pub mod syntax; 9 | pub mod tseq; 10 | -------------------------------------------------------------------------------- /au2tseq/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "au2tseq" 3 | version = "0.1.0" 4 | authors = ["gndl"] 5 | edition = "2024" 6 | 7 | [dependencies] 8 | rustfft = "6.2.0" 9 | audiofile = { path = "../audiofile" } 10 | scale = { path = "../scale" } 11 | -------------------------------------------------------------------------------- /talker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "talker" 3 | version = "0.1.0" 4 | authors = ["gndl"] 5 | 6 | [dependencies] 7 | failure = "0.1.8" 8 | lv2-sys = "2" 9 | livi = "0.7.5" 10 | suil-sys = { git = "https://github.com/gndl/suil-sys.git" } 11 | -------------------------------------------------------------------------------- /audiofile/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate failure; 2 | extern crate ffmpeg_next as ffmpeg; 3 | 4 | pub mod reader; 5 | pub mod writer; 6 | 7 | 8 | pub fn init() -> Result<(), failure::Error> { 9 | ffmpeg::init().map_err(|e| failure::err_msg(format!("FFMPEG init error : {}", e))) 10 | } 11 | -------------------------------------------------------------------------------- /playone/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "playone" 3 | version = "0.1.0" 4 | authors = ["gndl "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | failure = "0.1.8" 9 | granode = { path = "../talker" } 10 | session = { path = "../session" } 11 | #ffmpeg = "0.1.1" 12 | #portaudio = "0.7.0" 13 | -------------------------------------------------------------------------------- /gramotocaml/src/gramotor.mli: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | val create: unit -> (t, string) result 4 | val new_session: t -> unit 5 | val init_session: t -> string -> unit 6 | val start: t -> (unit, string) result 7 | val play: t -> (unit, string) result 8 | val pause: t -> (unit, string) result 9 | val stop: t -> (unit, string) result 10 | 11 | -------------------------------------------------------------------------------- /session/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "session" 3 | version = "0.1.0" 4 | authors = ["gndl"] 5 | 6 | [dependencies] 7 | failure = "0.1.8" 8 | ringbuf = "0.2.8" 9 | cpal = "0.15.2" 10 | livi = "0.7.5" 11 | nom = "7.1" 12 | audiofile = { path = "../audiofile" } 13 | scale = { path = "../scale" } 14 | talker = { path = "../talker" } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # cargo generated files 2 | target 3 | 4 | # emacs generated files 5 | *~ 6 | 7 | # dune generated files 8 | _build 9 | .merlin 10 | *.install 11 | 12 | # IntelliJ generated files 13 | .idea 14 | 15 | # ocaml generated files 16 | *.annot 17 | *.cmo 18 | *.cma 19 | *.cmi 20 | *.a 21 | *.o 22 | *.cmx 23 | *.cmxs 24 | *.cmxa 25 | *.docdir 26 | -------------------------------------------------------------------------------- /gramotocaml/src/gramotor_stubs.h: -------------------------------------------------------------------------------- 1 | #ifndef __OCAML_GRAMOTOR_GRAMOTOR_STUB_H_ 2 | #define __OCAML_GRAMOTOR_GRAMOTOR_STUB_H_ 3 | 4 | #include 5 | 6 | typedef struct gramotor Gramotor; 7 | 8 | /* Accessing the Gramotor part of a Caml custom block */ 9 | #define Gramotor_val(v) (*((Gramotor**) Data_custom_val(v))) 10 | 11 | #endif /*__OCAML_GRAMOTOR_GRAMOTOR_STUB_H_*/ 12 | -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | box.graphview_area { 2 | margin: 0px; 3 | border: 0px; 4 | padding: 0px; 5 | background-color: black; 6 | } 7 | 8 | entry.bounded_float_entry { 9 | font-size: 14pt; 10 | } 11 | 12 | scrolledwindow.graphview_scrolledwindow { 13 | background-color: black; 14 | } 15 | 16 | textview { 17 | font-family: Monospace; 18 | font-size: 12pt; 19 | } 20 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | 2 | ;(rule 3 | ; ((targets (graffophoneGui.ml)) 4 | ; (deps (gen_gui graffophoneGui.glade)) 5 | ; (action (run ${<})))) 6 | 7 | (executable 8 | (name graffophone) 9 | (libraries unix threads dynlink str bigarray sexplib ppx_sexp_conv ppxlib lablgtk2 lablgtk2.glade lablgtk2.gnomecanvas ffmpeg graffophone_plugin graffophone_plugins gramotor) 10 | (preprocess 11 | (pps ppxlib ppx_sexp_conv))) 12 | -------------------------------------------------------------------------------- /src/ui/talker_object/mod.rs: -------------------------------------------------------------------------------- 1 | mod imp; 2 | 3 | use gtk::glib; 4 | 5 | glib::wrapper! { 6 | pub struct TalkerObject(ObjectSubclass); 7 | } 8 | 9 | impl TalkerObject { 10 | pub fn new(label: &str, model: &str) -> Self { 11 | glib::Object::builder() 12 | .property("label", label) 13 | .property("model", model) 14 | .build() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /session/src/gp_error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | #[derive(Debug)] 5 | pub struct GpError {} 6 | 7 | impl fmt::Display for GpError { 8 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 9 | write!(f, "GpError is here!") 10 | } 11 | } 12 | 13 | impl Error for GpError { 14 | fn description(&self) -> &str { 15 | "I'm the gphero of errors" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /talker/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate failure; 2 | extern crate livi; 3 | 4 | pub mod atom_talker; 5 | pub mod audio_format; 6 | pub mod audio_talker; 7 | pub mod control_talker; 8 | pub mod cv_talker; 9 | pub mod data; 10 | pub mod dsp; 11 | pub mod ear; 12 | pub mod horn; 13 | pub mod identifier; 14 | pub mod lv2_handler; 15 | pub mod talker; 16 | pub mod talker_handler; 17 | pub mod voice; 18 | 19 | pub use identifier::Identifier; 20 | -------------------------------------------------------------------------------- /talker/META: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: 74e6f5d04c9ff5e4cf7d1be9977cce97) 3 | package "plugin" ( 4 | version = "0.1" 5 | description = "Graffophone plugin library" 6 | archive(byte) = "graffophone-plugin.cma" 7 | archive(byte, plugin) = "graffophone-plugin.cma" 8 | archive(native) = "graffophone-plugin.cmxa" 9 | archive(native, plugin) = "graffophone-plugin.cmxs" 10 | exists_if = "graffophone-plugin.cma" 11 | ) 12 | # OASIS_STOP 13 | 14 | -------------------------------------------------------------------------------- /configure.sh: -------------------------------------------------------------------------------- 1 | sudo apt install gcc gcc-multilib make autoconf automake libtool flex bison gdb 2 | sudo apt install libasound2-dev liblilv-dev libgtk-3-dev libgtksourceview-4-dev 3 | sudo apt install abgate ardour-lv2-plugins avldrums.lv2 bsequencer bshapr calf-plugins dpf-plugins-lv2 dragonfly-reverb-lv2 drumgizmo drumkv1-lv2 eq10q fomp guitarix-lv2 invada-studio-plugins-lv2 ir.lv2 lsp-plugins-lv2 lv2vocoder mda-lv2 padthv1-lv2 samplv1-lv2 so-synth-lv2 swh-lv2 synthv1-lv2 ubuntustudio-audio-plugins vocproc x42-plugins zam-plugins zynaddsubfx-lv2 4 | -------------------------------------------------------------------------------- /session/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cpal; 2 | extern crate failure; 3 | extern crate livi; 4 | extern crate nom; 5 | extern crate ringbuf; 6 | 7 | extern crate audiofile; 8 | extern crate scale; 9 | extern crate talker; 10 | 11 | pub mod audio_data; 12 | pub mod audiofile_output; 13 | pub mod band; 14 | pub mod channel; 15 | pub mod event_bus; 16 | pub mod factory; 17 | pub mod feedback; 18 | pub mod mixer; 19 | pub mod output; 20 | pub mod parser; 21 | pub mod player; 22 | pub mod plugins_manager; 23 | pub mod session; 24 | pub mod state; 25 | pub mod tables; 26 | pub mod talkers; 27 | pub mod track; 28 | -------------------------------------------------------------------------------- /session/src/talkers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod accumulator; 2 | pub mod adsrp; 3 | pub mod audio_switch; 4 | pub mod audiofile_input; 5 | pub mod bounded_sinusoidal; 6 | pub mod bounded_square; 7 | pub mod damper; 8 | pub mod dynamic_modulator; 9 | pub mod envelope_shaper; 10 | pub mod speed_modulator; 11 | pub mod fuzz; 12 | pub mod hub; 13 | pub mod lv2; 14 | pub mod math; 15 | pub mod parabolic; 16 | pub mod regulator; 17 | pub mod round; 18 | pub mod second_degree_frequency_progression; 19 | pub mod sinusoidal; 20 | pub mod sinusoidal_fptg; 21 | pub mod square; 22 | pub mod table_talker; 23 | pub mod tseq; 24 | -------------------------------------------------------------------------------- /graffophone.opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | synopsis: "Modular audio processing system" 3 | maintainer: "gndl@users.noreply.github.com" 4 | authors: ["gndl"] 5 | homepage: "https://github.com/gndl/graffophone" 6 | bug-reports: "https://github.com/gndl/graffophone/issues" 7 | dev-repo: "https://github.com/gndl/graffophone.git" 8 | 9 | build: [ 10 | ["dune" "subst"] {pinned} 11 | ["dune" "build" "-p" graffophone] 12 | ] 13 | 14 | build-test: ["dune" "runtest" "-p" graffophone] 15 | 16 | depends: [ 17 | "dune" {build} 18 | "ppx_sexp_conv" {build} 19 | "ppxlib" {build} 20 | "alcotest" {test} 21 | "sexplib" 22 | "lablgtk" 23 | "ffmpeg" {<= "0.2.0"} 24 | ] 25 | 26 | available: [ 27 | ocaml-version >= "4.05.0" 28 | ] 29 | -------------------------------------------------------------------------------- /talker/src/atom_talker.rs: -------------------------------------------------------------------------------- 1 | use crate::lv2_handler::Lv2Handler; 2 | use crate::talker::{CTalker, Talker, TalkerBase}; 3 | use ctalker; 4 | 5 | pub const MODEL: &str = "AtomTalker"; 6 | 7 | pub struct AtomTalker {} 8 | 9 | impl AtomTalker { 10 | pub fn new(olv2_handler: Option<&Lv2Handler>, hidden: Option) -> CTalker { 11 | let mut base = TalkerBase::new("", MODEL, true); 12 | 13 | base.add_atom_voice(None, olv2_handler); 14 | base.set_hidden(hidden.unwrap_or(false)); 15 | 16 | ctalker!(base, Self {}) 17 | } 18 | } 19 | 20 | impl Talker for AtomTalker { 21 | fn talk(&mut self, _base: &TalkerBase, _port: usize, _tick: i64, len: usize) -> usize { 22 | len 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default build install uninstall test clean 2 | 3 | default: build 4 | 5 | deps: 6 | opam install -y --deps-only . 7 | 8 | build: 9 | dune build src/graffophone.exe 10 | 11 | gramotocaml: 12 | dune build gramotocaml/src/libgramotor_stubs.a 13 | 14 | dbg: 15 | dune build src/graffophone.bc 16 | cp ./_build/default/src/graffophone.bc . 17 | 18 | test: 19 | dune runtest -f 20 | 21 | exec: 22 | dune exec src/graffophone.exe 23 | 24 | run: exec 25 | 26 | gdb: build 27 | gdb _build/default/src/graffophone.exe 28 | 29 | install: 30 | dune install 31 | 32 | uninstall: 33 | dune uninstall 34 | 35 | clean: 36 | dune clean 37 | # Optionally, remove all files/folders ignored by git as defined 38 | # in .gitignore (-X). 39 | #git clean -dfXq 40 | -------------------------------------------------------------------------------- /talker/src/cv_talker.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Data; 2 | use crate::talker::{CTalker, Talker, TalkerBase}; 3 | use ctalker; 4 | 5 | pub const MODEL: &str = "CvTalker"; 6 | 7 | pub struct CvTalker {} 8 | 9 | impl CvTalker { 10 | pub fn new(def_value: f32, hidden: Option) -> CTalker { 11 | let value = if def_value.is_nan() { 0. } else { def_value }; 12 | let mut base = TalkerBase::new_data("", MODEL, Data::f(value), true); 13 | 14 | base.add_cv_voice(None, value); 15 | base.set_hidden(hidden.unwrap_or(false)); 16 | 17 | ctalker!(base, Self {}) 18 | } 19 | } 20 | 21 | impl Talker for CvTalker { 22 | fn talk(&mut self, _base: &TalkerBase, _port: usize, _tick: i64, len: usize) -> usize { 23 | len 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /talker/src/audio_talker.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Data; 2 | use crate::talker::{CTalker, Talker, TalkerBase}; 3 | use ctalker; 4 | 5 | pub const MODEL: &str = "AudioTalker"; 6 | 7 | pub struct AudioTalker {} 8 | 9 | impl AudioTalker { 10 | pub fn new(def_value: f32, hidden: Option) -> CTalker { 11 | let value = if def_value.is_nan() { 0. } else { def_value }; 12 | let mut base = TalkerBase::new_data("", MODEL, Data::f(value), true); 13 | 14 | base.add_audio_voice(None, value); 15 | base.set_hidden(hidden.unwrap_or(false)); 16 | 17 | ctalker!(base, Self {}) 18 | } 19 | } 20 | 21 | impl Talker for AudioTalker { 22 | fn talk(&mut self, _base: &TalkerBase, _port: usize, _tick: i64, len: usize) -> usize { 23 | len 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /talker/src/control_talker.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Data; 2 | use crate::talker::{CTalker, Talker, TalkerBase}; 3 | use ctalker; 4 | 5 | pub const MODEL: &str = "ControlTalker"; 6 | 7 | pub struct ControlTalker {} 8 | 9 | impl ControlTalker { 10 | pub fn new(def_value: f32, hidden: Option) -> CTalker { 11 | let value = if def_value.is_nan() { 1. } else { def_value }; 12 | let mut base = TalkerBase::new_data("", MODEL, Data::f(value), true); 13 | 14 | base.add_control_voice(None, value); 15 | base.set_hidden(hidden.unwrap_or(false)); 16 | 17 | ctalker!(base, Self {}) 18 | } 19 | } 20 | 21 | impl Talker for ControlTalker { 22 | fn talk(&mut self, _base: &TalkerBase, _port: usize, _tick: i64, len: usize) -> usize { 23 | len 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plugins/META: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: c6c1649f22c5828f152cf05328db4c05) 3 | package "plugins" ( 4 | version = "0.1" 5 | description = "Graffophone plugin example" 6 | requires = "graffophone.plugin" 7 | archive(byte) = "graffophone-plugins.cma" 8 | archive(byte, plugin) = "graffophone-plugins.cma" 9 | archive(native) = "graffophone-plugins.cmxa" 10 | archive(native, plugin) = "graffophone-plugins.cmxs" 11 | exists_if = "graffophone-plugins.cma" 12 | ) 13 | package "plugin" ( 14 | version = "0.1" 15 | description = "Graffophone plugin library" 16 | archive(byte) = "graffophone-plugin.cma" 17 | archive(byte, plugin) = "graffophone-plugin.cma" 18 | archive(native) = "graffophone-plugin.cmxa" 19 | archive(native, plugin) = "graffophone-plugin.cmxs" 20 | exists_if = "graffophone-plugin.cma" 21 | ) 22 | # OASIS_STOP 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graffophone 2 | 3 | Modular audio processing application including sequencer, synthesis and mixing functions. 4 | 5 | Supported formats : 6 | - Audio, MIDI 7 | - Scales : 12 ET, 17 ET, 19 ET, 53 ET, natural, pythagorean 8 | - Plugins : Lv2 9 | - Sample rates (Hz) : 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000 10 | - Codecs : FLAC, MP3, Ogg Vorbis, Opus, WAV 11 | 12 | 13 | ![Graffophone](https://github.com/gndl/graffophone/wiki/graffophone-0.3.0.png) 14 | 15 | Building and installing Graffophone 16 | ============================== 17 | 18 | 19 | Configuration 20 | ------------- 21 | 22 | Prerequisites: rust >= 1.73.0, ffmpeg 7, liblilv-dev >= 0.24 23 | 24 | Compilation 25 | ----------- 26 | 27 | $ cargo build --bin graffophone --release 28 | 29 | 30 | Execution 31 | --------- 32 | 33 | $ cargo run --bin graffophone --release 34 | 35 | 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graffophone" 3 | version = "0.3.0" 4 | authors = ["gndl "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "playone" 9 | path = "playone/src/main.rs" 10 | 11 | [[bin]] 12 | name = "au2tseq" 13 | path = "au2tseq/src/main.rs" 14 | 15 | [dependencies] 16 | failure = "0.1.8" 17 | ringbuf = "0.2.8" 18 | dirs = "6.0.0" 19 | #glib = "0.20.9" 20 | cairo-rs = "0.20.7" 21 | sourceview5 = "0.9.1" 22 | audiofile = { path = "./audiofile" } 23 | talker = { path = "./talker" } 24 | scale = { path = "./scale" } 25 | session = { path = "./session" } 26 | rustfft = "6.2.0" 27 | lv2-sys = "2" 28 | lv2_raw = "0.2" 29 | suil-sys = { git = "https://github.com/gndl/suil-sys.git" } 30 | 31 | [dependencies.gtk] 32 | package = "gtk4" 33 | version = "0.9.6" 34 | features = ["v4_12"] 35 | 36 | [dependencies.gio] 37 | version = "0.20.9" 38 | features = ["v2_58"] 39 | -------------------------------------------------------------------------------- /gramotocaml/src/gramotor.ml: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | external create: unit -> t = "gramotor_create" 4 | let create()= 5 | try Result.ok (create()) with Failure msg -> Result.error msg 6 | 7 | 8 | external new_session: t -> unit = "gramotor_new_session" 9 | external init_session: t -> string -> unit = "gramotor_init_session" 10 | 11 | external start: t -> unit = "gramotor_start" 12 | let start motor = 13 | try Result.ok (start motor) with Failure msg -> Result.error msg 14 | 15 | external play: t -> unit = "gramotor_play" 16 | let play motor = 17 | try Result.ok (play motor) with Failure msg -> Result.error msg 18 | 19 | external pause: t -> unit = "gramotor_pause" 20 | let pause motor = 21 | try Result.ok (pause motor) with Failure msg -> Result.error msg 22 | 23 | external stop: t -> unit = "gramotor_stop" 24 | let stop motor = 25 | try Result.ok (stop motor) with Failure msg -> Result.error msg 26 | 27 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | /* 2 | use std::cmp::Eq; 3 | use std::collections::HashMap; 4 | use std::hash::Hash; 5 | 6 | pub fn hashmap_visit(hashmap: HashMap, key: K, mut f: F) 7 | where 8 | F: FnMut(&V), 9 | { 10 | if let Some(v) = &hashmap.get(&key) { 11 | f(v); 12 | } 13 | } 14 | 15 | pub fn option_visit(option: Option, mut f: F) 16 | where 17 | F: FnMut(&V), 18 | { 19 | if let Some(v) = &option { 20 | f(v); 21 | } 22 | } 23 | */ 24 | 25 | pub fn filename_with_extention(filename: &str, extention: &str) -> String { 26 | let ext_pos = filename.rfind(".").unwrap_or(filename.len()); 27 | format!("{}.{}", filename.get(..ext_pos).unwrap(), extention) 28 | } 29 | 30 | pub fn print_cairo_result(result: Result<(), cairo::Error>) { 31 | match result { 32 | Ok(()) => (), 33 | Err(e) => println!("TalkerControl Cairo error {}", e), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /session/src/talkers/round.rs: -------------------------------------------------------------------------------- 1 | use talker::ctalker; 2 | use talker::talker::{CTalker, Talker, TalkerBase}; 3 | use talker::talker_handler::TalkerHandlerBase; 4 | 5 | use tables::round; 6 | use talkers::table_talker::TableTalker; 7 | 8 | pub const MODEL: &str = "Round"; 9 | 10 | pub struct Round { 11 | table_talker: TableTalker, 12 | } 13 | 14 | impl Round { 15 | pub fn new(mut base: TalkerBase) -> Result { 16 | let table_talker = TableTalker::new(&mut base, round::LEN)?; 17 | 18 | Ok(ctalker!(base, Self { table_talker })) 19 | } 20 | 21 | pub fn descriptor() -> TalkerHandlerBase { 22 | TalkerHandlerBase::builtin("Oscillator", MODEL, MODEL) 23 | } 24 | } 25 | 26 | impl Talker for Round { 27 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 28 | self.table_talker.talk(base, port, tick, len, &round::TAB) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gramotocaml/test/test.ml: -------------------------------------------------------------------------------- 1 | let gsr = {| 2 | Sinusoidal 1#Sinusoidal_1 3 | > frequence 440 4 | > phase 0 5 | 6 | track 2#track_2 7 | > I 1#Sinusoidal_1:O 8 | > gain 1 9 | 10 | mixer 5#mixer_5 11 | > volume 1 12 | > track 2#track_2 13 | |} 14 | 15 | let () = 16 | print_endline "Gramotor.create()"; 17 | (match Gramotor.create() with 18 | | Ok gramotor -> ( 19 | print_endline "Gc.full_major ()"; 20 | Gc.full_major (); Gc.full_major (); 21 | print_endline "Gramotor.init_session"; 22 | Gramotor.init_session gramotor gsr; 23 | print_endline "Gc.full_major ()"; 24 | Gc.full_major (); Gc.full_major (); 25 | print_endline "Gramotor.play"; 26 | match Gramotor.play gramotor with 27 | Ok()->( 28 | Gc.full_major (); Gc.full_major (); 29 | "OK!" 30 | ) 31 | | Error msg -> msg 32 | ) 33 | | Error msg -> msg 34 | ) |> print_endline 35 | 36 | -------------------------------------------------------------------------------- /session/src/talkers/parabolic.rs: -------------------------------------------------------------------------------- 1 | use talker::ctalker; 2 | use talker::talker::{CTalker, Talker, TalkerBase}; 3 | use talker::talker_handler::TalkerHandlerBase; 4 | 5 | use tables::parabolic; 6 | use talkers::table_talker::TableTalker; 7 | 8 | pub const MODEL: &str = "Parabolic"; 9 | 10 | pub struct Parabolic { 11 | table_talker: TableTalker, 12 | } 13 | 14 | impl Parabolic { 15 | pub fn new(mut base: TalkerBase) -> Result { 16 | let table_talker = TableTalker::new(&mut base, parabolic::LEN)?; 17 | 18 | Ok(ctalker!(base, Self { table_talker })) 19 | } 20 | 21 | pub fn descriptor() -> TalkerHandlerBase { 22 | TalkerHandlerBase::builtin("Oscillator", MODEL, MODEL) 23 | } 24 | } 25 | 26 | impl Talker for Parabolic { 27 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 28 | self.table_talker 29 | .talk(base, port, tick, len, ¶bolic::TAB) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /playone/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_variables, unused_imports)] 2 | extern crate failure; 3 | extern crate session; 4 | 5 | use std::env; 6 | use std::fs; 7 | use std::fs::File; 8 | use std::io::Read; 9 | 10 | use session::player::Player; 11 | use session::state::State; 12 | 13 | fn main() { 14 | let args: Vec = env::args().collect(); 15 | for i in 1..args.len() { 16 | let filename = &args[i]; 17 | 18 | match play(filename) { 19 | Ok(_) => {} 20 | e => { 21 | eprintln!("playing {} failed : {:?}", filename, e); 22 | } 23 | } 24 | } 25 | } 26 | 27 | fn play(filename: &str) -> Result<(), failure::Error> { 28 | let band_description = String::from_utf8(fs::read(filename)?)?; 29 | let mut player = Player::new(band_description)?; 30 | 31 | let mut state = player.play()?; 32 | 33 | while state != State::Exited { 34 | state = player.wait()?; 35 | println!("Player state : {}", state.to_string()); 36 | } 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /src/ui/session_saving_dialog.rs: -------------------------------------------------------------------------------- 1 | 2 | use gtk::gio::Cancellable; 3 | 4 | use crate::session_presenter::RSessionPresenter; 5 | 6 | pub fn create(window: >k::ApplicationWindow, session_presenter: &RSessionPresenter, on_ok: OnOk) -> gtk::AlertDialog { 7 | let dialog = gtk::AlertDialog::builder() 8 | .modal(true) 9 | .buttons(["Cancel", "Don't save", "Save"]) 10 | .message("Save changes?") 11 | .build(); 12 | 13 | let win = window.clone(); 14 | let session = session_presenter.clone(); 15 | 16 | dialog.choose(Some(window), Cancellable::NONE, move |r| { 17 | match r { 18 | Ok(button_idx) => { 19 | if button_idx > 0 { 20 | if button_idx == 2 { 21 | session.borrow_mut().save_session(); 22 | } 23 | on_ok(&win, &session); 24 | } 25 | }, 26 | Err(e) => println!("Error {}", e), 27 | } 28 | }); 29 | dialog 30 | } 31 | -------------------------------------------------------------------------------- /session/src/talkers/hub.rs: -------------------------------------------------------------------------------- 1 | use talker::ctalker; 2 | use talker::ear; 3 | use talker::ear::Init; 4 | use talker::talker::{CTalker, Talker, TalkerBase}; 5 | use talker::talker_handler::TalkerHandlerBase; 6 | 7 | pub const MODEL: &str = "Hub"; 8 | 9 | pub struct Hub {} 10 | impl Hub { 11 | pub fn new(mut base: TalkerBase) -> Result { 12 | base.add_ear(ear::audio(None, -1., 1., 0., &Init::DefValue)?); 13 | 14 | base.add_audio_voice(None, 0.); 15 | 16 | Ok(ctalker!(base, Self {})) 17 | } 18 | 19 | pub fn descriptor() -> TalkerHandlerBase { 20 | TalkerHandlerBase::builtin("Mixer", MODEL, MODEL) 21 | } 22 | } 23 | 24 | impl Talker for Hub { 25 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 26 | let ln = base.listen(tick, len); 27 | let input_buf = base.ear_audio_buffer(0); 28 | let voice_buf = base.voice(port).audio_buffer(); 29 | 30 | for i in 0..ln { 31 | voice_buf[i] = input_buf[i]; 32 | } 33 | 34 | ln 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ui/talker_object/imp.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use glib::{ParamSpec, Properties, Value}; 3 | use gtk::glib; 4 | use gtk::prelude::*; 5 | use gtk::subclass::prelude::*; 6 | 7 | // Object holding the state 8 | #[derive(Properties, Default)] 9 | #[properties(wrapper_type = super::TalkerObject)] 10 | pub struct TalkerObject { 11 | #[property(get, set)] 12 | label:RefCell, 13 | #[property(get, set)] 14 | model:RefCell, 15 | } 16 | 17 | // The central trait for subclassing a GObject 18 | #[glib::object_subclass] 19 | impl ObjectSubclass for TalkerObject { 20 | const NAME: &'static str = "GraffophoneTalkerObject"; 21 | type Type = super::TalkerObject; 22 | } 23 | // Trait shared by all GObjects 24 | impl ObjectImpl for TalkerObject { 25 | fn properties() -> &'static [ParamSpec] { 26 | Self::derived_properties() 27 | } 28 | 29 | fn set_property(&self, id: usize, value: &Value, pspec: &ParamSpec) { 30 | self.derived_set_property(id, value, pspec) 31 | } 32 | 33 | fn property(&self, id: usize, pspec: &ParamSpec) -> Value { 34 | self.derived_property(id, pspec) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ui/session_opening_dialog.rs: -------------------------------------------------------------------------------- 1 | use gtk::prelude::*; 2 | use gtk::FileDialog; 3 | use gtk::gio::Cancellable; 4 | 5 | 6 | use crate::session_presenter::RSessionPresenter; 7 | 8 | pub fn create(window: >k::ApplicationWindow, session_presenter: &RSessionPresenter) -> gtk::FileDialog { 9 | let filters = gio::ListStore::new::(); 10 | 11 | let gsr_filter = gtk::FileFilter::new(); 12 | gsr_filter.add_pattern("*.gsr"); 13 | filters.append(&gsr_filter); 14 | 15 | let no_filter = gtk::FileFilter::new(); 16 | no_filter.add_pattern("*"); 17 | filters.append(&no_filter); 18 | 19 | let dialog = FileDialog::builder() 20 | .title("Choose a Graffophone session record file") 21 | .accept_label("Open") 22 | .filters(&filters) 23 | .build(); 24 | 25 | let session = session_presenter.clone(); 26 | 27 | dialog.open(Some(window), Cancellable::NONE, move |file| { 28 | if let Ok(file) = file { 29 | let path_buf = file.path().expect("Couldn't get file path"); 30 | session.borrow_mut().open_session(&path_buf.to_string_lossy()); 31 | } 32 | }); 33 | dialog 34 | } 35 | -------------------------------------------------------------------------------- /gramotocaml/src/dune: -------------------------------------------------------------------------------- 1 | (include_subdirs unqualified) 2 | 3 | (rule 4 | (deps (source_tree .)) 5 | (targets libgramotor_stubs.a dllgramotor_stubs.so) 6 | (action 7 | (progn 8 | ; (run cargo build -Z unstable-options --out-dir . --target-dir ../rust --release) 9 | ; (run cargo build -Z unstable-options --out-dir .) 10 | ; (run mv libgramotor_stubs.so ./dllgramotor_stubs.so) 11 | (run cargo build) 12 | (run cp ../../../../target/debug/libgramotor_stubs.a .) 13 | (run cp ../../../../target/debug/libgramotor_stubs.so dllgramotor_stubs.so) 14 | ) 15 | ) 16 | ) 17 | 18 | (library 19 | (name gramotor) 20 | (foreign_archives gramotor_stubs) 21 | ; (self_build_stubs_archive (gramotor)) 22 | (c_library_flags (-lpthread -lc -lm -lasound -llilv-0)) 23 | ) 24 | 25 | (library 26 | (name lilv) 27 | (public_name lilv) 28 | (libraries threads.posix) 29 | (c_names ocaml_lilv lv2_stub feature_stub node_stub ui_stub scalepoint_stub port_stub instance_stub plugin_stub pluginclass_stub world_stub) 30 | (foreign_stubs (language c) (-I/usr/include/lilv-0 -g -O2 -Wall -DCAML_NAME_SPACE)) 31 | (c_library_flags (-llilv-0)) 32 | ) 33 | -------------------------------------------------------------------------------- /session/src/state.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Gaëtan Dubreil 3 | * 4 | * All rights reserved.This file is distributed under the terms of the 5 | * GNU General Public License version 3.0. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU Lesser General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU Lesser General Public License 13 | * along with this program; if not, write to the Free Software 14 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | */ 16 | 17 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 18 | pub enum State { 19 | Playing, 20 | Recording, 21 | Paused, 22 | Stopped, 23 | Exited, 24 | } 25 | 26 | impl State { 27 | pub fn to_string(&self) -> String { 28 | (match self { 29 | State::Playing => "Playing", 30 | State::Recording => "Recording", 31 | State::Paused => "Paused", 32 | State::Stopped => "Stopped", 33 | State::Exited => "Exited", 34 | }) 35 | .to_string() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /session/src/talkers/tseq/envelope.rs: -------------------------------------------------------------------------------- 1 | use talkers::tseq::audio_event::{self, Shapes}; 2 | use talkers::tseq::parser::PEnvelope; 3 | 4 | pub const UNDEFINED: usize = usize::MAX; 5 | 6 | pub fn create(shapes: &Shapes, penvelope: &PEnvelope, ticks_per_second: f32) -> Vec { 7 | let mut duration = 0.; 8 | let mut sections = Vec::with_capacity(penvelope.points.len()); 9 | let mut start_level: f32 = 0.; 10 | 11 | for point in &penvelope.points { 12 | let end_tick = (point.duration * ticks_per_second) as i64; 13 | 14 | sections.push(audio_event::create( 15 | shapes, 16 | 0, 17 | end_tick, 18 | start_level, 19 | point.level, 20 | point.shape, 21 | false, 22 | false, 23 | UNDEFINED, 24 | )); 25 | 26 | start_level = point.level; 27 | 28 | duration += point.duration; 29 | } 30 | 31 | let env_len = (duration * ticks_per_second) as usize; 32 | let mut envelop = Vec::with_capacity(env_len); 33 | envelop.resize(env_len, 0.); 34 | 35 | let buf = envelop.as_mut_slice(); 36 | let mut ofset: usize = 0; 37 | 38 | for section in sections { 39 | ofset += section.assign_buffer(shapes, 0, buf, ofset, env_len - ofset) as usize; 40 | } 41 | envelop 42 | } 43 | -------------------------------------------------------------------------------- /session/src/talkers/abs_sine.rs: -------------------------------------------------------------------------------- 1 | use talker::audio_format::AudioFormat; 2 | use talker::ctalker; 3 | use talker::ear; 4 | use talker::ear::Init; 5 | use talker::talker::{CTalker, Talker, TalkerBase}; 6 | use talker::talker_handler::TalkerHandlerBase; 7 | 8 | pub const MODEL: &str = "AbsoluteSinusoidal"; 9 | 10 | pub struct AbsSine {} 11 | 12 | impl AbsSine { 13 | pub fn new(mut base: TalkerBase) -> Result { 14 | let freq = ear::audio(Some("freq"), 0., 20000., 440., &Init::DefValue)?; 15 | base.add_ear(freq); 16 | base.add_audio_voice(None, 0.); 17 | 18 | Ok(ctalker!(base, Self {})) 19 | } 20 | pub fn descriptor() -> TalkerHandlerBase { 21 | TalkerHandlerBase::builtin("Oscillator", MODEL, "Absolute Sinusoidal") 22 | } 23 | } 24 | 25 | impl Talker for AbsSine { 26 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 27 | let ln = base.listen(tick, len); 28 | let c = AudioFormat::frequence_coef(); 29 | 30 | let freq_buf = base.ear_audio_buffer(0); 31 | let voice_buf = base.voice(port).audio_buffer(); 32 | 33 | for i in 0..ln { 34 | let sample = ((tick + i as i64) as f64 * freq_buf[i] as f64 * c).sin() as f32; 35 | voice_buf[i] = sample; 36 | } 37 | ln 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /talker/src/talker_handler.rs: -------------------------------------------------------------------------------- 1 | use crate::talker::Talker; 2 | 3 | pub struct TalkerHandlerBase { 4 | pub categories: Vec, 5 | pub model: String, 6 | pub label: String, 7 | } 8 | 9 | impl TalkerHandlerBase { 10 | pub fn with_multi_categories(categories: Vec, model: &str, label: &str) -> Self { 11 | Self { 12 | categories: categories, 13 | model: model.to_string(), 14 | label: label.to_string(), 15 | } 16 | } 17 | pub fn builtin(category: &str, model: &str, label: &str) -> Self { 18 | let builtin_label = format!("G {}", label); 19 | TalkerHandlerBase::with_multi_categories(vec![category.to_string()], &model, &builtin_label) 20 | } 21 | 22 | pub fn categories<'a>(&'a self) -> &'a Vec { 23 | &self.categories 24 | } 25 | pub fn model<'a>(&'a self) -> &'a String { 26 | &self.model 27 | } 28 | pub fn label<'a>(&'a self) -> &'a String { 29 | &self.label 30 | } 31 | } 32 | 33 | pub trait TalkerHandler { 34 | fn base<'a>(&'a self) -> &'a TalkerHandlerBase; 35 | 36 | fn categories<'a>(&'a self) -> &'a Vec { 37 | &self.base().categories 38 | } 39 | fn model<'a>(&'a self) -> &'a String { 40 | &self.base().model 41 | } 42 | fn label<'a>(&'a self) -> &'a String { 43 | &self.base().label 44 | } 45 | 46 | fn make(&self) -> Result, failure::Error>; 47 | } 48 | -------------------------------------------------------------------------------- /session/src/talkers/second_degree_frequency_progression.rs: -------------------------------------------------------------------------------- 1 | use talker::audio_format::AudioFormat; 2 | use talker::ctalker; 3 | use talker::talker::{CTalker, Talker, TalkerBase}; 4 | use talker::talker_handler::TalkerHandlerBase; 5 | 6 | pub const MODEL: &str = "SecondDegreeFrequencyProgression"; 7 | 8 | pub struct SecondDegreeFrequencyProgression { 9 | f: f64, 10 | a: f64, 11 | b: f64, 12 | c: f64, 13 | } 14 | 15 | impl SecondDegreeFrequencyProgression { 16 | pub fn new(f: f64, a: f64, b: f64, c: f64, mut base: TalkerBase) -> Result { 17 | base.add_audio_voice(None, 0.); 18 | 19 | Ok(ctalker!(base, Self { f, a, b, c })) 20 | } 21 | 22 | pub fn descriptor() -> TalkerHandlerBase { 23 | TalkerHandlerBase::builtin("Oscillator", MODEL, "Second Degree Frequency Progression") 24 | } 25 | } 26 | 27 | impl Talker for SecondDegreeFrequencyProgression { 28 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 29 | let c = AudioFormat::frequence_coef(); 30 | let f = self.f; 31 | 32 | let voice_buf = base.voice(port).audio_buffer(); 33 | 34 | for i in 0..len { 35 | let t = (tick + i as i64) as f64; 36 | println!("tick = {}, i = {}, t = {}", tick, i, t); 37 | let sample = (t * f * c).sin() as f32; 38 | voice_buf[i] = sample; 39 | } 40 | 41 | self.f = self.a * f * f + self.b * f + self.c; 42 | len 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gramotocaml/src/gramotor_stubs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "gramotor.h" 10 | 11 | #include "gramotor_stub.h" 12 | 13 | static void custom_finalize_gramotor (value v) 14 | { 15 | Gramotor * m = Gramotor_val(v); 16 | 17 | gramotor_gramotor_drop(m); 18 | } 19 | 20 | // Encapsulation of opaque gramotor handles (of type GramotorGramotor) as Caml custom blocks. 21 | static struct custom_operations gramotor_gramotor_ops = 22 | { 23 | "GRAMOTOR/OCAMLinterface/" OCAML_GRAMOTOR_VERSION "/gramotor", 24 | custom_finalize_gramotor, 25 | custom_compare_default, 26 | custom_hash_default, 27 | custom_serialize_default, 28 | custom_deserialize_default 29 | }; 30 | 31 | value caml_gramotor_gramotor_new (value unit) 32 | { 33 | CAMLparam1 (unit); 34 | CAMLlocal1(ans); 35 | 36 | // caml_release_runtime_system(); 37 | Gramotor* m = gramotor_gramotor_new(); 38 | // caml_acquire_runtime_system(); 39 | 40 | if(m == NULL) caml_failwith("Gramotor.Gramotor initialization failed"); 41 | 42 | ans = caml_alloc_custom(&gramotor_gramotor_ops, sizeof(Gramotor*), 0, 1); 43 | 44 | Gramotor_val(ans) = gramotor; 45 | 46 | CAMLreturn (ans); 47 | } 48 | 49 | value caml_gramotor_gramotor_play(value v_gramotor) 50 | { 51 | CAMLparam1 (v_gramotor); 52 | 53 | gramotor_gramotor_play(Gramotor_val(v_gramotor)); 54 | CAMLreturn (Val_unit); 55 | } 56 | -------------------------------------------------------------------------------- /session/src/audio_data.rs: -------------------------------------------------------------------------------- 1 | //const VECTOR_SIZE: usize = 960; 2 | 3 | //pub type Vector = [f32; VECTOR_SIZE]; 4 | pub type Vector = Vec; 5 | 6 | pub struct Interleaved { 7 | nb_channels: usize, 8 | nb_samples_per_channel: usize, 9 | vector: Vector, 10 | is_end: bool, 11 | } 12 | 13 | impl Interleaved { 14 | pub fn new(channels: &Vec, nb_samples_per_channel: usize) -> Self { 15 | let nb_channels = channels.len(); 16 | // let mut vector = Vec::with_capacity(nb_channels * nb_samples_per_channel); 17 | let mut vector = vec![0.; nb_channels * nb_samples_per_channel]; 18 | 19 | for (ch_n, ch) in channels.iter().enumerate() { 20 | for i in 0..nb_samples_per_channel { 21 | vector[nb_channels * i + ch_n] = ch[i]; 22 | } 23 | } 24 | Self { 25 | nb_channels, 26 | nb_samples_per_channel, 27 | vector, 28 | is_end: false, 29 | } 30 | } 31 | pub fn end() -> Self { 32 | Self { 33 | nb_channels: 0, 34 | nb_samples_per_channel: 0, 35 | vector: Vec::new(), 36 | is_end: true, 37 | } 38 | } 39 | pub fn nb_channels(&self) -> usize { 40 | self.nb_channels 41 | } 42 | pub fn nb_samples_per_channel(&self) -> usize { 43 | self.nb_samples_per_channel 44 | } 45 | pub fn vector(&self) -> Vector { 46 | self.vector.to_vec() 47 | } 48 | pub fn is_end(&self) -> bool { 49 | self.is_end 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /session/src/track.rs: -------------------------------------------------------------------------------- 1 | use talker::ear::Set; 2 | use talker::identifier::Index; 3 | use crate::audio_data::Vector; 4 | 5 | pub const KIND: &str = "track"; 6 | 7 | const INPUT_INDEX: Index = 0; 8 | const GAIN_INDEX: Index = 1; 9 | const CHANNEL_GAIN_INDEX: Index = 2; 10 | 11 | fn compute_input_gain(set: &Set, _tick: i64, buf: &mut Vector, len: usize) -> usize { 12 | 13 | let in_buf = set.get_hum_audio_buffer(INPUT_INDEX); 14 | let gain_buf = set.get_hum_audio_buffer(GAIN_INDEX); 15 | 16 | for i in 0..len { 17 | buf[i] = in_buf[i] * gain_buf[i]; 18 | } 19 | len 20 | } 21 | 22 | pub fn set( 23 | set: &Set, 24 | tick: i64, 25 | buf: &mut Vector, 26 | len: usize, 27 | channels: &mut Vec, 28 | ) -> usize { 29 | let ln = compute_input_gain(set, tick, buf, len); 30 | 31 | for i in 0..channels.len() { 32 | let ch = &mut channels[i]; 33 | let cg = set.get_hum_cv_buffer(CHANNEL_GAIN_INDEX + i); 34 | 35 | for j in 0..ln { 36 | ch[j] = cg[j] * buf[j]; 37 | } 38 | } 39 | ln 40 | } 41 | 42 | pub fn add( 43 | set: &Set, 44 | tick: i64, 45 | buf: &mut Vector, 46 | len: usize, 47 | channels: &mut Vec, 48 | ) -> usize { 49 | let ln = compute_input_gain(set, tick, buf, len); 50 | 51 | for i in 0..channels.len() { 52 | let ch = &mut channels[i]; 53 | let cg = set.get_hum_cv_buffer(CHANNEL_GAIN_INDEX + i); 54 | 55 | for j in 0..ln { 56 | ch[j] = ch[j] + cg[j] * buf[j]; 57 | } 58 | } 59 | 60 | ln 61 | } 62 | -------------------------------------------------------------------------------- /plugins/inversion.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (C) 2015 Gaetan Dubreil 3 | * 4 | * All rights reserved.This file is distributed under the terms of the 5 | * GNU General Public License version 3.0. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU Lesser General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU Lesser General Public License 13 | * along with this program; if not, write to the Free Software 14 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | *) 16 | 17 | open Graffophone_plugin 18 | open Util 19 | 20 | module Tkr = Talker 21 | 22 | let kind = "inversion" 23 | 24 | class c = object inherit Tkr.c 25 | 26 | val mInput = Tkr.mkTalk () 27 | val mOutput = Tkr.mkVoice () 28 | 29 | method getKind = kind 30 | method! getTalks = [mInput] 31 | method! getVoices = [|mOutput|] 32 | 33 | method! talk _ tick len = 34 | let ir = Listen.talk mInput tick len ~copy:false in 35 | let irl = Listen.getLength ir in 36 | 37 | Voice.checkLength mOutput irl; 38 | 39 | for i = 0 to irl - 1 do 40 | let v = Listen.(ir@+i) 41 | in 42 | if v = 0. then Voice.set mOutput i 1. 43 | else Voice.set mOutput i (minf (1. /. v) 1.) 44 | done; 45 | 46 | Voice.setTick mOutput tick; 47 | Voice.setLength mOutput irl; 48 | end 49 | 50 | let handler = Plugin.{kind; category = "Handling"; make = fun() -> new c} 51 | -------------------------------------------------------------------------------- /src/mixer_presenter.rs: -------------------------------------------------------------------------------- 1 | 2 | use talker::identifier::{Id, RIdentifier}; 3 | 4 | use session::mixer::RMixer; 5 | 6 | use crate::output_presenter::OutputPresenter; 7 | 8 | pub struct MixerPresenter { 9 | identifier: RIdentifier, 10 | outputs: Vec, 11 | } 12 | 13 | impl MixerPresenter { 14 | pub fn new(mixer: &RMixer) -> MixerPresenter { 15 | let mxr = mixer.borrow(); 16 | let identifier = mxr.identifier().clone(); 17 | let mut outputs = Vec::with_capacity(mxr.outputs().len()); 18 | 19 | for output in mxr.outputs() { 20 | outputs.push(OutputPresenter::from(output)); 21 | } 22 | 23 | Self { 24 | identifier, 25 | outputs, 26 | } 27 | } 28 | 29 | pub fn id(&self) -> Id { 30 | self.identifier.borrow().id() 31 | } 32 | 33 | pub fn name(&self) -> String { 34 | self.identifier.borrow().name().to_string() 35 | } 36 | 37 | pub fn outputs<'a>(&'a self) -> &'a Vec { 38 | &self.outputs 39 | } 40 | 41 | pub fn mutable_outputs<'a>(&'a mut self) -> &'a mut Vec { 42 | &mut self.outputs 43 | } 44 | 45 | pub fn add_output(&mut self, output: OutputPresenter) { 46 | self.outputs.push(output); 47 | } 48 | 49 | pub fn remove_output(&mut self, id: Id) { 50 | 51 | for idx in 0..self.outputs.len() { 52 | if self.outputs[idx].identifier().id() == id { 53 | self.outputs.remove(idx); 54 | break; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /talker/src/hidden_constant_talker.rs: -------------------------------------------------------------------------------- 1 | use crate::talker::{Talker, TalkerBase}; 2 | use crate::voice::Voice; 3 | 4 | pub struct HiddenConstantTalker { 5 | base: TalkerBase, 6 | value: f32, 7 | } 8 | 9 | impl HiddenConstantTalker { 10 | pub fn new(value: Option) -> HiddenConstantTalker { 11 | Self { 12 | base: TalkerBase::new(), 13 | value: value.unwrap_or(1.), 14 | } 15 | } 16 | } 17 | 18 | impl Talker for HiddenConstantTalker { 19 | fn base<'b>(&'b self) -> &'b TalkerBase { 20 | &self.base 21 | } 22 | } 23 | /* 24 | and ?(value = 1.) () = 25 | object(self) inherit c 26 | val mutable mOutput = None 27 | 28 | method provideOutput = 29 | match mOutput with 30 | | Some o -> o 31 | | None -> 32 | let o = {vTag = defOutputTag; port = 0; tick = 0; len = 1; 33 | tkr = self#base; cor = Cornet.init ~v:value 1} 34 | in 35 | mOutput <- Some o; 36 | o 37 | 38 | 39 | method! getValue = Float (Voice.get self#provideOutput 0) 40 | 41 | 42 | method! setValue v = 43 | let output = self#provideOutput in 44 | fill output 0 (dim output) (v2f v) 45 | 46 | method getKind = "hiddenConstant" 47 | method! isHidden = true 48 | method! getVoices = [|self#provideOutput|] 49 | 50 | method! talk _ tick len = 51 | let output = self#provideOutput in 52 | 53 | if output.len < len then ( 54 | output.cor <- Cornet.init ~v:(Cornet.get output.cor 0) len; 55 | output.len <- len; 56 | ); 57 | output.tick <- tick; 58 | end 59 | */ 60 | -------------------------------------------------------------------------------- /session/src/talkers/fuzz.rs: -------------------------------------------------------------------------------- 1 | use talker::ctalker; 2 | use talker::ear; 3 | use talker::ear::Init; 4 | use talker::talker::{CTalker, Talker, TalkerBase}; 5 | use talker::talker_handler::TalkerHandlerBase; 6 | 7 | pub const MODEL: &str = "Fuzz"; 8 | 9 | pub struct Fuzz {} 10 | impl Fuzz { 11 | pub fn new(mut base: TalkerBase) -> Result { 12 | base.add_ear(ear::audio(None, -1., 1., 0., &Init::DefValue)?); 13 | base.add_ear(ear::cv(Some("iterations"), 0., 100., 1., &Init::DefValue)?); 14 | 15 | base.add_audio_voice(None, 0.); 16 | 17 | Ok(ctalker!(base, Self {})) 18 | } 19 | 20 | pub fn descriptor() -> TalkerHandlerBase { 21 | TalkerHandlerBase::builtin("Modulator", MODEL, MODEL) 22 | } 23 | } 24 | 25 | impl Talker for Fuzz { 26 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 27 | let ln = base.listen(tick, len); 28 | let src_buf = base.ear_audio_buffer(0); 29 | let iterations_buf = base.ear_cv_buffer(1); 30 | let voice_buf = base.voice(port).audio_buffer(); 31 | 32 | for i in 0..ln { 33 | let mut input = src_buf[i]; 34 | let num_iter = iterations_buf[i] as usize; 35 | 36 | for _ in 0..num_iter { 37 | if input < 0.0 { 38 | let negated = input + 1.0; 39 | input = (negated * negated * negated) - 1.0; 40 | } else { 41 | let negated = input - 1.0; 42 | input = (negated * negated * negated) + 1.0; 43 | } 44 | } 45 | voice_buf[i] = input; 46 | } 47 | 48 | ln 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/undo_redo_list.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct UndoRedoList { 3 | states: Vec, 4 | position: usize, 5 | } 6 | 7 | impl UndoRedoList { 8 | pub fn new(init_state: String) -> UndoRedoList { 9 | let mut states = Vec::with_capacity(1024); 10 | states.push(init_state); 11 | 12 | Self { 13 | states, 14 | position: 0, 15 | } 16 | } 17 | 18 | pub fn reset(&mut self) { 19 | self.states.clear(); 20 | self.position = 0; 21 | } 22 | 23 | pub fn new_state(&mut self, state: String) { 24 | 25 | let mut end_pos = self.end_position(); 26 | 27 | if self.position < end_pos { 28 | let mut start_pos = self.position; 29 | let step_count = (end_pos - start_pos + 1) / 2; 30 | 31 | for _ in 0..step_count { 32 | self.states.swap(start_pos, end_pos); 33 | start_pos += 1; 34 | end_pos -= 1; 35 | } 36 | } 37 | self.states.push(state); 38 | self.position = self.end_position(); 39 | } 40 | 41 | pub fn undo(&mut self) -> Option { 42 | if self.position > 0 { 43 | self.position -= 1; 44 | Some(self.states[self.position].clone()) 45 | } 46 | else { 47 | None 48 | } 49 | } 50 | 51 | pub fn redo(&mut self) -> Option { 52 | if self.position < self.end_position() { 53 | self.position += 1; 54 | Some(self.states[self.position].clone()) 55 | } 56 | else { 57 | None 58 | } 59 | } 60 | 61 | fn end_position(&self) -> usize { 62 | self.states.len().max(1) - 1 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /plugins/temporalInversion.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (C) 2015 Gaetan Dubreil 3 | * 4 | * All rights reserved.This file is distributed under the terms of the 5 | * GNU General Public License version 3.0. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU Lesser General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU Lesser General Public License 13 | * along with this program; if not, write to the Free Software 14 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | *) 16 | 17 | open Graffophone_plugin 18 | 19 | module Tkr = Talker 20 | 21 | let kind = "temporalInversion" 22 | 23 | class c = object inherit Tkr.c 24 | 25 | val mInput = Tkr.mkTalk () 26 | val mPeriod = Tkr.mkTalk ~tag:"period" () 27 | val mOutput = Tkr.mkVoice () 28 | 29 | method getKind = kind 30 | method! getTalks = [mInput; mPeriod] 31 | method! getVoices = [|mOutput|] 32 | 33 | method! talk _ tick len = 34 | let ir = Listen.talk mInput tick len in 35 | let gr = Listen.talk mInput tick (Listen.getLength ir) ~copy:false in 36 | let grl = Listen.getLength gr in 37 | 38 | Voice.checkLength mOutput grl; 39 | 40 | for i = 0 to grl - 1 do 41 | Voice.set mOutput i Listen.((ir@+i) *. (gr@+i)) 42 | done; 43 | 44 | Voice.setTick mOutput tick; 45 | Voice.setLength mOutput grl; 46 | end 47 | 48 | let handler = Plugin.{kind; category = "Handling"; make = fun() -> new c} 49 | 50 | (* 51 | let registerPlugin fileName = 52 | Factory.addTalkerMaker kind "Handling" make; 53 | print_string ("Plugin "^fileName^" registered\n"); 54 | flush stdout;; 55 | 56 | Factory.registerPlugin := registerPlugin; 57 | *) 58 | -------------------------------------------------------------------------------- /session/src/talkers/static_sine.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | 3 | use talker::audio_format::AudioFormat; 4 | use talker::ear; 5 | use talker::ear::Init; 6 | use talker::talker::{Talker, TalkerBase}; 7 | use talker::talker_handler::TalkerHandlerBase; 8 | use talker::voice; 9 | 10 | pub struct AbsSine { 11 | base: TalkerBase, 12 | } 13 | 14 | impl AbsSine { 15 | pub fn new(mut base: TalkerBase) -> Result { 16 | let freq = ear::audio(Some("freq".to_string()), 0., 20000., 440., Init::DefValue)?; 17 | base.add_ear(freq); 18 | base.add_audio_voice(None, 0.); 19 | 20 | Ok(Self { base }) 21 | } 22 | } 23 | 24 | impl Talker for AbsSine { 25 | fn base<'a>(&'a self) -> &'a TalkerBase { 26 | &self.base 27 | } 28 | /* 29 | fn activate(&mut self) {} 30 | fn deactivate(&mut self) {} 31 | */ 32 | fn talk(&mut self, _port: usize, tick: i64, len: usize) -> usize { 33 | let mut ln = len; 34 | let c = (PI * 2.0) / AudioFormat::sample_rate() as f64; 35 | 36 | for ear in self.ears() { 37 | ln = ear::listen(ear, tick, ln); 38 | } 39 | for voice in self.voices() { 40 | let freq_buf = self.ear_audio_buffer(0).borrow(); 41 | let mut vc = voice.borrow_mut(); 42 | let voice_buf = vc.audio_buffer().borrow_mut(); 43 | 44 | for i in 0..ln { 45 | let sample = ((tick + i as i64) as f64 * freq_buf[i] as f64 * c).sin() as f32; 46 | voice_buf[i] = sample; 47 | } 48 | vc.set_len(ln); 49 | vc.set_tick(tick); 50 | } 51 | ln 52 | } 53 | } 54 | 55 | pub fn id() -> &'static str { 56 | "AbsSine" 57 | } 58 | pub fn descriptor() -> TalkerHandlerBase { 59 | TalkerHandlerBase::new(id(), "Absolute sinusoidal", "Generator") 60 | } 61 | -------------------------------------------------------------------------------- /talker/src/dsp.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::f64; 3 | 4 | use audio_format::AudioFormat; 5 | 6 | pub fn audioize_buffer_by_clipping(buffer: &mut [f32], start: usize, len: usize) { 7 | for i in start..len { 8 | let v = buffer[i]; 9 | 10 | if v < AudioFormat::MIN_AUDIO { 11 | buffer[i] = AudioFormat::MIN_AUDIO; 12 | } else if v > AudioFormat::MAX_AUDIO { 13 | buffer[i] = AudioFormat::MAX_AUDIO; 14 | } 15 | } 16 | } 17 | 18 | 19 | pub fn audioize_buffer_by_tanh(buffer: &mut [f32], start: usize, len: usize) { 20 | for i in start..len { 21 | buffer[i] = buffer[i].tanh(); 22 | } 23 | } 24 | 25 | pub fn audioize_buffer_by_atan(buffer: &mut [f32], start: usize, len: usize) { 26 | for i in start..len { 27 | buffer[i] = buffer[i].atan(); 28 | } 29 | } 30 | 31 | pub fn fade_len(sample_rate: usize) -> usize { 32 | sample_rate / 100 33 | } 34 | 35 | fn fade_from_value_buffer(buffer: &mut [f32], start: usize, len: usize, value: f32) { 36 | let step = 1. / len as f32; 37 | let mut c = 0.; 38 | 39 | for i in start..(start + len) { 40 | buffer[i] = value * (1. - c) + buffer[i] * c; 41 | c += step; 42 | } 43 | } 44 | 45 | pub fn recoveryless_fade_buffer(sample_rate: usize, buffer: &mut [f32], start: usize, vm2: f32, vm1: f32) { 46 | let dt = 1. / sample_rate as f64; 47 | 48 | let v1 = vm2 as f64; 49 | let mut v2 = vm1 as f64; 50 | let mut g = (v2 - v1) / dt; 51 | 52 | let mut idx = start; 53 | 54 | while g > 0.25 || g < -0.25 { 55 | v2 += g * dt; 56 | 57 | if v2 > 1. { 58 | break; 59 | } 60 | buffer[idx] = v2 as f32; 61 | g *= 0.9; 62 | idx += 1; 63 | } 64 | let remaining_len = fade_len(sample_rate) - (idx - start); 65 | 66 | fade_from_value_buffer(buffer, idx, remaining_len, v2 as f32); 67 | } 68 | -------------------------------------------------------------------------------- /talker/src/audio_format.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | const CHANNELS: usize = 2; 5 | const DEFAULT_SAMPLE_RATE: usize = 48000; // 44_100; 6 | static SAMPLE_RATE: AtomicUsize = AtomicUsize::new(DEFAULT_SAMPLE_RATE); 7 | //const FRAMES_PER_SECOND: usize = 10; 8 | pub const DEFAULT_CHUNK_SIZE: usize = 2048; //4410; 9 | static DYNAMIC_CHUNK_SIZE: AtomicUsize = AtomicUsize::new(DEFAULT_CHUNK_SIZE); 10 | 11 | pub const MIN_AUDIO: f32 = -0.99999; 12 | pub const MAX_AUDIO: f32 = 0.99999; 13 | pub const DEF_AUDIO: f32 = 0.; 14 | pub const MIN_CONTROL: f32 = 0.; 15 | pub const MAX_CONTROL: f32 = 20000.; 16 | pub const DEF_CONTROL: f32 = 0.; 17 | pub const MIN_CV: f32 = 0.; 18 | pub const MAX_CV: f32 = 20000.; 19 | pub const DEF_CV: f32 = 0.; 20 | 21 | pub struct AudioFormat { 22 | pub sample_rate: usize, // samples per second (in a single channel) 23 | pub channels: usize, // number of audio channels 24 | } 25 | 26 | impl AudioFormat { 27 | pub const CHUNK_SIZE: usize = 2048; //4410; 28 | pub const MIN_AUDIO: f32 = MIN_AUDIO; 29 | pub const MAX_AUDIO: f32 = MAX_AUDIO; 30 | pub const DEF_AUDIO: f32 = DEF_AUDIO; 31 | 32 | pub fn default() -> AudioFormat { 33 | AudioFormat { 34 | sample_rate: DEFAULT_SAMPLE_RATE, 35 | channels: CHANNELS, 36 | } 37 | } 38 | pub fn sample_rate() -> usize { 39 | SAMPLE_RATE.load(Ordering::Relaxed) 40 | } 41 | pub fn set_sample_rate(sample_rate: usize) { 42 | SAMPLE_RATE.store(sample_rate, Ordering::Relaxed); 43 | } 44 | pub fn chunk_size() -> usize { 45 | DYNAMIC_CHUNK_SIZE.load(Ordering::Relaxed) 46 | } 47 | pub fn set_chunk_size(chunk_size: usize) { 48 | DYNAMIC_CHUNK_SIZE.store(chunk_size, Ordering::Relaxed); 49 | } 50 | pub fn frequence_coef() -> f64 { 51 | (PI * 2.0) / SAMPLE_RATE.load(Ordering::Relaxed) as f64 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /session/src/output.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | //use std::boxed::Box; 4 | 5 | extern crate failure; 6 | 7 | use talker::identifier::{Identifier, RIdentifier}; 8 | 9 | use crate::audio_data::Vector; 10 | 11 | pub const KIND: &str = "output"; 12 | 13 | const EMPTY_STR: &str = ""; 14 | 15 | pub fn new_identifier(name: &str, model: &str) -> RIdentifier { 16 | RefCell::new(Identifier::new(name, model)) 17 | } 18 | 19 | pub trait Output { 20 | fn identifier<'a>(&'a self) -> &'a RIdentifier; 21 | 22 | fn id(&self) -> u32 { 23 | self.identifier().borrow().id() 24 | } 25 | 26 | fn model(&self) -> String { 27 | self.identifier().borrow().model().to_string() 28 | } 29 | 30 | fn name(&self) -> String { 31 | self.identifier().borrow().name().to_string() 32 | } 33 | fn set_name(&self, name: &str) { 34 | self.identifier().borrow_mut().set_name(name); 35 | } 36 | 37 | fn codec_name<'a>(&'a self) -> &'a str { 38 | EMPTY_STR 39 | } 40 | 41 | fn sample_rate(&self) -> usize; 42 | 43 | fn channel_layout<'a>(&'a self) -> &'a str; 44 | 45 | fn channels(&self) -> usize; 46 | 47 | fn channels_names(&self) -> Vec<&'static str>; 48 | 49 | fn file_path<'a>(&'a self) -> &'a str { 50 | EMPTY_STR 51 | } 52 | 53 | fn open(&mut self) -> Result<(), failure::Error>; 54 | 55 | fn write( 56 | &mut self, 57 | channels: &Vec, 58 | nb_samples_per_channel: usize, 59 | ) -> Result<(), failure::Error>; 60 | 61 | fn pause(&mut self) -> Result<(), failure::Error>; 62 | 63 | fn run(&mut self) -> Result<(), failure::Error>; 64 | 65 | fn close(&mut self) -> Result<(), failure::Error>; 66 | 67 | // kind model configuration 68 | fn backup(&self) -> (&str, &str, String); 69 | } 70 | 71 | pub type ROutput = Rc>; 72 | //pub type ROutput = Box; 73 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | 2 | use gio::{prelude::ApplicationExt, ActionEntry}; 3 | use crate::gio::prelude::ActionMapExtManual; 4 | 5 | use crate::session_presenter::RSessionPresenter; 6 | use crate::ui::general_settings; 7 | use crate::ui::session_settings; 8 | 9 | pub fn create_actions_entries(app: >k::Application, session_presenter: &RSessionPresenter,) { 10 | 11 | let toggle_feedback = ActionEntry::builder("toggle_feedback") 12 | .activate(|_, _, _| println!("TODO : toggle_feedback")) 13 | .build(); 14 | 15 | let rssp = session_presenter.clone(); 16 | let general_settings = ActionEntry::builder("general_settings") 17 | .activate(move |app, _, _| general_settings::expose(app, &rssp)) 18 | .build(); 19 | 20 | let ossp = session_presenter.clone(); 21 | let session_settings = ActionEntry::builder("session_settings") 22 | .activate(move |app, _, _| session_settings::expose(app, &ossp)) 23 | .build(); 24 | 25 | let about = ActionEntry::builder("about") 26 | .activate(|_, _, _| println!("About was pressed")) 27 | .build(); 28 | 29 | let quit = ActionEntry::builder("quit") 30 | .activate(|app: >k::Application, _, _| app.quit()) 31 | .build(); 32 | 33 | app.add_action_entries([toggle_feedback, general_settings, session_settings, about, quit]); 34 | } 35 | 36 | pub fn menu() -> gio::Menu { 37 | let menu = gio::Menu::new(); 38 | 39 | // TODO : menu.append(Some("Feedback"), Some("app.toggle_feedback")); 40 | menu.append(Some("General settings"), Some("app.general_settings")); 41 | menu.append(Some("Session settings"), Some("app.session_settings")); 42 | 43 | menu 44 | } 45 | 46 | pub fn get_directory() -> std::path::PathBuf { 47 | match dirs::config_local_dir() { 48 | Some(path) => { 49 | path.join("graffophone") 50 | } 51 | None => { 52 | match dirs::home_dir() { 53 | Some(path) => { 54 | path.join(".graffophone") 55 | } 56 | None => std::path::PathBuf::from(".graffophone") 57 | } 58 | }, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /gramotocaml/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate ocaml; 3 | 4 | extern crate failure; 5 | extern crate gramotor; 6 | 7 | use std::mem; 8 | 9 | use ocaml::Value; 10 | 11 | use gramotor::gramotor::Gramotor; 12 | 13 | extern "C" fn finalize(value: ocaml::core::Value) { 14 | let handle = Value(value); 15 | let ptr = handle.custom_ptr_val_mut::(); 16 | mem::drop(ptr); 17 | println!("Finalize Gramotor"); 18 | } 19 | 20 | caml!(gramotor_create() { 21 | let mut gramotor = Gramotor::new(); 22 | let ptr = &mut gramotor as *mut Gramotor; 23 | mem::forget(gramotor); 24 | Value::alloc_custom(ptr, finalize) 25 | }); 26 | 27 | caml!(gramotor_new_session(handle) { 28 | let motor = &mut *handle.custom_ptr_val_mut::(); 29 | let _ = motor.new_session(); 30 | Value::unit() 31 | }); 32 | 33 | caml!(gramotor_init_session(handle, session_description) { 34 | let motor = &mut *handle.custom_ptr_val_mut::(); 35 | let sd = ocaml::Str::from(session_description.clone()); 36 | let _ = motor.init_session(sd.as_str().to_string()); 37 | Value::unit() 38 | }); 39 | 40 | caml!(gramotor_start(handle) { 41 | let motor = &mut *handle.custom_ptr_val_mut::(); 42 | match motor.start() { 43 | Ok(()) => Value::unit(), 44 | Err(e)=>{ocaml::runtime::failwith(format!("{}", e)); Value::unit()} 45 | } 46 | }); 47 | caml!(gramotor_play(handle) { 48 | let motor = &mut *handle.custom_ptr_val_mut::(); 49 | match motor.play() { 50 | Ok(()) => Value::unit(), 51 | Err(e)=>{ocaml::runtime::failwith(format!("{}", e)); Value::unit()} 52 | } 53 | }); 54 | caml!(gramotor_pause(handle) { 55 | let motor = &mut *handle.custom_ptr_val_mut::(); 56 | match motor.pause() { 57 | Ok(()) => Value::unit(), 58 | Err(e)=>{ocaml::runtime::failwith(format!("{}", e)); Value::unit()} 59 | } 60 | }); 61 | caml!(gramotor_stop(handle) { 62 | let motor = &mut *handle.custom_ptr_val_mut::(); 63 | match motor.stop() { 64 | Ok(()) => Value::unit(), 65 | Err(e)=>{ocaml::runtime::failwith(format!("{}", e)); Value::unit()} 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate failure; 2 | 3 | extern crate cairo; 4 | extern crate gio; 5 | extern crate gtk; 6 | 7 | extern crate session; 8 | 9 | use crate::gtk::prelude::ApplicationExt; 10 | use crate::gtk::prelude::ApplicationExtManual; 11 | use gtk::gdk::Display; 12 | use gtk::prelude::GtkApplicationExt; 13 | use gtk::{CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION}; 14 | 15 | use session::event_bus::EventBus; 16 | 17 | mod application_view; 18 | mod graph_control; 19 | mod graph_presenter; 20 | mod graph_view; 21 | mod mixer_control; 22 | mod mixer_presenter; 23 | mod output_presenter; 24 | mod session_actions; 25 | mod session_presenter; 26 | mod settings; 27 | mod style; 28 | mod talker_control; 29 | mod talker_data_view; 30 | mod ui; 31 | mod undo_redo_list; 32 | mod util; 33 | 34 | use application_view::ApplicationView; 35 | use session_presenter::SessionPresenter; 36 | 37 | fn main() { 38 | ui::plugin_ui::init(); 39 | 40 | let application = 41 | gtk::Application::new(Some("com.gitlab.gndl.graffophone"), Default::default()); 42 | 43 | application.connect_startup(|_: >k::Application| { 44 | // The CSS "magic" happens here. 45 | let provider = CssProvider::new(); 46 | provider.load_from_string(include_str!("css/style.css").as_ref()); 47 | // We give the CssProvided to the default screen so the CSS rules we added 48 | // can be applied to our window. 49 | gtk::style_context_add_provider_for_display( 50 | &Display::default().expect("Could not connect to a display."), 51 | &provider, 52 | STYLE_PROVIDER_PRIORITY_APPLICATION, 53 | ); 54 | }); 55 | 56 | application.connect_activate(|app| { 57 | sourceview5::init(); 58 | let event_bus = EventBus::new_ref(); 59 | 60 | let session_presenter = SessionPresenter::new_ref(&event_bus); 61 | 62 | settings::create_actions_entries(app, &session_presenter); 63 | app.set_accels_for_action("window.close", &["Q"]); 64 | 65 | match ApplicationView::new_ref(app, &session_presenter, &event_bus) { 66 | Ok(_) => session_presenter.borrow().init(), 67 | Err(e) => eprintln!("{}", e), 68 | } 69 | }); 70 | 71 | application.run(); 72 | 73 | sourceview5::finalize(); 74 | } 75 | -------------------------------------------------------------------------------- /session/src/event_bus.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use talker::identifier::{Id, Index}; 5 | 6 | use crate::state::State; 7 | 8 | pub enum Notification { 9 | State(State), 10 | NewSession(String), 11 | SessionSaved, 12 | SessionSavedAs(String), 13 | Tick(i64), 14 | TimeRange(i64, i64), 15 | Pause, 16 | End, 17 | Volume(f32), 18 | TalkersRange(Vec<(String, Vec<(String, String)>)>), 19 | NewTalker, 20 | TalkerChanged, 21 | TalkerRenamed(Id), 22 | EarSelected(Id, Index, Index, Index), 23 | EarUnselected(Id, Index, Index, Index), 24 | EarAddInSelected(Id, Index, Index, Index), 25 | EarAddInUnselected(Id, Index, Index, Index), 26 | EarValueSelected(Id, Index, Index, Index), 27 | VoiceSelected(Id, Index), 28 | VoiceUnselected(Id, Index), 29 | TalkSelected(Id, Index), 30 | SelectionChanged, 31 | EditTalkerData(Id), 32 | CurveAdded, 33 | CurveRemoved, 34 | Info(String), 35 | Warning(String), 36 | Error(String), 37 | } 38 | pub type RObserver = Box; 39 | 40 | pub struct EventBus { 41 | observers: Vec, 42 | } 43 | 44 | pub type REventBus = Rc>; 45 | 46 | impl EventBus { 47 | pub fn new() -> EventBus { 48 | Self { 49 | observers: Vec::new(), 50 | } 51 | } 52 | 53 | pub fn new_ref() -> REventBus { 54 | Rc::new(RefCell::new(EventBus::new())) 55 | } 56 | 57 | pub fn add_observer(&mut self, observer: RObserver) { 58 | self.observers.push(observer); 59 | } 60 | 61 | pub fn notify(&self, notification: Notification) { 62 | for obs in &self.observers { 63 | obs(¬ification); 64 | } 65 | } 66 | 67 | pub fn notify_error(&self, error: failure::Error) { 68 | self.notify(Notification::Error(format!("{}", error))); 69 | } 70 | 71 | pub fn notify_notifications_result( 72 | &self, 73 | notifications_result: Result, failure::Error>, 74 | ) { 75 | match notifications_result { 76 | Ok(notifications) => { 77 | for notification in notifications { 78 | self.notify(notification); 79 | } 80 | } 81 | Err(e) => self.notify_error(e), 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /session/src/talkers/sinusoidal.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::f64::consts::PI; 3 | 4 | use talker::audio_format::AudioFormat; 5 | use talker::ctalker; 6 | use talker::ear; 7 | use talker::ear::Init; 8 | use talker::talker::{CTalker, Talker, TalkerBase}; 9 | use talker::talker_handler::TalkerHandlerBase; 10 | 11 | pub const MODEL: &str = "Sinusoidal"; 12 | 13 | pub struct Sinusoidal { 14 | frequence_coef: f64, 15 | last_tick: i64, 16 | last_angle: f64, 17 | } 18 | 19 | impl Sinusoidal { 20 | pub fn new(mut base: TalkerBase) -> Result { 21 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 22 | base.add_ear(ear::audio(Some("phase"), -1., 2., 0., &Init::DefValue)?); 23 | base.add_ear(ear::audio(Some("gain"), -1., 1., 1., &Init::DefValue)?); 24 | 25 | base.add_audio_voice(None, 0.); 26 | 27 | Ok(ctalker!( 28 | base, 29 | Self { 30 | frequence_coef: AudioFormat::frequence_coef(), 31 | last_tick: 0, 32 | last_angle: 0., 33 | } 34 | )) 35 | } 36 | 37 | pub fn descriptor() -> TalkerHandlerBase { 38 | TalkerHandlerBase::builtin("Oscillator", MODEL, MODEL) 39 | } 40 | } 41 | 42 | impl Talker for Sinusoidal { 43 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 44 | let ln = base.listen(tick, len); 45 | let freq_buf = base.ear_cv_buffer(0); 46 | let phase_buf = base.ear_audio_buffer(1); 47 | let gain_buf = base.ear_audio_buffer(2); 48 | let voice_buf = base.voice(port).audio_buffer(); 49 | let c = self.frequence_coef; 50 | 51 | let mut last_angle = if self.last_tick == tick { 52 | self.last_angle 53 | } else { 54 | if tick == 0 { 55 | 0. 56 | } else { 57 | -freq_buf[0] as f64 * c 58 | } 59 | }; 60 | 61 | for i in 0..ln { 62 | let f = freq_buf[i] as f64; 63 | let a = last_angle + c * f; 64 | let p = phase_buf[i] as f64 * PI; 65 | 66 | voice_buf[i] = ((a + p).sin() as f32) * gain_buf[i]; 67 | last_angle = a; 68 | } 69 | 70 | self.last_angle = last_angle; 71 | self.last_tick = tick + ln as i64; 72 | ln 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /session/src/talkers/damped_sinusoidal.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::f64::consts::PI; 3 | 4 | use talker::audio_format::AudioFormat; 5 | use talker::ctalker; 6 | use talker::ear; 7 | use talker::ear::Init; 8 | use talker::talker::{CTalker, Talker, TalkerBase}; 9 | use talker::talker_handler::TalkerHandlerBase; 10 | 11 | pub const MODEL: &str = "Sinusoidal"; 12 | 13 | pub struct Sinusoidal { 14 | last_tick: i64, 15 | last_angle: f64, 16 | } 17 | 18 | impl Sinusoidal { 19 | pub fn new(mut base: TalkerBase) -> Result { 20 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 21 | base.add_ear(ear::audio(Some("phase"), -1., 2., 0., &Init::DefValue)?); 22 | base.add_ear(ear::audio(Some("gain"), -1., 1., 1., &Init::DefValue)?); 23 | 24 | base.add_audio_voice(None, 0.); 25 | 26 | Ok(ctalker!( 27 | base, 28 | Self { 29 | last_tick: 0, 30 | last_angle: 0., 31 | } 32 | )) 33 | } 34 | 35 | pub fn descriptor() -> TalkerHandlerBase { 36 | TalkerHandlerBase::new("Oscillator", MODEL, "Sin") 37 | } 38 | } 39 | 40 | impl Talker for Sinusoidal { 41 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 42 | let ln = base.listen(tick, len); 43 | let freq_buf = base.ear_cv_buffer(0); 44 | let phase_buf = base.ear_audio_buffer(1); 45 | let gain_buf = base.ear_audio_buffer(2); 46 | let voice_buf = base.voice(port).audio_buffer(); 47 | let c = AudioFormat::frequence_coef(); 48 | 49 | let mut last_angle = if self.last_tick == tick { 50 | self.last_angle 51 | } else { 52 | -freq_buf[0] as f64 * c 53 | }; 54 | 55 | for i in 0..ln { 56 | let gain = gain_buf[i]; 57 | 58 | if gain == 0. { 59 | last_angle = 0.; 60 | 61 | if i > 0 { 62 | voice_buf[i] = voice_buf[i - 1] * 0.95; 63 | } else { 64 | voice_buf[i] = 0.; 65 | } 66 | } else { 67 | let a = last_angle + freq_buf[i] as f64 * c; 68 | let p = phase_buf[i] as f64 * PI; 69 | 70 | voice_buf[i] = ((a + p).sin() as f32) * gain; 71 | last_angle = a; 72 | } 73 | } 74 | 75 | self.last_angle = last_angle; 76 | self.last_tick = tick + ln as i64; 77 | ln 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/track_control.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Gaetan Dubreil 3 | * 4 | * All rights reserved.This file is distributed under the terms of the 5 | * GNU General Public License version 3.0. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU Lesser General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU Lesser General Public License 13 | * along with this program; if not, write to the Free Software 14 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | */ 16 | use std::cell::RefCell; 17 | use std::rc::Rc; 18 | 19 | use cairo::Context; 20 | 21 | use talker::talker::RTalker; 22 | 23 | use session::track::RTrack; 24 | 25 | use crate::graph_presenter::GraphPresenter; 26 | use crate::talker_control::{ 27 | ControlSupply, RTalkerControl, RTalkerControlBase, TalkerControl, TalkerControlBase, 28 | }; 29 | 30 | pub struct TrackControl { 31 | base: RTalkerControlBase, 32 | } 33 | 34 | impl TrackControl { 35 | pub fn new( 36 | rtrack: &RTrack, 37 | control_supply: &ControlSupply, 38 | ) -> Result { 39 | let rtalker: RTalker = rtrack.clone(); 40 | let base = 41 | TalkerControlBase::new_ref(&rtalker, control_supply, false, false, false, false)?; 42 | 43 | Ok(Self { base }) 44 | } 45 | pub fn new_ref( 46 | rtrack: &RTrack, 47 | control_supply: &ControlSupply, 48 | ) -> Result { 49 | Ok(Rc::new(RefCell::new(TrackControl::new( 50 | rtrack, 51 | control_supply, 52 | )?))) 53 | } 54 | } 55 | 56 | impl TalkerControl for TrackControl { 57 | fn base<'a>(&'a self) -> &'a RTalkerControlBase { 58 | &self.base 59 | } 60 | /* 61 | _____________________ 62 | | NAME | 63 | |in # | 64 | |gain # | 65 | |channelGain 1 # | 66 | |channelGain 2 # | 67 | |_____________________| 68 | */ 69 | 70 | fn draw(&self, cc: &Context, graph_presenter: &GraphPresenter) { 71 | let base = self.base.borrow(); 72 | base.draw_box(cc, graph_presenter); 73 | // base.draw_header(cc); 74 | 75 | base.draw_ears_and_voices(cc, graph_presenter); 76 | } 77 | 78 | fn move_to(&mut self, _x: f64, _y: f64) { // the move is managed by the mixer 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /session/src/talkers/tseq/pitch.rs: -------------------------------------------------------------------------------- 1 | use std::usize; 2 | 3 | use scale::scale::Scale; 4 | 5 | use talkers::tseq::parser::{PPitch, PShape}; 6 | 7 | #[derive(Debug, PartialEq, Clone)] 8 | pub struct Pitch { 9 | pub id: String, 10 | pub transition: PShape, 11 | } 12 | impl Pitch { 13 | pub fn new(pitch: &Pitch) -> Pitch { 14 | Self { 15 | id: pitch.id.clone(), 16 | transition: pitch.transition, 17 | } 18 | } 19 | 20 | pub fn from_parser_pitch(ppitch: &PPitch) -> Pitch { 21 | Self { 22 | id: String::from(ppitch.id), 23 | transition: ppitch.transition, 24 | } 25 | } 26 | } 27 | 28 | pub fn notes_shift(pitchs: &mut Vec, shift_count: usize) -> Result<(), failure::Error> { 29 | 30 | if shift_count >= pitchs.len() { 31 | return Err(failure::err_msg(format!("Pitchline notes shift count {} invalide!", shift_count))); 32 | } 33 | pitchs.rotate_left(shift_count); 34 | Ok(()) 35 | } 36 | 37 | pub fn backward_notes_shift(pitchs: &mut Vec, shift_count: usize) -> Result<(), failure::Error> { 38 | 39 | if shift_count >= pitchs.len() { 40 | return Err(failure::err_msg(format!("Pitchline notes shift count {} invalide!", shift_count))); 41 | } 42 | pitchs.rotate_left(shift_count); 43 | pitchs.reverse(); 44 | Ok(()) 45 | } 46 | 47 | pub fn pitchs_transposition(pitchs: &mut Vec, scale: &Scale, initial_pitch: &str, final_pitch: &str) -> Result<(), failure::Error> { 48 | 49 | let init = scale.pitch_name_to_number(initial_pitch)? as i64; 50 | let fin = scale.pitch_name_to_number(final_pitch)? as i64; 51 | let dn = fin - init; 52 | 53 | for pitch in pitchs { 54 | let new_number = (scale.pitch_name_to_number(&pitch.id)? as i64 + dn) as usize; 55 | pitch.id = scale.pitch_number_to_name(new_number); 56 | } 57 | Ok(()) 58 | } 59 | 60 | pub fn pitchs_inversion(pitchs: &mut Vec, scale: &Scale) -> Result<(), failure::Error> { 61 | 62 | let pitchs_count = pitchs.len(); 63 | let mut n_min = usize::MAX; 64 | let mut n_max = usize::MIN; 65 | let mut nums = Vec::with_capacity(pitchs_count); 66 | 67 | for pitch in &mut *pitchs { 68 | let num = scale.pitch_name_to_number(&pitch.id)?; 69 | n_min = n_min.min(num); 70 | n_max = n_max.max(num); 71 | nums.push(num); 72 | } 73 | let min_plus_max = n_min + n_max; 74 | 75 | for i in 0..pitchs_count { 76 | let new_number = min_plus_max - nums[i]; 77 | pitchs[i].id = scale.pitch_number_to_name(new_number); 78 | } 79 | Ok(()) 80 | } 81 | -------------------------------------------------------------------------------- /talker/src/identifier.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use std::sync::atomic::{AtomicU32, Ordering}; 4 | 5 | static ID_COUNT: AtomicU32 = AtomicU32::new(1); 6 | 7 | pub type Id = u32; 8 | pub type Index = usize; 9 | 10 | pub fn get_next_id() -> Id { 11 | ID_COUNT.load(Ordering::SeqCst) 12 | } 13 | 14 | #[derive(Clone)] 15 | pub struct Identifier { 16 | id: Id, 17 | name: String, 18 | model: String, 19 | } 20 | 21 | impl Identifier { 22 | pub fn initialize_id_count() { 23 | ID_COUNT.store(1, Ordering::SeqCst); 24 | } 25 | pub fn new(name: &str, model: &str) -> Self { 26 | let id = ID_COUNT.fetch_add(1, Ordering::SeqCst); 27 | 28 | let name_model_fm = |close_char| { 29 | if model.is_empty() { 30 | format!("{}{}{}", name, id, close_char) 31 | } else { 32 | format!("{}{} {}{}", name, model, id, close_char) 33 | } 34 | }; 35 | 36 | let name = if name.is_empty() { 37 | if model.is_empty() { 38 | format!("{}", id) 39 | } else { 40 | format!("{} {}", model, id) 41 | } 42 | } else { 43 | if name.ends_with("(") { 44 | name_model_fm(")") 45 | } else if name.ends_with("[") { 46 | name_model_fm("]") 47 | } else { 48 | name.to_string() 49 | } 50 | }; 51 | 52 | Self { 53 | id, 54 | name, 55 | model: model.to_string(), 56 | } 57 | } 58 | 59 | pub fn id(&self) -> Id { 60 | self.id 61 | } 62 | pub fn set_id(&mut self, id: Id) { 63 | self.id = id; 64 | if id >= ID_COUNT.load(Ordering::SeqCst) { 65 | ID_COUNT.store(id + 1, Ordering::SeqCst); 66 | } 67 | } 68 | pub fn name<'a>(&'a self) -> &'a str { 69 | &self.name 70 | } 71 | pub fn set_name(&mut self, name: &str) { 72 | self.name = name.to_string(); 73 | } 74 | 75 | pub fn model<'a>(&'a self) -> &'a str { 76 | &self.model 77 | } 78 | pub fn set_model(&mut self, model: &str) { 79 | self.model = model.to_string(); 80 | } 81 | 82 | pub fn is(&self, id: Id) -> bool { 83 | self.id == id 84 | } 85 | } 86 | 87 | pub type RIdentifier = RefCell; 88 | 89 | pub trait Identifiable { 90 | fn id(&self) -> Id; 91 | fn set_id(&self, id: Id); 92 | fn name(&self) -> String; 93 | fn set_name(&self, name: &str); 94 | } 95 | pub type CIdentifiable = RefCell; 96 | pub type RIdentifiable = Rc; 97 | -------------------------------------------------------------------------------- /session/src/talkers/sinusoidal_fptg.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::f64::consts::PI; 3 | 4 | use talker::audio_format::AudioFormat; 5 | use talker::ctalker; 6 | use talker::ear; 7 | use talker::ear::Init; 8 | use talker::talker::{CTalker, Talker, TalkerBase}; 9 | use talker::talker_handler::TalkerHandlerBase; 10 | 11 | pub const MODEL: &str = "SinFPTG"; 12 | 13 | pub struct SinusoidalFPTG { 14 | frequence_coef: f64, 15 | last_tick: i64, 16 | last_angle: f64, 17 | } 18 | 19 | impl SinusoidalFPTG { 20 | pub fn new(mut base: TalkerBase) -> Result { 21 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 22 | base.add_ear(ear::audio(Some("phase"), -1., 2., 0., &Init::DefValue)?); 23 | base.add_ear(ear::cv(Some("trig"), 0., 1., 0., &Init::DefValue)?); 24 | base.add_ear(ear::audio(Some("gain"), -1., 1., 1., &Init::DefValue)?); 25 | 26 | base.add_audio_voice(None, 0.); 27 | 28 | Ok(ctalker!( 29 | base, 30 | Self { 31 | frequence_coef: AudioFormat::frequence_coef(), 32 | last_tick: 0, 33 | last_angle: 0., 34 | } 35 | )) 36 | } 37 | 38 | pub fn descriptor() -> TalkerHandlerBase { 39 | TalkerHandlerBase::builtin("Oscillator", MODEL, "Sinusoidal FPTG") 40 | } 41 | } 42 | 43 | impl Talker for SinusoidalFPTG { 44 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 45 | let ln = base.listen(tick, len); 46 | let freq_buf = base.ear_cv_buffer(0); 47 | let phase_buf = base.ear_audio_buffer(1); 48 | let trigger_buf = base.ear_cv_buffer(2); 49 | let gain_buf = base.ear_audio_buffer(3); 50 | let voice_buf = base.voice(port).audio_buffer(); 51 | let c = self.frequence_coef; 52 | let mut last_angle = self.last_angle; 53 | /* 54 | let mut last_angle = if self.last_tick == tick { 55 | self.last_angle 56 | } else { 57 | if tick == 0 { 58 | 0. 59 | } else { 60 | -freq_buf[0] as f64 * c 61 | } 62 | }; 63 | */ 64 | for i in 0..ln { 65 | let f = freq_buf[i] as f64; 66 | 67 | let a = if trigger_buf[i] == 0. { 68 | last_angle + c * f 69 | } else { 70 | 0. 71 | }; 72 | let p = phase_buf[i] as f64 * PI; 73 | 74 | voice_buf[i] = ((a + p).sin() as f32) * gain_buf[i]; 75 | last_angle = a; 76 | } 77 | 78 | self.last_angle = last_angle; 79 | self.last_tick = tick + ln as i64; 80 | ln 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /session/src/talkers/table_talker.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::audio_format::AudioFormat; 4 | use talker::ear; 5 | use talker::ear::Init; 6 | use talker::talker::TalkerBase; 7 | 8 | pub struct TableTalker { 9 | tab_len: f32, 10 | tab_len_on_sr: f32, 11 | last_tick: i64, 12 | last_pos: f32, 13 | last_phase: f32, 14 | } 15 | 16 | impl TableTalker { 17 | pub fn new(base: &mut TalkerBase, tab_len: usize) -> Result { 18 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 19 | base.add_ear(ear::audio(Some("phase"), -1., 2., 0., &Init::DefValue)?); 20 | base.add_ear(ear::audio(Some("gain"), -1., 1., 1., &Init::DefValue)?); 21 | 22 | base.add_audio_voice(None, 0.); 23 | 24 | let tab_len_on_sr = (tab_len as f64 / AudioFormat::sample_rate() as f64) as f32; 25 | 26 | Ok(Self { 27 | tab_len: tab_len as f32, 28 | tab_len_on_sr, 29 | last_tick: 0, 30 | last_pos: 0., 31 | last_phase: 0., 32 | }) 33 | } 34 | 35 | pub fn talk( 36 | &mut self, 37 | base: &TalkerBase, 38 | port: usize, 39 | tick: i64, 40 | len: usize, 41 | tab: &[f32], 42 | ) -> usize { 43 | let ln = base.listen(tick, len); 44 | let freq_buf = base.ear_cv_buffer(0); 45 | let phase_buf = base.ear_audio_buffer(1); 46 | let gain_buf = base.ear_audio_buffer(2); 47 | let voice_buf = base.voice(port).audio_buffer(); 48 | 49 | let phase_coef = self.tab_len * 0.5; 50 | let tab_len_on_sr = self.tab_len_on_sr; 51 | let mut last_pos = 0.; 52 | let mut last_phase = 0.; 53 | 54 | if self.last_tick == tick { 55 | last_pos = self.last_pos; 56 | last_phase = self.last_phase; 57 | } 58 | 59 | for i in 0..ln { 60 | let phase = phase_buf[i]; 61 | 62 | if phase != last_phase { 63 | last_pos += (phase - last_phase) * phase_coef; 64 | 65 | if last_pos < 0. { 66 | last_pos += self.tab_len; 67 | } 68 | } 69 | 70 | let freq = freq_buf[i]; 71 | let pos = (last_pos + freq * tab_len_on_sr) % self.tab_len; 72 | let tab_idx = pos as usize; 73 | 74 | let v = if freq < 1. { 75 | let pv = tab[tab_idx]; 76 | ((tab[tab_idx + 1] - pv) * pos.fract()) + pv 77 | } else { 78 | tab[tab_idx] 79 | }; 80 | 81 | voice_buf[i] = v * gain_buf[i]; 82 | last_pos = pos; 83 | last_phase = phase; 84 | } 85 | 86 | self.last_pos = last_pos; 87 | self.last_phase = last_phase; 88 | self.last_tick = tick + ln as i64; 89 | ln 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /scale/src/pitch_fetcher.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use scale::{self, Scale}; 4 | 5 | pub struct FreqPitch { 6 | pub freq: f32, 7 | pub pitch: String, 8 | } 9 | 10 | pub struct PitchFetcher { 11 | pub name: &'static str, 12 | freqs_pitch: Vec, 13 | } 14 | impl PitchFetcher { 15 | pub fn new(scale: &Scale,) -> Self { 16 | let pitchs_names = scale.get_pitchs_names(); 17 | let mut freqs_pitch = Vec::with_capacity(pitchs_names.len() * 11); 18 | 19 | freqs_pitch.push(FreqPitch{freq: f32::MIN, pitch: "MIN".to_string()}); 20 | 21 | for octave in 0..11 { 22 | for name in &pitchs_names { 23 | let pitch = format!("{}{}", name, octave); 24 | let freq = scale.pitch_name_to_frequency(&pitch).expect("Pitch error"); 25 | 26 | freqs_pitch.push(FreqPitch{freq, pitch}); 27 | } 28 | } 29 | 30 | freqs_pitch.push(FreqPitch{freq: f32::MAX, pitch: "MAX".to_string()}); 31 | 32 | Self { 33 | name: scale.name, 34 | freqs_pitch, 35 | } 36 | } 37 | 38 | 39 | pub fn fetch_pitch(&self, freq: f32) -> Result { 40 | let mut left = 0; 41 | let mut right = self.freqs_pitch.len() - 1; 42 | let mut m = right / 2; 43 | 44 | while left <= right { 45 | if self.freqs_pitch[m].freq == freq { 46 | return Ok(self.freqs_pitch[m].pitch.clone()); 47 | } 48 | else if self.freqs_pitch[m].freq > freq { 49 | right = m - 1; 50 | } 51 | else { 52 | left = m + 1; 53 | } 54 | 55 | m = (left + right) / 2; 56 | } 57 | 58 | if freq - self.freqs_pitch[m].freq < self.freqs_pitch[m + 1].freq - freq { 59 | return Ok(self.freqs_pitch[m].pitch.clone()); 60 | } 61 | else { 62 | return Ok(self.freqs_pitch[m + 1].pitch.clone()); 63 | } 64 | } 65 | } 66 | 67 | 68 | pub struct Collection { 69 | map: HashMap<&'static str, PitchFetcher>, 70 | } 71 | impl Collection { 72 | pub fn new() -> Self { 73 | let mut map = HashMap::new(); 74 | let scale_col = scale::Collection::new(); 75 | 76 | for scale in scale_col.values() { 77 | map.insert(scale.name, PitchFetcher::new(scale)); 78 | } 79 | 80 | Self { 81 | map, 82 | } 83 | } 84 | 85 | pub fn fetch<'a>(&'a self, scale_name: &str) -> Result<&'a PitchFetcher, failure::Error> { 86 | match self.map.get(scale_name) { 87 | Some(pf) => Ok(pf), 88 | None => Err(failure::err_msg(format!("Scale {} unknown!", scale_name))), 89 | } 90 | } 91 | 92 | pub fn default<'a>(&'a self) -> &'a PitchFetcher { 93 | self.map.get(scale::DEFAULT).unwrap() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/ui/general_settings.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::str::FromStr; 3 | 4 | use gtk::{ 5 | glib::{self, clone}, prelude::{BoxExt, ButtonExt, GtkWindowExt, GridExt}, DropDown, 6 | }; 7 | 8 | use crate::session_presenter::RSessionPresenter; 9 | 10 | 11 | pub fn expose(app: >k::Application, session_presenter: &RSessionPresenter,) { 12 | let sample_rates_strs = ["8000", "11025", "16000", "22050", "32000", "44100", "48000", "88200", "96000"]; 13 | let sample_rates = sample_rates_strs.map(|s| usize::from_str(s).unwrap()); 14 | 15 | let sample_rate = session_presenter.borrow().sample_rate(); 16 | 17 | let grid = gtk::Grid::builder() 18 | .margin_start(6) 19 | .margin_end(6) 20 | .margin_top(6) 21 | .margin_bottom(6) 22 | .halign(gtk::Align::Center) 23 | .valign(gtk::Align::Center) 24 | .row_spacing(6) 25 | .column_spacing(6) 26 | .build(); 27 | 28 | let row = 0; 29 | 30 | let sample_rate_label = gtk::Label::new(Some("Sample rate : ")); 31 | let sample_rate_selector = DropDown::from_strings(&sample_rates_strs); 32 | 33 | match sample_rates.binary_search(&sample_rate) { 34 | Result::Ok(idx) => sample_rate_selector.set_selected(idx as u32), 35 | Result::Err(e) => eprintln!("Sample rate {} unknown ({}).", sample_rate, e), 36 | } 37 | 38 | grid.attach(&sample_rate_label, 0, row, 1, 1); 39 | grid.attach(&sample_rate_selector, 1, row, 1, 1); 40 | 41 | let action_separator = gtk::Separator::builder().hexpand(true).vexpand(true).orientation(gtk::Orientation::Horizontal).build(); 42 | 43 | let cancel_button = gtk::Button::builder().label("Cancel").hexpand(true).build(); 44 | let ok_button = gtk::Button::builder().label("Ok").hexpand(true).build(); 45 | 46 | let action_box = gtk::Box::builder() 47 | .orientation(gtk::Orientation::Horizontal) 48 | .spacing(20) 49 | .build(); 50 | action_box.append(&cancel_button); 51 | action_box.append(&ok_button); 52 | 53 | let widget = gtk::Box::builder() 54 | .orientation(gtk::Orientation::Vertical) 55 | .spacing(20).margin_start(20).margin_end(20).margin_top(20).margin_bottom(20) 56 | .build(); 57 | widget.append(&grid); 58 | widget.append(&action_separator); 59 | widget.append(&action_box); 60 | 61 | let window = gtk::Window::builder() 62 | .application(app) 63 | .title("Runtime") 64 | .child(&widget) 65 | .modal(true) 66 | .visible(true) 67 | .build(); 68 | 69 | let rssp = session_presenter.clone(); 70 | ok_button.connect_clicked(clone!(@weak window => move |_| { 71 | let new_sample_rate = sample_rates[sample_rate_selector.selected() as usize]; 72 | 73 | if new_sample_rate != sample_rate { 74 | rssp.borrow_mut().set_sample_rate(new_sample_rate); 75 | } 76 | 77 | window.destroy(); 78 | })); 79 | cancel_button.connect_clicked(clone!(@weak window => move |_| window.destroy())); 80 | 81 | window.present(); 82 | } 83 | -------------------------------------------------------------------------------- /session/src/talkers/bounded_sinusoidal.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::f64::consts::PI; 3 | 4 | use talker::audio_format::AudioFormat; 5 | use talker::ctalker; 6 | use talker::dsp; 7 | use talker::ear; 8 | use talker::ear::Init; 9 | use talker::identifier::Index; 10 | use talker::talker::{CTalker, Talker, TalkerBase}; 11 | use talker::talker_handler::TalkerHandlerBase; 12 | 13 | pub const MODEL: &str = "BoundedSinusoidal"; 14 | 15 | pub struct BoundedSinusoidal { 16 | last_tick: i64, 17 | last_freq: f64, 18 | last_angle: f64, 19 | } 20 | 21 | const FREQ_EAR_INDEX: Index = 0; 22 | const PHASE_EAR_INDEX: Index = 1; 23 | const ROOF_EAR_INDEX: Index = 2; 24 | const FLOOR_EAR_INDEX: Index = 3; 25 | 26 | const AUDIO_VOICE_PORT: usize = 1; 27 | 28 | impl BoundedSinusoidal { 29 | pub fn new(mut base: TalkerBase) -> Result { 30 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 31 | base.add_ear(ear::cv(Some("phase"), -1., 2., 0., &Init::DefValue)?); 32 | base.add_ear(ear::cv(Some("roof"), -1000., 1000., 1., &Init::DefValue)?); 33 | base.add_ear(ear::cv(Some("floor"), -1000., 1000., 0., &Init::DefValue)?); 34 | 35 | base.add_cv_voice(Some("cv"), 0.); 36 | base.add_audio_voice(Some("au"), 0.); 37 | 38 | Ok(ctalker!( 39 | base, 40 | Self { 41 | last_tick: 0, 42 | last_freq: 0., 43 | last_angle: 0., 44 | } 45 | )) 46 | } 47 | 48 | pub fn descriptor() -> TalkerHandlerBase { 49 | TalkerHandlerBase::builtin("Oscillator", MODEL, "Bounded Sinusoidal") 50 | } 51 | } 52 | 53 | impl Talker for BoundedSinusoidal { 54 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 55 | let ln = base.listen(tick, len); 56 | let freq_buf = base.ear_cv_buffer(FREQ_EAR_INDEX); 57 | let phase_buf = base.ear_cv_buffer(PHASE_EAR_INDEX); 58 | let roof_buf = base.ear_cv_buffer(ROOF_EAR_INDEX); 59 | let floor_buf = base.ear_cv_buffer(FLOOR_EAR_INDEX); 60 | let voice_buf = base.voice(port).audio_buffer(); 61 | let c = AudioFormat::frequence_coef(); 62 | 63 | let (mut last_freq, mut last_angle) = if self.last_tick == tick { 64 | (self.last_freq, self.last_angle) 65 | } else { 66 | let lf = -freq_buf[0] as f64; 67 | (lf, lf * c) 68 | }; 69 | 70 | for i in 0..ln { 71 | let p = phase_buf[i] as f64 * PI; 72 | let rv = roof_buf[i] as f64; 73 | let fv = floor_buf[i] as f64; 74 | let a = last_angle + c * last_freq; 75 | 76 | let v = (a + p).sin(); 77 | 78 | voice_buf[i] = ((((v * 0.5) + 0.5) * (rv - fv)) + fv) as f32; 79 | 80 | last_freq = freq_buf[i] as f64; 81 | last_angle = a; 82 | } 83 | 84 | if port == AUDIO_VOICE_PORT { 85 | dsp::audioize_buffer_by_clipping(voice_buf, 0, ln); 86 | } 87 | 88 | self.last_freq = last_freq; 89 | self.last_angle = last_angle; 90 | self.last_tick = tick + ln as i64; 91 | 92 | ln 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /talker/src/lv2_handler.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, LazyLock, Mutex}; 2 | use std::sync::mpsc::{Sender, Receiver}; 3 | use std::thread; 4 | 5 | use livi; 6 | 7 | extern crate suil_sys; 8 | 9 | use audio_format; 10 | 11 | const MIN_BLOCK_SIZE: usize = 1; 12 | const MAX_BLOCK_SIZE: usize = audio_format::DEFAULT_CHUNK_SIZE; 13 | 14 | // pub const LV2_UI_HOST_TYPE_URI: &'static [u8; 42] = lv2_sys::LV2_UI__Gtk4UI; 15 | pub const LV2_UI_HOST_TYPE_URI: &'static [u8; 42] = b"http://lv2plug.in/ns/extensions/ui#Gtk4UI\0"; 16 | 17 | 18 | enum WorkerOrder { 19 | Run, 20 | } 21 | 22 | pub struct Lv2Handler { 23 | pub world: livi::World, 24 | pub features: Arc, 25 | workers_sender: Sender, 26 | } 27 | 28 | impl Lv2Handler { 29 | pub fn new() -> Lv2Handler { 30 | let world = livi::World::new(); 31 | 32 | let features = world.build_features(livi::FeaturesBuilder { 33 | min_block_length: MIN_BLOCK_SIZE, 34 | max_block_length: MAX_BLOCK_SIZE, 35 | }); 36 | 37 | let worker_manager = features.worker_manager().clone(); 38 | 39 | let (workers_sender, workers_receiver): (Sender, Receiver) = 40 | std::sync::mpsc::channel(); 41 | 42 | let _ = thread::spawn(move || { 43 | loop { 44 | match workers_receiver.recv() { 45 | Ok(_) => worker_manager.run_workers(), 46 | Err(_) => break, 47 | } 48 | } 49 | }); 50 | 51 | Lv2Handler { world, features, workers_sender} 52 | } 53 | 54 | pub fn plugin_ui_supported(&self, plugin: &livi::Plugin) -> bool { 55 | 56 | if let Some(uis) = plugin.raw().uis() { 57 | if let Some(ui) = uis.iter().next() { 58 | if let Some(ui_type_uri_node) = ui.classes().iter().next() { 59 | if let Some(ui_type_uri) = ui_type_uri_node.as_uri() { 60 | 61 | let host_type_uri = LV2_UI_HOST_TYPE_URI.as_ptr() as *const i8; 62 | let ui_type_uri = ui_type_uri.as_ptr() as *const i8; 63 | 64 | return unsafe{suil_sys::suil_ui_supported(host_type_uri, ui_type_uri)} > 0 65 | } 66 | } 67 | } 68 | } 69 | false 70 | } 71 | } 72 | 73 | static INSTANCE: LazyLock> = LazyLock::new(|| Mutex::new(Lv2Handler::new())); 74 | 75 | pub fn visit(mut f: F) -> Result 76 | where 77 | F: FnMut(&Lv2Handler) -> Result, 78 | { 79 | let res = match (*INSTANCE).lock() { 80 | Ok(instance) => f(&instance), 81 | Err(e) => Err(failure::err_msg(format!( 82 | "lv2_handler::visite failed on lock : {}", 83 | e 84 | ))), 85 | }; 86 | res 87 | } 88 | 89 | pub fn run_workers() -> Result<(), failure::Error> { 90 | match (*INSTANCE).lock() { 91 | Ok(instance) => instance.workers_sender.send(WorkerOrder::Run).map_err(|e| failure::err_msg(format!("Run LV2 workers : {}", e))), 92 | Err(e) => Err(failure::err_msg(format!("Run LV2 workers failed on lock : {}", e))), 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /session/src/talkers/audiofile_input.rs: -------------------------------------------------------------------------------- 1 | extern crate audiofile; 2 | 3 | use talker::ctalker; 4 | use talker::audio_format::AudioFormat; 5 | use talker::data::Data; 6 | use talker::talker::{CTalker, Talker, TalkerBase}; 7 | use talker::talker_handler::TalkerHandlerBase; 8 | use audiofile::reader::Reader; 9 | 10 | use crate::channel; 11 | 12 | pub const MODEL: &str = "AudioFile"; 13 | 14 | pub struct AudioFileInput { 15 | channels: Vec>, 16 | } 17 | 18 | impl AudioFileInput { 19 | pub fn new(base: TalkerBase) -> Result { 20 | base.set_data(Data::File("Click here to select a file".to_string())); 21 | 22 | Ok(ctalker!( 23 | base, 24 | Self { 25 | channels: Vec::new(), 26 | } 27 | )) 28 | } 29 | 30 | pub fn descriptor() -> TalkerHandlerBase { 31 | TalkerHandlerBase::builtin("Generator", MODEL, "Audio File") 32 | } 33 | } 34 | 35 | impl Talker for AudioFileInput { 36 | fn set_data_update( 37 | &mut self, 38 | base: &TalkerBase, 39 | data: Data, 40 | ) -> Result, failure::Error> { 41 | match data { 42 | Data::File(ref filename) => { 43 | let mut new_base = base.with(None, None, None); 44 | 45 | let sample_rate = AudioFormat::sample_rate(); 46 | let mut file_reader = Reader::new(filename, sample_rate)?; 47 | 48 | let channels_count = file_reader.channels(); 49 | let channels_names = channel::Layout::channels_names_from_channels(channels_count); 50 | 51 | for c in 0..channels_count { 52 | let tag = if c < channels_names.len() { 53 | Some(channels_names[c]) 54 | } 55 | else { 56 | None 57 | }; 58 | new_base.add_audio_voice(tag, 0.); 59 | } 60 | 61 | if base.is_effective() { 62 | self.channels =file_reader.read_all_samples()?; 63 | } 64 | 65 | new_base.set_data(data); 66 | Ok(Some(new_base)) 67 | } 68 | _ => Err(failure::err_msg(format!("{} data type {} is not File", MODEL, data.type_str()))), 69 | } 70 | } 71 | 72 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 73 | 74 | if port < self.channels.len() { 75 | let channel = &self.channels[port]; 76 | let voice_buf = base.voice(port).audio_buffer(); 77 | let t = tick as usize; 78 | let channel_len = channel.len(); 79 | 80 | let ln = if t < channel_len { 81 | len.min(channel_len - t) 82 | } 83 | else { 84 | 0 85 | }; 86 | 87 | for i in 0..ln { 88 | voice_buf[i] = channel[t + i]; 89 | } 90 | for i in ln..len { 91 | voice_buf[i] = 0.; 92 | } 93 | len 94 | } 95 | else { 96 | 0 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/mixer_control.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Gaëtan Dubreil 3 | * 4 | * All rights reserved.This file is distributed under the terms of the 5 | * GNU General Public License version 3.0. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU Lesser General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU Lesser General Public License 13 | * along with this program; if not, write to the Free Software 14 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | */ 16 | use std::cell::RefCell; 17 | use std::collections::HashMap; 18 | use std::rc::Rc; 19 | 20 | use cairo::Context; 21 | 22 | use talker::identifier::Id; 23 | 24 | use session::event_bus::Notification; 25 | use session::mixer::RMixer; 26 | 27 | use crate::graph_presenter::{GraphPresenter, RGraphPresenter}; 28 | use crate::talker_control::{ 29 | ControlSupply, RTalkerControl, RTalkerControlBase, TalkerControl, TalkerControlBase, 30 | }; 31 | use crate::util; 32 | 33 | pub struct MixerControl { 34 | base: RTalkerControlBase, 35 | } 36 | 37 | impl MixerControl { 38 | pub fn new( 39 | rmixer: &RMixer, 40 | control_supply: &ControlSupply, 41 | ) -> Result { 42 | let mixer = rmixer.borrow(); 43 | let base = 44 | TalkerControlBase::new_ref(mixer.talker(), control_supply, true, false, false, false)?; 45 | 46 | Ok(Self { base }) 47 | } 48 | pub fn new_ref( 49 | mixer: &RMixer, 50 | control_supply: &ControlSupply, 51 | ) -> Result { 52 | Ok(Rc::new(RefCell::new(MixerControl::new( 53 | mixer, 54 | control_supply, 55 | )?))) 56 | } 57 | } 58 | 59 | impl TalkerControl for MixerControl { 60 | fn base<'a>(&'a self) -> &'a RTalkerControlBase { 61 | &self.base 62 | } 63 | /* 64 | _______________ 65 | | NAME | 66 | |volume # | 67 | |---------------| 68 | |[TRACK 1] | 69 | |---------------| 70 | |[TRACK 2] | 71 | |_______________| 72 | */ 73 | fn draw_connections(&self, cc: &Context, talker_controls: &HashMap) { 74 | util::print_cairo_result(self.base.borrow().draw_connections(cc, talker_controls)); 75 | } 76 | 77 | fn draw(&self, cc: &Context, graph_presenter: &GraphPresenter) { 78 | let base = self.base.borrow(); 79 | util::print_cairo_result(base.draw_box(cc, graph_presenter)); 80 | util::print_cairo_result(base.draw_header(cc, false)); 81 | 82 | util::print_cairo_result(base.draw_ears_and_voices(cc, graph_presenter)); 83 | } 84 | 85 | fn move_to(&mut self, x: f64, y: f64) { 86 | self.base().borrow_mut().move_to(x, y); 87 | } 88 | 89 | fn on_button_release( 90 | &self, 91 | x: f64, 92 | y: f64, 93 | graph_presenter: &RGraphPresenter, 94 | ) -> Result>, failure::Error> { 95 | self.base() 96 | .borrow() 97 | .on_button_release(x, y, graph_presenter) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /session/src/talkers/damped_round.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::audio_format::AudioFormat; 4 | use talker::ctalker; 5 | use talker::ear; 6 | use talker::ear::Init; 7 | use talker::talker::{CTalker, Talker, TalkerBase}; 8 | use talker::talker_handler::TalkerHandlerBase; 9 | 10 | use tables::round; 11 | 12 | pub const MODEL: &str = "Round"; 13 | 14 | pub struct Round { 15 | tab_len_on_sr: f32, 16 | last_tick: i64, 17 | last_pos: i64, 18 | last_phase: f32, 19 | } 20 | 21 | impl Round { 22 | pub fn new(mut base: TalkerBase) -> Result { 23 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 24 | base.add_ear(ear::audio(Some("phase"), -1., 2., 0., &Init::DefValue)?); 25 | base.add_ear(ear::audio(Some("gain"), -1., 1., 1., &Init::DefValue)?); 26 | 27 | base.add_audio_voice(None, 0.); 28 | 29 | let tab_len_on_sr = (round::LEN as f64 / AudioFormat::sample_rate() as f64) as f32; 30 | 31 | Ok(ctalker!( 32 | base, 33 | Self { 34 | tab_len_on_sr, 35 | last_tick: 0, 36 | last_pos: 0, 37 | last_phase: 0., 38 | } 39 | )) 40 | } 41 | 42 | pub fn descriptor() -> TalkerHandlerBase { 43 | TalkerHandlerBase::new("Oscillator", MODEL, "Round") 44 | } 45 | } 46 | const TAB_LEN: i64 = round::LEN as i64; 47 | 48 | impl Talker for Round { 49 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 50 | let ln = base.listen(tick, len); 51 | let freq_buf = base.ear_cv_buffer(0); 52 | let phase_buf = base.ear_audio_buffer(1); 53 | let gain_buf = base.ear_audio_buffer(2); 54 | let voice_buf = base.voice(port).audio_buffer(); 55 | 56 | let phase_coef = round::LEN as f32 * 0.5; 57 | let tab_len_on_sr = self.tab_len_on_sr; 58 | let mut last_pos = 0; 59 | let mut last_phase = 0.; 60 | 61 | if self.last_tick == tick { 62 | last_pos = self.last_pos; 63 | last_phase = self.last_phase; 64 | } 65 | 66 | for i in 0..ln { 67 | let gain = gain_buf[i]; 68 | 69 | if gain == 0. { 70 | last_pos = 0; 71 | 72 | if i > 0 { 73 | voice_buf[i] = voice_buf[i - 1] * 0.95; 74 | } else { 75 | voice_buf[i] = 0.; 76 | } 77 | } else { 78 | let phase = phase_buf[i]; 79 | 80 | if phase != last_phase { 81 | last_pos += ((phase - last_phase) * phase_coef) as i64; 82 | 83 | if last_pos < 0 { 84 | last_pos += TAB_LEN; 85 | } 86 | } 87 | 88 | let pos = last_pos + (freq_buf[i] * tab_len_on_sr) as i64; 89 | let tab_idx = pos % TAB_LEN; 90 | 91 | voice_buf[i] = round::TAB[tab_idx as usize] * gain; 92 | last_pos = tab_idx; 93 | last_phase = phase; 94 | } 95 | } 96 | 97 | self.last_pos = last_pos; 98 | self.last_phase = last_phase; 99 | self.last_tick = tick + ln as i64; 100 | ln 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /talker/src/voice.rs: -------------------------------------------------------------------------------- 1 | use identifier::Index; 2 | use crate::horn::{ 3 | AudioVal, ControlVal, CvVal, Horn, MAtomBuf, MAudioBuf, MControlBuf, MCvBuf, PortType, 4 | }; 5 | use crate::lv2_handler::Lv2Handler; 6 | use std::cell::Cell; 7 | 8 | pub const DEF_OUTPUT_TAG: &'static str = "Out"; 9 | 10 | #[derive(Clone)] 11 | pub struct Voice { 12 | tag: String, 13 | tick: Cell, 14 | len: Cell, 15 | horn: Horn, 16 | associated_ear_set: (Index, Index), 17 | } 18 | 19 | impl Voice { 20 | pub fn new(tag: Option<&str>, len: usize, horn: Horn) -> Self { 21 | Self { 22 | tag: tag.unwrap_or(DEF_OUTPUT_TAG).to_string(), 23 | tick: Cell::new(-1), 24 | len: Cell::new(len), 25 | horn, 26 | associated_ear_set: (Index::MAX, Index::MAX), 27 | } 28 | } 29 | 30 | pub fn port_type(&self) -> PortType { 31 | self.horn.port_type() 32 | } 33 | pub fn tag<'a>(&'a self) -> &'a String { 34 | &self.tag 35 | } 36 | pub fn tick(&self) -> i64 { 37 | self.tick.get() 38 | } 39 | pub fn set_tick(&self, tick: i64) { 40 | self.tick.set(tick); 41 | } 42 | pub fn len(&self) -> usize { 43 | self.len.get() 44 | } 45 | pub fn set_len(&self, len: usize) { 46 | self.len.set(len); 47 | } 48 | pub fn set_tick_len(&self, tick: i64, len: usize) { 49 | self.tick.set(tick); 50 | self.len.set(len); 51 | } 52 | pub fn set_associated_ear_set(&mut self, ear_idx: Index, set_idx: Index,) { 53 | self.associated_ear_set = (ear_idx, set_idx); 54 | } 55 | pub fn get_associated_ear_set(&self) -> (Index, Index) { 56 | self.associated_ear_set 57 | } 58 | 59 | pub fn horn<'a>(&'a self) -> &'a Horn { 60 | &self.horn 61 | } 62 | 63 | pub fn audio_buffer(&self) -> MAudioBuf { 64 | self.horn.audio_buffer() 65 | } 66 | pub fn control_buffer(&self) -> MControlBuf { 67 | self.horn.control_buffer() 68 | } 69 | pub fn cv_buffer(&self) -> MCvBuf { 70 | self.horn.cv_buffer() 71 | } 72 | pub fn atom_buffer(&self) -> MAtomBuf { 73 | self.horn.atom_buffer() 74 | } 75 | pub fn audio_value(&self, index: usize) -> AudioVal { 76 | self.horn.audio_value(index) 77 | } 78 | pub fn control_value(&self) -> ControlVal { 79 | self.horn.control_value() 80 | } 81 | pub fn set_control_value(&self, value: ControlVal) { 82 | self.horn.set_control_value(value) 83 | } 84 | pub fn cv_value(&self, index: usize) -> CvVal { 85 | self.horn.cv_value(index) 86 | } 87 | pub fn can_have_a_value(&self) -> bool { 88 | self.horn.port_type() != PortType::Atom 89 | } 90 | pub fn value(&self, index: usize) -> f32 { 91 | self.horn.value(index) 92 | } 93 | } 94 | 95 | pub fn audio(tag: Option<&str>, value: f32, len: usize) -> Voice { 96 | Voice::new(tag, len, Horn::audio(value, Some(len))) 97 | } 98 | 99 | pub fn control(tag: Option<&str>, value: f32) -> Voice { 100 | Voice::new(tag, 1, Horn::control(value)) 101 | } 102 | 103 | pub fn cv(tag: Option<&str>, value: f32, len: usize) -> Voice { 104 | Voice::new(tag, len, Horn::cv(value, Some(len))) 105 | } 106 | 107 | pub fn atom(tag: Option<&str>, olv2_handler: Option<&Lv2Handler>, len: usize) -> Voice { 108 | Voice::new(tag, len, Horn::atom(olv2_handler)) 109 | } 110 | -------------------------------------------------------------------------------- /session/src/talkers/dynamic_modulator.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::ctalker; 4 | use talker::ear::Ear; 5 | use talker::ear::Init; 6 | use talker::ear::Set; 7 | use talker::horn::PortType; 8 | use talker::identifier::Index; 9 | use talker::talker::{CTalker, Talker, TalkerBase}; 10 | use talker::talker_handler::TalkerHandlerBase; 11 | use talker::voice; 12 | 13 | pub const MODEL: &str = "DynamicModulators"; 14 | 15 | struct State { 16 | prev_output: f32, 17 | } 18 | impl State { 19 | pub fn new() -> Self { 20 | Self { 21 | prev_output: 0., 22 | } 23 | } 24 | } 25 | 26 | const INPUTS_EAR_INDEX: Index = 0; 27 | const IN_HUM_INDEX: Index = 0; 28 | const GAIN_HUM_INDEX: Index = 1; 29 | 30 | pub struct DynamicModulators { 31 | states: Vec, 32 | } 33 | impl DynamicModulators { 34 | pub fn new(mut base: TalkerBase) -> Result { 35 | let stem_set = Set::from_attributs(&vec![ 36 | ("in", PortType::Audio, -1., 1., 0., Init::DefValue), 37 | ("gain", PortType::Audio, -1., 1., 1., Init::DefValue), 38 | ])?; 39 | 40 | base.add_ear(Ear::new(Some("inputs"), true, Some(stem_set), None)); 41 | 42 | Ok(ctalker!(base, Self { 43 | states: Vec::new(), 44 | })) 45 | } 46 | 47 | pub fn descriptor() -> TalkerHandlerBase { 48 | TalkerHandlerBase::builtin("Modulator", MODEL, "Dynamic Modulators") 49 | } 50 | } 51 | 52 | impl Talker for DynamicModulators { 53 | fn add_set_to_ear_update( 54 | &mut self, 55 | base: &TalkerBase, 56 | ear_idx: Index, 57 | hum_idx: Index, 58 | entree: talker::ear::Entree, 59 | ) -> Result, failure::Error> { 60 | let mut new_base = base.clone(); 61 | new_base.ear(ear_idx).add_set(hum_idx, entree)?; 62 | 63 | if ear_idx == INPUTS_EAR_INDEX { 64 | self.states.push(State::new()); 65 | let mut voice = voice::audio(None, 0., base.buffer_len()); 66 | voice.set_associated_ear_set(ear_idx, new_base.ear(ear_idx).sets_len() - 1); 67 | new_base.add_voice(voice); 68 | } 69 | Ok(Some(new_base)) 70 | } 71 | fn sup_ear_set_update( 72 | &mut self, 73 | base: &TalkerBase, 74 | ear_idx: usize, 75 | set_idx: usize, 76 | ) -> Result, failure::Error> { 77 | let mut new_base = base.clone(); 78 | new_base.sup_ear_set_with_associated_voice(ear_idx, set_idx)?; 79 | 80 | if ear_idx == INPUTS_EAR_INDEX { 81 | self.states.remove(set_idx); 82 | } 83 | 84 | Ok(Some(new_base)) 85 | } 86 | 87 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 88 | let ear = base.ear(INPUTS_EAR_INDEX); 89 | let ln = ear.listen_set(tick, len, port); 90 | 91 | let input_buf = ear.get_set_hum_audio_buffer(port, IN_HUM_INDEX); 92 | let gain_buf = ear.get_set_hum_audio_buffer(port, GAIN_HUM_INDEX); 93 | 94 | let state = &mut self.states[port]; 95 | 96 | let voice_buf = base.voice(port).audio_buffer(); 97 | 98 | for i in 0..ln { 99 | let input = input_buf[i]; 100 | let gain = gain_buf[i]; 101 | 102 | state.prev_output += (input - state.prev_output) * (gain + 1.); 103 | 104 | voice_buf[i] = state.prev_output; 105 | } 106 | 107 | ln 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /session/src/talkers/square.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::audio_format::AudioFormat; 4 | use talker::ctalker; 5 | use talker::ear; 6 | use talker::ear::Init; 7 | use talker::identifier::Index; 8 | use talker::talker::{CTalker, Talker, TalkerBase}; 9 | use talker::talker_handler::TalkerHandlerBase; 10 | 11 | pub const MODEL: &str = "Square"; 12 | 13 | pub struct Square { 14 | next_rising_edge_tick: i64, 15 | next_falling_edge_tick: i64, 16 | gain: f32, 17 | } 18 | 19 | const FREQ_EAR_INDEX: Index = 0; 20 | const RATIO_EAR_INDEX: Index = 1; 21 | const GAIN_EAR_INDEX: Index = 2; 22 | 23 | impl Square { 24 | pub fn new(mut base: TalkerBase) -> Result { 25 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 26 | base.add_ear(ear::audio(Some("ratio"), -1., 1., 0., &Init::DefValue)?); 27 | base.add_ear(ear::audio(Some("gain"), -1., 1., 1., &Init::DefValue)?); 28 | 29 | base.add_audio_voice(None, 0.); 30 | 31 | Ok(ctalker!( 32 | base, 33 | Self { 34 | next_rising_edge_tick: 0, 35 | next_falling_edge_tick: 0, 36 | gain: 0. 37 | } 38 | )) 39 | } 40 | 41 | pub fn descriptor() -> TalkerHandlerBase { 42 | TalkerHandlerBase::builtin("Oscillator", MODEL, MODEL) 43 | } 44 | } 45 | 46 | impl Talker for Square { 47 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 48 | let freq_ear = base.ear(FREQ_EAR_INDEX); 49 | let freq_buf = base.ear_cv_buffer(FREQ_EAR_INDEX); 50 | let ratio_ear = base.ear(RATIO_EAR_INDEX); 51 | let ratio_buf = base.ear_audio_buffer(RATIO_EAR_INDEX); 52 | let gain_ear = base.ear(GAIN_EAR_INDEX); 53 | let gain_buf = base.ear_audio_buffer(GAIN_EAR_INDEX); 54 | let voice_buf = base.voice(port).audio_buffer(); 55 | let sample_rate = AudioFormat::sample_rate() as f32; 56 | 57 | let mut next_rising_edge_idx = if self.next_rising_edge_tick < tick { 58 | 0 59 | } else { 60 | (self.next_rising_edge_tick - tick) as usize 61 | }; 62 | let mut next_falling_edge_idx = if self.next_falling_edge_tick < tick { 63 | 0 64 | } else { 65 | (self.next_falling_edge_tick - tick) as usize 66 | }; 67 | let mut gain = self.gain; 68 | 69 | let mut i: usize = 0; 70 | 71 | while i < len { 72 | if i == next_rising_edge_idx { 73 | let tck = tick + i as i64; 74 | 75 | freq_ear.listen(tck, 1); 76 | let f = freq_buf[0]; 77 | 78 | ratio_ear.listen(tck, 1); 79 | let r = ratio_buf[0]; 80 | 81 | gain_ear.listen(tck, 1); 82 | gain = gain_buf[0]; 83 | 84 | let p = sample_rate / f; 85 | 86 | next_rising_edge_idx = i + p as usize; 87 | next_falling_edge_idx = i + (p * (r * 0.5 + 0.5)) as usize; 88 | } 89 | 90 | let roof_end = len.min(next_falling_edge_idx); 91 | 92 | for _ in i..roof_end { 93 | voice_buf[i] = gain; 94 | i += 1; 95 | } 96 | 97 | let floor_end = len.min(next_rising_edge_idx); 98 | 99 | for _ in i..floor_end { 100 | voice_buf[i] = -gain; 101 | i += 1; 102 | } 103 | } 104 | 105 | self.next_rising_edge_tick = next_rising_edge_idx as i64 + tick; 106 | self.next_falling_edge_tick = next_falling_edge_idx as i64 + tick; 107 | self.gain = gain; 108 | 109 | i 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /session/src/talkers/damper.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::ctalker; 4 | use talker::ear::Ear; 5 | use talker::ear::Init; 6 | use talker::ear::Set; 7 | use talker::horn::PortType; 8 | use talker::identifier::Index; 9 | use talker::talker::{CTalker, Talker, TalkerBase}; 10 | use talker::talker_handler::TalkerHandlerBase; 11 | use talker::voice; 12 | 13 | pub const MODEL: &str = "Dampers"; 14 | 15 | struct State { 16 | prev_output: f32, 17 | } 18 | impl State { 19 | pub fn new() -> Self { 20 | Self { 21 | prev_output: 0., 22 | } 23 | } 24 | } 25 | 26 | const INPUTS_EAR_INDEX: Index = 0; 27 | const IN_HUM_INDEX: Index = 0; 28 | const CEILING_HUM_INDEX: Index = 1; 29 | const GAIN_HUM_INDEX: Index = 2; 30 | 31 | pub struct Dampers { 32 | states: Vec, 33 | } 34 | impl Dampers { 35 | pub fn new(mut base: TalkerBase) -> Result { 36 | let stem_set = Set::from_attributs(&vec![ 37 | ("in", PortType::Audio, -1., 1., 0., Init::DefValue), 38 | ("ceiling", PortType::Cv, 0., 1000., 1., Init::DefValue), 39 | ("gain", PortType::Audio, -1., 1., 1., Init::DefValue), 40 | ])?; 41 | 42 | base.add_ear(Ear::new(Some("inputs"), true, Some(stem_set), None)); 43 | 44 | Ok(ctalker!(base, Self { 45 | states: Vec::new(), 46 | })) 47 | } 48 | 49 | pub fn descriptor() -> TalkerHandlerBase { 50 | TalkerHandlerBase::builtin("Modulator", MODEL, MODEL) 51 | } 52 | } 53 | 54 | impl Talker for Dampers { 55 | fn add_set_to_ear_update( 56 | &mut self, 57 | base: &TalkerBase, 58 | ear_idx: Index, 59 | hum_idx: Index, 60 | entree: talker::ear::Entree, 61 | ) -> Result, failure::Error> { 62 | let mut new_base = base.clone(); 63 | new_base.ear(ear_idx).add_set(hum_idx, entree)?; 64 | 65 | if ear_idx == INPUTS_EAR_INDEX { 66 | self.states.push(State::new()); 67 | let mut voice = voice::audio(None, 0., base.buffer_len()); 68 | voice.set_associated_ear_set(ear_idx, new_base.ear(ear_idx).sets_len() - 1); 69 | new_base.add_voice(voice); 70 | } 71 | Ok(Some(new_base)) 72 | } 73 | fn sup_ear_set_update( 74 | &mut self, 75 | base: &TalkerBase, 76 | ear_idx: usize, 77 | set_idx: usize, 78 | ) -> Result, failure::Error> { 79 | let mut new_base = base.clone(); 80 | new_base.sup_ear_set_with_associated_voice(ear_idx, set_idx)?; 81 | 82 | if ear_idx == INPUTS_EAR_INDEX { 83 | self.states.remove(set_idx); 84 | } 85 | 86 | Ok(Some(new_base)) 87 | } 88 | 89 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 90 | let ear = base.ear(INPUTS_EAR_INDEX); 91 | let ln = ear.listen_set(tick, len, port); 92 | 93 | let input_buf = ear.get_set_hum_audio_buffer(port, IN_HUM_INDEX); 94 | let ceiling_buf = ear.get_set_hum_cv_buffer(port, CEILING_HUM_INDEX); 95 | let gain_buf = ear.get_set_hum_audio_buffer(port, GAIN_HUM_INDEX); 96 | 97 | let state = &mut self.states[port]; 98 | 99 | let mut output = state.prev_output; 100 | 101 | let voice_buf = base.voice(port).audio_buffer(); 102 | 103 | for i in 0..ln { 104 | let input = input_buf[i]; 105 | let ceiling = ceiling_buf[i]; 106 | let gain = gain_buf[i]; 107 | 108 | output += if input > 0. { 109 | (input - output).min((ceiling - output) * gain) 110 | } else { 111 | (input - output).max((-ceiling - output) * gain) 112 | }; 113 | 114 | voice_buf[i] = output; 115 | } 116 | state.prev_output = output; 117 | 118 | ln 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/output_presenter.rs: -------------------------------------------------------------------------------- 1 | 2 | use talker::identifier::{Id, Identifier}; 3 | use session::{channel, output::ROutput}; 4 | 5 | pub const DEFAULT_CODEC: &str = "flac"; 6 | pub const CODECS_LABELS: [&str; 6] = ["FLAC", "MP3", "Ogg Vorbis", "Opus", "WAV 16-bit", "WAV 24-bit"]; 7 | pub const CODECS_NAMES: [&str; 6] = [DEFAULT_CODEC, "libmp3lame", "libvorbis", "libopus", "pcm_s16le", "pcm_s24le"]; 8 | pub const CODEC_CONTAINERS_EXTENTIONS: [&str; 6] = [DEFAULT_CODEC, "mp3", "ogg", "opus", "wav", "wav"]; 9 | 10 | pub const DEFAULT_SAMPLE_RATE: usize = 44100; 11 | pub const SAMPLE_RATES: [&str; 9] = ["8000", "11025", "16000", "22050", "32000", "44100", "48000", "88200", "96000"]; 12 | 13 | pub const DEFAULT_AUDIO_FILE_EXTENTION: &str = DEFAULT_CODEC; 14 | 15 | pub struct OutputPresenter { 16 | identifier: Identifier, 17 | codec_name: String, 18 | sample_rate: usize, 19 | channel_layout: String, 20 | file_path: String, 21 | } 22 | 23 | impl OutputPresenter { 24 | pub fn new( 25 | identifier: Identifier, 26 | codec_name: &str, 27 | sample_rate: usize, 28 | channel_layout: &str, 29 | file_path: &str, 30 | ) -> OutputPresenter { 31 | Self { 32 | identifier, 33 | codec_name: codec_name.to_string(), 34 | sample_rate, 35 | channel_layout: channel_layout.to_string(), 36 | file_path: file_path.to_string(), 37 | } 38 | } 39 | 40 | pub fn from(output: &ROutput) -> OutputPresenter { 41 | let out = output.borrow(); 42 | let identifier = out.identifier().borrow().clone(); 43 | let codec_name = out.codec_name(); 44 | let sample_rate = out.sample_rate(); 45 | let channel_layout = out.channel_layout(); 46 | let file_path = out.file_path(); 47 | OutputPresenter::new(identifier, codec_name, sample_rate, channel_layout, file_path) 48 | } 49 | 50 | pub fn identifier<'a>(&'a self) -> &'a Identifier { 51 | &self.identifier 52 | } 53 | pub fn id(&self) -> Id { 54 | self.identifier.id() 55 | } 56 | 57 | pub fn codec_name<'a>(&'a self) -> &'a str { 58 | self.codec_name.as_str() 59 | } 60 | 61 | pub fn set_codec_name(&mut self, value: &str) { 62 | self.codec_name = value.to_string(); 63 | } 64 | 65 | pub fn codec_index(&self) -> usize { 66 | let codec = self.codec_name(); 67 | 68 | for idx in 0..CODECS_NAMES.len() { 69 | if CODECS_NAMES[idx] == codec { 70 | return idx; 71 | } 72 | } 73 | eprintln!("Unknow sample format {}. Fallback to {}.", codec, CODECS_NAMES[0]); 74 | 0 75 | } 76 | 77 | pub fn sample_rate(&self) -> usize { 78 | self.sample_rate 79 | } 80 | 81 | pub fn set_sample_rate(&mut self, value: usize) { 82 | self.sample_rate = value; 83 | } 84 | 85 | pub fn sample_rate_index(&self) -> usize { 86 | let sample_rate = self.sample_rate().to_string(); 87 | 88 | for idx in 0..SAMPLE_RATES.len() { 89 | if SAMPLE_RATES[idx] == sample_rate { 90 | return idx; 91 | } 92 | } 93 | eprintln!("Unknow sample rate {}. Fallback to {}.", sample_rate, SAMPLE_RATES[0]); 94 | 0 95 | } 96 | 97 | pub fn channel_layout<'a>(&'a self) -> &'a str { 98 | self.channel_layout.as_str() 99 | } 100 | 101 | pub fn set_channel_layout(&mut self, value: &str) { 102 | self.channel_layout = value.to_string(); 103 | } 104 | 105 | pub fn channel_layout_index(&self) -> usize { 106 | channel::Layout::index(self.channel_layout()) 107 | } 108 | 109 | pub fn file_path<'a>(&'a self) -> &'a str { 110 | self.file_path.as_str() 111 | } 112 | 113 | pub fn set_file_path(&mut self, value: &str) { 114 | self.file_path = value.to_string(); 115 | } 116 | } -------------------------------------------------------------------------------- /talker/src/data.rs: -------------------------------------------------------------------------------- 1 | extern crate failure; 2 | use std::cell::RefCell; 3 | use std::str::FromStr; 4 | 5 | #[derive(Clone)] 6 | pub enum Data { 7 | Nil, 8 | Int(i64), 9 | Float(f32), 10 | String(String), 11 | Text(String), 12 | File(String), 13 | UI, 14 | } 15 | pub type RData = RefCell; 16 | 17 | impl Data { 18 | pub fn type_str(&self) -> &'static str { 19 | match self { 20 | Data::Int(_) => "Int", 21 | Data::Float(_) => "Float", 22 | Data::String(_) => "String", 23 | Data::Text(_) => "Text", 24 | Data::File(_) => "File", 25 | Data::UI => "UI", 26 | Data::Nil => "Nil", 27 | } 28 | } 29 | pub fn notify_incompatibility(&self, expected_type: &str) -> failure::Error { 30 | eprintln!( 31 | "Type {} is incompatible with the expected {} type", 32 | self.type_str(), 33 | expected_type 34 | ); 35 | failure::err_msg("Value type is incompatible with the expected type") 36 | } 37 | 38 | pub fn to_i(&self) -> Result { 39 | match self { 40 | Data::Int(i) => Ok(*i), 41 | _ => Err(self.notify_incompatibility("Int")), 42 | } 43 | } 44 | pub fn to_f(&self) -> Result { 45 | match self { 46 | Data::Float(f) => Ok(*f), 47 | _ => Err(self.notify_incompatibility("Float")), 48 | } 49 | } 50 | pub fn to_s(&self) -> Result { 51 | match self { 52 | Data::String(s) => Ok(s.to_string()), 53 | _ => Err(self.notify_incompatibility("String")), 54 | } 55 | } 56 | pub fn to_t(&self) -> Result { 57 | match self { 58 | Data::Text(t) => Ok(t.to_string()), 59 | _ => Err(self.notify_incompatibility("Text")), 60 | } 61 | } 62 | pub fn to_fl(&self) -> Result { 63 | match self { 64 | Data::File(f) => Ok(f.to_string()), 65 | _ => Err(self.notify_incompatibility("File")), 66 | } 67 | } 68 | 69 | pub fn i(i: i64) -> Self { 70 | Data::Int(i) 71 | } 72 | pub fn f(f: f32) -> Self { 73 | Data::Float(f) 74 | } 75 | pub fn s(s: String) -> Self { 76 | Data::String(s) 77 | } 78 | pub fn t(t: String) -> Self { 79 | Data::Text(t) 80 | } 81 | pub fn fl(f: String) -> Self { 82 | Data::File(f) 83 | } 84 | 85 | pub fn to_string(&self) -> String { 86 | match self { 87 | Data::Int(i) => i.to_string(), 88 | Data::Float(f) => f.to_string(), 89 | Data::String(s) => s.to_string(), 90 | Data::Text(s) => s.to_string(), 91 | Data::File(s) => s.to_string(), 92 | Data::UI => "[-O-]".to_string(), 93 | Data::Nil => "".to_string(), 94 | } 95 | } 96 | 97 | fn notify_string_incompatibility(&self, s: &str) -> Result { 98 | eprintln!( 99 | "Value string {} is incompatible with the expected {} type", 100 | s, 101 | self.type_str() 102 | ); 103 | Err(failure::err_msg( 104 | "Value string is incompatible with the expected type", 105 | )) 106 | } 107 | 108 | pub fn birth(&self, s: &str) -> Result { 109 | match self { 110 | Data::Int(_) => match i64::from_str(s) { 111 | Ok(i) => Ok(Data::Int(i)), 112 | _ => self.notify_string_incompatibility(s), 113 | }, 114 | Data::Float(_) => match f32::from_str(s) { 115 | Ok(f) => Ok(Data::Float(f)), 116 | _ => self.notify_string_incompatibility(s), 117 | }, 118 | Data::String(_) => Ok(Data::String(s.to_string())), 119 | Data::Text(_) => Ok(Data::Text(s.to_string())), 120 | Data::File(_) => Ok(Data::File(s.to_string())), 121 | Data::UI => Ok(Data::UI), 122 | Data::Nil => Ok(Data::Nil), 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /session/src/talkers/regulator.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::ctalker; 4 | use talker::ear::Ear; 5 | use talker::ear::Init; 6 | use talker::ear::Set; 7 | use talker::horn::PortType; 8 | use talker::identifier::Index; 9 | use talker::talker::{CTalker, Talker, TalkerBase}; 10 | use talker::talker_handler::TalkerHandlerBase; 11 | use talker::voice; 12 | 13 | pub const MODEL: &str = "Regulators"; 14 | 15 | struct State { 16 | prev_error: f32, 17 | prev_output: f32, 18 | integral: f32, 19 | } 20 | impl State { 21 | pub fn new() -> Self { 22 | Self { 23 | prev_error: 0., 24 | prev_output: 0., 25 | integral: 0., 26 | } 27 | } 28 | } 29 | 30 | const INPUTS_EAR_INDEX: Index = 0; 31 | const IN_HUM_INDEX: Index = 0; 32 | const PRO_HUM_INDEX: Index = 1; 33 | const INT_HUM_INDEX: Index = 2; 34 | const DER_HUM_INDEX: Index = 3; 35 | 36 | pub struct Regulators { 37 | states: Vec, 38 | } 39 | impl Regulators { 40 | pub fn new(mut base: TalkerBase) -> Result { 41 | let stem_set = Set::from_attributs(&vec![ 42 | ("in", PortType::Audio, -1., 1., 0., Init::DefValue), 43 | ("proportional", PortType::Cv, 0., 10., 1., Init::DefValue), 44 | ("integral", PortType::Cv, 0., 10., 0., Init::DefValue), 45 | ("derivative", PortType::Cv, 0., 10., 0., Init::DefValue), 46 | ])?; 47 | 48 | base.add_ear(Ear::new(Some("inputs"), true, Some(stem_set), None)); 49 | 50 | Ok(ctalker!(base, Self { 51 | states: Vec::new(), 52 | })) 53 | } 54 | 55 | pub fn descriptor() -> TalkerHandlerBase { 56 | TalkerHandlerBase::builtin("Modulator", MODEL, MODEL) 57 | } 58 | } 59 | 60 | impl Talker for Regulators { 61 | fn add_set_to_ear_update( 62 | &mut self, 63 | base: &TalkerBase, 64 | ear_idx: Index, 65 | hum_idx: Index, 66 | entree: talker::ear::Entree, 67 | ) -> Result, failure::Error> { 68 | let mut new_base = base.clone(); 69 | new_base.ear(ear_idx).add_set(hum_idx, entree)?; 70 | 71 | if ear_idx == INPUTS_EAR_INDEX { 72 | self.states.push(State::new()); 73 | let mut voice = voice::audio(None, 0., base.buffer_len()); 74 | voice.set_associated_ear_set(ear_idx, new_base.ear(ear_idx).sets_len() - 1); 75 | new_base.add_voice(voice); 76 | } 77 | Ok(Some(new_base)) 78 | } 79 | fn sup_ear_set_update( 80 | &mut self, 81 | base: &TalkerBase, 82 | ear_idx: usize, 83 | set_idx: usize, 84 | ) -> Result, failure::Error> { 85 | let mut new_base = base.clone(); 86 | new_base.sup_ear_set_with_associated_voice(ear_idx, set_idx)?; 87 | 88 | if ear_idx == INPUTS_EAR_INDEX { 89 | self.states.remove(set_idx); 90 | } 91 | 92 | Ok(Some(new_base)) 93 | } 94 | 95 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 96 | let ear = base.ear(INPUTS_EAR_INDEX); 97 | let ln = ear.listen_set(tick, len, port); 98 | 99 | let input_buf = ear.get_set_hum_audio_buffer(port, IN_HUM_INDEX); 100 | let pro_buf = ear.get_set_hum_cv_buffer(port, PRO_HUM_INDEX); 101 | let int_buf = ear.get_set_hum_cv_buffer(port, INT_HUM_INDEX); 102 | let der_buf = ear.get_set_hum_cv_buffer(port, DER_HUM_INDEX); 103 | 104 | let state = &mut self.states[port]; 105 | 106 | let voice_buf = base.voice(port).audio_buffer(); 107 | 108 | for i in 0..ln { 109 | let input = input_buf[i]; 110 | let pk = pro_buf[i]; 111 | let ik = int_buf[i]; 112 | let dk = der_buf[i]; 113 | 114 | let e = input - state.prev_output; 115 | let pv = e * pk; 116 | state.integral += e * ik; 117 | let dv = (e - state.prev_error) * dk; 118 | 119 | state.prev_output += pv + state.integral + dv; 120 | 121 | voice_buf[i] = state.prev_output; 122 | state.prev_error = e; 123 | } 124 | 125 | ln 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /session/src/talkers/accumulator.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::ctalker; 4 | use talker::ear::Ear; 5 | use talker::ear::Init; 6 | use talker::ear::Set; 7 | use talker::horn::PortType; 8 | use talker::identifier::Index; 9 | use talker::talker::{CTalker, Talker, TalkerBase}; 10 | use talker::talker_handler::TalkerHandlerBase; 11 | use talker::voice; 12 | 13 | pub const MODEL: &str = "Accumulators"; 14 | struct State { 15 | prev_error: f32, 16 | mid_error: f32, 17 | prev_output: f32, 18 | integ_val: f32, 19 | } 20 | impl State { 21 | pub fn new() -> Self { 22 | Self { 23 | prev_error: 0., 24 | mid_error: 0., 25 | prev_output: 0., 26 | integ_val: 0., 27 | } 28 | } 29 | } 30 | 31 | const INPUTS_EAR_INDEX: Index = 0; 32 | const IN_HUM_INDEX: Index = 0; 33 | const INT_HUM_INDEX: Index = 1; 34 | const DMP_HUM_INDEX: Index = 2; 35 | 36 | pub struct Accumulators { 37 | states: Vec, 38 | } 39 | impl Accumulators { 40 | pub fn new(mut base: TalkerBase) -> Result { 41 | let stem_set = Set::from_attributs(&vec![ 42 | ("in", PortType::Audio, -1., 1., 0., Init::DefValue), 43 | ("integral", PortType::Cv, 0., 1000., 1., Init::DefValue), 44 | ("damper", PortType::Cv, 0., 1000., 1., Init::DefValue), 45 | ])?; 46 | 47 | base.add_ear(Ear::new(Some("inputs"), true, Some(stem_set), None)); 48 | 49 | Ok(ctalker!(base, Self { 50 | states: Vec::new(), 51 | })) 52 | } 53 | 54 | pub fn descriptor() -> TalkerHandlerBase { 55 | TalkerHandlerBase::builtin("Modulator", MODEL, MODEL) 56 | } 57 | } 58 | 59 | impl Talker for Accumulators { 60 | fn add_set_to_ear_update( 61 | &mut self, 62 | base: &TalkerBase, 63 | ear_idx: Index, 64 | hum_idx: Index, 65 | entree: talker::ear::Entree, 66 | ) -> Result, failure::Error> { 67 | let mut new_base = base.clone(); 68 | new_base.ear(ear_idx).add_set(hum_idx, entree)?; 69 | 70 | if ear_idx == INPUTS_EAR_INDEX { 71 | self.states.push(State::new()); 72 | let mut voice = voice::audio(None, 0., base.buffer_len()); 73 | voice.set_associated_ear_set(ear_idx, new_base.ear(ear_idx).sets_len() - 1); 74 | new_base.add_voice(voice); 75 | } 76 | Ok(Some(new_base)) 77 | } 78 | fn sup_ear_set_update( 79 | &mut self, 80 | base: &TalkerBase, 81 | ear_idx: usize, 82 | set_idx: usize, 83 | ) -> Result, failure::Error> { 84 | let mut new_base = base.clone(); 85 | new_base.sup_ear_set_with_associated_voice(ear_idx, set_idx)?; 86 | 87 | if ear_idx == INPUTS_EAR_INDEX { 88 | self.states.remove(set_idx); 89 | } 90 | 91 | Ok(Some(new_base)) 92 | } 93 | 94 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 95 | let ear = base.ear(INPUTS_EAR_INDEX); 96 | let ln = ear.listen_set(tick, len, port); 97 | 98 | let input_buf = ear.get_set_hum_audio_buffer(port, IN_HUM_INDEX); 99 | let integral_buf = ear.get_set_hum_cv_buffer(port, INT_HUM_INDEX); 100 | let damper_buf = ear.get_set_hum_cv_buffer(port, DMP_HUM_INDEX); 101 | 102 | let state = &mut self.states[port]; 103 | 104 | let voice_buf = base.voice(port).audio_buffer(); 105 | 106 | for i in 0..ln { 107 | let v = input_buf[i]; 108 | let ik = integral_buf[i]; 109 | let dk = damper_buf[i]; 110 | let e = v - state.prev_output; 111 | 112 | if (e > 0. && e > state.prev_error) || (e < 0. && e < state.prev_error) { 113 | state.mid_error = e * 0.5; 114 | state.prev_error = e; 115 | } else if e == 0. { 116 | state.mid_error = 0.; 117 | state.prev_error = 0.; 118 | } 119 | 120 | state.integ_val += (e - (state.mid_error * dk)) * ik; 121 | state.prev_output += state.integ_val; 122 | 123 | voice_buf[i] = state.prev_output; 124 | } 125 | 126 | ln 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /session/src/factory.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{LazyLock, Mutex}; 2 | 3 | use talker::audio_format::AudioFormat; 4 | use talker::identifier::RIdentifier; 5 | use talker::talker::RTalker; 6 | 7 | use crate::audiofile_output::AudioFileOutput; 8 | use crate::{audiofile_output, feedback}; 9 | use crate::feedback::Feedback; 10 | use crate::mixer::{Mixer, RMixer}; 11 | use crate::output::ROutput; 12 | use crate::plugins_manager::PluginsManager; 13 | 14 | #[derive(PartialEq, Debug, Clone)] 15 | pub enum OutputParam { 16 | File(String, usize, String, String), 17 | Jack, 18 | } 19 | 20 | pub struct Factory { 21 | plugins_manager: PluginsManager, 22 | } 23 | 24 | pub type RFactory = Mutex; 25 | 26 | static INSTANCE: LazyLock = LazyLock::new(|| Mutex::new(Factory::new())); 27 | 28 | impl Factory { 29 | pub fn new() -> Factory { 30 | Self { 31 | plugins_manager: PluginsManager::new(), 32 | } 33 | } 34 | 35 | pub fn get_categorized_talkers_label_model(&self) -> Vec<(String, Vec<(String, String)>)> { 36 | self.plugins_manager.get_categorized_talkers_label_model() 37 | } 38 | 39 | pub fn make_talker( 40 | &self, 41 | model: &str, 42 | oid: Option, 43 | oname: Option<&str>, effective: bool, 44 | ) -> Result { 45 | let tkr = self.plugins_manager.make_talker(model, effective)?; 46 | Factory::set_identity(tkr.identifier(), oid, oname); 47 | Ok(tkr) 48 | } 49 | 50 | pub fn make_mixer( 51 | id: u32, 52 | name: &str, 53 | oparent: Option<&RMixer>, 54 | outputs: Vec, 55 | ) -> Result { 56 | let rmixer = Mixer::new_ref(oparent, outputs)?; 57 | Factory::set_identity(rmixer.borrow().identifier(), Some(id), Some(name)); 58 | Ok(rmixer) 59 | } 60 | 61 | pub fn make_output( 62 | model: &str, 63 | oid: Option, 64 | oname: Option<&str>, 65 | configuration: Option<&str>, 66 | ) -> Result { 67 | 68 | if model == audiofile_output::MODEL { 69 | match configuration { 70 | Some(conf) => { 71 | let output = AudioFileOutput::from_backup(AudioFormat::chunk_size(), conf)?; 72 | Factory::set_identity(output.borrow().identifier(), oid, oname); 73 | Ok(output) 74 | }, 75 | None => Err(failure::err_msg(format!("{} output need configuration date!", model))), 76 | } 77 | } else if model == feedback::MODEL { 78 | let output = Feedback::new_ref(AudioFormat::chunk_size())?; 79 | Factory::set_identity(output.borrow().identifier(), oid, oname); 80 | Ok(output) 81 | } else { 82 | Err(failure::err_msg(format!("Unknown output model {}!", model))) 83 | } 84 | } 85 | 86 | pub fn make_outputs(outputs_params: &Vec) -> Result, failure::Error> { 87 | let in_sample_rate = AudioFormat::sample_rate(); 88 | let mut outputs = Vec::with_capacity(outputs_params.len()); 89 | 90 | for op in outputs_params { 91 | match op { 92 | OutputParam::File(codec, out_sample_rate, channel_layout, file_path) => { 93 | let output = AudioFileOutput::new_ref( 94 | codec.as_str(), 95 | in_sample_rate, 96 | *out_sample_rate, 97 | channel_layout, 98 | file_path.as_str())?; 99 | 100 | outputs.push(output); 101 | }, 102 | _ => (), 103 | } 104 | } 105 | Ok(outputs) 106 | } 107 | 108 | fn set_identity(identifier: &RIdentifier, oid: Option, oname: Option<&str>) { 109 | match oid { 110 | Some(id) => identifier.borrow_mut().set_id(id), 111 | None => (), 112 | }; 113 | match oname { 114 | Some(name) => identifier.borrow_mut().set_name(name), 115 | None => (), 116 | }; 117 | } 118 | 119 | pub fn visit(mut f: F) -> Result 120 | where 121 | F: FnMut(&Factory) -> Result, 122 | { 123 | let res = match (*INSTANCE).lock() { 124 | Ok(factory) => f(&factory), 125 | Err(_) => Err(failure::err_msg("Factory::visite failed on lock!")), 126 | }; 127 | res 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /session/src/talkers/bounded_square.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use talker::audio_format::AudioFormat; 4 | use talker::ctalker; 5 | use talker::dsp; 6 | use talker::ear; 7 | use talker::ear::Init; 8 | use talker::identifier::Index; 9 | use talker::talker::{CTalker, Talker, TalkerBase}; 10 | use talker::talker_handler::TalkerHandlerBase; 11 | 12 | pub const MODEL: &str = "BoundedSquare"; 13 | 14 | pub struct BoundedSquare { 15 | next_rising_edge_tick: i64, 16 | next_falling_edge_tick: i64, 17 | } 18 | 19 | const FREQ_EAR_INDEX: Index = 0; 20 | const RATIO_EAR_INDEX: Index = 1; 21 | const ROOF_EAR_INDEX: Index = 2; 22 | const FLOOR_EAR_INDEX: Index = 3; 23 | 24 | const AUDIO_VOICE_PORT: usize = 1; 25 | 26 | impl BoundedSquare { 27 | pub fn new(mut base: TalkerBase) -> Result { 28 | base.add_ear(ear::cv(Some("freq"), 0., 20000., 440., &Init::DefValue)?); 29 | base.add_ear(ear::audio(Some("ratio"), -1., 1., 0., &Init::DefValue)?); 30 | base.add_ear(ear::cv(Some("roof"), -1000., 1000., 1., &Init::DefValue)?); 31 | base.add_ear(ear::cv(Some("floor"), -1000., 1000., 0., &Init::DefValue)?); 32 | 33 | base.add_cv_voice(Some("cv"), 0.); 34 | base.add_audio_voice(Some("au"), 0.); 35 | 36 | Ok(ctalker!( 37 | base, 38 | Self { 39 | next_rising_edge_tick: 0, 40 | next_falling_edge_tick: 0, 41 | } 42 | )) 43 | } 44 | 45 | pub fn descriptor() -> TalkerHandlerBase { 46 | TalkerHandlerBase::builtin("Oscillator", MODEL, "Bounded Square") 47 | } 48 | } 49 | 50 | impl Talker for BoundedSquare { 51 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 52 | let freq_ear = base.ear(FREQ_EAR_INDEX); 53 | let freq_buf = base.ear_cv_buffer(FREQ_EAR_INDEX); 54 | let ratio_ear = base.ear(RATIO_EAR_INDEX); 55 | let ratio_buf = base.ear_audio_buffer(RATIO_EAR_INDEX); 56 | let roof_ear = base.ear(ROOF_EAR_INDEX); 57 | let roof_buf = base.ear_cv_buffer(ROOF_EAR_INDEX); 58 | let floor_ear = base.ear(FLOOR_EAR_INDEX); 59 | let floor_buf = base.ear_cv_buffer(FLOOR_EAR_INDEX); 60 | let voice_buf = base.voice(port).audio_buffer(); 61 | let sample_rate = AudioFormat::sample_rate() as f32; 62 | 63 | let mut next_rising_edge_idx = if self.next_rising_edge_tick < tick { 64 | 0 65 | } else { 66 | (self.next_rising_edge_tick - tick) as usize 67 | }; 68 | let mut next_falling_edge_idx = if self.next_falling_edge_tick < tick { 69 | 0 70 | } else { 71 | (self.next_falling_edge_tick - tick) as usize 72 | }; 73 | 74 | let mut i: usize = 0; 75 | 76 | while i < len { 77 | let t = tick + i as i64; 78 | 79 | if i == next_rising_edge_idx { 80 | freq_ear.listen(t, 1); 81 | let f = freq_buf[0]; 82 | ratio_ear.listen(t, 1); 83 | let r = ratio_buf[0]; 84 | let p = sample_rate / f; 85 | 86 | next_rising_edge_idx = i + p as usize; 87 | next_falling_edge_idx = i + (p * (r * 0.5 + 0.5)) as usize; 88 | } 89 | 90 | let roof_end = len.min(next_falling_edge_idx); 91 | 92 | if i < roof_end { 93 | let roof_len = roof_end - i; 94 | let obtained_roof_len = roof_ear.listen(t, roof_len); 95 | 96 | for j in 0..obtained_roof_len { 97 | voice_buf[i] = roof_buf[j]; 98 | i += 1; 99 | } 100 | if obtained_roof_len < roof_len { 101 | break; 102 | } 103 | } 104 | 105 | let floor_end = len.min(next_rising_edge_idx); 106 | 107 | if i < floor_end { 108 | let floor_len = floor_end - i; 109 | let obtained_floor_len = 110 | floor_ear.listen(tick + next_falling_edge_idx as i64, floor_len); 111 | 112 | for j in 0..obtained_floor_len { 113 | voice_buf[i] = floor_buf[j]; 114 | i += 1; 115 | } 116 | if obtained_floor_len < floor_len { 117 | break; 118 | } 119 | } 120 | } 121 | 122 | if port == AUDIO_VOICE_PORT { 123 | dsp::audioize_buffer_by_clipping(voice_buf, 0, i); 124 | } 125 | 126 | self.next_rising_edge_tick = next_rising_edge_idx as i64 + tick; 127 | self.next_falling_edge_tick = next_falling_edge_idx as i64 + tick; 128 | 129 | i 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /session/src/audiofile_output.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use std::str::FromStr; 4 | 5 | use audiofile::writer::Writer; 6 | use talker::identifier::RIdentifier; 7 | 8 | use crate::audio_data::Vector; 9 | use crate::channel; 10 | use crate::output; 11 | use crate::output::{Output, ROutput}; 12 | 13 | pub const MODEL: &str = "file"; 14 | 15 | 16 | 17 | pub struct AudioFileOutput { 18 | identifier: RIdentifier, 19 | codec_name: String, 20 | in_sample_rate: usize, 21 | out_sample_rate: usize, 22 | channel_layout: String, 23 | file_path: String, 24 | writer: Option, 25 | } 26 | 27 | impl AudioFileOutput { 28 | pub fn new(codec_name: &str, in_sample_rate: usize, out_sample_rate: usize, channel_layout: &str, file_path: &str,) -> Result { 29 | Ok(Self { 30 | identifier: output::new_identifier("", MODEL), 31 | codec_name: codec_name.to_string(), 32 | in_sample_rate, 33 | out_sample_rate, 34 | channel_layout: channel_layout.to_string(), 35 | file_path: file_path.to_string(), 36 | writer: None, 37 | }) 38 | } 39 | 40 | pub fn new_ref(codec_name: &str, in_sample_rate: usize, out_sample_rate: usize, channel_layout: &str, file_path: &str,) -> Result { 41 | Ok(Rc::new(RefCell::new(AudioFileOutput::new(codec_name, in_sample_rate, out_sample_rate, channel_layout, file_path)?))) 42 | } 43 | 44 | pub fn from_backup(in_sample_rate: usize, configuration: &str,) -> Result { 45 | let params: Vec<&str> = configuration.split('|').collect(); 46 | 47 | if params.len() == 4 { 48 | let codec_name = params[0]; 49 | let out_sample_rate = usize::from_str(params[1]).map_err(|e| failure::err_msg(format!("{}", e)))?; 50 | let channel_layout = params[2]; 51 | let file_path = params[3]; 52 | AudioFileOutput::new_ref(codec_name, in_sample_rate, out_sample_rate, channel_layout, file_path) 53 | } 54 | else { 55 | Err(failure::err_msg(format!("AudioFileOutput configuration {} need 4 parameters!", configuration))) 56 | } 57 | } 58 | } 59 | 60 | impl Output for AudioFileOutput { 61 | fn identifier<'a>(&'a self) -> &'a RIdentifier { 62 | &self.identifier 63 | } 64 | 65 | fn model(&self) -> String{ 66 | MODEL.to_string() 67 | } 68 | 69 | fn codec_name<'a>(&'a self) -> &'a str { 70 | &self.codec_name 71 | } 72 | 73 | fn sample_rate(&self) -> usize { 74 | self.out_sample_rate 75 | } 76 | 77 | fn channel_layout<'a>(&'a self) -> &'a str{ 78 | &self.channel_layout 79 | } 80 | 81 | fn channels(&self) -> usize { 82 | match &self.writer { 83 | Some(ctx) => ctx.channels(), 84 | None => channel::Layout::channels(&self.channel_layout), 85 | } 86 | } 87 | 88 | fn channels_names(&self) -> Vec<&'static str> { 89 | channel::Layout::channels_names(&self.channel_layout) 90 | } 91 | 92 | fn file_path<'a>(&'a self) -> &'a str { 93 | &self.file_path 94 | } 95 | 96 | fn open(&mut self) -> Result<(), failure::Error> { 97 | 98 | let channels = channel::Layout::channels(&self.channel_layout); 99 | 100 | let mut writer = Writer::new(self.codec_name.as_str(), self.in_sample_rate, self.out_sample_rate, channels, self.file_path.as_str())?; 101 | 102 | if writer.channels() != channels { 103 | self.channel_layout = channel::Layout::from_channels(writer.channels()).to_string(); 104 | } 105 | 106 | writer.write_header()?; 107 | 108 | self.writer = Some(writer); 109 | 110 | Ok(()) 111 | } 112 | 113 | fn write( 114 | &mut self, 115 | channels: &Vec, 116 | nb_samples_per_channel: usize, 117 | ) -> Result<(), failure::Error> { 118 | let writer = self.writer.as_mut().ok_or(failure::err_msg(format!("AudioFileOutput not open")))?; 119 | 120 | writer.write_samples(channels, nb_samples_per_channel) 121 | } 122 | 123 | fn pause(&mut self) -> Result<(), failure::Error> { 124 | Ok(()) 125 | } 126 | 127 | fn run(&mut self) -> Result<(), failure::Error> { 128 | Ok(()) 129 | } 130 | 131 | fn close(&mut self) -> Result<(), failure::Error> { 132 | let ctx = self.writer.as_mut().ok_or(failure::err_msg(format!("AudioFileOutput not open")))?; 133 | 134 | let res = ctx.close(); 135 | self.writer = None; 136 | res 137 | } 138 | 139 | fn backup(&self) -> (&str, &str, String) { 140 | let conf = format!("{}|{}|{}|{}", self.codec_name(), self.out_sample_rate, self.channel_layout, self.file_path()); 141 | (output::KIND, MODEL, conf) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /session/src/channel.rs: -------------------------------------------------------------------------------- 1 | 2 | pub struct Layout{} 3 | 4 | pub const DEFAULT_LAYOUT: &str = "stereo"; 5 | pub const LAYOUTS: [&str; 30] = [ 6 | "mono", 7 | DEFAULT_LAYOUT, 8 | "2.1", 9 | "3.0", 10 | "3.0(back)", 11 | "4.0", 12 | "quad", 13 | "quad(side)", 14 | "3.1", 15 | "5.0", 16 | "5.0(side)", 17 | "4.1", 18 | "5.1", 19 | "5.1(side)", 20 | "6.0", 21 | "6.0(front)", 22 | "hexagonal", 23 | "6.1", 24 | "6.1(back)", 25 | "6.1(front)", 26 | "7.0", 27 | "7.0(front)", 28 | "7.1", 29 | "7.1(wide)", 30 | "7.1(wide-side)", 31 | "7.1(top)", 32 | "octagonal", 33 | "cube", 34 | "hexadecagonal", 35 | "22.2" 36 | ]; 37 | 38 | impl Layout { 39 | 40 | fn definitions() -> Vec> { 41 | vec![ 42 | vec!["FC"], // mono 43 | vec!["FL", "FR"], // stereo 44 | vec!["FL", "FR", "LFE"], // 2.1 45 | vec!["FL", "FR", "FC"], // 3.0 46 | vec!["FL", "FR", "BC"], // 3.0(back) 47 | vec!["FL", "FR", "FC", "BC"], // 4.0 48 | vec!["FL", "FR", "BL", "BR"], // quad 49 | vec!["FL", "FR", "SL", "SR"], // quad(side) 50 | vec!["FL", "FR", "FC", "LFE"], // 3.1 51 | vec!["FL", "FR", "FC", "BL", "BR"], // 5.0 52 | vec!["FL", "FR", "FC", "SL", "SR"], // 5.0(side) 53 | vec!["FL", "FR", "FC", "LFE", "BC"], // 4.1 54 | vec!["FL", "FR", "FC", "LFE", "BL", "BR"], // 5.1 55 | vec!["FL", "FR", "FC", "LFE", "SL", "SR"], // 5.1(side) 56 | vec!["FL", "FR", "FC", "BC", "SL", "SR"], // 6.0 57 | vec!["FL", "FR", "FLC", "FRC", "SL", "SR"], // 6.0(front) 58 | vec!["FL", "FR", "FC", "BL", "BR", "BC"], // hexagonal 59 | vec!["FL", "FR", "FC", "LFE", "BC", "SL", "SR"], // 6.1 60 | vec!["FL", "FR", "FC", "LFE", "BL", "BR", "BC"], // 6.1(back) 61 | vec!["FL", "FR", "LFE", "FLC", "FRC", "SL", "SR"], // 6.1(front) 62 | vec!["FL", "FR", "FC", "BL", "BR", "SL", "SR"], // 7.0 63 | vec!["FL", "FR", "FC", "FLC", "FRC", "SL", "SR"], // 7.0(front) 64 | vec!["FL", "FR", "FC", "LFE", "BL", "BR", "SL", "SR"], // 7.1 65 | vec!["FL", "FR", "FC", "LFE", "BL", "BR", "FLC", "FRC"], // 7.1(wide) 66 | vec!["FL", "FR", "FC", "LFE", "FLC", "FRC", "SL", "SR"], // 7.1(wide-side) 67 | vec!["FL", "FR", "FC", "LFE", "BL", "BR", "TFL", "TFR"], // 7.1(top) 68 | vec!["FL", "FR", "FC", "BL", "BR", "BC", "SL", "SR"], // octagonal 69 | vec!["FL", "FR", "BL", "BR", "TFL", "TFR", "TBL", "TBR"], // cube 70 | vec!["FL", "FR", "FC", "BL", "BR", "BC", "SL", "SR", "TFL", "TFC", "TFR", "TBL", "TBC", "TBR", "WL", "WR"], // hexadecagonal 71 | vec!["FL", "FR", "FC", "LFE", "BL", "BR", "FLC", "FRC", "BC", "SL", "SR", "TC", "TFL", "TFC", "TFR", "TBL", "TBC", "TBR", "LFE2", "TSL", "TSR", "BFC", "BFL", "BFR"], // 22.2 72 | ] 73 | } 74 | 75 | pub fn names() -> &'static[&'static str] { 76 | &LAYOUTS 77 | } 78 | 79 | pub fn index(layout: &str) -> usize { 80 | for idx in 0..LAYOUTS.len() { 81 | if LAYOUTS[idx] == layout { 82 | return idx; 83 | } 84 | } 85 | eprintln!("Unknow channel layout {}. Fallback to {}.", layout, DEFAULT_LAYOUT); 86 | 1 87 | } 88 | 89 | pub fn from_index(index: usize) -> &'static str { 90 | LAYOUTS[index] 91 | } 92 | 93 | pub fn channels_names(layout: &str) -> Vec<&'static str> { 94 | let mut idx = 0; 95 | 96 | for channels_names in Layout::definitions() { 97 | if LAYOUTS[idx] == layout { 98 | return channels_names; 99 | } 100 | idx += 1; 101 | } 102 | eprintln!("Unknow channel layout {}. Fallback to {}.", layout, DEFAULT_LAYOUT); 103 | vec!["Left", "Right"] 104 | } 105 | 106 | pub fn channels(layout: &str) -> usize { 107 | Layout::channels_names(layout).len() 108 | } 109 | 110 | pub fn from_channels(channels: usize) -> &'static str { 111 | for (idx, channels_names) in Layout::definitions().iter().enumerate() { 112 | if channels_names.len() == channels { 113 | return LAYOUTS[idx]; 114 | } 115 | } 116 | eprintln!("Unknow channel layout with {} channels. Fallback to {}.", channels, DEFAULT_LAYOUT); 117 | DEFAULT_LAYOUT 118 | } 119 | 120 | pub fn channels_names_from_channels(channels: usize) -> Vec<&'static str> { 121 | for channels_names in Layout::definitions() { 122 | if channels_names.len() == channels { 123 | return channels_names; 124 | } 125 | } 126 | eprintln!("Unknow channel layout with {} channels. Fallback to {}.", channels, DEFAULT_LAYOUT); 127 | vec!["Left", "Right"] 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Gaetan Dubreil 3 | * 4 | * All rights reserved.This file is distributed under the terms of the 5 | * GNU General Public License version 3.0. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU Lesser General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU Lesser General Public License 13 | * along with this program; if not, write to the Free Software 14 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | */ 16 | use cairo::Context; 17 | 18 | pub type Color = (f64, f64, f64); 19 | 20 | pub fn set_color(cc: &Context, (r, g, b): Color) { 21 | cc.set_source_rgb(r, g, b); 22 | } 23 | 24 | const FONT_SIZE: f64 = 12.; 25 | 26 | const H00: f64 = 0.; 27 | const HFF: f64 = 1.; 28 | const H20: f64 = 32. / 255.; 29 | const H50: f64 = 80. / 255.; 30 | const H90: f64 = 144. / 255.; 31 | const HD3: f64 = 211. / 255.; 32 | const HEE: f64 = 238. / 255.; 33 | 34 | pub const WHITE_COLOR: Color = (1., 1., 1.); // cyan 35 | const BACKGROUND_COLOR: Color = (H00, H00, H00); 36 | const SELECTED_IO_BACKGROUND_COLOR: Color = (1., 0.8, 0.4); 37 | const BOX_BACKGROUND_COLOR: Color = (H20, H20, H20); 38 | const BOX_BORDER_COLOR: Color = (H50, H50, H50); 39 | const MODEL_COLOR: Color = (HD3, HD3, HD3); // lightgray 40 | const NAME_COLOR: Color = (HFF, HFF, HFF); // white 41 | const DATA_COLOR: Color = (H90, HEE, H90); // lightgreen 42 | const AUDIO_COLOR: Color = (0., 1., 1.); // cyan 43 | const CONTROL_COLOR: Color = (1., 1., 0.); // yellow 44 | const CV_COLOR: Color = (1., 0.7, 0.); 45 | const ATOM_COLOR: Color = (1., 0., 1.); // magenta 46 | const IO_COLOR: Color = (0.7, 0.7, 0.7); 47 | const SELECTED_AUDIO_COLOR: Color = (0., 0.4, 0.4); // cyan 48 | const SELECTED_CONTROL_COLOR: Color = (0.4, 0.4, 0.); // yellow 49 | const SELECTED_CV_COLOR: Color = (0.5, 0.3, 0.); 50 | const SELECTED_ATOM_COLOR: Color = (0.4, 0., 0.4); // magenta 51 | const VALUE_COLOR: Color = (0.5, 0.5, 1.); // cyan 52 | const SUP_COLOR: Color = (HFF, H00, H00); // red 53 | const ADD_COLOR: Color = (0., 1., 0.); // green 54 | const SWITCH_COLOR: Color = (0.9, 0.87, 0.77); //(0.7, 0.4, 0.3); 55 | 56 | pub fn background(cc: &Context) { 57 | set_color(cc, BACKGROUND_COLOR); 58 | } 59 | pub fn selected_io_background(cc: &Context) { 60 | set_color(cc, SELECTED_IO_BACKGROUND_COLOR); 61 | cc.set_font_size(FONT_SIZE); 62 | } 63 | pub fn box_background(cc: &Context) { 64 | set_color(cc, BOX_BACKGROUND_COLOR); 65 | } 66 | pub fn box_border(cc: &Context) { 67 | set_color(cc, BOX_BORDER_COLOR); 68 | cc.set_line_width(0.5); 69 | } 70 | pub fn selected(cc: &Context) { 71 | selected_io_background(cc); 72 | } 73 | 74 | pub fn model(cc: &Context) { 75 | set_color(cc, MODEL_COLOR); 76 | cc.set_font_size(FONT_SIZE); 77 | } 78 | pub fn name(cc: &Context) { 79 | set_color(cc, NAME_COLOR); 80 | cc.set_font_size(FONT_SIZE); 81 | } 82 | pub fn data(cc: &Context) { 83 | set_color(cc, DATA_COLOR); 84 | cc.set_font_size(FONT_SIZE); 85 | } 86 | pub fn audio(cc: &Context) { 87 | set_color(cc, AUDIO_COLOR); 88 | cc.set_font_size(FONT_SIZE); 89 | } 90 | pub fn control(cc: &Context) { 91 | set_color(cc, CONTROL_COLOR); 92 | cc.set_font_size(FONT_SIZE); 93 | } 94 | pub fn cv(cc: &Context) { 95 | set_color(cc, CV_COLOR); 96 | cc.set_font_size(FONT_SIZE); 97 | } 98 | pub fn atom(cc: &Context) { 99 | set_color(cc, ATOM_COLOR); 100 | cc.set_font_size(FONT_SIZE); 101 | } 102 | pub fn io(cc: &Context) { 103 | set_color(cc, IO_COLOR); 104 | cc.set_font_size(FONT_SIZE); 105 | } 106 | pub fn selected_audio(cc: &Context) { 107 | set_color(cc, SELECTED_AUDIO_COLOR); 108 | cc.set_font_size(FONT_SIZE); 109 | } 110 | pub fn selected_control(cc: &Context) { 111 | set_color(cc, SELECTED_CONTROL_COLOR); 112 | cc.set_font_size(FONT_SIZE); 113 | } 114 | pub fn selected_cv(cc: &Context) { 115 | set_color(cc, SELECTED_CV_COLOR); 116 | cc.set_font_size(FONT_SIZE); 117 | } 118 | pub fn selected_atom(cc: &Context) { 119 | set_color(cc, SELECTED_ATOM_COLOR); 120 | cc.set_font_size(FONT_SIZE); 121 | } 122 | pub fn value(cc: &Context) { 123 | set_color(cc, VALUE_COLOR); 124 | cc.set_font_size(FONT_SIZE); 125 | } 126 | 127 | pub fn symbol(cc: &Context) { 128 | cc.set_line_width(1.); 129 | } 130 | pub fn sup(cc: &Context) { 131 | symbol(cc); 132 | set_color(cc, SUP_COLOR); 133 | } 134 | pub fn add(cc: &Context) { 135 | symbol(cc); 136 | set_color(cc, ADD_COLOR); 137 | } 138 | pub fn switch(cc: &Context) { 139 | symbol(cc); 140 | set_color(cc, SWITCH_COLOR); 141 | } 142 | 143 | pub fn connection(cc: &Context, color: &Color) { 144 | set_color(cc, *color); 145 | cc.set_line_width(2.); 146 | } 147 | 148 | pub fn make_color(d1: u64, d2: u64) -> Color { 149 | let v = d1 + (d2 << 14); 150 | let r = (95 + (v % 3) * 80) as f64 / 255.; 151 | let g = (95 + (v % 5) * 40) as f64 / 255.; 152 | let b = (75 + (v % 7) * 30) as f64 / 255.; 153 | (r, g, b) 154 | } 155 | -------------------------------------------------------------------------------- /session/src/talkers/audio_switch.rs: -------------------------------------------------------------------------------- 1 | use talker::audio_format::AudioFormat; 2 | use talker::ctalker; 3 | use talker::dsp; 4 | use talker::ear::{self, Ear, Init, Set}; 5 | use talker::horn::PortType; 6 | use talker::identifier::Index; 7 | use talker::talker::{CTalker, Talker, TalkerBase}; 8 | use talker::talker_handler::TalkerHandlerBase; 9 | 10 | pub const MODEL: &str = "AudioSwitch"; 11 | 12 | #[derive(Debug)] 13 | pub struct AudioSwitch { 14 | sample_rate: usize, input_index: usize, input_times: usize, vm2: f32, vm1: f32, 15 | } 16 | 17 | const TRIGGER_EAR_INDEX: Index = 0; 18 | const INPUTS_EAR_INDEX: Index = 1; 19 | const INPUT_HUM_INDEX: Index = 0; 20 | const TIMES_HUM_INDEX: Index = 1; 21 | 22 | impl AudioSwitch { 23 | pub fn new(mut base: TalkerBase) -> Result { 24 | base.add_ear(ear::cv(Some("trig"), 0., 1., 0., &Init::DefValue)?); 25 | 26 | let in_stem_set = Set::from_attributs(&vec![ 27 | ("", PortType::Audio, -1., 1., 0., Init::DefValue), 28 | ("times", PortType::Control, 1., 100., 1., Init::DefValue), 29 | ])?; 30 | base.add_ear(Ear::new(None, true, Some(in_stem_set), None)); 31 | 32 | base.add_audio_voice(None, 0.); 33 | 34 | Ok(ctalker!( 35 | base, 36 | Self { 37 | sample_rate: AudioFormat::sample_rate(), input_index: 0, input_times: 1, vm2: 0., vm1: 0., 38 | } 39 | )) 40 | } 41 | 42 | pub fn descriptor() -> TalkerHandlerBase { 43 | TalkerHandlerBase::builtin("Sequencer", MODEL, "Audio Switch") 44 | } 45 | } 46 | 47 | impl Talker for AudioSwitch { 48 | fn activate(&mut self) { 49 | self.sample_rate = AudioFormat::sample_rate(); 50 | self.input_index = 0; 51 | self.input_times = 1; 52 | self.vm2 = 0.; 53 | self.vm1 = 0.; 54 | } 55 | 56 | fn sup_ear_set_update( 57 | &mut self, 58 | base: &TalkerBase, 59 | ear_idx: usize, 60 | set_idx: usize, 61 | ) -> Result, failure::Error> { 62 | base.ear(ear_idx).sup_set(set_idx)?; 63 | 64 | if self.input_index > 0 && self.input_index >= base.ear(ear_idx).sets_len() { 65 | self.input_index -= 1; 66 | } 67 | 68 | Ok(None) 69 | } 70 | 71 | fn talk(&mut self, base: &TalkerBase, port: usize, tick: i64, len: usize) -> usize { 72 | let trigger_ear = base.ear(TRIGGER_EAR_INDEX); 73 | let inputs_ear = base.ear(INPUTS_EAR_INDEX); 74 | let inputs_count = inputs_ear.sets_len(); 75 | 76 | let voice_buf = base.voice(port).audio_buffer(); 77 | 78 | if inputs_count == 0 { 79 | voice_buf.fill(0.); 80 | len 81 | } 82 | else { 83 | let ln = trigger_ear.listen(tick, len); 84 | let fade_edge = ln - dsp::fade_len(self.sample_rate); 85 | 86 | let trigger_buf = trigger_ear.get_cv_buffer(); 87 | 88 | let mut in_start_idx = 0; 89 | let last_input_index = inputs_count - 1; 90 | let mut commuting = false; 91 | 92 | if tick == 0 { 93 | let _ = inputs_ear.listen_set_hum(0, 1, self.input_index, TIMES_HUM_INDEX); 94 | self.input_times = inputs_ear.get_set_hum_control_value(self.input_index, TIMES_HUM_INDEX) as usize; 95 | } 96 | 97 | for trig_idx in 0..ln { 98 | if trigger_buf[trig_idx] > 0.5 && (trig_idx > 0 || tick > 0) { 99 | self.input_times -= 1; 100 | 101 | if self.input_times == 0 { 102 | let idx = trig_idx.min(fade_edge); 103 | 104 | if idx > in_start_idx { 105 | let in_tick = tick + in_start_idx as i64; 106 | let in_len = idx - in_start_idx; 107 | 108 | let in_ln = inputs_ear.listen_set_hum(in_tick, in_len, self.input_index, INPUT_HUM_INDEX); 109 | let input_buf = inputs_ear.get_set_hum_audio_buffer(self.input_index, INPUT_HUM_INDEX); 110 | 111 | for i in 0..in_ln { 112 | voice_buf[in_start_idx + i] = input_buf[i]; 113 | } 114 | 115 | if commuting { 116 | dsp::recoveryless_fade_buffer(self.sample_rate, voice_buf, in_start_idx, self.vm2, self.vm1); 117 | } 118 | self.vm1 = voice_buf[idx - 1]; 119 | self.vm2 = if idx > 1 { voice_buf[idx - 2] } else { self.vm1 }; 120 | } 121 | in_start_idx = idx; 122 | 123 | self.input_index = if self.input_index < last_input_index { self.input_index + 1 } else { 0 }; 124 | commuting = true; 125 | 126 | let _ = inputs_ear.listen_set_hum(tick + trig_idx as i64, 1, self.input_index, TIMES_HUM_INDEX); 127 | self.input_times = inputs_ear.get_set_hum_control_value(self.input_index, TIMES_HUM_INDEX) as usize; 128 | } 129 | } 130 | } 131 | 132 | let in_tick = tick + in_start_idx as i64; 133 | let in_len = ln - in_start_idx; 134 | 135 | let in_ln = inputs_ear.listen_set_hum(in_tick, in_len, self.input_index, INPUT_HUM_INDEX); 136 | let input_buf = inputs_ear.get_set_hum_audio_buffer(self.input_index, INPUT_HUM_INDEX); 137 | 138 | for i in 0..in_ln { 139 | voice_buf[in_start_idx + i] = input_buf[i]; 140 | } 141 | 142 | if commuting { 143 | dsp::recoveryless_fade_buffer(self.sample_rate, voice_buf, in_start_idx, self.vm2, self.vm1); 144 | } 145 | let idx = in_start_idx + in_ln; 146 | self.vm1 = if idx > 0 { voice_buf[idx - 1] } else { self.vm1 }; 147 | self.vm2 = if idx > 1 { voice_buf[idx - 2] } else { self.vm2 }; 148 | 149 | ln 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/graph_control.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::HashMap; 3 | use std::rc::Rc; 4 | 5 | use gtk::gdk; 6 | use gtk::glib; 7 | use crate::gtk::prelude::{PopoverExt, WidgetExt}; 8 | 9 | use talker::identifier::{Id, Index}; 10 | 11 | use session::event_bus::{Notification, REventBus}; 12 | 13 | use crate::graph_presenter::RGraphPresenter; 14 | use crate::session_presenter::RSessionPresenter; 15 | use crate::talker_control::RTalkerControl; 16 | use crate::ui; 17 | 18 | 19 | pub struct GraphControl { 20 | session_presenter: RSessionPresenter, 21 | graph_presenter: RGraphPresenter, 22 | talker_controls: Vec, 23 | event_bus: REventBus, 24 | } 25 | pub type RGraphControl = Rc>; 26 | 27 | impl GraphControl { 28 | pub fn new_ref( 29 | window: >k::ApplicationWindow, 30 | session_presenter: &RSessionPresenter, 31 | graph_presenter: &RGraphPresenter, 32 | event_bus: &REventBus, 33 | ) -> RGraphControl { 34 | let instance = Rc::new(RefCell::new(Self { 35 | session_presenter: session_presenter.clone(), 36 | graph_presenter: graph_presenter.clone(), 37 | talker_controls: Vec::new(), 38 | event_bus: event_bus.clone(), 39 | })); 40 | 41 | // Key pressed event 42 | let key_pressed_ctrl = instance.clone(); 43 | let key_pressed_event_controller = gtk::EventControllerKey::builder().build(); 44 | key_pressed_event_controller.connect_key_pressed(move |_, key, _, _| key_pressed_ctrl.borrow().on_key_pressed(key)); 45 | window.add_controller(key_pressed_event_controller); 46 | 47 | // Key released event 48 | let key_released_ctrl = instance.clone(); 49 | let key_released_event_controller = gtk::EventControllerKey::builder().build(); 50 | key_released_event_controller.connect_key_released(move |_, key, _, _| key_released_ctrl.borrow().on_key_released(key)); 51 | window.add_controller(key_released_event_controller); 52 | 53 | instance 54 | } 55 | 56 | pub fn set_talker_controls(&mut self, talker_controls: &HashMap) { 57 | self.talker_controls.clear(); 58 | 59 | for tkrc in talker_controls.values() { 60 | self.talker_controls.push(tkrc.clone()); 61 | } 62 | } 63 | 64 | pub fn on_key_pressed(&self, key: gdk::Key) -> glib::signal::Propagation{ 65 | 66 | if key == gdk::Key::Control_L || key == gdk::Key::Control_R { 67 | self.graph_presenter.borrow_mut().set_multi_selection(true); 68 | } 69 | glib::signal::Propagation::Proceed 70 | } 71 | 72 | pub fn on_key_released(&self, key: gdk::Key) { 73 | 74 | if key == gdk::Key::Control_L || key == gdk::Key::Control_R { 75 | self.graph_presenter.borrow_mut().set_multi_selection(false); 76 | } 77 | } 78 | 79 | fn on_ear_value_selected( 80 | &self, 81 | x: f64, 82 | y: f64, 83 | talker_id: Id, 84 | ear_idx: Index, 85 | set_idx: Index, 86 | hum_idx: Index, 87 | popover: >k::Popover, 88 | ) { 89 | let session_presenter = self.session_presenter.borrow(); 90 | let tkr = session_presenter.find_talker(talker_id).unwrap(); 91 | 92 | let (min, max, def) = tkr.ear(ear_idx).hum_range(hum_idx); 93 | let cur = tkr.ear(ear_idx).talk_value_or_default(set_idx, hum_idx); 94 | 95 | let gp_on_scale = self.graph_presenter.clone(); 96 | let gp_on_ok = self.graph_presenter.clone(); 97 | let gp_on_cancel = self.graph_presenter.clone(); 98 | let gp_on_default = self.graph_presenter.clone(); 99 | 100 | let ok_popover = popover.clone(); 101 | let cancel_popover = popover.clone(); 102 | let default_popover = popover.clone(); 103 | 104 | let dialog = ui::bounded_float_entry::create( 105 | min, 106 | max, 107 | def, 108 | cur, 109 | move |v| { 110 | gp_on_scale.borrow_mut().set_talker_ear_talk_value_volatly( 111 | talker_id, ear_idx, set_idx, hum_idx, 0, v) 112 | }, 113 | move |v| { 114 | gp_on_ok.borrow_mut().set_talker_ear_talk_value( 115 | talker_id, ear_idx, set_idx, hum_idx, 0, v); 116 | ok_popover.popdown() 117 | }, 118 | move |_| { 119 | gp_on_cancel.borrow_mut().set_talker_ear_talk_value( 120 | talker_id, ear_idx, set_idx, hum_idx, 0, cur); 121 | cancel_popover.popdown() 122 | }, 123 | move |_| { 124 | gp_on_default.borrow_mut().set_talker_ear_talk_value( 125 | talker_id, ear_idx, set_idx, hum_idx, 0, def); 126 | default_popover.popdown() 127 | }, 128 | ); 129 | 130 | popover.set_child(Some(&dialog)); 131 | popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1))); 132 | popover.popup(); 133 | } 134 | 135 | pub fn on_button_release(&self, area_x: f64, area_y: f64, popover: >k::Popover) { 136 | let x = area_x - 5.; 137 | let y = area_y - 5.; 138 | 139 | for tkrc in &self.talker_controls { 140 | match tkrc.borrow().on_button_release(x, y, &self.graph_presenter) { 141 | Ok(None) => (), 142 | Ok(Some(notifications)) => { 143 | for notification in notifications { 144 | match notification { 145 | Notification::EarValueSelected( 146 | talker_id, 147 | ear_idx, 148 | set_idx, 149 | hum_idx, 150 | ) => self.on_ear_value_selected( 151 | x, y, talker_id, ear_idx, set_idx, hum_idx, popover, 152 | ), 153 | _ => self.event_bus.borrow().notify(notification), 154 | } 155 | } 156 | return; 157 | } 158 | Err(e) => self.event_bus.borrow().notify_error(e), 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /talker/src/horn.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::fmt; 3 | 4 | use livi::event::LV2AtomSequence; 5 | 6 | use crate::audio_format::AudioFormat; 7 | use crate::lv2_handler; 8 | use crate::lv2_handler::Lv2Handler; 9 | 10 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 11 | pub enum PortType { 12 | Audio, 13 | Control, 14 | Cv, 15 | Atom, 16 | } 17 | impl PortType { 18 | pub fn can_hear(&self, port_type: PortType) -> bool { 19 | match (port_type, self) { 20 | (PortType::Audio, PortType::Audio) => true, 21 | (_, PortType::Cv) => true, 22 | (_, PortType::Control) => true, 23 | (PortType::Atom, PortType::Atom) => true, 24 | _ => false, 25 | } 26 | } 27 | pub fn to_horn(&self) -> Horn { 28 | match self { 29 | PortType::Audio => Horn::audio(0., None), 30 | PortType::Control => Horn::control(0.), 31 | PortType::Cv => Horn::cv(0., None), 32 | PortType::Atom => Horn::atom(None), 33 | } 34 | } 35 | pub fn to_string(&self) -> &str { 36 | match self { 37 | PortType::Audio => "Audio", 38 | PortType::Control => "Control", 39 | PortType::Cv => "Cv", 40 | PortType::Atom => "Atom", 41 | } 42 | } 43 | } 44 | 45 | impl fmt::Display for PortType { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 47 | write!(f, "{}", self.to_string()) 48 | } 49 | } 50 | 51 | pub type AudioVal = f32; 52 | pub type AudioBuf<'a> = &'a [AudioVal]; 53 | pub type MAudioBuf<'a> = &'a mut [AudioVal]; 54 | 55 | pub type ControlVal = f32; 56 | pub type ControlBuf<'a> = &'a [ControlVal]; 57 | pub type MControlBuf<'a> = &'a mut [ControlVal]; 58 | 59 | pub type CvVal = f32; 60 | pub type CvBuf<'a> = &'a [CvVal]; 61 | pub type MCvBuf<'a> = &'a mut [CvVal]; 62 | 63 | pub type AtomBuf<'a> = &'a LV2AtomSequence; 64 | pub type MAtomBuf<'a> = &'a mut LV2AtomSequence; 65 | 66 | pub type HBuf = Cell>; 67 | pub type HAudioBuf = HBuf; 68 | pub type HControlBuf = HBuf; 69 | pub type HCvBuf = HBuf; 70 | pub type HAtomBuf = Option>; 71 | 72 | fn buf_val(value: f32, default: f32) -> f32 { 73 | if value.is_nan() { 74 | default 75 | } else { 76 | value 77 | } 78 | } 79 | fn buf_len(olen: Option) -> usize { 80 | olen.unwrap_or(AudioFormat::chunk_size()) 81 | } 82 | pub fn empty_buf() -> Cell> { 83 | Cell::new(Vec::new()) 84 | } 85 | pub fn empty_audio_buf() -> HAudioBuf { 86 | Cell::new(Vec::new()) 87 | } 88 | 89 | const ATOM_CAPACITY: usize = 8192; 90 | 91 | pub struct Horn { 92 | port_type: PortType, 93 | buf: HBuf, 94 | atom: HAtomBuf, 95 | } 96 | 97 | impl Horn { 98 | pub fn audio(value: AudioVal, len: Option) -> Horn { 99 | Horn { 100 | port_type: PortType::Audio, 101 | buf: Horn::audio_buf(value, len), 102 | atom: None, 103 | } 104 | } 105 | 106 | pub fn control(value: ControlVal) -> Horn { 107 | Horn { 108 | port_type: PortType::Control, 109 | buf: Horn::control_buf(value), 110 | atom: None, 111 | } 112 | } 113 | 114 | pub fn cv(value: CvVal, len: Option) -> Horn { 115 | Horn { 116 | port_type: PortType::Cv, 117 | buf: Horn::cv_buf(value, len), 118 | atom: None, 119 | } 120 | } 121 | 122 | pub fn atom(olv2_handler: Option<&Lv2Handler>) -> Horn { 123 | Horn { 124 | port_type: PortType::Atom, 125 | buf: empty_buf(), 126 | atom: Horn::atom_buf(olv2_handler), 127 | } 128 | } 129 | 130 | pub fn port_type(&self) -> PortType { 131 | self.port_type 132 | } 133 | 134 | pub fn audio_buffer(&self) -> MAudioBuf { 135 | unsafe { self.buf.as_ptr().as_mut().unwrap().as_mut_slice() } 136 | } 137 | pub fn control_buffer(&self) -> MControlBuf { 138 | unsafe { self.buf.as_ptr().as_mut().unwrap().as_mut_slice() } 139 | } 140 | pub fn cv_buffer(&self) -> MCvBuf { 141 | unsafe { self.buf.as_ptr().as_mut().unwrap().as_mut_slice() } 142 | } 143 | pub fn atom_buffer(&self) -> MAtomBuf { 144 | unsafe { &mut *self.atom.as_ref().unwrap().as_ptr().as_mut().unwrap() } 145 | } 146 | 147 | pub fn audio_value(&self, index: usize) -> AudioVal { 148 | self.audio_buffer()[index] 149 | } 150 | pub fn control_value(&self) -> ControlVal { 151 | self.control_buffer()[0] 152 | } 153 | pub fn set_control_value(&self, value: ControlVal) { 154 | let cb = self.control_buffer(); 155 | if value != cb[0] { 156 | cb.fill(value); 157 | } 158 | } 159 | pub fn cv_value(&self, index: usize) -> CvVal { 160 | self.cv_buffer()[index] 161 | } 162 | 163 | pub fn value(&self, index: usize) -> f32 { 164 | match self.port_type { 165 | PortType::Audio => self.audio_value(index), 166 | PortType::Control => self.control_value(), 167 | PortType::Cv => self.cv_value(index), 168 | PortType::Atom => 0., 169 | } 170 | } 171 | 172 | pub fn audio_buf(value: AudioVal, olen: Option) -> HAudioBuf { 173 | Cell::new(vec![buf_val(value, 0.); buf_len(olen)]) 174 | } 175 | 176 | pub fn control_buf(value: ControlVal) -> HControlBuf { 177 | Cell::new(vec![buf_val(value, 1.); buf_len(None)]) 178 | } 179 | 180 | pub fn cv_buf(value: CvVal, olen: Option) -> HCvBuf { 181 | Cell::new(vec![buf_val(value, 0.); buf_len(olen)]) 182 | } 183 | 184 | pub fn atom_buf(olv2_handler: Option<&Lv2Handler>) -> HAtomBuf { 185 | match olv2_handler { 186 | Some(lv2_handler) => Some(Cell::new(LV2AtomSequence::new( 187 | &lv2_handler.features, 188 | ATOM_CAPACITY, 189 | ))), 190 | None => lv2_handler::visit(|lv2_handler| { 191 | Ok(Some(Cell::new(LV2AtomSequence::new( 192 | &lv2_handler.features, 193 | ATOM_CAPACITY, 194 | )))) 195 | }) 196 | .unwrap_or(None), 197 | } 198 | } 199 | } 200 | 201 | impl Clone for Horn { 202 | fn clone(&self) -> Horn { 203 | self.port_type.to_horn() 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /audiofile/src/reader.rs: -------------------------------------------------------------------------------- 1 | use ffmpeg::{codec, filter, format, frame, media, util, ChannelLayout}; 2 | 3 | fn filter( 4 | decoder: &codec::decoder::Audio, 5 | sample_format: format::Sample, sample_rate: usize 6 | ) -> Result { 7 | let mut filter = filter::Graph::new(); 8 | 9 | let args = format!( 10 | "time_base={}:sample_rate={}:sample_fmt={}:channel_layout=0x{:x}", 11 | decoder.time_base(), 12 | decoder.rate(), 13 | decoder.format().name(), 14 | decoder.channel_layout().bits() 15 | ); 16 | 17 | filter.add(&filter::find("abuffer").unwrap(), "in", &args)?; 18 | filter.add(&filter::find("abuffersink").unwrap(), "out", "")?; 19 | 20 | { 21 | let mut out = filter.get("out").unwrap(); 22 | 23 | out.set_sample_format(sample_format); 24 | out.set_channel_layout(decoder.channel_layout()); 25 | out.set_sample_rate(sample_rate as u32); 26 | } 27 | 28 | filter.output("in", 0)?.input("out", 0)?.parse("anull")?; 29 | filter.validate()?; 30 | 31 | Ok(filter) 32 | } 33 | 34 | pub struct Reader { 35 | stream_index: usize, 36 | decoder: codec::decoder::Audio, 37 | input_context: format::context::Input, 38 | in_time_base: ffmpeg::Rational, 39 | filter: filter::Graph, 40 | frame: frame::audio::Audio, 41 | remaining_samples_count: usize, 42 | remaining_samples_index: usize, 43 | } 44 | 45 | impl Reader { 46 | pub fn new(file_path: &str, sample_rate: usize) -> Result { 47 | let input_context = format::input(&file_path).unwrap(); 48 | let input_stream = input_context 49 | .streams() 50 | .best(media::Type::Audio).ok_or(failure::err_msg("could not find best audio stream"))?; 51 | 52 | let context = ffmpeg::codec::context::Context::from_parameters(input_stream.parameters())?; 53 | let mut decoder = context.decoder().audio()?; 54 | 55 | decoder.set_parameters(input_stream.parameters())?; 56 | 57 | if decoder.channel_layout().bits() == 0 { 58 | if decoder.channels() == 1 { 59 | decoder.set_channel_layout(ChannelLayout::MONO); 60 | } 61 | else if decoder.channels() == 2 { 62 | decoder.set_channel_layout(ChannelLayout::STEREO); 63 | } 64 | else { 65 | decoder.set_channel_layout(ChannelLayout::default(0)); 66 | } 67 | } 68 | 69 | let sample_format = format::Sample::F32(util::format::sample::Type::Planar); 70 | let filter = filter(&decoder, sample_format, sample_rate).map_err(|e| failure::err_msg(format!("Filter graph : {}", e)))?; 71 | 72 | let mut frame = frame::audio::Audio::new(sample_format, 1024, decoder.channel_layout()); 73 | frame.set_rate(sample_rate as u32); 74 | 75 | let in_time_base = decoder.time_base(); 76 | 77 | Ok(Self { 78 | stream_index: input_stream.index(), 79 | decoder, 80 | input_context, 81 | in_time_base, 82 | filter, 83 | frame, 84 | remaining_samples_count: 0, 85 | remaining_samples_index: 0, 86 | }) 87 | } 88 | 89 | pub fn channels(&self) -> usize { 90 | self.decoder.channels() as usize 91 | } 92 | 93 | pub fn read_samples(&mut self, channels: &mut Vec>, nb_samples_per_channel: usize,) -> Result { 94 | let nb_channels = channels.len().min(self.channels()); 95 | let mut sample_idx = 0; 96 | let mut decoded_frame = frame::Audio::empty(); 97 | 98 | while sample_idx < nb_samples_per_channel { 99 | let rem_len = self.remaining_samples_count.min(nb_samples_per_channel - sample_idx); 100 | 101 | if rem_len > 0 { 102 | for chan_idx in 0..nb_channels { 103 | let channel = &mut channels[chan_idx]; 104 | let plane = self.frame.plane(chan_idx); 105 | 106 | for i in 0..rem_len { 107 | channel[sample_idx + i] = plane[self.remaining_samples_index + i]; 108 | } 109 | } 110 | self.remaining_samples_count -= rem_len; 111 | self.remaining_samples_index += rem_len; 112 | sample_idx += rem_len; 113 | } 114 | else if self.filter.get("out").unwrap().sink().frame(&mut self.frame).is_ok() { 115 | self.remaining_samples_count = self.frame.samples(); 116 | self.remaining_samples_index = 0; 117 | } 118 | else if self.decoder.receive_frame(&mut decoded_frame).is_ok() { 119 | let timestamp = decoded_frame.timestamp(); 120 | decoded_frame.set_pts(timestamp); 121 | self.filter.get("in").unwrap().source().add(&decoded_frame).map_err(|e| failure::err_msg(format!("{}", e)))?; 122 | } 123 | else if let Some((stream, mut packet)) = self.input_context.packets().next() { 124 | if stream.index() == self.stream_index { 125 | packet.rescale_ts(stream.time_base(), self.in_time_base); 126 | self.decoder.send_packet(&packet)?; 127 | } 128 | } 129 | else { 130 | return Ok(sample_idx); 131 | } 132 | } 133 | 134 | Ok(sample_idx) 135 | } 136 | 137 | pub fn read_all_samples(&mut self) -> Result>, failure::Error> { 138 | let channels_count = self.channels(); 139 | let chunk_size = 1_000_000; 140 | let mut chunks = Vec::with_capacity(channels_count); 141 | let mut channels = Vec::with_capacity(channels_count); 142 | 143 | for _ in 0..channels_count { 144 | chunks.push(vec![0.; chunk_size]); 145 | channels.push(Vec::new()); 146 | } 147 | 148 | loop { 149 | let len = self.read_samples(&mut chunks, chunk_size)?; 150 | 151 | if len == 0 { 152 | break; 153 | } 154 | 155 | if len == chunk_size { 156 | for c in 0..channels_count { 157 | channels[c].extend(chunks[c].iter()); 158 | } 159 | } 160 | else { 161 | for c in 0..channels_count { 162 | for t in 0..len { 163 | channels[c].push(chunks[c][t]); 164 | } 165 | } 166 | } 167 | } 168 | Ok(channels) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /session/src/session.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Gaëtan Dubreil 3 | * 4 | * All rights reserved.This file is distributed under the terms of the 5 | * GNU General Public License version 3.0. 6 | * 7 | * This program is distributed in the hope that it will be useful, 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | * GNU Lesser General Public License for more details. 11 | * 12 | * You should have received a copy of the GNU Lesser General Public License 13 | * along with this program; if not, write to the Free Software 14 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | */ 16 | 17 | use std::collections::HashMap; 18 | use std::fs::File; 19 | use std::io::Read; 20 | use std::io::Write; 21 | 22 | use talker::identifier; 23 | use talker::talker::RTalker; 24 | use talker::audio_format::AudioFormat; 25 | 26 | use crate::band::{Band, Operation}; 27 | use crate::mixer::RMixer; 28 | use crate::player::Player; 29 | use crate::state::State; 30 | 31 | pub const SESSION_FILE_EXT: &str = ".gsr"; 32 | pub const NEW_SESSION_FILENAME: &str = "new_session.gsr"; 33 | 34 | pub fn init() -> Result<(), failure::Error> { 35 | audiofile::init() 36 | } 37 | 38 | pub struct Session { 39 | filename: String, 40 | band: Band, 41 | player: Player, 42 | start_tick: i64, 43 | end_tick: i64, 44 | } 45 | 46 | impl Session { 47 | pub fn new(band_description: String) -> Result { 48 | Ok(Self { 49 | filename: NEW_SESSION_FILENAME.to_string(), 50 | band: Band::make(&band_description, false)?, 51 | player: Player::new(band_description)?, 52 | start_tick: 0, 53 | end_tick: 0, 54 | }) 55 | } 56 | 57 | pub fn from_file(filename: &str) -> Result { 58 | let mut band_description = String::new(); 59 | 60 | let mut f = File::open(filename)?; 61 | f.read_to_string(&mut band_description)?; 62 | 63 | Ok(Self { 64 | filename: filename.to_string(), 65 | band: Band::make(&band_description, false)?, 66 | player: Player::new(band_description)?, 67 | start_tick: 0, 68 | end_tick: 0, 69 | }) 70 | } 71 | 72 | pub fn filename<'a>(&'a self) -> &'a str { 73 | &self.filename 74 | } 75 | 76 | pub fn talkers<'a>(&'a self) -> &'a HashMap { 77 | self.band.talkers() 78 | } 79 | 80 | pub fn add_talker(&mut self, talker_model: &str) -> Result { 81 | self.modify_band(&Operation::AddTalker( 82 | identifier::get_next_id(), 83 | talker_model.to_string(), 84 | )) 85 | } 86 | 87 | pub fn mixers<'a>(&'a self) -> &'a HashMap { 88 | self.band.mixers() 89 | } 90 | 91 | pub fn state(&mut self) -> State { 92 | self.player.state() 93 | } 94 | 95 | pub fn start_tick(&self) -> i64 { 96 | self.start_tick 97 | } 98 | 99 | pub fn set_start_tick(&mut self, t: i64) -> Result { 100 | if self.start_tick == self.end_tick { 101 | self.start_tick = t; 102 | self.end_tick = t; 103 | } else { 104 | self.start_tick = t; 105 | } 106 | 107 | self.player.set_time_range(self.start_tick, self.end_tick) 108 | } 109 | 110 | pub fn end_tick(&self) -> i64 { 111 | self.end_tick 112 | } 113 | 114 | pub fn set_end_tick(&mut self, t: i64) -> Result { 115 | self.end_tick = t; 116 | 117 | self.player.set_time_range(self.start_tick, self.end_tick) 118 | } 119 | 120 | pub fn player<'a>(&'a mut self) -> &'a Player { 121 | &self.player 122 | } 123 | pub fn new_band(&mut self) -> Result<(), failure::Error> { 124 | self.band = Band::empty(false); 125 | self.player = Player::new("".to_string())?; 126 | Ok(()) 127 | } 128 | 129 | pub fn init(&mut self, band_description: String) -> Result<(), failure::Error> { 130 | self.band = Band::make(&band_description, false)?; 131 | self.player = Player::new(band_description)?; 132 | Ok(()) 133 | } 134 | 135 | fn check_not_exited(&mut self) -> Result<(), failure::Error> { 136 | 137 | if self.player.state() == State::Exited { 138 | self.player = Player::new(self.band.serialize()?)?; 139 | } 140 | Ok(()) 141 | } 142 | 143 | pub fn sample_rate(&self) -> usize { 144 | AudioFormat::sample_rate() 145 | } 146 | pub fn set_sample_rate(&self, sample_rate: usize) -> Result { 147 | AudioFormat::set_sample_rate(sample_rate); 148 | self.player.load_band(self.band.serialize()?) 149 | } 150 | 151 | pub fn load_band(&mut self, band_description: String) -> Result { 152 | self.band = Band::make(&band_description, false)?; 153 | self.player.load_band(band_description) 154 | } 155 | 156 | pub fn modify_band(&mut self, operation: &Operation) -> Result { 157 | self.band.modify(operation)?; 158 | self.player.modify_band(operation) 159 | } 160 | 161 | pub fn serialize_band(&self) -> Result { 162 | self.band.serialize() 163 | } 164 | 165 | pub fn save(&self) -> Result<(), failure::Error> { 166 | let mut file = File::create(&self.filename)?; 167 | 168 | writeln!(file, "{}", self.band.serialize()?)?; 169 | Ok(()) 170 | } 171 | pub fn save_as(&mut self, filename: &str) -> Result<(), failure::Error> { 172 | self.filename = filename.to_string(); 173 | 174 | if !filename.ends_with(SESSION_FILE_EXT) { 175 | self.filename.push_str(SESSION_FILE_EXT); 176 | } 177 | self.save() 178 | } 179 | 180 | pub fn play(&mut self) -> Result { 181 | self.check_not_exited()?; 182 | self.player.play() 183 | } 184 | 185 | pub fn pause(&mut self) -> Result { 186 | self.check_not_exited()?; 187 | self.player.pause() 188 | } 189 | 190 | pub fn stop(&mut self) -> Result { 191 | self.player.stop() 192 | } 193 | 194 | pub fn record(&mut self) -> Result { 195 | self.check_not_exited()?; 196 | self.player.record() 197 | } 198 | 199 | pub fn exit(&mut self) -> Result { 200 | self.player.exit() 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /session/src/tables/mod.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | use std::f64; 3 | use std::f64::consts::PI; 4 | 5 | pub mod parabolic; 6 | pub mod round; 7 | pub mod sinramp; 8 | pub mod roundramp; 9 | pub mod earlyramp; 10 | pub mod lateramp; 11 | 12 | pub fn fade_len(sample_rate: usize) -> usize { 13 | sample_rate / 100 14 | } 15 | 16 | pub fn create_fadein_fadeout(sample_rate: usize) -> (Vec, Vec) { 17 | let len = fade_len(sample_rate); 18 | let mut fadein_tab = Vec::with_capacity(len); 19 | let mut fadeout_tab = Vec::with_capacity(len); 20 | 21 | for i in 0..len { 22 | let a = ((i as f64 * PI) / (len as f64)) + (PI * 0.5); 23 | let v = (a.sin() + 1.) * 0.5; 24 | fadein_tab.push((1. - v) as f32); 25 | fadeout_tab.push(v as f32); 26 | } 27 | (fadein_tab, fadeout_tab) 28 | } 29 | 30 | pub fn create_fadeout(sample_rate: usize) -> Vec { 31 | let len = fade_len(sample_rate); 32 | let mut fadeout_tab = Vec::with_capacity(len); 33 | 34 | for i in 0..len { 35 | let a = ((i as f64 * PI) / (len as f64)) + (PI * 0.5); 36 | let v = (a.sin() + 1.) * 0.5; 37 | fadeout_tab.push(v as f32); 38 | } 39 | fadeout_tab 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use std::fs::File; 45 | use std::io::Write; 46 | 47 | use tables::f32; 48 | use tables::f64; 49 | use tables::PI; 50 | 51 | const RAMP_LEN : usize = 24000; 52 | 53 | #[test] 54 | fn test_conv() { 55 | assert!(3.9999_f32 as usize == 3); 56 | assert!(4.00001 as usize == 4); 57 | } 58 | 59 | #[test] 60 | fn create_round() -> Result<(), failure::Error> { 61 | let mut f = File::create("src/tables/round.rs")?; 62 | let len = 96000; 63 | 64 | writeln!(f, "pub const LEN:usize = {};", len)?; 65 | writeln!(f, "pub const TAB: [f32; LEN + 1] = [")?; 66 | 67 | let mid_len = len / 2; 68 | let step = 2. / mid_len as f64; 69 | let mut neg_part: Vec = Vec::with_capacity(mid_len); 70 | let mut x: f64 = -1.; 71 | 72 | for _ in 0..mid_len { 73 | let y = ((1. - (x * x)).sqrt()) as f32; 74 | writeln!(f, "{:.10},", y)?; 75 | neg_part.push(-y); 76 | x += step; 77 | } 78 | 79 | for y in neg_part { 80 | writeln!(f, "{:.10},", y)?; 81 | } 82 | writeln!(f, "0.0];")?; 83 | Ok(()) 84 | } 85 | 86 | #[test] 87 | fn create_parabolic() -> Result<(), failure::Error> { 88 | let mut f = File::create("src/tables/parabolic.rs")?; 89 | let len = 96000; 90 | 91 | writeln!(f, "pub const LEN:usize = {};", len)?; 92 | writeln!(f, "pub const TAB: [f32; LEN + 1] = [")?; 93 | 94 | let mid_len = len / 2; 95 | let step = 2. / mid_len as f64; 96 | let mut neg_part: Vec = Vec::with_capacity(mid_len); 97 | let mut x: f64 = -1.; 98 | 99 | for _ in 0..mid_len { 100 | let y = (1. - (x * x)) as f32; 101 | writeln!(f, "{:.10},", y)?; 102 | neg_part.push(-y); 103 | x += step; 104 | } 105 | 106 | for y in neg_part { 107 | writeln!(f, "{:.10},", y)?; 108 | } 109 | writeln!(f, "0.0];")?; 110 | Ok(()) 111 | } 112 | 113 | #[test] 114 | fn create_sinramp() -> Result<(), failure::Error> { 115 | let mut f = File::create("src/tables/sinramp.rs")?; 116 | 117 | writeln!(f, "pub const LEN:usize = {};", RAMP_LEN)?; 118 | writeln!(f, "pub const TAB: [f32; LEN] = [")?; 119 | 120 | for i in 0..RAMP_LEN { 121 | let a = ((i as f64 * PI) / (RAMP_LEN as f64)) - (PI * 0.5); 122 | let v = (a.sin() + 1.) * 0.5; 123 | writeln!(f, "{:.8},", v as f32)?; 124 | } 125 | writeln!(f, "];")?; 126 | 127 | Ok(()) 128 | } 129 | 130 | #[test] 131 | fn create_roundramp() -> Result<(), failure::Error> { 132 | let mut f = File::create("src/tables/roundramp.rs")?; 133 | 134 | writeln!(f, "pub const LEN:usize = {};", RAMP_LEN)?; 135 | writeln!(f, "pub const TAB: [f32; LEN] = [")?; 136 | 137 | let mid_len = RAMP_LEN / 2; 138 | let step = 1. / mid_len as f64; 139 | let mut x: f64 = 0.; 140 | 141 | for _ in 0..mid_len { 142 | let y = ((1.-((1. - (x * x)).sqrt())) * 0.5) as f32; 143 | writeln!(f, "{:.8},", y)?; 144 | x += step; 145 | } 146 | 147 | x = -1.; 148 | for _ in 0..mid_len { 149 | let y = ((0.5 * ((1. - (x * x)).sqrt())) + 0.5) as f32; 150 | writeln!(f, "{:.8},", y)?; 151 | x += step; 152 | } 153 | writeln!(f, "];")?; 154 | 155 | Ok(()) 156 | } 157 | 158 | #[test] 159 | fn create_earlyramp() -> Result<(), failure::Error> { 160 | let mut f = File::create("src/tables/earlyramp.rs")?; 161 | 162 | writeln!(f, "pub const LEN:usize = {};", RAMP_LEN)?; 163 | writeln!(f, "pub const TAB: [f32; LEN] = [")?; 164 | 165 | let step = 1. / RAMP_LEN as f64; 166 | let mut x: f64 = 1.; 167 | 168 | for _ in 0..RAMP_LEN { 169 | let y = (1.-(x * x * x)) as f32; 170 | writeln!(f, "{:.8},", y)?; 171 | x -= step; 172 | } 173 | writeln!(f, "];")?; 174 | 175 | Ok(()) 176 | } 177 | 178 | #[test] 179 | fn create_lateramp() -> Result<(), failure::Error> { 180 | let mut f = File::create("src/tables/lateramp.rs")?; 181 | 182 | writeln!(f, "pub const LEN:usize = {};", RAMP_LEN)?; 183 | writeln!(f, "pub const TAB: [f32; LEN] = [")?; 184 | 185 | let step = 1. / RAMP_LEN as f64; 186 | let mut x: f64 = 0.; 187 | 188 | for _ in 0..RAMP_LEN { 189 | let y = (x * x * x) as f32; 190 | writeln!(f, "{:.8},", y)?; 191 | x += step; 192 | } 193 | writeln!(f, "];")?; 194 | 195 | Ok(()) 196 | } 197 | 198 | #[test] 199 | fn create_fading() -> Result<(), failure::Error> { 200 | let len = 1200; 201 | let mut f_fadein = File::create("src/tables/fadein.rs")?; 202 | let mut f_fadeout = File::create("src/tables/fadeout.rs")?; 203 | 204 | writeln!(f_fadein, "pub const LEN:usize = {};\npub const TAB: [f32; LEN] = [", len)?; 205 | writeln!(f_fadeout, "pub const LEN:usize = {};\npub const TAB: [f32; LEN] = [", len)?; 206 | 207 | for i in 0..len { 208 | let a = ((i as f64 * PI) / (len as f64)) - (PI * 0.5); 209 | let v = (a.sin() + 1.) * 0.5; 210 | writeln!(f_fadein, "{:.10},", v as f32)?; 211 | writeln!(f_fadeout, "{:.10},", (1. - v) as f32)?; 212 | } 213 | writeln!(f_fadein, "];")?; 214 | writeln!(f_fadeout, "];")?; 215 | Ok(()) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/ui/plugin_ui.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ffi::{c_void, CString}; 3 | use std::os::raw::c_char; 4 | 5 | use lv2_raw::LV2Feature; 6 | 7 | extern crate suil_sys; 8 | 9 | use talker::lv2_handler; 10 | use talker::talker::RTalker; 11 | use talker::identifier::{Id, Identifiable}; 12 | 13 | use crate::session_presenter::RSessionPresenter; 14 | 15 | pub fn init() { 16 | let args = std::env::args().map(|arg| CString::new(arg).unwrap() ).collect::>(); 17 | let mut c_args = args.iter().map(|arg| arg.clone().into_raw()).collect::>(); 18 | 19 | let mut argc = c_args.len() as i32; 20 | let mut argv = c_args.as_mut_ptr(); 21 | 22 | unsafe { 23 | suil_sys::suil_init((&mut argc) as *mut i32, (&mut argv) as *mut *mut *mut i8, suil_sys::SuilArg_SUIL_ARG_NONE); 24 | } 25 | } 26 | struct PluginPresenter { 27 | talker: RTalker, 28 | session_presenter: RSessionPresenter, 29 | } 30 | 31 | unsafe extern "C" fn write_func( 32 | controller: suil_sys::SuilController, 33 | port_index: u32, 34 | buffer_size: u32, 35 | protocol: u32, 36 | buffer: *const ::std::os::raw::c_void, 37 | ) { 38 | let plugin_presenter: &mut PluginPresenter = unsafe { &mut *(controller as *mut PluginPresenter) }; 39 | 40 | let state = plugin_presenter.session_presenter.borrow_mut().check_state().to_string(); 41 | println!("port_index: {}, buffer_size: {}, protocol: {}, state: {}", port_index, buffer_size, protocol, state); 42 | } 43 | unsafe extern "C" fn index_func( 44 | controller: suil_sys::SuilController, 45 | port_symbol: *const ::std::os::raw::c_char, 46 | ) -> u32 { 47 | let plugin_presenter: &mut PluginPresenter = unsafe { &mut *(controller as *mut PluginPresenter) }; 48 | 0 49 | } 50 | unsafe extern "C" fn subscribe_func( 51 | controller: suil_sys::SuilController, 52 | port_index: u32, 53 | protocol: u32, 54 | features: *const *const lv2_raw::LV2Feature, 55 | ) -> u32 {0} 56 | unsafe extern "C" fn unsubscribe_func( 57 | controller: suil_sys::SuilController, 58 | port_index: u32, 59 | protocol: u32, 60 | features: *const *const lv2_raw::LV2Feature, 61 | ) -> u32 {0} 62 | 63 | struct InstanceHandler { 64 | instance: *mut suil_sys::SuilInstance, 65 | plugin_presenter: PluginPresenter, 66 | } 67 | 68 | pub struct Manager { 69 | suil_host: *mut suil_sys::SuilHost, 70 | instances: HashMap, 71 | features: Vec, 72 | } 73 | 74 | impl Manager { 75 | pub fn new() -> Manager { 76 | let suil_host = unsafe {suil_sys::suil_host_new(Some(write_func), 77 | Some(index_func), 78 | Some(subscribe_func), 79 | Some(unsubscribe_func)) 80 | }; 81 | 82 | let bounded_block_length_feature = lv2_raw::LV2Feature { 83 | uri: lv2_sys::LV2_BUF_SIZE__boundedBlockLength.as_ptr().cast(), 84 | data: std::ptr::null_mut(), 85 | }; 86 | 87 | let features = vec![bounded_block_length_feature]; 88 | 89 | Self { 90 | suil_host, 91 | instances: HashMap::new(), 92 | features, 93 | } 94 | } 95 | 96 | pub fn show(&mut self, talker: &RTalker, session_presenter: &RSessionPresenter) -> Result<(), failure::Error> { 97 | lv2_handler::visit(|lv2_handler| { 98 | let plugin_uri = &talker.model(); 99 | 100 | match lv2_handler.world.plugin_by_uri(plugin_uri) { 101 | Some(plugin) => { 102 | let plugin_uri_node = plugin.raw().uri(); 103 | let plugin_uri = plugin_uri_node.as_uri().expect("Missing plugin uri."); 104 | 105 | let uis = plugin.raw().uis().expect("Missing plugin UIs."); 106 | let ui = uis.iter().next().expect("Missing plugin UI."); 107 | let ui_type_uri_node = ui.classes().iter().next().expect("Missing UI classe."); 108 | let ui_type_uri = ui_type_uri_node.as_uri().unwrap().as_ptr() as *const i8; 109 | let ui_uri = ui.uri(); 110 | let ui_binary_uri = ui.binary_uri().expect("Missing binary uri."); 111 | let ui_binary_path = ui_binary_uri.as_str().expect("Missing binary uri str.").get(7..).unwrap(); 112 | // let (ui_binary_hostname, ui_binary_path) = binary_uri.path().expect("Missing binary path."); 113 | let ui_bundle_uri = ui.bundle_uri().expect("Missing bundle uri."); 114 | let ui_bundle_path = ui_bundle_uri.as_str().expect("Missing bundle uri str.").get(7..).unwrap(); 115 | // let (ui_bundle_hostname, ui_bundle_path) = ui.bundle_uri().map_or(None, |u| u.path()).expect("Missing bundle path."); 116 | 117 | let mut plugin_presenter = PluginPresenter { 118 | talker: talker.clone(), 119 | session_presenter: session_presenter.clone(), 120 | }; 121 | 122 | let plugin_presenter_ptr: *mut c_void = &mut plugin_presenter as *mut _ as *mut c_void; 123 | 124 | let host_type_uri = lv2_handler::LV2_UI_HOST_TYPE_URI.as_ptr() as *const i8; 125 | let plugin_uri = plugin_uri.as_ptr() as *const i8; 126 | let ui_uri = ui_uri.as_uri().unwrap().as_ptr() as *const i8; 127 | let ui_bundle_path = ui_bundle_path.as_ptr() as *const i8; 128 | let ui_binary_path = ui_binary_path.as_ptr() as *const i8; 129 | 130 | let features: Vec<*const LV2Feature> = self.features 131 | .iter() 132 | .map(|f| f as *const LV2Feature) 133 | .chain(std::iter::once(std::ptr::null())) 134 | .collect(); 135 | 136 | let instance = unsafe {suil_sys::suil_instance_new(self.suil_host, 137 | plugin_presenter_ptr, 138 | host_type_uri, 139 | plugin_uri, 140 | ui_uri, 141 | ui_type_uri, 142 | ui_bundle_path, 143 | ui_binary_path, 144 | features.as_ptr()) 145 | }; 146 | 147 | self.instances.insert(talker.id(), InstanceHandler{instance, plugin_presenter}); 148 | 149 | Ok(()) 150 | }, 151 | None => Err(failure::err_msg(format!("LV2 plugin {} not found.", plugin_uri))), 152 | } 153 | }) 154 | } 155 | } 156 | 157 | impl Drop for Manager { 158 | fn drop(&mut self) { 159 | for inst in self.instances.values() { 160 | unsafe{suil_sys::suil_instance_free(inst.instance);} 161 | } 162 | unsafe{suil_sys::suil_host_free(self.suil_host);} 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/ui/bounded_float_entry.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::gtk::Adjustment; 4 | use gtk::prelude::IsA; 5 | use gtk::prelude::EditableExt; 6 | use gtk::prelude::WidgetExt; 7 | use crate::gtk::prelude::AdjustmentExt; 8 | use crate::gtk::prelude::ButtonExt; 9 | use crate::gtk::prelude::BoxExt; 10 | 11 | fn key_is_numeric(c: u32) -> bool { 12 | (c >= 10 && c <= 19) || (c >= 79 && c <= 91) || c == 59 || c == 82 13 | } 14 | 15 | pub fn create< 16 | OnValueChanged: Fn(f32) + 'static, 17 | OnOk: Fn(f32) + 'static, 18 | OnCancel: Fn(f32) + 'static, 19 | OnDefault: Fn(f32) + 'static, 20 | >( 21 | min: f32, 22 | max: f32, 23 | def: f32, 24 | current: f32, 25 | on_value_changed: OnValueChanged, 26 | on_ok: OnOk, 27 | on_cancel: OnCancel, 28 | on_default: OnDefault, 29 | ) -> impl IsA { 30 | let step = f32::min((max - min) / 40000., 1.); 31 | let adjustment = Adjustment::new( 32 | current as f64, 33 | min as f64, 34 | max as f64, 35 | step as f64, 36 | step as f64 * 100., 37 | 0.); 38 | 39 | let scale = gtk::Scale::builder() 40 | .adjustment(&adjustment) 41 | .orientation(gtk::Orientation::Vertical) 42 | .width_request(64) 43 | .height_request(360) 44 | .inverted(true) 45 | .draw_value(false) 46 | .can_focus(false) 47 | .build(); 48 | 49 | let entry = gtk::Entry::builder() 50 | .input_purpose(gtk::InputPurpose::Number) 51 | .css_classes(["bounded_float_entry"]) 52 | .can_focus(false) 53 | .build(); 54 | entry.set_text(¤t.to_string()); 55 | entry.select_region(0, -1); 56 | 57 | let adjustment_entry = entry.clone(); 58 | 59 | adjustment.connect_value_changed(move |adj| { 60 | let v = adj.value() as f32; 61 | on_value_changed(v); 62 | adjustment_entry.set_text(&v.to_string()); 63 | adjustment_entry.select_region(0, -1); 64 | }); 65 | 66 | 67 | let key_event_receiver = gtk::Button::builder().can_focus(true).build(); 68 | key_event_receiver.set_child(Some(&entry)); 69 | 70 | let cancel_button = gtk::Button::builder() 71 | .label("Cancel") 72 | .hexpand(true) 73 | .can_focus(false) 74 | .build(); 75 | 76 | let default_button = gtk::Button::builder() 77 | .label("Default") 78 | .hexpand(true) 79 | .can_focus(false) 80 | .build(); 81 | 82 | let ok_button = gtk::Button::builder() 83 | .label("Ok") 84 | .hexpand(true) 85 | .can_focus(false) 86 | .build(); 87 | 88 | // box 89 | let value_box = gtk::Box::builder() 90 | .orientation(gtk::Orientation::Horizontal) 91 | .spacing(2) 92 | .build(); 93 | value_box.append(&key_event_receiver); 94 | value_box.append(&scale); 95 | 96 | let action_box = gtk::Box::builder() 97 | .orientation(gtk::Orientation::Horizontal) 98 | .spacing(2) 99 | .build(); 100 | action_box.append(&cancel_button); 101 | action_box.append(&default_button); 102 | action_box.append(&ok_button); 103 | 104 | let widget = gtk::Box::builder() 105 | .orientation(gtk::Orientation::Vertical) 106 | .spacing(2) 107 | .build(); 108 | widget.append(&value_box); 109 | widget.append(&action_box); 110 | 111 | 112 | // Key pressed event 113 | let key_pressed_adjustment = adjustment.clone(); 114 | let key_pressed_entry = entry.clone(); 115 | let key_pressed_cancel_button = cancel_button.clone(); 116 | let key_pressed_ok_button = ok_button.clone(); 117 | 118 | let key_pressed_event_controller = gtk::EventControllerKey::builder().build(); 119 | key_pressed_event_controller.connect_key_pressed(move |_, key, key_code, _| { 120 | 121 | let entry_value = key_pressed_entry.text(); 122 | 123 | if key_is_numeric(key_code) { 124 | if let Some(car) = key.to_unicode() { 125 | 126 | let new_value = match key_pressed_entry.selection_bounds() { 127 | Some((sel_start, sel_end)) => format!("{}{}{}", 128 | entry_value.get(..sel_start as usize).unwrap(), 129 | car, 130 | entry_value.get(sel_end as usize..).unwrap()), 131 | None => format!("{}{}", entry_value, car), 132 | }; 133 | if new_value.matches(".").count() < 2 { 134 | key_pressed_entry.set_text(&new_value); 135 | } 136 | } 137 | } 138 | else if key == gtk::gdk::Key::Up || key == gtk::gdk::Key::KP_Up { 139 | let v = key_pressed_adjustment.value() + key_pressed_adjustment.step_increment(); 140 | key_pressed_adjustment.set_value(v); 141 | } 142 | else if key == gtk::gdk::Key::Down || key == gtk::gdk::Key::KP_Down { 143 | let v = key_pressed_adjustment.value() - key_pressed_adjustment.step_increment(); 144 | key_pressed_adjustment.set_value(v); 145 | } 146 | else if key == gtk::gdk::Key::Page_Up || key == gtk::gdk::Key::KP_Page_Up { 147 | let v = key_pressed_adjustment.value() + key_pressed_adjustment.page_increment(); 148 | key_pressed_adjustment.set_value(v); 149 | } 150 | else if key == gtk::gdk::Key::Page_Down || key == gtk::gdk::Key::KP_Page_Down { 151 | let v = key_pressed_adjustment.value() - key_pressed_adjustment.page_increment(); 152 | key_pressed_adjustment.set_value(v); 153 | } 154 | else if key == gtk::gdk::Key::Home || key == gtk::gdk::Key::KP_Home { 155 | key_pressed_adjustment.set_value(key_pressed_adjustment.upper()); 156 | } 157 | else if key == gtk::gdk::Key::End || key == gtk::gdk::Key::KP_End { 158 | key_pressed_adjustment.set_value(key_pressed_adjustment.lower()); 159 | } 160 | else if key == gtk::gdk::Key::BackSpace { 161 | if entry_value.len() > 0 { 162 | key_pressed_entry.set_text(entry_value.get(..entry_value.len() - 1).unwrap()); 163 | } 164 | } 165 | else if key == gtk::gdk::Key::Delete { 166 | key_pressed_entry.set_text(""); 167 | } 168 | else if key == gtk::gdk::Key::space || key == gtk::gdk::Key::Return || key == gtk::gdk::Key::KP_Enter { 169 | match f32::from_str(&entry_value) { 170 | Ok(v) => key_pressed_adjustment.set_value(v as f64), 171 | Err(_) => key_pressed_entry.set_text(&(key_pressed_adjustment.value() as f32).to_string()), 172 | } 173 | if key != gtk::gdk::Key::space { 174 | key_pressed_ok_button.emit_clicked(); 175 | } 176 | } 177 | else if key == gtk::gdk::Key::Escape { 178 | key_pressed_cancel_button.emit_clicked(); 179 | } 180 | 181 | return gtk::glib::signal::Propagation::Stop; 182 | }); 183 | key_event_receiver.add_controller(key_pressed_event_controller); 184 | 185 | cancel_button.connect_clicked(move |_| on_cancel(current)); 186 | 187 | default_button.connect_clicked(move |_| on_default(def)); 188 | 189 | ok_button.connect_clicked(move |_| on_ok(adjustment.value() as f32)); 190 | 191 | return widget; 192 | } 193 | --------------------------------------------------------------------------------