├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── electron-src ├── api.ts ├── electron-next.d.ts ├── index.ts └── preload.mjs ├── eslint.config.js ├── package-lock.json ├── package.json ├── renderer ├── _docs │ ├── 02-create-a-project-config.md │ ├── 03-add-remove-plugins.md │ ├── 04-launch-project-config.md │ ├── 05-develop-new-plugins.md │ └── 06-command-line.md ├── components │ ├── audio.tsx │ ├── card.tsx │ ├── code.tsx │ ├── crumb.tsx │ ├── dependency.tsx │ ├── details.tsx │ ├── download.tsx │ ├── filters.tsx │ ├── header.tsx │ ├── installer.tsx │ ├── layout.tsx │ ├── list.tsx │ ├── multi-select.tsx │ ├── navigation.tsx │ ├── player.tsx │ ├── subnav.tsx │ └── tabs.tsx ├── global.d.ts ├── lib │ ├── api.ts │ ├── gtag.ts │ ├── image.ts │ ├── managers.ts │ ├── path.ts │ ├── plugin.ts │ ├── preset.ts │ ├── project.ts │ └── utils.ts ├── next-env.d.ts ├── next.config.js ├── pages │ ├── _app.tsx │ ├── docs │ │ ├── [slug].tsx │ │ └── index.tsx │ ├── index.tsx │ ├── plugins │ │ ├── [userId] │ │ │ ├── [pluginId].tsx │ │ │ └── index.tsx │ │ └── index.tsx │ ├── presets │ │ ├── [userId] │ │ │ ├── [pluginId].tsx │ │ │ └── index.tsx │ │ └── index.tsx │ ├── projects │ │ ├── [userId] │ │ │ ├── [pluginId].tsx │ │ │ └── index.tsx │ │ └── index.tsx │ └── settings │ │ └── index.tsx ├── public │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── icon-als.png │ │ ├── icon-cpr.png │ │ ├── icon-flp.png │ │ ├── icon-logic.png │ │ ├── icon-ptx.png │ │ ├── icon-rpp.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── site.webmanifest │ │ └── studiorack-badge.png │ └── images │ │ ├── creators-mobile.jpg │ │ ├── creators.jpg │ │ ├── icon-all.svg │ │ ├── icon-arrow-down.svg │ │ ├── icon-category.svg │ │ ├── icon-compatibility.svg │ │ ├── icon-cost.svg │ │ ├── icon-date.svg │ │ ├── icon-download.svg │ │ ├── icon-external-link.svg │ │ ├── icon-filesize.svg │ │ ├── icon-installed.svg │ │ ├── icon-license.svg │ │ ├── icon-linux.svg │ │ ├── icon-mac.svg │ │ ├── icon-pause.svg │ │ ├── icon-platform.svg │ │ ├── icon-play.svg │ │ ├── icon-tag.svg │ │ ├── icon-type.svg │ │ ├── icon-update.svg │ │ ├── icon-win.svg │ │ ├── license.svg │ │ ├── plugin.jpg │ │ ├── plugin.png │ │ ├── producers-mobile.jpg │ │ ├── producers.jpg │ │ ├── project.png │ │ ├── sfz-player.png │ │ ├── studio-audio-rack-mobile.jpg │ │ ├── studio-audio-rack.jpg │ │ └── studio-rack-logo.svg ├── styles │ ├── components │ │ ├── audio.module.css │ │ ├── card.module.css │ │ ├── code.module.css │ │ ├── crumb.module.css │ │ ├── details.module.css │ │ ├── download.module.css │ │ ├── filters.module.css │ │ ├── grid-item.module.css │ │ ├── header.module.css │ │ ├── layout.module.css │ │ ├── list.module.css │ │ ├── multi-select.module.css │ │ ├── navigation.module.css │ │ ├── player.module.css │ │ ├── subnav.module.css │ │ └── tabs.module.css │ ├── doc.module.css │ ├── global.css │ ├── index.module.css │ └── settings.module.css ├── tsconfig.json ├── types │ └── doc.ts └── window.d.ts ├── screenshot.jpg ├── test └── projects │ ├── Banwer Project │ ├── Banwer.als │ ├── Banwer.json │ ├── Banwer.png │ └── Banwer.wav │ └── example.json ├── tests └── crumb.test.tsx ├── tsconfig.json ├── tslint.json └── vitest.config.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: kmturley 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | create_release: 10 | name: Create release 11 | runs-on: ubuntu-latest 12 | outputs: 13 | upload_id: ${{ steps.draft_release.outputs.id }} 14 | upload_url: ${{ steps.draft_release.outputs.upload_url }} 15 | steps: 16 | - name: Draft release 17 | id: draft_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: Release ${{ github.ref }} 24 | draft: true 25 | 26 | build_release: 27 | name: Build release 28 | needs: create_release 29 | runs-on: ${{ matrix.os }} 30 | strategy: 31 | matrix: 32 | os: [ubuntu-latest, macos-13, windows-latest] 33 | include: 34 | - os: ubuntu-latest 35 | file_name: studiorack-linux.AppImage 36 | - os: macos-13 37 | file_name: studiorack-mac.dmg 38 | - os: windows-latest 39 | file_name: studiorack-win.exe 40 | steps: 41 | - name: Checkout code 42 | uses: actions/checkout@v4 43 | 44 | - name: Setup 45 | shell: bash 46 | run: | 47 | npm ci 48 | 49 | - name: Build 50 | shell: bash 51 | run: | 52 | npm run dist 53 | 54 | - name: Upload 55 | uses: actions/upload-release-asset@v1 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | with: 59 | upload_url: ${{ needs.create_release.outputs.upload_url }} 60 | asset_path: ./dist/${{ matrix.file_name }} 61 | asset_name: ${{ matrix.file_name }} 62 | asset_content_type: application/octet-stream 63 | 64 | publish_release: 65 | name: Publish release 66 | needs: [create_release, build_release] 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: eregon/publish-release@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | release_id: ${{ needs.create_release.outputs.upload_id }} 74 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test_code: 9 | name: Test code 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | include: 15 | - os: ubuntu-latest 16 | - os: macos-latest 17 | - os: windows-latest 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Install 23 | run: | 24 | npm ci 25 | 26 | - name: Audit 27 | run: | 28 | npm audit 29 | 30 | - name: Lint 31 | run: | 32 | npm run lint 33 | 34 | - name: Test 35 | run: | 36 | npm run test 37 | 38 | - name: Build 39 | run: | 40 | npm run build 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependencies 7 | node_modules/ 8 | 9 | # Coverage 10 | coverage 11 | 12 | # Transpiled files 13 | build/ 14 | dist/ 15 | out/ 16 | .next/ 17 | 18 | # VS Code 19 | .vscode 20 | !.vscode/tasks.js 21 | 22 | # JetBrains IDEs 23 | .idea/ 24 | 25 | # Optional npm cache directory 26 | .npm 27 | 28 | # Optional eslint cache 29 | .eslintcache 30 | 31 | # Misc 32 | .DS_Store 33 | test/* 34 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "bracketSameLine": false, 6 | "jsxSingleQuote": false, 7 | "printWidth": 120, 8 | "proseWrap": "preserve", 9 | "quoteProps": "as-needed", 10 | "semi": true, 11 | "singleQuote": true, 12 | "tabWidth": 2, 13 | "trailingComma": "all", 14 | "useTabs": false 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 StudioRack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # studiorack-app 2 | 3 | ![Release](https://github.com/studiorack/studiorack-app/workflows/Release/badge.svg) 4 | 5 | StudioRack audio plugin manager app. Search, view, download and install audio plugins. 6 | 7 | ![StudioRack App](/screenshot.jpg) 8 | 9 | Powered by Open Audio Stack 10 | 11 | ## Installation 12 | 13 | Navigate to GitHub Releases and find the latest download for your system: 14 | 15 | https://github.com/studiorack/studiorack-app/releases 16 | 17 | Download the file and open to install the app on to your machine. Follow instructions within the app. 18 | 19 | ## Developer information 20 | 21 | StudioRack App was built using: 22 | 23 | - NodeJS 20.x 24 | - TypeScript 5.x 25 | - NextJS 14.x 26 | - React 18.x 27 | - Electron 31.x 28 | 29 | ## Installation 30 | 31 | Install dependencies using: 32 | 33 | npm install 34 | 35 | ## Usage 36 | 37 | Run the development server using: 38 | 39 | npm run dev 40 | 41 | View the site at: 42 | 43 | http://localhost:3000 44 | 45 | Get the api at: 46 | 47 | http://localhost:3000/api/plugins 48 | 49 | ## Deployment 50 | 51 | Release an updated version on GitHub by simply creating a version tag: 52 | 53 | npm version patch 54 | git push && git push origin --tags 55 | 56 | This will run an automated build and deploy process on GitHub Actions: 57 | 58 | .github/workflows/release.yml 59 | 60 | ## Contact 61 | 62 | For more information please contact kmturley 63 | -------------------------------------------------------------------------------- /electron-src/api.ts: -------------------------------------------------------------------------------- 1 | import { ConfigInterface, Package, PackageVersion, RegistryType } from '@open-audio-stack/core'; 2 | import { ipcRenderer } from 'electron'; 3 | 4 | export function message(val: string | object) { 5 | return ipcRenderer.send('message', val); 6 | } 7 | 8 | export async function install(type: RegistryType, pkg: Package): Promise { 9 | console.log('install api', type, pkg); 10 | return ipcRenderer.invoke('install', type, pkg); 11 | } 12 | 13 | export async function uninstall(type: RegistryType, pkg: Package): Promise { 14 | console.log('uninstall api', type, pkg); 15 | return ipcRenderer.invoke('uninstall', type, pkg); 16 | } 17 | 18 | export async function installDependencies(filePath: string, type = RegistryType.Plugins): Promise { 19 | return ipcRenderer.invoke('installDependencies', filePath, type); 20 | } 21 | 22 | export async function open(filePath: string): Promise { 23 | return ipcRenderer.invoke('open', filePath); 24 | } 25 | 26 | export async function select(filePath: string): Promise { 27 | return ipcRenderer.invoke('select', filePath); 28 | } 29 | 30 | export async function get(key: keyof ConfigInterface): Promise { 31 | console.log('get api', key); 32 | return ipcRenderer.invoke('get', key); 33 | } 34 | 35 | export async function set(key: keyof ConfigInterface, val: string | object): Promise { 36 | console.log('set api', key, val); 37 | return ipcRenderer.invoke('set', key, val); 38 | } 39 | -------------------------------------------------------------------------------- /electron-src/electron-next.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'electron-next' { 2 | interface Directories { 3 | production: string; 4 | development: string; 5 | } 6 | 7 | export default function (directories: Directories | string, port?: number): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /electron-src/index.ts: -------------------------------------------------------------------------------- 1 | // Native 2 | import path, { join } from 'path'; 3 | import { parse } from 'url'; 4 | 5 | // Packages 6 | import { BrowserWindow, app, dialog, session, ipcMain, IpcMainEvent, protocol } from 'electron'; 7 | import fixPath from 'fix-path'; 8 | import isDev from 'electron-is-dev'; 9 | import { createServer as createServerHttp, IncomingMessage, ServerResponse } from 'http'; 10 | import createServer from 'next/dist/server/next.js'; 11 | import { ConfigInterface, fileOpen, Package, RegistryType } from '@open-audio-stack/core'; 12 | import { config, managers } from '../renderer/lib/managers.js'; 13 | 14 | // Ensure Electron apps subprocess on macOS and Linux inherit system $PATH 15 | fixPath(); 16 | 17 | const DEFAULT_PAGE = 'projects'; 18 | 19 | export const CONFIG: ConfigInterface = { 20 | registries: [ 21 | { 22 | name: 'Open Audio Registry', 23 | url: 'https://open-audio-stack.github.io/open-audio-stack-registry', 24 | }, 25 | ], 26 | version: '1.0.0', 27 | }; 28 | 29 | export const CONFIG_LOCAL_TEST: ConfigInterface = { 30 | ...CONFIG, 31 | appDir: 'test', 32 | pluginsDir: path.join('test', 'installed', 'plugins'), 33 | presetsDir: path.join('test', 'installed', 'presets'), 34 | projectsDir: path.join('test', 'installed', 'projects'), 35 | }; 36 | 37 | // Prepare the renderer once the app is ready 38 | app.on('ready', async () => { 39 | // Use server-side rendering for both dev and production builds 40 | // @ts-expect-error incorrect types returned 41 | const nextApp = createServer({ 42 | dev: isDev, 43 | dir: join(app.getAppPath(), 'renderer'), 44 | }); 45 | const requestHandler = nextApp.getRequestHandler(); 46 | 47 | // Build the renderer code and watch the files 48 | await nextApp.prepare(); 49 | 50 | // Create a new native HTTP server (which supports hot code reloading) 51 | createServerHttp((req: IncomingMessage, res: ServerResponse) => { 52 | const parsedUrl = parse(req.url ? req.url : '', true); 53 | requestHandler(req, res, parsedUrl); 54 | }).listen(3000, () => { 55 | console.log('> Ready on http://localhost:3000'); 56 | }); 57 | 58 | // Register custom media protocol for local images 59 | protocol.registerFileProtocol('media', (request, callback) => { 60 | const pathname = request.url.replace('media:///', ''); 61 | callback(pathname); 62 | }); 63 | 64 | // Dock is only available on MacOS 65 | if (app.dock) { 66 | app.dock.setIcon(join(app.getAppPath(), 'renderer', 'public', 'icons', 'android-chrome-512x512.png')); 67 | } 68 | 69 | // enable more secure http header 70 | session.defaultSession.webRequest.onHeadersReceived((details, callback) => { 71 | callback({ 72 | responseHeaders: { 73 | ...details.responseHeaders, 74 | 'Content-Security-Policy': [ 75 | ` 76 | default-src 'self' *.youtube.com; 77 | connect-src 'self' github.com *.github.com *.github.io *.githubusercontent.com *.google-analytics.com *.doubleclick.net *.google.com *.googleapis.com data:; 78 | font-src 'self' fonts.gstatic.com; 79 | img-src 'self' github.com *.github.com *.github.io *.githubusercontent.com *.s3.amazonaws.com *.youtube.com *.ytimg.com data: media:; 80 | media-src 'self' github.com *.github.com *.github.io *.githubusercontent.com *.s3.amazonaws.com *.youtube.com media:; 81 | object-src 'none'; 82 | script-src 'self' 'unsafe-inline' 'unsafe-eval' github.com *.github.com *.github.io *.githubusercontent.com *.doubleclick.net *.google.com *.gstatic.com *.googletagmanager.com *.google-analytics.com; 83 | style-src 'self' 'unsafe-inline' github.com *.github.com *.github.io *.githubusercontent.com fonts.googleapis.com 84 | `, 85 | ], 86 | }, 87 | }); 88 | }); 89 | 90 | const mainWindow = new BrowserWindow({ 91 | width: isDev ? 1280 + 445 : 1280, 92 | height: 800, 93 | webPreferences: { 94 | sandbox: false, 95 | preload: join(app.getAppPath(), 'build', 'preload.mjs'), 96 | }, 97 | }); 98 | 99 | mainWindow.loadURL(`http://localhost:3000/${DEFAULT_PAGE}`); 100 | 101 | // If developing locally, open developer tools 102 | if (isDev) { 103 | mainWindow.webContents.openDevTools(); 104 | } 105 | }); 106 | 107 | // Quit the app once all windows are closed 108 | app.on('window-all-closed', app.quit); 109 | 110 | // Listen the channel `message` and resend the received message to the renderer process 111 | ipcMain.on('message', (event: IpcMainEvent, message: string | object) => { 112 | event.sender.send('message', message); 113 | }); 114 | 115 | // Install package locally 116 | ipcMain.handle('install', async (_event, type: RegistryType, pkg: Package) => { 117 | console.log('install', type, pkg.slug, pkg.version); 118 | managers[type].scan(); 119 | return await managers[type].install(pkg.slug, pkg.version); 120 | }); 121 | 122 | // Uninstall package locally 123 | ipcMain.handle('uninstall', async (_event, type: RegistryType, pkg: Package) => { 124 | console.log('uninstall', type, pkg.slug, pkg.version); 125 | managers[type].scan(); 126 | return await managers[type].uninstall(pkg.slug, pkg.version); 127 | }); 128 | 129 | // Install package locally 130 | ipcMain.handle('installDependencies', async (_event, filePath: string, type = RegistryType.Plugins) => { 131 | console.log('installDependencies', filePath, type); 132 | managers[type].scan(); 133 | return await managers[type].installDependencies(filePath, type); 134 | }); 135 | 136 | // Open project 137 | ipcMain.handle('open', (_event, filePath: string) => { 138 | console.log('open', filePath); 139 | return fileOpen(filePath); 140 | }); 141 | 142 | // Select folder 143 | ipcMain.handle('select', (_event, filePath: string) => { 144 | console.log('select'); 145 | if (!path) return; 146 | return dialog.showOpenDialog({ 147 | defaultPath: filePath, 148 | properties: ['openDirectory'], 149 | }); 150 | }); 151 | 152 | // Get user-specific setting 153 | ipcMain.handle('get', (_event, key: keyof ConfigInterface) => { 154 | console.log('get', key, config.get(key)); 155 | if (!key) return; 156 | return { 157 | key, 158 | value: config.get(key), 159 | }; 160 | }); 161 | 162 | // Set user-specific setting 163 | ipcMain.handle('set', (_event, key: keyof ConfigInterface, val: string | object) => { 164 | console.log('set', key, val); 165 | if (!key || !val) return; 166 | return config.set(key, val); 167 | }); 168 | -------------------------------------------------------------------------------- /electron-src/preload.mjs: -------------------------------------------------------------------------------- 1 | // This file is intentionally .mjs to ensure Electron treats it as an ES module. 2 | // https://www.electronjs.org/docs/latest/tutorial/esm 3 | import { contextBridge } from 'electron'; 4 | import { get, install, installDependencies, message, open, select, set, uninstall } from './api.js'; 5 | 6 | contextBridge.exposeInMainWorld('electronAPI', { 7 | get, 8 | install, 9 | installDependencies, 10 | message, 11 | open, 12 | select, 13 | set, 14 | uninstall, 15 | }); 16 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import eslint from '@eslint/js'; 3 | import tseslint from 'typescript-eslint'; 4 | 5 | export default tseslint.config( 6 | eslint.configs.recommended, 7 | ...tseslint.configs.recommended, 8 | { 9 | ignores: ['build', 'coverage', 'renderer/.next', 'node_modules', 'out'], 10 | }, 11 | { 12 | languageOptions: { globals: globals.node }, 13 | rules: { 14 | '@typescript-eslint/no-explicit-any': 'off', 15 | '@typescript-eslint/no-empty-object-type': 'off', 16 | }, 17 | }, 18 | ); 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "studiorack-app", 3 | "version": "3.0.3", 4 | "productName": "StudioRack App", 5 | "description": "Audio plugin app, searchable list of plugins to install and share", 6 | "type": "module", 7 | "main": "build/index.js", 8 | "scripts": { 9 | "dev": "npm run build && electron .", 10 | "dev:next": "next dev ./renderer", 11 | "dev:static": "serve ./build", 12 | "start": "electron .", 13 | "build-renderer": "next build ./renderer", 14 | "build-electron": "tsc -p tsconfig.json", 15 | "build": "npm run build-renderer && npm run build-electron && npm run copy", 16 | "check": "npm run format && npm run lint && npm run build", 17 | "copy": "cp -rf ./electron-src/*.mjs ./build", 18 | "format": "prettier . --write", 19 | "lint": "eslint .", 20 | "pack-app": "npm run build && electron-builder --dir", 21 | "pack": "electron-builder --help", 22 | "test": "vitest run ./tests", 23 | "dist": "npm run build && DEBUG=electron-builder electron-builder --publish never", 24 | "type-check": "tsc" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/studiorack/studiorack-app.git" 29 | }, 30 | "keywords": [ 31 | "audio", 32 | "plugin", 33 | "app", 34 | "vst", 35 | "daw", 36 | "metadata", 37 | "search" 38 | ], 39 | "build": { 40 | "appId": "io.github.studiorack", 41 | "productName": "StudioRack App", 42 | "artifactName": "studiorack-${os}.${ext}", 43 | "asar": true, 44 | "asarUnpack": [ 45 | "node_modules" 46 | ], 47 | "files": [ 48 | "build", 49 | "renderer" 50 | ], 51 | "directories": { 52 | "buildResources": "renderer/public/icons" 53 | }, 54 | "mac": { 55 | "target": "dmg", 56 | "category": "public.app-category.utilities" 57 | }, 58 | "linux": { 59 | "target": "AppImage", 60 | "category": "Utility" 61 | }, 62 | "win": { 63 | "target": "nsis" 64 | }, 65 | "extraMetadata": { 66 | "main": "build/index.js" 67 | } 68 | }, 69 | "author": "kmturley", 70 | "license": "MIT", 71 | "bugs": { 72 | "url": "https://github.com/studiorack/studiorack-app/issues" 73 | }, 74 | "homepage": "https://github.com/studiorack/studiorack-app#readme", 75 | "engines": { 76 | "node": ">=18" 77 | }, 78 | "devDependencies": { 79 | "@eslint/js": "^9.12.0", 80 | "@testing-library/react": "^16.0.0", 81 | "@types/node": "20.14.8", 82 | "@types/react": "18.3.3", 83 | "@vitejs/plugin-react": "^4.3.4", 84 | "electron": "^31.2.1", 85 | "electron-builder": "^24.13.3", 86 | "eslint": "^9.12.0", 87 | "globals": "^15.2.0", 88 | "jsdom": "^24.1.0", 89 | "prettier": "^3.2.5", 90 | "serve": "^14.2.3", 91 | "tsx": "^4.19.3", 92 | "typescript": "^5.6.3", 93 | "typescript-eslint": "^8.9.0", 94 | "vitest": "^3.0.9" 95 | }, 96 | "dependencies": { 97 | "@open-audio-stack/core": "^0.1.29", 98 | "electron-is-dev": "^2.0.0", 99 | "electron-next": "^3.1.5", 100 | "fix-path": "^4.0.0", 101 | "gray-matter": "^4.0.3", 102 | "next": "^14.2.10", 103 | "react": "^18.3.1", 104 | "react-dom": "^18.3.1", 105 | "remark": "^15.0.1", 106 | "remark-html": "^16.0.1", 107 | "slugify": "^1.6.6" 108 | }, 109 | "overrides": { 110 | "path-to-regexp": "^8.1.0" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /renderer/_docs/02-create-a-project-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Create a project config' 3 | --- 4 | 5 | It is possible to install plugins by running `studiorack plugin install` commands directly. These will be shared system-wide. 6 | 7 | However to fully benefit from plugin management and versioning, it is recommended to use a `project.json` file, which tracks the plugin versions for each specific project. 8 | 9 | Create a new studiorack `project.json` file using: 10 | 11 | studiorack project create 12 | 13 | This will create a studiorack .json file with your configuration: 14 | 15 | { 16 | "id": "example", 17 | "author": "studiorack-user", 18 | "homepage": "https://studiorack.github.io/studiorack-site/", 19 | "name": "StudioRack Project", 20 | "description": "Created using StudioRack", 21 | "tags": [ 22 | "StudioRack" 23 | ], 24 | "version": "1.0.0", 25 | "date": "2021-05-30T21:58:39.138Z", 26 | "type": { 27 | "name": "Ableton", 28 | "ext": "als" 29 | }, 30 | "files": { 31 | "audio": { 32 | "name": "example.wav", 33 | "size": 1902788 34 | }, 35 | "image": { 36 | "name": "example.png", 37 | "size": 16360 38 | }, 39 | "project": { 40 | "name": "example.als", 41 | "size": 253018 42 | } 43 | }, 44 | "plugins": {}, 45 | "path": "songs/april", 46 | "status": "installed" 47 | } 48 | 49 | [Add & remove plugins >](/docs/03-add-remove-plugins) 50 | -------------------------------------------------------------------------------- /renderer/_docs/03-add-remove-plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Add & remove plugins' 3 | --- 4 | 5 | Search the plugin registry using: 6 | 7 | studiorack plugin search delay 8 | 9 | Add a plugin and update project.json config using: 10 | 11 | studiorack project install 12 | 13 | Remove a plugin and update project.json config using: 14 | 15 | studiorack plugin uninstall 16 | 17 | [Launch project config >](/docs/04-launch-project-config) 18 | -------------------------------------------------------------------------------- /renderer/_docs/04-launch-project-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Launch project config' 3 | --- 4 | 5 | List the projects found in projectFolder using: 6 | 7 | studiorack project listLocal 8 | 9 | Install a project's plugins using: 10 | 11 | studiorack project install 12 | 13 | Then open the project using: 14 | 15 | studiorack project open 16 | 17 | [Read the API Reference >](/docs/06-command-line) 18 | -------------------------------------------------------------------------------- /renderer/_docs/05-develop-new-plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Develop new plugins' 3 | --- 4 | 5 | ## Use our plugin templates (optional) 6 | 7 | We have plugin templates which automate plugin builds 8 | 9 | - [studiorack-template-clap](https://github.com/studiorack/studiorack-template-clap) 10 | - [studiorack-template-dpf](https://github.com/studiorack/studiorack-template-dpf) 11 | - [studiorack-template-dplug](https://github.com/studiorack/studiorack-template-dplug) 12 | - [studiorack-template-iplug](https://github.com/studiorack/studiorack-template-iplug) 13 | - [studiorack-template-juce](https://github.com/studiorack/studiorack-template-juce) 14 | - [studiorack-template-sf2](https://github.com/studiorack/studiorack-template-sf2) 15 | - [studiorack-template-sfz](https://github.com/studiorack/studiorack-template-sfz) 16 | - [studiorack-template-steinberg](https://github.com/studiorack/studiorack-template-steinberg) 17 | 18 | You can fork the repos on GitHub or use our command line tool. Create a new plugin using a starter template (clap, dpf, dplug, iplug, juce, sf2, sfz, steinberg): 19 | 20 | studiorack plugin create myplugin --type steinberg 21 | 22 | This creates a new plugin using the starter template with the following structure: 23 | 24 | /myplugin 25 | /index.js 26 | /LICENSE 27 | /README.md 28 | /src 29 | /vst3sdk 30 | 31 | Follow the instructions at `./myplugin/README.md` to install and build your plugin 32 | 33 | ## Adding your plugin to StudioRack 34 | 35 | The StudioRack Registry accepts multiple sources for plugin data: 36 | 37 | ### 1. Yaml files 38 | 39 | Create a fork of the repo [studiorack-registry](https://github.com/studiorack/studiorack-registry). Add new folders for your organization and plugin using [kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case): 40 | 41 | ./src/plugins/org-name/plugin-name 42 | 43 | Add a jpeg screenshot of the plugin, and flac audio file previewing the plugin: 44 | 45 | ./src/plugins/org-name/plugin-name/plugin-name.flac 46 | ./src/plugins/org-name/plugin-name/plugin-name.jpg 47 | 48 | `.jpg` and `.flac` compressed formats were chosen to optimize loading times on the [StudioRack website](https://studiorack.github.io/studiorack-site/). 49 | 50 | Create yaml files for each version of the plugin using [Semantic Versioning](https://semver.org). 51 | 52 | ./src/plugins/org-name/plugin-name/1.0.0.yaml 53 | ./src/plugins/org-name/plugin-name/2.0.0.yaml 54 | 55 | Semantic versioning allows the StudioRack installer to install the latest non-breaking version of a plugin. 56 | 57 | Use the below template yaml file as a starting point. StudioRack Registry validates each plugin's metadata, 58 | if you miss or enter incorrect information, your plugin will not be included in the registry. 59 | 60 | --- 61 | name: Sfizz 62 | author: SFZTools 63 | homepage: https://github.com/sfztools/sfizz 64 | description: SFZ parser and synth c++ library, providing AU / LV2 / VST3 plugins and JACK standalone client. 65 | date: 2024-01-14T00:00:00.000Z 66 | license: bsd-2-clause 67 | tags: 68 | - Instrument 69 | - Sampler 70 | - Synth 71 | files: 72 | audio: 73 | url: https://studiorack.github.io/studiorack-registry/plugins/sfztools/sfizz/sfizz.flac 74 | size: 47910 75 | image: 76 | url: https://studiorack.github.io/studiorack-registry/plugins/sfztools/sfizz/sfizz.jpg 77 | size: 33976 78 | linux: 79 | url: https://github.com/sfztools/sfizz/releases/download/1.2.3/sfizz-1.2.3.tar.gz 80 | size: 19102967 81 | mac: 82 | url: https://github.com/sfztools/sfizz/releases/download/1.2.3/sfizz-1.2.3-macos.tar.gz 83 | size: 1748833 84 | win: 85 | url: https://github.com/sfztools/sfizz/releases/download/1.2.3/sfizz-1.2.3-win64.zip 86 | size: 8286178 87 | 88 | For effects, tag your plugin with `Effect` and then any of the following: 89 | 90 | - Chorus 91 | - Phaser 92 | - Compression 93 | - Distortion 94 | - Amplifier 95 | - Equalizer 96 | - Pan 97 | - Filter 98 | - Reverb 99 | - Delay 100 | 101 | For instruments, tag your plugin with `Instrument` and then any of the following: 102 | 103 | - Drums 104 | - Percussion 105 | - Guitar 106 | - String 107 | - Keys 108 | - Piano 109 | - Orchestra 110 | - Sampler 111 | - Synth 112 | - Vocals 113 | 114 | For file downloads, StudioRack prefers `.zip` files as these can be extracted automatically and placed into the correct locations without user interaction. 115 | If you use other formats such as `deb, dmg, exe, msi` StudioRack will download and copy the file to the users directory, but not install. 116 | 117 | ### 2. GitHub repo 118 | 119 | StudioRack supports scanning GitHub for compatible plugins. NOTE: to implement GitHub compatibility requires more effort than the yaml approach above. 120 | 121 | StudioRack registry searches the GitHub API for topic `studiorack-plugin`: 122 | 123 | https://github.com/topics/studiorack-plugin 124 | https://api.github.com/search/repositories?q=topic:studiorack-plugin+fork:true 125 | 126 | Add the `studiorack-plugin` topic to your GitHub repository, so it can be discovered. 127 | 128 | For each GitHub repository, the Registry loops through each release/version and expects to find a file called `plugins.json`: 129 | 130 | https://github.com/REPOSITORY_NAME/releases/download/RELEASE_NAME/plugins.json 131 | 132 | This should be in the format: 133 | 134 | { 135 | "plugins": [ 136 | { 137 | "author": "Rytmenpinne", 138 | "homepage": "https://rytmenpinne.wordpress.com/sounds-and-such/salamander-drumkit/", 139 | "name": "Salamander Drumkit", 140 | "description": "Drumkit recorded in the garage with an acoustic sound/feel.", 141 | "tags": [ 142 | "Instrument", 143 | "Drums", 144 | "sfz" 145 | ], 146 | "version": "1.0.0", 147 | "id": "salamander-drumkit", 148 | "date": "2012-02-25T07:00:00.000Z", 149 | "files": { 150 | "audio": { 151 | "name": "salamander-drumkit.flac", 152 | "size": 162704 153 | }, 154 | "image": { 155 | "name": "salamander-drumkit.jpg", 156 | "size": 94023 157 | }, 158 | "linux": { 159 | "name": "salamander-drumkit.zip", 160 | "size": 269599176 161 | }, 162 | "mac": { 163 | "name": "salamander-drumkit.zip", 164 | "size": 269599176 165 | }, 166 | "win": { 167 | "name": "salamander-drumkit.zip", 168 | "size": 269599176 169 | } 170 | } 171 | } 172 | ] 173 | } 174 | 175 | StudioRack Registry then performs validation and mapping on the plugins.json before compiling into the json feeds: 176 | 177 | https://studiorack.github.io/studiorack-registry/v2/ 178 | https://studiorack.github.io/studiorack-registry/v2/instruments.json 179 | https://studiorack.github.io/studiorack-registry/v2/effects.json 180 | https://studiorack.github.io/studiorack-registry/v2/sfz.json 181 | 182 | [Read the API Reference >](/docs/06-command-line) 183 | -------------------------------------------------------------------------------- /renderer/_docs/06-command-line.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Command line' 3 | --- 4 | 5 | The StudioRack CLI allows you to create, install, and publish plugins. 6 | 7 | Configuration commands: 8 | 9 | - [Config get](#config-get) 10 | - [Config set](#config-set) 11 | 12 | Plugin commands: 13 | 14 | - [Plugin create](#plugin-create) 15 | - [Plugin get](#plugin-get) 16 | - [Plugin getLocal](#plugin-getlocal) 17 | - [Plugin install](#plugin-install) 18 | - [Plugin list](#plugin-list) 19 | - [Plugin listLocal](#plugin-listlocal) 20 | - [Plugin search](#plugin-search) 21 | - [Plugin uninstall](#plugin-uninstall) 22 | 23 | Project commands: 24 | 25 | - [Project create](#project-create) 26 | - [Project getLocal](#project-getlocal) 27 | - [Project install](#project-install) 28 | - [Project listLocal](#project-listlocal) 29 | - [Project open](#project-open) 30 | - [Project uninstall](#project-uninstall) 31 | 32 | Other commands: 33 | 34 | - [Validate](#validate) 35 | - [Help](#help) 36 | 37 | ## Config get 38 | 39 | `studiorack config get ` Get a config setting by key. List of keys available: 40 | 41 | extAudio: string; 42 | extFile: string; 43 | extImage: string; 44 | extZip: string; 45 | ignoredFolders: string[]; 46 | pluginFile: string; 47 | pluginFolder: string; 48 | pluginRegistry: string; 49 | pluginRelease: string; 50 | pluginTemplate: string; 51 | pluginTypes: PluginTypes; 52 | projectFile: string; 53 | projectFolder: string; 54 | projectRegistry: string; 55 | projectTypes: ProjectTypes; 56 | validatorUrl: string; 57 | 58 | ## Config set 59 | 60 | `studiorack config set set ` Get a config setting by key. List of keys and values available: 61 | 62 | extAudio: string; 63 | extFile: string; 64 | extImage: string; 65 | extZip: string; 66 | ignoredFolders: string[]; 67 | pluginFile: string; 68 | pluginFolder: string; 69 | pluginRegistry: string; 70 | pluginRelease: string; 71 | pluginTemplate: string; 72 | pluginTypes: PluginTypes; 73 | projectFile: string; 74 | projectFolder: string; 75 | projectRegistry: string; 76 | projectTypes: ProjectTypes; 77 | validatorUrl: string; 78 | 79 | ## Plugin create 80 | 81 | `studiorack plugin create --type ` creates a new plugin using the starter template with one of the types: 82 | 83 | clap 84 | dpf 85 | dplug 86 | iplug 87 | juce 88 | sf2 89 | sfz 90 | steinberg 91 | 92 | Follow the instructions in generated README.md to install and build your plugin. 93 | 94 | ## Plugin get 95 | 96 | `studiorack plugin get ` Get registry plugin metadata by id: 97 | 98 | $ studiorack plugin get studiorack/studiorack-template-steinberg/adelay 99 | ┌────────────────────────────────────┬────────────────────┬─────────────┬────────────┬─────────┬───────────┐ 100 | │ Id │ Name │ Description │ Date │ Version │ Tags │ 101 | ├────────────────────────────────────┼────────────────────┼─────────────┼────────────┼─────────┼───────────┤ 102 | │ studiorack/template-steinberg/adelay │ ADelayTest Factory │ Test Class │ 2020-12-25 │ 1.1.0 │ Fx, Delay │ 103 | └────────────────────────────────────┴────────────────────┴─────────────┴────────────┴─────────┴───────────┘ 104 | 105 | ## Plugin getLocal 106 | 107 | `studiorack plugin getLocal ` Get local plugin details by id: 108 | 109 | $ studiorack plugin getLocal studiorack/studiorack-template-steinberg/adelay 110 | ┌────────────────────────────────────┬────────────────────┬─────────────┬────────────┬─────────┬───────────┐ 111 | │ Id │ Name │ Description │ Date │ Version │ Tags │ 112 | ├────────────────────────────────────┼────────────────────┼─────────────┼────────────┼─────────┼───────────┤ 113 | │ studiorack/template-steinberg/adelay │ ADelayTest Factory │ Test Class │ 2020-12-25 │ 1.1.0 │ Fx, Delay │ 114 | └────────────────────────────────────┴────────────────────┴─────────────┴────────────┴─────────┴───────────┘ 115 | 116 | ## Plugin install 117 | 118 | `studiorack plugin install ` Install a plugin manually by id: 119 | 120 | studiorack plugin install studiorack/mda 121 | 122 | ## Plugin list 123 | 124 | `studiorack plugin list` List registry plugins: 125 | 126 | $ studiorack plugin list 127 | ┌────────────────────────────────┬────────────────────┬────────────────┬────────────┬─────────┬───────────┐ 128 | │ Id │ Name │ Description │ Date │ Version │ Tags │ 129 | ├────────────────────────────────┼────────────────────┼────────────────┼────────────┼─────────┼───────────┤ 130 | │ ryukau/vstplugins/seven-delay │ SevenDelay │ A stereo delay │ 2020-12-11 │ 0.1.14 │ Fx, Delay │ 131 | ├────────────────────────────────┼────────────────────┼────────────────┼────────────┼─────────┼───────────┤ 132 | │ steinberg/adelay │ ADelayTest Factory │ Test Class │ 2020-12-25 │ 1.1.0 │ Fx, Delay │ 133 | ├────────────────────────────────┼────────────────────┼────────────────┼────────────┼─────────┼───────────┤ 134 | │ steinberg/channelcontext │ ContextController │ Component │ 2020-12-25 │ 1.0.0 │ Spatial, │ 135 | └────────────────────────────────┴────────────────────┴────────────────┴────────────┴─────────┴───────────┘ 136 | 137 | ## Plugin listLocal 138 | 139 | `studiorack plugin listLocal` Get local plugin details by id: 140 | 141 | $ studiorack plugin listLocal 142 | ┌────────────────────────────────────┬────────────────────┬─────────────┬────────────┬─────────┬───────────┐ 143 | │ Id │ Name │ Description │ Date │ Version │ Tags │ 144 | ├────────────────────────────────────┼────────────────────┼─────────────┼────────────┼─────────┼───────────┤ 145 | │ studiorack/plugin-steinberg/adelay │ ADelayTest Factory │ Test Class │ 2020-12-25 │ 1.1.0 │ Fx, Delay │ 146 | └────────────────────────────────────┴────────────────────┴─────────────┴────────────┴─────────┴───────────┘ 147 | 148 | ## Plugin search 149 | 150 | `studiorack search ` Search plugin registry by query. For example: 151 | 152 | studiorack plugin search "delay" 153 | 154 | ## Plugin uninstall 155 | 156 | `studiorack plugin ` Uninstall a plugin manually by id: 157 | 158 | studiorack plugin uninstall studiorack/studiorack-template-steinberg/adelay 159 | 160 | ## Project create 161 | 162 | `studiorack project create ` creates a new project using the starter template. 163 | 164 | ## Project getLocal 165 | 166 | `studiorack project getLocal ` Get local project details by id: 167 | 168 | $ studiorack project getLocal demos/94th-project/94th 169 | ┌─────────────────────────┬──────┬──────────────────────────┬────────────┬─────────┬─────────┐ 170 | │ Id │ Name │ Description │ Date │ Version │ Tags │ 171 | ├─────────────────────────┼──────┼──────────────────────────┼────────────┼─────────┼─────────┤ 172 | │ demos/94th-project/94th │ 94th │ Created using StudioRack │ 2021-01-29 │ 1.0.0 │ Ableton │ 173 | └─────────────────────────┴──────┴──────────────────────────┴────────────┴─────────┴─────────┘ 174 | 175 | ## Project install 176 | 177 | `studiorack project install [input]` Install a project by id: 178 | 179 | studiorack project install demos/94th-project/94th 180 | 181 | ## Project listLocal 182 | 183 | `studiorack project listLocal` Get local plugin details by id: 184 | 185 | $ studiorack project listLocal 186 | ┌─────────────────────────────────┬──────────────────┬──────────────────────────┬────────────┬─────────┬──────────┐ 187 | │ Id │ Name │ Description │ Date │ Version │ Tags │ 188 | ├─────────────────────────────────┼──────────────────┼──────────────────────────┼────────────┼─────────┼──────────┤ 189 | │ /cubase-example │ Cubase Example │ Created using StudioRack │ 2021-01-27 │ 1.0.0 │ Cubase │ 190 | ├─────────────────────────────────┼──────────────────┼──────────────────────────┼────────────┼─────────┼──────────┤ 191 | │ demos/94th-project/94th │ 94th │ Created using StudioRack │ 2021-01-29 │ 1.0.0 │ Ableton │ 192 | ├─────────────────────────────────┼──────────────────┼──────────────────────────┼────────────┼─────────┼──────────┤ 193 | │ demos/alfredo-project/alfredo │ Alfredo │ Created using StudioRack │ 2020-10-24 │ 1.0.0 │ Ableton │ 194 | └─────────────────────────────────┴──────────────────┴──────────────────────────┴────────────┴─────────┴──────────┘ 195 | 196 | ## Project open 197 | 198 | `studiorack project open ` open the current music project. For example: 199 | 200 | studiorack project open demos/94th-project/94th 201 | 202 | ## Project uninstall 203 | 204 | `studiorack project uninstall [input]` Uninstall a project's plugins by id: 205 | 206 | studiorack plugin uninstall studiorack/studiorack-template-steinberg/adelay 207 | 208 | ## Validate 209 | 210 | `studiorack validate [path]` Validate plugin(s) using the Steinberg VST3 SDK validator. For example: 211 | 212 | studiorack validate "./myplugin/build/VST3/Release/myplugin.vst3" 213 | studiorack validate "./myplugin/build/VST3/Release/**/*.{vst,vst3}" 214 | 215 | ## Help 216 | 217 | `studiorack --help` lists the available CLI commands: 218 | 219 | Usage: 220 | studiorack [options] [command] 221 | 222 | Options: 223 | -V, --version output the version number 224 | -h, --help display help for command 225 | 226 | Commands: 227 | config View/update configuration 228 | plugin View/add/remove individual plugins 229 | project View/update projects 230 | validate [options] [path] Validate a plugin using the Steinberg VST3 SDK validator 231 | help [command] display help for command 232 | -------------------------------------------------------------------------------- /renderer/components/audio.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { getBasePath } from '../lib/path'; 3 | import styles from '../styles/components/audio.module.css'; 4 | 5 | type AudioProps = { 6 | file: string; 7 | }; 8 | 9 | const Audio = ({ file }: AudioProps) => { 10 | const [isPlaying, setIsPlaying] = useState(false); 11 | 12 | const play = () => { 13 | const el = document.getElementById('audio') as HTMLAudioElement; 14 | if (el.paused) { 15 | el.removeEventListener('ended', ended); 16 | el.addEventListener('ended', ended); 17 | el.currentTime = 0; 18 | el.play(); 19 | setIsPlaying(true); 20 | } 21 | }; 22 | 23 | const pause = () => { 24 | const el = document.getElementById('audio') as HTMLAudioElement; 25 | if (!el.paused) { 26 | el.pause(); 27 | setIsPlaying(false); 28 | } 29 | }; 30 | 31 | const ended = () => { 32 | setIsPlaying(false); 33 | }; 34 | 35 | return ( 36 |
37 | {isPlaying ? ( 38 | Pause 39 | ) : ( 40 | Play 41 | )} 42 | 45 |
46 | ); 47 | }; 48 | 49 | export default Audio; 50 | -------------------------------------------------------------------------------- /renderer/components/card.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/card.module.css'; 2 | import Link from 'next/link'; 3 | import { getBasePath } from '../lib/path'; 4 | import { PackageInterface, RegistryType } from '@open-audio-stack/core'; 5 | 6 | type CardProps = { 7 | section: RegistryType; 8 | item: PackageInterface; 9 | index: number; 10 | }; 11 | 12 | const Card = ({ section, item, index }: CardProps) => ( 13 | 14 |
15 |
16 |
17 |

