convert to workspace, add suil bindings crate

This commit is contained in:
🪞👃🪞 2024-07-24 13:21:11 +03:00
parent bd6f8ff9bf
commit dacce119c4
52 changed files with 1994 additions and 116 deletions

1396
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,34 +1,3 @@
[package] [workspace]
name = "tek" resolver = "2"
edition = "2021" members = [ "crates/*" ]
version = "0.1.0"
[dependencies]
jack = "0.10"
clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27"
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
backtrace = "0.3.72"
microxdg = "0.1.2"
toml = "0.8.12"
better-panic = "0.3.0"
midly = "0.5"
vst = "0.4.0"
#vst3 = "0.1.0"
livi = "0.7.4"
#atomic_enum = "0.3.0"
wavers = "1.4.3"
music-math = "0.1.1"
atomic_float = "1.0.0"
fraction = "0.15.3"
rlsf = "0.2.1"
r8brain-rs = "0.3.5"
clojure-reader = "0.1.0"
once_cell = "1.19.0"
symphonia = { version = "0.5.4", features = [ "all" ] }
dasp = { version = "0.11.0", features = [ "all" ] }
rubato = "0.15.0"

View file

@ -63,6 +63,7 @@ Ardour, and probably others. The main view consists of three sections:
* [ ] Pattern chain * [ ] Pattern chain
* [ ] Actually sync * [ ] Actually sync
* Chain: * Chain:
* [ ] Add device
* [ ] View and connect device ports in chain view * [ ] View and connect device ports in chain view
* [ ] Open LV2 GUI * [ ] Open LV2 GUI
* [ ] Pin favorite FX parameters with `*` * [ ] Pin favorite FX parameters with `*`

9
crates/suil/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "suil"
version = "0.1.0"
edition = "2021"
[dependencies]
[build-dependencies]
bindgen = "0.69.4"

18
crates/suil/build.rs Normal file
View file

