Skip to content

Commit

Permalink
#153 Use UUIDs because copyable and make OSC dev add/edit work
Browse files Browse the repository at this point in the history
  • Loading branch information
helgoboss committed Feb 21, 2021
1 parent 88558b6 commit bd507e5
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 65 deletions.
22 changes: 7 additions & 15 deletions main/src/domain/osc.rs
Expand Up @@ -8,6 +8,7 @@ use std::error::Error;
use std::io;
use std::net::{Ipv4Addr, SocketAddrV4, ToSocketAddrs, UdpSocket};
use std::str::FromStr;
use uuid::Uuid;

const OSC_BUFFER_SIZE: usize = 10_000;

Expand Down Expand Up @@ -101,22 +102,13 @@ impl OscOutputDevice {
///
/// This uniquely identifies an OSC device according to ReaLearn's device configuration.
#[derive(
Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Serialize, DeserializeFromStr,
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Serialize, Deserialize,
)]
pub struct OscDeviceId(String);
#[serde(transparent)]
pub struct OscDeviceId(uuid::Uuid);

impl FromStr for OscDeviceId {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let trimmed = s.trim();
if trimmed.is_empty() {
return Err("OSC device ID must not be empty");
}
let valid_regex = regex!(r#"^[A-Za-z0-9_~-]+$"#);
if !valid_regex.is_match(trimmed) {
return Err("OSC device ID contains illegal characters");
}
Ok(OscDeviceId(trimmed.to_owned()))
impl OscDeviceId {
pub fn random() -> OscDeviceId {
OscDeviceId(Uuid::new_v4())
}
}
83 changes: 71 additions & 12 deletions main/src/infrastructure/data/osc_device_management.rs
Expand Up @@ -2,6 +2,7 @@ use crate::core::default_util::{bool_true, is_bool_true, is_default};
use crate::domain::{OscDeviceId, OscInputDevice, OscOutputDevice};
use crate::infrastructure::plugin::App;
use derive_more::Display;
use rx_util::UnitEvent;
use rxrust::prelude::*;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
Expand All @@ -16,19 +17,19 @@ pub type SharedOscDeviceManager = Rc<RefCell<OscDeviceManager>>;

#[derive(Debug)]
pub struct OscDeviceManager {
devices: Vec<OscDevice>,
config: OscDeviceConfig,
changed_subject: LocalSubject<'static, (), ()>,
osc_device_config_file_path: PathBuf,
}

impl OscDeviceManager {
pub fn new(osc_device_config_file_path: PathBuf) -> OscDeviceManager {
let mut manager = OscDeviceManager {
config: Default::default(),
osc_device_config_file_path,
devices: vec![],
changed_subject: Default::default(),
};
let _ = manager.load().unwrap();
let _ = manager.load();
manager
}

Expand All @@ -37,47 +38,90 @@ impl OscDeviceManager {
.map_err(|_| "couldn't read OSC device config file".to_string())?;
let config: OscDeviceConfig = serde_json::from_str(&json)
.map_err(|e| format!("OSC device config file isn't valid. Details:\n\n{}", e))?;
self.devices = config.devices;
self.config = config;
Ok(())
}

fn save(&mut self) -> Result<(), String> {
fs::create_dir_all(&self.osc_device_config_file_path.parent().unwrap())
.map_err(|_| "couldn't create OSC device config file parent directory")?;
let json = serde_json::to_string_pretty(&self.config)
.map_err(|_| "couldn't serialize OSC device config")?;
fs::write(&self.osc_device_config_file_path, json)
.map_err(|_| "couldn't write OSC devie config file")?;
Ok(())
}

pub fn devices(&self) -> impl Iterator<Item = &OscDevice> + ExactSizeIterator {
self.devices.iter()
self.config.devices.iter()
}

pub fn find_index_by_id(&self, id: &OscDeviceId) -> Option<usize> {
self.devices.iter().position(|dev| dev.id() == id)
self.config.devices.iter().position(|dev| dev.id() == id)
}

pub fn find_device_by_id(&self, id: &OscDeviceId) -> Option<&OscDevice> {
self.config.devices.iter().find(|dev| dev.id() == id)
}

pub fn find_device_by_index(&self, index: usize) -> Option<&OscDevice> {
self.devices.get(index)
self.config.devices.get(index)
}

pub fn connect_all_enabled_inputs(&mut self) -> Vec<OscInputDevice> {
self.devices
self.config
.devices
.iter_mut()
.filter(|dev| dev.is_enabled_for_control())
.flat_map(|dev| dev.connect_input())
.collect()
}

pub fn connect_all_enabled_outputs(&mut self) -> Vec<OscOutputDevice> {
self.devices
self.config
.devices
.iter_mut()
.filter(|dev| dev.is_enabled_for_feedback())
.flat_map(|dev| dev.connect_output())
.collect()
}

pub fn changed(&self) -> impl UnitEvent {
self.changed_subject.clone()
}

pub fn add_device(&mut self, dev: OscDevice) -> Result<(), &'static str> {
self.config.devices.push(dev);
self.save_and_notify_changed();
Ok(())
}

pub fn update_device(&mut self, dev: OscDevice) -> Result<(), &'static str> {
let mut old_dev = self
.config
.devices
.iter_mut()
.find(|d| d.id() == dev.id())
.ok_or("couldn't find OSC device")?;
std::mem::replace(old_dev, dev);
self.save_and_notify_changed();
Ok(())
}

fn save_and_notify_changed(&mut self) {
self.save();
self.changed_subject.next(());
}
}