18 | {item.versions[item.version].name} v{item.version} 19 |

20 |

{item.versions[item.version].installed}

21 | {item.versions[item.version].installed === true ? ( 22 | 23 | Installed 29 | 30 | ) : ( 31 | 32 | Download 38 | 39 | )} 40 |
41 |
    42 | Tags 43 | {item.versions[item.version].tags.map((tag: string, tagIndex: number) => ( 44 |
  • 45 | {tag} 46 | {tagIndex !== item.versions[item.version].tags.length - 1 ? ',' : ''} 47 |
  • 48 | ))} 49 |
50 |
51 | {item.versions[item.version].image ? ( 52 | {item.versions[item.version].name} 58 | ) : ( 59 | '' 60 | )} 61 |
62 | 63 | ); 64 | 65 | export default Card; 66 | -------------------------------------------------------------------------------- /renderer/components/code.tsx: -------------------------------------------------------------------------------- 1 | import { PackageInterface, PackageVersion } from '@open-audio-stack/core'; 2 | import styles from '../styles/components/code.module.css'; 3 | 4 | type CodeProps = { 5 | pkg: PackageInterface; 6 | pkgVersion: PackageVersion; 7 | }; 8 | 9 | const Code = ({ pkg, pkgVersion }: CodeProps) => ( 10 |
11 |

12 | Install via{' '} 13 | 14 | StudioRack CLI 15 | 16 |

17 | {pkgVersion.tags.includes('sfz') ? ( 18 | 19 |
studiorack plugin install sfztools/sfizz
20 |
studiorack plugin install {pkg.slug}
21 |
22 | ) : pkgVersion.tags.includes('sf2') ? ( 23 | 24 |
studiorack plugin install birch-san/juicysfplugin
25 |
studiorack plugin install {pkg.slug}
26 |
27 | ) : ( 28 |
studiorack plugin install {pkg.slug}
29 | )} 30 |
31 | ); 32 | 33 | export default Code; 34 | -------------------------------------------------------------------------------- /renderer/components/crumb.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/crumb.module.css'; 2 | import { getBasePath, getCrumbUrl } from '../lib/path'; 3 | 4 | type CrumbProps = { 5 | items: string[]; 6 | }; 7 | 8 | const Crumb = ({ items }: CrumbProps) => ( 9 |
10 |
    11 | {items.map((item: string, itemIndex: number) => ( 12 |
  • 13 | / 14 | 15 | {item} 16 | 17 |
  • 18 | ))} 19 |
20 |
21 | ); 22 | 23 | export default Crumb; 24 | -------------------------------------------------------------------------------- /renderer/components/dependency.tsx: -------------------------------------------------------------------------------- 1 | import { PackageVersion } from '@open-audio-stack/core'; 2 | import { getBasePath } from '../lib/path'; 3 | 4 | type DependencyProps = { 5 | plugin: PackageVersion; 6 | }; 7 | 8 | const Dependency = ({ plugin }: DependencyProps) => { 9 | if (plugin.tags.includes('sfz')) { 10 | return ( 11 | 12 | {' '} 13 | (This instrument needs to be loaded into a{' '} 14 | 15 | SFZ player 16 | 17 | ) 18 | 19 | ); 20 | } else if (plugin.tags.includes('sf2')) { 21 | return ( 22 | 23 | {' '} 24 | (This instrument needs to be loaded into a{' '} 25 | 26 | SoundFont 2 player 27 | 28 | ) 29 | 30 | ); 31 | } else { 32 | return ; 33 | } 34 | }; 35 | 36 | export default Dependency; 37 | -------------------------------------------------------------------------------- /renderer/components/details.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/details.module.css'; 2 | import { getBasePath } from '../lib/path'; 3 | import Crumb from './crumb'; 4 | import { timeSince } from '../lib/utils'; 5 | import Audio from './audio'; 6 | import Player from './player'; 7 | import Dependency from './dependency'; 8 | import Downloads from './download'; 9 | import Code from './code'; 10 | import Installer from './installer'; 11 | import { licenses, PackageFileMap, PackageInterface, PackageVersion, RegistryType } from '@open-audio-stack/core'; 12 | 13 | type DetailsProps = { 14 | downloads: PackageFileMap; 15 | pkg: PackageInterface; 16 | pkgVersion: PackageVersion; 17 | type: RegistryType; 18 | }; 19 | 20 | const Details = ({ downloads, pkg, pkgVersion, type }: DetailsProps) => ( 21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | {pkgVersion.audio ?
37 |
38 |
39 |

40 | {pkgVersion.name || ''} v{pkg.version} 41 |

42 |

43 | By{' '} 44 | 45 | {pkgVersion.author} 46 | 47 |

48 |

49 | {pkgVersion.description} 50 | 51 |

52 |
53 | {/*
Filesize {formatBytes(plugin.files.linux.size)}
*/} 54 |
55 | Date updated{' '} 61 | {timeSince(pkgVersion.date)} ago 62 |
63 |
64 | License{' '} 70 | {pkgVersion.license ? ( 71 | 72 | {licenses.filter(license => pkgVersion.license === license.value)[0].name} 73 | 74 | ) : ( 75 | 'none' 76 | )} 77 |
78 |
79 | Tags 80 |
    81 | {pkgVersion.tags.map((tag: string, tagIndex: number) => ( 82 |
  • 83 | {tag} 84 | {tagIndex !== pkgVersion.tags.length - 1 ? ',' : ''} 85 |
  • 86 | ))} 87 |
88 |
89 | 95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | 103 | 104 |
105 |
106 |
107 | ); 108 | 109 | export default Details; 110 | -------------------------------------------------------------------------------- /renderer/components/download.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/download.module.css'; 2 | import { getBasePath } from '../lib/path'; 3 | import { PackageFile, PackageFileMap, pathGetExt, systemTypes } from '@open-audio-stack/core'; 4 | 5 | type DownloadsProps = { 6 | downloads: PackageFileMap; 7 | }; 8 | 9 | const Downloads = ({ downloads }: DownloadsProps) => ( 10 |
11 |

Download and install manually:

12 | {Object.keys(downloads).map((system: string) => ( 13 |
14 |
15 | {systemTypes.filter(systemType => systemType.value === system)[0].name} 16 |
17 |
18 | {downloads[system].map((file: PackageFile) => ( 19 |
20 | {file.architectures.join(', ')} 21 | 22 | .{pathGetExt(file.url)} 23 | Download 29 | 30 |
31 | ))} 32 |
33 |
34 | ))} 35 |
36 | ); 37 | 38 | export default Downloads; 39 | -------------------------------------------------------------------------------- /renderer/components/filters.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import styles from '../styles/components/filters.module.css'; 3 | import MultiSelect from './multi-select'; 4 | import { ChangeEvent } from 'react'; 5 | import { 6 | licenses, 7 | pluginCategoryInstruments, 8 | PluginCategoryOption, 9 | PluginType, 10 | pluginTypes, 11 | presetTypes, 12 | ProjectFormatOption, 13 | projectFormats, 14 | projectTypes, 15 | RegistryType, 16 | systemTypes, 17 | } from '@open-audio-stack/core'; 18 | import { pluginCategoryEffects } from '@open-audio-stack/core'; 19 | import { getParam } from '../lib/plugin'; 20 | 21 | type FiltersProps = { 22 | section: RegistryType; 23 | }; 24 | 25 | const Filters = ({ section }: FiltersProps) => { 26 | const router = useRouter(); 27 | const type = getParam(router, 'type'); 28 | const search = getParam(router, 'search'); 29 | let categories: PluginCategoryOption[] | ProjectFormatOption[] = 30 | type && type[0] === PluginType.Effect ? pluginCategoryEffects : pluginCategoryInstruments; 31 | let types; 32 | // TODO move this logic to parent 33 | if (section === RegistryType.Plugins) { 34 | types = pluginTypes; 35 | } else if (section === RegistryType.Presets) { 36 | types = presetTypes; 37 | } else { 38 | categories = projectFormats; 39 | types = projectTypes; 40 | } 41 | const onSearch = (event: ChangeEvent) => { 42 | const el: HTMLInputElement = event.target as HTMLInputElement; 43 | router.query['search'] = el.value ? el.value.toLowerCase() : ''; 44 | router.push({ 45 | pathname: router.pathname, 46 | query: router.query, 47 | }); 48 | }; 49 | 50 | return ( 51 |
52 | Filter by: 53 | 54 | 55 | 56 | 57 | 66 |
67 | ); 68 | }; 69 | 70 | export default Filters; 71 | -------------------------------------------------------------------------------- /renderer/components/header.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/header.module.css'; 2 | 3 | type HeaderProps = { 4 | count?: number; 5 | title: string; 6 | }; 7 | 8 | const Header = ({ title, count }: HeaderProps) => ( 9 |
10 |

