├── core ├── src │ ├── error.rs │ ├── song │ │ ├── clips │ │ │ ├── stepper.rs │ │ │ ├── pianoroll.rs │ │ │ ├── audio.rs │ │ │ └── mod.rs │ │ ├── source │ │ │ ├── audio.rs │ │ │ ├── mod.rs │ │ │ └── notes.rs │ │ ├── io.rs │ │ ├── track │ │ │ ├── audio.rs │ │ │ ├── instrument.rs │ │ │ ├── midi.rs │ │ │ └── mod.rs │ │ ├── events.rs │ │ └── mod.rs │ ├── time │ │ ├── drift_correction │ │ │ ├── mod.rs │ │ │ ├── clock.rs │ │ │ └── ticks.rs │ │ ├── mod.rs │ │ ├── tempo.rs │ │ ├── signature.rs │ │ ├── bars.rs │ │ ├── ticks.rs │ │ └── clock.rs │ ├── midi │ │ ├── types.rs │ │ ├── mod.rs │ │ ├── io.rs │ │ ├── buffer.rs │ │ ├── messages.rs │ │ ├── encoder.rs │ │ └── decoder.rs │ ├── lib.rs │ ├── color.rs │ ├── audio │ │ ├── mod.rs │ │ └── buffer.rs │ ├── pool.rs │ ├── studio.rs │ ├── config.rs │ ├── metronome.rs │ └── transport.rs └── Cargo.toml ├── rustfmt.toml ├── app-server ├── src │ ├── audio │ │ ├── mod.rs │ │ ├── drivers │ │ │ ├── mod.rs │ │ │ └── portaudio.rs │ │ └── callback.rs │ ├── events.rs │ ├── midi │ │ ├── mod.rs │ │ ├── endpoints.rs │ │ ├── drivers │ │ │ ├── mod.rs │ │ │ ├── coremidi.rs │ │ │ └── portmidi.rs │ │ └── io │ │ │ └── mod.rs │ ├── config.rs │ ├── controller.rs │ ├── realtime.rs │ ├── main.rs │ └── server.rs ├── app.toml ├── log4rs.yaml ├── studio.toml └── Cargo.toml ├── Cargo.toml ├── app-electron ├── .babelrc ├── src │ ├── ToolBar.css │ ├── tsd.d.ts │ ├── StatusBar.css │ ├── gui.tsx │ ├── style.scss │ ├── StatusBar.tsx │ ├── App.css │ ├── Workspace.css │ ├── App.tsx │ ├── Workspace.js │ ├── main.ts │ ├── svg │ │ └── metronome.svg │ └── ToolBar.tsx ├── tslint.json ├── tsconfig.json ├── package.json └── webpack.config.js ├── .gitignore ├── LICENSE ├── .travis.yml └── README.md /core/src/error.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | -------------------------------------------------------------------------------- /core/src/song/clips/stepper.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/song/source/audio.rs: -------------------------------------------------------------------------------- 1 | pub struct AudioDataSource; 2 | -------------------------------------------------------------------------------- /core/src/song/source/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod audio; 2 | pub mod notes; 3 | -------------------------------------------------------------------------------- /app-server/src/audio/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod callback; 2 | pub mod drivers; 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "core", 4 | "app-server" 5 | ] 6 | -------------------------------------------------------------------------------- /app-server/src/events.rs: -------------------------------------------------------------------------------- 1 | pub enum Event { 2 | Command, 3 | Update, 4 | } 5 | -------------------------------------------------------------------------------- /app-electron/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/react"] 3 | } 4 | -------------------------------------------------------------------------------- /app-server/src/midi/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod drivers; 2 | pub mod endpoints; 3 | pub mod io; 4 | -------------------------------------------------------------------------------- /app-electron/src/ToolBar.css: -------------------------------------------------------------------------------- 1 | /* .toolbar-tempo { 2 | width: 45px; 3 | text-align: center; 4 | } */ -------------------------------------------------------------------------------- /app-electron/src/tsd.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content:string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /core/src/song/io.rs: -------------------------------------------------------------------------------- 1 | pub struct AudioSource; // TODO 2 | pub struct AudioSink; // TODO 3 | 4 | pub struct NotesSource; // TODO 5 | pub struct NotesSink; // TODO 6 | -------------------------------------------------------------------------------- /app-electron/src/StatusBar.css: -------------------------------------------------------------------------------- 1 | .statusbar { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: flex-start; 5 | height: 28px; 6 | /* padding: 6px; */ 7 | } -------------------------------------------------------------------------------- /core/src/time/drift_correction/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clock; 2 | pub mod ticks; 3 | 4 | pub use self::clock::ClockDriftCorrection; 5 | pub use self::ticks::TicksDriftCorrection; 6 | -------------------------------------------------------------------------------- /core/src/midi/types.rs: -------------------------------------------------------------------------------- 1 | pub type U3 = u8; 2 | pub type U4 = u8; 3 | pub type U7 = u8; 4 | pub type U14 = u16; 5 | 6 | // Timestamp in nanoseconds 7 | // pub type Timestamp = u64; 8 | -------------------------------------------------------------------------------- /app-electron/src/gui.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import { App } from './App' 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ) 10 | -------------------------------------------------------------------------------- /app-server/app.toml: -------------------------------------------------------------------------------- 1 | #[audio] 2 | # 3 | #[midi] 4 | # 5 | #[midi.buffer_pool] 6 | #pool_capacity = 4096 7 | #item_capacity = 1024 8 | # 9 | #[midi.io_vec_pool] 10 | #pool_capacity = 4096 11 | #item_capacity = 1024 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | bin/ 6 | pkg/ 7 | wasm-pack.log 8 | .idea/ 9 | *.iml 10 | .DS_Store 11 | core-neon/ 12 | app-wasm/ 13 | *.code-workspace 14 | .vscode/ 15 | *.log 16 | gtk-client/ -------------------------------------------------------------------------------- /app-electron/src/style.scss: -------------------------------------------------------------------------------- 1 | // @import '~font-awesome/css/font-awesome.css'; 2 | 3 | @import "~normalize.css"; 4 | @import "~@blueprintjs/core/lib/css/blueprint.css"; 5 | @import "~@blueprintjs/icons/lib/css/blueprint-icons.css"; 6 | @import "~@blueprintjs/core/lib/scss/variables"; 7 | -------------------------------------------------------------------------------- /core/src/midi/mod.rs: -------------------------------------------------------------------------------- 1 | //pub mod bus; 2 | pub mod decoder; 3 | pub mod encoder; 4 | pub mod messages; 5 | pub use messages::Message; 6 | pub mod buffer; 7 | pub use buffer::{new_buffer_io_vec_pool, new_buffer_pool, Buffer, BufferIo, BufferIoVec, EventIo}; 8 | pub mod io; 9 | pub mod types; 10 | -------------------------------------------------------------------------------- /app-server/src/audio/drivers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod portaudio; 2 | 3 | use failure::Fail; 4 | 5 | #[derive(Debug, Fail)] 6 | pub enum AudioError { 7 | #[fail(display = "Driver error: {}", cause)] 8 | DriverError { cause: String }, 9 | } 10 | 11 | type AudioResult = Result; 12 | -------------------------------------------------------------------------------- /app-server/log4rs.yaml: -------------------------------------------------------------------------------- 1 | appenders: 2 | stdout: 3 | kind: console 4 | encoder: 5 | pattern: "{h({d(%Y-%m-%d %H:%M:%S)} {l:5.5} [{T}] [{t}] {m})}{n}" 6 | 7 | root: 8 | level: debug 9 | appenders: 10 | - stdout 11 | 12 | # loggers: 13 | # app::backend::db: 14 | # level: info -------------------------------------------------------------------------------- /core/src/midi/io.rs: -------------------------------------------------------------------------------- 1 | use crate::midi::EventIo; 2 | 3 | pub trait MidiInput { 4 | // fn next(&self) -> Option<&EventIo>; 5 | // fn iter(&self) -> &Iterator; 6 | fn pop(&mut self) -> Option; 7 | } 8 | 9 | pub trait MidiOutput { 10 | fn push(&mut self, event: EventIo); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/song/track/audio.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::song::{ 4 | clips::{audio::AudioClip, ClipIndex}, 5 | io::{AudioSink, AudioSource}, 6 | }; 7 | 8 | pub struct AudioTrack { 9 | source: AudioSource, 10 | sink: AudioSink, 11 | 12 | clips: BTreeMap, 13 | } 14 | -------------------------------------------------------------------------------- /app-electron/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-standard", 3 | "rules": { 4 | "indent": [true, "spaces"], 5 | "ter-indent": [true, 2], 6 | "space-before-function-paren": ["error", { 7 | "anonymous": "always", 8 | "named": "never", 9 | "asyncArrow": "ignore" 10 | }] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/src/song/track/instrument.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::song::{ 4 | clips::{pianoroll::NotesClip, ClipIndex}, 5 | io::{AudioSink, NotesSource}, 6 | }; 7 | 8 | pub struct InstrumentTrack { 9 | source: NotesSource, 10 | sink: AudioSink, 11 | 12 | clips: BTreeMap, 13 | } 14 | -------------------------------------------------------------------------------- /core/src/song/clips/pianoroll.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use crate::song::{ 4 | clips::{Clip, ClipId}, 5 | source::notes::NotesSource, 6 | }; 7 | 8 | pub struct Notes { 9 | source: Arc>, 10 | id: ClipId, 11 | } 12 | 13 | pub struct NotesClip { 14 | clip: Clip, 15 | 16 | notes: Notes, 17 | } 18 | -------------------------------------------------------------------------------- /core/src/song/track/midi.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::song::{ 4 | clips::{pianoroll::NotesClip, ClipIndex}, 5 | io::{NotesSink, NotesSource}, 6 | }; 7 | 8 | pub struct MidiTrack { 9 | source: NotesSource, 10 | sink: NotesSink, 11 | 12 | clips: BTreeMap, 13 | } 14 | 15 | impl MidiTrack {} 16 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hero-studio-core" 3 | version = "0.1.0" 4 | authors = ["Christian Zen"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | log = "0.4.6" 9 | log4rs = "0.8.1" 10 | 11 | serde = "^1.0" 12 | serde_derive = "^1.0" 13 | 14 | toml = "0.4.10" 15 | 16 | failure = "0.1.3" 17 | 18 | uuid = { version = "0.7", features = ["v4"] } -------------------------------------------------------------------------------- /core/src/song/clips/audio.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use crate::song::{ 4 | clips::{Clip, ClipId}, 5 | source::audio::AudioDataSource, 6 | }; 7 | 8 | pub struct AudioData { 9 | source: Arc>, 10 | id: ClipId, 11 | } 12 | 13 | pub struct AudioClip { 14 | clip: Clip, 15 | 16 | data: AudioData, 17 | } 18 | -------------------------------------------------------------------------------- /core/src/time/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bars; 2 | pub mod clock; 3 | pub mod drift_correction; 4 | pub mod signature; 5 | pub mod tempo; 6 | pub mod ticks; 7 | 8 | pub use self::bars::BarsTime; 9 | pub use self::clock::ClockTime; 10 | pub use self::signature::Signature; 11 | pub use self::tempo::Tempo; 12 | pub use self::ticks::TicksTime; 13 | 14 | pub type SampleRate = u32; 15 | -------------------------------------------------------------------------------- /app-electron/src/StatusBar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import './StatusBar.css' 4 | 5 | export interface StatusBarProps { 6 | } 7 | 8 | export default class StatusBar extends React.Component { 9 | render() { 10 | return ( 11 |
12 | Status bar 13 |
14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // FIXME Remove when the code gets more stable 2 | #![allow(unused_imports)] 3 | #![allow(dead_code)] 4 | #![allow(clippy::trivially_copy_pass_by_ref)] 5 | 6 | pub mod audio; 7 | pub mod color; 8 | pub mod config; 9 | pub mod metronome; 10 | pub mod midi; 11 | pub mod pool; 12 | pub mod song; 13 | pub mod studio; 14 | pub mod time; 15 | pub mod transport; 16 | -------------------------------------------------------------------------------- /core/src/song/clips/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod audio; 2 | pub mod pianoroll; 3 | pub mod stepper; 4 | 5 | use crate::time::{Signature, TicksTime}; 6 | 7 | pub type ClipId = u64; 8 | 9 | pub type ClipIndex = usize; 10 | 11 | pub struct Clip { 12 | pub uuid: ClipId, 13 | pub name: String, 14 | pub signature: Signature, 15 | pub start: TicksTime, 16 | pub length: TicksTime, 17 | } 18 | -------------------------------------------------------------------------------- /app-server/studio.toml: -------------------------------------------------------------------------------- 1 | [audio] 2 | # input_port = { name = "xyz" } 3 | # output_port = "none" 4 | sample_rate = 44100 5 | frames = 64 6 | 7 | [midi] 8 | 9 | 10 | [[midi.output_virtual_ports]] 11 | name = "metronome" 12 | sync_delay_ms = 0 13 | 14 | [metronome] 15 | enabled = true 16 | # port = { name = "IAC Driver Bus 1" } 17 | port = "default" 18 | # bar_note = { key = 72 } 19 | # beat_note = { channel = 0, key = 65, velocity = 100 } 20 | -------------------------------------------------------------------------------- /app-electron/src/App.css: -------------------------------------------------------------------------------- 1 | html, body, #root { 2 | height: 100%; 3 | margin: 0px; 4 | padding: 0; 5 | } 6 | 7 | .r { 8 | border: 1px solid white; 9 | border-radius: 6px; 10 | background-color: #666; 11 | margin: 2px; 12 | height: 100%; 13 | width: 100%; 14 | } 15 | 16 | body { 17 | font-family: 'Roboto', sans-serif; 18 | } 19 | 20 | .container { 21 | display: flex; 22 | flex-flow: column wrap; 23 | min-height: 100%; 24 | color: white; 25 | } 26 | -------------------------------------------------------------------------------- /app-electron/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es2015", 5 | "moduleResolution": "node", 6 | "pretty": true, 7 | "newLine": "LF", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "sourceMap": true, 13 | "skipLibCheck": true, 14 | "allowJs": true, 15 | "jsx": "preserve" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app-electron/src/Workspace.css: -------------------------------------------------------------------------------- 1 | .workspace { 2 | flex: 1; 3 | /* padding: 6px; */ 4 | position: relative; 5 | overflow: hidden; 6 | } 7 | 8 | .vsplitter > .layout-splitter { 9 | width: 6px !important; 10 | background-color: #333 !important; 11 | } 12 | 13 | .vsplitter > .layout-splitter:hover { 14 | background-color: #666 !important; 15 | border-radius: 6px; 16 | } 17 | 18 | .hsplitter > .layout-splitter { 19 | height: 6px !important; 20 | background-color: #333 !important; 21 | } 22 | 23 | .hsplitter > .layout-splitter:hover { 24 | background-color: #666 !important; 25 | border-radius: 6px; 26 | } 27 | -------------------------------------------------------------------------------- /app-electron/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { FocusStyleManager } from "@blueprintjs/core"; 4 | 5 | import ToolBar from './ToolBar' 6 | import Workspace from './Workspace' 7 | import StatusBar from './StatusBar' 8 | 9 | FocusStyleManager.onlyShowFocusOnTabs(); 10 | 11 | import './style.scss' 12 | import './App.css' 13 | 14 | export interface AppProps { 15 | } 16 | 17 | export class App extends React.Component { 18 | render() { 19 | return ( 20 |
21 | 22 | 23 | 24 |
25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/color.rs: -------------------------------------------------------------------------------- 1 | pub struct Color(String); 2 | 3 | impl Color { 4 | pub fn new(value: String) -> Color { 5 | Color(value) 6 | } 7 | 8 | pub fn from_rgb(r: u8, g: u8, b: u8) -> Color { 9 | Color(format!("rgb({},{},{})", r, g, b)) 10 | } 11 | 12 | pub fn get_value(&self) -> &str { 13 | self.0.as_str() 14 | } 15 | } 16 | 17 | #[cfg(test)] 18 | mod test { 19 | 20 | use super::Color; 21 | 22 | #[test] 23 | pub fn new() { 24 | let color = Color::new("red".into()); 25 | assert_eq!(color.get_value(), "red"); 26 | } 27 | 28 | #[test] 29 | pub fn from_rgb() { 30 | let color = Color::from_rgb(10, 20, 30); 31 | assert_eq!(color.get_value(), "rgb(10,20,30)"); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app-server" 3 | version = "0.1.0" 4 | authors = ["Christian Zen"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | hero-studio-core = { path = "../core" } 9 | 10 | log = "0.4.6" 11 | log4rs = "0.8.1" 12 | 13 | serde = "^1.0" 14 | serde_derive = "^1.0" 15 | 16 | toml = "0.4.10" 17 | 18 | failure = "0.1.3" 19 | failure_derive = "*" 20 | 21 | # thread-priority = "0.1.0" 22 | audio_thread_priority = "0.3.0" 23 | 24 | crossbeam-channel = "0.3.6" 25 | 26 | portmidi = "^0.2" 27 | portaudio = "^0.7" 28 | 29 | [dependencies.websocket] 30 | version = "0.22.2" 31 | default-features = false 32 | features = ["sync-ssl"] 33 | 34 | [target.'cfg(target_os = "macos")'.dependencies] 35 | coremidi = "0.3.1" -------------------------------------------------------------------------------- /core/src/time/tempo.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub struct Tempo(u16); 3 | 4 | impl Tempo { 5 | pub fn new(value: u16) -> Tempo { 6 | Tempo(value) 7 | } 8 | 9 | pub fn get_value(&self) -> u16 { 10 | self.0 11 | } 12 | } 13 | 14 | impl From for f64 { 15 | fn from(item: Tempo) -> Self { 16 | f64::from(item.0) 17 | } 18 | } 19 | 20 | impl From for u64 { 21 | fn from(item: Tempo) -> Self { 22 | u64::from(item.0) 23 | } 24 | } 25 | 26 | impl From for u16 { 27 | fn from(item: Tempo) -> Self { 28 | item.0 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod test { 34 | 35 | use super::Tempo; 36 | 37 | #[test] 38 | pub fn tempo_new() { 39 | let tempo = Tempo::new(120); 40 | assert_eq!(tempo.get_value(), 120); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app-electron/src/Workspace.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | // import SplitterLayout from 'react-splitter-layout' 3 | 4 | // import { ReactComponent as MetronomeIcon } from './svg/metronome.svg' 5 | 6 | import './Workspace.css' 7 | 8 | export default class Workspace extends React.Component { 9 | render () { 10 | return ( 11 |
12 | {/* */} 13 | {/* 14 |
Left
15 | 16 |
Top
17 |
Bottom
18 |
19 |
*/} 20 |
21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/audio/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod buffer; 2 | pub use buffer::{new_buffer_pool, Buffer}; 3 | 4 | use crate::time::ClockTime; 5 | 6 | pub struct AudioInput<'a> { 7 | pub time: ClockTime, 8 | pub channels: usize, 9 | pub buffer: &'a [f32], 10 | } 11 | 12 | impl<'a> AudioInput<'a> { 13 | pub fn new(time: ClockTime, channels: usize, buffer: &'a [f32]) -> Self { 14 | AudioInput { 15 | time, 16 | channels, 17 | buffer, 18 | } 19 | } 20 | } 21 | 22 | pub struct AudioOutput<'a> { 23 | pub time: ClockTime, 24 | pub channels: usize, 25 | pub buffer: &'a mut [f32], 26 | } 27 | 28 | impl<'a> AudioOutput<'a> { 29 | pub fn new(time: ClockTime, channels: usize, buffer: &'a mut [f32]) -> Self { 30 | AudioOutput { 31 | time, 32 | channels, 33 | buffer, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/time/signature.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub struct Signature { 3 | num_beats: u8, // numerator 4 | note_value: u8, // denominator 5 | } 6 | 7 | impl Signature { 8 | pub fn new(num_beats: u8, note_value: u8) -> Signature { 9 | assert!(note_value <= 16); 10 | Signature { 11 | num_beats, 12 | note_value, 13 | } 14 | } 15 | 16 | pub fn get_num_beats(&self) -> u8 { 17 | self.num_beats 18 | } 19 | 20 | pub fn get_note_value(&self) -> u8 { 21 | self.note_value 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod test { 27 | 28 | use super::Signature; 29 | 30 | #[test] 31 | pub fn signature_new() { 32 | let signature = Signature::new(3, 4); 33 | assert_eq!(signature.get_num_beats(), 3); 34 | assert_eq!(signature.get_note_value(), 4); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app-electron/src/main.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron' 2 | 3 | // require('electron-reload')(__dirname) 4 | 5 | declare var __dirname: string 6 | 7 | let mainWindow: Electron.BrowserWindow 8 | 9 | function onReady() { 10 | mainWindow = new BrowserWindow({ 11 | width: 800, 12 | height: 600, 13 | // titleBarStyle: 'hiddenInset', 14 | // title: 'Hero Studio', 15 | backgroundColor: '#333333', 16 | // Don't show the window until it's ready, this prevents any white flickering 17 | // show: false, 18 | webPreferences: { 19 | nodeIntegration: false, 20 | contextIsolation: true 21 | } 22 | }) 23 | 24 | const fileName = `file://${__dirname}/index.html` 25 | console.log(`fileName=${fileName}`) 26 | 27 | mainWindow.loadURL(fileName) 28 | mainWindow.on('close', () => app.quit()) 29 | } 30 | 31 | app.on('ready', () => onReady()) 32 | app.on('window-all-closed', () => app.quit()) 33 | console.log(`Electron Version ${app.getVersion()}`) 34 | -------------------------------------------------------------------------------- /core/src/pool.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | pub type Allocator = Fn() -> Box; 4 | pub type Reset = Fn(&mut T); 5 | 6 | #[allow(clippy::vec_box)] 7 | pub struct Pool { 8 | allocator: Box>, 9 | reset: Box>, 10 | items: Vec>, 11 | } 12 | 13 | impl Pool { 14 | pub fn new(capacity: usize, allocator: Box>, reset: Box>) -> Pool { 15 | let mut items = Vec::>::with_capacity(capacity); 16 | for _ in 0..capacity { 17 | items.push((allocator)()); 18 | } 19 | 20 | Pool { 21 | allocator, 22 | reset, 23 | items, 24 | } 25 | } 26 | 27 | pub fn get(&mut self) -> Option> { 28 | self.items.pop() 29 | } 30 | 31 | pub fn get_or_alloc(&mut self) -> Box { 32 | let alloc = &*self.allocator; 33 | self.items.pop().unwrap_or_else(alloc) 34 | } 35 | 36 | pub fn release(&mut self, mut item: Box) { 37 | (self.reset)(&mut item); 38 | self.items.push(item); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/song/events.rs: -------------------------------------------------------------------------------- 1 | 2 | enum Event { 3 | NoteStart { 4 | key: u8, 5 | velocity: f64, 6 | end: TicksTime 7 | }, 8 | 9 | NoteEnd { 10 | key: u8, 11 | velocity: f64, 12 | start: TicksTime 13 | } 14 | } 15 | 16 | pub struct NotesClip { 17 | // TODO BTreeMap> 18 | events: BTreeMap 19 | } 20 | 21 | impl NotesClip { 22 | pub fn new() -> NotesClip { 23 | NotesClip { 24 | events: BTreeMap::new() 25 | } 26 | } 27 | 28 | pub fn add_note(&mut self, note: Note) -> &mut Self { 29 | let note_end = note.start + note.length; 30 | 31 | let note_start_event = Event::NoteStart { 32 | key: note.key, 33 | velocity: note.velocity, 34 | end: note_end 35 | }; 36 | self.events.insert(note.start, note_start_event); 37 | 38 | let note_end_event = Event::NoteEnd { 39 | key: note.key, 40 | velocity: note.velocity, 41 | start: note.start 42 | }; 43 | self.events.insert(note_end, note_end_event); 44 | 45 | self 46 | } 47 | 48 | // delete_note 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christian Pérez-Llamas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app-electron/src/svg/metronome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /core/src/song/track/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod audio; 2 | pub mod instrument; 3 | pub mod midi; 4 | 5 | use crate::color::Color; 6 | 7 | use crate::song::{ 8 | clips::Clip, 9 | track::{audio::AudioTrack, instrument::InstrumentTrack, midi::MidiTrack}, 10 | }; 11 | 12 | use crate::time::TicksTime; 13 | 14 | pub enum TrackMedia { 15 | Midi(MidiTrack), 16 | Audio(AudioTrack), 17 | Instrument(InstrumentTrack), 18 | } 19 | 20 | pub struct Track { 21 | pub name: String, 22 | pub color: Color, 23 | pub mute: bool, 24 | pub solo: bool, 25 | pub rec: bool, 26 | 27 | pub volume: f64, 28 | pub pan: f64, 29 | 30 | pub media: TrackMedia, 31 | 32 | clips: Vec, 33 | } 34 | 35 | impl Track { 36 | pub fn new(name: T, color: Color, media: TrackMedia) -> Track 37 | where 38 | T: Into, 39 | { 40 | Track { 41 | name: name.into(), 42 | color, 43 | mute: false, 44 | solo: false, 45 | rec: false, 46 | volume: 1.0, 47 | pan: 0.0, 48 | media, 49 | clips: Vec::new(), 50 | } 51 | } 52 | 53 | pub fn clips_in_range(&self, start: TicksTime, until: TicksTime) -> impl Iterator { 54 | // TODO use an Interval Tree (http://www.davismol.net/2016/02/07/data-structures-augmented-interval-tree-to-search-for-interval-overlapping/) 55 | self.clips.iter().filter(move |clip| { 56 | let end = clip.start + clip.length; 57 | clip.start < until && end >= start 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /core/src/audio/buffer.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use crate::pool::Pool; 4 | 5 | pub const MAX_AUDIO_BUFFER_SIZE: usize = 2 * 4 * 1024; 6 | 7 | // pub type BufferData = [f32; MAX_AUDIO_BUFFER_SIZE]; 8 | pub type BufferData = Vec; 9 | 10 | pub struct Buffer(BufferData); 11 | 12 | impl Default for Buffer { 13 | fn default() -> Self { 14 | let mut data = Vec::new(); 15 | unsafe { data.set_len(MAX_AUDIO_BUFFER_SIZE) } 16 | Buffer(data) 17 | } 18 | } 19 | 20 | impl Buffer { 21 | pub fn new() -> Self { 22 | Buffer::default() 23 | } 24 | 25 | pub fn with_capacity(capacity: usize) -> Buffer { 26 | let mut data = Vec::with_capacity(capacity); 27 | unsafe { data.set_len(capacity) } 28 | Buffer(data) 29 | } 30 | 31 | pub fn slice(&self, size: usize) -> &[f32] { 32 | &self.0[0..size] 33 | } 34 | 35 | pub fn slice_mut(&mut self, size: usize) -> &mut [f32] { 36 | &mut self.0[0..size] 37 | } 38 | } 39 | 40 | impl Deref for Buffer { 41 | type Target = BufferData; 42 | fn deref(&self) -> &Self::Target { 43 | &self.0 44 | } 45 | } 46 | 47 | impl DerefMut for Buffer { 48 | fn deref_mut(&mut self) -> &mut BufferData { 49 | &mut self.0 50 | } 51 | } 52 | 53 | pub fn new_buffer_pool(pool_capacity: usize, buffer_capacity: usize) -> Pool { 54 | let allocator = Box::new(move || Box::new(Buffer::with_capacity(buffer_capacity))); 55 | let reset = Box::new(|_item: &mut Buffer| {}); 56 | Pool::new(pool_capacity, allocator, reset) 57 | } 58 | -------------------------------------------------------------------------------- /core/src/song/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clips; 2 | pub mod io; 3 | pub mod source; 4 | pub mod track; 5 | 6 | use crate::config::Config; 7 | use crate::metronome::Metronome; 8 | use crate::time::{BarsTime, SampleRate, Signature, TicksTime}; 9 | use crate::transport::{Segment, Transport}; 10 | 11 | use self::track::{Track, TrackMedia}; 12 | 13 | pub struct Song { 14 | name: String, 15 | tracks: Vec, 16 | } 17 | 18 | impl Song { 19 | pub fn new(name: T, _config: &Config) -> Song 20 | where 21 | T: Into, 22 | { 23 | Song { 24 | name: name.into(), 25 | tracks: Vec::new(), 26 | } 27 | } 28 | 29 | pub fn set_name(&mut self, name: T) 30 | where 31 | T: Into, 32 | { 33 | self.name = name.into(); 34 | } 35 | 36 | pub fn get_name(&self) -> &str { 37 | self.name.as_str() 38 | } 39 | 40 | pub fn process_segment(&mut self, _segment: &Segment) { 41 | // println!( 42 | // "=> Segment T [{:06?}, {:06?}) <{:06?}> C [{:010?}, {:010?}) <{:010?}> @ PT {:06?} PC {:010?}", 43 | // u64::from(segment.start_ticks), 44 | // u64::from(segment.end_ticks), 45 | // u64::from(segment.segment_ticks), 46 | // segment.start_time.units(), 47 | // segment.end_time.units(), 48 | // segment.segment_time.units(), 49 | // u64::from(segment.play_ticks), 50 | // segment.play_time.units() 51 | // ); 52 | 53 | for track in self.tracks.iter_mut() { 54 | // let clips = track.clips_in_range(start_ticks, end_ticks); 55 | match &track.media { 56 | TrackMedia::Midi(_midi_track) => { 57 | // prepare buffer for midi_track.sink 58 | } 59 | TrackMedia::Audio(_audio_track) => {} 60 | TrackMedia::Instrument(_instrument_track) => {} 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | os: 9 | - osx 10 | - linux 11 | 12 | addons: 13 | apt: 14 | packages: 15 | - libportaudio2 16 | - libportmidi-dev 17 | homebrew: 18 | update: true 19 | packages: 20 | - portaudio 21 | - portmidi 22 | 23 | env: 24 | global: 25 | - PROJECT_NAME: hero-studio 26 | - RUST_BACKTRACE: full 27 | 28 | matrix: 29 | allow_failures: 30 | - rust: nightly 31 | fast_finish: true 32 | 33 | before_script: 34 | - | 35 | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then 36 | rustup component add clippy --toolchain=nightly || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy 37 | else 38 | rustup component add clippy 39 | fi 40 | 41 | script: 42 | - cargo clippy 43 | - cargo build --all 44 | - cargo test --verbose --all 45 | 46 | before_deploy: 47 | - mv target/release/$PROJECT_NAME "target/release/$PROJECT_NAME-$TRAVIS_TAG-$TRAVIS_OS_NAME" 48 | 49 | deploy: 50 | - provider: releases 51 | skip_cleanup: true 52 | on: 53 | tags: true 54 | condition: "$TRAVIS_RUST_VERSION = stable" 55 | branch: master 56 | file: 57 | - target/release/$PROJECT_NAME-* 58 | api_key: 59 | secure: "qPh3NPSSEXjeXoWp6UqFH4RzUOgCUMH2gQHTwh+mOzCWO/PW5L0KbrRznNtGpqKbiUAiZ6FLNp/4w8ThbYh1uom9SBD23WXG92B/8O4bBumt0KPMvRftkMy5zcjHl4ZCEs8ob644pHmJ7okp0Z88LKWMgtr3Uxd1ubrpBFJrN4Q2a9Sl1NOax0qT0k9xhvmi6ANDDnLXUS40zaZowXYZMGjzY71kCt4j+jJYsWnTbZgm4Q1C5ZEEfP3wnbizYeqeF33xup/LNxm0CQStOrhdEMjGt4v8nap6Dh1suMED1PQfQzOnS+J9Sb4B24exsDlP64kYLPdmDWl5ynwqMQCGIdXvp+9tDJwaob3wp8HD9vsoRf2AbLGHyQs3SnxsbL4svjEfj65LmuEi5kEg3p68kleadE+xITrql4HmIo4oAGMRJ+CxCASk8k1Mw2akX9jCb2/AD3VX5ElmlTPoGvYsCtWxgebcm/vhGwRxiMnDyva7bYW8osjPfqYYfe7fC9s2nrzK5ylM0aad93cLWl0IQHDm2sMXuwH9WfGEUx5Zb9BdpXlYfSWKTt3wST9Rts3S7zB+ZLiLTgGqvjWE/sf3LwYgdgxUqzTORWUJ5Cw+qT88NnxvsjmJkOqMJBrDb+JQPixbZO8Q498EjlkGgtG3+b8BSIi7/SzHMtioJuGtsGo=" 60 | 61 | notifications: 62 | email: false 63 | -------------------------------------------------------------------------------- /app-server/src/config.rs: -------------------------------------------------------------------------------- 1 | use failure; 2 | use failure::Error; 3 | 4 | use serde_derive::Deserialize; 5 | 6 | use std::fs::File; 7 | use std::io::Read; 8 | 9 | #[serde(default)] 10 | #[derive(Deserialize, Debug, Clone)] 11 | pub struct Config { 12 | pub midi: Midi, 13 | pub websocket: WebSocket, 14 | } 15 | 16 | impl Default for Config { 17 | fn default() -> Config { 18 | Config { 19 | midi: Midi::default(), 20 | websocket: WebSocket::default(), 21 | } 22 | } 23 | } 24 | 25 | impl Config { 26 | pub fn from_file<'a, T>(path: T) -> Result 27 | where 28 | T: Into<&'a str>, 29 | { 30 | let mut content = String::new(); 31 | let path_str = path.into(); 32 | let mut file = File::open(path_str)?; 33 | file.read_to_string(&mut content)?; 34 | let config: Config = toml::from_str(&content)?; 35 | Ok(config) 36 | } 37 | 38 | #[allow(dead_code)] 39 | pub fn from_str<'a, T>(content: T) -> Result 40 | where 41 | T: Into<&'a str>, 42 | { 43 | let config: Config = toml::from_str(content.into())?; 44 | Ok(config) 45 | } 46 | } 47 | 48 | #[derive(Deserialize, Debug, Clone)] 49 | pub struct PoolWithItemCapacity { 50 | pub pool_capacity: usize, 51 | pub item_capacity: usize, 52 | } 53 | 54 | #[serde(default)] 55 | #[derive(Deserialize, Debug, Clone)] 56 | pub struct Midi { 57 | pub buffer_pool: PoolWithItemCapacity, 58 | pub io_vec_pool: PoolWithItemCapacity, 59 | } 60 | 61 | impl Default for Midi { 62 | fn default() -> Midi { 63 | Midi { 64 | buffer_pool: PoolWithItemCapacity { 65 | pool_capacity: 256, 66 | item_capacity: 1024, 67 | }, 68 | io_vec_pool: PoolWithItemCapacity { 69 | pool_capacity: 128, 70 | item_capacity: 1024, 71 | }, 72 | } 73 | } 74 | } 75 | 76 | #[serde(default)] 77 | #[derive(Deserialize, Debug, Clone)] 78 | pub struct WebSocket { 79 | pub port: u16, 80 | } 81 | 82 | impl Default for WebSocket { 83 | fn default() -> WebSocket { 84 | WebSocket { port: 3001 } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app-server/src/midi/endpoints.rs: -------------------------------------------------------------------------------- 1 | use crate::midi::drivers::MidiEndpoint; 2 | use std::collections::{HashMap, HashSet}; 3 | 4 | pub type EndpointId = usize; 5 | 6 | pub struct Endpoints 7 | where 8 | T: MidiEndpoint + ?Sized, 9 | { 10 | next_id: EndpointId, 11 | ids_by_name: HashMap, 12 | endpoints_by_id: HashMap>, 13 | } 14 | 15 | impl Endpoints 16 | where 17 | T: MidiEndpoint + ?Sized, 18 | { 19 | pub fn new() -> Self { 20 | Endpoints { 21 | next_id: 0, 22 | ids_by_name: HashMap::::new(), 23 | endpoints_by_id: HashMap::>::new(), 24 | } 25 | } 26 | 27 | pub fn next_id(&self) -> EndpointId { 28 | self.next_id 29 | } 30 | 31 | pub fn ids(&self) -> impl Iterator { 32 | self.endpoints_by_id.keys() 33 | } 34 | 35 | pub fn iter_mut(&mut self) -> impl Iterator> { 36 | self.endpoints_by_id.values_mut() 37 | } 38 | 39 | pub fn get_id_from_name(&self, name: &str) -> Option { 40 | self.ids_by_name.get(name).cloned() 41 | } 42 | 43 | pub fn add(&mut self, name: U, endpoint: Box) -> EndpointId 44 | where 45 | U: Into, 46 | { 47 | let id = self.next_id; 48 | self.next_id += 1; 49 | self.ids_by_name.insert(name.into(), id); 50 | self.endpoints_by_id.insert(id, endpoint); 51 | id 52 | } 53 | 54 | pub fn remove(&mut self, ids: HashSet, handler: F) 55 | where 56 | F: Fn(&str, EndpointId), 57 | { 58 | for id in ids.iter() { 59 | let maybe_name = self 60 | .endpoints_by_id 61 | .get(id) 62 | .map(|port| port.name().to_string()); 63 | maybe_name.into_iter().for_each(|name| { 64 | self.ids_by_name.remove(&name); 65 | self.endpoints_by_id.remove(id); 66 | (handler)(&name, *id) 67 | }); 68 | } 69 | } 70 | 71 | pub fn get_mut(&mut self, id: EndpointId) -> Option<&mut T> { 72 | self.endpoints_by_id.get_mut(&id).map(Box::as_mut) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/src/midi/buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::midi::messages::Message; 2 | use crate::pool::Pool; 3 | use crate::time::ClockTime; 4 | 5 | #[derive(Debug)] 6 | pub struct EventIo { 7 | pub timestamp: ClockTime, 8 | pub endpoint: Endpoint, 9 | pub message: Message, 10 | } 11 | 12 | impl EventIo { 13 | pub fn new(timestamp: ClockTime, endpoint: Endpoint, message: Message) -> Self { 14 | EventIo { 15 | timestamp, 16 | endpoint, 17 | message, 18 | } 19 | } 20 | } 21 | 22 | pub struct Event { 23 | pub timestamp: ClockTime, 24 | pub message: Message, 25 | } 26 | 27 | pub struct Buffer { 28 | events: Vec, 29 | } 30 | 31 | impl Default for Buffer { 32 | fn default() -> Self { 33 | Buffer { events: Vec::new() } 34 | } 35 | } 36 | 37 | impl Buffer { 38 | pub fn new() -> Buffer { 39 | Buffer::default() 40 | } 41 | 42 | pub fn with_capacity(capacity: usize) -> Buffer { 43 | Buffer { 44 | events: Vec::with_capacity(capacity), 45 | } 46 | } 47 | 48 | pub fn iter(&self) -> std::slice::Iter<'_, Event> { 49 | self.events.iter() 50 | } 51 | 52 | pub fn reset(&mut self) -> &mut Self { 53 | self.events.clear(); 54 | self 55 | } 56 | 57 | pub fn push(&mut self, timestamp: ClockTime, message: Message) { 58 | self.events.push(Event { timestamp, message }); 59 | } 60 | } 61 | 62 | pub fn new_buffer_pool(pool_capacity: usize, buffer_capacity: usize) -> Pool { 63 | let allocator = Box::new(move || Box::new(Buffer::with_capacity(buffer_capacity))); 64 | let reset = Box::new(|item: &mut Buffer| { 65 | item.reset(); 66 | }); 67 | Pool::new(pool_capacity, allocator, reset) 68 | } 69 | 70 | #[derive(Debug, Clone, Copy)] 71 | pub enum Endpoint { 72 | None, 73 | Default, 74 | All, 75 | Id(usize), 76 | } 77 | 78 | pub struct BufferIo { 79 | pub endpoint: Endpoint, 80 | pub buffer: Option>, 81 | } 82 | 83 | pub type BufferIoVec = Vec; 84 | 85 | pub fn new_buffer_io_vec_pool(pool_capacity: usize, vec_capacity: usize) -> Pool { 86 | let allocator = Box::new(move || Box::new(Vec::with_capacity(vec_capacity))); 87 | let reset = Box::new(std::vec::Vec::clear); 88 | Pool::new(pool_capacity, allocator, reset) 89 | } 90 | -------------------------------------------------------------------------------- /app-electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hero-studio", 3 | "version": "0.1.0", 4 | "description": "Hero Studio", 5 | "main": "dist/main.js", 6 | "repository": "https://github.com/chris-zen/hero-studio", 7 | "author": "Christian Perez-Llamas", 8 | "license": "MIT", 9 | "scripts": { 10 | "clean": "rm -rf dist coverage", 11 | "build": "webpack --config webpack.config.js --mode development", 12 | "build-watch": "webpack --config webpack.config.js --mode development --watch", 13 | "start-electron": "electron .", 14 | "start": "npm-run-all --parallel build-watch start-electron" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.2.2", 18 | "@babel/preset-env": "^7.2.3", 19 | "@babel/preset-react": "^7.0.0", 20 | "@beyonk/google-fonts-webpack-plugin": "^1.2.1", 21 | "@types/electron": "^1.6.10", 22 | "@types/node": "^10.12.18", 23 | "@types/react": "^16.7.20", 24 | "@types/react-dom": "^16.0.11", 25 | "babel": "^6.23.0", 26 | "babel-loader": "^8.0.5", 27 | "copy-webpack-plugin": "^4.6.0", 28 | "csp-html-webpack-plugin": "^2.5.0", 29 | "css-loader": "^2.1.0", 30 | "electron": "^9.1.0", 31 | "electron-reload": "^1.4.0", 32 | "file-loader": "^3.0.1", 33 | "html-webpack-include-assets-plugin": "^1.0.6", 34 | "html-webpack-plugin": "^3.2.0", 35 | "html-webpack-root-plugin": "^0.10.0", 36 | "mini-css-extract-plugin": "^0.5.0", 37 | "node-sass": "^4.11.0", 38 | "npm-run-all": "^3.1.1", 39 | "react-svg-loader": "^2.1.0", 40 | "sass-loader": "^7.1.0", 41 | "standard": "^12.0.1", 42 | "standard-loader": "^6.0.1", 43 | "style-loader": "^0.23.1", 44 | "svg-inline-loader": "^0.8.0", 45 | "ts-loader": "^5.3.3", 46 | "tslint": "^5.12.1", 47 | "tslint-config-standard": "^8.0.1", 48 | "tslint-loader": "^3.5.4", 49 | "typescript": "^3.2.2", 50 | "webpack": "^4.28.4", 51 | "webpack-cli": "^3.2.1", 52 | "webpack-node-externals": "^1.7.2" 53 | }, 54 | "dependencies": { 55 | "@blueprintjs/core": "^3.11.0", 56 | "@blueprintjs/icons": "^3.5.0", 57 | "@types/react-splitter-layout": "^3.0.0", 58 | "font-awesome": "^4.7.0", 59 | "react": "^16.7.0", 60 | "react-dom": "^16.7.0", 61 | "react-splitter-layout": "^3.0.1" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app-electron/src/ToolBar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { 3 | Navbar, 4 | Button, 5 | ButtonGroup, 6 | NumericInput, 7 | ControlGroup, 8 | InputGroup, 9 | // FormGroup 10 | } from '@blueprintjs/core' 11 | 12 | import MetronomeIcon from './svg/metronome.svg' 13 | 14 | import './ToolBar.css' 15 | 16 | export interface ToolBarProps { 17 | } 18 | 19 | export default class ToolBar extends React.Component { 20 | render() { 21 | return ( 22 | 23 | 24 | 25 | 56 | {/* */} 57 | 58 | 59 | 61 | 62 | 65 | 68 | 71 | 72 | 73 | 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app-server/src/controller.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::thread::JoinHandle; 3 | 4 | use crossbeam_channel::{Receiver, Sender}; 5 | use failure::Fail; 6 | use log::{debug, info}; 7 | 8 | use crate::audio::callback::Protocol as AudioProtocol; 9 | use crate::midi::io::Protocol as MidiOutputProtocol; 10 | use crate::server::Message as ServerMessage; 11 | 12 | #[derive(Debug, Fail)] 13 | pub enum ControllerError { 14 | #[fail(display = "Failed to create the Controller thread: {}", cause)] 15 | Start { cause: String }, 16 | 17 | #[fail(display = "Failed to join the Controller thread")] 18 | Stop, 19 | } 20 | 21 | pub enum Protocol { 22 | Stop, 23 | 24 | ServerInput(ServerMessage), 25 | 26 | MidiInitialised, 27 | } 28 | 29 | struct ControllerThread { 30 | audio_tx: Sender, 31 | midi_tx: Sender, 32 | } 33 | 34 | impl ControllerThread { 35 | fn new(audio_tx: Sender, midi_tx: Sender) -> ControllerThread { 36 | ControllerThread { audio_tx, midi_tx } 37 | } 38 | 39 | pub fn handle_messages(&mut self, protocol_rx: Receiver) { 40 | for msg in protocol_rx.iter() { 41 | match msg { 42 | Protocol::Stop => { 43 | drop(self.audio_tx.send(AudioProtocol::Stop)); 44 | drop(self.midi_tx.send(MidiOutputProtocol::Stop)); 45 | break; 46 | } 47 | 48 | Protocol::ServerInput(message) => { 49 | debug!("Received {:#?}", message); 50 | } 51 | 52 | Protocol::MidiInitialised => {} 53 | } 54 | } 55 | } 56 | } 57 | 58 | pub struct Controller { 59 | handler: JoinHandle<()>, 60 | protocol_tx: Sender, 61 | } 62 | 63 | impl Controller { 64 | // TODO Use an spsc array when published by crossbeam 65 | pub const CHANNEL_CAPACITY: usize = 128 * 1024; 66 | pub fn new_channel() -> (Sender, Receiver) { 67 | crossbeam_channel::bounded::(Self::CHANNEL_CAPACITY) 68 | } 69 | 70 | #[allow(clippy::too_many_arguments)] 71 | pub fn new( 72 | protocol_tx: Sender, 73 | protocol_rx: Receiver, 74 | audio_tx: Sender, 75 | midi_tx: Sender, 76 | ) -> Result { 77 | info!("Starting Controller ..."); 78 | 79 | thread::Builder::new() 80 | .name("controller".into()) 81 | .spawn(move || ControllerThread::new(audio_tx, midi_tx).handle_messages(protocol_rx)) 82 | .map_err(|err| ControllerError::Start { 83 | cause: err.to_string(), 84 | }) 85 | .map(|handler| Controller { 86 | handler, 87 | protocol_tx, 88 | }) 89 | } 90 | 91 | pub fn stop(self) -> Result<(), ControllerError> { 92 | info!("Stopping Controller ..."); 93 | 94 | self 95 | .protocol_tx 96 | .send(Protocol::Stop) 97 | .map_err(|_| ControllerError::Stop) 98 | .and_then(|()| self.handler.join().map_err(|_| ControllerError::Stop)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app-server/src/realtime.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | 3 | use log::{debug, warn}; 4 | 5 | #[cfg(target_os = "macos")] 6 | use audio_thread_priority::{ 7 | demote_current_thread_from_real_time, promote_current_thread_to_real_time, RtPriorityHandle, 8 | }; 9 | 10 | pub type Result = std::result::Result; 11 | 12 | const ERROR_MSG: &str = "Thread could not be promoted to real time"; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct RealTimeAudioPriorityError {} 16 | 17 | impl std::fmt::Display for RealTimeAudioPriorityError { 18 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 19 | write!(f, "{}", ERROR_MSG) 20 | } 21 | } 22 | 23 | impl std::error::Error for RealTimeAudioPriorityError { 24 | fn description(&self) -> &str { 25 | ERROR_MSG 26 | } 27 | 28 | fn cause(&self) -> Option<&std::error::Error> { 29 | None 30 | } 31 | } 32 | 33 | pub struct RealTimeAudioPriority { 34 | #[cfg(any(target_os = "macos", target_os = "windows"))] 35 | handle: Option, 36 | } 37 | 38 | impl RealTimeAudioPriority { 39 | pub fn promote(sample_rate: u32, buffer_size: u32) -> Result { 40 | let current_thread = thread::current(); 41 | let thread_name = current_thread.name().unwrap_or("unknown"); 42 | 43 | match Self::promote_rt(sample_rate, buffer_size) { 44 | Ok(priority) => { 45 | debug!("Thread {} has now real-time priority", thread_name); 46 | Ok(priority) 47 | } 48 | Err(err) => { 49 | warn!( 50 | "Couldn't promote the thread {} into real time: {:?}", 51 | thread_name, err 52 | ); 53 | Err(err) 54 | } 55 | } 56 | } 57 | 58 | #[cfg(any(target_os = "macos", target_os = "windows"))] 59 | fn promote_rt(sample_rate: u32, buffer_size: u32) -> Result { 60 | promote_current_thread_to_real_time(buffer_size, sample_rate) 61 | .map(|handle| RealTimeAudioPriority { 62 | handle: Some(handle), 63 | }) 64 | .map_err(|_err| RealTimeAudioPriorityError {}) 65 | } 66 | 67 | #[cfg(any(target_os = "macos", target_os = "windows"))] 68 | fn demote_rt(&mut self) { 69 | self.handle.take().into_iter().for_each(|handle| { 70 | let _ = demote_current_thread_from_real_time(handle); 71 | }); 72 | } 73 | 74 | #[cfg(target_os = "linux")] 75 | fn promote_rt(sample_rate: u32, buffer_size: u32) -> Result { 76 | // TODO try something with thread_priority or rtkit 77 | Ok(RealTimeAudioPriority {}) 78 | } 79 | 80 | #[cfg(target_os = "linux")] 81 | fn demote_rt(&mut self) {} 82 | 83 | #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] 84 | fn promote_rt(sample_rate: u32, buffer_size: u32) -> Result { 85 | Ok(RealTimeAudioPriority {}) 86 | } 87 | 88 | #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] 89 | fn demote_rt(&mut self) {} 90 | } 91 | 92 | impl Drop for RealTimeAudioPriority { 93 | fn drop(&mut self) { 94 | self.demote_rt(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /core/src/time/drift_correction/clock.rs: -------------------------------------------------------------------------------- 1 | use crate::time::{clock, ClockTime, SampleRate}; 2 | 3 | #[derive(Clone)] 4 | pub struct ClockDriftCorrection { 5 | time_per_sample: ClockTime, 6 | error_per_sample: f64, 7 | error_accumulated: f64, 8 | last_correction: f64, 9 | } 10 | 11 | impl ClockDriftCorrection { 12 | pub fn new(sample_rate: SampleRate) -> ClockDriftCorrection { 13 | ClockDriftCorrection { 14 | time_per_sample: ClockTime::from_samples(1, sample_rate), 15 | error_per_sample: ClockDriftCorrection::error_per_sample(sample_rate), 16 | error_accumulated: 0.0, 17 | last_correction: 0.0, 18 | } 19 | } 20 | 21 | pub fn error_per_sample(sample_rate: SampleRate) -> f64 { 22 | let time_per_sample = ClockTime::from_samples(1, sample_rate); 23 | let error_per_second = ClockTime::from_seconds(1.0) - (time_per_sample * sample_rate); 24 | error_per_second.units() as f64 / f64::from(sample_rate) 25 | } 26 | 27 | pub fn get_time_per_sample(&self) -> ClockTime { 28 | self.time_per_sample 29 | } 30 | 31 | pub fn get_error_per_sample(&self) -> f64 { 32 | self.error_per_sample 33 | } 34 | 35 | pub fn get_error_accumulated(&self) -> f64 { 36 | self.error_accumulated 37 | } 38 | 39 | pub fn get_last_correction(&self) -> f64 { 40 | self.last_correction 41 | } 42 | 43 | pub fn next(&mut self, samples: u32) -> ClockTime { 44 | let samples_time = self.time_per_sample * samples; 45 | let total_error = self.error_accumulated + self.error_per_sample * f64::from(samples); 46 | if total_error.abs() >= 1.0 { 47 | self.last_correction = total_error.round(); 48 | self.error_accumulated = total_error - self.last_correction; 49 | samples_time + ClockTime::new(self.last_correction as clock::UnitType) 50 | } else { 51 | self.last_correction = 0.0; 52 | self.error_accumulated = total_error; 53 | samples_time 54 | } 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod test { 60 | 61 | use super::ClockDriftCorrection; 62 | use super::ClockTime; 63 | 64 | #[test] 65 | pub fn clock_drift_correction_new() { 66 | let correction = ClockDriftCorrection::new(44100); 67 | assert_eq!(correction.time_per_sample, ClockTime::new(22675)); 68 | assert_eq!(correction.error_per_sample, 0.736_961_451_247_165_5); 69 | assert_eq!(correction.error_accumulated, 0.0); 70 | assert_eq!(correction.last_correction, 0.0); 71 | } 72 | 73 | #[test] 74 | pub fn clock_drift_correction_error_per_sample() { 75 | let error = ClockDriftCorrection::error_per_sample(44100); 76 | assert_eq!(error, 0.736_961_451_247_165_5); 77 | } 78 | 79 | #[test] 80 | pub fn clock_drift_correction_next() { 81 | let mut correction = ClockDriftCorrection::new(44100); 82 | let samples_time = correction.next(100); 83 | assert_eq!(samples_time, ClockTime::new(2_267_574)); 84 | let samples_time = correction.next(100); 85 | assert_eq!(samples_time, ClockTime::new(2_267_573)); 86 | let samples_time = correction.next(100); 87 | assert_eq!(samples_time, ClockTime::new(2_267_574)); 88 | let samples_time = correction.next(100); 89 | assert_eq!(samples_time, ClockTime::new(2_267_574)); 90 | let samples_time = correction.next(100); 91 | assert_eq!(samples_time, ClockTime::new(2_267_573)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app-server/src/audio/callback.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::{Receiver, Sender}; 2 | use failure::Fail; 3 | 4 | use hero_studio_core::audio::{AudioInput, AudioOutput}; 5 | use hero_studio_core::midi::buffer::EventIo; 6 | use hero_studio_core::midi::io::{MidiInput, MidiOutput}; 7 | use hero_studio_core::studio::Studio; 8 | 9 | use crate::midi::io::Protocol as MidiIoProtocol; 10 | 11 | #[derive(Debug, Fail)] 12 | pub enum CallbackError { 13 | // #[fail(display = "Failed to try receiving an event: {}", cause)] 14 | // TryRecvError { cause: String }, 15 | } 16 | 17 | impl std::fmt::Display for CallbackError { 18 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 19 | write!(f, "CallbackError") 20 | } 21 | } 22 | 23 | pub enum Protocol { 24 | Stop, 25 | } 26 | 27 | struct ReceiverMidiInput { 28 | rx: Receiver 29 | } 30 | 31 | impl ReceiverMidiInput { 32 | fn new(rx: Receiver) -> Self { 33 | ReceiverMidiInput { rx } 34 | } 35 | } 36 | 37 | impl MidiInput for ReceiverMidiInput { 38 | fn pop(&mut self) -> Option { 39 | self.rx.try_recv().ok().and_then(|message| match message { 40 | MidiIoProtocol::EventIn(event_io) => Some(event_io), 41 | _ => None 42 | }) 43 | } 44 | } 45 | 46 | struct SenderMidiOutput { 47 | tx: Sender, 48 | } 49 | 50 | impl SenderMidiOutput { 51 | fn new(tx: Sender) -> Self { 52 | SenderMidiOutput { tx } 53 | } 54 | } 55 | 56 | impl MidiOutput for SenderMidiOutput { 57 | fn push(&mut self, event: EventIo) { 58 | let msg = MidiIoProtocol::EventOut(event); 59 | drop(self.tx.send(msg)) 60 | } 61 | } 62 | 63 | pub enum AudioCallbackResult { 64 | Continue, 65 | Stop, 66 | } 67 | 68 | pub struct AudioCallback { 69 | studio: Studio, 70 | protocol_rx: Receiver, 71 | midi_input: ReceiverMidiInput, 72 | midi_output: SenderMidiOutput, 73 | } 74 | 75 | impl AudioCallback { 76 | pub fn new( 77 | studio: Studio, 78 | protocol_rx: Receiver, 79 | midi_out_tx: Sender, 80 | midi_in_rx: Receiver, 81 | ) -> AudioCallback { 82 | AudioCallback { 83 | studio, 84 | protocol_rx, 85 | midi_input: ReceiverMidiInput::new(midi_in_rx), 86 | midi_output: SenderMidiOutput::new(midi_out_tx), 87 | } 88 | } 89 | 90 | #[allow(clippy::too_many_arguments)] 91 | pub fn process( 92 | &mut self, 93 | frames: usize, 94 | audio_input: AudioInput, 95 | mut audio_output: AudioOutput, 96 | ) -> Result { 97 | let result = self.handle_messages()?; 98 | 99 | self.studio.process( 100 | frames, 101 | &audio_input, 102 | &mut audio_output, 103 | &mut self.midi_input, 104 | &mut self.midi_output, 105 | ); 106 | 107 | Ok(result) 108 | } 109 | 110 | fn handle_messages(&mut self) -> Result { 111 | match self.protocol_rx.try_recv() { 112 | Ok(msg) => self.handle_message(msg), 113 | Err(_) => Ok(AudioCallbackResult::Continue), 114 | } 115 | } 116 | 117 | fn handle_message(&mut self, msg: Protocol) -> Result { 118 | match msg { 119 | Protocol::Stop => Ok(AudioCallbackResult::Stop), 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /core/src/time/drift_correction/ticks.rs: -------------------------------------------------------------------------------- 1 | use crate::time::{BarsTime, ClockTime, SampleRate, Signature, Tempo, TicksTime}; 2 | 3 | const SECONDS_PER_MINUTE: f64 = 60.0; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct TicksDriftCorrection { 7 | ticks_per_sample: f64, 8 | error_per_sample: f64, 9 | error_accumulated: f64, 10 | last_correction: f64, 11 | } 12 | 13 | impl TicksDriftCorrection { 14 | pub fn new(signature: Signature, tempo: Tempo, sample_rate: SampleRate) -> TicksDriftCorrection { 15 | let ticks_per_beat = f64::from(BarsTime::new(0, 1, 0, 0).to_ticks(signature)); 16 | let ticks_per_sample = 17 | ticks_per_beat * f64::from(tempo) / (SECONDS_PER_MINUTE * f64::from(sample_rate)); 18 | let error_per_second = f64::from(ClockTime::from_seconds(1.0).to_ticks(signature, tempo)) 19 | - ticks_per_sample * f64::from(sample_rate); 20 | let error_per_sample = error_per_second / f64::from(sample_rate); 21 | // println!("ticks_per_sample={:?}, ticks_per_sec={:?}, ticks_per_sec_we={:?}, error_per_sec={:?}, error_per_sample={:?}", 22 | // ticks_per_sample, 23 | // f64::from(ClockTime::from_seconds(1.0).to_ticks(signature, tempo)), 24 | // ticks_per_sample * sample_rate as f64, 25 | // error_per_second, error_per_sample); 26 | 27 | TicksDriftCorrection { 28 | ticks_per_sample, 29 | error_per_sample, 30 | error_accumulated: 0.0, 31 | last_correction: 0.0, 32 | } 33 | } 34 | 35 | pub fn get_ticks_per_sample(&self) -> f64 { 36 | self.ticks_per_sample 37 | } 38 | 39 | pub fn get_error_per_sample(&self) -> f64 { 40 | self.error_per_sample 41 | } 42 | 43 | pub fn get_error_accumulated(&self) -> f64 { 44 | self.error_accumulated 45 | } 46 | 47 | pub fn get_last_correction(&self) -> f64 { 48 | self.last_correction 49 | } 50 | 51 | pub fn next(&mut self, samples: u32) -> TicksTime { 52 | let samples_ticks = self.ticks_per_sample * f64::from(samples); 53 | let samples_error = 54 | samples_ticks - samples_ticks.round() + self.error_per_sample * f64::from(samples); 55 | let total_error = self.error_accumulated + samples_error; 56 | if total_error.abs() >= 1.0 { 57 | self.last_correction = total_error.round(); 58 | self.error_accumulated = total_error - self.last_correction; 59 | TicksTime::new((samples_ticks + self.last_correction) as u64) 60 | } else { 61 | self.last_correction = 0.0; 62 | self.error_accumulated = total_error; 63 | TicksTime::new(samples_ticks as u64) 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod test { 70 | 71 | use super::TicksDriftCorrection; 72 | use super::{Signature, Tempo, TicksTime}; 73 | 74 | #[test] 75 | pub fn ticks_drift_correction_new() { 76 | let correction = TicksDriftCorrection::new(Signature::new(6, 13), Tempo::new(130), 44100); 77 | assert_eq!(correction.ticks_per_sample, 30_719.999_958_427_816); 78 | assert_eq!(correction.error_per_sample, -0.000_003_779_291_295_680_87); 79 | assert_eq!(correction.error_accumulated, 0.0); 80 | assert_eq!(correction.last_correction, 0.0); 81 | } 82 | 83 | #[test] 84 | pub fn ticks_drift_correction_next() { 85 | let mut correction = TicksDriftCorrection::new(Signature::new(6, 7), Tempo::new(120), 44100); 86 | for _ in 0..7 { 87 | let ticks = correction.next(1000); 88 | assert_eq!(ticks, TicksTime::new(52_662_857)); 89 | } 90 | let ticks = correction.next(1000); 91 | assert_eq!(ticks, TicksTime::new(52_662_858)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /core/src/time/bars.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::time::{ticks::TICKS_RESOLUTION, Signature, TicksTime}; 4 | 5 | #[derive(PartialEq)] 6 | pub struct BarsTime { 7 | bars: u16, 8 | beats: u16, 9 | sixteenths: u16, 10 | ticks: u32, 11 | } 12 | 13 | impl BarsTime { 14 | pub fn new(bars: u16, beats: u16, sixteenths: u16, ticks: u32) -> BarsTime { 15 | BarsTime { 16 | bars, 17 | beats, 18 | sixteenths, 19 | ticks, 20 | } 21 | } 22 | 23 | pub fn from_bars(bars: u16) -> BarsTime { 24 | BarsTime::new(bars, 0, 0, 0) 25 | } 26 | 27 | pub fn from_ticks(ticks_time: TicksTime, signature: Signature) -> BarsTime { 28 | let num_ticks = u64::from(ticks_time); 29 | let total_sixteenths = num_ticks / TICKS_RESOLUTION; 30 | let num_sixteenths_per_beat = 16 / u64::from(signature.get_note_value()); 31 | let total_beats = total_sixteenths / num_sixteenths_per_beat; 32 | BarsTime { 33 | bars: (total_beats / u64::from(signature.get_num_beats())) as u16, 34 | beats: (total_beats % u64::from(signature.get_num_beats())) as u16, 35 | sixteenths: (total_sixteenths % num_sixteenths_per_beat) as u16, 36 | ticks: (num_ticks % TICKS_RESOLUTION) as u32, 37 | } 38 | } 39 | 40 | pub fn get_bars(&self) -> u16 { 41 | self.bars 42 | } 43 | 44 | pub fn get_beats(&self) -> u16 { 45 | self.beats 46 | } 47 | 48 | pub fn get_sixteenths(&self) -> u16 { 49 | self.sixteenths 50 | } 51 | 52 | pub fn get_ticks(&self) -> u32 { 53 | self.ticks 54 | } 55 | 56 | pub fn to_ticks(&self, signature: Signature) -> TicksTime { 57 | let num_sixteenths_per_beat = 16.0 / f64::from(signature.get_note_value()); 58 | let num_ticks_per_beat = num_sixteenths_per_beat * TICKS_RESOLUTION as f64; 59 | let num_ticks_per_bar = f64::from(signature.get_num_beats()) * num_ticks_per_beat; 60 | TicksTime::new( 61 | u64::from(self.bars) * num_ticks_per_bar as u64 62 | + u64::from(self.beats) * num_ticks_per_beat as u64 63 | + u64::from(self.sixteenths) * TICKS_RESOLUTION 64 | + u64::from(self.ticks), 65 | ) 66 | } 67 | } 68 | 69 | impl fmt::Debug for BarsTime { 70 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | write!( 72 | f, 73 | "{:05}:{:02}:{:02}:{:04}", 74 | self.bars + 1, 75 | self.beats + 1, 76 | self.sixteenths + 1, 77 | self.ticks 78 | ) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod test { 84 | 85 | use super::BarsTime; 86 | use crate::time::{ticks::TicksTime, ticks::TICKS_RESOLUTION, Signature}; 87 | 88 | #[test] 89 | pub fn new() { 90 | let time = BarsTime::new(10, 1, 2, 100); 91 | assert_eq!(time.get_bars(), 10); 92 | assert_eq!(time.get_beats(), 1); 93 | assert_eq!(time.get_sixteenths(), 2); 94 | assert_eq!(time.get_ticks(), 100); 95 | } 96 | 97 | #[test] 98 | pub fn from_bars() { 99 | let time = BarsTime::from_bars(2); 100 | assert_eq!(time.get_bars(), 2); 101 | assert_eq!(time.get_beats(), 0); 102 | assert_eq!(time.get_sixteenths(), 0); 103 | assert_eq!(time.get_ticks(), 0); 104 | } 105 | 106 | #[test] 107 | pub fn from_ticks() { 108 | let ticks = TicksTime::new( 109 | TICKS_RESOLUTION * 4 * 3 * 10 + // 10 bars 110 | TICKS_RESOLUTION * 4 * 2 + // 2 beats 111 | TICKS_RESOLUTION + // 1 sixteens 112 | 30, // 30 ticks 113 | ); 114 | 115 | let time = BarsTime::from_ticks(ticks, Signature::new(3, 4)); 116 | assert_eq!(time.get_bars(), 10); 117 | assert_eq!(time.get_beats(), 2); 118 | assert_eq!(time.get_sixteenths(), 1); 119 | assert_eq!(time.get_ticks(), 30); 120 | } 121 | 122 | #[test] 123 | pub fn to_ticks() { 124 | let signature = Signature::new(3, 4); 125 | let ticks = TicksTime::new(123_456_789); 126 | let time = BarsTime::from_ticks(ticks, signature); 127 | let ticks = time.to_ticks(signature); 128 | assert_eq!(u64::from(ticks), 123_456_789); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app-server/src/midi/drivers/mod.rs: -------------------------------------------------------------------------------- 1 | mod portmidi; 2 | pub use self::portmidi::ID as PORT_MIDI_ID; 3 | 4 | #[cfg(target_os = "macos")] 5 | mod coremidi; 6 | 7 | #[cfg(target_os = "macos")] 8 | pub use self::coremidi::ID as CORE_MIDI_ID; 9 | 10 | #[cfg(not(target_os = "macos"))] 11 | const DEFAULT_ID: &'static str = PORT_MIDI_ID; 12 | 13 | #[cfg(target_os = "macos")] 14 | const DEFAULT_ID: &str = CORE_MIDI_ID; 15 | 16 | use std::collections::HashMap; 17 | 18 | use failure::Fail; 19 | 20 | use hero_studio_core::midi::buffer::Buffer; 21 | use hero_studio_core::time::ClockTime; 22 | 23 | #[derive(Debug, Fail)] 24 | pub enum MidiError { 25 | #[fail(display = "Failed to initialise the MIDI driver: {}", cause)] 26 | Init { cause: String }, 27 | 28 | #[fail(display = "Driver not found: {}", id)] 29 | DriverNotFound { id: String }, 30 | 31 | #[fail(display = "Failed to open a destination: {}", cause)] 32 | DestinationOpen { cause: String }, 33 | 34 | #[fail(display = "Failed to open a source: {}", cause)] 35 | SourceOpen { cause: String }, 36 | } 37 | 38 | pub type MidiResult = Result; 39 | 40 | type MidiDriverFactory = Box MidiResult>>; 41 | 42 | pub struct MidiDrivers { 43 | drivers: HashMap, 44 | } 45 | 46 | impl MidiDrivers { 47 | pub fn new() -> MidiDrivers { 48 | let mut drivers: HashMap = HashMap::new(); 49 | 50 | Self::add_platform_drivers(&mut drivers); 51 | 52 | Self::add_common_drivers(&mut drivers); 53 | 54 | MidiDrivers { drivers } 55 | } 56 | 57 | #[cfg(target_os = "macos")] 58 | fn add_platform_drivers(drivers: &mut HashMap) { 59 | let coremidi_factory = Box::new(|app_name: String| { 60 | coremidi::CoreMidi::new(app_name).map(|driver| Box::new(driver) as Box) 61 | }); 62 | drivers.insert(coremidi::ID.to_string(), coremidi_factory); 63 | } 64 | 65 | #[cfg(not(target_os = "macos"))] 66 | fn add_platform_drivers(drivers: &mut HashMap) {} 67 | 68 | fn add_common_drivers(drivers: &mut HashMap) { 69 | let portmidi_factory = Box::new(|_app_name: String| { 70 | portmidi::PortMidiDriver::new().map(|driver| Box::new(driver) as Box) 71 | }); 72 | drivers.insert(portmidi::ID.to_string(), portmidi_factory); 73 | } 74 | 75 | #[allow(dead_code)] 76 | pub fn drivers(&self) -> Vec<&String> { 77 | self.drivers.keys().collect() 78 | } 79 | 80 | pub fn driver(&self, id: A, app_name: B) -> MidiResult> 81 | where 82 | A: Into, 83 | B: Into, 84 | { 85 | let id = id.into(); 86 | self 87 | .drivers 88 | .get(&id) 89 | .map(|driver_factory| driver_factory(app_name.into())) 90 | .unwrap_or_else(|| Err(MidiError::DriverNotFound { id })) 91 | } 92 | 93 | pub fn default(&self, app_name: T) -> MidiResult> 94 | where 95 | T: Into, 96 | { 97 | self.driver(DEFAULT_ID, app_name.into()) 98 | } 99 | } 100 | 101 | pub trait MidiDriver { 102 | fn id(&self) -> &str; 103 | 104 | fn sources(&self) -> Vec>; 105 | fn destinations(&self) -> Vec>; 106 | 107 | // fn create_virtual_output(&self, name: T) -> dyn MidiOutput where T: Into; 108 | } 109 | 110 | pub trait MidiDestination { 111 | fn name(&self) -> &str; 112 | fn open(&self) -> MidiResult>; 113 | } 114 | 115 | pub type MidiSourceCallback = Fn(&Buffer) + Send + 'static; 116 | 117 | pub trait MidiSource { 118 | fn name(&self) -> &str; 119 | fn open(&self, callback: Box) -> MidiResult>; 120 | } 121 | 122 | pub trait MidiEndpoint { 123 | fn name(&self) -> &str; 124 | } 125 | 126 | pub trait MidiOutput: MidiEndpoint { 127 | fn send(&mut self, base_time: ClockTime, buffer: &Buffer); 128 | } 129 | 130 | pub trait MidiInput: MidiEndpoint { 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![TravisCI build status](https://travis-ci.org/chris-zen/hero-studio.svg)](https://travis-ci.org/chris-zen/hero-studio) 2 | 3 | # Hero Studio 4 | 5 | This project is an attempt to build a music studio similar to Ableton Live or Bitwig Studio in the long term. 6 | 7 | For the short term my main goal is to learn the core details about DAWs and audio programming, and may be to use the core engine to build more modest applications such as a VST host for development of plugins, a Raspberry Pi recording device, or a tracker. 8 | 9 | ## UI development 10 | 11 | I plan to make this project very modular and keep UIs as separate applications. The plan is to have an engine that allows communication from external systems such a UI or a surface MIDI controllers, through some RPC mechanism (currently WebSockets + some serialization protocol). 12 | 13 | I have been exploring several possibilities for building GUIs, more information can be found in [the wiki](https://github.com/chris-zen/hero-studio/wiki). 14 | 15 | ## Organisation 16 | 17 | This is a multi-project composed by: 18 | 19 | - [core](core): The core data model and logic. 20 | - [app-server](app-server): The application server that runs the Audio/MIDI engine. 21 | - [app-electron](app-electron): Very preliminary prototype of a GUI with Electron. 22 | 23 | ## Running 24 | 25 | For running the main application you need to run the following commands: 26 | 27 | ```sh 28 | cd app-server 29 | cargo run --release 30 | ``` 31 | 32 | But bear in mind that you will need to install [PortAudio](http://www.portaudio.com/download.html) and [PortMIDI](http://portmedia.sourceforge.net/portmidi/) before running the project, as this is using the [portaudio](https://docs.rs/crate/portaudio/0.7.0) and [portmidi](https://crates.io/crates/portmidi) crates that depend on their binaries and headers. 33 | 34 | If you get an error like: 35 | 36 | ```sh 37 | error while loading shared libraries: libportaudio.so.2: cannot open shared object file: No such file or directory 38 | ``` 39 | 40 | then you should install the binary dependencies and headers for PortAudio and/or PortMIDI: 41 | 42 | ### Installing PortAudio & PortMIDI in Ubuntu 43 | 44 | ```sh 45 | # ubuntu 46 | sudo apt-get install libportaudio2 libportmidi-dev 47 | ``` 48 | 49 | ### Installing PortAudio & PortMIDI in MacOS 50 | 51 | ```sh 52 | brew install portaudio portmidi 53 | ``` 54 | 55 | ## Tests 56 | 57 | Tests can be run with: 58 | 59 | ```sh 60 | cargo test 61 | ``` 62 | 63 | ## Roadmap 64 | 65 | Since the begining I knew that the scope of this project was too big for a single person, but anyway I was willing to learn as much as possible by exploring ideas myself through this project. At some point I needed to explore some more ideas from a fresher and newer perspective, so I put this project on hold and started working on a different one (see [kiro-synth](https://github.com/chris-zen/kiro-synth)). This is giving me a new perspective on several fronts such as MIDI handling, threads communication, and UI development. So I expect to come back to this project and apply all of my learnings at some point in the future. I knew this was going to be a looong hobby project ;-P 66 | 67 | I keep this roadmap for the record, although it might completely change once I'm back: 68 | 69 | - [x] core: Basic transport logic with accurate timing: play, stop, loop 70 | - [x] app-native: Allow MIDI configuration through `studio.toml` 71 | - [x] core: MIDI bus system 72 | - [x] core: Implement a basic metronome using MIDI notes 73 | - [x] core: Management of time and sync between different clocks: Audio, Midi, Host, Ticks 74 | - [x] app-server: Build MIDI output abstraction to send MIDI events to a device using CoreMIDI & PortMIDI as an initial impl 75 | - [x] app-server: Explore ways to avoid locks and allocations from the real-time processing threads 76 | - [ ] core: Implement an audio based metronome and investigate the MIDI metronome drift. 77 | - [ ] app-server: Add MIDI input support 78 | - [ ] app-server: Add VST plugins support 79 | - [ ] rethink the project submodules into something like (ui-electron, server and engine) 80 | - [ ] Start exploring UI integrations (some progress already done with Electron, but would like to test Qt too) 81 | - [ ] core: Filling MIDI output buffers from the Song clips (initially choose one type of clip between pianoroll, step-sequencer, drum-box). 82 | - [ ] ... 83 | -------------------------------------------------------------------------------- /core/src/studio.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | use crate::audio; 5 | use crate::audio::{AudioInput, AudioOutput}; 6 | use crate::config::{Config, MidiPort}; 7 | use crate::metronome::Metronome; 8 | use crate::midi; 9 | use crate::midi::buffer::EventIo; 10 | use crate::midi::io::{MidiInput, MidiOutput}; 11 | use crate::pool::Pool; 12 | use crate::song::Song; 13 | use crate::time::{BarsTime, ClockTime}; 14 | use crate::transport::{Segment, Transport}; 15 | use crate::midi::Buffer; 16 | 17 | const MIDI_BUFFER_CAPACITY: usize = 256 * 1024; 18 | 19 | fn fill_with_zero(s: &mut [f32]) { 20 | for d in s { 21 | *d = 0.0; 22 | } 23 | } 24 | 25 | pub struct Studio { 26 | config: Config, 27 | transport: Transport, 28 | metronome: Metronome, 29 | song: Song, 30 | midi_buffer: Vec, 31 | } 32 | 33 | unsafe impl Send for Studio {} 34 | 35 | impl Studio { 36 | pub fn new(config: Config) -> Studio { 37 | let song = Song::new("untitled", &config); 38 | 39 | let sample_rate = config.audio.sample_rate; 40 | let transport = Transport::new(sample_rate); 41 | 42 | let metronome_config = config.metronome.clone(); 43 | let signature = *transport.get_signature(); 44 | let metronome = Metronome::new(metronome_config, signature); 45 | 46 | let midi_buffer = Vec::with_capacity(MIDI_BUFFER_CAPACITY); 47 | 48 | Studio { 49 | config, 50 | transport, 51 | metronome, 52 | song, 53 | midi_buffer, 54 | } 55 | } 56 | 57 | pub fn config(&self) -> &Config { 58 | &self.config 59 | } 60 | 61 | pub fn song(&self) -> &Song { 62 | &self.song 63 | } 64 | 65 | pub fn song_mut(&mut self) -> &mut Song { 66 | &mut self.song 67 | } 68 | 69 | pub fn set_loop_enabled(&mut self, enabled: bool) { 70 | self.transport.set_loop_enabled(enabled); 71 | } 72 | 73 | pub fn set_loop_start(&mut self, position: BarsTime) { 74 | self.transport.set_loop_start(position); 75 | } 76 | 77 | pub fn set_loop_end(&mut self, position: BarsTime) { 78 | self.transport.set_loop_end(position) 79 | } 80 | 81 | pub fn play(&mut self, restart: bool) -> bool { 82 | self.transport.play(restart); 83 | self.transport.is_playing() 84 | } 85 | 86 | pub fn stop(&mut self) { 87 | self.transport.stop(); 88 | } 89 | 90 | #[allow(clippy::too_many_arguments)] 91 | pub fn process( 92 | &mut self, 93 | audio_frames: usize, 94 | _audio_input: &AudioInput, 95 | audio_output: &mut AudioOutput, 96 | midi_input: &mut MidiIn, 97 | midi_output: &mut MidiOut, 98 | ) where 99 | MidiIn: MidiInput, 100 | MidiOut: MidiOutput, 101 | { 102 | self.capture_midi_in(midi_input); 103 | 104 | if self.transport.is_playing() { 105 | let master_clock = audio_output.time; 106 | 107 | let mut segments = self 108 | .transport 109 | .segments_iterator(master_clock, audio_frames as u32); 110 | 111 | while let Some(segment) = segments.next(&self.transport) { 112 | self.metronome.process_segment(&segment, midi_output); 113 | self.song.process_segment(&segment); 114 | } 115 | 116 | self.transport.update_from_segments(&segments); 117 | 118 | fill_with_zero(audio_output.buffer); 119 | 120 | // for i in 0..audio_frames { 121 | // let v = i as f32 / audio_frames as f32; 122 | // let u = i * audio_input.channels; 123 | // let j = i * audio_output.channels; 124 | // for k in 0..audio_output.channels { 125 | // audio_output.buffer[j + k] = audio_input.buffer[u] + v * 0.20; 126 | // } 127 | // } 128 | } else { 129 | fill_with_zero(audio_output.buffer); 130 | } 131 | } 132 | 133 | fn capture_midi_in(&mut self, midi_input: &mut MidiIn) where MidiIn: MidiInput { 134 | self.midi_buffer.clear(); 135 | while let Some(event_io) = midi_input.pop() { 136 | // println!("{:?}", event_io); 137 | self.midi_buffer.push(event_io); 138 | if self.midi_buffer.len() == MIDI_BUFFER_CAPACITY { 139 | break; 140 | } 141 | } 142 | } 143 | } 144 | 145 | impl fmt::Debug for Studio { 146 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 147 | write!(f, "Studio({:?})", self.song.get_name()) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /core/src/config.rs: -------------------------------------------------------------------------------- 1 | use failure; 2 | use failure::Error; 3 | 4 | use serde; 5 | use serde_derive::Deserialize; 6 | 7 | use std::fs::File; 8 | use std::io::Read; 9 | use std::str::FromStr; 10 | use std::sync::{Arc, RwLock}; 11 | 12 | pub type ConfigLock = Arc>; 13 | 14 | #[serde(default)] 15 | #[derive(Deserialize, Debug, Clone)] 16 | pub struct Config { 17 | pub audio: Audio, 18 | pub midi: Midi, 19 | pub metronome: Metronome, 20 | } 21 | 22 | impl Default for Config { 23 | fn default() -> Config { 24 | Config { 25 | audio: Audio::default(), 26 | midi: Midi::default(), 27 | metronome: Metronome::default(), 28 | } 29 | } 30 | } 31 | 32 | impl FromStr for Config { 33 | type Err = Error; 34 | 35 | fn from_str(content: &str) -> Result { 36 | let config: Config = toml::from_str(content)?; 37 | Ok(config) 38 | } 39 | } 40 | 41 | impl Config { 42 | pub fn from_file<'a, T>(path: T) -> Result 43 | where 44 | T: Into<&'a str>, 45 | { 46 | let mut content = String::new(); 47 | let path_str = path.into(); 48 | let mut file = File::open(path_str)?; 49 | file.read_to_string(&mut content)?; 50 | let config: Config = toml::from_str(&content)?; 51 | Ok(config) 52 | } 53 | } 54 | 55 | #[serde(default)] 56 | #[derive(Deserialize, Debug, Clone)] 57 | pub struct Audio { 58 | pub input_port: AudioPort, 59 | pub output_port: AudioPort, 60 | pub sample_rate: u32, 61 | pub frames: u16, 62 | } 63 | 64 | #[derive(Deserialize, Debug, Clone)] 65 | pub enum AudioPort { 66 | #[serde(rename = "none")] 67 | None, 68 | #[serde(rename = "default")] 69 | SystemDefault, 70 | #[serde(rename = "name")] 71 | ByName(String), 72 | } 73 | 74 | impl Default for Audio { 75 | fn default() -> Audio { 76 | Audio { 77 | input_port: AudioPort::SystemDefault, 78 | output_port: AudioPort::SystemDefault, 79 | sample_rate: 44100, 80 | frames: 512, 81 | } 82 | } 83 | } 84 | 85 | #[serde(default)] 86 | #[derive(Deserialize, Debug, Clone)] 87 | pub struct Midi { 88 | pub driver_id: String, 89 | pub default_input: MidiPort, 90 | pub default_output: MidiPort, 91 | pub virtual_ports: Vec, 92 | } 93 | 94 | #[derive(Deserialize, Debug, Clone)] 95 | pub enum MidiPort { 96 | #[serde(rename = "none")] 97 | None, 98 | #[serde(rename = "all")] 99 | All, 100 | #[serde(rename = "default")] 101 | SystemDefault, 102 | #[serde(rename = "name")] 103 | ByName(String), 104 | } 105 | 106 | #[derive(Deserialize, Debug, Clone)] 107 | pub struct MidiVirtualPort { 108 | pub name: String, 109 | #[serde(default)] 110 | pub sync_delay_ms: u32, 111 | } 112 | 113 | impl Default for Midi { 114 | fn default() -> Midi { 115 | Midi { 116 | driver_id: "default".to_string(), 117 | default_input: MidiPort::All, 118 | default_output: MidiPort::SystemDefault, 119 | virtual_ports: Vec::new(), 120 | } 121 | } 122 | } 123 | 124 | #[serde(default)] 125 | #[derive(Deserialize, Debug, Clone)] 126 | pub struct Metronome { 127 | pub enabled: bool, 128 | pub port: MidiPort, 129 | pub bar_note: MetronomeNote, 130 | pub beat_note: MetronomeNote, 131 | } 132 | 133 | #[derive(Deserialize, Debug, Clone)] 134 | pub struct MetronomeNote { 135 | #[serde(default = "default_metronome_note_channel")] 136 | pub channel: u8, 137 | pub key: u8, 138 | #[serde(default = "default_metronome_note_velocity")] 139 | pub velocity: u8, 140 | #[serde(default = "default_metronome_note_duration")] 141 | pub duration: u8, 142 | } 143 | 144 | fn default_metronome_note_channel() -> u8 { 145 | 0 146 | } 147 | 148 | fn default_metronome_note_velocity() -> u8 { 149 | 127 150 | } 151 | 152 | fn default_metronome_note_duration() -> u8 { 153 | 16 // 1/16 (a sixteenth) 154 | } 155 | 156 | impl Default for Metronome { 157 | fn default() -> Metronome { 158 | Metronome { 159 | enabled: true, 160 | port: MidiPort::SystemDefault, 161 | bar_note: MetronomeNote { 162 | channel: 0, 163 | key: 84, 164 | velocity: 127, 165 | duration: 16, 166 | }, 167 | beat_note: MetronomeNote { 168 | channel: 0, 169 | key: 77, 170 | velocity: 120, 171 | duration: 16, 172 | }, 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /core/src/midi/messages.rs: -------------------------------------------------------------------------------- 1 | use crate::midi::types::{U14, U3, U4, U7}; 2 | 3 | #[derive(Debug, Clone, Eq, PartialEq)] 4 | pub enum Message { 5 | // --- Channel Voice Messages [nnnn = 0-15 (MIDI Channel Number 1-16)] 6 | /// This message is sent when a note is released (ended). 7 | NoteOff { channel: U4, key: U7, velocity: U7 }, 8 | 9 | /// This message is sent when a note is depressed (start). 10 | NoteOn { channel: U4, key: U7, velocity: U7 }, 11 | 12 | /// This message is most often sent by pressing down on the key after it "bottoms out". 13 | PolyphonicKeyPressure { channel: U4, key: U7, value: U7 }, 14 | 15 | /// This message is sent when a controller value changes. 16 | ControlChange { 17 | channel: U4, 18 | controller: U7, 19 | value: U7, 20 | }, 21 | 22 | /// This message sent when the patch number changes. 23 | ProgramChange { channel: U4, value: U7 }, 24 | 25 | /// This message is most often sent by pressing down on the key after it "bottoms out". 26 | /// Use this message to send the single greatest pressure value (of all the current depressed keys). 27 | ChannelPressure { channel: U4, value: U7 }, 28 | 29 | /// Pitch Bend Change. This message is sent to indicate a change in the pitch bender 30 | /// (wheel or lever, typically). The pitch bender is measured by a fourteen bit value. Center 31 | /// (no pitch change) is 2000H. 32 | PitchBend { channel: U4, value: U14 }, 33 | 34 | // --- Channel Mode Messages 35 | /// When All Sound Off is received all oscillators will turn off, and their 36 | /// volume envelopes are set to zero as soon as possible. 37 | AllSoundOff { channel: U4 }, 38 | 39 | /// When Reset All Controllers is received, all controller values are reset to their default values. 40 | ResetAllControllers { channel: U4 }, 41 | 42 | /// When Local Control is Off, all devices on a given channel will respond only to data 43 | /// received over MIDI. Played data, etc. will be ignored. 44 | LocalControlOff { channel: U4 }, 45 | 46 | /// Local Control On restores the functions of the normal controllers. 47 | LocalControlOn { channel: U4 }, 48 | 49 | /// When an All Notes Off is received, all oscillators will turn off. 50 | AllNotesOff { channel: U4 }, 51 | 52 | /// It also causes all notes off. 53 | OmniModeOff { channel: U4 }, 54 | 55 | /// It also causes all notes off. 56 | OmniModeOn { channel: U4 }, 57 | 58 | /// It also causes all notes off. 59 | MonoModeOn { channel: U4, num_channels: U7 }, 60 | 61 | /// It also causes all notes off. 62 | PolyModeOn { channel: U4 }, 63 | 64 | // --- System Common Messages 65 | /// MIDI Time Code Quarter Frame. 66 | /// The type determines how to interpret the value: 67 | /// 0 Current Frames Low Nibble 68 | /// 1 Current Frames High Nibble 69 | /// 2 Current Seconds Low Nibble 70 | /// 3 Current Seconds High Nibble 71 | /// 4 Current Minutes Low Nibble 72 | /// 5 Current Minutes High Nibble 73 | /// 6 Current Hours Low Nibble 74 | /// 7 Current Hours High Nibble and SMPTE Type 75 | MTCQuarterFrame { msg_type: U3, value: U4 }, 76 | 77 | /// Song Position Pointer. 78 | /// This is an internal 14 bit register that holds the number of MIDI beats 79 | /// (1 beat= six MIDI clocks) since the start of the song. 80 | SongPositionPointer { beats: U14 }, 81 | 82 | /// Song Select 83 | /// The Song Select specifies which sequence or song is to be played. 84 | SongSelect { song: U7 }, 85 | 86 | /// Upon receiving a Tune Request, all analog synthesizers should tune their oscillators. 87 | TuneRequest, 88 | 89 | // --- System Real-Time Messages 90 | /// Timing Clock. Sent 24 times per quarter note when synchronization is required. 91 | TimingClock, 92 | 93 | /// Start the current sequence playing. 94 | /// (This message will be followed with Timing Clocks). 95 | Start, 96 | 97 | /// Continue at the point the sequence was Stopped 98 | Continue, 99 | 100 | /// Stop the current sequence. 101 | Stop, 102 | 103 | /// This message is intended to be sent repeatedly to tell the receiver that a 104 | /// connection is alive. Use of this message is optional. When initially received, the receiver 105 | /// will expect to receive another Active Sensing message each 300ms (max), and if it does not 106 | /// then it will assume that the connection has been terminated. At termination, the receiver 107 | /// will turn off all voices and return to normal (non- active sensing) operation. 108 | ActiveSensing, 109 | 110 | /// Reset all receivers in the system to power-up status. This should be used sparingly, 111 | /// preferably under manual control. In particular, it should not be sent on power-up. 112 | SystemReset, 113 | } 114 | -------------------------------------------------------------------------------- /app-electron/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const nodeExternals = require('webpack-node-externals') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | const HtmlWebpackRootPlugin = require('html-webpack-root-plugin') 6 | const CspHtmlWebpackPlugin = require('csp-html-webpack-plugin') 7 | const CspPolicy = { 8 | 'base-uri': "'self'", 9 | 'object-src': "'none'", 10 | 'script-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"], 11 | 'style-src': ["'unsafe-inline'", "'self'", "'unsafe-eval'"] 12 | } 13 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 14 | // const CopyWebpackPlugin = require('copy-webpack-plugin') 15 | // const HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin') 16 | // const GoogleFontsPlugin = require('@beyonk/google-fonts-webpack-plugin') 17 | 18 | const commonConfig = { 19 | // watch: true, 20 | output: { 21 | path: path.resolve(__dirname, 'dist'), 22 | filename: '[name].js' 23 | }, 24 | node: { 25 | __filename: false, 26 | __dirname: false 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.ts$/, 32 | enforce: 'pre', 33 | loader: 'tslint-loader', 34 | options: { 35 | typeCheck: true, 36 | emitErrors: true 37 | } 38 | }, 39 | { 40 | test: /\.tsx?$/, 41 | use: ['babel-loader', 'ts-loader'] 42 | }, 43 | { 44 | test: /\.js$/, 45 | enforce: 'pre', 46 | loader: 'standard-loader', 47 | options: { 48 | typeCheck: true, 49 | emitErrors: true 50 | } 51 | }, 52 | { 53 | test: /\.jsx?$/, 54 | loader: 'babel-loader' 55 | }, 56 | { 57 | test: /\.s?css$/, 58 | use: [ 59 | // 'style-loader', 60 | MiniCssExtractPlugin.loader, 61 | 'css-loader', 62 | 'sass-loader' 63 | ] 64 | }, 65 | { 66 | test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/, 67 | use: [ 68 | { 69 | loader: 'file-loader', 70 | options: { 71 | name: '[name].[ext]', 72 | outputPath: 'fonts/' 73 | } 74 | } 75 | ] 76 | }, 77 | { 78 | test: /\.html$/, 79 | loader: 'file-loader', 80 | options: { 81 | name: '[name].[ext]' 82 | } 83 | }, 84 | { 85 | test: /\.svg$/, 86 | // loader: 'svg-inline-loader' 87 | use: [ 88 | { 89 | loader: 'babel-loader' 90 | }, 91 | { 92 | loader: 'react-svg-loader', 93 | options: { 94 | jsx: true // true outputs JSX tags 95 | } 96 | } 97 | ] 98 | } 99 | ] 100 | }, 101 | resolve: { 102 | extensions: [ 103 | '.js', '.ts', '.tsx', '.jsx', '.json', '.scss', '.css', '.html', '.svg' 104 | ] 105 | } 106 | } 107 | 108 | module.exports = [ 109 | Object.assign( 110 | { 111 | target: 'electron-main', 112 | entry: { main: './src/main.ts' }, 113 | externals: [nodeExternals()] 114 | }, 115 | commonConfig), 116 | Object.assign( 117 | { 118 | target: 'electron-renderer', 119 | entry: { gui: './src/gui.tsx' }, 120 | plugins: [ 121 | new HtmlWebpackPlugin({ title: 'Hero Studio' }), 122 | new HtmlWebpackRootPlugin(), 123 | new CspHtmlWebpackPlugin(CspPolicy), 124 | new MiniCssExtractPlugin(), 125 | // new CopyWebpackPlugin([ 126 | // { from: 'node_modules/normalize.css/normalize.css', to: 'blueprint/' }, 127 | // { from: 'node_modules/@blueprintjs/core/lib/css/blueprint.css', to: 'blueprint/' }, 128 | // { from: 'node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css', to: 'blueprint/' } 129 | // ]), 130 | // new HtmlWebpackIncludeAssetsPlugin({ 131 | // assets: [ 132 | // 'blueprint/normalize.css', 133 | // 'blueprint/blueprint.css', 134 | // 'blueprint/blueprint-icons.css' 135 | // ], 136 | // append: true 137 | // }), 138 | // new GoogleFontsPlugin({ 139 | // path: 'fonts/', 140 | // fonts: [ 141 | // // { family: 'Source Sans Pro' }, 142 | // { family: 'Roboto', variants: [ '300', '400', '500' ] } 143 | // ] 144 | // }), 145 | new webpack.DefinePlugin({ 146 | 'process.env': { 147 | NODE_ENV: '"development"' 148 | }, 149 | 'global': { 150 | GENTLY: false 151 | } // bizarre lodash(?) webpack workaround 152 | }) 153 | ] 154 | }, 155 | commonConfig) 156 | ] 157 | -------------------------------------------------------------------------------- /core/src/metronome.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use crate::config::{Metronome as MetronomeConfig, MetronomeNote, MidiPort}; 5 | use crate::midi; 6 | use crate::midi::buffer::{Endpoint, EventIo}; 7 | use crate::midi::io::MidiOutput; 8 | use crate::time::{ 9 | ticks::TICKS_RESOLUTION, BarsTime, ClockTime, SampleRate, Signature, Tempo, TicksTime, 10 | }; 11 | use crate::transport::{Segment, Transport}; 12 | 13 | pub struct Metronome { 14 | config: MetronomeConfig, 15 | enabled: bool, 16 | endpoint: Endpoint, 17 | bar_duration: TicksTime, 18 | beat_duration: TicksTime, 19 | } 20 | 21 | impl Metronome { 22 | pub fn new(config: MetronomeConfig, signature: Signature) -> Metronome { 23 | let enabled = config.enabled; 24 | let endpoint = Self::endpoint_from_midi_port(&config.port); 25 | let (bar_duration, beat_duration) = Self::bar_and_beat_duration(signature); 26 | 27 | Metronome { 28 | config, 29 | enabled, 30 | endpoint, 31 | bar_duration, 32 | beat_duration, 33 | } 34 | } 35 | 36 | pub fn set_enabled(&mut self, enabled: bool) { 37 | self.enabled = enabled; 38 | } 39 | 40 | pub fn is_enabled(&self) -> bool { 41 | self.enabled 42 | } 43 | 44 | pub fn endpoint(&self) -> Endpoint { 45 | self.endpoint 46 | } 47 | 48 | pub fn process_segment(&mut self, segment: &Segment, midi_output: &mut MidiOut) 49 | where 50 | MidiOut: MidiOutput, 51 | { 52 | if self.enabled { 53 | let signature = segment.signature; 54 | let tempo = segment.tempo; 55 | let mut next_bar_position = Self::ceil_ticks(segment.start_position, self.bar_duration); 56 | let mut next_beat_position = Self::ceil_ticks(segment.start_position, self.beat_duration); 57 | 58 | while next_beat_position < segment.end_position { 59 | let advanced_ticks = next_beat_position - segment.start_position; 60 | let note_time = segment.master_clock + advanced_ticks.to_clock(signature, tempo); 61 | 62 | // let bars_time = BarsTime::from_ticks(next_beat_position, signature); 63 | if next_beat_position == next_bar_position { 64 | // println!("Metronome: |> {:?}", bars_time); 65 | let note = &self.config.bar_note; 66 | Self::push_note( 67 | midi_output, 68 | self.endpoint, 69 | note_time, 70 | note, 71 | signature, 72 | tempo, 73 | ); 74 | next_bar_position += self.bar_duration; 75 | } else { 76 | // println!("Metronome: ~> {:?}", bars_time); 77 | let note = &self.config.beat_note; 78 | Self::push_note( 79 | midi_output, 80 | self.endpoint, 81 | note_time, 82 | note, 83 | signature, 84 | tempo, 85 | ); 86 | } 87 | next_beat_position += self.beat_duration; 88 | } 89 | } 90 | } 91 | 92 | fn push_note( 93 | midi_output: &mut MidiOut, 94 | endpoint: Endpoint, 95 | start_time: ClockTime, 96 | note: &MetronomeNote, 97 | signature: Signature, 98 | tempo: Tempo, 99 | ) where 100 | MidiOut: MidiOutput, 101 | { 102 | // TODO duration_ticks only needs to be calculated once per note 103 | let duration_ticks = TicksTime::new(16 * TICKS_RESOLUTION / u64::from(note.duration)); 104 | let duration_time = duration_ticks.to_clock(signature, tempo); 105 | let end_time = start_time + duration_time; 106 | 107 | midi_output.push(EventIo::new( 108 | start_time, 109 | endpoint, 110 | midi::Message::NoteOn { 111 | channel: note.channel, 112 | key: note.key, 113 | velocity: note.velocity, 114 | }, 115 | )); 116 | 117 | midi_output.push(EventIo::new( 118 | end_time, 119 | endpoint, 120 | midi::Message::NoteOff { 121 | channel: note.channel, 122 | key: note.key, 123 | velocity: note.velocity, 124 | }, 125 | )); 126 | } 127 | 128 | fn bar_and_beat_duration(signature: Signature) -> (TicksTime, TicksTime) { 129 | let bar_duration = BarsTime::from_bars(1).to_ticks(signature); 130 | let beat_duration = bar_duration / u64::from(signature.get_num_beats()); 131 | (bar_duration, beat_duration) 132 | } 133 | 134 | fn ceil_ticks(start: TicksTime, module: TicksTime) -> TicksTime { 135 | ((start + module - TicksTime::new(1)) / module) * module 136 | } 137 | 138 | fn endpoint_from_midi_port(port: &MidiPort) -> Endpoint { 139 | // TODO Select the endpoint from the configuration when update events are received 140 | match port { 141 | MidiPort::None => Endpoint::None, 142 | MidiPort::SystemDefault => Endpoint::Default, 143 | MidiPort::All => Endpoint::All, 144 | MidiPort::ByName(_name) => Endpoint::None, // TODO 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /core/src/time/ticks.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::{min, Ordering}, 3 | ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}, 4 | }; 5 | 6 | use crate::time::{clock, ClockTime, Signature, Tempo}; 7 | 8 | pub const TICKS_RESOLUTION: u64 = 508_032_000; // 2^10 * 3^4 * 5^3 * 7^2 9 | 10 | #[derive(Debug, Eq, Copy, Clone)] 11 | pub struct TicksTime(u64); 12 | 13 | impl TicksTime { 14 | pub fn new(ticks: u64) -> TicksTime { 15 | TicksTime(ticks) 16 | } 17 | 18 | pub fn zero() -> TicksTime { 19 | TicksTime(0) 20 | } 21 | 22 | pub fn per_minute(signature: Signature, tempo: Tempo) -> TicksTime { 23 | let ticks_per_beat = TICKS_RESOLUTION * 16 / u64::from(signature.get_note_value()); 24 | TicksTime::new(ticks_per_beat * u64::from(tempo)) 25 | } 26 | 27 | pub fn to_clock(&self, signature: Signature, tempo: Tempo) -> ClockTime { 28 | let ticks_per_minute = TicksTime::per_minute(signature, tempo).0; 29 | let clock_units = 30 | u128::from(self.0) * u128::from(clock::UNITS_PER_MINUTE) / u128::from(ticks_per_minute); 31 | ClockTime::new(clock_units as u64) 32 | } 33 | } 34 | 35 | impl Ord for TicksTime { 36 | fn cmp(&self, other: &TicksTime) -> Ordering { 37 | self.0.cmp(&other.0) 38 | } 39 | } 40 | 41 | impl PartialOrd for TicksTime { 42 | fn partial_cmp(&self, other: &TicksTime) -> Option { 43 | Some(self.cmp(other)) 44 | } 45 | } 46 | 47 | impl PartialEq for TicksTime { 48 | fn eq(&self, other: &TicksTime) -> bool { 49 | self.0 == other.0 50 | } 51 | } 52 | 53 | impl Add for TicksTime { 54 | type Output = TicksTime; 55 | fn add(self, rhs: TicksTime) -> Self { 56 | TicksTime::new(self.0 + rhs.0) 57 | } 58 | } 59 | 60 | impl AddAssign for TicksTime { 61 | fn add_assign(&mut self, rhs: TicksTime) { 62 | *self = *self + rhs; 63 | } 64 | } 65 | 66 | impl Sub for TicksTime { 67 | type Output = TicksTime; 68 | fn sub(self, rhs: TicksTime) -> Self { 69 | TicksTime::new(self.0 - min(self.0, rhs.0)) 70 | } 71 | } 72 | 73 | impl SubAssign for TicksTime { 74 | fn sub_assign(&mut self, rhs: TicksTime) { 75 | *self = *self - rhs; 76 | } 77 | } 78 | 79 | impl Mul for TicksTime { 80 | type Output = TicksTime; 81 | fn mul(self, rhs: TicksTime) -> Self { 82 | TicksTime::new(self.0 * rhs.0) 83 | } 84 | } 85 | 86 | impl Div for TicksTime { 87 | type Output = TicksTime; 88 | fn div(self, rhs: TicksTime) -> Self { 89 | TicksTime::new(self.0 / rhs.0) 90 | } 91 | } 92 | 93 | impl Div for TicksTime { 94 | type Output = TicksTime; 95 | fn div(self, rhs: u64) -> Self { 96 | TicksTime::new(self.0 / rhs) 97 | } 98 | } 99 | 100 | impl From for f64 { 101 | fn from(item: TicksTime) -> Self { 102 | item.0 as f64 103 | } 104 | } 105 | 106 | impl From for u64 { 107 | fn from(item: TicksTime) -> Self { 108 | item.0 as u64 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod test { 114 | 115 | use super::{clock, Signature, Tempo, TicksTime}; 116 | use std::cmp::Ordering; 117 | 118 | #[test] 119 | pub fn new() { 120 | let ticks_time = TicksTime::new(1234); 121 | assert_eq!(ticks_time.0, 1234); 122 | } 123 | 124 | #[test] 125 | pub fn zero() { 126 | let ticks_time = TicksTime::zero(); 127 | assert_eq!(ticks_time.0, 0); 128 | } 129 | 130 | #[test] 131 | pub fn per_minute() { 132 | let signature = Signature::new(4, 4); 133 | let tempo = Tempo::new(120); 134 | let ticks = TicksTime::per_minute(signature, tempo); 135 | assert_eq!(ticks.0, 243_855_360_000); 136 | } 137 | 138 | #[test] 139 | pub fn to_clock() { 140 | let signature = Signature::new(4, 4); 141 | let tempo = Tempo::new(120); 142 | let ticks = TicksTime::per_minute(signature, tempo); 143 | let time = ticks.to_clock(signature, tempo); 144 | assert_eq!(time.units() / clock::UNITS_PER_MINUTE, 1); 145 | } 146 | 147 | #[test] 148 | pub fn ord_cmp() { 149 | let time1 = TicksTime::new(1234); 150 | let time2 = TicksTime::new(1235); 151 | assert_eq!(time1.cmp(&time2), Ordering::Less); 152 | assert_eq!(time2.cmp(&time1), Ordering::Greater); 153 | assert_eq!(time1.cmp(&time1), Ordering::Equal); 154 | } 155 | 156 | #[test] 157 | pub fn partial_ord_cmp() { 158 | let time1 = TicksTime::new(1234); 159 | let time2 = TicksTime::new(4321); 160 | assert_eq!(time1.partial_cmp(&time2), Some(Ordering::Less)); 161 | assert_eq!(time2.partial_cmp(&time1), Some(Ordering::Greater)); 162 | assert_eq!(time1.partial_cmp(&time1), Some(Ordering::Equal)); 163 | } 164 | 165 | #[test] 166 | pub fn partial_eq() { 167 | let time1 = TicksTime::new(1234); 168 | let time2 = TicksTime::new(1234); 169 | let time3 = TicksTime::new(4321); 170 | assert!(time1.eq(&time2)); 171 | assert!(!time2.eq(&time3)); 172 | } 173 | 174 | #[test] 175 | pub fn add() { 176 | let time1 = TicksTime::new(100); 177 | let time2 = TicksTime::new(50); 178 | let result = time1 + time2; 179 | assert_eq!(result, TicksTime(150)); 180 | } 181 | 182 | #[test] 183 | pub fn sub() { 184 | let time1 = TicksTime::new(100); 185 | let time2 = TicksTime::new(30); 186 | let result = time1 - time2; 187 | assert_eq!(result, TicksTime(70)); 188 | } 189 | 190 | #[test] 191 | pub fn mul() { 192 | let time1 = TicksTime::new(100); 193 | let time2 = TicksTime::new(5); 194 | let result = time1 * time2; 195 | assert_eq!(result, TicksTime(500)); 196 | } 197 | 198 | #[test] 199 | pub fn div() { 200 | let time1 = TicksTime::new(100); 201 | let time2 = TicksTime::new(5); 202 | let result = time1 / time2; 203 | assert_eq!(result, TicksTime(20)); 204 | } 205 | 206 | #[test] 207 | pub fn div_u64() { 208 | let time1 = TicksTime::new(100); 209 | let result = time1 / 5; 210 | assert_eq!(result, TicksTime(20)); 211 | } 212 | 213 | #[test] 214 | pub fn f64_from() { 215 | let time1 = TicksTime::new(1234); 216 | assert_eq!(f64::from(time1), 1234.0); 217 | } 218 | 219 | #[test] 220 | pub fn u64_from() { 221 | let time1 = TicksTime::new(1234); 222 | assert_eq!(u64::from(time1), 1234); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /app-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use log::{debug, info}; 4 | 5 | use failure; 6 | use failure::{Error, Fail}; 7 | 8 | use crossbeam_channel::{Receiver, Sender}; 9 | 10 | use hero_studio_core::{config::Config as StudioConfig, studio::Studio, time::BarsTime}; 11 | 12 | mod config; 13 | use crate::config::Config as AppConfig; 14 | 15 | mod midi; 16 | use crate::midi::io::{MidiIo, Protocol as MidiOutputProtocol}; 17 | 18 | mod audio; 19 | use crate::audio::callback::{AudioCallback, Protocol as AudioProtocol}; 20 | use crate::audio::drivers::portaudio::{PortAudioDriver, PortAudioStream}; 21 | 22 | mod controller; 23 | use crate::controller::{Controller, Protocol as ControllerProtocol}; 24 | 25 | mod server; 26 | use crate::server::{Message as ServerMessage, Server}; 27 | 28 | //mod events; 29 | 30 | mod realtime; 31 | 32 | //const APP_NAME: &'static str = "Hero Studio"; 33 | 34 | const HERO_STUDIO_CONFIG: &str = "HERO_STUDIO_CONFIG"; 35 | const DEFAULT_HERO_STUDIO_CONFIG: &str = "studio.toml"; 36 | 37 | const HERO_STUDIO_APP_CONFIG: &str = "HERO_STUDIO_APP_CONFIG"; 38 | const DEFAULT_HERO_STUDIO_APP_CONFIG: &str = "app.toml"; 39 | 40 | const HERO_STUDIO_LOG_CONFIG: &str = "HERO_STUDIO_LOG_CONFIG"; 41 | const DEFAULT_HERO_STUDIO_LOG_CONFIG: &str = "log4rs.yaml"; 42 | 43 | #[derive(Debug, Fail)] 44 | enum MainError { 45 | #[fail(display = "Failed to init logging: {}", cause)] 46 | LoggingInit { cause: String }, 47 | } 48 | 49 | fn main() -> Result<(), Error> { 50 | init_logging()?; 51 | 52 | let app_config = init_app_config()?; 53 | let websocket_port = app_config.websocket.port; 54 | 55 | let studio_config = init_studio_config()?; 56 | let midi_config = &studio_config.midi; 57 | let audio_config = &studio_config.audio; 58 | 59 | let (server_tx, server_rx) = Server::new_channel(); 60 | let (ctrl_tx, ctrl_rx) = Controller::new_channel(); 61 | let (audio_tx, audio_rx) = PortAudioStream::new_channel(); 62 | let (midi_out_tx, midi_out_rx) = MidiIo::new_channel(); 63 | let (midi_in_tx, midi_in_rx) = MidiIo::new_channel(); 64 | 65 | let midi_output = MidiIo::new( 66 | midi_config, 67 | audio_config, 68 | midi_out_tx.clone(), 69 | midi_out_rx.clone(), 70 | midi_in_tx.clone(), 71 | ctrl_tx.clone(), 72 | )?; 73 | 74 | let studio = init_studio(studio_config)?; 75 | 76 | let (_, mut stream) = init_audio(studio, 77 | audio_rx.clone(), 78 | midi_out_tx.clone(), 79 | midi_in_rx.clone())?; 80 | 81 | let controller = Controller::new( 82 | ctrl_tx.clone(), 83 | ctrl_rx.clone(), 84 | audio_tx.clone(), 85 | midi_out_tx.clone(), 86 | )?; 87 | 88 | let server = init_server( 89 | websocket_port, 90 | server_tx.clone(), 91 | server_rx.clone(), 92 | ctrl_tx.clone(), 93 | )?; 94 | 95 | drop(server_tx); 96 | drop(server_rx); 97 | drop(ctrl_tx); 98 | drop(ctrl_rx); 99 | drop(audio_tx); 100 | drop(audio_rx); 101 | drop(midi_out_tx); 102 | drop(midi_out_rx); 103 | 104 | stream.wait(); 105 | 106 | server.close(); 107 | 108 | controller.stop()?; 109 | 110 | stream.stop()?; 111 | stream.close()?; 112 | 113 | midi_output.stop()?; 114 | 115 | Ok(()) 116 | } 117 | 118 | fn init_logging() -> Result<(), Error> { 119 | let log_config_path = std::env::var(HERO_STUDIO_LOG_CONFIG) 120 | .unwrap_or_else(|_| DEFAULT_HERO_STUDIO_LOG_CONFIG.to_string()); 121 | 122 | log4rs::init_file(log_config_path.as_str(), Default::default()).map_err(|err| { 123 | MainError::LoggingInit { 124 | cause: err.to_string(), 125 | } 126 | })?; 127 | 128 | Ok(()) 129 | } 130 | 131 | fn init_app_config() -> Result { 132 | let config_path = std::env::var(HERO_STUDIO_APP_CONFIG) 133 | .unwrap_or_else(|_| DEFAULT_HERO_STUDIO_APP_CONFIG.to_string()); 134 | 135 | info!("Loading app configuration from {} ...", config_path); 136 | let config = AppConfig::from_file(config_path.as_str())?; 137 | debug!("{:#?}", config); 138 | 139 | Ok(config) 140 | } 141 | 142 | fn init_studio_config() -> Result { 143 | let config_path = 144 | std::env::var(HERO_STUDIO_CONFIG).unwrap_or_else(|_| DEFAULT_HERO_STUDIO_CONFIG.to_string()); 145 | 146 | info!("Loading studio configuration from {} ...", config_path); 147 | let config = StudioConfig::from_file(config_path.as_str())?; 148 | debug!("{:#?}", config); 149 | 150 | Ok(config) 151 | } 152 | 153 | fn init_studio(config: StudioConfig) -> Result { 154 | info!("Initialising the studio ..."); 155 | 156 | let mut studio = Studio::new(config); 157 | 158 | studio.set_loop_end(BarsTime::new(2, 0, 0, 0)); 159 | studio.play(true); 160 | 161 | Ok(studio) 162 | } 163 | 164 | fn init_audio( 165 | studio: Studio, 166 | audio_rx: Receiver, 167 | midi_out_tx: Sender, 168 | midi_in_rx: Receiver, 169 | ) -> Result<(Rc, PortAudioStream), Error> { 170 | info!("Initialising audio ..."); 171 | 172 | let audio_config = &studio.config().audio.clone(); 173 | 174 | let driver = PortAudioDriver::new().map(Rc::new)?; 175 | let audio_callback = AudioCallback::new(studio, audio_rx, midi_out_tx, midi_in_rx); 176 | let mut stream = PortAudioStream::new(driver.clone(), audio_config, audio_callback)?; 177 | stream.start()?; 178 | 179 | Ok((driver, stream)) 180 | } 181 | 182 | fn init_server( 183 | port: u16, 184 | server_tx: Sender, 185 | server_rx: Receiver, 186 | ctrl_tx: Sender, 187 | ) -> Result { 188 | info!("Initialising the websocket server ..."); 189 | 190 | let server = Server::new(port, server_tx, server_rx, ctrl_tx)?; 191 | 192 | // let sender = server.sender(); 193 | // std::thread::spawn(move || { 194 | // let mut count = 0; 195 | // loop { 196 | // let data = format!("{:?}", count); 197 | // drop(sender.send(Message::Outgoing { port: ALL_PORTS, data: data.into_bytes() } )); 198 | // std::thread::sleep_ms(1); 199 | // count += 1; 200 | // } 201 | // }); 202 | 203 | Ok(server) 204 | } 205 | -------------------------------------------------------------------------------- /core/src/time/clock.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; 2 | 3 | use super::{ticks, SampleRate, Signature, Tempo, TicksTime}; 4 | 5 | pub const MILLIS_PER_SECOND: u64 = 1_000; 6 | pub const NANOS_PER_SECOND: u64 = 1_000_000_000; 7 | // pub const PICOS_PER_SECOND: u64 = 1_000_000_000_000; 8 | // pub const FEMTOS_PER_SECOND: u64 = 1_000_000_000_000_000; 9 | // pub const ATTOS_PER_SECOND: u64 = 1_000_000_000_000_000_000; 10 | 11 | pub type UnitType = u64; 12 | pub const UNITS_PER_SECOND: UnitType = NANOS_PER_SECOND as UnitType; 13 | pub const UNITS_PER_NANO: UnitType = UNITS_PER_SECOND / NANOS_PER_SECOND; 14 | pub const UNITS_PER_MILLI: UnitType = UNITS_PER_SECOND / MILLIS_PER_SECOND; 15 | 16 | const SECONDS_PER_MINUTE: u64 = 60; 17 | pub const UNITS_PER_MINUTE: u64 = UNITS_PER_SECOND * SECONDS_PER_MINUTE; 18 | 19 | ///! High resolution time 20 | #[derive(Debug, PartialOrd, PartialEq, Clone, Copy)] 21 | pub struct ClockTime(UnitType); 22 | 23 | impl ClockTime { 24 | pub fn zero() -> ClockTime { 25 | ClockTime(0) 26 | } 27 | 28 | pub fn new(units: UnitType) -> ClockTime { 29 | ClockTime(units) 30 | } 31 | 32 | pub fn from_nanos(nanos: u64) -> ClockTime { 33 | debug_assert!(UNITS_PER_NANO != 0); 34 | ClockTime(nanos * UNITS_PER_NANO) 35 | } 36 | 37 | pub fn from_millis(nanos: u64) -> ClockTime { 38 | debug_assert!(UNITS_PER_MILLI != 0); 39 | ClockTime(nanos * UNITS_PER_MILLI) 40 | } 41 | 42 | pub fn from_seconds(seconds: f64) -> ClockTime { 43 | ClockTime((seconds * UNITS_PER_SECOND as f64).round() as UnitType) 44 | } 45 | 46 | pub fn from_samples(samples: u32, sample_rate: SampleRate) -> ClockTime { 47 | ClockTime(UnitType::from(samples) * UNITS_PER_SECOND / UnitType::from(sample_rate)) 48 | } 49 | 50 | pub fn units(&self) -> UnitType { 51 | self.0 52 | } 53 | 54 | pub fn to_seconds(&self) -> f64 { 55 | self.0 as f64 / UNITS_PER_SECOND as f64 56 | } 57 | 58 | pub fn to_nanos(&self) -> u64 { 59 | self.0 as u64 / UNITS_PER_NANO 60 | } 61 | 62 | pub fn to_ticks(&self, signature: Signature, tempo: Tempo) -> TicksTime { 63 | let ticks_per_minute = u64::from(TicksTime::per_minute(signature, tempo)); 64 | let ticks = u128::from(self.0) * u128::from(ticks_per_minute) / u128::from(UNITS_PER_MINUTE); 65 | TicksTime::new(ticks as u64) 66 | } 67 | } 68 | 69 | impl Add for ClockTime { 70 | type Output = ClockTime; 71 | 72 | fn add(self, rhs: ClockTime) -> ClockTime { 73 | ClockTime(self.0 + rhs.0) 74 | } 75 | } 76 | 77 | impl AddAssign for ClockTime { 78 | fn add_assign(&mut self, rhs: ClockTime) { 79 | *self = *self + rhs; 80 | } 81 | } 82 | 83 | impl Sub for ClockTime { 84 | type Output = ClockTime; 85 | 86 | fn sub(self, rhs: ClockTime) -> ClockTime { 87 | ClockTime(self.0 - rhs.0) 88 | // FIXME thread '' panicked at 'attempt to subtract with overflow', core/src/time/clock.rs:75:15 89 | } 90 | } 91 | 92 | impl SubAssign for ClockTime { 93 | fn sub_assign(&mut self, rhs: ClockTime) { 94 | *self = *self - rhs; 95 | } 96 | } 97 | 98 | impl Mul for ClockTime { 99 | type Output = ClockTime; 100 | 101 | fn mul(self, rhs: u32) -> ClockTime { 102 | ClockTime(self.0 * UnitType::from(rhs)) 103 | } 104 | } 105 | 106 | impl MulAssign for ClockTime { 107 | fn mul_assign(&mut self, rhs: u32) { 108 | *self = *self * rhs; 109 | } 110 | } 111 | 112 | impl Mul for u32 { 113 | type Output = ClockTime; 114 | 115 | fn mul(self, rhs: ClockTime) -> ClockTime { 116 | rhs * self 117 | } 118 | } 119 | 120 | impl Div for ClockTime { 121 | type Output = ClockTime; 122 | 123 | fn div(self, rhs: u32) -> ClockTime { 124 | ClockTime(self.0 / UnitType::from(rhs)) 125 | } 126 | } 127 | 128 | impl DivAssign for ClockTime { 129 | fn div_assign(&mut self, rhs: u32) { 130 | *self = *self / rhs; 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod test { 136 | use super::ClockTime; 137 | use crate::time::clock::UNITS_PER_SECOND; 138 | 139 | #[test] 140 | pub fn clock_time_new() { 141 | let time = ClockTime::new(15); 142 | assert_eq!(time.units(), 15); 143 | } 144 | 145 | #[test] 146 | pub fn clock_time_zero() { 147 | let time = ClockTime::zero(); 148 | assert_eq!(time.units(), 0); 149 | } 150 | 151 | #[test] 152 | pub fn clock_time_from_nanos() { 153 | let time = ClockTime::from_nanos(15); 154 | assert_eq!(time.units(), 15); 155 | } 156 | 157 | #[test] 158 | pub fn clock_time_from_millis() { 159 | let time = ClockTime::from_millis(15); 160 | assert_eq!(time.units(), 15_000_000); 161 | } 162 | 163 | #[test] 164 | pub fn clock_time_from_seconds() { 165 | let time = ClockTime::from_seconds(0.5); 166 | assert_eq!(time.units(), UNITS_PER_SECOND / 2); 167 | } 168 | 169 | #[test] 170 | pub fn clock_time_add() { 171 | let time1 = ClockTime::new(15); 172 | let time2 = ClockTime::new(5); 173 | assert_eq!(time1 + time2, ClockTime::new(20)); 174 | } 175 | 176 | #[test] 177 | pub fn clock_time_add_assign() { 178 | let mut time1 = ClockTime::new(15); 179 | time1 += ClockTime::new(5); 180 | assert_eq!(time1, ClockTime::new(20)); 181 | } 182 | 183 | #[test] 184 | pub fn clock_time_sub() { 185 | let time1 = ClockTime::new(15); 186 | let time2 = ClockTime::new(5); 187 | assert_eq!(time1 - time2, ClockTime::new(10)); 188 | } 189 | 190 | #[test] 191 | pub fn clock_time_sub_assign() { 192 | let mut time1 = ClockTime::new(15); 193 | time1 -= ClockTime::new(5); 194 | assert_eq!(time1, ClockTime::new(10)); 195 | } 196 | 197 | #[test] 198 | pub fn clock_time_mul() { 199 | let time1 = ClockTime::new(15); 200 | assert_eq!(time1 * 2u32, ClockTime::new(30)); 201 | } 202 | 203 | #[test] 204 | pub fn clock_time_mul_assign() { 205 | let mut time1 = ClockTime::new(15); 206 | time1 *= 2u32; 207 | assert_eq!(time1, ClockTime::new(30)); 208 | } 209 | 210 | #[test] 211 | pub fn clock_time_mul_rhs() { 212 | let time1 = ClockTime::new(15); 213 | assert_eq!(2u32 * time1, ClockTime::new(30)); 214 | } 215 | 216 | #[test] 217 | pub fn clock_time_div() { 218 | let time1 = ClockTime::new(30); 219 | assert_eq!(time1 / 2u32, ClockTime::new(15)); 220 | } 221 | 222 | #[test] 223 | pub fn clock_time_div_assign() { 224 | let mut time1 = ClockTime::new(30); 225 | time1 /= 2u32; 226 | assert_eq!(time1, ClockTime::new(15)); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /app-server/src/audio/drivers/portaudio.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::time::Duration; 3 | 4 | use log::{debug, error, info, trace}; 5 | 6 | use crossbeam_channel::{Receiver, Sender}; 7 | 8 | use portaudio::{ 9 | stream::callback_flags, stream::callback_flags::CallbackFlags, DuplexStreamCallbackArgs, 10 | DuplexStreamSettings, PortAudio, Stream, StreamParameters, 11 | }; 12 | 13 | use hero_studio_core::audio::{AudioInput, AudioOutput}; 14 | use hero_studio_core::config::Audio as AudioConfig; 15 | use hero_studio_core::time::ClockTime; 16 | 17 | use crate::audio::callback::Protocol; 18 | use crate::audio::callback::{AudioCallback, AudioCallbackResult}; 19 | use crate::audio::drivers::{AudioError, AudioResult}; 20 | 21 | const INTERLEAVED: bool = true; 22 | 23 | impl From for AudioError { 24 | fn from(cause: portaudio::error::Error) -> AudioError { 25 | AudioError::DriverError { 26 | cause: cause.to_string(), 27 | } 28 | } 29 | } 30 | 31 | type PaStream = Stream>; 32 | 33 | pub struct PortAudioDriver { 34 | portaudio: PortAudio, 35 | } 36 | 37 | impl PortAudioDriver { 38 | pub fn new() -> AudioResult { 39 | let portaudio = PortAudio::new()?; 40 | 41 | trace!("PortAudio:"); 42 | trace!(" version: {}", portaudio.version()); 43 | trace!(" version text: {:?}", portaudio.version_text()); 44 | trace!(" host count: {}", portaudio.host_api_count()?); 45 | 46 | let default_host = portaudio.default_host_api()?; 47 | trace!( 48 | " default host: {:#?}", 49 | portaudio.host_api_info(default_host) 50 | ); 51 | 52 | trace!("All devices:"); 53 | for device in portaudio.devices()? { 54 | let (idx, info) = device?; 55 | trace!("[{:?}] ---------------------------------------", idx); 56 | trace!("{:#?}", &info); 57 | } 58 | 59 | Ok(PortAudioDriver { portaudio }) 60 | } 61 | 62 | pub fn sleep(&self, duration: Duration) { 63 | self.portaudio.sleep(duration.as_millis() as i32); 64 | } 65 | } 66 | 67 | pub struct PortAudioStream { 68 | driver: Rc, 69 | stream: PaStream, 70 | } 71 | 72 | impl PortAudioStream { 73 | // TODO Use an spsc array when published by crossbeam 74 | pub const CHANNEL_CAPACITY: usize = 128 * 1024; 75 | pub fn new_channel() -> (Sender, Receiver) { 76 | crossbeam_channel::bounded::(Self::CHANNEL_CAPACITY) 77 | } 78 | 79 | pub fn new( 80 | driver: Rc, 81 | config: &AudioConfig, 82 | mut audio_callback: AudioCallback, 83 | ) -> AudioResult { 84 | info!("Creating an audio stream ..."); 85 | 86 | let portaudio = &driver.portaudio; 87 | 88 | // TODO get devices to use from config 89 | 90 | let def_output = portaudio.default_output_device()?; 91 | let output_info = portaudio.device_info(def_output)?; 92 | debug!("Output device info: {:#?}", &output_info); 93 | 94 | let def_input = portaudio.default_input_device()?; 95 | let input_info = portaudio.device_info(def_input)?; 96 | debug!("Input device info: {:#?}", &input_info); 97 | 98 | // Construct the stream parameters 99 | let latency = input_info.default_low_input_latency; 100 | let input_params = StreamParameters::::new( 101 | def_input, 102 | input_info.max_input_channels, 103 | INTERLEAVED, 104 | latency, 105 | ); 106 | 107 | let latency = output_info.default_low_output_latency; 108 | let output_params = StreamParameters::::new( 109 | def_output, 110 | output_info.max_output_channels, 111 | INTERLEAVED, 112 | latency, 113 | ); 114 | 115 | let sample_rate = f64::from(config.sample_rate); 116 | portaudio.is_duplex_format_supported(input_params, output_params, sample_rate)?; 117 | 118 | // Construct the duplex stream 119 | let num_frames = u32::from(config.frames); 120 | let settings = DuplexStreamSettings::new(input_params, output_params, sample_rate, num_frames); 121 | 122 | let num_input_channels = input_info.max_input_channels as usize; 123 | let num_output_channels = output_info.max_output_channels as usize; 124 | 125 | let starting = true; 126 | let callback = move |args| { 127 | Self::callback( 128 | args, 129 | &mut audio_callback, 130 | num_input_channels as usize, 131 | num_output_channels as usize, 132 | starting, 133 | ) 134 | }; 135 | 136 | let stream = portaudio.open_non_blocking_stream(settings, callback)?; 137 | 138 | Ok(PortAudioStream { driver, stream }) 139 | } 140 | 141 | fn callback( 142 | args: DuplexStreamCallbackArgs, 143 | audio_callback: &mut AudioCallback, 144 | in_channels: usize, 145 | out_channels: usize, 146 | starting: bool, 147 | ) -> portaudio::stream::CallbackResult { 148 | let DuplexStreamCallbackArgs { 149 | in_buffer, 150 | out_buffer, 151 | frames, 152 | time, 153 | flags, 154 | } = args; 155 | 156 | Self::detect_and_report_xruns(starting, time.out_buffer_dac, flags); 157 | 158 | let in_time = ClockTime::from_seconds(time.in_buffer_adc); 159 | let out_time = ClockTime::from_seconds(time.out_buffer_dac); 160 | let audio_input = AudioInput::new(in_time, in_channels, in_buffer); 161 | let audio_output = AudioOutput::new(out_time, out_channels, out_buffer); 162 | match audio_callback.process(frames, audio_input, audio_output) { 163 | Ok(AudioCallbackResult::Continue) => portaudio::Continue, 164 | Ok(AudioCallbackResult::Stop) => portaudio::Complete, 165 | Err(_err) => { 166 | // TODO handle error 167 | error!("{}", _err.to_string()); 168 | portaudio::Complete 169 | } 170 | } 171 | } 172 | 173 | pub fn start(&mut self) -> AudioResult<()> { 174 | info!("Starting the audio stream ..."); 175 | self.stream.start().map_err(Into::into) 176 | } 177 | 178 | pub fn wait(&self) { 179 | while let Ok(true) = self.stream.is_active() { 180 | self.driver.sleep(Duration::from_secs(1)); 181 | } 182 | } 183 | 184 | pub fn stop(&mut self) -> AudioResult<()> { 185 | info!("Stopping the audio stream ..."); 186 | self.stream.stop().map_err(Into::into) 187 | } 188 | 189 | pub fn close(mut self) -> AudioResult<()> { 190 | info!("Closing the audio stream ..."); 191 | self.stream.close().map_err(Into::into) 192 | } 193 | 194 | fn detect_and_report_xruns(mut starting: bool, output_time: f64, flags: CallbackFlags) { 195 | if starting && flags != callback_flags::INPUT_UNDERFLOW { 196 | starting = false; 197 | } 198 | 199 | if !starting && !flags.is_empty() { 200 | let nanos = ClockTime::from_seconds(output_time).to_nanos(); 201 | let microseconds = nanos / 1000; 202 | let seconds = nanos / 1_000_000_000; 203 | let minutes = seconds / 60; 204 | let hours = minutes / 60; 205 | debug!( 206 | "xrun {}:{:02}:{:02}:{:06} {:?}", 207 | hours, 208 | minutes % 60, 209 | seconds % 60, 210 | microseconds % 1_000_000, 211 | flags 212 | ); 213 | // TODO measure xrun rate and stop if too high 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /app-server/src/midi/drivers/coremidi.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use coremidi::{ 4 | Client, Destination, Destinations, InputPort, OutputPort, PacketBuffer, PacketList, Source, 5 | Sources, 6 | }; 7 | 8 | use hero_studio_core::midi::buffer::Buffer; 9 | use hero_studio_core::midi::encoder::Encoder; 10 | use hero_studio_core::time::ClockTime; 11 | 12 | use super::{ 13 | MidiDestination, MidiDriver, MidiEndpoint, MidiError, MidiInput, MidiOutput, MidiResult, 14 | MidiSource, MidiSourceCallback, 15 | }; 16 | use hero_studio_core::midi::decoder::{DecodedMessage, Decoder}; 17 | 18 | pub const ID: &str = "CoreMIDI"; 19 | 20 | const INPUT_BUFFER_CAPACITY: usize = 16 * 1024; 21 | 22 | const OUTPUT_MESSAGE_CAPACITY: usize = 8; 23 | const OUTPUT_PACKET_BUFFER_CAPACITY: usize = 16 * 1024; 24 | 25 | pub struct CoreMidi { 26 | client: Rc, 27 | } 28 | 29 | impl CoreMidi { 30 | pub fn new(app_name: T) -> MidiResult 31 | where 32 | T: Into, 33 | { 34 | Client::new(app_name.into().as_str()) 35 | .map_err(|status| MidiError::Init { 36 | cause: format!("OSStatus={:?}", status), 37 | }) 38 | .map(|client| CoreMidi { 39 | client: Rc::new(client), 40 | }) 41 | } 42 | } 43 | 44 | impl MidiDriver for CoreMidi { 45 | fn id(&self) -> &str { 46 | ID 47 | } 48 | 49 | fn sources(&self) -> Vec> { 50 | Sources 51 | .into_iter() 52 | .enumerate() 53 | .map(|(index, source)| { 54 | let name = source 55 | .display_name() 56 | .unwrap_or_else(|| format!("source-{}", index)); 57 | let client = Rc::clone(&self.client); 58 | let source = Rc::new(source); 59 | Box::new(CoreMidiSource::new(name, client, source)) as Box 60 | }) 61 | .collect() 62 | } 63 | 64 | fn destinations(&self) -> Vec> { 65 | Destinations 66 | .into_iter() 67 | .enumerate() 68 | .map(|(index, destination)| { 69 | let name = destination 70 | .display_name() 71 | .unwrap_or_else(|| format!("destination-{}", index)); 72 | let client = Rc::clone(&self.client); 73 | let destination = Rc::new(destination); 74 | Box::new(CoreMidiDestination::new(name, client, destination)) as Box 75 | }) 76 | .collect() 77 | } 78 | 79 | // fn create_virtual_output(&self, name: T) -> dyn MidiOutput where T: Into { 80 | 81 | // } 82 | } 83 | 84 | pub struct CoreMidiSource { 85 | name: String, 86 | client: Rc, 87 | source: Rc, 88 | } 89 | 90 | impl CoreMidiSource { 91 | fn new(name: String, client: Rc, source: Rc) -> Self { 92 | CoreMidiSource { 93 | name, 94 | client, 95 | source, 96 | } 97 | } 98 | 99 | fn callback_proxy(packet_list: &PacketList, buffer: &mut Buffer, callback: &MidiSourceCallback) { 100 | buffer.reset(); 101 | for packet in packet_list.iter() { 102 | let nanos = unsafe { external::AudioConvertHostTimeToNanos(packet.timestamp()) }; 103 | let timestamp = ClockTime::from_nanos(nanos); 104 | for event in Decoder::new(packet.data()) { 105 | if let DecodedMessage::Message(msg) = event { 106 | buffer.push(timestamp, msg) 107 | } 108 | } 109 | } 110 | (callback)(&buffer) 111 | } 112 | } 113 | 114 | impl MidiSource for CoreMidiSource { 115 | fn name(&self) -> &str { 116 | self.name.as_str() 117 | } 118 | 119 | fn open(&self, callback: Box) -> Result, MidiError> { 120 | let mut buffer = Buffer::with_capacity(INPUT_BUFFER_CAPACITY); 121 | self 122 | .client 123 | .input_port(self.name.as_str(), move |packet_list: &PacketList| { 124 | Self::callback_proxy(packet_list, &mut buffer, &*callback) 125 | }) 126 | .and_then(|port| port.connect_source(&self.source).map(|_| port)) 127 | .map_err(|status| MidiError::SourceOpen { 128 | cause: format!("Source={:?}, OSStatus={:?}", self.name, status), 129 | }) 130 | .map(|port| { 131 | Box::new(CoreMidiInput::new( 132 | self.name.clone(), 133 | self.client.clone(), 134 | self.source.clone(), 135 | port, 136 | )) as Box 137 | }) 138 | } 139 | } 140 | 141 | struct CoreMidiInput { 142 | name: String, 143 | _client: Rc, 144 | _source: Rc, 145 | _port: InputPort, 146 | } 147 | 148 | impl CoreMidiInput { 149 | fn new(name: String, client: Rc, source: Rc, port: InputPort) -> CoreMidiInput { 150 | CoreMidiInput { 151 | name, 152 | _client: client, 153 | _source: source, 154 | _port: port, 155 | } 156 | } 157 | } 158 | 159 | impl MidiEndpoint for CoreMidiInput { 160 | fn name(&self) -> &str { 161 | self.name.as_str() 162 | } 163 | } 164 | 165 | impl MidiInput for CoreMidiInput {} 166 | 167 | pub struct CoreMidiDestination { 168 | name: String, 169 | client: Rc, 170 | destination: Rc, 171 | } 172 | 173 | impl CoreMidiDestination { 174 | fn new(name: String, client: Rc, destination: Rc) -> Self { 175 | CoreMidiDestination { 176 | name, 177 | client, 178 | destination, 179 | } 180 | } 181 | } 182 | 183 | impl MidiDestination for CoreMidiDestination { 184 | fn name(&self) -> &str { 185 | self.name.as_str() 186 | } 187 | 188 | fn open(&self) -> MidiResult> { 189 | self 190 | .client 191 | .output_port(self.name.as_str()) 192 | .map_err(|status| MidiError::DestinationOpen { 193 | cause: format!("Destination={:?}, OSStatus={:?}", self.name, status), 194 | }) 195 | .map(|port| { 196 | Box::new(CoreMidiOutput::new( 197 | self.name.clone(), 198 | self.client.clone(), 199 | self.destination.clone(), 200 | port, 201 | )) as Box 202 | }) 203 | } 204 | } 205 | 206 | struct CoreMidiOutput { 207 | name: String, 208 | _client: Rc, 209 | destination: Rc, 210 | port: OutputPort, 211 | message_buffer: [u8; OUTPUT_MESSAGE_CAPACITY], 212 | packet_buffer: PacketBuffer, 213 | } 214 | 215 | impl CoreMidiOutput { 216 | fn new( 217 | name: String, 218 | client: Rc, 219 | destination: Rc, 220 | port: OutputPort, 221 | ) -> CoreMidiOutput { 222 | let message_buffer = [0; OUTPUT_MESSAGE_CAPACITY]; 223 | let packet_buffer = PacketBuffer::with_capacity(OUTPUT_PACKET_BUFFER_CAPACITY); 224 | 225 | CoreMidiOutput { 226 | name, 227 | _client: client, 228 | destination, 229 | port, 230 | message_buffer, 231 | packet_buffer, 232 | } 233 | } 234 | } 235 | 236 | impl MidiEndpoint for CoreMidiOutput { 237 | fn name(&self) -> &str { 238 | self.name.as_str() 239 | } 240 | } 241 | 242 | impl MidiOutput for CoreMidiOutput { 243 | fn send(&mut self, base_time: ClockTime, buffer: &Buffer) { 244 | self.packet_buffer.clear(); 245 | let mut packet_buffer_size = 0; 246 | 247 | for event in buffer.iter() { 248 | let timestamp = base_time + event.timestamp; 249 | let data_size = Encoder::data_size(&event.message); 250 | if packet_buffer_size + data_size >= OUTPUT_PACKET_BUFFER_CAPACITY { 251 | let _ = self.port.send(&self.destination, &self.packet_buffer); 252 | self.packet_buffer.clear(); 253 | packet_buffer_size = 0; 254 | } 255 | let host_time = unsafe { external::AudioConvertNanosToHostTime(timestamp.to_nanos()) }; 256 | 257 | Encoder::encode(&event.message, &mut self.message_buffer); 258 | 259 | self 260 | .packet_buffer 261 | .push_data(host_time, &self.message_buffer[0..data_size]); 262 | packet_buffer_size += data_size; 263 | } 264 | let _ = self.port.send(&self.destination, &self.packet_buffer); 265 | } 266 | } 267 | 268 | mod external { 269 | #[link(name = "CoreAudio", kind = "framework")] 270 | extern "C" { 271 | pub fn AudioConvertNanosToHostTime(inNanos: u64) -> u64; 272 | pub fn AudioConvertHostTimeToNanos(inHostTime: u64) -> u64; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /app-server/src/midi/io/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::thread; 3 | use std::thread::JoinHandle; 4 | 5 | use failure::Fail; 6 | 7 | use log::{debug, error, info}; 8 | 9 | use crossbeam_channel::{Receiver, Sender}; 10 | use hero_studio_core::time::ClockTime; 11 | 12 | use hero_studio_core::config::{Audio as AudioConfig, Midi as MidiConfig}; 13 | use hero_studio_core::midi::buffer::{Buffer, Endpoint, EventIo}; 14 | 15 | use crate::controller::Protocol as StudioProtocol; 16 | use crate::midi::drivers::{MidiDriver, MidiDrivers, MidiOutput as MidiOutputPort, MidiInput as MidiInputPort}; 17 | use crate::midi::endpoints::{EndpointId, Endpoints}; 18 | use crate::realtime::RealTimeAudioPriority; 19 | 20 | 21 | #[derive(Debug, Fail)] 22 | pub enum MidiIoError { 23 | #[fail(display = "Failed to create the MIDI output thread: {}", cause)] 24 | Start { cause: String }, 25 | 26 | #[fail(display = "Failed to join the MIDI output thread")] 27 | Stop, 28 | } 29 | 30 | #[derive(Debug)] 31 | pub enum Protocol { 32 | Stop, 33 | 34 | EventOut(EventIo), 35 | 36 | EventIn(EventIo), 37 | } 38 | 39 | pub struct MidiIoThread { 40 | _driver: Box, 41 | endpoints_out: Endpoints, 42 | _endpoints_in: Endpoints, 43 | buffer: Option, 44 | _rta_priority: Option, 45 | } 46 | 47 | impl MidiIoThread { 48 | pub fn new( 49 | config: &MidiConfig, 50 | audio_config: &AudioConfig, 51 | midi_in_tx: Sender, 52 | studio_tx: Sender, 53 | ) -> MidiIoThread { 54 | let (driver, endpoints_out, endpoints_in) = Self::init_endpoints(config, midi_in_tx); 55 | 56 | drop(studio_tx.send(StudioProtocol::MidiInitialised)); 57 | 58 | let _rta_priority = 59 | RealTimeAudioPriority::promote(audio_config.sample_rate, audio_config.frames.into()).ok(); 60 | 61 | MidiIoThread { 62 | _driver: driver, 63 | endpoints_out, 64 | _endpoints_in: endpoints_in, 65 | buffer: Some(Buffer::with_capacity(1)), 66 | _rta_priority, 67 | } 68 | } 69 | 70 | pub fn handle_messages(&mut self, protocol_rx: Receiver) { 71 | info!("Handling MIDI output messages ..."); 72 | 73 | for message in protocol_rx.iter() { 74 | match message { 75 | Protocol::EventOut(event) => { 76 | self.send_event(event); 77 | } 78 | 79 | Protocol::Stop => { 80 | info!("MIDI output thread stopped ..."); 81 | break; 82 | } 83 | 84 | _ => unreachable!() 85 | } 86 | } 87 | } 88 | 89 | // FIXME Temporal solution until we can use crossbeam spsc array and read on chunks rather than individual events 90 | fn send_event(&mut self, event: EventIo) { 91 | let mut buffer = self.buffer.take().unwrap(); 92 | 93 | buffer.reset().push(event.timestamp, event.message); 94 | 95 | match event.endpoint { 96 | Endpoint::None => {} 97 | 98 | Endpoint::Default => { 99 | if let Some(endpoint) = self.endpoints_out.get_mut(0) { 100 | endpoint.send(ClockTime::zero(), &buffer) 101 | } 102 | } 103 | 104 | Endpoint::All => self 105 | .endpoints_out 106 | .iter_mut() 107 | .for_each(|endpoint| endpoint.send(ClockTime::zero(), &buffer)), 108 | 109 | Endpoint::Id(id) => { 110 | if let Some(endpoint) = self.endpoints_out.get_mut(id) { 111 | endpoint.send(ClockTime::zero(), &buffer) 112 | } 113 | } 114 | } 115 | 116 | self.buffer = Some(buffer); 117 | } 118 | 119 | // TODO This logic should go into another thread that will scan ports regularly and report back to this one 120 | fn update_endpoints_out( 121 | _config: &MidiConfig, 122 | driver: &MidiDriver, 123 | endpoints_out: &mut Endpoints, 124 | ) { 125 | let mut unvisited: HashSet = endpoints_out.ids().cloned().collect(); 126 | 127 | // TODO send the updates to the studio worker 128 | 129 | debug!("Updating output endpoints:"); 130 | for destination in driver.destinations() { 131 | let name = destination.name(); 132 | if let Some(id) = endpoints_out.get_id_from_name(&name) { 133 | unvisited.remove(&id); 134 | debug!("(=) {} [{}]", name, id); 135 | } else if let Ok(endpoint) = destination.open() { 136 | let id = endpoints_out.add(name, endpoint); 137 | debug!("(+) {} [{}]", name, id); 138 | } else { 139 | error!("Error opening MIDI output port: {}", name); 140 | } 141 | } 142 | endpoints_out.remove(unvisited, |name, id| debug!("(-) {} [{}]", name, id)); 143 | } 144 | 145 | fn update_endpoints_in( 146 | _config: &MidiConfig, 147 | driver: &MidiDriver, 148 | midi_in_tx: Sender, 149 | endpoints_in: &mut Endpoints, 150 | ) { 151 | let mut unvisited: HashSet = endpoints_in.ids().cloned().collect(); 152 | 153 | // TODO send the updates to the studio worker 154 | 155 | debug!("Updating input endpoints:"); 156 | 157 | for source in driver.sources() { 158 | let name = source.name(); 159 | let tx = midi_in_tx.clone(); 160 | 161 | if let Some(id) = endpoints_in.get_id_from_name(&name) { 162 | unvisited.remove(&id); 163 | debug!("(=) {} [{}]", name, id); 164 | } else { 165 | let id = endpoints_in.next_id(); 166 | 167 | let callback = Box::new(move |buffer: &Buffer| { 168 | for event in buffer.iter() { 169 | let endpoint = Endpoint::Id(id); 170 | let event_io = EventIo::new(event.timestamp, endpoint, event.message.clone()); 171 | drop(tx.send(Protocol::EventIn(event_io))); 172 | } 173 | }); 174 | 175 | if let Ok(endpoint) = source.open(callback) { 176 | endpoints_in.add(name, endpoint); 177 | debug!("(+) {} [{}]", name, id); 178 | } else { 179 | error!("Error opening MIDI input port: {}", name); 180 | } 181 | } 182 | } 183 | endpoints_in.remove(unvisited, |name, id| debug!("(-) {} [{}]", name, id)); 184 | } 185 | 186 | fn init_endpoints(config: &MidiConfig, midi_in_tx: Sender) -> (Box, Endpoints, Endpoints) { 187 | info!("Initialising MIDI IO ..."); 188 | 189 | let drivers = MidiDrivers::new(); 190 | let app_name = "hero-studio"; // TODO from app_config ? 191 | let driver = drivers 192 | .driver(config.driver_id.clone(), app_name) 193 | .or_else(|_| drivers.default(app_name)) 194 | .unwrap(); // FIXME maybe we need a thread supervisor ? 195 | 196 | debug!("MIDI Driver: {}", driver.id()); 197 | 198 | let mut endpoints_out = Endpoints::new(); 199 | Self::update_endpoints_out(config, driver.as_ref(), &mut endpoints_out); 200 | 201 | let mut endpoints_in = Endpoints::new(); 202 | Self::update_endpoints_in(config, driver.as_ref(), midi_in_tx, &mut endpoints_in); 203 | 204 | (driver, endpoints_out, endpoints_in) 205 | } 206 | } 207 | 208 | pub struct MidiIo { 209 | handler: JoinHandle<()>, 210 | midi_out_tx: Sender, 211 | } 212 | 213 | impl MidiIo { 214 | // TODO Use an spsc array when published by crossbeam 215 | pub const CHANNEL_CAPACITY: usize = 128 * 1024; 216 | pub fn new_channel() -> (Sender, Receiver) { 217 | crossbeam_channel::bounded::(Self::CHANNEL_CAPACITY) 218 | } 219 | 220 | pub fn new( 221 | config: &MidiConfig, 222 | audio_config: &AudioConfig, 223 | midi_out_tx: Sender, 224 | midi_out_rx: Receiver, 225 | midi_in_tx: Sender, 226 | studio_tx: Sender, 227 | ) -> Result { 228 | info!("Spawning MIDI IO thread ..."); 229 | 230 | let cloned_config = config.clone(); 231 | let cloned_audio_config = audio_config.clone(); 232 | 233 | thread::Builder::new() 234 | .name("midi-io".into()) 235 | .spawn(move || { 236 | MidiIoThread::new(&cloned_config, &cloned_audio_config, midi_in_tx, studio_tx) 237 | .handle_messages(midi_out_rx) 238 | }) 239 | .map_err(|err| MidiIoError::Start { 240 | cause: err.to_string(), 241 | }) 242 | .map(|handler| MidiIo { 243 | handler, 244 | midi_out_tx, 245 | }) 246 | } 247 | 248 | pub fn stop(self) -> Result<(), MidiIoError> { 249 | info!("Stopping MIDI IO thread ..."); 250 | 251 | self 252 | .midi_out_tx 253 | .send(Protocol::Stop) 254 | .map_err(|_| MidiIoError::Stop) 255 | .and_then(|()| self.handler.join().map_err(|_| MidiIoError::Stop)) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /app-server/src/midi/drivers/portmidi.rs: -------------------------------------------------------------------------------- 1 | // use log::{debug}; 2 | 3 | use std::rc::Rc; 4 | use std::sync::Arc; 5 | use std::thread::JoinHandle; 6 | use std::time::Duration; 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | 9 | use portmidi::{DeviceInfo, InputPort, MidiEvent, MidiMessage, OutputPort, PortMidi}; 10 | 11 | use hero_studio_core::time::ClockTime; 12 | use hero_studio_core::midi::buffer::Buffer; 13 | use hero_studio_core::midi::{encoder::Encoder, messages::Message}; 14 | use hero_studio_core::midi::decoder::{DecodedMessage, Decoder}; 15 | 16 | use super::{ 17 | MidiDestination, MidiDriver, MidiEndpoint, MidiError, MidiInput, MidiOutput, MidiResult, 18 | MidiSource, 19 | }; 20 | use crate::midi::drivers::MidiSourceCallback; 21 | 22 | 23 | pub const ID: &str = "PortMIDI"; 24 | 25 | const MIDI_BUF_LEN: usize = 8 * 1024; 26 | 27 | const POLL_MAX_WAIT_NANOS: u64 = 1_000_000; // 1 ms 28 | 29 | const INPUT_BUFFER_CAPACITY: usize = 16 * 1024; 30 | 31 | pub struct PortMidiDriver { 32 | context: Rc, 33 | } 34 | 35 | // impl Drop for PortMidiDriver { 36 | // fn drop(&mut self) { 37 | // println!("PortMidiDriver::Drop"); 38 | // } 39 | // } 40 | 41 | impl PortMidiDriver { 42 | pub fn new() -> MidiResult { 43 | PortMidi::new() 44 | .map_err(|err| MidiError::Init { 45 | cause: format!("{:?}", err), 46 | }) 47 | .map(|context| PortMidiDriver { 48 | context: Rc::new(context), 49 | }) 50 | } 51 | } 52 | 53 | impl MidiDriver for PortMidiDriver { 54 | fn id(&self) -> &str { 55 | ID 56 | } 57 | 58 | fn sources(&self) -> Vec> { 59 | self 60 | .context 61 | .devices() 62 | .into_iter() 63 | .flat_map(|devices| { 64 | devices 65 | .into_iter() 66 | .filter(DeviceInfo::is_input) 67 | .map(|device| { 68 | Box::new(PortMidiSource { 69 | name: device.name().clone(), 70 | context: Rc::clone(&self.context), 71 | device: device.clone(), 72 | }) as Box 73 | }) 74 | }) 75 | .collect() 76 | } 77 | 78 | fn destinations(&self) -> Vec> { 79 | self 80 | .context 81 | .devices() 82 | .into_iter() 83 | .flat_map(|devices| { 84 | devices 85 | .into_iter() 86 | .filter(DeviceInfo::is_output) 87 | .map(|device| { 88 | Box::new(PortMidiDestination { 89 | name: device.name().clone(), 90 | context: Rc::clone(&self.context), 91 | device: device.clone(), 92 | }) as Box 93 | }) 94 | }) 95 | .collect() 96 | } 97 | } 98 | 99 | pub struct PortMidiSource { 100 | name: String, 101 | context: Rc, 102 | device: DeviceInfo, 103 | } 104 | 105 | impl MidiSource for PortMidiSource { 106 | fn name(&self) -> &str { 107 | self.name.as_str() 108 | } 109 | 110 | fn open(&self, callback: Box) -> Result, MidiError> { 111 | self 112 | .context 113 | .input_port(self.device.clone(), MIDI_BUF_LEN) 114 | .map_err(|err| MidiError::SourceOpen { 115 | cause: format!("Device={:?}, Error={:?}", self.name, err), 116 | }) 117 | .map(|port| { 118 | Box::new(PortMidiInput::new( 119 | self.name.clone(), 120 | self.context.clone(), 121 | port, 122 | callback, 123 | )) as Box 124 | }) 125 | } 126 | } 127 | 128 | struct PortMidiInput { 129 | name: String, 130 | _context: Rc, 131 | handler: Option>, 132 | done: Arc, 133 | } 134 | 135 | impl PortMidiInput { 136 | fn new( 137 | name: String, 138 | context: Rc, 139 | port: InputPort, 140 | callback: Box, 141 | ) -> PortMidiInput { 142 | let done = Arc::new(AtomicBool::new(false)); 143 | let done_clone = Arc::clone(&done); 144 | 145 | let thread_name = format!("portmidi-{}", name); 146 | let handler = std::thread::Builder::new() 147 | .name(thread_name) 148 | .spawn(|| Self::poll(port, callback, done_clone)) 149 | .ok(); 150 | 151 | PortMidiInput { 152 | name, 153 | _context: context, 154 | handler, 155 | done, 156 | } 157 | } 158 | 159 | fn poll(port: InputPort, callback: Box, done: Arc) { 160 | let mut wait_nanos: u64 = 1; 161 | let mut buffer = Buffer::with_capacity(INPUT_BUFFER_CAPACITY); 162 | while !done.load(Ordering::Relaxed) { 163 | if let Ok(events_available) = port.poll() { 164 | if events_available { 165 | buffer.reset(); 166 | if let Ok(Some(events)) = port.read_n(MIDI_BUF_LEN) { 167 | for event in events.into_iter() { 168 | let raw_msg = event.message; 169 | let data = [raw_msg.status, raw_msg.data1, raw_msg.data2]; 170 | if let Some(DecodedMessage::Message(message)) = Decoder::new(&data).next() { 171 | let timestamp = ClockTime::from_millis(u64::from(event.timestamp)); 172 | buffer.push(timestamp, message) 173 | } 174 | } 175 | (callback)(&buffer); 176 | wait_nanos = 1; 177 | } 178 | } 179 | } 180 | 181 | std::thread::sleep(Duration::from_nanos(wait_nanos)); 182 | wait_nanos = POLL_MAX_WAIT_NANOS.min(wait_nanos * 2); 183 | } 184 | } 185 | } 186 | 187 | impl MidiEndpoint for PortMidiInput { 188 | fn name(&self) -> &str { 189 | self.name.as_str() 190 | } 191 | } 192 | 193 | impl MidiInput for PortMidiInput {} 194 | 195 | impl Drop for PortMidiInput { 196 | fn drop(&mut self) { 197 | self.done.store(true, Ordering::Relaxed); 198 | self.handler.take().into_iter().for_each(|handler| { 199 | let _ = handler.join(); 200 | }) 201 | } 202 | } 203 | 204 | pub struct PortMidiDestination { 205 | name: String, 206 | context: Rc, 207 | device: DeviceInfo, 208 | } 209 | 210 | // impl Drop for PortMidiDestination { 211 | // fn drop(&mut self) { 212 | // println!("PortMidiDestination::Drop"); 213 | // } 214 | // } 215 | 216 | impl MidiDestination for PortMidiDestination { 217 | fn name(&self) -> &str { 218 | self.name.as_str() 219 | } 220 | 221 | fn open(&self) -> MidiResult> { 222 | self 223 | .context 224 | .output_port(self.device.clone(), MIDI_BUF_LEN) 225 | .map_err(|err| MidiError::DestinationOpen { 226 | cause: format!("Device={:?}, Error={:?}", self.name, err), 227 | }) 228 | .map(|port| { 229 | Box::new(PortMidiOutput::new( 230 | self.name.clone(), 231 | self.context.clone(), 232 | port, 233 | )) as Box 234 | }) 235 | } 236 | } 237 | 238 | const MESSAGE_BUFFER_CAPACITY: usize = 8; 239 | 240 | struct PortMidiOutput { 241 | name: String, 242 | _context: Rc, 243 | port: OutputPort, 244 | message_buffer: [u8; MESSAGE_BUFFER_CAPACITY], 245 | } 246 | 247 | // impl Drop for OutputBusNode { 248 | // fn drop(&mut self) { 249 | // println!("OutputBusNode::Drop"); 250 | // } 251 | // } 252 | 253 | impl PortMidiOutput { 254 | fn new(name: String, context: Rc, port: OutputPort) -> PortMidiOutput { 255 | PortMidiOutput { 256 | name, 257 | _context: context, 258 | port, 259 | message_buffer: [0; MESSAGE_BUFFER_CAPACITY], 260 | } 261 | } 262 | 263 | fn send_message(&mut self, time: ClockTime, msg: &Message) { 264 | // trace!(">>> {:?} {:?}", time, msg); 265 | let timestamp = (time.to_nanos() / 1000) as u32; 266 | let data_size = Encoder::data_size(msg); 267 | 268 | Encoder::encode(msg, &mut self.message_buffer); 269 | 270 | let message = match data_size { 271 | 1 => MidiMessage { 272 | status: self.message_buffer[0], 273 | data1: 0, 274 | data2: 0, 275 | }, 276 | 2 => MidiMessage { 277 | status: self.message_buffer[0], 278 | data1: self.message_buffer[1], 279 | data2: 0, 280 | }, 281 | 3 => MidiMessage { 282 | status: self.message_buffer[0], 283 | data1: self.message_buffer[1], 284 | data2: self.message_buffer[2], 285 | }, 286 | _ => unreachable!(), 287 | }; 288 | 289 | let event = MidiEvent { message, timestamp }; 290 | let _ = self.port.write_event(event); 291 | } 292 | 293 | // fn send_sysex_message(&mut self, time: ClockTime, msg: &[U7]) { 294 | // // trace!(">>> {:?} {:?}", time, msg); 295 | // let timestamp = (time.to_nanos() / 1000) as u32; 296 | // let data_size = Encoder::sysex_data_size(msg); 297 | // let mut data = Vec::with_capacity(data_size); 298 | // unsafe { data.set_len(data_size) }; 299 | // let slice = data.as_mut_slice(); 300 | // Encoder::sysex_encode(msg, slice); 301 | // self 302 | // .port 303 | // .write_sysex(timestamp, data.as_slice()) 304 | // .unwrap_or(()); 305 | // } 306 | } 307 | 308 | impl MidiEndpoint for PortMidiOutput { 309 | fn name(&self) -> &str { 310 | self.name.as_str() 311 | } 312 | } 313 | 314 | impl MidiOutput for PortMidiOutput { 315 | fn send(&mut self, base_time: ClockTime, buffer: &Buffer) { 316 | for event in buffer.iter() { 317 | self.send_message(base_time + event.timestamp, &event.message) 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /core/src/transport.rs: -------------------------------------------------------------------------------- 1 | use crate::time::{ 2 | drift_correction::ClockDriftCorrection, drift_correction::TicksDriftCorrection, BarsTime, 3 | ClockTime, SampleRate, Signature, Tempo, TicksTime, 4 | }; 5 | 6 | const DEFAULT_TEMPO: u16 = 120; 7 | const DEFAULT_SIGNATURE_NUM_BEATS: u8 = 4; 8 | const DEFAULT_SIGNATURE_NOTE_VALUE: u8 = 4; 9 | 10 | pub struct Transport { 11 | sample_rate: SampleRate, 12 | signature: Signature, 13 | tempo: Tempo, 14 | 15 | playing: bool, 16 | 17 | next_play_duration: TicksTime, 18 | 19 | start_position: TicksTime, 20 | current_position: TicksTime, 21 | next_position: TicksTime, 22 | time_drift_correction: TicksDriftCorrection, 23 | 24 | loop_enabled: bool, 25 | loop_start: TicksTime, 26 | loop_end: TicksTime, 27 | } 28 | 29 | impl Transport { 30 | pub fn new(sample_rate: SampleRate) -> Self { 31 | let signature = Signature::new(DEFAULT_SIGNATURE_NUM_BEATS, DEFAULT_SIGNATURE_NOTE_VALUE); 32 | let tempo = Tempo::new(DEFAULT_TEMPO); 33 | let mut transport = Transport { 34 | sample_rate, 35 | signature, 36 | tempo, 37 | 38 | playing: false, 39 | 40 | next_play_duration: TicksTime::zero(), 41 | 42 | start_position: TicksTime::zero(), 43 | current_position: TicksTime::zero(), 44 | next_position: TicksTime::zero(), 45 | time_drift_correction: TicksDriftCorrection::new(signature, tempo, sample_rate), 46 | 47 | loop_enabled: true, 48 | loop_start: TicksTime::zero(), 49 | loop_end: TicksTime::zero(), 50 | }; 51 | transport.update_timing_constants(); 52 | transport 53 | } 54 | 55 | pub fn set_sample_rate(&mut self, sample_rate: SampleRate) { 56 | self.sample_rate = sample_rate; 57 | self.update_timing_constants(); 58 | } 59 | 60 | pub fn get_sample_rate(&self) -> &SampleRate { 61 | &self.sample_rate 62 | } 63 | 64 | pub fn set_signature(&mut self, signature: Signature) { 65 | self.signature = signature; 66 | } 67 | 68 | pub fn get_signature(&self) -> &Signature { 69 | &self.signature 70 | } 71 | 72 | pub fn set_tempo(&mut self, tempo: Tempo) { 73 | self.tempo = tempo; 74 | } 75 | 76 | pub fn get_tempo(&self) -> &Tempo { 77 | &self.tempo 78 | } 79 | 80 | pub fn is_playing(&self) -> bool { 81 | self.playing 82 | } 83 | 84 | pub fn play(&mut self, restart: bool) -> bool { 85 | self.playing = !self.playing; 86 | if restart { 87 | self.reset_position(); 88 | } 89 | self.playing 90 | } 91 | 92 | pub fn stop(&mut self) { 93 | if !self.playing { 94 | self.reset_position(); 95 | } 96 | self.playing = false; 97 | } 98 | 99 | fn reset_position(&mut self) { 100 | self.next_play_duration = TicksTime::zero(); 101 | self.current_position = self.start_position; 102 | self.next_position = self.current_position; 103 | } 104 | 105 | pub fn set_position(&mut self, position: BarsTime) { 106 | self.current_position = position.to_ticks(self.signature); 107 | self.next_position = self.current_position; 108 | } 109 | 110 | pub fn get_position(&self) -> BarsTime { 111 | BarsTime::from_ticks(self.current_position, self.signature) 112 | } 113 | 114 | pub fn set_loop_enabled(&mut self, enabled: bool) { 115 | self.loop_enabled = enabled; 116 | } 117 | 118 | pub fn is_loop_enabled(&self) -> bool { 119 | self.loop_enabled 120 | } 121 | 122 | pub fn set_loop_start(&mut self, position: BarsTime) { 123 | self.loop_start = position.to_ticks(self.signature); 124 | } 125 | 126 | pub fn get_loop_start(&self) -> BarsTime { 127 | BarsTime::from_ticks(self.loop_start, self.signature) 128 | } 129 | 130 | pub fn set_loop_end(&mut self, position: BarsTime) { 131 | self.loop_end = position.to_ticks(self.signature); 132 | } 133 | 134 | pub fn get_loop_end(&self) -> BarsTime { 135 | BarsTime::from_ticks(self.loop_end, self.signature) 136 | } 137 | 138 | pub(super) fn segments_iterator( 139 | &self, 140 | master_clock: ClockTime, 141 | samples: u32, 142 | ) -> SegmentsIterator { 143 | SegmentsIterator::new( 144 | samples, 145 | self, 146 | master_clock, 147 | self.next_play_duration, 148 | self.next_position, 149 | &self.time_drift_correction, 150 | ) 151 | } 152 | 153 | pub(super) fn update_from_segments(&mut self, segments: &SegmentsIterator) { 154 | self.next_play_duration = segments.next_play_duration; 155 | self.current_position = segments.next_position; 156 | self.next_position = segments.next_position; 157 | self.time_drift_correction = segments.time_drift_correction.clone(); 158 | // println!("))))))))> {:#?}", self.time_drift_correction); 159 | } 160 | 161 | ///! Update timing constants that change sporadically (ex. changes on sample rate, tempo, signature, ...) 162 | fn update_timing_constants(&mut self) { 163 | self.time_drift_correction = 164 | TicksDriftCorrection::new(self.signature, self.tempo, self.sample_rate); 165 | println!( 166 | "Ticks error per sample = {:?} ticks", 167 | self.time_drift_correction.get_error_per_sample() 168 | ); 169 | } 170 | 171 | ///! Determine whether or not not to move the song position to the start of the loop 172 | fn crossing_loop_end(&self, prev_ticks: TicksTime, next_position: TicksTime) -> bool { 173 | self.loop_enabled && prev_ticks < self.loop_end && self.loop_end <= next_position 174 | } 175 | } 176 | 177 | pub struct SegmentsIterator { 178 | master_clock: ClockTime, 179 | next_master_clock: ClockTime, 180 | 181 | play_duration: TicksTime, 182 | next_play_duration: TicksTime, 183 | 184 | current_position: TicksTime, 185 | next_position: TicksTime, 186 | 187 | remaining_duration: TicksTime, 188 | time_drift_correction: TicksDriftCorrection, 189 | } 190 | 191 | impl SegmentsIterator { 192 | fn new( 193 | samples: u32, 194 | _transport: &Transport, 195 | next_master_clock: ClockTime, 196 | next_play_duration: TicksTime, 197 | next_position: TicksTime, 198 | time_drift_correction: &TicksDriftCorrection, 199 | ) -> SegmentsIterator { 200 | let mut time_drift_correction = time_drift_correction.clone(); 201 | 202 | // println!( 203 | // "[{:?}] <{:010?}> Err: {:?} Correction {:?} ({:010?}) A {:010?}", 204 | // BarsTime::from_ticks(next_position, _transport.signature), 205 | // u64::from(next_position), 206 | // time_drift_correction.get_error_accumulated(), 207 | // time_drift_correction.get_last_correction(), 208 | // u64::from(next_play_duration), 209 | // next_master_clock.units() 210 | // ); 211 | 212 | let remaining_duration = time_drift_correction.next(samples); 213 | 214 | SegmentsIterator { 215 | master_clock: next_master_clock, 216 | next_master_clock, 217 | play_duration: next_play_duration, 218 | next_play_duration, 219 | current_position: next_position, 220 | next_position, 221 | remaining_duration, 222 | time_drift_correction, 223 | } 224 | } 225 | 226 | pub fn next(&mut self, transport: &Transport) -> Option { 227 | self.master_clock = self.next_master_clock; 228 | self.play_duration = self.next_play_duration; 229 | self.current_position = self.next_position; 230 | 231 | if self.remaining_duration > TicksTime::zero() { 232 | let end_position = self.current_position + self.remaining_duration; 233 | 234 | if transport.crossing_loop_end(self.current_position, end_position) { 235 | self.remaining_duration = end_position - transport.loop_end; 236 | let end_position = transport.loop_end; 237 | self.next_position = transport.loop_start; 238 | let segment_duration = end_position - self.current_position; 239 | self.next_play_duration = self.play_duration + segment_duration; 240 | let segment = Segment::new( 241 | transport.sample_rate, 242 | transport.signature, 243 | transport.tempo, 244 | self.master_clock, 245 | self.current_position, 246 | end_position, 247 | segment_duration, 248 | self.play_duration, 249 | ); 250 | self.next_master_clock = self.master_clock + segment.clock_duration; 251 | Some(segment) 252 | } else { 253 | self.next_position = end_position; 254 | let segment_duration = self.remaining_duration; 255 | self.remaining_duration = TicksTime::zero(); 256 | self.next_play_duration = self.play_duration + segment_duration; 257 | let segment = Segment::new( 258 | transport.sample_rate, 259 | transport.signature, 260 | transport.tempo, 261 | self.master_clock, 262 | self.current_position, 263 | end_position, 264 | segment_duration, 265 | self.play_duration, 266 | ); 267 | self.next_master_clock = self.master_clock + segment.clock_duration; 268 | Some(segment) 269 | } 270 | } else { 271 | None 272 | } 273 | } 274 | } 275 | 276 | pub struct Segment { 277 | pub(super) sample_rate: SampleRate, 278 | pub(super) signature: Signature, 279 | pub(super) tempo: Tempo, 280 | 281 | pub(super) master_clock: ClockTime, 282 | 283 | pub(super) start_position: TicksTime, 284 | pub(super) end_position: TicksTime, 285 | pub(super) duration: TicksTime, 286 | 287 | pub(super) clock_start_position: ClockTime, 288 | pub(super) clock_end_position: ClockTime, 289 | pub(super) clock_duration: ClockTime, 290 | 291 | pub(super) play_duration: TicksTime, 292 | pub(super) clock_play_duration: ClockTime, 293 | } 294 | 295 | impl Segment { 296 | #[allow(clippy::too_many_arguments)] 297 | pub fn new( 298 | sample_rate: SampleRate, 299 | signature: Signature, 300 | tempo: Tempo, 301 | master_clock: ClockTime, 302 | start_position: TicksTime, 303 | end_position: TicksTime, 304 | duration: TicksTime, 305 | play_duration: TicksTime, 306 | ) -> Segment { 307 | Segment { 308 | sample_rate, 309 | signature, 310 | tempo, 311 | master_clock, 312 | start_position, 313 | end_position, 314 | duration, 315 | play_duration, 316 | clock_start_position: start_position.to_clock(signature, tempo), 317 | clock_end_position: end_position.to_clock(signature, tempo), 318 | clock_duration: duration.to_clock(signature, tempo), 319 | clock_play_duration: play_duration.to_clock(signature, tempo), 320 | } 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /core/src/song/source/notes.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, HashMap}; 2 | 3 | use crate::{song::clips::ClipId, time::TicksTime}; 4 | 5 | type Key = u8; 6 | 7 | #[derive(Debug, PartialEq, Clone, Copy)] 8 | pub struct Note { 9 | key: Key, 10 | velocity: f64, 11 | start: TicksTime, 12 | length: TicksTime, 13 | } 14 | 15 | #[derive(Debug, PartialEq, Clone, Copy)] 16 | pub enum NoteEvent { 17 | NoteStart { 18 | key: Key, 19 | velocity: f64, 20 | end: TicksTime, 21 | }, 22 | 23 | NoteEnd { 24 | key: Key, 25 | velocity: f64, 26 | start: TicksTime, 27 | }, 28 | } 29 | 30 | type NoteEvents = Vec; 31 | 32 | pub struct NotesClip { 33 | events: BTreeMap, 34 | } 35 | 36 | impl Default for NotesClip { 37 | fn default() -> Self { 38 | NotesClip { 39 | events: BTreeMap::new(), 40 | } 41 | } 42 | } 43 | 44 | impl NotesClip { 45 | pub fn new() -> NotesClip { 46 | NotesClip::default() 47 | } 48 | 49 | pub fn add_note(&mut self, note: Note) -> &mut Self { 50 | let (note_start_event, note_end_event, note_end) = self.split_note_into_events(¬e); 51 | self.insert_note_event(note.start, note_start_event); 52 | self.insert_note_event(note_end, note_end_event); 53 | self 54 | } 55 | 56 | pub fn add_notes(&mut self, notes: Vec) -> &mut Self { 57 | notes.iter().for_each(|note| { 58 | self.add_note(*note); 59 | }); 60 | self 61 | } 62 | 63 | pub fn remove_note(&mut self, note: Note) -> &mut Self { 64 | let (note_start_event, note_end_event, note_end) = self.split_note_into_events(¬e); 65 | self.remove_note_event(note.start, ¬e_start_event); 66 | self.remove_note_event(note_end, ¬e_end_event); 67 | self 68 | } 69 | 70 | pub fn notes_range<'a>( 71 | &'a self, 72 | range_start: TicksTime, 73 | range_end: TicksTime, 74 | ) -> impl Iterator + 'a { 75 | self 76 | .events 77 | .range(range_start..range_end) 78 | .flat_map(move |(tick, tick_events)| { 79 | tick_events.iter().flat_map(move |event| match *event { 80 | NoteEvent::NoteStart { key, velocity, end } => Some(Note { 81 | key, 82 | velocity, 83 | start: *tick, 84 | length: end - *tick, 85 | }), 86 | NoteEvent::NoteEnd { 87 | key, 88 | velocity, 89 | start, 90 | } => Some(Note { 91 | key, 92 | velocity, 93 | start, 94 | length: *tick - start, 95 | }) 96 | .filter(|_event| start < range_start), 97 | }) 98 | }) 99 | } 100 | 101 | pub fn events_range<'a>( 102 | &'a self, 103 | start: TicksTime, 104 | end: TicksTime, 105 | ) -> impl Iterator + 'a { 106 | self 107 | .events 108 | .range(start..end) 109 | .flat_map(|(_tick, tick_events)| tick_events.iter().map(move |event| event)) 110 | } 111 | 112 | fn split_note_into_events(&self, note: &Note) -> (NoteEvent, NoteEvent, TicksTime) { 113 | let note_end = note.start + note.length; 114 | 115 | let note_start_event = NoteEvent::NoteStart { 116 | key: note.key, 117 | velocity: note.velocity, 118 | end: note_end, 119 | }; 120 | 121 | let note_end_event = NoteEvent::NoteEnd { 122 | key: note.key, 123 | velocity: note.velocity, 124 | start: note.start, 125 | }; 126 | 127 | (note_start_event, note_end_event, note_end) 128 | } 129 | 130 | fn insert_note_event(&mut self, tick: TicksTime, event: NoteEvent) { 131 | self 132 | .events 133 | .entry(tick) 134 | .and_modify(|tick_events| tick_events.push(event)) 135 | .or_insert_with(|| vec![event]); 136 | } 137 | 138 | fn remove_note_event(&mut self, tick: TicksTime, event: &NoteEvent) { 139 | if let Some(tick_events) = self.events.get_mut(&tick) { 140 | if let Some(index) = tick_events 141 | .iter() 142 | .position(|prev_event| prev_event == event) 143 | { 144 | tick_events.swap_remove(index); 145 | if tick_events.is_empty() { 146 | self.events.remove(&tick); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | pub struct NotesSource { 154 | clips: HashMap, 155 | } 156 | 157 | #[cfg(test)] 158 | mod test { 159 | 160 | use super::{BTreeMap, Note, NoteEvent, NoteEvents, NotesClip, TicksTime}; 161 | 162 | #[test] 163 | /// NotesClip should add notes as events and allow repeated notes 164 | pub fn notes_clip_add_note() { 165 | let mut clip = NotesClip::new(); 166 | let note1 = Note { 167 | key: 24, 168 | velocity: 1.0, 169 | start: TicksTime::new(1), 170 | length: TicksTime::new(2), 171 | }; 172 | let note2 = Note { 173 | key: 25, 174 | velocity: 1.0, 175 | start: TicksTime::new(2), 176 | length: TicksTime::new(2), 177 | }; 178 | let note3 = Note { 179 | key: 25, 180 | velocity: 1.0, 181 | start: TicksTime::new(2), 182 | length: TicksTime::new(2), 183 | }; 184 | clip.add_note(note1); 185 | clip.add_note(note2); 186 | clip.add_note(note3); 187 | 188 | let mut expected_events: BTreeMap = BTreeMap::new(); 189 | expected_events.insert( 190 | TicksTime::new(1), 191 | vec![NoteEvent::NoteStart { 192 | key: 24, 193 | velocity: 1.0, 194 | end: TicksTime::new(3), 195 | }], 196 | ); 197 | expected_events.insert( 198 | TicksTime::new(2), 199 | vec![ 200 | NoteEvent::NoteStart { 201 | key: 25, 202 | velocity: 1.0, 203 | end: TicksTime::new(4), 204 | }, 205 | NoteEvent::NoteStart { 206 | key: 25, 207 | velocity: 1.0, 208 | end: TicksTime::new(4), 209 | }, 210 | ], 211 | ); 212 | expected_events.insert( 213 | TicksTime::new(3), 214 | vec![NoteEvent::NoteEnd { 215 | key: 24, 216 | velocity: 1.0, 217 | start: TicksTime::new(1), 218 | }], 219 | ); 220 | expected_events.insert( 221 | TicksTime::new(4), 222 | vec![ 223 | NoteEvent::NoteEnd { 224 | key: 25, 225 | velocity: 1.0, 226 | start: TicksTime::new(2), 227 | }, 228 | NoteEvent::NoteEnd { 229 | key: 25, 230 | velocity: 1.0, 231 | start: TicksTime::new(2), 232 | }, 233 | ], 234 | ); 235 | 236 | assert_eq!(clip.events, expected_events) 237 | } 238 | 239 | #[test] 240 | /// NotesClip should remove events when removing notes 241 | pub fn notes_clip_remove_note() { 242 | let mut clip = NotesClip::new(); 243 | let note1 = Note { 244 | key: 24, 245 | velocity: 1.0, 246 | start: TicksTime::new(1), 247 | length: TicksTime::new(2), 248 | }; 249 | let note2 = Note { 250 | key: 25, 251 | velocity: 1.0, 252 | start: TicksTime::new(2), 253 | length: TicksTime::new(2), 254 | }; 255 | let note3 = Note { 256 | key: 25, 257 | velocity: 1.0, 258 | start: TicksTime::new(2), 259 | length: TicksTime::new(2), 260 | }; 261 | clip.add_notes(vec![note1, note2, note3]); 262 | 263 | clip.remove_note(note3); 264 | clip.remove_note(note1); 265 | 266 | let mut expected_events: BTreeMap = BTreeMap::new(); 267 | expected_events.insert( 268 | TicksTime::new(2), 269 | vec![NoteEvent::NoteStart { 270 | key: 25, 271 | velocity: 1.0, 272 | end: TicksTime::new(4), 273 | }], 274 | ); 275 | expected_events.insert( 276 | TicksTime::new(4), 277 | vec![NoteEvent::NoteEnd { 278 | key: 25, 279 | velocity: 1.0, 280 | start: TicksTime::new(2), 281 | }], 282 | ); 283 | 284 | assert_eq!(clip.events, expected_events) 285 | } 286 | 287 | #[test] 288 | /// NotesClip should iterate notes over a range of ticks 289 | pub fn notes_clip_notes_range() { 290 | let mut clip = NotesClip::new(); 291 | let note1 = Note { 292 | key: 21, 293 | velocity: 1.0, 294 | start: TicksTime::new(0), 295 | length: TicksTime::new(1), 296 | }; 297 | let note2 = Note { 298 | key: 22, 299 | velocity: 1.0, 300 | start: TicksTime::new(1), 301 | length: TicksTime::new(2), 302 | }; 303 | let note3 = Note { 304 | key: 23, 305 | velocity: 1.0, 306 | start: TicksTime::new(2), 307 | length: TicksTime::new(2), 308 | }; 309 | let note4 = Note { 310 | key: 24, 311 | velocity: 1.0, 312 | start: TicksTime::new(1), 313 | length: TicksTime::new(4), 314 | }; 315 | let note5 = Note { 316 | key: 24, 317 | velocity: 1.0, 318 | start: TicksTime::new(4), 319 | length: TicksTime::new(3), 320 | }; 321 | clip.add_notes(vec![note1, note2, note3, note4, note5]); 322 | 323 | let range_result: Vec = clip 324 | .notes_range(TicksTime::new(2), TicksTime::new(5)) 325 | .collect(); 326 | assert_eq!(range_result, vec![note3, note2, note5]) 327 | } 328 | 329 | #[test] 330 | /// NotesClip should add notes as events and allow repeated notes 331 | pub fn notes_clip_events_range() { 332 | let mut clip = NotesClip::new(); 333 | let note1 = Note { 334 | key: 21, 335 | velocity: 1.0, 336 | start: TicksTime::new(0), 337 | length: TicksTime::new(1), 338 | }; 339 | let note2 = Note { 340 | key: 22, 341 | velocity: 1.0, 342 | start: TicksTime::new(1), 343 | length: TicksTime::new(2), 344 | }; 345 | let note3 = Note { 346 | key: 23, 347 | velocity: 1.0, 348 | start: TicksTime::new(2), 349 | length: TicksTime::new(2), 350 | }; 351 | let note4 = Note { 352 | key: 24, 353 | velocity: 1.0, 354 | start: TicksTime::new(1), 355 | length: TicksTime::new(4), 356 | }; 357 | let note5 = Note { 358 | key: 24, 359 | velocity: 1.0, 360 | start: TicksTime::new(4), 361 | length: TicksTime::new(3), 362 | }; 363 | clip.add_notes(vec![note1, note2, note3, note4, note5]); 364 | 365 | let event1 = NoteEvent::NoteStart { 366 | key: 23, 367 | velocity: 1.0, 368 | end: TicksTime::new(4), 369 | }; 370 | let event2 = NoteEvent::NoteEnd { 371 | key: 22, 372 | velocity: 1.0, 373 | start: TicksTime::new(1), 374 | }; 375 | let event3 = NoteEvent::NoteEnd { 376 | key: 23, 377 | velocity: 1.0, 378 | start: TicksTime::new(2), 379 | }; 380 | let event4 = NoteEvent::NoteStart { 381 | key: 24, 382 | velocity: 1.0, 383 | end: TicksTime::new(7), 384 | }; 385 | let expected_events = vec![&event1, &event2, &event3, &event4]; 386 | 387 | let range_result: Vec<&NoteEvent> = clip 388 | .events_range(TicksTime::new(2), TicksTime::new(5)) 389 | .collect(); 390 | assert_eq!(range_result, expected_events) 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /app-server/src/server.rs: -------------------------------------------------------------------------------- 1 | // FIXME Remove when the code gets more stable 2 | #![allow(unused_imports)] 3 | #![allow(dead_code)] 4 | 5 | use log::{debug, info, trace}; 6 | 7 | use std::collections::HashMap; 8 | use std::net::{SocketAddr, TcpStream}; 9 | use std::thread; 10 | use std::thread::JoinHandle; 11 | 12 | use crossbeam_channel; 13 | use crossbeam_channel::{Receiver, Select, Sender}; 14 | 15 | use failure::{Error, Fail}; 16 | 17 | use websocket::client::sync::Client; 18 | use websocket::receiver::Reader; 19 | use websocket::sender::Writer; 20 | use websocket::server::upgrade::sync::Buffer; 21 | use websocket::server::upgrade::WsUpgrade; 22 | use websocket::sync::Server as WsServer; 23 | use websocket::OwnedMessage; 24 | 25 | use crate::controller::Protocol as ControllerProtocol; 26 | 27 | #[derive(Debug, Fail)] 28 | enum ServerError { 29 | #[fail(display = "Unable to accept connection")] 30 | RequestAccept { cause: String }, 31 | #[fail(display = "Failed to retrieve client address")] 32 | ClientAddress { cause: String }, 33 | #[fail(display = "Only localhost connections are allowed, but found {:?}", ip)] 34 | NotLocalhost { ip: String }, 35 | #[fail(display = "Failed to split client IO")] 36 | ClientSplit { cause: String }, 37 | } 38 | 39 | pub const ALL_PORTS: u16 = 0; 40 | 41 | #[derive(Debug, Clone)] 42 | pub enum Message { 43 | Connection { port: u16, sender: Sender }, 44 | Close { port: u16 }, 45 | Incoming { data: Vec, port: u16 }, 46 | Outgoing { data: Vec, port: u16 }, 47 | Stop, 48 | } 49 | 50 | impl Message { 51 | pub fn is_stop(&self) -> bool { 52 | match self { 53 | Message::Stop => true, 54 | _ => false, 55 | } 56 | } 57 | 58 | pub fn is_close(&self) -> bool { 59 | match self { 60 | Message::Close { .. } => true, 61 | _ => false, 62 | } 63 | } 64 | 65 | pub fn into_websocket_message(self) -> Option { 66 | match self { 67 | Message::Connection { .. } => None, 68 | Message::Close { .. } => Some(OwnedMessage::Close(None)), 69 | Message::Incoming { .. } => None, 70 | Message::Outgoing { data, .. } => Some(OwnedMessage::Binary(data)), 71 | Message::Stop => None, 72 | } 73 | } 74 | } 75 | 76 | type Clients = HashMap>; 77 | 78 | pub struct Server { 79 | server_output_tx: Sender, 80 | router_thread: JoinHandle>, 81 | websocket_thread: JoinHandle>, 82 | } 83 | 84 | impl Server { 85 | pub fn new_channel() -> (Sender, Receiver) { 86 | crossbeam_channel::unbounded::() 87 | } 88 | 89 | pub fn new( 90 | port: u16, 91 | server_output_tx: Sender, 92 | server_output_rx: Receiver, 93 | controller_tx: Sender, 94 | ) -> Result { 95 | let (client_receive_tx, client_receive_rx) = crossbeam_channel::unbounded::(); 96 | 97 | let router_thread = Self::start_router(server_output_rx, client_receive_rx, controller_tx); 98 | 99 | let websocket_thread = Self::start_server(client_receive_tx, port); 100 | 101 | Ok(Server { 102 | server_output_tx, 103 | router_thread, 104 | websocket_thread, 105 | }) 106 | } 107 | 108 | fn start_router( 109 | server_send_rx: Receiver, 110 | client_receive_rx: Receiver, 111 | controller_tx: Sender, 112 | ) -> JoinHandle> { 113 | thread::Builder::new() 114 | .name("ws-router".into()) 115 | .spawn(move || { 116 | let mut clients: Clients = HashMap::new(); 117 | 118 | let mut select = Select::new(); 119 | let server_index = select.recv(&server_send_rx); 120 | let client_index = select.recv(&client_receive_rx); 121 | 122 | loop { 123 | let result = match select.ready() { 124 | index if index == server_index => server_send_rx.try_recv(), 125 | index if index == client_index => client_receive_rx.try_recv(), 126 | _ => unreachable!(), 127 | }; 128 | match result { 129 | Ok(msg) => { 130 | let is_stop = msg.is_stop(); 131 | drop(Self::dispatch_message( 132 | &mut clients, 133 | controller_tx.clone(), 134 | msg, 135 | )); 136 | if is_stop { 137 | break; 138 | } 139 | } 140 | Err(err) => { 141 | debug!("Failed to select next message from the router: {:?}", err); 142 | } 143 | }; 144 | } 145 | 146 | Ok(()) 147 | }) 148 | .unwrap() 149 | } 150 | 151 | fn dispatch_message( 152 | clients: &mut Clients, 153 | controller_tx: Sender, 154 | msg: Message, 155 | ) -> Result<(), Error> { 156 | // TODO Can we avoid the msg.clone() ? 157 | 158 | match msg { 159 | Message::Connection { port, sender } => { 160 | clients.insert(port, sender); 161 | } 162 | Message::Close { port } => { 163 | clients.remove(&port); 164 | // TODO what else ? 165 | } 166 | 167 | Message::Incoming { .. } => { 168 | drop(controller_tx.send(ControllerProtocol::ServerInput(msg))); 169 | } 170 | Message::Outgoing { port, .. } => { 171 | if port == ALL_PORTS { 172 | clients 173 | .values() 174 | .for_each(|send_tx| drop(send_tx.send(msg.clone()))); 175 | } else { 176 | clients 177 | .get(&port) 178 | .iter() 179 | .for_each(|send_tx| drop(send_tx.send(msg.clone()))); 180 | } 181 | } 182 | 183 | Message::Stop => { 184 | clients 185 | .values() 186 | .for_each(|send_tx| drop(send_tx.send(msg.clone()))); 187 | } 188 | }; 189 | Ok(()) 190 | } 191 | 192 | fn start_server(client_receive_tx: Sender, port: u16) -> JoinHandle> { 193 | thread::Builder::new() 194 | .name("ws-server".into()) 195 | .spawn(move || { 196 | let addr = format!("127.0.0.1:{}", port); 197 | info!("Starting WebSocket server at {} ...", addr); 198 | let server = WsServer::bind(addr)?; 199 | for request in server.filter_map(Result::ok) { 200 | Self::accept_request(client_receive_tx.clone(), request); 201 | } 202 | Ok(()) 203 | }) 204 | .unwrap() 205 | } 206 | 207 | fn accept_request( 208 | client_receive_tx: Sender, 209 | request: WsUpgrade>, 210 | ) { 211 | thread::spawn(move || { 212 | let (addr, receiver, sender) = request 213 | .accept() 214 | .map_err(|(_, err)| ServerError::RequestAccept { 215 | cause: err.to_string(), 216 | }) 217 | .and_then(|mut client| { 218 | Self::ensure_valid_source_or_close(&mut client).and_then(|addr| { 219 | info!("New WebSocket connection: {}", addr.to_string()); 220 | client 221 | .split() 222 | .map_err(|err| ServerError::ClientSplit { 223 | cause: err.to_string(), 224 | }) 225 | .map(|(receiver, sender)| (addr, receiver, sender)) 226 | }) 227 | })?; 228 | 229 | let (client_send_tx, client_send_rx) = crossbeam_channel::unbounded::(); 230 | 231 | drop(client_receive_tx.send(Message::Connection { 232 | port: addr.port(), 233 | sender: client_send_tx, 234 | })); 235 | 236 | let internal_tx = Self::send_messages(addr, client_send_rx, sender); 237 | 238 | Self::receive_messages(addr, client_receive_tx, internal_tx, receiver); 239 | 240 | Ok(()) 241 | }) as JoinHandle>; 242 | } 243 | 244 | fn send_messages( 245 | addr: SocketAddr, 246 | send_rx: Receiver, 247 | mut sender: Writer, 248 | ) -> Sender { 249 | let (internal_tx, internal_rx) = crossbeam_channel::unbounded::(); 250 | 251 | let thread_name = format!("ws-send-{}", addr.port()); 252 | thread::Builder::new() 253 | .name(thread_name) 254 | .spawn(move || { 255 | let mut sel = Select::new(); 256 | let send_index = sel.recv(&send_rx); 257 | let internal_index = sel.recv(&internal_rx); 258 | 259 | loop { 260 | trace!("{:?} Waiting for messages to be sent ...", addr); 261 | 262 | let try_msg = match sel.ready() { 263 | index if index == send_index => send_rx.try_recv(), 264 | index if index == internal_index => internal_rx.try_recv(), 265 | _ => unreachable!(), 266 | }; 267 | 268 | if let Ok(msg) = try_msg { 269 | let is_close = msg.is_close(); 270 | trace!("{:?} Send: {:?}", addr, msg); 271 | msg 272 | .into_websocket_message() 273 | .iter() 274 | .for_each(|ws_msg| drop(sender.send_message::(ws_msg))); 275 | if is_close { 276 | break; 277 | } 278 | } 279 | } 280 | 281 | trace!("{:?} Finished thread for sending messages", addr); 282 | }) 283 | .unwrap(); 284 | 285 | internal_tx 286 | } 287 | 288 | fn receive_messages( 289 | addr: SocketAddr, 290 | receive_tx: Sender, 291 | internal_tx: Sender, 292 | mut receiver: Reader, 293 | ) { 294 | let port = addr.port(); 295 | 296 | for message in receiver.incoming_messages() { 297 | match message { 298 | Ok(OwnedMessage::Text(data)) => { 299 | trace!("{:?} Text: {:?}", addr, data); 300 | drop(receive_tx.send(Message::Incoming { 301 | port, 302 | data: data.into_bytes(), 303 | })); 304 | } 305 | Ok(OwnedMessage::Binary(data)) => { 306 | trace!("{:?} Binary: {:?}", addr, data); 307 | drop(receive_tx.send(Message::Incoming { port, data })); 308 | } 309 | Ok(OwnedMessage::Close(data)) => { 310 | trace!("{:?} Close: {:?}", addr, data); 311 | drop(internal_tx.send(Message::Close { port })); 312 | break; 313 | } 314 | Err(err) => { 315 | //A forced websocket close (client probably crashed, and the kernel cleaned up the socket) 316 | trace!("{:?} Err: {:?}", addr, err); 317 | drop(internal_tx.send(Message::Close { port })); 318 | break; 319 | } 320 | _ => {} 321 | } 322 | } 323 | 324 | trace!("{:?} Finished thread for receiving messages", addr); 325 | } 326 | 327 | fn ensure_valid_source_or_close( 328 | client: &mut Client, 329 | ) -> Result { 330 | client 331 | .peer_addr() 332 | .map_err(|err| ServerError::ClientAddress { 333 | cause: err.to_string(), 334 | }) 335 | .and_then(|addr| { 336 | let ip = addr.ip(); 337 | if ip.is_loopback() { 338 | Ok(addr) 339 | } else { 340 | drop(client.send_message(&OwnedMessage::Close(None))); 341 | Err(ServerError::NotLocalhost { ip: ip.to_string() }) 342 | } 343 | }) 344 | } 345 | 346 | pub fn close(self) { 347 | info!("Closing server ..."); 348 | 349 | drop( 350 | self 351 | .server_output_tx 352 | .send(Message::Close { port: ALL_PORTS }), 353 | ); 354 | // TODO figure out how to stop the websocket thread 355 | drop(self.websocket_thread.join()); 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /core/src/midi/encoder.rs: -------------------------------------------------------------------------------- 1 | use super::messages::Message; 2 | use super::types::{U14, U3, U4, U7}; 3 | 4 | #[inline] 5 | fn u3(d: &U3) -> u8 { 6 | d & 0x07 7 | } 8 | 9 | #[inline] 10 | fn u4(d: &U4) -> u8 { 11 | d & 0x0f 12 | } 13 | 14 | #[inline] 15 | fn u7(d: &U7) -> u8 { 16 | d & 0x7f 17 | } 18 | 19 | #[inline] 20 | fn u14_msb(d: &U14) -> u8 { 21 | ((d >> 7) & 0x7fu16) as u8 22 | } 23 | 24 | #[inline] 25 | fn u14_lsb(d: &U14) -> u8 { 26 | (d & 0x7f) as u8 27 | } 28 | 29 | #[inline] 30 | fn status_and_channel(status: U4, channel: &U4) -> u8 { 31 | (status << 4) | (channel & 0x0f) 32 | } 33 | 34 | pub struct Encoder; 35 | 36 | impl Encoder { 37 | pub fn data_size(msg: &Message) -> usize { 38 | match msg { 39 | Message::NoteOff { .. } => 3, 40 | Message::NoteOn { .. } => 3, 41 | Message::PolyphonicKeyPressure { .. } => 3, 42 | Message::ControlChange { .. } => 3, 43 | Message::ProgramChange { .. } => 2, 44 | Message::ChannelPressure { .. } => 2, 45 | Message::PitchBend { .. } => 3, 46 | Message::AllSoundOff { .. } => 3, 47 | Message::ResetAllControllers { .. } => 3, 48 | Message::LocalControlOff { .. } => 3, 49 | Message::LocalControlOn { .. } => 3, 50 | Message::AllNotesOff { .. } => 3, 51 | Message::OmniModeOff { .. } => 3, 52 | Message::OmniModeOn { .. } => 3, 53 | Message::MonoModeOn { .. } => 3, 54 | Message::PolyModeOn { .. } => 3, 55 | Message::MTCQuarterFrame { .. } => 2, 56 | Message::SongPositionPointer { .. } => 3, 57 | Message::SongSelect { .. } => 2, 58 | Message::TuneRequest => 1, 59 | Message::TimingClock => 1, 60 | Message::Start => 1, 61 | Message::Continue => 1, 62 | Message::Stop => 1, 63 | Message::ActiveSensing => 1, 64 | Message::SystemReset => 1, 65 | } 66 | } 67 | 68 | pub fn encode(msg: &Message, out: &mut [u8]) { 69 | match msg { 70 | Message::NoteOff { 71 | channel, 72 | key, 73 | velocity, 74 | } => out[..3].copy_from_slice(&[status_and_channel(0b1000, channel), u7(key), u7(velocity)]), 75 | Message::NoteOn { 76 | channel, 77 | key, 78 | velocity, 79 | } => out[..3].copy_from_slice(&[status_and_channel(0b1001, channel), u7(key), u7(velocity)]), 80 | Message::PolyphonicKeyPressure { 81 | channel, 82 | key, 83 | value, 84 | } => out[..3].copy_from_slice(&[status_and_channel(0b1010, channel), u7(key), u7(value)]), 85 | Message::ControlChange { 86 | channel, 87 | controller, 88 | value, 89 | } => out[..3].copy_from_slice(&[ 90 | status_and_channel(0b1011, channel), 91 | u7(controller), 92 | u7(value), 93 | ]), 94 | Message::ProgramChange { channel, value } => { 95 | out[..2].copy_from_slice(&[status_and_channel(0b1100, channel), u7(value)]) 96 | } 97 | Message::ChannelPressure { channel, value } => { 98 | out[..2].copy_from_slice(&[status_and_channel(0b1101, channel), u7(value)]) 99 | } 100 | Message::PitchBend { channel, value } => out[..3].copy_from_slice(&[ 101 | status_and_channel(0b1110, channel), 102 | u14_lsb(value), 103 | u14_msb(value), 104 | ]), 105 | Message::AllSoundOff { channel } => { 106 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 120, 0]) 107 | } 108 | Message::ResetAllControllers { channel } => { 109 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 121, 0]) 110 | } 111 | Message::LocalControlOff { channel } => { 112 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 122, 0]) 113 | } 114 | Message::LocalControlOn { channel } => { 115 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 122, 127]) 116 | } 117 | Message::AllNotesOff { channel } => { 118 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 123, 0]) 119 | } 120 | Message::OmniModeOff { channel } => { 121 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 124, 0]) 122 | } 123 | Message::OmniModeOn { channel } => { 124 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 125, 0]) 125 | } 126 | Message::MonoModeOn { 127 | channel, 128 | num_channels, 129 | } => out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 126, u7(num_channels)]), 130 | Message::PolyModeOn { channel } => { 131 | out[..3].copy_from_slice(&[status_and_channel(0b1011, channel), 127, 0]) 132 | } 133 | Message::MTCQuarterFrame { msg_type, value } => { 134 | out[..2].copy_from_slice(&[0b1111_0001, (u3(msg_type) << 4) | u4(value)]) 135 | } 136 | Message::SongPositionPointer { beats } => { 137 | out[..3].copy_from_slice(&[0b1111_0010, u14_lsb(beats), u14_msb(beats)]) 138 | } 139 | Message::SongSelect { song } => out[..2].copy_from_slice(&[0b1111_0011, u7(song)]), 140 | Message::TuneRequest => out[0] = 0b1111_0110, 141 | Message::TimingClock => out[0] = 0b1111_1000, 142 | Message::Start => out[0] = 0b1111_1010, 143 | Message::Continue => out[0] = 0b1111_1011, 144 | Message::Stop => out[0] = 0b1111_1100, 145 | Message::ActiveSensing => out[0] = 0b1111_1110, 146 | Message::SystemReset => out[0] = 0b1111_1111, 147 | } 148 | } 149 | 150 | pub fn sysex_data_size(data: &[U7]) -> usize { 151 | data.len() + 2 152 | } 153 | 154 | pub fn sysex_encode(data: &[U7], out: &mut [u8]) { 155 | out[0] = 0b1111_0000; 156 | out[1..=data.len()].copy_from_slice(&data); 157 | out[data.len() + 1] = 0b1111_0111 158 | } 159 | } 160 | 161 | #[cfg(test)] 162 | mod test { 163 | 164 | use super::*; 165 | 166 | #[test] 167 | pub fn test_u3() { 168 | assert_eq!(u3(&0xff), 0x07); 169 | } 170 | 171 | #[test] 172 | pub fn test_u4() { 173 | assert_eq!(u4(&0xff), 0x0f); 174 | } 175 | 176 | #[test] 177 | pub fn test_u7() { 178 | assert_eq!(u7(&0xff), 0x7f); 179 | } 180 | 181 | #[test] 182 | pub fn test_u14_lsb() { 183 | assert_eq!(u14_lsb(&0b10_1010_1010_1010), 0b010_1010); 184 | } 185 | 186 | #[test] 187 | pub fn test_u14_msb() { 188 | assert_eq!(u14_msb(&0b10_1010_1010_1010), 0b101_0101); 189 | } 190 | 191 | #[test] 192 | pub fn test_status_and_channel() { 193 | assert_eq!(status_and_channel(0b1010_1010, &0b0101_0101), 0b1010_0101); 194 | } 195 | 196 | #[test] 197 | pub fn note_off() { 198 | assert_encoding( 199 | &Message::NoteOff { 200 | channel: 1, 201 | key: 65, 202 | velocity: 120, 203 | }, 204 | vec![0b1000_0001, 65, 120], 205 | ) 206 | } 207 | 208 | #[test] 209 | pub fn note_on() { 210 | assert_encoding( 211 | &Message::NoteOn { 212 | channel: 1, 213 | key: 65, 214 | velocity: 120, 215 | }, 216 | vec![0b1001_0001, 65, 120], 217 | ) 218 | } 219 | 220 | #[test] 221 | pub fn polyphonic_key_pressure() { 222 | assert_encoding( 223 | &Message::PolyphonicKeyPressure { 224 | channel: 1, 225 | key: 65, 226 | value: 120, 227 | }, 228 | vec![0b1010_0001, 65, 120], 229 | ) 230 | } 231 | 232 | #[test] 233 | pub fn control_change() { 234 | assert_encoding( 235 | &Message::ControlChange { 236 | channel: 1, 237 | controller: 65, 238 | value: 120, 239 | }, 240 | vec![0b1011_0001, 65, 120], 241 | ) 242 | } 243 | 244 | #[test] 245 | pub fn program_change() { 246 | assert_encoding( 247 | &Message::ProgramChange { 248 | channel: 1, 249 | value: 120, 250 | }, 251 | vec![0b1100_0001, 120], 252 | ) 253 | } 254 | 255 | #[test] 256 | pub fn channel_pressure() { 257 | assert_encoding( 258 | &Message::ChannelPressure { 259 | channel: 1, 260 | value: 120, 261 | }, 262 | vec![0b1101_0001, 120], 263 | ) 264 | } 265 | 266 | #[test] 267 | pub fn pitch_bend() { 268 | assert_encoding( 269 | &Message::PitchBend { 270 | channel: 1, 271 | value: 0b10_1010_1010_1010, 272 | }, 273 | vec![0b1110_0001, 0b010_1010, 0b101_0101], 274 | ) 275 | } 276 | 277 | #[test] 278 | pub fn all_sound_off() { 279 | assert_encoding( 280 | &Message::AllSoundOff { channel: 1 }, 281 | vec![0b1011_0001, 120, 0], 282 | ) 283 | } 284 | 285 | #[test] 286 | pub fn reset_all_controllers() { 287 | assert_encoding( 288 | &Message::ResetAllControllers { channel: 1 }, 289 | vec![0b1011_0001, 121, 0], 290 | ) 291 | } 292 | 293 | #[test] 294 | pub fn local_control_off() { 295 | assert_encoding( 296 | &Message::LocalControlOff { channel: 1 }, 297 | vec![0b1011_0001, 122, 0], 298 | ) 299 | } 300 | 301 | #[test] 302 | pub fn local_control_on() { 303 | assert_encoding( 304 | &Message::LocalControlOn { channel: 1 }, 305 | vec![0b1011_0001, 122, 127], 306 | ) 307 | } 308 | 309 | #[test] 310 | pub fn all_notes_off() { 311 | assert_encoding( 312 | &Message::AllNotesOff { channel: 1 }, 313 | vec![0b1011_0001, 123, 0], 314 | ) 315 | } 316 | 317 | #[test] 318 | pub fn omni_mode_off() { 319 | assert_encoding( 320 | &Message::OmniModeOff { channel: 1 }, 321 | vec![0b1011_0001, 124, 0], 322 | ) 323 | } 324 | 325 | #[test] 326 | pub fn mono_mode_on() { 327 | assert_encoding( 328 | &Message::MonoModeOn { 329 | channel: 1, 330 | num_channels: 5, 331 | }, 332 | vec![0b1011_0001, 126, 5], 333 | ) 334 | } 335 | 336 | #[test] 337 | pub fn poly_mode_on() { 338 | assert_encoding( 339 | &Message::PolyModeOn { channel: 1 }, 340 | vec![0b1011_0001, 127, 0], 341 | ) 342 | } 343 | 344 | #[test] 345 | pub fn sysex() { 346 | assert_sysex_encoding( 347 | vec![1, 2, 3, 4, 5], 348 | vec![0b1111_0000, 1, 2, 3, 4, 5, 0b1111_0111], 349 | ) 350 | } 351 | 352 | #[test] 353 | pub fn mtc_quarter_frame() { 354 | assert_encoding( 355 | &Message::MTCQuarterFrame { 356 | msg_type: 2, 357 | value: 5, 358 | }, 359 | vec![0b1111_0001, 0b010_0101], 360 | ) 361 | } 362 | 363 | #[test] 364 | pub fn song_position_pointer() { 365 | assert_encoding( 366 | &Message::SongPositionPointer { 367 | beats: 0b10_1010_1010_1010, 368 | }, 369 | vec![0b1111_0010, 0b010_1010, 0b101_0101], 370 | ) 371 | } 372 | 373 | #[test] 374 | pub fn song_select() { 375 | assert_encoding(&Message::SongSelect { song: 54 }, vec![0b1111_0011, 54]) 376 | } 377 | 378 | #[test] 379 | pub fn tune_request() { 380 | assert_encoding(&Message::TuneRequest, vec![0b1111_0110]) 381 | } 382 | 383 | #[test] 384 | pub fn timing_clock() { 385 | assert_encoding(&Message::TimingClock, vec![0b1111_1000]) 386 | } 387 | 388 | #[test] 389 | pub fn start() { 390 | assert_encoding(&Message::Start, vec![0b1111_1010]) 391 | } 392 | 393 | #[test] 394 | pub fn test_continue() { 395 | assert_encoding(&Message::Continue, vec![0b1111_1011]) 396 | } 397 | 398 | #[test] 399 | pub fn stop() { 400 | assert_encoding(&Message::Stop, vec![0b1111_1100]) 401 | } 402 | 403 | #[test] 404 | pub fn active_sensing() { 405 | assert_encoding(&Message::ActiveSensing, vec![0b1111_1110]) 406 | } 407 | 408 | #[test] 409 | pub fn system_reset() { 410 | assert_encoding(&Message::SystemReset, vec![0b1111_1111]) 411 | } 412 | 413 | fn assert_encoding(msg: &Message, expected: Vec) { 414 | let data_len = Encoder::data_size(msg); 415 | let mut data = Vec::::with_capacity(data_len); 416 | unsafe { data.set_len(data_len) }; 417 | Encoder::encode(msg, data.as_mut_slice()); 418 | assert_eq!(data, expected); 419 | } 420 | 421 | fn assert_sysex_encoding(msg: Vec, expected: Vec) { 422 | let data_len = Encoder::sysex_data_size(&msg); 423 | let mut data = Vec::::with_capacity(data_len); 424 | unsafe { data.set_len(data_len) }; 425 | Encoder::sysex_encode(&msg, data.as_mut_slice()); 426 | assert_eq!(data, expected); 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /core/src/midi/decoder.rs: -------------------------------------------------------------------------------- 1 | use crate::midi::messages::Message; 2 | use crate::midi::types::{U14, U4, U7}; 3 | 4 | #[derive(Debug, Clone, Eq, PartialEq)] 5 | pub enum DecodedMessage { 6 | Message(Message), 7 | 8 | /// This message type allows manufacturers to create their own messages (such 9 | /// as bulk dumps, patch parameters, and other non-spec data) and provides a mechanism for 10 | /// creating additional MIDI Specification messages. 11 | SysEx { 12 | data: Vec, 13 | }, 14 | 15 | /// Unknown data. Used by the decoder when the data can not be decoded. 16 | Unknown(Vec), // TODO that should be a pointer to a vector 17 | } 18 | 19 | impl From for DecodedMessage { 20 | fn from(msg: Message) -> DecodedMessage { 21 | DecodedMessage::Message(msg) 22 | } 23 | } 24 | 25 | pub struct Decoder<'a> { 26 | pos: usize, 27 | start: usize, 28 | sysex_data: Vec, 29 | sysex_decoding: bool, 30 | data: &'a [u8], 31 | } 32 | 33 | impl<'a> Decoder<'a> { 34 | pub fn new(data: &'a [u8]) -> Decoder<'a> { 35 | Decoder { 36 | pos: 0, 37 | start: 0, 38 | sysex_data: Vec::new(), 39 | sysex_decoding: false, 40 | data, 41 | } 42 | } 43 | 44 | fn unknown(&self, end: usize) -> DecodedMessage { 45 | DecodedMessage::Unknown(self.data[self.start..end].to_vec()) 46 | } 47 | 48 | fn next_data(&mut self) -> Result { 49 | if self.pos < self.data.len() { 50 | let d1 = self.data[self.pos]; 51 | if d1 & 0b1000_0000 == 0 { 52 | self.pos += 1; 53 | Ok(d1) 54 | } else { 55 | Err(self.pos) 56 | } 57 | } else { 58 | Err(self.pos) 59 | } 60 | } 61 | 62 | fn next_data2(&mut self) -> Result<(U7, U7), usize> { 63 | self 64 | .next_data() 65 | .and_then(|d1| self.next_data().and_then(|d2| Ok((d1, d2)))) 66 | } 67 | 68 | fn decode_note(&mut self, channel: U4, is_on: bool) -> DecodedMessage { 69 | match self.next_data2() { 70 | Ok((key, velocity)) => { 71 | if is_on { 72 | Message::NoteOn { 73 | channel, 74 | key, 75 | velocity, 76 | } 77 | .into() 78 | } else { 79 | Message::NoteOff { 80 | channel, 81 | key, 82 | velocity, 83 | } 84 | .into() 85 | } 86 | } 87 | Err(end) => self.unknown(end), 88 | } 89 | } 90 | 91 | fn decode_polyphonic_key_pressure(&mut self, channel: U4) -> DecodedMessage { 92 | match self.next_data2() { 93 | Ok((key, pressure)) => Message::PolyphonicKeyPressure { 94 | channel, 95 | key, 96 | value: pressure, 97 | } 98 | .into(), 99 | Err(end) => self.unknown(end), 100 | } 101 | } 102 | 103 | fn decode_control_change(&mut self, channel: U4) -> DecodedMessage { 104 | match self.next_data2() { 105 | Ok((controller, value)) => match controller { 106 | 120 => match value { 107 | 0 => Message::AllSoundOff { channel }.into(), 108 | _ => self.unknown(self.pos), 109 | }, 110 | 121 => Message::ResetAllControllers { channel }.into(), 111 | 122 => match value { 112 | 0 => Message::LocalControlOff { channel }.into(), 113 | 127 => Message::LocalControlOn { channel }.into(), 114 | _ => self.unknown(self.pos), 115 | }, 116 | 123 => match value { 117 | 0 => Message::AllNotesOff { channel }.into(), 118 | _ => self.unknown(self.pos), 119 | }, 120 | 124 => match value { 121 | 0 => Message::OmniModeOff { channel }.into(), 122 | _ => self.unknown(self.pos), 123 | }, 124 | 125 => match value { 125 | 0 => Message::OmniModeOn { channel }.into(), 126 | _ => self.unknown(self.pos), 127 | }, 128 | 126 => Message::MonoModeOn { 129 | channel, 130 | num_channels: value, 131 | } 132 | .into(), 133 | 127 => match value { 134 | 0 => Message::PolyModeOn { channel }.into(), 135 | _ => self.unknown(self.pos), 136 | }, 137 | _ => Message::ControlChange { 138 | channel, 139 | controller, 140 | value, 141 | } 142 | .into(), 143 | }, 144 | Err(end) => self.unknown(end), 145 | } 146 | } 147 | 148 | fn decode_program_change(&mut self, channel: U4) -> DecodedMessage { 149 | match self.next_data() { 150 | Ok(program) => Message::ProgramChange { 151 | channel, 152 | value: program, 153 | } 154 | .into(), 155 | Err(end) => self.unknown(end), 156 | } 157 | } 158 | 159 | fn decode_channel_pressure(&mut self, channel: U4) -> DecodedMessage { 160 | match self.next_data() { 161 | Ok(pressure) => Message::ChannelPressure { 162 | channel, 163 | value: pressure, 164 | } 165 | .into(), 166 | Err(end) => self.unknown(end), 167 | } 168 | } 169 | 170 | fn decode_pitch_bend(&mut self, channel: U4) -> DecodedMessage { 171 | match self.next_data2() { 172 | Ok((lsb, msb)) => Message::PitchBend { 173 | channel, 174 | value: (U14::from(msb) << 7) | U14::from(lsb), 175 | } 176 | .into(), 177 | Err(end) => self.unknown(end), 178 | } 179 | } 180 | 181 | fn decode_mtc_quarter_frame(&mut self) -> DecodedMessage { 182 | match self.next_data() { 183 | Ok(data) => Message::MTCQuarterFrame { 184 | msg_type: (data >> 4) & 0x07, 185 | value: data & 0x0f, 186 | } 187 | .into(), 188 | Err(end) => self.unknown(end), 189 | } 190 | } 191 | 192 | fn decode_song_position_pointer(&mut self) -> DecodedMessage { 193 | match self.next_data2() { 194 | Ok((lsb, msb)) => Message::SongPositionPointer { 195 | beats: (U14::from(msb) << 7) | U14::from(lsb), 196 | } 197 | .into(), 198 | Err(end) => self.unknown(end), 199 | } 200 | } 201 | 202 | fn decode_song_select(&mut self) -> DecodedMessage { 203 | match self.next_data() { 204 | Ok(song) => Message::SongSelect { song }.into(), 205 | Err(end) => self.unknown(end), 206 | } 207 | } 208 | 209 | fn decode_sysex_start(&mut self) -> Option { 210 | if !self.sysex_decoding { 211 | self.sysex_decoding = true; 212 | self.decode_sysex_data() 213 | } else { 214 | Some(self.unknown(self.pos)) 215 | } 216 | } 217 | 218 | fn decode_sysex_end(&mut self) -> Option { 219 | if self.sysex_decoding { 220 | self.sysex_decoding = false; 221 | let data = self.sysex_data.to_owned(); 222 | self.sysex_data = Vec::new(); 223 | Some(DecodedMessage::SysEx { data }) 224 | } else { 225 | Some(self.unknown(self.pos)) 226 | } 227 | } 228 | 229 | fn decode_sysex_data(&mut self) -> Option { 230 | let start = self.pos; 231 | let mut pos = start; 232 | while pos < self.data.len() && (self.data[pos] & 0x80) == 0 { 233 | pos += 1 234 | } 235 | self.pos = pos; 236 | let data = &self.data[start..pos]; 237 | self.sysex_data.extend(data); 238 | if pos < self.data.len() { 239 | let status = self.data[pos]; 240 | self.start = self.pos; 241 | self.pos += 1; 242 | self.decode(status) 243 | } else { 244 | self.sysex_decoding = false; 245 | let mut data = self.sysex_data.to_owned(); 246 | self.sysex_data = Vec::new(); 247 | data.insert(0, 0b1111_0000); 248 | Some(DecodedMessage::Unknown(data)) 249 | } 250 | } 251 | 252 | fn decode(&mut self, status: U7) -> Option { 253 | match (status >> 4) & 0x0f { 254 | 0b1000 => Some(self.decode_note(status & 0x0f, false)), 255 | 0b1001 => Some(self.decode_note(status & 0x0f, true)), 256 | 0b1010 => Some(self.decode_polyphonic_key_pressure(status & 0x0f)), 257 | 0b1011 => Some(self.decode_control_change(status & 0x0f)), 258 | 0b1100 => Some(self.decode_program_change(status & 0x0f)), 259 | 0b1101 => Some(self.decode_channel_pressure(status & 0x0f)), 260 | 0b1110 => Some(self.decode_pitch_bend(status & 0x0f)), 261 | 0b1111 => match status & 0x0f { 262 | 0b0000 => self.decode_sysex_start(), 263 | 0b0001 => Some(self.decode_mtc_quarter_frame()), 264 | 0b0010 => Some(self.decode_song_position_pointer()), 265 | 0b0011 => Some(self.decode_song_select()), 266 | 0b0100 => Some(self.unknown(self.pos)), 267 | 0b0101 => Some(self.unknown(self.pos)), 268 | 0b0110 => Some(Message::TuneRequest.into()), 269 | 0b0111 => self.decode_sysex_end(), 270 | 0b1000 => Some(Message::TimingClock.into()), 271 | 0b1001 => Some(self.unknown(self.pos)), 272 | 0b1010 => Some(Message::Start.into()), 273 | 0b1011 => Some(Message::Continue.into()), 274 | 0b1100 => Some(Message::Stop.into()), 275 | 0b1101 => Some(self.unknown(self.pos)), 276 | 0b1110 => Some(Message::ActiveSensing.into()), 277 | 0b1111 => Some(Message::SystemReset.into()), 278 | _ => unreachable!(), 279 | }, 280 | _ => Some(DecodedMessage::Unknown(vec![status])), 281 | } 282 | } 283 | } 284 | 285 | impl<'a> Iterator for Decoder<'a> { 286 | type Item = DecodedMessage; 287 | 288 | fn next(&mut self) -> Option { 289 | if self.sysex_decoding { 290 | self.decode_sysex_data() 291 | } else if self.pos < self.data.len() { 292 | let status = self.data[self.pos]; 293 | self.start = self.pos; 294 | self.pos += 1; 295 | self.decode(status) 296 | } else { 297 | None 298 | } 299 | } 300 | } 301 | 302 | #[cfg(test)] 303 | mod tests { 304 | use super::*; 305 | use crate::midi::messages::Message; 306 | 307 | #[test] 308 | fn decode_empty_vec() { 309 | let data = &Vec::new(); 310 | let mut dec = Decoder::new(data); 311 | assert_eq!(dec.next(), None); 312 | assert_eq!(dec.next(), None); 313 | } 314 | 315 | #[test] 316 | fn next_data_unknown() { 317 | let data = &vec![0b1000_0000, 64, 0b1000_0001, 0b1000_0010, 12]; 318 | let mut dec = Decoder::new(data); 319 | assert_eq!( 320 | dec.next(), 321 | Some(DecodedMessage::Unknown(vec![0b1000_0000, 64])) 322 | ); 323 | assert_eq!(dec.next(), Some(DecodedMessage::Unknown(vec![0b1000_0001]))); 324 | assert_eq!( 325 | dec.next(), 326 | Some(DecodedMessage::Unknown(vec![0b1000_0010, 12])) 327 | ); 328 | } 329 | 330 | #[test] 331 | fn decode_notes() { 332 | let data = &vec![0b1000_0101u8, 64, 127, 0b1001_1010, 0, 127]; 333 | let mut dec = Decoder::new(data); 334 | assert_eq!( 335 | dec.next(), 336 | Some( 337 | Message::NoteOff { 338 | channel: 0b0101, 339 | key: 64, 340 | velocity: 127 341 | } 342 | .into() 343 | ) 344 | ); 345 | assert_eq!( 346 | dec.next(), 347 | Some( 348 | Message::NoteOn { 349 | channel: 0b1010, 350 | key: 0, 351 | velocity: 127 352 | } 353 | .into() 354 | ) 355 | ); 356 | assert_eq!(dec.next(), None); 357 | } 358 | 359 | #[test] 360 | fn decode_polyphonic_key_pressure() { 361 | let data = &vec![0b1010_0101u8, 64, 127]; 362 | let mut dec = Decoder::new(data); 363 | assert_eq!( 364 | dec.next(), 365 | Some( 366 | Message::PolyphonicKeyPressure { 367 | channel: 0b0101, 368 | key: 64, 369 | value: 127 370 | } 371 | .into() 372 | ) 373 | ); 374 | assert_eq!(dec.next(), None); 375 | } 376 | 377 | #[test] 378 | fn decode_control_change() { 379 | let data = &vec![0b1011_0101u8, 64, 127]; 380 | let mut dec = Decoder::new(data); 381 | assert_eq!( 382 | dec.next(), 383 | Some( 384 | Message::ControlChange { 385 | channel: 0b0101, 386 | controller: 64, 387 | value: 127 388 | } 389 | .into() 390 | ) 391 | ); 392 | assert_eq!(dec.next(), None); 393 | } 394 | 395 | #[test] 396 | fn decode_program_change() { 397 | let data = &vec![0b1100_0101u8, 0b0_1010101]; 398 | let mut dec = Decoder::new(data); 399 | assert_eq!( 400 | dec.next(), 401 | Some( 402 | Message::ProgramChange { 403 | channel: 0b0101, 404 | value: 0b0_1010101 405 | } 406 | .into() 407 | ) 408 | ); 409 | assert_eq!(dec.next(), None); 410 | } 411 | 412 | #[test] 413 | fn decode_channel_pressure() { 414 | let data = &vec![0b1101_0101u8, 0b0_1010101]; 415 | let mut dec = Decoder::new(data); 416 | assert_eq!( 417 | dec.next(), 418 | Some( 419 | Message::ChannelPressure { 420 | channel: 0b0101, 421 | value: 0b0_1010101 422 | } 423 | .into() 424 | ) 425 | ); 426 | assert_eq!(dec.next(), None); 427 | } 428 | 429 | #[test] 430 | fn decode_pitch_bend() { 431 | let data = &vec![0b1110_0101u8, 0b0_1010101, 0b0_0101010]; 432 | let mut dec = Decoder::new(data); 433 | assert_eq!( 434 | dec.next(), 435 | Some( 436 | Message::PitchBend { 437 | channel: 0b0101, 438 | value: 0b0_01010101010101 439 | } 440 | .into() 441 | ) 442 | ); 443 | assert_eq!(dec.next(), None); 444 | } 445 | 446 | #[test] 447 | #[allow(clippy::inconsistent_digit_grouping)] 448 | fn decode_mtc_quarter_frame() { 449 | let data = &vec![0b1111_0001u8, 0b0_101_1010]; 450 | let mut dec = Decoder::new(data); 451 | assert_eq!( 452 | dec.next(), 453 | Some( 454 | Message::MTCQuarterFrame { 455 | msg_type: 0b101, 456 | value: 0b1010 457 | } 458 | .into() 459 | ) 460 | ); 461 | assert_eq!(dec.next(), None); 462 | } 463 | 464 | #[test] 465 | fn decode_song_position_pointer() { 466 | let data = &vec![ 467 | 0b1111_0010u8, 468 | 0b0_1010101, 469 | 0b0_0101010, 470 | 0b1111_0010u8, 471 | 0b0_0101010, 472 | 0b0_1010101, 473 | ]; 474 | let mut dec = Decoder::new(data); 475 | assert_eq!( 476 | dec.next(), 477 | Some( 478 | Message::SongPositionPointer { 479 | beats: 0b01_0101_0101_0101 480 | } 481 | .into() 482 | ) 483 | ); 484 | assert_eq!( 485 | dec.next(), 486 | Some( 487 | Message::SongPositionPointer { 488 | beats: 0b10_1010_1010_1010 489 | } 490 | .into() 491 | ) 492 | ); 493 | assert_eq!(dec.next(), None); 494 | } 495 | 496 | #[test] 497 | fn decode_song_select() { 498 | let data = &vec![0b1111_0011u8, 0b0_1010101]; 499 | let mut dec = Decoder::new(data); 500 | assert_eq!( 501 | dec.next(), 502 | Some(Message::SongSelect { song: 0b101_0101 }.into()) 503 | ); 504 | assert_eq!(dec.next(), None); 505 | } 506 | 507 | #[test] 508 | fn decode_tune_request() { 509 | let data = &vec![0b1111_0110u8]; 510 | let mut dec = Decoder::new(data); 511 | assert_eq!(dec.next(), Some(Message::TuneRequest.into())); 512 | assert_eq!(dec.next(), None); 513 | } 514 | 515 | #[test] 516 | fn decode_timing_clock() { 517 | let data = &vec![0b1111_1000u8]; 518 | let mut dec = Decoder::new(data); 519 | assert_eq!(dec.next(), Some(Message::TimingClock.into())); 520 | assert_eq!(dec.next(), None); 521 | } 522 | 523 | #[test] 524 | fn decode_start_continue_stop() { 525 | let data = &vec![0b1111_1010u8, 0b1111_1011, 0b1111_1100]; 526 | let mut dec = Decoder::new(data); 527 | assert_eq!(dec.next(), Some(Message::Start.into())); 528 | assert_eq!(dec.next(), Some(Message::Continue.into())); 529 | assert_eq!(dec.next(), Some(Message::Stop.into())); 530 | assert_eq!(dec.next(), None); 531 | } 532 | 533 | #[test] 534 | fn decode_active_sensing() { 535 | let data = &vec![0b1111_1110u8]; 536 | let mut dec = Decoder::new(data); 537 | assert_eq!(dec.next(), Some(Message::ActiveSensing.into())); 538 | assert_eq!(dec.next(), None); 539 | } 540 | 541 | #[test] 542 | fn decode_system_reset() { 543 | let data = &vec![0b1111_1111u8]; 544 | let mut dec = Decoder::new(data); 545 | assert_eq!(dec.next(), Some(Message::SystemReset.into())); 546 | assert_eq!(dec.next(), None); 547 | } 548 | 549 | #[test] 550 | fn decode_reserved() { 551 | let data = &vec![0b1111_0100u8, 0b1111_0101, 0b1111_1001, 0b1111_1101]; 552 | let mut dec = Decoder::new(data); 553 | assert_eq!(dec.next(), Some(DecodedMessage::Unknown(vec![0b1111_0100]))); 554 | assert_eq!(dec.next(), Some(DecodedMessage::Unknown(vec![0b1111_0101]))); 555 | assert_eq!(dec.next(), Some(DecodedMessage::Unknown(vec![0b1111_1001]))); 556 | assert_eq!(dec.next(), Some(DecodedMessage::Unknown(vec![0b1111_1101]))); 557 | assert_eq!(dec.next(), None); 558 | } 559 | 560 | #[test] 561 | fn decode_sysex_continuous() { 562 | let data = &vec![0b1111_0000u8, 1, 2, 3, 4, 0b1111_0111]; 563 | let mut dec = Decoder::new(data); 564 | assert_eq!( 565 | dec.next(), 566 | Some(DecodedMessage::SysEx { 567 | data: vec![1u8, 2, 3, 4] 568 | }) 569 | ); 570 | assert_eq!(dec.next(), None); 571 | } 572 | 573 | #[test] 574 | fn decode_sysex_interleaved() { 575 | let data = &vec![ 576 | 0b1111_0000u8, 577 | 1, 578 | 2, 579 | 0b1000_0101u8, 580 | 64, 581 | 127, 582 | 3, 583 | 4, 584 | 0b1111_0111, 585 | ]; 586 | let mut dec = Decoder::new(data); 587 | assert_eq!( 588 | dec.next(), 589 | Some( 590 | Message::NoteOff { 591 | channel: 0b0101, 592 | key: 64, 593 | velocity: 127 594 | } 595 | .into() 596 | ) 597 | ); 598 | assert_eq!( 599 | dec.next(), 600 | Some(DecodedMessage::SysEx { 601 | data: vec![1u8, 2, 3, 4] 602 | }) 603 | ); 604 | assert_eq!(dec.next(), None); 605 | } 606 | 607 | #[test] 608 | fn decode_sysex_unexpected_end_data() { 609 | let data = &vec![0b1111_0000u8, 1, 2]; 610 | let mut dec = Decoder::new(data); 611 | assert_eq!( 612 | dec.next(), 613 | Some(DecodedMessage::Unknown(vec![0b1111_0000u8, 1, 2])) 614 | ); 615 | assert_eq!(dec.next(), None); 616 | } 617 | 618 | #[test] 619 | fn decode_sysex_unexpected_end_interleaved() { 620 | let data = &vec![0b1111_0000u8, 1, 2, 0b1000_0000u8, 64]; 621 | let mut dec = Decoder::new(data); 622 | assert_eq!( 623 | dec.next(), 624 | Some(DecodedMessage::Unknown(vec![0b1000_0000u8, 64])) 625 | ); 626 | assert_eq!( 627 | dec.next(), 628 | Some(DecodedMessage::Unknown(vec![0b1111_0000u8, 1, 2])) 629 | ); 630 | assert_eq!(dec.next(), None); 631 | } 632 | } 633 | --------------------------------------------------------------------------------