diff --git a/neothesia-core/src/config.rs b/neothesia-core/src/config.rs index 702d3f13..53c7d2ad 100644 --- a/neothesia-core/src/config.rs +++ b/neothesia-core/src/config.rs @@ -19,6 +19,9 @@ pub struct Config { #[serde(default = "default_playback_offset")] pub playback_offset: f32, + #[serde(default = "default_audio_gain")] + pub audio_gain: f32, + #[serde(default = "default_vertical_guidelines")] pub vertical_guidelines: bool, @@ -70,6 +73,7 @@ impl Config { speed_multiplier: default_speed_multiplier(), animation_speed: default_animation_speed(), playback_offset: default_playback_offset(), + audio_gain: default_audio_gain(), vertical_guidelines: default_vertical_guidelines(), horizontal_guidelines: default_horizontal_guidelines(), color_schema: default_color_schema(), @@ -122,6 +126,10 @@ fn default_playback_offset() -> f32 { 0.0 } +fn default_audio_gain() -> f32 { + 0.2 +} + fn default_vertical_guidelines() -> bool { false } diff --git a/neothesia/src/output_manager/midi_backend.rs b/neothesia/src/output_manager/midi_backend.rs index ab9a60cc..1e405f35 100644 --- a/neothesia/src/output_manager/midi_backend.rs +++ b/neothesia/src/output_manager/midi_backend.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, collections::HashSet, rc::Rc}; -use crate::output_manager::{OutputConnectionProxy, OutputDescriptor}; +use crate::output_manager::OutputDescriptor; use midi_file::midly::{ self, @@ -62,8 +62,8 @@ impl MidiBackend { } } -impl OutputConnectionProxy for MidiOutputConnection { - fn midi_event(&self, channel: u4, message: midly::MidiMessage) { +impl MidiOutputConnection { + pub fn midi_event(&self, channel: u4, message: midly::MidiMessage) { let inner = &mut *self.inner.borrow_mut(); match message { midly::MidiMessage::NoteOff { key, .. } => { @@ -82,7 +82,7 @@ impl OutputConnectionProxy for MidiOutputConnection { inner.conn.send(&inner.buf).ok(); } - fn stop_all(&self) { + pub fn stop_all(&self) { let inner = &mut *self.inner.borrow_mut(); for note in std::mem::take(&mut inner.active_notes).iter() { inner.buf.clear(); diff --git a/neothesia/src/output_manager/mod.rs b/neothesia/src/output_manager/mod.rs index 4451c440..89d88d41 100644 --- a/neothesia/src/output_manager/mod.rs +++ b/neothesia/src/output_manager/mod.rs @@ -33,11 +33,6 @@ impl Display for OutputDescriptor { } } -trait OutputConnectionProxy { - fn midi_event(&self, channel: u4, msg: MidiMessage); - fn stop_all(&self); -} - #[derive(Clone)] pub enum OutputConnection { Midi(midi_backend::MidiOutputConnection), @@ -55,6 +50,13 @@ impl OutputConnection { OutputConnection::DummyOutput => {} } } + pub fn set_gain(&self, gain: f32) { + match self { + #[cfg(feature = "synth")] + OutputConnection::Synth(b) => b.set_gain(gain), + _ => {} + } + } pub fn stop_all(&self) { match self { OutputConnection::Midi(b) => b.stop_all(), diff --git a/neothesia/src/output_manager/synth_backend.rs b/neothesia/src/output_manager/synth_backend.rs index 788767e5..102bce2a 100644 --- a/neothesia/src/output_manager/synth_backend.rs +++ b/neothesia/src/output_manager/synth_backend.rs @@ -1,6 +1,6 @@ use std::{error::Error, path::Path, rc::Rc, sync::mpsc::Receiver}; -use crate::output_manager::{OutputConnectionProxy, OutputDescriptor}; +use crate::output_manager::OutputDescriptor; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use midi_file::midly::{self, num::u4}; @@ -14,6 +14,7 @@ pub struct SynthBackend { stream_config: cpal::StreamConfig, sample_format: cpal::SampleFormat, + gain: f32, } impl SynthBackend { @@ -35,19 +36,20 @@ impl SynthBackend { stream_config, sample_format, + gain: 0.2, }) } fn run>( &self, - rx: Receiver, + rx: Receiver, path: &Path, ) -> cpal::Stream { #[cfg(all(feature = "fluid-synth", not(feature = "oxi-synth")))] let mut next_value = fluidsynth_adapter(self, rx, path); #[cfg(all(feature = "oxi-synth", not(feature = "fluid-synth")))] - let mut next_value = oxisynth_adapter(self, rx, path); + let mut next_value = oxisynth_adapter(self, rx, path, self.gain); let err_fn = |err| eprintln!("an error occurred on stream: {}", err); @@ -81,7 +83,7 @@ impl SynthBackend { } pub fn new_output_connection(&mut self, path: &Path) -> SynthOutputConnection { - let (tx, rx) = std::sync::mpsc::channel::(); + let (tx, rx) = std::sync::mpsc::channel::(); let stream = match self.sample_format { cpal::SampleFormat::I8 => self.run::(rx, path), cpal::SampleFormat::I16 => self.run::(rx, path), @@ -109,25 +111,38 @@ impl SynthBackend { } } +enum SynthEvent { + SetGain(f32), + Midi(oxisynth::MidiEvent), +} + #[derive(Clone)] pub struct SynthOutputConnection { _stream: Rc, - tx: std::sync::mpsc::Sender, + tx: std::sync::mpsc::Sender, } -impl OutputConnectionProxy for SynthOutputConnection { - fn midi_event(&self, channel: u4, msg: midly::MidiMessage) { +impl SynthOutputConnection { + pub fn midi_event(&self, channel: u4, msg: midly::MidiMessage) { let event = libmidi_to_oxisynth_event(channel, msg); - self.tx.send(event).ok(); + self.tx.send(SynthEvent::Midi(event)).ok(); + } + + pub fn set_gain(&self, gain: f32) { + self.tx.send(SynthEvent::SetGain(gain)).ok(); } - fn stop_all(&self) { + pub fn stop_all(&self) { for channel in 0..16 { self.tx - .send(oxisynth::MidiEvent::AllNotesOff { channel }) + .send(SynthEvent::Midi(oxisynth::MidiEvent::AllNotesOff { + channel, + })) .ok(); self.tx - .send(oxisynth::MidiEvent::AllSoundOff { channel }) + .send(SynthEvent::Midi(oxisynth::MidiEvent::AllSoundOff { + channel, + })) .ok(); } } @@ -175,13 +190,15 @@ fn libmidi_to_oxisynth_event(channel: u4, message: midly::MidiMessage) -> oxisyn #[cfg(all(feature = "oxi-synth", not(feature = "fluid-synth")))] fn oxisynth_adapter( this: &SynthBackend, - rx: Receiver, + rx: Receiver, path: &Path, + gain: f32, ) -> impl FnMut() -> (f32, f32) { let sample_rate = this.stream_config.sample_rate.0 as f32; let mut synth = oxisynth::Synth::new(oxisynth::SynthDescriptor { sample_rate, + gain, ..Default::default() }) .unwrap(); @@ -196,7 +213,14 @@ fn oxisynth_adapter( let (l, r) = synth.read_next(); if let Ok(event) = rx.try_recv() { - synth.send_event(event).ok(); + match event { + SynthEvent::SetGain(gain) => { + synth.set_gain(gain); + } + SynthEvent::Midi(event) => { + synth.send_event(event).ok(); + } + } } (l, r) @@ -206,7 +230,7 @@ fn oxisynth_adapter( #[cfg(all(feature = "fluid-synth", not(feature = "oxi-synth")))] fn fluidsynth_adapter( this: &SynthBackend, - rx: Receiver, + rx: Receiver, path: &Path, ) -> impl FnMut() -> (f32, f32) { use fluidlite::{IsSettings, Settings}; @@ -242,46 +266,51 @@ fn fluidsynth_adapter( if let Ok(e) = rx.try_recv() { match e { - oxisynth::MidiEvent::NoteOn { channel, key, vel } => { - synth.note_on(channel as u32, key as u32, vel as u32).ok(); - } - oxisynth::MidiEvent::NoteOff { channel, key } => { - synth.note_off(channel as u32, key as u32).ok(); - } - oxisynth::MidiEvent::PitchBend { channel, value } => { - synth.pitch_bend(channel as u32, value as u32).ok(); - } - oxisynth::MidiEvent::ProgramChange { - channel, - program_id, - } => { - synth.program_change(channel as u32, program_id as u32).ok(); - } - oxisynth::MidiEvent::ChannelPressure { channel, value } => { - synth.channel_pressure(channel as u32, value as u32).ok(); + SynthEvent::SetGain(_g) => { + // TODO } - oxisynth::MidiEvent::PolyphonicKeyPressure { - channel, - key, - value, - } => { - synth - .key_pressure(channel as u32, key as u32, value as u32) - .ok(); - } - oxisynth::MidiEvent::SystemReset => { - synth.system_reset().ok(); - } - oxisynth::MidiEvent::ControlChange { - channel, - ctrl, - value, - } => { - synth.cc(channel as u32, ctrl as u32, value as u32).ok(); - } - // TODO: Where are those for fluidsynth? - oxisynth::MidiEvent::AllNotesOff { .. } => {} - oxisynth::MidiEvent::AllSoundOff { .. } => {} + SynthEvent::Midi(e) => match e { + oxisynth::MidiEvent::NoteOn { channel, key, vel } => { + synth.note_on(channel as u32, key as u32, vel as u32).ok(); + } + oxisynth::MidiEvent::NoteOff { channel, key } => { + synth.note_off(channel as u32, key as u32).ok(); + } + oxisynth::MidiEvent::PitchBend { channel, value } => { + synth.pitch_bend(channel as u32, value as u32).ok(); + } + oxisynth::MidiEvent::ProgramChange { + channel, + program_id, + } => { + synth.program_change(channel as u32, program_id as u32).ok(); + } + oxisynth::MidiEvent::ChannelPressure { channel, value } => { + synth.channel_pressure(channel as u32, value as u32).ok(); + } + oxisynth::MidiEvent::PolyphonicKeyPressure { + channel, + key, + value, + } => { + synth + .key_pressure(channel as u32, key as u32, value as u32) + .ok(); + } + oxisynth::MidiEvent::SystemReset => { + synth.system_reset().ok(); + } + oxisynth::MidiEvent::ControlChange { + channel, + ctrl, + value, + } => { + synth.cc(channel as u32, ctrl as u32, value as u32).ok(); + } + // TODO: Where are those for fluidsynth? + oxisynth::MidiEvent::AllNotesOff { .. } => {} + oxisynth::MidiEvent::AllSoundOff { .. } => {} + }, } } diff --git a/neothesia/src/scene/menu_scene/iced_menu/mod.rs b/neothesia/src/scene/menu_scene/iced_menu/mod.rs index 9b060a9f..9b19c872 100644 --- a/neothesia/src/scene/menu_scene/iced_menu/mod.rs +++ b/neothesia/src/scene/menu_scene/iced_menu/mod.rs @@ -232,7 +232,10 @@ fn play(data: &Data, ctx: &mut Context) { o => o, }; - ctx.output_manager.connect(out) + ctx.output_manager.connect(out); + ctx.output_manager + .connection() + .set_gain(ctx.config.audio_gain); } if let Some(port) = data.selected_input.clone() { diff --git a/neothesia/src/scene/menu_scene/iced_menu/settings.rs b/neothesia/src/scene/menu_scene/iced_menu/settings.rs index 93848558..452bb6a5 100644 --- a/neothesia/src/scene/menu_scene/iced_menu/settings.rs +++ b/neothesia/src/scene/menu_scene/iced_menu/settings.rs @@ -34,6 +34,7 @@ pub enum Event { RangeStart(RangeUpdateKind), RangeEnd(RangeUpdateKind), + AudioGain(RangeUpdateKind), GoBack, } @@ -104,6 +105,18 @@ impl Page for SettingsPage { } } }, + Event::AudioGain(kind) => { + match kind { + RangeUpdateKind::Add => { + ctx.config.audio_gain += 0.1; + } + RangeUpdateKind::Sub => { + ctx.config.audio_gain = (ctx.config.audio_gain - 0.1).max(0.0); + } + } + + ctx.config.audio_gain = (ctx.config.audio_gain * 10.0).round() / 10.0; + } Event::GoBack => { return PageMessage::go_back(); } @@ -225,11 +238,17 @@ fn output_group<'a>(data: &'a Data, ctx: &Context) -> Element<'a, Event> { row }); + let synth_gain_settings = is_synth.then(|| { + ActionRow::new() + .title("Audio Gain") + .suffix(counter(ctx.config.audio_gain, Event::AudioGain)) + }); PreferencesGroup::new() .title("Output") .push(output_settings) .push_maybe(synth_settings) + .push_maybe(synth_gain_settings) .build() } @@ -245,7 +264,7 @@ fn input_group<'a>(data: &'a Data, _ctx: &Context) -> Element<'a, Event> { .build() } -fn counter<'a>(value: u8, msg: fn(RangeUpdateKind) -> Event) -> Element<'a, Event> { +fn counter<'a>(value: impl ToString, msg: fn(RangeUpdateKind) -> Event) -> Element<'a, Event> { let label = centered_text(value); let sub = button(centered_text("-").width(30).height(30)) .padding(0)