@ -0,0 +1,18 @@
use std::path::PathBuf;
use std::env::var;
use bindgen::{Builder, CargoCallbacks};
fn main() {
//println!("cargo:rustc-link-lib=lv2");
//println!("cargo:rustc-link-lib=suil");
let bindings = Builder::default()
.header("wrapper.h")
.clang_arg("-Ilv2/include/lv2")
.clang_arg("-Isuil/include/suil")
.parse_callbacks(Box::new(CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

5
crates/suil/src/bound.rs Normal file
View file

@ -0,0 +1,5 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

72
crates/suil/src/lib.rs Normal file
View file

@ -0,0 +1,72 @@
use std::ffi::CString;
pub mod bound;
pub struct Host(*mut self::bound::SuilHost);
pub struct Instance(*mut self::bound::SuilInstance);
pub struct Controller(*mut self::bound::SuilController);
impl Host {
fn new () -> Self {
//let write = std::ptr::null();
//let index = std::ptr::null();
//let subscribe = std::ptr::null();
//let unsubscribe = std::ptr::null();
Self(unsafe {
bound::suil_init(&mut 0, std::ptr::null_mut(), 0);
bound::suil_host_new(None, None, None, None)
})
}
fn set_touch_func (&self) {
unimplemented!();
}
fn instance (
&self,
controller: &Controller,
container_type_uri: &str,
plugin_uri: &str,
ui_uri: &str,
ui_type_uri: &str,
ui_bundle_path: &str,
ui_binary_path: &str,
features: &[*const bound::LV2_Feature],
) -> Result<Instance, Box<dyn std::error::Error>> {
let container_type_uri = CString::new(container_type_uri)?;
let plugin_uri = CString::new(plugin_uri)?;
let ui_uri = CString::new(ui_uri)?;
let ui_type_uri = CString::new(ui_type_uri)?;
let ui_bundle_path = CString::new(ui_bundle_path)?;
let ui_binary_path = CString::new(ui_binary_path)?;
Ok(Instance(unsafe {
bound::suil_instance_new(
self.0,
*controller.0,
container_type_uri.into_raw(),
plugin_uri.into_raw(),
ui_uri.into_raw(),
ui_type_uri.into_raw(),
ui_bundle_path.into_raw(),
ui_binary_path.into_raw(),
features.as_ptr()
)
}))
}
}
impl Drop for Host {
fn drop (&mut self) -> () {
unsafe {
bound::suil_host_free(self.0)
}
}
}
impl Drop for Instance {
fn drop (&mut self) -> () {
unsafe {
bound::suil_instance_free(self.0)
}
}
}

302
crates/suil/wrapper.h Normal file
View file

@ -0,0 +1,302 @@
// Copyright 2011-2017 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
/// @file suil.h Public API for Suil
#ifndef SUIL_SUIL_H
#define SUIL_SUIL_H
#include "lv2/core/lv2.h"
#include <stdbool.h>
#include <stdint.h>
// SUIL_LIB_IMPORT and SUIL_LIB_EXPORT mark the entry points of shared libraries
#ifdef _WIN32
# define SUIL_LIB_IMPORT __declspec(dllimport)
# define SUIL_LIB_EXPORT __declspec(dllexport)
#else
# define SUIL_LIB_IMPORT __attribute__((visibility("default")))
# define SUIL_LIB_EXPORT __attribute__((visibility("default")))
#endif
// SUIL_API exposes symbols in the public API
#ifndef SUIL_API
# ifdef SUIL_STATIC
# define SUIL_API
# elif defined(SUIL_INTERNAL)
# define SUIL_API SUIL_LIB_EXPORT
# else
# define SUIL_API SUIL_LIB_IMPORT
# endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
@defgroup suil Suil C API
@{
*/
/**
@defgroup suil_callbacks Callbacks
@{
*/
/**
UI controller.
This is an opaque pointer passed by the user which is passed to the various
UI control functions (e.g. SuilPortWriteFunc). It is typically used to pass
a pointer to some controller object the host uses to communicate with
plugins.
*/
typedef void* SuilController;
/// Function to write/send a value to a port
typedef void (*SuilPortWriteFunc)( //
SuilController controller,
uint32_t port_index,
uint32_t buffer_size,
uint32_t protocol,
void const* buffer);
/// Function to return the index for a port by symbol
typedef uint32_t (*SuilPortIndexFunc)( //
SuilController controller,
const char* port_symbol);
/// Function to subscribe to notifications for a port
typedef uint32_t (*SuilPortSubscribeFunc)( //
SuilController controller,
uint32_t port_index,
uint32_t protocol,
const LV2_Feature* const* features);
/// Function to unsubscribe from notifications for a port
typedef uint32_t (*SuilPortUnsubscribeFunc)( //
SuilController controller,
uint32_t port_index,
uint32_t protocol,
const LV2_Feature* const* features);
/// Function called when a control is grabbed or released
typedef void (*SuilTouchFunc)( //
SuilController controller,
uint32_t port_index,
bool grabbed);
/**
@}
@defgroup suil_library Library
@{
*/
/// Initialization argument
typedef enum { SUIL_ARG_NONE } SuilArg;
/**
Initialize suil.
This function should be called as early as possible, before any other GUI
toolkit functions. The variable argument list is a sequence of SuilArg keys
and corresponding value pairs for passing any necessary platform-specific
information. It must be terminated with SUIL_ARG_NONE.
*/
SUIL_API
void
suil_init(int* argc, char*** argv, SuilArg key, ...);
/**
Check if suil can wrap a UI type.
@param host_type_uri The URI of the desired widget type of the host,
corresponding to the `type_uri` parameter of suil_instance_new().
@param ui_type_uri The URI of the UI widget type.
@return 0 if wrapping is unsupported, otherwise the quality of the wrapping
where 1 is the highest quality (direct native embedding with no wrapping)
and increasing values are of a progressively lower quality and/or stability.
*/
SUIL_API
unsigned
suil_ui_supported(const char* host_type_uri, const char* ui_type_uri);
/**
@}
@defgroup suil_host Host
@{
*/
/**
UI host descriptor.
This contains the various functions that a plugin UI may use to communicate
with the plugin. It is passed to suil_instance_new() to provide these
functions to the UI.
*/
typedef struct SuilHostImpl SuilHost;
/**
Create a new UI host descriptor.
@param write_func Function to send a value to a plugin port.
@param index_func Function to get the index for a port by symbol.
@param subscribe_func Function to subscribe to port updates.
@param unsubscribe_func Function to unsubscribe from port updates.
*/
SUIL_API
SuilHost*
suil_host_new(SuilPortWriteFunc write_func,
SuilPortIndexFunc index_func,
SuilPortSubscribeFunc subscribe_func,
SuilPortUnsubscribeFunc unsubscribe_func);
/**
Set a touch function for a host descriptor.
Note this function will only be called if the UI supports it.
*/
SUIL_API
void
suil_host_set_touch_func(SuilHost* host, SuilTouchFunc touch_func);
/**
Free `host`.
*/
SUIL_API
void
suil_host_free(SuilHost* host);
/**
@}
*/
/**
@defgroup suil_instance Instance
@{
*/
/// An instance of an LV2 plugin UI
typedef struct SuilInstanceImpl SuilInstance;
/// Opaque pointer to a UI handle
typedef void* SuilHandle;
/// Opaque pointer to a UI widget
typedef void* SuilWidget;
/**
Instantiate a UI for an LV2 plugin.
This funcion may load a suil module to adapt the UI to the desired toolkit.
Suil is configured at compile time to load modules from the appropriate
place, but this can be changed at run-time via the environment variable
SUIL_MODULE_DIR. This makes it possible to bundle suil with an application.
Note that some situations (Gtk in Qt, Windows in Gtk) require a parent
container to be passed as a feature with URI LV2_UI__parent
(http://lv2plug.in/ns/extensions/ui#ui) in order to work correctly. The
data must point to a single child container of the host widget set.
@param host Host descriptor.
@param controller Opaque host controller pointer.
@param container_type_uri URI of the desired host container widget type.
@param plugin_uri URI of the plugin to instantiate this UI for.
@param ui_uri URI of the specifically desired UI.
@param ui_type_uri URI of the actual UI widget type.
@param ui_bundle_path Path of the UI bundle.
@param ui_binary_path Path of the UI binary.
@param features NULL-terminated array of supported features, or NULL.
@return A new UI instance, or NULL if instantiation failed.
*/
SUIL_API
SuilInstance*
suil_instance_new(SuilHost* host,
SuilController controller,
const char* container_type_uri,
const char* plugin_uri,
const char* ui_uri,
const char* ui_type_uri,
const char* ui_bundle_path,
const char* ui_binary_path,
const LV2_Feature* const* features);
/**
Free a plugin UI instance.
The caller must ensure all references to the UI have been dropped before
calling this function (e.g. it has been removed from its parent).
*/
SUIL_API
void
suil_instance_free(SuilInstance* instance);
/**
Get the handle for a UI instance.
Returns the handle to the UI instance. The returned handle has opaque type
to insulate the Suil API from LV2 extensions, but in pactice it is currently
of type `LV2UI_Handle`. This should not normally be needed.
The returned handle is shared and must not be deleted.
*/
SUIL_API
SuilHandle
suil_instance_get_handle(SuilInstance* instance);
/**
Get the widget for a UI instance.
Returns an opaque pointer to a widget, the type of which matches the
`container_type_uri` parameter of suil_instance_new(). Note this may be a
wrapper widget created by Suil, and not necessarily the widget directly
implemented by the UI.
*/
SUIL_API
SuilWidget
suil_instance_get_widget(SuilInstance* instance);
/**
Notify the UI about a change in a plugin port.
This function can be used to notify the UI about any port change, but in the
simplest case is used to set the value of lv2:ControlPort ports. For
simplicity, this is a special case where `format` is 0, `buffer_size` is 4,
and `buffer` should point to a single float.
The `buffer` must be valid only for the duration of this call, the UI must
not keep a reference to it.
@param instance UI instance.
@param port_index Index of the port which has changed.
@param buffer_size Size of `buffer` in bytes.
@param format Format of `buffer` (mapped URI, or 0 for float).
@param buffer Change data, e.g. the new port value.
*/
SUIL_API
void
suil_instance_port_event(SuilInstance* instance,
uint32_t port_index,
uint32_t buffer_size,
uint32_t format,
const void* buffer);
/// Return a data structure defined by some LV2 extension URI
SUIL_API
const void*
suil_instance_extension_data(SuilInstance* instance, const char* uri);
/**
@}
@}
*/
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* SUIL_SUIL_H */

39
crates/tek/Cargo.toml Normal file
View file

@ -0,0 +1,39 @@
[package]
name = "tek"
edition = "2021"
version = "0.1.0"
[dependencies]
jack = "0.10"
clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27"
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
backtrace = "0.3.72"
microxdg = "0.1.2"
toml = "0.8.12"
better-panic = "0.3.0"
midly = "0.5"
vst = "0.4.0"
#vst3 = "0.1.0"
livi = "0.7.4"
#atomic_enum = "0.3.0"
wavers = "1.4.3"
music-math = "0.1.1"
atomic_float = "1.0.0"
fraction = "0.15.3"
rlsf = "0.2.1"
r8brain-rs = "0.3.5"
clojure-reader = "0.1.0"
once_cell = "1.19.0"
symphonia = { version = "0.5.4", features = [ "all" ] }
dasp = { version = "0.11.0", features = [ "all" ] }
rubato = "0.15.0"
winit = { version = "0.30.4", features = [ "x11" ] }
#winit = { path = "../winit" }
suil = { path = "../suil" }

View file

@ -1,15 +1,8 @@
//! Plugin (currently LV2 only; TODO other formats) //! Plugin (currently LV2 only; TODO other formats)
use crate::core::*; use crate::core::*;
use ::livi::{
World, submod! { lv2 }
Instance,
Plugin as LiviPlugin,
Features,
FeaturesBuilder,
Port,
event::LV2AtomSequence,
};
pub fn handle_plugin (state: &mut Plugin, event: &AppEvent) -> Usually<bool> { pub fn handle_plugin (state: &mut Plugin, event: &AppEvent) -> Usually<bool> {
handle_keymap(state, event, KEYMAP_PLUGIN) handle_keymap(state, event, KEYMAP_PLUGIN)
@ -17,29 +10,29 @@ pub fn handle_plugin (state: &mut Plugin, event: &AppEvent) -> Usually<bool> {
/// Key bindings for plugin device. /// Key bindings for plugin device.
pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin { pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin {
[Up, NONE, "cursor_up", "move cursor up", |s: &mut Plugin|{ [Up, NONE, "/plugin/cursor_up", "move cursor up", |s: &mut Plugin|{
s.selected = s.selected.saturating_sub(1); s.selected = s.selected.saturating_sub(1);
Ok(true) Ok(true)
}], }],
[Down, NONE, "cursor_down", "move cursor down", |s: &mut Plugin|{ [Down, NONE, "/plugin/cursor_down", "move cursor down", |s: &mut Plugin|{
s.selected = (s.selected + 1).min(match &s.plugin { s.selected = (s.selected + 1).min(match &s.plugin {
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
_ => unimplemented!() _ => unimplemented!()
}); });
Ok(true) Ok(true)
}], }],
[PageUp, NONE, "cursor_up", "move cursor up", |s: &mut Plugin|{ [PageUp, NONE, "/plugin/cursor_page_up", "move cursor up", |s: &mut Plugin|{
s.selected = s.selected.saturating_sub(8); s.selected = s.selected.saturating_sub(8);
Ok(true) Ok(true)
}], }],
[PageDown, NONE, "cursor_down", "move cursor down", |s: &mut Plugin|{ [PageDown, NONE, "/plugin/cursor_page_down", "move cursor down", |s: &mut Plugin|{
s.selected = (s.selected + 10).min(match &s.plugin { s.selected = (s.selected + 10).min(match &s.plugin {
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
_ => unimplemented!() _ => unimplemented!()
}); });
Ok(true) Ok(true)
}], }],
[Char(','), NONE, "decrement", "decrement value", |s: &mut Plugin|{ [Char(','), NONE, "/plugin/decrement", "decrement value", |s: &mut Plugin|{
match s.plugin.as_mut() { match s.plugin.as_mut() {
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
let index = port_list[s.selected].index; let index = port_list[s.selected].index;
@ -51,7 +44,7 @@ pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin {
} }
Ok(true) Ok(true)
}], }],
[Char('.'), NONE, "increment", "increment value", |s: &mut Plugin|{ [Char('.'), NONE, "/plugin/decrement", "increment value", |s: &mut Plugin|{
match s.plugin.as_mut() { match s.plugin.as_mut() {
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
let index = port_list[s.selected].index; let index = port_list[s.selected].index;
@ -63,6 +56,16 @@ pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin {
} }
Ok(true) Ok(true)
}], }],
[Char('g'), NONE, "/plugin/gui_toggle", "toggle plugin UI", |s: &mut Plugin|{
match s.plugin {
Some(PluginKind::LV2(ref mut plugin)) => {
plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
},
Some(_) => unreachable!(),
None => {}
}
Ok(true)
}],
}); });
/// A plugin device. /// A plugin device.
@ -203,68 +206,6 @@ fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usu
Ok(Rect { x, y, width: w, height: 1 }) Ok(Rect { x, y, width: w, height: 1 })
} }
/// A LV2 plugin.
pub struct LV2Plugin {
pub world: World,
pub instance: Instance,
pub plugin: LiviPlugin,
pub features: Arc<Features>,
pub port_list: Vec<Port>,
pub input_buffer: Vec<LV2AtomSequence>
}
impl LV2Plugin {
pub fn new (uri: &str) -> Usually<Self> {
// Get 1st plugin at URI
let world = World::with_load_bundle(&uri);
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
let features = world.build_features(features);
let mut plugin = None;
for p in world.iter_plugins() {
plugin = Some(p);
break
}
let plugin = plugin.unwrap();
let err = &format!("init {uri}");
// Instantiate
Ok(Self {
world,
instance: unsafe {
plugin
.instantiate(features.clone(), 48000.0)
.expect(&err)
},
port_list: {
let mut port_list = vec![];
for port in plugin.ports() {
port_list.push(port);
}
port_list
},
plugin,
features,
input_buffer: Vec::with_capacity(1024)
})
}
}
impl Plugin {
pub fn lv2 (name: &str, path: &str) -> Usually<JackDevice> {
let plugin = LV2Plugin::new(path)?;
Jack::new(name)?
.ports_from_lv2(&plugin.plugin)
.run(|ports|Box::new(Self {
name: name.into(),
path: Some(String::from(path)),
plugin: Some(PluginKind::LV2(plugin)),
selected: 0,
mapping: false,
ports
}))
}
}
impl ::vst::host::Host for Plugin {} impl ::vst::host::Host for Plugin {}
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind> { fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind> {
@ -277,3 +218,10 @@ fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind
}) })
} }
//pub struct LV2PluginUI {
//write: (),
//controller: (),
//widget: (),
//features: (),
//transfer: (),
//}

View file

@ -0,0 +1,123 @@
use super::*;
use ::livi::{
World,
Instance,
Plugin as LiviPlugin,
Features,
FeaturesBuilder,
Port,
event::LV2AtomSequence,
};
use ::winit::{
application::ApplicationHandler,
event::WindowEvent,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::{Window, WindowId},
platform::x11::EventLoopBuilderExtX11
};
use ::suil::{self};
impl Plugin {
pub fn lv2 (name: &str, path: &str) -> Usually<JackDevice> {
let plugin = LV2Plugin::new(path)?;
Jack::new(name)?
.ports_from_lv2(&plugin.plugin)
.run(|ports|Box::new(Self {
name: name.into(),
path: Some(String::from(path)),
plugin: Some(PluginKind::LV2(plugin)),
selected: 0,
mapping: false,
ports
}))
}
}
/// A LV2 plugin.
pub struct LV2Plugin {
pub world: World,
pub instance: Instance,
pub plugin: LiviPlugin,
pub features: Arc<Features>,
pub port_list: Vec<Port>,
pub input_buffer: Vec<LV2AtomSequence>,
pub ui_thread: Option<JoinHandle<()>>,
}
impl LV2Plugin {
pub fn new (uri: &str) -> Usually<Self> {
// Get 1st plugin at URI
let world = World::with_load_bundle(&uri);
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
let features = world.build_features(features);
let mut plugin = None;
for p in world.iter_plugins() {
plugin = Some(p);
break
}
let plugin = plugin.unwrap();
let err = &format!("init {uri}");
// Instantiate
Ok(Self {
world,
instance: unsafe {
plugin
.instantiate(features.clone(), 48000.0)
.expect(&err)
},
port_list: {
let mut port_list = vec![];
for port in plugin.ports() {
port_list.push(port);
}
port_list
},
plugin,
features,
input_buffer: Vec::with_capacity(1024),
ui_thread: None
})
}
}
pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually<JoinHandle<()>> {
Ok(spawn(move||{
let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap();
event_loop.set_control_flow(ControlFlow::Wait);
event_loop.run_app(&mut ui).unwrap()
}))
}
/// A LV2 plugin's X11 UI.
pub struct LV2PluginUI {
pub window: Option<Window>
}
impl LV2PluginUI {
pub fn new () -> Usually<Self> {
Ok(Self { window: None })
}
}
impl ApplicationHandler for LV2PluginUI {
fn resumed (&mut self, event_loop: &ActiveEventLoop) {
self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
}
fn window_event (&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => {
self.window.as_ref().unwrap().set_visible(false);
event_loop.exit();
},
WindowEvent::RedrawRequested => {
self.window.as_ref().unwrap().request_redraw();
}
_ => (),
}
}
}
fn lv2_ui_instantiate (kind: &str) {
//let host = Suil
}

