initial commit

This commit is contained in:
Dark 2023-05-12 21:57:20 -04:00
commit 2ea7749bcd
Signed by: Dark
GPG key ID: 8910EE89544A1676
4 changed files with 730 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
/target
# Added by cargo
#
# already existing elements were commented out
#/target

346
Cargo.lock generated Normal file
View file

@ -0,0 +1,346 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "alsa"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44"
dependencies = [
"alsa-sys",
"bitflags",
"libc",
"nix",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "coremidi"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5"
dependencies = [
"core-foundation",
"core-foundation-sys",
"coremidi-sys",
]
[[package]]
name = "coremidi-sys"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79a6deed0c97b2d40abbab77e4c97f81d71e162600423382c277dd640019116c"
dependencies = [
"core-foundation-sys",
]
[[package]]
name = "hidapi"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7bd942bc25db46cfed7e4fc0a5ff1eca990475460621d1fa85486b1c15ef7a3"
dependencies = [
"cc",
"libc",
"pkg-config",
"winapi",
]
[[package]]
name = "js-sys"
version = "0.3.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "midir"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a456444d83e7ead06ae6a5c0a215ed70282947ff3897fb45fcb052b757284731"
dependencies = [
"alsa",
"bitflags",
"coremidi",
"js-sys",
"libc",
"wasm-bindgen",
"web-sys",
"windows",
]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "protar"
version = "0.1.0"
dependencies = [
"hidapi",
"midir",
]
[[package]]
name = "quote"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "wasm-bindgen"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb"
[[package]]
name = "web-sys"
version = "0.3.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[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 = "windows"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

10
Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "protar"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hidapi = "2.3.1"
midir = "0.9.1"

366
src/main.rs Normal file
View file

@ -0,0 +1,366 @@
use std::error::Error;
use std::time::Instant;
use hidapi::HidApi;
use hidapi::HidDevice;
use midir::os::unix::VirtualOutput;
use midir::MidiOutput;
fn main() -> Result<(), Box<dyn Error>> {
let api = HidApi::new()?;
let device = get_hid_device(&api)?;
let midiout = MidiOutput::new("Pro Guitar Handler")?;
let mut port = midiout.create_virtual("output")?;
let mut sysex = SysExHandler::new();
let mut notes = NoteHandler::new();
loop {
let mut buf = [0u8; 27];
device.read(&mut buf)?;
let state = InputState::from(buf.as_slice());
let mut events = sysex.emit(&state);
events.extend(notes.emit(&state).into_iter());
for i in events {
//println!("sending: {i:?}");
port.send(i.0.as_slice())?;
}
}
}
fn get_hid_device(api: &HidApi) -> Result<HidDevice, Box<dyn Error>> {
for device in api.device_list() {
if device.vendor_id() == 0x1bad && device.product_id() == 0x3430 {
return Ok(device.open_device(api)?);
}
}
Err("no device found".into())
}
#[derive(Default, Debug, Clone)]
struct InputState {
north: bool,
south: bool,
east: bool,
west: bool,
dpad: u8,
start: bool,
select: bool,
guide: bool,
tilt: bool,
pedal: bool,
frets: [u8; 6],
strings: [u8; 6],
}
impl From<&[u8]> for InputState {
fn from(buf: &[u8]) -> Self {
Self {
north: buf[0] & 0b1000 == 0b1000,
south: buf[0] & 0b0010 == 0b0010,
west: buf[0] & 0b0001 == 0b0001,
east: buf[0] & 0b0100 == 0b0100,
dpad: buf[2],
start: buf[1] & 0b0010 == 0b0010,
select: buf[1] & 0b0001 == 0b0001,
guide: buf[1] & 0b10000 == 0b10000,
tilt: buf[15] == 0x7f,
pedal: buf[18] == 0xff,
strings: [
buf[14] & !0x80,
buf[13] & !0x80,
buf[12] & !0x80,
buf[11] & !0x80,
buf[10] & !0x80,
buf[9] & !0x80,
],
frets: [
(buf[8] >> 2) & 0x1f,
(buf[8] << 3 & 0b11000) | (buf[7] >> 5 & 0b111),
(buf[7] & 0b11111),
(buf[6] >> 2) & 0x1f,
(buf[6] << 3 & 0b11000) | (buf[5] >> 5 & 0b111),
(buf[5] & 0b11111),
],
}
}
}
#[derive(Debug)]
struct MidiEvent(Vec<u8>);
struct SysExHandler {
last: InputState,
}
impl SysExHandler {
const FRET_OFFSETS: [u8; 6] = [0x40, 0x3B, 0x37, 0x32, 0x2D, 0x28];
fn new() -> Self {
Self {
last: InputState::default(),
}
}
fn emit(&mut self, new: &InputState) -> Vec<MidiEvent> {
let mut events = Vec::new();
// emit string events
for i in 0..6 {
if self.last.strings[i] != new.strings[i] {
events.push(MidiEvent(vec![
0xF0,
0x08,
0x40,
0x0A,
0x05,
i as u8 + 1,
new.strings[i],
0xF7,
]));
}
}
// emit fret events
for i in 0..6 {
if self.last.frets[i] != new.frets[i] {
events.push(MidiEvent(vec![
0xF0,
0x08,
0x40,
0x0A,
0x01,
i as u8 + 1,
new.frets[i] + Self::FRET_OFFSETS[i],
0xF7,
]));
}
}
// why.....
if self.last.north != new.north
|| self.last.south != new.south
|| self.last.east != new.east
|| self.last.west != new.west
|| self.last.dpad != new.dpad
|| self.last.start != new.start
|| self.last.select != new.select
|| self.last.guide != new.guide
|| self.last.tilt != new.tilt
{
let mut event = MidiEvent(vec![
0xF0, 0x08, 0x40, 0x0A, 0x08, 0x00, 0x00, 0x00, 0x01, 0xF7,
]);
event.0[7] = new.dpad;
event.0[7] |= 0b0100_0000 * new.tilt as u8;
event.0[6] |= 0b0000_0001 * new.select as u8;
event.0[6] |= 0b0000_0010 * new.start as u8;
event.0[6] |= 0b0001_0000 * new.guide as u8;
event.0[5] |= 0b0000_0001 * new.west as u8;
event.0[5] |= 0b0000_0010 * new.south as u8;
event.0[5] |= 0b0000_0100 * new.east as u8;
event.0[5] |= 0b0000_1000 * new.north as u8;
events.push(event)
}
if self.last.pedal != new.pedal {
for i in 0..6 {
events.push(MidiEvent(vec![0xB0 + i, 0x40, 0x7f * new.pedal as u8]));
}
}
self.last = new.clone();
events
}
}
struct Tuning {
name: &'static str,
tuning: [i8; 6],
}
struct NoteHandler {
last: InputState,
hopos: bool,
always_hopo: bool,
last_trigger: Instant,
hopo_threshold: f64,
transpose: i8,
tuning: usize,
}
impl NoteHandler {
const BASE_TUNING: [u8; 6] = [0x40, 0x3B, 0x37, 0x32, 0x2D, 0x28];
const TUNINGS: &[Tuning] = &[
Tuning {
name: "Standard",
tuning: [0, 0, 0, 0, 0, 0],
},
Tuning {
name: "Drop-D",
tuning: [0, 0, 0, 0, 0, -2],
},
];
fn new() -> Self {
Self {
last: InputState::default(),
hopos: false,
always_hopo: false,
last_trigger: Instant::now(),
hopo_threshold: 0.200,
transpose: 0,
tuning: 1,
}
}
fn emit(&mut self, new: &InputState) -> Vec<MidiEvent> {
let mut events = Vec::new();
// handle buttons
if !self.last.select && new.select {
self.hopos = !self.hopos;
if self.hopos {
println!("hopos enabled");
} else {
println!("hopos disabled");
}
}
if !self.last.north && new.north {
self.hopo_threshold = (self.hopo_threshold + 0.05).clamp(0.0, 1.0);
println!("hopo threshold: {:.2}", self.hopo_threshold);
}
if !self.last.south && new.south {
self.hopo_threshold = (self.hopo_threshold - 0.05).clamp(0.0, 1.0);
println!("hopo threshold: {:.2}", self.hopo_threshold);
}
if !self.last.start && new.start {
self.always_hopo = !self.always_hopo;
if self.always_hopo {
println!("always hopo enabled");
} else {
println!("always hopo disabled");
}
}
if !self.last.east && new.east {
if self.tuning == Self::TUNINGS.len() - 1 {
self.tuning = 0;
} else {
self.tuning += 1;
}
println!("tuning: {}", Self::TUNINGS[self.tuning].name);
}
if !self.last.west && new.west {
if self.tuning == 0 {
self.tuning = Self::TUNINGS.len() - 1;
} else {
self.tuning -= 1;
}
println!("tuning: {}", Self::TUNINGS[self.tuning].name);
}
if self.last.dpad == 8 && new.dpad != 8 {
match new.dpad {
0 => {
self.transpose = self.transpose.saturating_add(12);
}
4 => {
self.transpose = self.transpose.saturating_add(-12);
}
2 => {
self.transpose = self.transpose.saturating_add(1);
}
6 => {
self.transpose = self.transpose.saturating_add(-1);
}
1 | 3 | 5 | 7 => (),
_ => unreachable!(),
}
for i in 0..6 {
events.push(MidiEvent(vec![0xB0 + i, 123, 0]));
}
println!("transpose: {}", self.transpose);
}
let current_time = Instant::now();
let can_retrigger = new.frets.iter().fold(false, |acc, v| acc || *v != 0);
let mut can_hopo =
current_time.duration_since(self.last_trigger).as_secs_f64() < self.hopo_threshold;
if can_retrigger && can_hopo {
self.last_trigger = current_time;
}
// handle string and fret events
for i in 0..6 {
let last_note = (self.last.frets[i].saturating_add(Self::BASE_TUNING[i]) as i8)
.saturating_add(self.transpose)
.saturating_add(Self::TUNINGS[self.tuning].tuning[i])
.clamp(0, 127);
let current_note = (new.frets[i].saturating_add(Self::BASE_TUNING[i]) as i8)
.saturating_add(self.transpose)
.saturating_add(Self::TUNINGS[self.tuning].tuning[i])
.clamp(0, 127);
if self.last.strings[i] != new.strings[i] {
events.push(MidiEvent(vec![
0x80 + i as u8,
current_note as u8,
self.last.strings[i],
]));
events.push(MidiEvent(vec![
0x90 + i as u8,
current_note as u8,
new.strings[i],
]));
self.last_trigger = current_time;
can_hopo = true
}
if self.last.frets[i] != new.frets[i] {
events.push(MidiEvent(vec![
0x80 + i as u8,
last_note as u8,
self.last.strings[i],
]));
if new.frets[i] != 0 && self.hopos && (can_hopo || self.always_hopo) {
events.push(MidiEvent(vec![
0x90 + i as u8,
current_note as u8,
new.strings[i],
]));
}
}
}
self.last = new.clone();
events
}
}