├── src-tauri ├── build.rs ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square310x310Logo.png │ ├── ios │ │ ├── AppIcon-512@2x.png │ │ ├── AppIcon-20x20@1x.png │ │ ├── AppIcon-20x20@2x.png │ │ ├── AppIcon-20x20@3x.png │ │ ├── AppIcon-29x29@1x.png │ │ ├── AppIcon-29x29@2x.png │ │ ├── AppIcon-29x29@3x.png │ │ ├── AppIcon-40x40@1x.png │ │ ├── AppIcon-40x40@2x.png │ │ ├── AppIcon-40x40@3x.png │ │ ├── AppIcon-60x60@2x.png │ │ ├── AppIcon-60x60@3x.png │ │ ├── AppIcon-76x76@1x.png │ │ ├── AppIcon-76x76@2x.png │ │ ├── AppIcon-20x20@2x-1.png │ │ ├── AppIcon-29x29@2x-1.png │ │ ├── AppIcon-40x40@2x-1.png │ │ └── AppIcon-83.5x83.5@2x.png │ ├── android │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ └── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ └── logo.svg ├── .gitignore ├── src │ ├── main.rs │ ├── devices │ │ ├── device.rs │ │ ├── trigger.rs │ │ ├── monitor.rs │ │ ├── delay.rs │ │ ├── output.rs │ │ ├── splitter.rs │ │ └── input.rs │ └── globals.rs ├── capabilities │ ├── desktop.json │ └── default.json ├── tauri.conf.json └── Cargo.toml ├── site ├── .vscode │ └── extensions.json ├── public │ ├── ss-dark.png │ ├── ss-light.png │ ├── grid.svg │ └── logo.svg ├── src │ └── main.js ├── vite.config.js ├── .gitignore ├── README.md ├── package.json └── index.html ├── docs ├── ss-dark.png ├── ss-light.png ├── grid.svg ├── index.html └── logo.svg ├── src-assets ├── ss-dark.png └── ss-light.png ├── src ├── stores │ └── index.js ├── lib │ └── vnodes │ │ ├── lib │ │ └── svg-pan-zoom │ │ │ ├── browserify.js │ │ │ ├── stand-alone.js │ │ │ └── uniwheel.js │ │ ├── src │ │ ├── main.js │ │ ├── demo │ │ │ ├── Sink.vue │ │ │ ├── CanvasDemo.vue │ │ │ ├── Benchmark.vue │ │ │ ├── Markers.vue │ │ │ ├── Demo.vue │ │ │ ├── Groups.vue │ │ │ └── Labels.vue │ │ ├── themes │ │ │ └── cssutil.js │ │ ├── components │ │ │ ├── Port.vue │ │ │ ├── Group.vue │ │ │ ├── Node.vue │ │ │ ├── Label.vue │ │ │ └── Markers.vue │ │ └── mixins │ │ │ └── drag.js │ │ └── index.js ├── assets │ ├── down-arrow.svg │ ├── close.svg │ ├── play.svg │ ├── minus.svg │ ├── pause.svg │ ├── new-file.svg │ ├── test.svg │ ├── zoom.svg │ ├── broom.svg │ ├── plus.svg │ ├── map.svg │ ├── wrench.svg │ ├── split.svg │ ├── moon.svg │ ├── reload.svg │ ├── minimize.svg │ ├── code.svg │ ├── save.svg │ ├── magnet.svg │ ├── monitor-in.svg │ ├── delay.svg │ ├── piano.svg │ ├── monitor-out.svg │ ├── output.svg │ ├── input.svg │ ├── layout.svg │ ├── expand.svg │ ├── monitor.svg │ ├── note.svg │ ├── script.svg │ ├── sun.svg │ ├── gear.svg │ ├── logo.svg │ ├── GM.INS │ └── virtual.svg ├── main.js ├── components │ ├── SettingsView.vue │ ├── graph │ │ ├── ScriptNode.vue │ │ ├── NoteNode.vue │ │ ├── MonitorNode.vue │ │ ├── IOGroup.vue │ │ └── MapperNode.vue │ ├── inspector │ │ ├── InspDelay.vue │ │ └── ReplacePopup.vue │ ├── global │ │ ├── FlashMessages.vue │ │ ├── forms │ │ │ ├── Checkbox.vue │ │ │ ├── SwitchButton.vue │ │ │ └── NumberInput.vue │ │ ├── Loader.vue │ │ ├── Popup.vue │ │ ├── VirtualScroll.vue │ │ └── ContextMenu.vue │ ├── AboutView.vue │ └── modules │ │ └── ScriptTemplatesPopup.vue └── midi-data.js ├── .vscode ├── extensions.json └── settings.json ├── public └── icons │ ├── down-arrow.svg │ └── down-arrow-dark.svg ├── .gitignore ├── index.html ├── eslint.config.js ├── vite.config.js ├── package.json ├── .github └── workflows │ └── release.yml └── README.md /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /site/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/ss-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/docs/ss-dark.png -------------------------------------------------------------------------------- /docs/ss-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/docs/ss-light.png -------------------------------------------------------------------------------- /site/public/ss-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/site/public/ss-dark.png -------------------------------------------------------------------------------- /src-assets/ss-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-assets/ss-dark.png -------------------------------------------------------------------------------- /src-assets/ss-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-assets/ss-light.png -------------------------------------------------------------------------------- /site/public/ss-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/site/public/ss-light.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src/stores/index.js: -------------------------------------------------------------------------------- 1 | export { default as appStore } from './app'; 2 | export { default as graphStore } from './graph'; -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-512@2x.png -------------------------------------------------------------------------------- /src/lib/vnodes/lib/svg-pan-zoom/browserify.js: -------------------------------------------------------------------------------- 1 | import SvgPanZoom from './svg-pan-zoom.js' 2 | 3 | export default SvgPanZoom; 4 | -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "tauri-apps.tauri-vscode", 5 | "rust-lang.rust-analyzer" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit" 4 | }, 5 | "vue.inlayHints.optionsWrapper": false, 6 | } -------------------------------------------------------------------------------- /site/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import '../../src/styles.styl' 3 | import Site from './Site.vue' 4 | 5 | createApp(Site).mount('#app') 6 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/lib/vnodes/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import Demo from './demo/Demo.vue' 3 | 4 | const demo = createApp(Demo) 5 | demo.mount('#app') 6 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiagolr/mididash/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | mididash_lib::run() 6 | } 7 | -------------------------------------------------------------------------------- /public/icons/down-arrow.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/down-arrow.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/down-arrow-dark.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src-tauri/capabilities/desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "desktop-capability", 3 | "platforms": [ 4 | "macOS", 5 | "windows", 6 | "linux" 7 | ], 8 | "windows": [ 9 | "main" 10 | ], 11 | "permissions": [ 12 | "window-state:default" 13 | ] 14 | } -------------------------------------------------------------------------------- /src/assets/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/grid.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /site/public/grid.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | # .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /site/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import svgLoader from 'vite-svg-loader' 4 | 5 | // https://vite.dev/config/ 6 | export default defineConfig({ 7 | base: './', 8 | plugins: [ 9 | vue(), 10 | svgLoader({ 11 | svgo: false, 12 | defaultImport: 'component' 13 | }) 14 | ], 15 | }) 16 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/assets/play.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/minus.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mididash.com", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "start": "npm run dev", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "vue": "^3.5.13" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^5.2.1", 16 | "stylus": "^0.64.0", 17 | "stylus-loader": "^8.1.1", 18 | "vite": "^6.0.1", 19 | "vite-svg-loader": "^5.1.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Mididash 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": [ 6 | "main" 7 | ], 8 | "permissions": [ 9 | "core:default", 10 | "shell:allow-open", 11 | "window-state:default", 12 | "core:window:allow-destroy", 13 | "core:window:allow-hide", 14 | "core:window:allow-show", 15 | "core:window:allow-set-focus", 16 | "os:default", 17 | "dialog:allow-open", 18 | "dialog:allow-save" 19 | ] 20 | } -------------------------------------------------------------------------------- /src/assets/pause.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/vnodes/lib/svg-pan-zoom/stand-alone.js: -------------------------------------------------------------------------------- 1 | import svgPanZoom from './svg-pan-zoom.js' 2 | 3 | // UMD module definition 4 | (function(window, document){ 5 | // AMD 6 | if (typeof define === 'function' && define.amd) { 7 | define('svg-pan-zoom', function () { 8 | return svgPanZoom; 9 | }); 10 | // CMD 11 | } else if (typeof module !== 'undefined' && module.exports) { 12 | module.exports = svgPanZoom; 13 | 14 | // Browser 15 | // Keep exporting globally as module.exports is available because of browserify 16 | window.svgPanZoom = svgPanZoom; 17 | } 18 | })(window, document) 19 | -------------------------------------------------------------------------------- /src/assets/new-file.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Mididash 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/vnodes/index.js: -------------------------------------------------------------------------------- 1 | import graph from './src/graph' 2 | import Screen from './src/components/Screen.vue' 3 | import Node from './src/components/Node.vue' 4 | import Edge from './src/components/Edge.vue' 5 | import Group from './src/components/Group.vue' 6 | import Port from './src/components/Port.vue' 7 | import Label from './src/components/Label.vue' 8 | 9 | export { graph } 10 | export { Screen } 11 | export { Node } 12 | export { Edge } 13 | export { Group } 14 | export { Port } 15 | export { Label } 16 | export default { 17 | graph, 18 | Screen, 19 | Node, 20 | Edge, 21 | Group, 22 | Port, 23 | Label 24 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import './styles.styl' 2 | import { path } from '@tauri-apps/api'; 3 | import { createApp } from "vue"; 4 | import { createPinia } from 'pinia'; 5 | import App from "./App.vue"; 6 | import appStore from './stores/app' 7 | import graphStore from './stores/graph' 8 | 9 | const pinia = createPinia() 10 | const app = createApp(App) 11 | app.use(pinia) 12 | 13 | // expose stores to all components 14 | app.config.globalProperties.$store = { 15 | app: appStore(), 16 | graph: graphStore() 17 | } 18 | 19 | window.path = path; 20 | window.store = app.config.globalProperties.$store // expose global stores for dev 21 | 22 | app.mount("#app"); 23 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // import standard from 'eslint-config-standard' 2 | import pluginVue from 'eslint-plugin-vue' 3 | 4 | export default [ 5 | // standard, 6 | ...pluginVue.configs['flat/recommended'], 7 | { 8 | 9 | ignores: [ 10 | "node_modules/**", 11 | 'dist/**', 12 | 'src-tauri/**', 13 | '.gitignore' 14 | ], 15 | 16 | rules: { 17 | "vue/html-self-closing": "off", 18 | "vue/max-attributes-per-line": "off", 19 | "comma-dangle": "off", 20 | "vue/singleline-html-element-content-newline": "off", 21 | "brace-style": "off", 22 | "vue/multi-word-component-names": "off", 23 | "vue/require-default-prop": "off", 24 | "vue/no-v-html": "off", 25 | }, 26 | }]; -------------------------------------------------------------------------------- /src-tauri/src/devices/device.rs: -------------------------------------------------------------------------------- 1 | use serde_json::{Value, Error}; 2 | use std::error::Error as StdErr; 3 | 4 | pub trait Device: Send + Sync { 5 | fn get_id(&self) -> &str; 6 | fn get_class(&self) -> &str; 7 | fn destroy(&mut self); 8 | fn serialize(&self) -> Result; 9 | fn init(&mut self) -> Result<(), Box>; 10 | fn get_data(&mut self, key: String) -> Result, String>; 11 | fn set_data(&mut self, key: String, data:Value) -> Result<(), String>; 12 | fn delete_data(&mut self, key: String) -> Result<(), String>; 13 | fn process( 14 | &mut self, 15 | bytes: &Vec, 16 | from: &str, 17 | to: &str, 18 | from_port: &str, 19 | to_port: &str 20 | ) -> Vec<(String, Vec)>; 21 | } -------------------------------------------------------------------------------- /src/assets/test.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/zoom.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/broom.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/plus.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/map.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/wrench.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/split.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "Mididash", 4 | "version": "0.4.2", 5 | "identifier": "com.mididash.app", 6 | "build": { 7 | "beforeDevCommand": "npm run dev", 8 | "devUrl": "http://localhost:1420", 9 | "beforeBuildCommand": "npm run build", 10 | "frontendDist": "../dist" 11 | }, 12 | "app": { 13 | "withGlobalTauri": false, 14 | "windows": [ 15 | { 16 | "fullscreen": false, 17 | "resizable": true, 18 | "title": "Mididash", 19 | "label": "main", 20 | "width": 1150, 21 | "height": 800, 22 | "dragDropEnabled": false 23 | } 24 | ], 25 | "security": { 26 | "csp": null 27 | } 28 | }, 29 | "bundle": { 30 | "active": true, 31 | "targets": "all", 32 | "icon": [ 33 | "icons/32x32.png", 34 | "icons/128x128.png", 35 | "icons/128x128@2x.png", 36 | "icons/icon.icns", 37 | "icons/icon.ico" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | import svgLoader from 'vite-svg-loader' 4 | 5 | const host = process.env.TAURI_DEV_HOST; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig(async () => ({ 9 | plugins: [ 10 | vue(), 11 | svgLoader({ 12 | svgo: false, 13 | defaultImport: 'component' 14 | }) 15 | ], 16 | 17 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 18 | // 19 | // 1. prevent vite from obscuring rust errors 20 | clearScreen: false, 21 | // 2. tauri expects a fixed port, fail if that port is not available 22 | server: { 23 | port: 1420, 24 | strictPort: true, 25 | host: host || false, 26 | hmr: host 27 | ? { 28 | protocol: "ws", 29 | host, 30 | port: 1421, 31 | } 32 | : undefined, 33 | watch: { 34 | // 3. tell vite to ignore watching `src-tauri` 35 | ignored: ["**/src-tauri/**"], 36 | }, 37 | }, 38 | })); 39 | -------------------------------------------------------------------------------- /src/components/SettingsView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/assets/moon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/reload.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/minimize.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/demo/Sink.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 45 | 46 | 48 | 49 | -------------------------------------------------------------------------------- /src/assets/code.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/graph/ScriptNode.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/assets/save.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/inspector/InspDelay.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/assets/magnet.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/monitor-in.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mididash", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "test": "cd src-tauri/ && cargo t && cd .. || cd ..", 8 | "start": "npm run tauri dev", 9 | "dev": "vite", 10 | "build": "vite build", 11 | "preview": "vite preview", 12 | "tauri": "tauri", 13 | "site": " rm -rf docs && cd site && npm run build && mv dist ../docs" 14 | }, 15 | "dependencies": { 16 | "@tauri-apps/api": "^2", 17 | "@tauri-apps/plugin-dialog": "^2.0.1", 18 | "@tauri-apps/plugin-os": "^2.0.0", 19 | "@tauri-apps/plugin-shell": "^2", 20 | "@tauri-apps/plugin-window-state": "^2.0.0", 21 | "ace-builds": "^1.36.5", 22 | "d3-flextree": "^2.1.2", 23 | "path-browserify": "^1.0.1", 24 | "pinia": "^2.2.6", 25 | "semver": "^7.6.3", 26 | "tiny-emitter": "^2.1.0", 27 | "vue": "^3.5.13", 28 | "vue-3-slider-component": "^1.0.1", 29 | "vue3-ace-editor": "^2.2.4" 30 | }, 31 | "devDependencies": { 32 | "@eslint/eslintrc": "^3.2.0", 33 | "@eslint/js": "^9.15.0", 34 | "@tauri-apps/cli": "^2", 35 | "@vitejs/plugin-vue": "^5.0.5", 36 | "eslint": "^8.57.1", 37 | "eslint-config-standard": "^17.1.0", 38 | "eslint-plugin-vue": "^9.31.0", 39 | "globals": "^15.12.0", 40 | "tinycolor2": "^1.6.0", 41 | "vite": "^5.3.1", 42 | "vite-svg-loader": "^5.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src-tauri/src/devices/trigger.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use serde_json::{Value, Error}; 3 | use std::error::Error as StdErr; 4 | use super::device::Device; 5 | 6 | /* 7 | * Delays midi inputs and outputs to display on the viewport 8 | */ 9 | 10 | #[derive(Serialize)] 11 | pub struct Trigger { 12 | pub id: String, 13 | pub class: String, 14 | } 15 | 16 | impl Trigger { 17 | pub fn new(id: &str) -> Self { 18 | Trigger { 19 | id: String::from(id), 20 | class: String::from("trigger"), 21 | } 22 | } 23 | } 24 | 25 | impl Device for Trigger { 26 | fn get_id(&self) -> &str { 27 | return &self.id; 28 | } 29 | fn get_class(&self) -> &str { 30 | return &self.class; 31 | } 32 | fn destroy(&mut self) {} 33 | fn get_data(&mut self, _key: String) -> Result, String> { Ok(None) } 34 | fn set_data(&mut self, _key: String, _data:Value) -> Result<(), String> { Ok(()) } 35 | fn delete_data(&mut self, _key: String) -> Result<(), String> { Ok(()) } 36 | 37 | fn init(&mut self) -> Result<(), Box> { 38 | Ok(()) 39 | } 40 | fn serialize(&self) -> Result { 41 | serde_json::to_value(self) 42 | } 43 | fn process( 44 | &mut self, 45 | _bytes: &Vec, 46 | _from: &str, 47 | _to: &str, 48 | _from_port: &str, 49 | _to_port: &str 50 | ) -> Vec<(String, Vec)> { 51 | vec![] 52 | } 53 | } -------------------------------------------------------------------------------- /src/components/graph/NoteNode.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src-tauri/src/globals.rs: -------------------------------------------------------------------------------- 1 | pub const EVT_MIDI: &str = "midi"; 2 | pub const EVT_SETTINGS_CHANGE: &str = "settings-change"; 3 | pub const EVT_PROJECT_NEW: &str = "project-new"; 4 | pub const EVT_WINDOW_SHOW: &str = "window-show"; 5 | pub const EVT_ERROR: &str = "error"; 6 | pub const EVT_FILE_OPEN: &str = "open-file"; 7 | pub const EVT_FILE_SAVE_AS: &str = "save-file-as"; 8 | pub const EVT_FILE_SAVE: &str = "save-file"; 9 | pub const EVT_SCRIPT_ERROR: &str = "script-error"; 10 | pub const EVT_SCRIPT_LOG: &str = "script-log"; 11 | pub const EVT_SHOW_ABOUT: &str = "show-about"; 12 | 13 | pub const PORT_NOTE_ON: &str = "noteon"; 14 | pub const PORT_NOTE_OFF: &str = "noteoff"; 15 | pub const PORT_AFTERTOUCH: &str = "AT"; 16 | pub const PORT_CC: &str = "CC"; 17 | pub const PORT_PROGRAM: &str = "program"; 18 | pub const PORT_PITCH: &str = "pitch"; 19 | pub const PORT_CHANNEL_AT: &str = "channelAT"; 20 | pub const PORT_REALTIME: &str = "RT"; 21 | pub const PORT_START: &str = "start"; 22 | pub const PORT_CONTINUE: &str = "continue"; 23 | pub const PORT_STOP: &str = "stop"; 24 | pub const PORT_COMMON: &str = "CM"; 25 | pub const PORT_SYSEX: &str = "sysex"; 26 | pub const PORT_UNKNOWN: &str = "unknown"; 27 | 28 | pub const PREFIX_INPUT: &str = "Mdash In - "; 29 | pub const PREFIX_OUTPUT: &str = "Mdash Out - "; 30 | pub const PREFIX_I: &str = "Mdash In - "; // virtual device input 31 | pub const PREFIX_O: &str = "Mdash Out - "; 32 | pub const PREFIX_VI: &str = "Mdash VIn - "; // input for virtual device virtual input 33 | pub const PREFIX_VO: &str = "Mdash VOut - "; 34 | 35 | -------------------------------------------------------------------------------- /src/assets/delay.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src-tauri/src/devices/monitor.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use serde_json::{Value, Error}; 3 | use std::error::Error as StdErr; 4 | use crate::devices::device::Device; 5 | 6 | /* 7 | * Monitors midi inputs and outputs to display on the viewport 8 | */ 9 | 10 | #[derive(Serialize)] 11 | pub struct Monitor { 12 | pub id: String, 13 | pub class: String, 14 | } 15 | 16 | impl Monitor { 17 | pub fn new(id: &str) -> Self { 18 | Monitor { 19 | id: String::from(id), 20 | class: String::from("monitor"), 21 | } 22 | } 23 | } 24 | 25 | impl Device for Monitor { 26 | fn get_id(&self) -> &str { 27 | return &self.id; 28 | } 29 | fn get_class(&self) -> &str { 30 | return &self.class; 31 | } 32 | fn destroy(&mut self) {} 33 | fn get_data(&mut self, _key: String) -> Result, String> { Ok(None) } 34 | fn set_data(&mut self, _key: String, _data:Value) -> Result<(), String> { Ok(()) } 35 | fn delete_data(&mut self, _key: String) -> Result<(), String> { Ok(()) } 36 | 37 | fn init(&mut self) -> Result<(), Box> { 38 | Ok(()) 39 | } 40 | fn serialize(&self) -> Result { 41 | serde_json::to_value(self) 42 | } 43 | fn process( 44 | &mut self, 45 | bytes: &Vec, 46 | _from: &str, 47 | _to: &str, 48 | _from_port: &str, 49 | _to_port: &str 50 | ) -> Vec<(String, Vec)> { 51 | let mut res = vec![]; 52 | res.push(("*".to_string(), bytes.clone())); 53 | res 54 | } 55 | } -------------------------------------------------------------------------------- /src/assets/piano.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/global/FlashMessages.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 29 | 30 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/themes/cssutil.js: -------------------------------------------------------------------------------- 1 | import parse from 'css-parse' 2 | 3 | module.exports = function (name="") { 4 | const themes = { 5 | // load themes here 6 | } 7 | 8 | /** 9 | * Applies a set of css rules on elements of the current page 10 | * @id the name of the theme 11 | */ 12 | 13 | // many ways to swap stylesheets 14 | // https://www.rainbodesign.com/pub/css/css-javascript.html 15 | function applyTheme (id='light', rootComponent=null, rootSel='body') { 16 | let rules 17 | try { 18 | rules = parse(this.theme) 19 | .stylesheet.rules 20 | .filter(r => r.type === 'rule') 21 | } catch (e) { 22 | return; 23 | } 24 | 25 | // TRY 26 | // ref.$forceUpdate() 27 | // ref.$mount() 28 | // await this.forceRender(); - clear last applied theme 29 | 30 | // no need, just apply/remove the stylesheet 31 | 32 | rules.forEach(rule => { 33 | const sel = rule.selectors.length ? '#styles-demo ' + rule.selectors.join(', ') : '' 34 | const els = [...document.querySelectorAll(sel)] 35 | rule.declarations 36 | .filter(dec => dec.type === 'declaration') 37 | .forEach(dec => { 38 | els.filter(el => el).forEach(el => { 39 | const prop = dec.property.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); 40 | el.style[prop] = dec.value 41 | }) 42 | }) 43 | }) 44 | } 45 | 46 | return { 47 | themes, 48 | applyTheme 49 | } 50 | } 51 | 52 | // LAZY LOADING 53 | // main.js 54 | // const getCat = () => import('./cat.js') 55 | // later in the code as a response to some user interaction like click or route change 56 | // getCat() 57 | // .then({ meow } => meow()) -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "Mididash" 3 | version = "0.4.2" 4 | description = "MIDI router with Lua scripting and a node based interface" 5 | authors = ["tilr"] 6 | edition = "2021" 7 | readme = "../README.md" 8 | homepage = "mididash.com" 9 | repository = "https://github.com/tiagolr/mididash" 10 | license = "../LICENSE" 11 | keywords = ["audio", "cross-platform", "router", "midi", "midi-events", "tauri", "audio-midi", "midi-router"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [lib] 16 | # The `_lib` suffix may seem redundant but it is necessary 17 | # to make the lib name unique and wouldn't conflict with the bin name. 18 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 19 | name = "mididash_lib" 20 | crate-type = ["staticlib", "cdylib", "rlib"] 21 | doctest = false 22 | 23 | [build-dependencies] 24 | tauri-build = { version = "2", features = [] } 25 | 26 | [dependencies] 27 | tauri = { version = "2", features = ["tray-icon", "image-png", "image-ico"] } 28 | tauri-plugin-shell = "2" 29 | serde = { version = "1", features = ["derive"] } 30 | serde_json = "1" 31 | midir = "0.10.1" 32 | once_cell = "1.20.2" 33 | serial_test = "3.2.0" 34 | tauri-plugin-store = "2.1.0" 35 | tauri-plugin-os = "2" 36 | regex = "1.11.1" 37 | tauri-plugin-dialog = "2" 38 | tokio = { version = "1.42.0", features = ["full"] } 39 | lazy_static = "1.5.0" 40 | mlua = { version = "0.10.2", features = ["lua54", "vendored", "send"] } 41 | 42 | [target.'cfg(target_os = "macos")'.dependencies] 43 | coremidi = "0.8.0" 44 | 45 | [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] 46 | tauri-plugin-window-state = "2" 47 | 48 | -------------------------------------------------------------------------------- /src/components/global/forms/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 62 | -------------------------------------------------------------------------------- /src/assets/monitor-out.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/output.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/global/Loader.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 30 | 31 | -------------------------------------------------------------------------------- /src/assets/input.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/global/Popup.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | 36 | -------------------------------------------------------------------------------- /src/assets/layout.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/expand.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/monitor.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/components/Port.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 74 | 75 | -------------------------------------------------------------------------------- /src-tauri/src/devices/delay.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use serde_json::{Value, Error}; 3 | use tokio::time::sleep; 4 | use std::{error::Error as StdErr, time::Duration}; 5 | use crate::{app::TOKIO_RUNTIME, devices::device::Device, hub::Hub}; 6 | 7 | /* 8 | * Delays midi inputs and outputs to display on the viewport 9 | */ 10 | 11 | #[derive(Serialize)] 12 | pub struct Delay { 13 | pub id: String, 14 | pub class: String, 15 | pub delay: u64, 16 | } 17 | 18 | impl Delay { 19 | pub fn new(id: &str) -> Self { 20 | Delay { 21 | id: String::from(id), 22 | class: String::from("delay"), 23 | delay: 1000, 24 | } 25 | } 26 | } 27 | 28 | impl Device for Delay { 29 | fn get_id(&self) -> &str { 30 | return &self.id; 31 | } 32 | fn get_class(&self) -> &str { 33 | return &self.class; 34 | } 35 | fn destroy(&mut self) {} 36 | fn get_data(&mut self, _key: String) -> Result, String> { Ok(None) } 37 | fn set_data(&mut self, key: String, data:Value) -> Result<(), String> { 38 | if key == "delay" { 39 | self.delay = data.as_u64().unwrap(); 40 | } 41 | Ok(()) 42 | } 43 | fn delete_data(&mut self, _key: String) -> Result<(), String> { Ok(()) } 44 | 45 | fn init(&mut self) -> Result<(), Box> { 46 | Ok(()) 47 | } 48 | fn serialize(&self) -> Result { 49 | serde_json::to_value(self) 50 | } 51 | fn process( 52 | &mut self, 53 | bytes: &Vec, 54 | _from: &str, 55 | _to: &str, 56 | _from_port: &str, 57 | _to_port: &str 58 | ) -> Vec<(String, Vec)> { 59 | let delay = self.delay; 60 | let bytes = bytes.clone(); 61 | let id = self.id.clone(); 62 | let runtime = TOKIO_RUNTIME.lock().unwrap(); 63 | 64 | runtime.spawn(async move { 65 | sleep(Duration::from_millis(delay)).await; 66 | let hub_instance = Hub::get_instance(); 67 | let mut hub = hub_instance.lock().unwrap(); 68 | hub.process(0, &bytes, &id, "*", "*", "*"); 69 | }); 70 | 71 | vec![] 72 | } 73 | } -------------------------------------------------------------------------------- /src/components/global/VirtualScroll.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 82 | 83 | 94 | -------------------------------------------------------------------------------- /src/components/global/forms/SwitchButton.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 26 | 104 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/components/Group.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 68 | 69 | -------------------------------------------------------------------------------- /src/assets/note.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/AboutView.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/assets/script.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/sun.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/demo/CanvasDemo.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 71 | 72 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'publish' 2 | 3 | on: 4 | push: 5 | branches: 6 | - release 7 | 8 | jobs: 9 | release: 10 | permissions: 11 | contents: write 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - platform: 'macos-latest' # for Arm based macs (M1 and above). 17 | args: '--target aarch64-apple-darwin' 18 | - platform: 'macos-latest' # for Intel based macs. 19 | args: '--target x86_64-apple-darwin' 20 | - platform: 'ubuntu-22.04' 21 | args: '' 22 | - platform: 'windows-latest' 23 | args: '' 24 | 25 | runs-on: ${{ matrix.platform }} 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: install dependencies (ubuntu only) 30 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. 31 | run: | 32 | sudo apt-get update 33 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libasound2-dev pkg-config 34 | 35 | - name: setup node 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: 22 39 | cache: 'npm' # Set this to npm, yarn or pnpm. 40 | 41 | - name: install Rust stable 42 | uses: dtolnay/rust-toolchain@stable # Set this to dtolnay/rust-toolchain@nightly 43 | with: 44 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. 45 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} 46 | 47 | - name: Rust cache 48 | uses: swatinem/rust-cache@v2 49 | with: 50 | workspaces: './src-tauri -> target' 51 | 52 | - name: install frontend dependencies 53 | # If you don't have `beforeBuildCommand` configured you may want to build your frontend here too. 54 | run: npm install # change this to npm or pnpm depending on which one you use. 55 | 56 | - uses: tauri-apps/tauri-action@v0 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | with: 60 | tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. 61 | releaseName: 'Mididash v__VERSION__' 62 | releaseBody: 'Another day another build' 63 | releaseDraft: true 64 | prerelease: false 65 | args: ${{ matrix.args }} -------------------------------------------------------------------------------- /src/assets/gear.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/demo/Benchmark.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 74 | 75 | -------------------------------------------------------------------------------- /src/components/inspector/ReplacePopup.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/components/graph/MonitorNode.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/demo/Markers.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 80 | 81 | -------------------------------------------------------------------------------- /src/components/graph/IOGroup.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | 86 | 87 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/mixins/drag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds drag behavior to Vue component 3 | * @drag event emmited 4 | */ 5 | export default { 6 | props: { 7 | dragThreshold: { 8 | type: Number, 9 | default: 2 10 | } 11 | }, 12 | data () { 13 | return { 14 | drag: { 15 | zoom: 1, 16 | active: false, 17 | prev: { x: 0, y: 0 }, 18 | threshold: { x: 0, y: 0, crossed: false } 19 | } 20 | } 21 | }, 22 | methods: { 23 | preventClicks (e) { 24 | if (this.drag.threshold.crossed) { 25 | e.preventDefault() 26 | e.stopPropagation() 27 | e.stopImmediatePropagation() 28 | document.removeEventListener('click', this.preventClicks, true) 29 | } 30 | }, 31 | startDrag (e) { 32 | let parent = this.$parent 33 | while (parent) { 34 | if (parent.panzoom) { 35 | this.drag.zoom = parent.panzoom.getZoom() 36 | break; 37 | } 38 | parent = parent.$parent 39 | } 40 | // touch normalize 41 | if (e.touches && e.touches.length) { 42 | e.clientX = e.touches[0].clientX 43 | e.clientY = e.touches[0].clientY 44 | } 45 | this.drag.active = true 46 | this.drag.prev = { x: e.clientX, y: e.clientY } 47 | this.drag.threshold = {x: 0, y: 0, crossed: false} 48 | document.addEventListener('mouseup', this.stopDrag) 49 | document.addEventListener('touchend', this.stopDrag) 50 | document.addEventListener('mousemove', this.applyDrag, { passive: true }) 51 | document.addEventListener('touchmove', this.applyDrag, { passive: true }) 52 | document.addEventListener('click', this.preventClicks, true) 53 | }, 54 | stopDrag () { 55 | this.drag.active = false 56 | document.removeEventListener('mouseup', this.stopDrag) 57 | document.removeEventListener('touchend', this.stopDrag) 58 | document.removeEventListener('mousemove', this.applyDrag, { passive: true }) 59 | document.removeEventListener('touchmove', this.applyDrag, { passive: true }) 60 | }, 61 | applyDrag (e) { 62 | if (e.touches && e.touches.length) { 63 | e.clientX = e.touches[0].clientX 64 | e.clientY = e.touches[0].clientY 65 | } 66 | let x = (e.clientX - this.drag.prev.x) / this.drag.zoom 67 | let y = (e.clientY - this.drag.prev.y) / this.drag.zoom 68 | this.drag.prev = {x: e.clientX, y: e.clientY} 69 | 70 | if (!this.drag.threshold.crossed) { 71 | if (Math.abs(this.drag.threshold.x) < this.dragThreshold && Math.abs(this.drag.threshold.y) < this.dragThreshold) { 72 | this.drag.threshold.x += x 73 | this.drag.threshold.y += y 74 | return // don't apply drag until threshold is reached 75 | } else { 76 | this.drag.threshold.crossed = true 77 | x += this.drag.threshold.x 78 | y += this.drag.threshold.y 79 | } 80 | } 81 | this.onDrag({ x, y }) 82 | }, 83 | }, 84 | beforeDestroy () { 85 | document.removeEventListener('mousemove', this.applyDrag) 86 | document.removeEventListener('mouseup', this.stopDrag) 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/components/Node.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 102 | 103 | -------------------------------------------------------------------------------- /src/components/global/ContextMenu.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/demo/Demo.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 71 | 72 | 83 | 84 | 129 | -------------------------------------------------------------------------------- /src/components/graph/MapperNode.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/demo/Groups.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 96 | 97 | 106 | 107 | -------------------------------------------------------------------------------- /src-tauri/src/devices/output.rs: -------------------------------------------------------------------------------- 1 | use midir::{MidiOutput, MidiOutputConnection}; 2 | use serde::Serialize; 3 | use serde_json::{Value, Error}; 4 | use std::error::Error as StdErr; 5 | use std::sync::Mutex; 6 | 7 | use crate::devices::device::Device; 8 | use crate::globals::PREFIX_OUTPUT; 9 | 10 | /* 11 | * Output for midi hardware 12 | */ 13 | 14 | #[derive(Serialize)] 15 | pub struct Output { 16 | pub id: String, 17 | pub class: String, 18 | #[serde(skip_serializing)] 19 | conn: Option>, 20 | } 21 | 22 | impl Output { 23 | pub fn new(id: &str) -> Self { 24 | let preid = if id.starts_with(PREFIX_OUTPUT) { id } else { &format!("{}{}", PREFIX_OUTPUT, id) }; 25 | Output { 26 | id: String::from(preid), 27 | class: String::from("output"), 28 | conn: None, 29 | } 30 | } 31 | } 32 | 33 | impl Device for Output { 34 | fn init(&mut self) -> Result<(), Box> { 35 | let output = MidiOutput::new(&self.id)?; 36 | let ports = &output.ports(); 37 | let mut found = false; 38 | let mut idx = 0; 39 | for (i, p) in ports.iter().enumerate() { 40 | if &output.port_name(p)? == &self.id.strip_prefix(PREFIX_OUTPUT).unwrap() { 41 | found = true; 42 | idx = i; 43 | break; 44 | } 45 | } 46 | if !found { 47 | let str = String::from("Device port not found ") + &self.id; 48 | return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, str))); 49 | } 50 | let port = &ports[idx]; 51 | let id = self.id.clone(); 52 | self.conn = Some(Mutex::new(output.connect(port, &id)?)); 53 | Ok(()) 54 | } 55 | fn get_id(&self) -> &str { 56 | return &self.id; 57 | } 58 | fn get_class(&self) -> &str { 59 | return &self.class; 60 | } 61 | fn get_data(&mut self, _key: String) -> Result, String> { Ok(None) } 62 | fn set_data(&mut self, _key: String, _data:Value) -> Result<(), String> { Ok(()) } 63 | fn delete_data(&mut self, _key: String) -> Result<(), String> { Ok(()) } 64 | fn destroy(&mut self) { 65 | if let Some(mutex) = self.conn.take() { 66 | let conn = mutex.into_inner().unwrap(); 67 | conn.close(); 68 | } 69 | } 70 | 71 | fn serialize(&self) -> Result { 72 | serde_json::to_value(self) 73 | } 74 | fn process( 75 | &mut self, 76 | bytes: &Vec, 77 | _from: &str, 78 | _to: &str, 79 | _from_port: &str, 80 | _to_port: &str 81 | ) -> Vec<(String, Vec)> { 82 | if let Some(ref mutex) = self.conn { 83 | let mut conn = mutex.lock().unwrap(); 84 | if let Err(e) = conn.send(bytes) { // send message to midi channel this output is connected to 85 | eprintln!("Error sending bytes from output {} {}", self.id, e); 86 | } 87 | } 88 | vec![] // ignore forward processing 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | use midir::MidiOutput; 96 | use serial_test::serial; 97 | 98 | #[test] 99 | #[serial] 100 | fn create_destroy() { 101 | let mut input = Output::new("Test1234"); 102 | input.destroy(); 103 | } 104 | 105 | #[test] 106 | #[serial] 107 | fn init() { 108 | let inp = MidiOutput::new("Test").expect("Failed to create input"); 109 | let ports = inp.ports(); 110 | if ports.len() > 0 { 111 | let pname = inp.port_name(&ports[0]).expect("Get port name error"); 112 | let mut input = Output::new(&pname); 113 | input.init().expect("Failed to connect"); 114 | input.destroy(); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /src/lib/vnodes/src/demo/Labels.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 107 | 108 | -------------------------------------------------------------------------------- /src-tauri/src/devices/splitter.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde::Serialize; 3 | use serde_json::{Value, Error}; 4 | use std::error::Error as StdErr; 5 | use crate::{devices::device::Device, globals::{PORT_AFTERTOUCH, PORT_CC, PORT_CHANNEL_AT, PORT_COMMON, PORT_CONTINUE, PORT_NOTE_OFF, PORT_NOTE_ON, PORT_PITCH, PORT_PROGRAM, PORT_REALTIME, PORT_START, PORT_STOP, PORT_SYSEX, PORT_UNKNOWN}, utils::{parse_midi, MIDI_AFTERTOUCH, MIDI_CC, MIDI_CHANNEL_AT, 6 | MIDI_EXT_ACTIVE_SNS, MIDI_EXT_CLOCK, MIDI_EXT_CONTINUE, MIDI_EXT_MTC, MIDI_EXT_POSITION, 7 | MIDI_EXT_RESET, MIDI_EXT_SELECT, MIDI_EXT_START, MIDI_EXT_STOP, MIDI_EXT_SYSEX, 8 | MIDI_EXT_TUNE, MIDI_NOTE_OFF, MIDI_NOTE_ON, MIDI_PITCH, MIDI_PROG_CHNG 9 | }}; 10 | 11 | /* 12 | * Input for midi hardware 13 | */ 14 | #[derive(Serialize)] 15 | pub struct Splitter { 16 | pub id: String, 17 | pub class: String, 18 | #[serde(skip_serializing)] 19 | pub sysex: HashMap> 20 | } 21 | 22 | impl Splitter { 23 | pub fn new(id: &str) -> Self { 24 | Splitter { 25 | id: String::from(id), 26 | class: String::from("split"), 27 | sysex: HashMap::new() 28 | } 29 | } 30 | } 31 | 32 | impl Device for Splitter { 33 | fn get_id(&self) -> &str { &self.id } 34 | fn get_class(&self) -> &str { &self.class } 35 | fn destroy(&mut self) {} 36 | fn get_data(&mut self, _key: String) -> Result, String> { Ok(None) } 37 | fn set_data(&mut self, _key: String, _data:Value) -> Result<(), String> { Ok(()) } 38 | fn delete_data(&mut self, _key: String) -> Result<(), String> { Ok(()) } 39 | fn init(&mut self) -> Result<(), Box> { Ok(()) } 40 | 41 | fn serialize(&self) -> Result { 42 | serde_json::to_value(self) 43 | } 44 | fn process( 45 | &mut self, 46 | bytes: &Vec, 47 | from: &str, 48 | _to: &str, 49 | from_port: &str, 50 | _to_port: &str 51 | ) -> Vec<(String, Vec)> { 52 | let src = from.to_string() + from_port; 53 | if !self.sysex.contains_key(&src) { 54 | self.sysex.insert(src.clone(), vec![]); 55 | } 56 | let mut results = vec![]; 57 | let sysex = self.sysex.get_mut(&src); 58 | let events = parse_midi(bytes.clone(), sysex.unwrap()); 59 | for event in events { 60 | let name = event.0; 61 | let channel = event.1; 62 | let bytes = event.2; 63 | let port = match name { 64 | MIDI_NOTE_OFF => PORT_NOTE_OFF, 65 | MIDI_NOTE_ON => PORT_NOTE_ON, 66 | MIDI_AFTERTOUCH => PORT_AFTERTOUCH, 67 | MIDI_CHANNEL_AT => PORT_CHANNEL_AT, 68 | MIDI_CC => PORT_CC, 69 | MIDI_PROG_CHNG => PORT_PROGRAM, 70 | MIDI_PITCH => PORT_PITCH, 71 | MIDI_EXT_SYSEX => PORT_SYSEX, 72 | MIDI_EXT_MTC | MIDI_EXT_POSITION | MIDI_EXT_SELECT | MIDI_EXT_TUNE => PORT_COMMON, 73 | MIDI_EXT_CLOCK | MIDI_EXT_START | MIDI_EXT_CONTINUE | MIDI_EXT_STOP | MIDI_EXT_ACTIVE_SNS | MIDI_EXT_RESET => PORT_REALTIME, 74 | _ => PORT_UNKNOWN 75 | }; 76 | 77 | results.push(("*".to_string(), bytes.clone())); 78 | results.push((port.to_string(), bytes.clone())); 79 | 80 | if port == MIDI_EXT_START { 81 | results.push((PORT_START.to_string(), bytes.clone())); 82 | } 83 | else if port == MIDI_EXT_CONTINUE { 84 | results.push((PORT_CONTINUE.to_string(), bytes.clone())); 85 | } 86 | else if port == MIDI_EXT_STOP { 87 | results.push((PORT_STOP.to_string(), bytes.clone())); 88 | } 89 | 90 | if channel != 0xFF { 91 | results.push(((channel + 1).to_string(), bytes.clone())) 92 | } 93 | } 94 | 95 | 96 | results 97 | } 98 | } -------------------------------------------------------------------------------- /src-tauri/src/devices/input.rs: -------------------------------------------------------------------------------- 1 | use midir::{Ignore, MidiInput, MidiInputConnection}; 2 | use serde::Serialize; 3 | use serde_json::{Value, Error}; 4 | use std::error::Error as StdErr; 5 | use std::sync::Mutex; 6 | 7 | use crate::hub::Hub; 8 | use crate::devices::device::Device; 9 | 10 | use crate::globals::PREFIX_INPUT; 11 | 12 | /* 13 | * Input for midi hardware 14 | */ 15 | 16 | #[derive(Serialize)] 17 | pub struct Input { 18 | pub id: String, 19 | pub class: String, 20 | #[serde(skip_serializing)] 21 | conn: Option>>, 22 | } 23 | 24 | impl Input { 25 | pub fn new(id: &str) -> Self { 26 | let preid = if id.starts_with(PREFIX_INPUT) { id } else { &format!("{}{}", PREFIX_INPUT, id) }; 27 | Input { 28 | id: String::from(preid), 29 | class: String::from("input"), 30 | conn: None, 31 | } 32 | } 33 | } 34 | 35 | impl Device for Input { 36 | fn get_id(&self) -> &str { 37 | return &self.id; 38 | } 39 | fn get_class(&self) -> &str { 40 | return &self.class; 41 | } 42 | fn destroy(&mut self) { 43 | if let Some(mutex) = self.conn.take() { 44 | let conn = mutex.into_inner().unwrap(); 45 | conn.close(); 46 | } 47 | } 48 | fn get_data(&mut self, _key: String) -> Result, String> { Ok(None) } 49 | fn set_data(&mut self, _key: String, _data:Value) -> Result<(), String> { Ok(()) } 50 | fn delete_data(&mut self, _key: String) -> Result<(), String> { Ok(()) } 51 | 52 | fn init(&mut self) -> Result<(), Box> { 53 | let mut input = MidiInput::new(&self.id)?; 54 | input.ignore(Ignore::None); 55 | let ports = &input.ports(); 56 | let mut found = false; 57 | let mut idx = 0; 58 | for (i, p) in ports.iter().enumerate() { 59 | if &input.port_name(p)? == &self.id.strip_prefix(PREFIX_INPUT).unwrap() { 60 | found = true; 61 | idx = i; 62 | break; 63 | } 64 | } 65 | if !found { 66 | let str = String::from("Device port not found ") + &self.id; 67 | Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, str)))?; 68 | } 69 | let port = &ports[idx]; 70 | let id = self.id.clone(); 71 | self.conn = Some(Mutex::new(input.connect( 72 | &port, 73 | &id.clone(), 74 | move |ts, bytes, _| { 75 | let hub_instance = Hub::get_instance(); 76 | let mut hub = hub_instance.lock().unwrap(); 77 | hub.process(ts, &bytes.to_vec(), &id, "*", "*", "*"); 78 | }, 79 | () 80 | )?)); 81 | 82 | Ok(()) 83 | } 84 | fn serialize(&self) -> Result { 85 | serde_json::to_value(self) 86 | } 87 | fn process( 88 | &mut self, 89 | _bytes: &Vec, 90 | _from: &str, 91 | _to: &str, 92 | _from_port: &str, 93 | _to_port: &str 94 | ) -> Vec<(String, Vec)> { 95 | vec![] 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use midir::MidiInput; 103 | use serial_test::serial; 104 | 105 | #[test] 106 | #[serial] 107 | fn create_destroy() { 108 | let mut input = Input::new("Test1234"); 109 | input.destroy(); 110 | } 111 | 112 | #[test] 113 | #[serial] 114 | fn init() { 115 | let inp = MidiInput::new("Test").expect("Failed to create input"); 116 | let ports = inp.ports(); 117 | if ports.len() > 0 { 118 | let pname = inp.port_name(&ports[0]).expect("Get port name error"); 119 | let mut input = Input::new(&pname); 120 | input.init().expect("Failed to connect"); 121 | input.destroy(); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /src/lib/vnodes/src/components/Label.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 122 | 123 | 133 | -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /site/public/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src-tauri/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/global/forms/NumberInput.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/components/modules/ScriptTemplatesPopup.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/assets/GM.INS: -------------------------------------------------------------------------------- 1 | 2 | ; ---------------------------------------------------------------------- 3 | 4 | .Patch Names 5 | 6 | [General MIDI] 7 | 0=Acoustic Grand Piano 8 | 1=Bright Acoustic Piano 9 | 2=Electric Grand Piano 10 | 3=Honky-tonk Piano 11 | 4=Rhodes Piano 12 | 5=Chorused Piano 13 | 6=Harpsichord 14 | 7=Clavinet 15 | 8=Celesta 16 | 9=Glockenspiel 17 | 10=Music Box 18 | 11=Vibraphone 19 | 12=Marimba 20 | 13=Xylophone 21 | 14=Tubular Bells 22 | 15=Dulcimer 23 | 16=Hammond Organ 24 | 17=Percussive Organ 25 | 18=Rock Organ 26 | 19=Church Organ 27 | 20=Reed Organ 28 | 21=Accordion 29 | 22=Harmonica 30 | 23=Tango Accordion 31 | 24=Acoustic Guitar (nylon) 32 | 25=Acoustic Guitar (steel) 33 | 26=Electric Guitar (jazz) 34 | 27=Electric Guitar (clean) 35 | 28=Electric Guitar (muted) 36 | 29=Overdriven Guitar 37 | 30=Distortion Guitar 38 | 31=Guitar Harmonics 39 | 32=Acoustic Bass 40 | 33=Electric Bass (finger) 41 | 34=Electric Bass (pick) 42 | 35=Fretless Bass 43 | 36=Slap Bass 1 44 | 37=Slap Bass 2 45 | 38=Synth Bass 1 46 | 39=Synth Bass 2 47 | 40=Violin 48 | 41=Viola 49 | 42=Cello 50 | 43=Contrabass 51 | 44=Tremolo Strings 52 | 45=Pizzicato Strings 53 | 46=Orchestral Harp 54 | 47=Timpani 55 | 48=String Ensemble 1 56 | 49=String Ensemble 2 57 | 50=SynthStrings 1 58 | 51=SynthStrings 2 59 | 52=Choir Aahs 60 | 53=Voice Oohs 61 | 54=Synth Voice 62 | 55=Orchestra Hit 63 | 56=Trumpet 64 | 57=Trombone 65 | 58=Tuba 66 | 59=Muted Trumpet 67 | 60=French Horn 68 | 61=Brass Section 69 | 62=Synth Brass 1 70 | 63=Synth Brass 2 71 | 64=Soprano Sax 72 | 65=Alto Sax 73 | 66=Tenor Sax 74 | 67=Baritone Sax 75 | 68=Oboe 76 | 69=English Horn 77 | 70=Bassoon 78 | 71=Clarinet 79 | 72=Piccolo 80 | 73=Flute 81 | 74=Recorder 82 | 75=Pan Flute 83 | 76=Bottle Blow 84 | 77=Shakuhachi 85 | 78=Whistle 86 | 79=Ocarina 87 | 80=Lead 1 (square) 88 | 81=Lead 2 (sawtooth) 89 | 82=Lead 3 (calliope lead) 90 | 83=Lead 4 (chiff lead) 91 | 84=Lead 5 (charang) 92 | 85=Lead 6 (voice) 93 | 86=Lead 7 (fifths) 94 | 87=Lead 8 (bass + lead) 95 | 88=Pad 1 (new age) 96 | 89=Pad 2 (warm) 97 | 90=Pad 3 (polysynth) 98 | 91=Pad 4 (choir) 99 | 92=Pad 5 (bowed) 100 | 93=Pad 6 (metallic) 101 | 94=Pad 7 (halo) 102 | 95=Pad 8 (sweep) 103 | 96=FX 1 (rain) 104 | 97=FX 2 (soundtrack) 105 | 98=FX 3 (crystal) 106 | 99=FX 4 (atmosphere) 107 | 100=FX 5 (brightness) 108 | 101=FX 6 (goblins) 109 | 102=FX 7 (echoes) 110 | 103=FX 8 (sci-fi) 111 | 104=Sitar 112 | 105=Banjo 113 | 106=Shamisen 114 | 107=Koto 115 | 108=Kalimba 116 | 109=Bagpipe 117 | 110=Fiddle 118 | 111=Shanai 119 | 112=Tinkle Bell 120 | 113=Agogo 121 | 114=Steel Drums 122 | 115=Woodblock 123 | 116=Taiko Drum 124 | 117=Melodic Tom 125 | 118=Synth Drum 126 | 119=Reverse Cymbal 127 | 120=Guitar Fret Noise 128 | 121=Breath Noise 129 | 122=Seashore 130 | 123=Bird Tweet 131 | 124=Telephone Ring 132 | 125=Helicopter 133 | 126=Applause 134 | 127=Gunshot 135 | 136 | ; ---------------------------------------------------------------------- 137 | 138 | .Note Names 139 | 140 | [General MIDI Drums] 141 | 35=Acoustic Bass Drum 142 | 36=Bass Drum 1 143 | 37=Side Stick 144 | 38=Acoustic Snare 145 | 39=Hand Clap 146 | 40=Electric Snare 147 | 41=Low Floor Tom 148 | 42=Close Hi-Hat 149 | 43=High Floor Tom 150 | 44=Pedal Hi-Hat 151 | 45=Low Tom 152 | 46=Open Hi-Hat 153 | 47=Low-Mid Tom 154 | 48=Hi-Mid Tom 155 | 49=Crash Cymbal 1 156 | 50=High Tom 157 | 51=Ride Cymbal 1 158 | 52=Chinese Cymbal 159 | 53=Ride Bell 160 | 54=Tambourine 161 | 55=Splash Cymbal 162 | 56=Cowbell 163 | 57=Crash Cymbal 2 164 | 58=Vibraslap 165 | 59=Ride Cymbal 2 166 | 60=Hi Bongo 167 | 61=Low Bongo 168 | 62=Mute Hi Conga 169 | 63=Open Hi Conga 170 | 64=Low Conga 171 | 65=High Timbale 172 | 66=Low Timbale 173 | 67=High Agogo 174 | 68=Low Agogo 175 | 69=Cabasa 176 | 70=Maracas 177 | 71=Short Whistle 178 | 72=Long Whistle 179 | 73=Short Guiro 180 | 74=Long Guiro 181 | 75=Claves 182 | 76=Hi Wood Block 183 | 77=Low Wood Block 184 | 78=Mute Cuica 185 | 79=Open Cuica 186 | 80=Mute Triangle 187 | 81=Open Triangle 188 | 189 | ; ---------------------------------------------------------------------- 190 | 191 | .Instrument Definitions 192 | 193 | [General MIDI] 194 | Patch[0]=General MIDI 195 | Patch[*]=General MIDI 196 | 197 | [General MIDI Drums] 198 | Patch[*]=1..128 199 | Key[*,*]=General MIDI Drums 200 | Drum[*,*]=1 201 | -------------------------------------------------------------------------------- /src/lib/vnodes/lib/svg-pan-zoom/uniwheel.js: -------------------------------------------------------------------------------- 1 | // uniwheel 0.1.2 (customized) 2 | // A unified cross browser mouse wheel event handler 3 | // https://github.com/teemualap/uniwheel 4 | 5 | export default (function(){ 6 | 7 | //Full details: https://developer.mozilla.org/en-US/docs/Web/Reference/Events/wheel 8 | 9 | var prefix = "", _addEventListener, _removeEventListener, support, fns = []; 10 | var passiveOption = {passive: true}; 11 | 12 | // detect event model 13 | if ( window.addEventListener ) { 14 | _addEventListener = "addEventListener"; 15 | _removeEventListener = "removeEventListener"; 16 | } else { 17 | _addEventListener = "attachEvent"; 18 | _removeEventListener = "detachEvent"; 19 | prefix = "on"; 20 | } 21 | 22 | // detect available wheel event 23 | support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel" 24 | document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel" 25 | "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox 26 | 27 | 28 | function createCallback(element,callback) { 29 | 30 | var fn = function(originalEvent) { 31 | 32 | !originalEvent && ( originalEvent = window.event ); 33 | 34 | // create a normalized event object 35 | var event = { 36 | // keep a ref to the original event object 37 | originalEvent: originalEvent, 38 | target: originalEvent.target || originalEvent.srcElement, 39 | type: "wheel", 40 | deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1, 41 | deltaX: 0, 42 | delatZ: 0, 43 | preventDefault: function() { 44 | originalEvent.preventDefault ? 45 | originalEvent.preventDefault() : 46 | originalEvent.returnValue = false; 47 | } 48 | }; 49 | 50 | // calculate deltaY (and deltaX) according to the event 51 | if ( support == "mousewheel" ) { 52 | event.deltaY = - 1/40 * originalEvent.wheelDelta; 53 | // Webkit also support wheelDeltaX 54 | originalEvent.wheelDeltaX && ( event.deltaX = - 1/40 * originalEvent.wheelDeltaX ); 55 | } else { 56 | event.deltaY = originalEvent.detail; 57 | } 58 | 59 | // it's time to fire the callback 60 | return callback( event ); 61 | 62 | }; 63 | 64 | fns.push({ 65 | element: element, 66 | fn: fn, 67 | }); 68 | 69 | return fn; 70 | } 71 | 72 | function getCallback(element) { 73 | for (var i = 0; i < fns.length; i++) { 74 | if (fns[i].element === element) { 75 | return fns[i].fn; 76 | } 77 | } 78 | return function(){}; 79 | } 80 | 81 | function removeCallback(element) { 82 | for (var i = 0; i < fns.length; i++) { 83 | if (fns[i].element === element) { 84 | return fns.splice(i,1); 85 | } 86 | } 87 | } 88 | 89 | function _addWheelListener(elem, eventName, callback, isPassiveListener ) { 90 | var cb; 91 | 92 | if (support === "wheel") { 93 | cb = callback; 94 | } else { 95 | cb = createCallback(elem, callback); 96 | } 97 | 98 | elem[_addEventListener](prefix + eventName, cb, isPassiveListener ? passiveOption : false); 99 | } 100 | 101 | function _removeWheelListener(elem, eventName, callback, isPassiveListener ) { 102 | 103 | var cb; 104 | 105 | if (support === "wheel") { 106 | cb = callback; 107 | } else { 108 | cb = getCallback(elem); 109 | } 110 | 111 | elem[_removeEventListener](prefix + eventName, cb, isPassiveListener ? passiveOption : false); 112 | 113 | removeCallback(elem); 114 | } 115 | 116 | function addWheelListener( elem, callback, isPassiveListener ) { 117 | _addWheelListener(elem, support, callback, isPassiveListener ); 118 | 119 | // handle MozMousePixelScroll in older Firefox 120 | if( support == "DOMMouseScroll" ) { 121 | _addWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener ); 122 | } 123 | } 124 | 125 | function removeWheelListener(elem, callback, isPassiveListener){ 126 | _removeWheelListener(elem, support, callback, isPassiveListener); 127 | 128 | // handle MozMousePixelScroll in older Firefox 129 | if( support == "DOMMouseScroll" ) { 130 | _removeWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener); 131 | } 132 | } 133 | 134 | return { 135 | on: addWheelListener, 136 | off: removeWheelListener 137 | }; 138 | 139 | })(); 140 | -------------------------------------------------------------------------------- /src/midi-data.js: -------------------------------------------------------------------------------- 1 | export const CCHR = { 2 | '00/32': 'BankSel HR', 3 | '01/33': 'Mod Whl HR', 4 | '02/34': 'Breath HR', 5 | '03/35': 'HR', 6 | '04/36': 'Foot P HR', 7 | '05/37': 'Porta HR', 8 | '06/38': 'Data E. HR', 9 | '07/39': 'Vol HR', 10 | '08/40': 'Balance HR', 11 | '09/41': 'HR', 12 | '10/42': 'Pan HR', 13 | '11/43': 'Exprss HR', 14 | '12/44': 'Ctrl 1 HR', 15 | '13/45': 'Ctrl 2 HR', 16 | '14/46': 'HR', 17 | '15/47': 'HR', 18 | '16/48': 'GP Sldr HR', 19 | '17/49': 'GP Sldr HR', 20 | '18/50': 'GP Sldr HR', 21 | '19/51': 'GP Sldr HR', 22 | '20/52': 'HR', 23 | '21/53': 'HR', 24 | '22/54': 'HR', 25 | '23/55': 'HR', 26 | '24/56': 'HR', 27 | '25/57': 'HR', 28 | '26/58': 'HR', 29 | '27/59': 'HR', 30 | '28/60': 'HR', 31 | '29/61': 'HR', 32 | '30/62': 'HR', 33 | '31/63': 'HR', 34 | } 35 | 36 | export const CC = { 37 | '0':'BankSel M', 38 | '1':'Mod Whl M', 39 | '2':'Breath M', 40 | '3':'', 41 | '4':'Foot P M', 42 | '5':'Porta M', 43 | '6':'Data E. M', 44 | '7':'Vol M', 45 | '8':'Balance M', 46 | '9':'', 47 | '10':'Pan M', 48 | '11':'Express M', 49 | '12':'Ctrl 1 M', 50 | '13':'Ctrl 2 M', 51 | '14':'', 52 | '15':'', 53 | '16':'GP 1', 54 | '17':'GP 2', 55 | '18':'GP 3', 56 | '19':'GP 4', 57 | '20':'', 58 | '21':'', 59 | '22':'', 60 | '23':'', 61 | '24':'', 62 | '25':'', 63 | '26':'', 64 | '27':'', 65 | '28':'', 66 | '29':'', 67 | '30':'', 68 | '31':'', 69 | '32':'BankSel L', 70 | '33':'Mod Whl L', 71 | '34':'Breath L', 72 | '35':'', 73 | '36':'Foot P L', 74 | '37':'Porta L', 75 | '38':'Data E. L', 76 | '39':'Vol L', 77 | '40':'Balance L', 78 | '41':'', 79 | '42':'Pan L', 80 | '43':'Express L', 81 | '44':'Ctrl 1 L', 82 | '45':'Ctrl 2 L', 83 | '46':'', 84 | '47':'', 85 | '48':'', 86 | '49':'', 87 | '50':'', 88 | '51':'', 89 | '52':'', 90 | '53':'', 91 | '54':'', 92 | '55':'', 93 | '56':'', 94 | '57':'', 95 | '58':'', 96 | '59':'', 97 | '60':'', 98 | '61':'', 99 | '62':'', 100 | '63':'', 101 | '64':'Hold P', 102 | '65':'Porta', 103 | '66':'Sosten', 104 | '67':'Soft P', 105 | '68':'LegatoP', 106 | '69':'Hold2 P', 107 | '70':'S.Variatn', 108 | '71':'S.Timbre', 109 | '72':'S.Release', 110 | '73':'S.Attack', 111 | '74':'S.Bright', 112 | '75':'S.Ctrl 6', 113 | '76':'S.Ctrl 7', 114 | '77':'S.Ctrl 8', 115 | '78':'S.Ctrl 9', 116 | '79':'S.Ctrl 10', 117 | '80':'GP B.1 sw', 118 | '81':'GP B.2 sw', 119 | '82':'GP B.3 sw', 120 | '83':'GP B.4 sw', 121 | '84':'Porta Ctrl', 122 | '85':'', 123 | '86':'', 124 | '87':'', 125 | '88':'', 126 | '89':'', 127 | '90':'', 128 | '91':'Effects', 129 | '92':'Trem', 130 | '93':'Chorus', 131 | '94':'Celeste', 132 | '95':'Phaser', 133 | '96':'Data B.Inc', 134 | '97':'Data B.Dec', 135 | '98':'NRP LSB', 136 | '99':'NRP MSB', 137 | '100':'RP LSB', 138 | '101':'RP MSB', 139 | '102':'', 140 | '103':'', 141 | '104':'', 142 | '105':'', 143 | '106':'', 144 | '107':'', 145 | '108':'', 146 | '109':'', 147 | '110':'', 148 | '111':'', 149 | '112':'', 150 | '113':'', 151 | '114':'', 152 | '115':'', 153 | '116':'', 154 | '117':'', 155 | '118':'', 156 | '119':'', 157 | '120':'Sound OFF', 158 | '121':'Reset All', 159 | '122':'Lcl Ctrl', 160 | '123':'Notes OFF', 161 | '124':'Omni OFF', 162 | '125':'Omni ON', 163 | '126':'Mono ON', 164 | '127':'Poly ON', 165 | } 166 | 167 | export const TRIGGER_CC_RAW = [ 168 | { id: 'pitch', label: 'Pitch Wheel', mode: 'pitch', min: -8192, max: 8191 }, 169 | { id: 'channelAT', label: 'Channel AT', mode: 'channelAT', min: 0, max: 127 } 170 | ] 171 | 172 | Object.keys(CC).forEach(key => { 173 | TRIGGER_CC_RAW.push({ 174 | id: key, 175 | cc: parseInt(key), 176 | label: `CC ${key} ${CC[key]}`, 177 | mode: 'raw', 178 | min: 0, 179 | max: 127 180 | }) 181 | }) 182 | 183 | // HR 184 | export const TRIGGER_CC_HR = JSON.parse(JSON.stringify(TRIGGER_CC_RAW)) 185 | Object.keys(CCHR).forEach((key, i) => { 186 | Object.assign(TRIGGER_CC_HR[i+2], { 187 | label: `${key} ${CCHR[key]}`, 188 | mode: 'HR', 189 | min: 0, 190 | max: 16383, 191 | cc1: parseInt(key.split('/')[0]), 192 | cc2: parseInt(key.split('/')[1]) 193 | }) 194 | }) 195 | // Pan HR 196 | TRIGGER_CC_HR[10 + 2].min = -8192 197 | TRIGGER_CC_HR[10 + 2].max = 8191 198 | // Switches 199 | const switches = [64, 65, 66, 67, 68, 69, 80, 81, 82, 83, 120, 121, 122, 123, 124, 125, 126, 127] 200 | switches.forEach(sw => { 201 | TRIGGER_CC_HR[sw + 2].mode = 'switch' 202 | }) 203 | // RPN and NRPN 204 | Object.assign(TRIGGER_CC_HR[99+2], { 205 | min: 0, 206 | max: 16383, 207 | mode: 'HR', 208 | label: 'CC 99 NRP', 209 | cc1: 99, 210 | cc2: 98 211 | }) 212 | Object.assign(TRIGGER_CC_HR[101+2], { 213 | min: 0, 214 | max: 16383, 215 | mode: 'HR', 216 | label: 'CC 101 RP', 217 | cc1: 101, 218 | cc2: 100 219 | }) 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | Mididash 5 |
6 |

