Skip to content
Snippets Groups Projects
Commit 437ad343 authored by David Huss's avatar David Huss :speech_balloon:
Browse files

Initial commit

parents
Branches
No related tags found
No related merge requests found
/target
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "arr_macro"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a105bfda48707cf19220129e78fca01e9639433ffaef4163546ed8fb04120a5"
dependencies = [
"arr_macro_impl",
"proc-macro-hack",
]
[[package]]
name = "arr_macro_impl"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0609c78bd572f4edc74310dfb63a01f5609d53fa8b4dd7c4d98aef3b3e8d72d1"
dependencies = [
"proc-macro-hack",
"quote",
"syn",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "hexmatrix"
version = "0.1.0"
dependencies = [
"arr_macro",
"jack",
"libc",
"rosc",
]
[[package]]
name = "jack"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49e720259b4a3e1f33cba335ca524a99a5f2411d405b05f6405fadd69269e2db"
dependencies = [
"bitflags",
"jack-sys",
"lazy_static",
"libc",
]
[[package]]
name = "jack-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57983f0d72dfecf2b719ed39bc9cacd85194e1a94cb3f9146009eff9856fef41"
dependencies = [
"lazy_static",
"libc",
"libloading",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
[[package]]
name = "libloading"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rosc"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ca359b640ca8ef191ad8a56dd897fc46a7c733ea7b360085891cc7a70effdc"
dependencies = [
"byteorder",
]
[[package]]
name = "syn"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[package]
name = "hexmatrix"
version = "0.1.0"
authors = ["David Huss <dh@atoav.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
jack = "0.7"
libc = "0.2"
rosc = "~0.5"
arr_macro = "0.1.3"
\ No newline at end of file
# hexmixer
Hexmixer is a OSX-controlled 16x16 matrix mixer for the JACK audio system.
A matrix mixer allows you to send any input to any number of outputs. Internal computations are done at 32 bits.
## Usage
1. Start up hexmixer and patch the jack connections to taste (e.g. using Catia)
2. Send OSC messages to `127.0.0.1:7011` (as for now hardcoded)
The pattern for the OSC addresses is as follows: `/chn/1/send/2 <volume>`. This would set the send volume of input `1` to output `2`. The value `<volume>` should be a float, where a value of `0.0` equals -inf dB, `1.0` equals 0 dB and `7.0` equals +18 dB.
If you are curious how to caluclate this:
$$
dB = 20.0 \cdot \log10{(float)}
$$
## Installation
1. Clone this repository with git
2. Make sure you have rust installed then within the project directory run `cargo build --release`
3. Grab the resulting hexmixer binary from the `./target` directory
4. Run it
\ No newline at end of file
//! # 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;
// 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; 16] = arr![AtomicUsize::new(0); 16];
static VOLS2: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS3: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS4: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS5: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS6: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS7: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS8: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS9: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS10: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS11: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS12: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS13: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS14: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS15: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
static VOLS16: [AtomicUsize; 16] = arr![AtomicUsize::new(0); 16];
fn main() {
// Receive OSC-Packets at this address
let addr = SocketAddrV4::from_str("127.0.0.1:7011").unwrap();
let sock = UdpSocket::bind(addr).unwrap();
let mut buf = [0u8; rosc::decoder::MTU];
// Create new jack client (visible e.g. in Catia)
let (client, _status) =
jack::Client::new("Hexmatrix", jack::ClientOptions::NO_START_SERVER).unwrap();
// 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];
// 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()
.enumerate()
.for_each(|(output_index, b)| {
b.iter_mut()
.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
};
// 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} ({:.2} 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",
}
);
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment