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

main.rs

Blame
  • main.rs 16.98 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};
    
    /*
    TODO:
    - [ ] Document dependecies
    - [ ] Performance improvements:
        - [ ] implement performance monitoring
        - [ ] f32.mul_add() or f32.to_bits() / f32.from_bits()
        - [ ] only calculate connected ports https://docs.rs/jack/0.7.1/jack/struct.Port.html#method.connected_count
        - [ ] Flag volume modulations as dirty
    - [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 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")
                .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")
                .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")
                .long("--name")
                .value_name("NAME")
                .about("Set the name of the jack node")
                .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");
    
        // 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()
                  .for_each(|(input_index, inport)| {
                    // Get a slice [f32; 1024] of samples for each port
                    let input_samples = inport.as_slice(ps);
    
                    // Sum each input to output buffer
                    output_buffers.iter_mut()
                                  .take(buffersize)
                                  .enumerate()
                                  .for_each(|(output_index, b)| {
                                    b.iter_mut()
                                     .take(buffersize)
                                     .enumerate()
                                     .for_each(|(sample_index, sample)| {
                                        // For each input get the corrsponding atomic value for the volume control
                                        let volume = 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
                                        };
    
                                        if volume != 0.0 {
                                            // Multiply each input sample at position `sample_index` with the
                                            // volume and sum it with the corresponding (existing) output sample 
                                            // at the same position
                                            *sample += input_samples[sample_index] * volume;
                                        }
                                     });
                                  });
                  });
    
            // 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).clone_from_slice(&output_buffers[output_index]);
                   });
            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:
    /// - `/ch/{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[..] {
            ["ch", _, "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",
                }
            );
        }
    }