7 |
8 | 9 | [![Windows Support](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white)](https://github.com/tiagolr/mididash/releases) 10 | [![Ubuntu Support](https://img.shields.io/badge/Linux-E95420?style=for-the-badge&logo=linux&logoColor=white)](https://github.com/tiagolr/mididash/releases) 11 | [![Mac Support](https://img.shields.io/badge/MACOS-adb8c5?style=for-the-badge&logo=macos&logoColor=white)](https://github.com/tiagolr/mididash/releases) 12 | 13 |
14 |
15 | 16 | [![GitHub package.json version](https://img.shields.io/github/v/release/tiagolr/mididash?color=%40&label=latest)](https://github.com/tiagolr/mididash/releases/latest) 17 | ![GitHub issues](https://img.shields.io/github/issues-raw/tiagolr/mididash) 18 | ![GitHub all releases](https://img.shields.io/github/downloads/tiagolr/mididash/total) 19 | ![Github license](https://img.shields.io/github/license/tiagolr/mididash) 20 | 21 |
22 | 23 | Mididash is an open source MIDI routing software with a node-based interface and Lua scripting. A modern take on programs like [MIDI-OX](http://www.midiox.com). 24 | 25 |
26 | 27 | ![Screenshot](./src-assets/ss-dark.png) 28 | 29 | 30 |
31 | 32 | ### Features 33 | 34 | * **Cross-platform** available on Windows, Linux and macOS 35 | * **Node-based MIDI routing** allows for versatile configurations 36 | * **Hot-plugging** reconnect or replace MIDI devices 37 | * **MIDI monitoring** for inputs, outputs or individual nodes 38 | * **Pre-configured nodes** for MIDI splitting, mapping and more 39 | * **Script nodes** using Lua 5.4 40 | * **Lightweight** installer under 10MB 41 | * **High performance** built with Tauri and Rust 42 | 43 | ### Download 44 | 45 | The latest version and others can be found on [Github releases](https://github.com/tiagolr/mididash/releases) 46 | 47 | ### How does it compare with MIDI-OX? 48 | 49 | MIDI-OX is the main reference for this program, this one offers more routing capabilities and practical scripting via Lua nodes, it does not contain the same amount of MIDI tools like bank selectors or NPRN calculators but those can be added over time. 50 | 51 | In terms of performance, it's about the same CPU usage (e.g. 5% CPU on high midi throughput), the bundle size is small but cannot compete with 1MB of MIDI-OX, the memory footprint of the program is also similar but the webview can consume a few dozen MBs. 52 | 53 | ### Limitations 54 | 55 | #### Beta software 56 | 57 | This software is under active development, the core features are ready but there may be changes in appearance, stability patches and other changes until version 1.0. 58 | 59 | #### Unsigned software 60 | 61 | Signing licenses have associated costs and required setup, for now this software is unsigned which means that additional steps are required for installation: 62 | 63 | - **Windows**: Skip the installation protection screen when prompted. 64 | - **macOS**: Navigate to System Preferences → Privacy & Security → Unblock the app after attempting to open it. 65 | 66 | #### macOS Testing 67 | 68 | MacOs builds are tested on a slow, unstable virtual machine using Ventura 13.7, other versions of the OS have not been tested and it's the least tested target, please open a ticket for new issues found. 69 | 70 | ### Hot plugging 71 | 72 | New devices or disconnected devices are detected every few seconds. To add a new device to the project simply drag and drop it from the sidebar into the viewport. To replace a missing device or a device with changed ID click its node (if it exists) and then `Replace device` from the sidebar - it will assign the device to that node and re-establish all previous connections. 73 | 74 | ### Scripts and templates 75 | 76 | Using Lua 5.4 it's possible to program nodes for any kind MIDI processing. A few examples have been included to demonstrate how to modify and forward bytes. To test a script press `Ctrl+Enter` or `Cmd+Enter` from the code editor. Once a script is loaded into a node, it will run every time a signal is received on its input port. 77 | 78 | Scripts can be saved as global templates to be reused in projects, to do so 79 | click the settings icon near the code editor and then `Save as template`. 80 | 81 | ### Development 82 | 83 | Make sure [tauri 2 requirements](https://v2.tauri.app/start/prerequisites/) are met for the target platform, hit `npm install` and then `npm start` should start the application in development mode. 84 | 85 | ### License 86 | 87 | [GPL 3](https://www.gnu.org/licenses/gpl-3.0.en.html) 88 | -------------------------------------------------------------------------------- /src/assets/virtual.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/lib/vnodes/src/components/Markers.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | --------------------------------------------------------------------------------