Skip to content
Snippets Groups Projects
Select Git revision
  • 05d96d0e7e442259abff500e939443e73cd15d85
  • master default protected
2 results

main.rs

Blame
  • main.rs 18.74 KiB
    //! # 16x16 Hexmatrix OSC-Jack-Mixer
    
    use std::io;
    use jack;
    use jack::{Port, AudioIn, AudioOut};
    use rosc;
    use rosc::{OscPacket, OscMessage};
    use std::net::{SocketAddrV4, UdpSocket};
    use std::str::FromStr;
    use std::sync::atomic::{AtomicUsize, Ordering};
    use arr_macro::arr;
    use clap::{Arg, App};
    use rayon::prelude::*;
    
    /*
    TODO:
    - [ ] Document dependecies
    - [ ] Performance improvements:
        - [ ] implement performance monitoring
        - [ ] Flag volume modulations as dirty
        - [x] f32.mul_add() or f32.to_bits() / f32.from_bits()
        - [x] only calculate connected ports https://docs.rs/jack/0.7.1/jack/struct.Port.html#method.connected_count
    - [x] Support other Buffers than 1024
    - [ ]  Features:
        - [ ] Flags to control printing (--quiet etc)
        - [ ] Option to set initial values
        - [ ] Option to limit certain inputs/outputs to certain volumes
        - [ ] Option to ignore certain modulations
        - [ ] GUI/TLI to see matrix and current volumes
        - [ ] OSC: Per Channel Solo/Mute/Invert
        - [ ] OSC: Output Volumes
        - [ ] OSC: Output Levels
    */
    
    
    
    // Number of inputs and outputs.
    // Careful: this needs to be changed in many places
    const N_INPUTS: usize  = 16;
    const N_OUTPUTS: usize = 16;
    
    // Atomic Variables for Shared Mutability of Modulation values
    // One per Input, with n values where n is the number of outputs
    // Incoming float values get scaled to a unsigned usize (e.g. 64 bit)
    // Static arrays are used here for maximum performance
    // (No: it is not possible to nest arr! macro calls. yet.)
    static VOLS1:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS2:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS3:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS4:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS5:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS6:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS7:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS8:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS9:  [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS10: [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS11: [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS12: [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS13: [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS14: [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS15: [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    static VOLS16: [AtomicUsize; N_INPUTS] = arr![AtomicUsize::new(0); 16];
    
    
    
    fn get_volume_for_input_output_pair(input_index: usize, output_index: usize) -> f32 {
        // For each input get the corrsponding atomic value for the volume control
        match input_index {
            0 => VOLS1[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            1 => VOLS2[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            2 => VOLS3[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            3 => VOLS4[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            4 => VOLS5[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            5 => VOLS6[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            6 => VOLS7[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            7 => VOLS8[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            8 => VOLS9[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            9 => VOLS10[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            10 => VOLS11[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            11 => VOLS12[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            12 => VOLS13[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            13 => VOLS14[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            14 => VOLS15[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
            15 => VOLS16[output_index].load(Ordering::Relaxed) as f32 / usize::MAX as f32,
    
            _ => 0.0
        }
    }
    
    fn port_has_connections<T>(port: &Port<T>) -> bool {
        match port.connected_count() {
            Ok(count) =>  count > 0,
            _ => true
        }
    }
    
    pub struct Connection {
        inputs: Vec<String>,
        outputs: Vec<String>
    }
    
    impl Connection {
        pub fn from_vec(v: Vec<&str>) -> Self {
            let connections: Vec<Connector> = v.iter()
             .map(|c| {
                c.split(",")
                 .collect::<Vec<&str>>()
                 .iter()
                 .map(|x| x.trim())
                 .map(|x| Connector::new(x))
                 .collect::<Vec<Connector>>()
             })
             .flatten()
             .collect::<Vec<Connector>>();
    
            if connections.first().unwrap().is_own() {
                // Outgoing Connections
                
            } else {
                // Incoming Connections
            }
    
            Self {
                inputs:  Vec::new(),
                outputs: Vec::new()
            }
        }
    }
    
    
    pub struct Connector {
        name: String
    }
    
    impl Connector {
        pub fn new<S: AsRef<str>>(stringlike: S) -> Self {
            Self {
                name: stringlike.as_ref().to_string()
            }
        }
    
        pub fn is_own(&self) -> bool {
            !self.name.contains(":") || self.name == "all"
        }
    
        pub fn is_other(&self) -> bool {
            !self.is_own()
        }
    
        pub fn is_own_input(&self) -> bool {
            self.is_own() && self.name.starts_with("in_")
        }
    
        pub fn is_own_output(&self) -> bool {
            self.is_own() && self.name.starts_with("out_")
        }
    }
    
    
    fn main() {
        let matches = App::new(option_env!("CARGO_PKG_NAME").unwrap())
            .version(option_env!("CARGO_PKG_VERSION").unwrap())
            .about(option_env!("CARGO_PKG_DESCRIPTION").unwrap())
            .help_template("{bin} ({version})\n{about}\n\nUsage:\n    {usage}\n\nTo send a certain amount of one input to a output send a OSC message that looks like this: /ch/1/send/16 <volume>\nThis would set the send volume of input 1 to output 16 to the value of <volume>. A value of 0.0 would be off, a value of 1.0 would result in a unattenuated signal. Values above 1.0 result in amplification.\n\nOSC-Addresses:\n    /ch/<in>/send/<out> <volume>     Set the send volume (in → out)\n\nOptions:\n{options}")
            .arg(Arg::new("--host")
                .short('a')
                .long("--host")
                .value_name("HOST")
                .required(true)
                .about(format!("Set IP-address of {}", option_env!("CARGO_PKG_NAME").unwrap()).as_str())
                .takes_value(true))
            .arg(Arg::new("--receiving-port")
                .short('p')
                .long("--receiving-port")
                .value_name("INPORT")
                .required(true)
                .about("Set receiving port (OSC)")
                .takes_value(true))
            .arg(Arg::new("--sending-port")
                .long("--sending-port")
                .value_name("OUTPORT")
                .about("Set sending port (OSC)")
                .takes_value(true))
            .arg(Arg::new("--name")
                .short('n')
                .long("--name")
                .value_name("NAME")
                .about("Set the name of the jack node")
                .takes_value(true))
            .arg(Arg::new("--connect")
                .short('c')
                .long("--connect")
                .alias("--connection")
                .value_name("CONNECTION")
                .about("Connect to annother jack node.")
                . long_about("Foo")
                .multiple_occurrences(true)
                .multiple_values(true)
                .min_values(2)
                .takes_value(true))
            .get_matches();
    
        let ip = matches.value_of("--host").unwrap();
        let inport = matches.value_of("--receiving-port").unwrap();
    
        // Receive OSC-Packets at this address
        let addr = SocketAddrV4::from_str(format!("{}:{}", ip, inport).as_str()).expect("Please use a valid IP:Port addr");
        let sock = UdpSocket::bind(addr).unwrap();
        let mut buf = [0u8; rosc::decoder::MTU];
    
        let nodename = matches.value_of("--name").unwrap_or("Hexmatrix");
    
        let connections: Vec<_> = matches.grouped_values_of("--connect").unwrap().filter_map(|c| {
            println!("Got connection {:#?}", c);
            let connection = Connection::from_vec(c);
            Some(connection)
        }).collect();
    
        // Create new jack client (visible e.g. in Catia)
        let (client, _status) =
            jack::Client::new(nodename, jack::ClientOptions::NO_START_SERVER).expect("Couldn't connect to jack server. Not running?");
    
        // Open n Jack Input Ports via arr! macro.
        let mut i = 0u16;
        let inputs: [Port<AudioIn>; 16] = arr![
            client.register_port(
                format!("in_{}", {i += 1; i}).as_str(),
                jack::AudioIn::default())
                  .unwrap(); 
            16];
    
        // Open n Jack Output Ports via arr! macro.
        i = 0;
        let mut outputs: [Port<AudioOut>; 16] = arr![
            client.register_port(
                format!("out_{}", {i += 1; i}).as_str(),
                jack::AudioOut::default())
                  .unwrap(); 
            16];
    
        let buffersize = client.buffer_size() as usize;
        
        // Is called asynchronously. Moves/mixes the actual audio samples from A to be
        let process_callback = move |_: &jack::Client, ps: &jack::ProcessScope| -> jack::Control {
            // For every output get a sum of corresponding inputs
            let mut output_buffers: [[f32; 1024]; N_OUTPUTS] = [[0.0; 1024]; N_OUTPUTS];
    
            // Iterate over the inputs and add values to the corresponding outputs
            // (in this case for testing purposes: all to all)
            inputs.iter()
                  .enumerate()
                  .filter(|(_input_index, inport)| port_has_connections(inport))
                  .for_each(|(input_index, inport)| {
                    // Get a slice [f32; 1024] of samples for each port
                    let input_samples = &inport.as_slice(ps)[..buffersize];
    
                    // Sum each input to output buffer
                    output_buffers.par_iter_mut()
                                  .enumerate()
                                  .filter(|(output_index, _buffer)|  port_has_connections(&outputs[*output_index]))
                                  .map(|(output_index, buffer)| {
                                      let volume = get_volume_for_input_output_pair(input_index, output_index);
                                      (output_index, buffer, volume)
                                  })
                                  .filter(|(_, _, volume)| *volume != 0.0)
                                  .for_each(|(_output_index, b, volume)| {
                                    b.par_iter_mut()
                                     .take(buffersize)
                                     .enumerate()
                                     .for_each(|(sample_index, sample)| {
                                        *sample = input_samples[sample_index].mul_add(volume, *sample);
                                     });
                                  });
                  });
    
            // Iterate over the outputs and clone the values of the corresponding
            // output buffer to the output
            outputs.iter_mut()
                   .enumerate()
                   .for_each(|(output_index, outport)| {
                    outport.as_mut_slice(ps)[..buffersize].clone_from_slice(&output_buffers[output_index][..buffersize]);
                   });
            jack::Control::Continue
        };
    
        // Register the above closure with jack
        let process = jack::ClosureProcessHandler::new(process_callback);
    
        // Activate the client, which starts the processing.
        let active_client = client.activate_async(Notifications, process).unwrap();
    
        // Loop and receive OSC packages
        loop {
            match sock.recv_from(&mut buf) {
                Ok((size, _addr)) => {
                    // println!("Received packet with size {} from: {}", size, addr);
                    let packet = rosc::decoder::decode(&buf[..size]).unwrap();
                    handle_packet(packet);
                }
                Err(e) => {
                    println!("Error receiving from socket: {}", e);
                    break;
                }
            }
        }
    
        // Wait for user input to quit
        println!("Press enter/return to quit...");
        let mut user_input = String::new();
        io::stdin().read_line(&mut user_input).ok();
    
        active_client.deactivate().unwrap();
    }
    
    /// Handles received OSC packets
    fn handle_packet(packet: OscPacket) {
        match packet {
            OscPacket::Message(msg) => {
                process_message(&msg);
            }
            OscPacket::Bundle(bundle) => {
                match &bundle.content[0] {
                    OscPacket::Message(msg) => process_message(msg),
                    _ => {}
                }
            }
        }
    }
    
    /// Decides what to do with incoming OSC messages (e.g. set the atomic variables)
    /// 
    /// Implemented addresses:
    /// - `/chn/{input}/send/{output} volume`: Set the volume at which a input is sent to an output (default is 0.0, no attenuation is 1.0, 7.0 equals +18 dB)
    fn process_message(msg: &OscMessage) {
        // Split address at "/" and skip initial empty split
        let addr = msg.addr.trim().split("/").skip(1).collect::<Vec<&str>>();
    
        // Match the addresses
        match addr[..] {
            ["chn", _, "send", _] => {
                // Set the send volume for a given input/output pair
    
                // Match the input channel
                let maybe_input = match addr[..][1].parse::<usize>() {
                    Ok(n @ 1..=N_INPUTS) => Some(n),
                    _ => None
                };
    
                // Match the output channel
                let maybe_output = match addr[..][3].parse::<usize>() {
                    Ok(n @ 1..=N_OUTPUTS) => Some(n),
                    _ => None
                };
    
                // Try to extract the Volume from the first arg
                let maybe_volume = msg.args[0].clone().float();
    
                // When both is set, do things
                match (maybe_input, maybe_output, maybe_volume) {
                    (Some(input), Some(output), Some(volume)) => {
    
                        println!("ᐅ OSC: Set Volume of Input {} at Output {} to {:.8} ({:.8} dB)", input, output, volume.max(0.0), (20.0* volume.max(0.0).log(10.0)));
                        match input {
                            1 => VOLS1[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            2 => VOLS2[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            3 => VOLS3[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            4 => VOLS4[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            5 => VOLS5[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            6 => VOLS6[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            7 => VOLS7[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            8 => VOLS8[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            9 => VOLS9[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            10 => VOLS10[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            11 => VOLS11[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            12 => VOLS12[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            13 => VOLS13[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            14 => VOLS14[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            15 => VOLS15[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            16 => VOLS16[output-1].store((volume * usize::MAX as f32) as usize, Ordering::Relaxed),
                            _ => ()
                        }
                    },
                    _ => println!("✖ OSC: Ignored message with non-existing channels: {}, {:?} {:?}", msg.addr, maybe_input, maybe_output)
                }
            },
            _ => {
                println!("✖ OSC: Ignored message with unknown address: {}", msg.addr);
            }
    
        }
    }
    
    
    
    
    
    /// Notifications for Jack
    struct Notifications;
    
    impl jack::NotificationHandler for Notifications {
        fn thread_init(&self, _: &jack::Client) {
            // println!("JACK: thread init");
        }
    
        fn shutdown(&mut self, status: jack::ClientStatus, reason: &str) {
            println!(
                "JACK: shutdown with status {:?} because \"{}\"",
                status, reason
            );
        }
    
        fn freewheel(&mut self, _: &jack::Client, _is_enabled: bool) {
            // println!(
            //     "JACK: freewheel mode is {}",
            //     if is_enabled { "on" } else { "off" }
            // );
        }
    
        fn sample_rate(&mut self, _: &jack::Client, _srate: jack::Frames) -> jack::Control {
            // println!("JACK: sample rate changed to {}", srate);
            jack::Control::Continue
        }
    
        fn client_registration(&mut self, _: &jack::Client, _name: &str, _is_reg: bool) {
            // println!(
            //     "JACK: {} client with name \"{}\"",
            //     if is_reg { "registered" } else { "unregistered" },
            //     name
            // );
        }
    
        fn port_registration(&mut self, _: &jack::Client, _port_id: jack::PortId, _is_reg: bool) {
            // println!(
            //     "JACK: {} port with id {}",
            //     if is_reg { "registered" } else { "unregistered" },
            //     port_id
            // );
        }
    
        fn port_rename(
            &mut self,
            _: &jack::Client,
            _port_id: jack::PortId,
            _old_name: &str,
            _new_name: &str,
        ) -> jack::Control {
            // println!(
            //     "JACK: port with id {} renamed from {} to {}",
            //     port_id, old_name, new_name
            // );
            jack::Control::Continue
        }
    
        fn ports_connected(
            &mut self,
            _: &jack::Client,
            _port_id_a: jack::PortId,
            _port_id_b: jack::PortId,
            _are_connected: bool,
        ) {
            // println!(
            //     "JACK: ports with id {} and {} are {}",
            //     port_id_a,
            //     port_id_b,
            //     if are_connected {
            //         "connected"
            //     } else {
            //         "disconnected"
            //     }
            // );
        }
    
        fn graph_reorder(&mut self, _: &jack::Client) -> jack::Control {
            // println!("JACK: graph reordered");
            jack::Control::Continue
        }
    
        fn xrun(&mut self, _: &jack::Client) -> jack::Control {
            // println!("JACK: xrun occurred");
            jack::Control::Continue
        }
    
        fn latency(&mut self, _: &jack::Client, _mode: jack::LatencyType) {
            // println!(
            //     "JACK: {} latency has changed",
            //     match mode {
            //         jack::LatencyType::Capture => "capture",
            //         jack::LatencyType::Playback => "playback",
            //     }
            // );
        }
    }