This commit is contained in:
Moritz Ruth 2024-02-01 16:21:53 +01:00
parent b5a7ab3c6b
commit ebb0552a13
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
9 changed files with 186 additions and 36 deletions

View file

@ -10,8 +10,8 @@ clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
serde = { version = "1.0.196", features = ["derive"] }
serde_regex = "1.1.0"
regex = "1.10.3"
parse-display = "0.8.2"
once_cell = "1.19.0"
env_logger = "0.11.1"
log = "0.4.20"
im = "15.1.0"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt-multi-thread", "sync"] }

View file

@ -1,10 +1,18 @@
use log::warn;
use once_cell::sync::Lazy;
use parse_display::helpers::regex::Regex;
use parse_display::Display;
use serde::Deserialize;
use std::sync::Arc;
use deckster_mode::{DecksterHandler, HandlerEvent, HandlerInitializationError, InitialHandlerMessage, KnobPath, KnobStyleByStateMap};
use log::warn;
use parse_display::Display;
use regex::Regex;
use serde::Deserialize;
use tokio::select;
use tokio::sync::broadcast;
use deckster_mode::shared::handler_communication::{
HandlerCommand, HandlerEvent, HandlerInitializationError, InitialHandlerMessage, KnobEvent, RotationDirection,
};
use deckster_mode::shared::path::KnobPath;
use deckster_mode::shared::state::KnobStyleByStateMap;
use deckster_mode::{send_command, DecksterHandler};
use pa_volume_interface::{PaEntityKind, PaEntityMetadata, PaEntityState, PaVolumeInterface};
#[derive(Debug, Clone, Deserialize)]
@ -92,8 +100,6 @@ pub enum State {
Muted,
}
static PA_VOLUME_INTERFACE: Lazy<PaVolumeInterface> = Lazy::new(|| PaVolumeInterface::spawn_thread("deckster".to_owned()));
fn get_volume_from_cv(channel_volumes: &[f32]) -> f32 {
*channel_volumes.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
@ -141,19 +147,156 @@ fn state_matches(target: &Target, state: &PaEntityState) -> bool {
}
pub struct Handler {
knobs: im::HashMap<KnobPath, KnobConfig>,
events_sender: broadcast::Sender<(KnobPath, KnobEvent)>,
#[allow(unused)]
runtime: tokio::runtime::Runtime,
}
impl Handler {
pub fn new(data: InitialHandlerMessage<(), KnobConfig>) -> Result<Self, HandlerInitializationError> {
Ok(Handler {
knobs: data.knob_configs.into_iter().map(|(p, (_, c))| (p, c)).collect(),
})
let (events_sender, _) = broadcast::channel::<(KnobPath, KnobEvent)>(5);
let pa_volume_interface = Arc::new(PaVolumeInterface::spawn_thread("deckster handler".to_owned()));
let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(1).build().unwrap();
for (path, (mode, config)) in data.knob_configs {
if !mode.is_empty() {
return Err(HandlerInitializationError::InvalidModeString {
message: "No mode string allowed.".into(),
});
}
let events_receiver = events_sender.subscribe();
let a = Arc::clone(&pa_volume_interface);
runtime.spawn(manage_knob(path, config, events_receiver, a));
}
Ok(Handler { events_sender, runtime })
}
}
impl DecksterHandler for Handler {
fn handle(&mut self, event: HandlerEvent) {
dbg!(&self.knobs, event);
if let HandlerEvent::Knob { path, event } = event {
self.events_sender.send((path, event)).unwrap();
}
}
}
async fn manage_knob(path: KnobPath, config: KnobConfig, mut events: broadcast::Receiver<(KnobPath, KnobEvent)>, pa_volume_interface: Arc<PaVolumeInterface>) {
let mut entity_state: Option<Arc<PaEntityState>> = None;
let (initial_state, mut volume_states) = pa_volume_interface.subscribe_to_state();
let update_knob_value = {
let config = &config;
let path = path.clone();
move |entity_state: &Option<Arc<PaEntityState>>| {
send_command(HandlerCommand::SetKnobValue {
path: path.clone(),
value: entity_state.as_ref().map(|s| {
if s.is_muted() && config.muted_turn_action == MutedTurnAction::UnmuteAtZero {
0.0
} else {
get_volume_from_cv(&s.channel_volumes())
}
}),
});
}
};
let update_knob_style = {
let config = &config;
let path = path.clone();
move |entity_state: &Option<Arc<PaEntityState>>| {
let state = match entity_state {
None => State::Inactive,
Some(s) if s.is_muted() => State::Muted,
Some(_) => State::Active,
};
let mut style = config.style.get(&state).cloned();
if let Some(ref mut s) = &mut style {
let v = entity_state.as_ref().map(|s| get_volume_from_cv(&s.channel_volumes()));
if let Some(ref mut label) = &mut s.label {
if let Some(v) = v {
// v is only None when state is State::Inactive
*label = label.replace("{percentage}", &((v * 100.0).round() as u32).to_string());
}
}
}
send_command(HandlerCommand::SetKnobStyle {
path: path.clone(),
value: style,
});
}
};
if let Some(state) = initial_state {
entity_state = state
.entities_by_id()
.values()
.find(|entity| state_matches(&config.target, entity))
.map(Arc::clone);
}
loop {
select! {
Ok(volume_state) = volume_states.recv() => {
entity_state = volume_state.entities_by_id().values().find(|entity| state_matches(&config.target, entity)).map(Arc::clone);
update_knob_style(&entity_state);
update_knob_value(&entity_state);
}
Ok((event_path, event)) = events.recv() => {
if event_path != path {
continue;
}
if let Some(entity_state) = &entity_state {
match event {
KnobEvent::Rotate { direction } => {
let factor: f32 = match direction {
RotationDirection::Clockwise => 1.0,
RotationDirection::Counterclockwise => -1.0,
};
let mut current_v = get_volume_from_cv(&entity_state.channel_volumes());
if entity_state.is_muted() {
match config.muted_turn_action {
MutedTurnAction::Ignore => continue,
MutedTurnAction::UnmuteAtZero => {
current_v = 0.0
}
MutedTurnAction::Unmute => {},
MutedTurnAction::Normal => {}
}
}
let new_v = (current_v + (factor * config.delta.unwrap_or(0.01))).clamp(0.0, 1.0);
if new_v > 0.0 && matches!(config.muted_turn_action, MutedTurnAction::Unmute | MutedTurnAction::UnmuteAtZero) {
pa_volume_interface.set_is_muted(*entity_state.id(), false);
}
pa_volume_interface.set_channel_volumes(*entity_state.id(), vec![new_v; entity_state.channel_volumes().len()]);
}
KnobEvent::Press => {
let is_muted = entity_state.is_muted();
if (is_muted && !config.disable_press_to_unmute) || (!is_muted && !config.disable_press_to_mute) {
pa_volume_interface.set_is_muted(*entity_state.id(), !is_muted)
}
}
_ => {}
}
}
}
}
}
}