11 | {title} {count ? ({count}) : ''} 12 |

13 |
14 | ); 15 | 16 | export default Header; 17 | -------------------------------------------------------------------------------- /renderer/components/installer.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import styles from '../styles/components/download.module.css'; 3 | import { PackageInterface, PackageVersion, RegistryType } from '@open-audio-stack/core'; 4 | 5 | type InstallerProps = { 6 | pkg: PackageInterface; 7 | pkgVersion: PackageVersion; 8 | type: RegistryType; 9 | }; 10 | 11 | const Installer = ({ pkg, pkgVersion, type }: InstallerProps) => { 12 | const [isDisabled, setDisabled] = useState(false); 13 | const [, setPkVersion] = useState(pkgVersion); 14 | 15 | const install = () => { 16 | console.log('install', pkgVersion); 17 | if (typeof window !== 'undefined' && window.electronAPI) { 18 | setDisabled(true); 19 | window.electronAPI.install(type, pkg).then((pkgResponse: PackageVersion) => { 20 | console.log('install response', pkgResponse); 21 | pkgVersion.installed = true; 22 | setPkVersion(pkgVersion); 23 | setDisabled(false); 24 | }); 25 | } 26 | }; 27 | 28 | const uninstall = () => { 29 | console.log('uninstall', pkgVersion); 30 | if (typeof window !== 'undefined' && window.electronAPI) { 31 | setDisabled(true); 32 | window.electronAPI.uninstall(type, pkg).then((pkgResponse: PackageVersion) => { 33 | console.log('uninstall response', pkgResponse); 34 | pkgVersion.installed = false; 35 | setPkVersion(pkgVersion); 36 | setDisabled(false); 37 | }); 38 | } 39 | }; 40 | 41 | return ( 42 | 43 | {pkgVersion.installed !== true ? ( 44 | 47 | ) : ( 48 | 51 | )} 52 | 53 | ); 54 | }; 55 | 56 | export default Installer; 57 | -------------------------------------------------------------------------------- /renderer/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Navigation from './navigation'; 3 | import styles from '../styles/components/layout.module.css'; 4 | import { getBasePath } from '../lib/path'; 5 | import { pageTitle } from '../lib/utils'; 6 | 7 | export const siteTitle = 'StudioRack'; 8 | export const siteDesc = 'Automate your plugin publishing workflow, easy plugin installation and management'; 9 | 10 | type LayoutProps = { 11 | children: React.ReactNode; 12 | }; 13 | 14 | const Layout = ({ children }: LayoutProps) => ( 15 |
16 | 17 | {pageTitle(['An open-source audio plugin ecosystem'])} 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | {siteTitle} 34 | 35 | StudioRack 36 | 37 | 38 | 39 |
40 |
{children}
41 |
42 | ); 43 | 44 | export default Layout; 45 | -------------------------------------------------------------------------------- /renderer/components/list.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/list.module.css'; 2 | import Header from './header'; 3 | import Card from './card'; 4 | import Filters from './filters'; 5 | import Crumb from './crumb'; 6 | import Tabs from './tabs'; 7 | import { 8 | PackageInterface, 9 | PluginFormatOption, 10 | PresetFormatOption, 11 | ProjectFormatOption, 12 | RegistryType, 13 | } from '@open-audio-stack/core'; 14 | 15 | type ListProps = { 16 | filters?: boolean; 17 | items: PackageInterface[]; 18 | type: RegistryType; 19 | tabs?: PluginFormatOption[] | PresetFormatOption[] | ProjectFormatOption[]; 20 | title: string; 21 | }; 22 | 23 | const List = ({ filters = true, items, type, tabs, title }: ListProps) => ( 24 |
25 | 26 |
27 | {filters ? : ''} 28 | {tabs ? : ''} 29 |
30 | {items.map((item: PackageInterface, index: number) => ( 31 | 32 | ))} 33 |
34 |
35 | ); 36 | 37 | export default List; 38 | -------------------------------------------------------------------------------- /renderer/components/multi-select.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { includesValue, toSlug } from '../lib/utils'; 3 | import styles from '../styles/components/multi-select.module.css'; 4 | import { 5 | ArchitectureOption, 6 | LicenseOption, 7 | PluginTypeOption, 8 | PresetTypeOption, 9 | ProjectFormatOption, 10 | ProjectTypeOption, 11 | SystemTypeOption, 12 | } from '@open-audio-stack/core'; 13 | import { PluginCategoryOption } from '@open-audio-stack/core/build/types/PluginCategory'; 14 | 15 | type MultiSelectProps = { 16 | label: string; 17 | items: 18 | | PluginTypeOption[] 19 | | PresetTypeOption[] 20 | | ProjectTypeOption[] 21 | | ProjectFormatOption[] 22 | | PluginCategoryOption[] 23 | | LicenseOption[] 24 | | ArchitectureOption[] 25 | | SystemTypeOption[]; 26 | }; 27 | 28 | const MultiSelect = ({ label, items }: MultiSelectProps) => { 29 | const router = useRouter(); 30 | const slug: string = toSlug(label); 31 | 32 | const showCheckboxes = (e: any) => { 33 | e.preventDefault(); 34 | e.target.blur(); 35 | window.focus(); 36 | const checkboxes = document.getElementById(label); 37 | if (checkboxes?.style.display === 'block') { 38 | if (checkboxes) checkboxes.style.display = 'none'; 39 | } else { 40 | if (checkboxes) checkboxes.style.display = 'block'; 41 | } 42 | }; 43 | 44 | const isChecked = (value: string) => { 45 | if (!router.query[slug]) return false; 46 | return includesValue(router.query[slug], value); 47 | }; 48 | 49 | const updateUrl = () => { 50 | const form: HTMLFormElement = document.getElementById(slug) as HTMLFormElement; 51 | router.query[slug] = Array.from(new FormData(form).keys()); 52 | router.push({ 53 | pathname: router.pathname, 54 | query: router.query, 55 | }); 56 | }; 57 | 58 | return ( 59 |
60 | 63 |
64 | {items.map(item => ( 65 |
66 | 74 | 82 |
83 | ))} 84 |
85 |
86 | ); 87 | }; 88 | 89 | export default MultiSelect; 90 | -------------------------------------------------------------------------------- /renderer/components/navigation.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/navigation.module.css'; 2 | import { getBasePath, isSelected } from '../lib/path'; 3 | import { ELECTRON_APP } from '../lib/utils'; 4 | import { RegistryType } from '@open-audio-stack/core'; 5 | 6 | const Navigation = () => ( 7 |
8 | 9 | 12 | 66 |
67 | ); 68 | 69 | export default Navigation; 70 | -------------------------------------------------------------------------------- /renderer/components/player.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/components/player.module.css'; 2 | import Script from 'next/script'; 3 | import { getBasePath } from '../lib/path'; 4 | import { PackageVersion } from '@open-audio-stack/core'; 5 | 6 | type PlayerProps = { 7 | plugin: PackageVersion; 8 | }; 9 | 10 | const Player = ({ plugin }: PlayerProps) => { 11 | // Prototype of embedded sfz web player. 12 | // There are better ways to do this. 13 | const loadSfzPlayer = (event: React.MouseEvent) => { 14 | const el = document.getElementById('sfzPlayer'); 15 | if (!el) return; 16 | if (el.className === 'open') { 17 | el.className = ''; 18 | return; 19 | } 20 | const name = (event.currentTarget as HTMLTextAreaElement).getAttribute('data-name') || ''; 21 | const id = (event.currentTarget as HTMLTextAreaElement).getAttribute('data-id') || ''; 22 | console.log('loadSfzPlayer', name, id); 23 | el.innerHTML = ''; 24 | new window.Sfz.Player('sfzPlayer', { 25 | audio: {}, 26 | instrument: { name, id }, 27 | interface: {}, 28 | }); 29 | window.setTimeout(() => { 30 | el.className = 'open'; 31 | }, 0); 32 | }; 33 | 34 | return ( 35 |
36 | open in sfz player 45 | 49 |