├── 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 |
--------------------------------------------------------------------------------
/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