#[derive(Serialize, Deserialize)]
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct OscDeviceConfig {
#[serde(default)]
devices: Vec<OscDevice>,
}

#[derive(Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OscDevice {
id: OscDeviceId,
Expand All @@ -101,7 +145,7 @@ pub struct OscDevice {
impl Default for OscDevice {
fn default() -> Self {
Self {
id: OscDeviceId::from_str(nanoid::nanoid!(8).as_str()).unwrap(),
id: OscDeviceId::random(),
name: "".to_string(),
is_enabled_for_control: true,
is_enabled_for_feedback: true,
Expand Down Expand Up @@ -212,6 +256,21 @@ impl OscDevice {
}
Connected
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}

pub fn set_local_port(&mut self, local_port: Option<u16>) {
self.local_port = local_port;
}

pub fn set_device_host(&mut self, device_host: Option<Ipv4Addr>) {
self.device_host = device_host;
}

pub fn set_device_port(&mut self, device_port: Option<u16>) {
self.device_port = device_port;
}
}

#[derive(Display)]
Expand Down
86 changes: 53 additions & 33 deletions main/src/infrastructure/data/session_data.rs
Expand Up @@ -44,12 +44,12 @@ pub struct SessionData {
send_feedback_only_if_armed: bool,
/// `None` means "<FX input>"
#[serde(default, skip_serializing_if = "is_default")]
control_device_id: Option<String>,
control_device_id: Option<ControlDeviceId>,
///
/// - `None` means "\<None>"
/// - `Some("fx-output")` means "\<FX output>"
#[serde(default, skip_serializing_if = "is_default")]
feedback_device_id: Option<String>,
feedback_device_id: Option<FeedbackDeviceId>,
// Not set before 1.12.0-pre9
#[serde(default, skip_serializing_if = "is_default")]
default_group: Option<GroupModelData>,
Expand All @@ -69,6 +69,20 @@ pub struct SessionData {
parameters: HashMap<u32, ParameterData>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
enum ControlDeviceId {
Osc(OscDeviceId),
Midi(String),
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
enum FeedbackDeviceId {
Osc(OscDeviceId),
MidiOrFxOutput(String),
}

impl Default for SessionData {
fn default() -> Self {
use crate::application::session_defaults;
Expand Down Expand Up @@ -115,23 +129,23 @@ impl SessionData {
control_device_id: if let Some(osc_dev_id) =
session.osc_input_device_id.get_ref().as_ref()
{
Some(osc_dev_id.to_string())
Some(ControlDeviceId::Osc(osc_dev_id.clone()))
} else {
use MidiControlInput::*;
match session.midi_control_input.get() {
FxInput => None,
Device(dev) => Some(dev.id().to_string()),
Device(dev) => Some(ControlDeviceId::Midi(dev.id().to_string())),
}
},
feedback_device_id: if let Some(osc_dev_id) =
session.osc_output_device_id.get_ref().as_ref()
{
Some(osc_dev_id.to_string())
Some(FeedbackDeviceId::Osc(osc_dev_id.clone()))
} else {
use MidiFeedbackOutput::*;
session.midi_feedback_output.get().map(|o| match o {
Device(dev) => dev.id().to_string(),
FxOutput => "fx-output".to_string(),
Device(dev) => FeedbackDeviceId::MidiOrFxOutput(dev.id().to_string()),
FxOutput => FeedbackDeviceId::MidiOrFxOutput("fx-output".to_owned()),
})
},
default_group: Some(GroupModelData::from_model(
Expand Down Expand Up @@ -174,41 +188,47 @@ impl SessionData {
// Validation
let (midi_control_input, osc_control_input) = match self.control_device_id.as_ref() {
None => (MidiControlInput::FxInput, None),
Some(dev_id_string) => {
let raw_dev_id = dev_id_string.parse::<u8>();
if let Ok(raw_dev_id) = raw_dev_id {
// MIDI
let dev_id: MidiInputDeviceId = raw_dev_id
.try_into()
.map_err(|_| "invalid MIDI input device ID")?;
(MidiControlInput::Device(MidiInputDevice::new(dev_id)), None)
} else {
// OSC
(
MidiControlInput::FxInput,
Some(dev_id_string.parse::<OscDeviceId>()?),
)
Some(dev_id) => {
use ControlDeviceId::*;
match dev_id {
Midi(midi_dev_id_string) => {
let raw_midi_dev_id = midi_dev_id_string
.parse::<u8>()
.map_err(|_| "invalid MIDI input device ID")?;
let midi_dev_id: MidiInputDeviceId = raw_midi_dev_id
.try_into()
.map_err(|_| "MIDI input device ID out of range")?;
(
MidiControlInput::Device(MidiInputDevice::new(midi_dev_id)),
None,
)
}
Osc(osc_dev_id) => (MidiControlInput::FxInput, Some(osc_dev_id.clone())),
}
}
};
let (midi_feedback_output, osc_feedback_output) = match self.feedback_device_id.as_ref() {
None => (None, None),
Some(dev_id_string) => {
if dev_id_string == "fx-output" {
(Some(MidiFeedbackOutput::FxOutput), None)
} else {
let raw_dev_id = dev_id_string.parse::<u8>();
if let Ok(raw_dev_id) = raw_dev_id {
// MIDI
let dev_id = MidiOutputDeviceId::new(raw_dev_id);
Some(dev_id) => {
use FeedbackDeviceId::*;
match dev_id {
MidiOrFxOutput(s) if s == "fx-output" => {
(Some(MidiFeedbackOutput::FxOutput), None)
}
MidiOrFxOutput(midi_dev_id_string) => {
let midi_dev_id = midi_dev_id_string
.parse::<u8>()
.map(MidiOutputDeviceId::new)
.map_err(|_| "invalid MIDI output device ID")?;
(
Some(MidiFeedbackOutput::Device(MidiOutputDevice::new(dev_id))),
Some(MidiFeedbackOutput::Device(MidiOutputDevice::new(
midi_dev_id,
))),
None,
)
} else {
// OSC
(None, Some(dev_id_string.parse::<OscDeviceId>()?))
}
Osc(osc_dev_id) => (None, Some(osc_dev_id.clone())),
_ => return Err("unknown feedback device ID"),
}
}
};
Expand Down

0 comments on commit bd507e5

Please sign in to comment.