View file

@ -17,7 +17,7 @@ use crate::{core::*, model::*};
/// Application entrypoint. /// Application entrypoint.
pub fn main () -> Usually<()> { pub fn main () -> Usually<()> {
run(App::from_edn(include_str!("../demos/project.edn"))? run(App::from_edn(include_str!("../../../demos/project.edn"))?
.activate(Some(|app: &Arc<RwLock<App>>|Ok({ .activate(Some(|app: &Arc<RwLock<App>>|Ok({
let (midi_in, mut midi_outs) = { let (midi_in, mut midi_outs) = {
let app = app.read().unwrap(); let app = app.read().unwrap();

View file

@ -1,5 +1,6 @@
(bpm 150) (bpm 150)
(midi-in "nanoKEY Studio.*capture.*")
(midi-in "nanoKEY Studio.*capture.*") (midi-in "nanoKEY Studio.*capture.*")
(audio-out "Built-.+:playback_FL", "Built-.+:playback_FR") (audio-out "Built-.+:playback_FL", "Built-.+:playback_FR")

View file

@ -19,5 +19,8 @@
libgcc.lib libgcc.lib
# for Panagement # for Panagement
xorg.libX11 xorg.libX11
xorg.libXcursor
xorg.libXi
libxkbcommon
]); ]);
} }