├── saved-audio
└── .gitkeep
├── g4l-ui
├── saved-audio
│ └── .gitkeep
├── .erb
│ ├── mocks
│ │ └── fileMock.js
│ ├── img
│ │ ├── erb-logo.png
│ │ └── erb-banner.svg
│ ├── configs
│ │ ├── .eslintrc
│ │ ├── webpack.config.eslint.ts
│ │ ├── webpack.paths.ts
│ │ ├── webpack.config.base.ts
│ │ ├── webpack.config.renderer.dev.dll.ts
│ │ ├── webpack.config.preload.dev.ts
│ │ ├── webpack.config.main.prod.ts
│ │ ├── webpack.config.renderer.prod.ts
│ │ └── webpack.config.renderer.dev.ts
│ └── scripts
│ │ ├── .eslintrc
│ │ ├── link-modules.ts
│ │ ├── clean.js
│ │ ├── check-node-env.js
│ │ ├── check-port-in-use.js
│ │ ├── delete-source-maps.js
│ │ ├── electron-rebuild.js
│ │ ├── check-build-exists.ts
│ │ ├── notarize.js
│ │ └── check-native-dep.js
├── .vs
│ ├── ProjectSettings.json
│ ├── slnx.sqlite
│ ├── g4l-ui
│ │ ├── v17
│ │ │ └── .wsuo
│ │ └── FileContentIndex
│ │ │ ├── 74cca680-690e-4bdc-bf30-d9b022aef20a.vsidx
│ │ │ ├── 0566ba45-b611-454b-94d1-4c7906388fb4.vsidx
│ │ │ ├── 63ef41b1-ad84-47f2-ade4-53c1bce2aeba.vsidx
│ │ │ ├── 78fc0749-c947-4ca3-8ef0-98cf7aaeb034.vsidx
│ │ │ └── 91a97c04-0a45-4402-b1a6-ad9ac6958337.vsidx
│ └── VSWorkspaceState.json
├── assets
│ ├── icon.icns
│ ├── icon.ico
│ ├── icon.png
│ ├── icons
│ │ ├── 16x16.png
│ │ ├── 24x24.png
│ │ ├── 32x32.png
│ │ ├── 48x48.png
│ │ ├── 64x64.png
│ │ ├── 96x96.png
│ │ ├── 128x128.png
│ │ ├── 256x256.png
│ │ ├── 512x512.png
│ │ └── 1024x1024.png
│ ├── entitlements.mac.plist
│ ├── assets.d.ts
│ └── icon.svg
├── .vscode
│ ├── extensions.json
│ ├── settings.json
│ └── launch.json
├── .github
│ ├── config.yml
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── 3-Feature_request.md
│ │ ├── 2-Question.md
│ │ └── 1-Bug_report.md
│ ├── stale.yml
│ └── workflows
│ │ ├── test.yml
│ │ ├── publish.yml
│ │ └── codeql-analysis.yml
├── src
│ ├── renderer
│ │ ├── preload.d.ts
│ │ ├── index.ejs
│ │ ├── index.tsx
│ │ ├── LoadingIndicator.tsx
│ │ ├── NotificationOverlay.tsx
│ │ ├── GuideNumber.tsx
│ │ └── App.css
│ ├── __tests__
│ │ └── App.test.tsx
│ ├── main
│ │ ├── util.ts
│ │ ├── preload.ts
│ │ ├── main.ts
│ │ └── menu.ts
│ └── global.d.ts
├── .editorconfig
├── .gitattributes
├── .gitignore
├── tsconfig.json
├── .eslintignore
├── release
│ └── app
│ │ └── package.json
├── LICENSE
├── .eslintrc.js
├── CODE_OF_CONDUCT.md
├── package.json
└── CHANGELOG.md
├── GARY.amxd
├── gary4live_screenshot.png
├── .gitignore
├── launch_electron.js
├── package.json
├── README.md
├── electron-communication.js
└── commentedout.js
/saved-audio/.gitkeep:
--------------------------------------------------------------------------------
1 | ECHO is on.
2 |
--------------------------------------------------------------------------------
/g4l-ui/saved-audio/.gitkeep:
--------------------------------------------------------------------------------
1 | ECHO is on.
2 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/g4l-ui/.vs/ProjectSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "CurrentProjectSetting": null
3 | }
--------------------------------------------------------------------------------
/GARY.amxd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/GARY.amxd
--------------------------------------------------------------------------------
/g4l-ui/.vs/slnx.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/.vs/slnx.sqlite
--------------------------------------------------------------------------------
/g4l-ui/assets/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icon.icns
--------------------------------------------------------------------------------
/g4l-ui/assets/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icon.ico
--------------------------------------------------------------------------------
/g4l-ui/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icon.png
--------------------------------------------------------------------------------
/gary4live_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/gary4live_screenshot.png
--------------------------------------------------------------------------------
/g4l-ui/.erb/img/erb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/.erb/img/erb-logo.png
--------------------------------------------------------------------------------
/g4l-ui/.vs/g4l-ui/v17/.wsuo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/.vs/g4l-ui/v17/.wsuo
--------------------------------------------------------------------------------
/g4l-ui/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"]
3 | }
4 |
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/16x16.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/24x24.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/32x32.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/48x48.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/64x64.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/96x96.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/128x128.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/256x256.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/512x512.png
--------------------------------------------------------------------------------
/g4l-ui/assets/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/assets/icons/1024x1024.png
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/g4l-ui/.github/config.yml:
--------------------------------------------------------------------------------
1 | requiredHeaders:
2 | - Prerequisites
3 | - Expected Behavior
4 | - Current Behavior
5 | - Possible Solution
6 | - Your Environment
7 |
--------------------------------------------------------------------------------
/g4l-ui/.vs/g4l-ui/FileContentIndex/74cca680-690e-4bdc-bf30-d9b022aef20a.vsidx:
--------------------------------------------------------------------------------
1 | CDG G ' 3
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.config.eslint.ts:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 |
3 | module.exports = require('./webpack.config.renderer.dev').default;
4 |
--------------------------------------------------------------------------------
/g4l-ui/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [electron-react-boilerplate, amilajack]
4 | patreon: amilajack
5 | open_collective: electron-react-boilerplate-594
6 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off",
6 | "import/no-extraneous-dependencies": "off"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/g4l-ui/.vs/g4l-ui/FileContentIndex/0566ba45-b611-454b-94d1-4c7906388fb4.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/.vs/g4l-ui/FileContentIndex/0566ba45-b611-454b-94d1-4c7906388fb4.vsidx
--------------------------------------------------------------------------------
/g4l-ui/.vs/g4l-ui/FileContentIndex/63ef41b1-ad84-47f2-ade4-53c1bce2aeba.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/.vs/g4l-ui/FileContentIndex/63ef41b1-ad84-47f2-ade4-53c1bce2aeba.vsidx
--------------------------------------------------------------------------------
/g4l-ui/.vs/g4l-ui/FileContentIndex/78fc0749-c947-4ca3-8ef0-98cf7aaeb034.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/.vs/g4l-ui/FileContentIndex/78fc0749-c947-4ca3-8ef0-98cf7aaeb034.vsidx
--------------------------------------------------------------------------------
/g4l-ui/.vs/g4l-ui/FileContentIndex/91a97c04-0a45-4402-b1a6-ad9ac6958337.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/betweentwomidnights/gary4live/HEAD/g4l-ui/.vs/g4l-ui/FileContentIndex/91a97c04-0a45-4402-b1a6-ad9ac6958337.vsidx
--------------------------------------------------------------------------------
/g4l-ui/src/renderer/preload.d.ts:
--------------------------------------------------------------------------------
1 | import { ElectronHandler } from '../main/preload';
2 |
3 | declare global {
4 | // eslint-disable-next-line no-unused-vars
5 | interface Window {
6 | electron: ElectronHandler;
7 | }
8 | }
9 |
10 | export {};
11 |
--------------------------------------------------------------------------------
/g4l-ui/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/g4l-ui/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.exe binary
3 | *.png binary
4 | *.jpg binary
5 | *.jpeg binary
6 | *.ico binary
7 | *.icns binary
8 | *.eot binary
9 | *.otf binary
10 | *.ttf binary
11 | *.woff binary
12 | *.woff2 binary
13 |
--------------------------------------------------------------------------------
/g4l-ui/src/__tests__/App.test.tsx:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 | import { render } from '@testing-library/react';
3 | import App from '../renderer/App';
4 |
5 | describe('App', () => {
6 | it('should render', () => {
7 | expect(render()).toBeTruthy();
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/g4l-ui/.vs/VSWorkspaceState.json:
--------------------------------------------------------------------------------
1 | {
2 | "ExpandedNodes": [
3 | "",
4 | "\\assets",
5 | "\\release",
6 | "\\release\\app",
7 | "\\src",
8 | "\\src\\main",
9 | "\\src\\renderer",
10 | "\\src\\__tests__"
11 | ],
12 | "SelectedNode": "\\package.json",
13 | "PreviewInSolutionExplorer": false
14 | }
--------------------------------------------------------------------------------
/g4l-ui/src/renderer/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 | gary4live
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/link-modules.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import webpackPaths from '../configs/webpack.paths';
3 |
4 | const { srcNodeModulesPath } = webpackPaths;
5 | const { appNodeModulesPath } = webpackPaths;
6 |
7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');
9 | }
10 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/clean.js:
--------------------------------------------------------------------------------
1 | import { rimrafSync } from 'rimraf';
2 | import fs from 'fs';
3 | import webpackPaths from '../configs/webpack.paths';
4 |
5 | const foldersToRemove = [
6 | webpackPaths.distPath,
7 | webpackPaths.buildPath,
8 | webpackPaths.dllPath,
9 | ];
10 |
11 | foldersToRemove.forEach((folder) => {
12 | if (fs.existsSync(folder)) rimrafSync(folder);
13 | });
14 |
--------------------------------------------------------------------------------
/g4l-ui/assets/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-unsigned-executable-memory
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ffmpeg-master-latest-win64-gpl/
2 | node_modules/
3 | g4l-ui/node_modules/
4 | *.logg4l-ui/saved-audio/
5 | *.wav
6 | *.wav.asd
7 | *.log
8 | node-v20.13.1-x64.msi
9 | commentedout_backup.js
10 | commentedout_websockets_backup.js
11 | myBufferUI.js
12 | update_path.bat
13 | waveform.js
14 | recordtoggle.js
15 | package-lock.json
16 | commentedout_test.js
17 | g4l-ui/package-lock.json
18 | g4l-ui/release/build/
19 | . v s /
20 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/check-node-env.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 |
3 | export default function checkNodeEnv(expectedEnv) {
4 | if (!expectedEnv) {
5 | throw new Error('"expectedEnv" not set');
6 | }
7 |
8 | if (process.env.NODE_ENV !== expectedEnv) {
9 | console.log(
10 | chalk.whiteBright.bgRed.bold(
11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`,
12 | ),
13 | );
14 | process.exit(2);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/g4l-ui/.github/ISSUE_TEMPLATE/3-Feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: You want something added to the boilerplate. 🎉
4 | labels: 'enhancement'
5 | ---
6 |
7 |
16 |
--------------------------------------------------------------------------------
/g4l-ui/.github/ISSUE_TEMPLATE/2-Question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question.❓
4 | labels: 'question'
5 | ---
6 |
7 | ## Summary
8 |
9 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/g4l-ui/src/main/util.ts:
--------------------------------------------------------------------------------
1 | /* eslint import/prefer-default-export: off */
2 | import { URL } from 'url';
3 | import path from 'path';
4 |
5 | export function resolveHtmlPath(htmlFileName: string) {
6 | if (process.env.NODE_ENV === 'development') {
7 | const port = process.env.PORT || 1212;
8 | const url = new URL(`http://localhost:${port}`);
9 | url.pathname = htmlFileName;
10 | return url.href;
11 | }
12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`;
13 | }
14 |
--------------------------------------------------------------------------------
/g4l-ui/src/renderer/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 | import App from './App';
3 |
4 | const container = document.getElementById('root') as HTMLElement;
5 | const root = createRoot(container);
6 | root.render();
7 |
8 | // calling IPC exposed from preload script
9 | window.electron.ipcRenderer.once('ipc-example', (arg) => {
10 | // eslint-disable-next-line no-console
11 | console.log(arg);
12 | });
13 | window.electron.ipcRenderer.sendMessage('ipc-example', ['ping']);
14 |
--------------------------------------------------------------------------------
/g4l-ui/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage directory used by tools like istanbul
11 | coverage
12 | .eslintcache
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # OSX
19 | .DS_Store
20 |
21 | release/app/dist
22 | release/build
23 | .erb/dll
24 |
25 | .idea
26 | npm-debug.log.*
27 | *.css.d.ts
28 | *.sass.d.ts
29 | *.scss.d.ts
30 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/check-port-in-use.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import detectPort from 'detect-port';
3 |
4 | const port = process.env.PORT || '1212';
5 |
6 | detectPort(port, (_err, availablePort) => {
7 | if (port !== String(availablePort)) {
8 | throw new Error(
9 | chalk.whiteBright.bgRed.bold(
10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`,
11 | ),
12 | );
13 | } else {
14 | process.exit(0);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/delete-source-maps.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { rimrafSync } from 'rimraf';
4 | import webpackPaths from '../configs/webpack.paths';
5 |
6 | export default function deleteSourceMaps() {
7 | if (fs.existsSync(webpackPaths.distMainPath))
8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), {
9 | glob: true,
10 | });
11 | if (fs.existsSync(webpackPaths.distRendererPath))
12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), {
13 | glob: true,
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/g4l-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "incremental": true,
4 | "target": "es2022",
5 | "module": "commonjs",
6 | "lib": [ "dom", "es2022" ],
7 | "jsx": "react-jsx",
8 | "strict": true,
9 | "sourceMap": true,
10 | "moduleResolution": "node",
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "resolveJsonModule": true,
14 | "allowJs": true,
15 | "outDir": ".erb/dll",
16 | "typeRoots": [ "./node_modules/@types", "./src" ]
17 | },
18 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"]
19 | }
20 |
--------------------------------------------------------------------------------
/g4l-ui/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage directory used by tools like istanbul
11 | coverage
12 | .eslintcache
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # OSX
19 | .DS_Store
20 |
21 | release/app/dist
22 | release/build
23 | .erb/dll
24 |
25 | .idea
26 | npm-debug.log.*
27 | *.css.d.ts
28 | *.sass.d.ts
29 | *.scss.d.ts
30 |
31 | # eslint ignores hidden directories by default:
32 | # https://github.com/eslint/eslint/issues/8429
33 | !.erb
34 |
--------------------------------------------------------------------------------
/g4l-ui/release/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gary4live",
3 | "version": "1.0.0",
4 | "description": "ya know, for music",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Kevin Griffing",
8 | "email": "kev@thecollabagepatch.com",
9 | "url": "https://github.com/betweentwomidnights"
10 | },
11 | "main": "./dist/main/main.js",
12 | "scripts": {
13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js",
14 | "postinstall": "npm run rebuild && npm run link-modules",
15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts"
16 | },
17 | "dependencies": {}
18 | }
19 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/electron-rebuild.js:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process';
2 | import fs from 'fs';
3 | import { dependencies } from '../../release/app/package.json';
4 | import webpackPaths from '../configs/webpack.paths';
5 |
6 | if (
7 | Object.keys(dependencies || {}).length > 0 &&
8 | fs.existsSync(webpackPaths.appNodeModulesPath)
9 | ) {
10 | const electronRebuildCmd =
11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .';
12 | const cmd =
13 | process.platform === 'win32'
14 | ? electronRebuildCmd.replace(/\//g, '\\')
15 | : electronRebuildCmd;
16 | execSync(cmd, {
17 | cwd: webpackPaths.appPath,
18 | stdio: 'inherit',
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/g4l-ui/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - discussion
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/g4l-ui/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | ".eslintrc": "jsonc",
4 | ".prettierrc": "jsonc",
5 | ".eslintignore": "ignore"
6 | },
7 |
8 | "eslint.validate": [
9 | "javascript",
10 | "javascriptreact",
11 | "html",
12 | "typescriptreact"
13 | ],
14 |
15 | "javascript.validate.enable": false,
16 | "javascript.format.enable": false,
17 | "typescript.format.enable": false,
18 |
19 | "search.exclude": {
20 | ".git": true,
21 | ".eslintcache": true,
22 | ".erb/dll": true,
23 | "release/{build,app/dist}": true,
24 | "node_modules": true,
25 | "npm-debug.log.*": true,
26 | "test/**/__snapshots__": true,
27 | "package-lock.json": true,
28 | "*.{css,sass,scss}.d.ts": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/g4l-ui/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Electron: Main",
6 | "type": "node",
7 | "request": "launch",
8 | "protocol": "inspector",
9 | "runtimeExecutable": "npm",
10 | "runtimeArgs": ["run", "start"],
11 | "env": {
12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223"
13 | }
14 | },
15 | {
16 | "name": "Electron: Renderer",
17 | "type": "chrome",
18 | "request": "attach",
19 | "port": 9223,
20 | "webRoot": "${workspaceFolder}",
21 | "timeout": 15000
22 | }
23 | ],
24 | "compounds": [
25 | {
26 | "name": "Electron: All",
27 | "configurations": ["Electron: Main", "Electron: Renderer"]
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/g4l-ui/assets/assets.d.ts:
--------------------------------------------------------------------------------
1 | type Styles = Record;
2 |
3 | declare module '*.svg' {
4 | import React = require('react');
5 |
6 | export const ReactComponent: React.FC>;
7 |
8 | const content: string;
9 | export default content;
10 | }
11 |
12 | declare module '*.png' {
13 | const content: string;
14 | export default content;
15 | }
16 |
17 | declare module '*.jpg' {
18 | const content: string;
19 | export default content;
20 | }
21 |
22 | declare module '*.scss' {
23 | const content: Styles;
24 | export default content;
25 | }
26 |
27 | declare module '*.sass' {
28 | const content: Styles;
29 | export default content;
30 | }
31 |
32 | declare module '*.css' {
33 | const content: Styles;
34 | export default content;
35 | }
36 |
--------------------------------------------------------------------------------
/g4l-ui/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | matrix:
11 | os: [macos-latest, windows-latest, ubuntu-latest]
12 |
13 | steps:
14 | - name: Check out Git repository
15 | uses: actions/checkout@v3
16 |
17 | - name: Install Node.js and NPM
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: 18
21 | cache: npm
22 |
23 | - name: npm install
24 | run: |
25 | npm install
26 |
27 | - name: npm test
28 | env:
29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | run: |
31 | npm run package
32 | npm run lint
33 | npm exec tsc
34 | npm test
35 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/check-build-exists.ts:
--------------------------------------------------------------------------------
1 | // Check if the renderer and main bundles are built
2 | import path from 'path';
3 | import chalk from 'chalk';
4 | import fs from 'fs';
5 | import webpackPaths from '../configs/webpack.paths';
6 |
7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js');
8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js');
9 |
10 | if (!fs.existsSync(mainPath)) {
11 | throw new Error(
12 | chalk.whiteBright.bgRed.bold(
13 | 'The main process is not built yet. Build it by running "npm run build:main"',
14 | ),
15 | );
16 | }
17 |
18 | if (!fs.existsSync(rendererPath)) {
19 | throw new Error(
20 | chalk.whiteBright.bgRed.bold(
21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"',
22 | ),
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/launch_electron.js:
--------------------------------------------------------------------------------
1 | const Max = require('max-api');
2 | const { exec } = require('child_process');
3 | const path = require('path')
4 |
5 | Max.post("Node script loaded.");
6 |
7 | // Function to launch the Electron app
8 | function launchElectronApp() {
9 | const path = "C:\\g4l\\g4l-ui\\release\\build\\Gary4Live Setup 1.0.0.exe";
10 | exec(`"${path}"`, (error, stdout, stderr) => {
11 | if (error) {
12 | Max.post(`exec error: ${error}`);
13 | return;
14 | }
15 | Max.post(`stdout: ${stdout}`);
16 | if (stderr) {
17 | Max.post(`stderr: ${stderr}`);
18 | }
19 | });
20 | }
21 |
22 | // Ensure Node script is ready
23 | Max.addHandler('launch', () => {
24 | launchElectronApp();
25 | });
26 |
27 | // Optional: add a ready check
28 | Max.addHandler('ready', () => {
29 | Max.outlet('ready');
30 | });
31 |
32 | // Listen for bang from Max
33 | Max.addHandler('bang', () => {
34 | launchElectronApp();
35 | });
36 |
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "g4l",
3 | "version": "1.0.0",
4 | "description": "this is gary4live. gary iterates on input audio using max for live and musicgen.",
5 | "main": "commentedout.js",
6 | "dependencies": {
7 | "async": "^0.2.10",
8 | "asynckit": "^0.4.0",
9 | "combined-stream": "^1.0.8",
10 | "debug": "^4.3.4",
11 | "delayed-stream": "^1.0.0",
12 | "engine.io-client": "^6.5.3",
13 | "engine.io-parser": "^5.2.2",
14 | "fluent-ffmpeg": "^2.1.3",
15 | "form-data": "^4.0.0",
16 | "isexe": "^2.0.0",
17 | "mime-db": "^1.52.0",
18 | "mime-types": "^2.1.35",
19 | "ms": "^2.1.2",
20 | "nvm": "^0.0.4",
21 | "python-shell": "^5.0.0",
22 | "socket.io-client": "^4.7.4",
23 | "socket.io-parser": "^4.2.4",
24 | "which": "^1.3.1",
25 | "ws": "^8.11.0",
26 | "xmlhttprequest-ssl": "^2.0.0"
27 | },
28 | "scripts": {
29 | "test": "echo \"Error: no test specified\" && exit 1"
30 | },
31 | "keywords": [],
32 | "author": "",
33 | "license": "ISC"
34 | }
35 |
--------------------------------------------------------------------------------
/g4l-ui/src/global.d.ts:
--------------------------------------------------------------------------------
1 | export { };
2 |
3 | declare global {
4 | interface Window {
5 | api: {
6 | send: (channel: string, data: any) => void;
7 | receive: (channel: string, func: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => void;
8 | remove: (channel: string, func: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => void;
9 | startDrag: (filePath: string) => void;
10 | saveAudioFile: (base64Data: string) => void;
11 | onAudioFileSaved: (callback: (filePath: string) => void) => void;
12 | };
13 | electron: {
14 | ipcRenderer: {
15 | sendMessage(channel: string, ...args: unknown[]): void;
16 | saveAudioFile: (base64Data: string) => void;
17 | onAudioFileSaved: (callback: (filePath: string) => void) => void;
18 | on(channel: string, func: (...args: unknown[]) => void): () => void;
19 | once(channel: string, func: (...args: unknown[]) => void): void;
20 | startDrag: (filePath: string) => void;
21 | };
22 | };
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/notarize.js:
--------------------------------------------------------------------------------
1 | const { notarize } = require('@electron/notarize');
2 | const { build } = require('../../package.json');
3 |
4 | exports.default = async function notarizeMacos(context) {
5 | const { electronPlatformName, appOutDir } = context;
6 | if (electronPlatformName !== 'darwin') {
7 | return;
8 | }
9 |
10 | if (process.env.CI !== 'true') {
11 | console.warn('Skipping notarizing step. Packaging is not running in CI');
12 | return;
13 | }
14 |
15 | if (
16 | !('APPLE_ID' in process.env && 'APPLE_APP_SPECIFIC_PASSWORD' in process.env)
17 | ) {
18 | console.warn(
19 | 'Skipping notarizing step. APPLE_ID and APPLE_APP_SPECIFIC_PASSWORD env variables must be set',
20 | );
21 | return;
22 | }
23 |
24 | const appName = context.packager.appInfo.productFilename;
25 |
26 | await notarize({
27 | appBundleId: build.appId,
28 | appPath: `${appOutDir}/${appName}.app`,
29 | appleId: process.env.APPLE_ID,
30 | appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
31 | });
32 | };
33 |
--------------------------------------------------------------------------------
/g4l-ui/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 thebigdookie
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 |
--------------------------------------------------------------------------------
/g4l-ui/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'erb',
3 | plugins: ['@typescript-eslint'],
4 | rules: {
5 | // A temporary hack related to IDE not resolving correct package.json
6 | 'import/no-extraneous-dependencies': 'off',
7 | 'react/react-in-jsx-scope': 'off',
8 | 'react/jsx-filename-extension': 'off',
9 | 'import/extensions': 'off',
10 | 'import/no-unresolved': 'off',
11 | 'import/no-import-module-exports': 'off',
12 | 'no-shadow': 'off',
13 | '@typescript-eslint/no-shadow': 'error',
14 | 'no-unused-vars': 'off',
15 | '@typescript-eslint/no-unused-vars': 'error',
16 | },
17 | parserOptions: {
18 | ecmaVersion: 2022,
19 | sourceType: 'module',
20 | },
21 | settings: {
22 | 'import/resolver': {
23 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
24 | node: {},
25 | webpack: {
26 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'),
27 | },
28 | typescript: {},
29 | },
30 | 'import/parsers': {
31 | '@typescript-eslint/parser': ['.ts', '.tsx'],
32 | },
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.paths.ts:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const rootPath = path.join(__dirname, '../..');
4 |
5 | const dllPath = path.join(__dirname, '../dll');
6 |
7 | const srcPath = path.join(rootPath, 'src');
8 | const srcMainPath = path.join(srcPath, 'main');
9 | const srcRendererPath = path.join(srcPath, 'renderer');
10 |
11 | const releasePath = path.join(rootPath, 'release');
12 | const appPath = path.join(releasePath, 'app');
13 | const appPackagePath = path.join(appPath, 'package.json');
14 | const appNodeModulesPath = path.join(appPath, 'node_modules');
15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules');
16 |
17 | const distPath = path.join(appPath, 'dist');
18 | const distMainPath = path.join(distPath, 'main');
19 | const distRendererPath = path.join(distPath, 'renderer');
20 |
21 | const buildPath = path.join(releasePath, 'build');
22 |
23 | export default {
24 | rootPath,
25 | dllPath,
26 | srcPath,
27 | srcMainPath,
28 | srcRendererPath,
29 | releasePath,
30 | appPath,
31 | appPackagePath,
32 | appNodeModulesPath,
33 | srcNodeModulesPath,
34 | distPath,
35 | distMainPath,
36 | distRendererPath,
37 | buildPath,
38 | };
39 |
--------------------------------------------------------------------------------
/g4l-ui/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | publish:
10 | # To enable auto publishing to github, update your electron publisher
11 | # config in package.json > "build" and remove the conditional below
12 | if: ${{ github.repository_owner == 'electron-react-boilerplate' }}
13 |
14 | runs-on: ${{ matrix.os }}
15 |
16 | strategy:
17 | matrix:
18 | os: [macos-latest]
19 |
20 | steps:
21 | - name: Checkout git repo
22 | uses: actions/checkout@v3
23 |
24 | - name: Install Node and NPM
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: 18
28 | cache: npm
29 |
30 | - name: Install and build
31 | run: |
32 | npm install
33 | npm run postinstall
34 | npm run build
35 |
36 | - name: Publish releases
37 | env:
38 | # These values are used for auto updates signing
39 | APPLE_ID: ${{ secrets.APPLE_ID }}
40 | APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASS }}
41 | CSC_LINK: ${{ secrets.CSC_LINK }}
42 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
43 | # This is used for uploading release assets to github
44 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 | run: |
46 | npm exec electron-builder -- --publish always --win --mac --linux
47 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.config.base.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import webpack from 'webpack';
6 | import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin';
7 | import webpackPaths from './webpack.paths';
8 | import { dependencies as externals } from '../../release/app/package.json';
9 |
10 | const configuration: webpack.Configuration = {
11 | externals: [...Object.keys(externals || {})],
12 |
13 | stats: 'errors-only',
14 |
15 | module: {
16 | rules: [
17 | {
18 | test: /\.[jt]sx?$/,
19 | exclude: /node_modules/,
20 | use: {
21 | loader: 'ts-loader',
22 | options: {
23 | // Remove this line to enable type checking in webpack builds
24 | transpileOnly: true,
25 | compilerOptions: {
26 | module: 'esnext',
27 | },
28 | },
29 | },
30 | },
31 | ],
32 | },
33 |
34 | output: {
35 | path: webpackPaths.srcPath,
36 | // https://github.com/webpack/webpack/issues/1114
37 | library: {
38 | type: 'commonjs2',
39 | },
40 | },
41 |
42 | /**
43 | * Determine the array of extensions that should be used to resolve modules.
44 | */
45 | resolve: {
46 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
47 | modules: [webpackPaths.srcPath, 'node_modules'],
48 | // There is no need to add aliases here, the paths in tsconfig get mirrored
49 | plugins: [new TsconfigPathsPlugins()],
50 | },
51 |
52 | plugins: [
53 | new webpack.EnvironmentPlugin({
54 | NODE_ENV: 'production',
55 | }),
56 | ],
57 | };
58 |
59 | export default configuration;
60 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.config.renderer.dev.dll.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds the DLL for development electron renderer process
3 | */
4 |
5 | import webpack from 'webpack';
6 | import path from 'path';
7 | import { merge } from 'webpack-merge';
8 | import baseConfig from './webpack.config.base';
9 | import webpackPaths from './webpack.paths';
10 | import { dependencies } from '../../package.json';
11 | import checkNodeEnv from '../scripts/check-node-env';
12 |
13 | checkNodeEnv('development');
14 |
15 | const dist = webpackPaths.dllPath;
16 |
17 | const configuration: webpack.Configuration = {
18 | context: webpackPaths.rootPath,
19 |
20 | devtool: 'eval',
21 |
22 | mode: 'development',
23 |
24 | target: 'electron-renderer',
25 |
26 | externals: ['fsevents', 'crypto-browserify'],
27 |
28 | /**
29 | * Use `module` from `webpack.config.renderer.dev.js`
30 | */
31 | module: require('./webpack.config.renderer.dev').default.module,
32 |
33 | entry: {
34 | renderer: Object.keys(dependencies || {}),
35 | },
36 |
37 | output: {
38 | path: dist,
39 | filename: '[name].dev.dll.js',
40 | library: {
41 | name: 'renderer',
42 | type: 'var',
43 | },
44 | },
45 |
46 | plugins: [
47 | new webpack.DllPlugin({
48 | path: path.join(dist, '[name].json'),
49 | name: '[name]',
50 | }),
51 |
52 | /**
53 | * Create global constants which can be configured at compile time.
54 | *
55 | * Useful for allowing different behaviour between development builds and
56 | * release builds
57 | *
58 | * NODE_ENV should be production so that modules do not perform certain
59 | * development checks
60 | */
61 | new webpack.EnvironmentPlugin({
62 | NODE_ENV: 'development',
63 | }),
64 |
65 | new webpack.LoaderOptionsPlugin({
66 | debug: true,
67 | options: {
68 | context: webpackPaths.srcPath,
69 | output: {
70 | path: webpackPaths.dllPath,
71 | },
72 | },
73 | }),
74 | ],
75 | };
76 |
77 | export default merge(baseConfig, configuration);
78 |
--------------------------------------------------------------------------------
/g4l-ui/.github/ISSUE_TEMPLATE/1-Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: You're having technical issues. 🐞
4 | labels: 'bug'
5 | ---
6 |
7 |
8 |
9 | ## Prerequisites
10 |
11 |
12 |
13 | - [ ] Using npm
14 | - [ ] Using an up-to-date [`main` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/main)
15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/)
16 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400)
17 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true npm run build && npm start`
18 |
19 | ## Expected Behavior
20 |
21 |
22 |
23 | ## Current Behavior
24 |
25 |
26 |
27 | ## Steps to Reproduce
28 |
29 |
30 |
31 |
32 | 1.
33 |
34 | 2.
35 |
36 | 3.
37 |
38 | 4.
39 |
40 | ## Possible Solution (Not obligatory)
41 |
42 |
43 |
44 | ## Context
45 |
46 |
47 |
48 |
49 |
50 | ## Your Environment
51 |
52 |
53 |
54 | - Node version :
55 | - electron-react-boilerplate version or branch :
56 | - Operating System and version :
57 | - Link to your project :
58 |
59 |
68 |
--------------------------------------------------------------------------------
/g4l-ui/src/renderer/LoadingIndicator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Loader2 } from 'lucide-react';
3 |
4 | interface LoadingIndicatorProps {
5 | action: string | null;
6 | }
7 |
8 | const LoadingIndicator: React.FC = ({ action }) => {
9 | if (!action) return null;
10 |
11 | // Map actions to user-friendly messages
12 | const actionMessages: { [key: string]: string } = {
13 | bang: 'generating initial audio...',
14 | continue: 'generating continuation...',
15 | retry: 'regenerating audio...',
16 | load_output: 'loading output...',
17 | crop: 'cropping audio...',
18 | transform: 'transforming audio...',
19 | reset_transform: 'clearing session state... you\'ll now go back to transforming the top waveform'
20 | };
21 |
22 | const message = actionMessages[action] || 'processing...';
23 |
24 | return (
25 |
43 |
48 | {message}
49 |
50 | );
51 | };
52 |
53 | // Add the keyframes for the spin animation
54 | const styleSheet = document.createElement('style');
55 | styleSheet.textContent = `
56 | @keyframes spin {
57 | from {
58 | transform: rotate(0deg);
59 | }
60 | to {
61 | transform: rotate(360deg);
62 | }
63 | }`;
64 | document.head.appendChild(styleSheet);
65 |
66 | export default LoadingIndicator;
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.config.preload.dev.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 | import { merge } from 'webpack-merge';
4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
5 | import baseConfig from './webpack.config.base';
6 | import webpackPaths from './webpack.paths';
7 | import checkNodeEnv from '../scripts/check-node-env';
8 |
9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
10 | // at the dev webpack config is not accidentally run in a production environment
11 | if (process.env.NODE_ENV === 'production') {
12 | checkNodeEnv('development');
13 | }
14 |
15 | const configuration: webpack.Configuration = {
16 | devtool: 'inline-source-map',
17 |
18 | mode: 'development',
19 |
20 | target: 'electron-preload',
21 |
22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'),
23 |
24 | output: {
25 | path: webpackPaths.dllPath,
26 | filename: 'preload.js',
27 | library: {
28 | type: 'umd',
29 | },
30 | },
31 |
32 | plugins: [
33 | new BundleAnalyzerPlugin({
34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
35 | }),
36 |
37 | /**
38 | * Create global constants which can be configured at compile time.
39 | *
40 | * Useful for allowing different behaviour between development builds and
41 | * release builds
42 | *
43 | * NODE_ENV should be production so that modules do not perform certain
44 | * development checks
45 | *
46 | * By default, use 'development' as NODE_ENV. This can be overriden with
47 | * 'staging', for example, by changing the ENV variables in the npm scripts
48 | */
49 | new webpack.EnvironmentPlugin({
50 | NODE_ENV: 'development',
51 | }),
52 |
53 | new webpack.LoaderOptionsPlugin({
54 | debug: true,
55 | }),
56 | ],
57 |
58 | /**
59 | * Disables webpack processing of __dirname and __filename.
60 | * If you run the bundle in node.js it falls back to these values of node.js.
61 | * https://github.com/webpack/webpack/issues/2010
62 | */
63 | node: {
64 | __dirname: false,
65 | __filename: false,
66 | },
67 |
68 | watch: true,
69 | };
70 |
71 | export default merge(baseConfig, configuration);
72 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/scripts/check-native-dep.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import chalk from 'chalk';
3 | import { execSync } from 'child_process';
4 | import { dependencies } from '../../package.json';
5 |
6 | if (dependencies) {
7 | const dependenciesKeys = Object.keys(dependencies);
8 | const nativeDeps = fs
9 | .readdirSync('node_modules')
10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
11 | if (nativeDeps.length === 0) {
12 | process.exit(0);
13 | }
14 | try {
15 | // Find the reason for why the dependency is installed. If it is installed
16 | // because of a devDependency then that is okay. Warn when it is installed
17 | // because of a dependency
18 | const { dependencies: dependenciesObject } = JSON.parse(
19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString(),
20 | );
21 | const rootDependencies = Object.keys(dependenciesObject);
22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
23 | dependenciesKeys.includes(rootDependency),
24 | );
25 | if (filteredRootDependencies.length > 0) {
26 | const plural = filteredRootDependencies.length > 1;
27 | console.log(`
28 | ${chalk.whiteBright.bgYellow.bold(
29 | 'Webpack does not work with native dependencies.',
30 | )}
31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${
32 | plural ? 'are native dependencies' : 'is a native dependency'
33 | } and should be installed inside of the "./release/app" folder.
34 | First, uninstall the packages from "./package.json":
35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}
36 | ${chalk.bold(
37 | 'Then, instead of installing the package to the root "./package.json":',
38 | )}
39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')}
40 | ${chalk.bold('Install the package to "./release/app/package.json"')}
41 | ${chalk.whiteBright.bgGreen.bold(
42 | 'cd ./release/app && npm install your-package',
43 | )}
44 | Read more about native dependencies at:
45 | ${chalk.bold(
46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure',
47 | )}
48 | `);
49 | process.exit(1);
50 | }
51 | } catch (e) {
52 | console.log('Native dependencies could not be checked');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.config.main.prod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { merge } from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import baseConfig from './webpack.config.base';
11 | import webpackPaths from './webpack.paths';
12 | import checkNodeEnv from '../scripts/check-node-env';
13 | import deleteSourceMaps from '../scripts/delete-source-maps';
14 |
15 | checkNodeEnv('production');
16 | deleteSourceMaps();
17 |
18 | const configuration: webpack.Configuration = {
19 | devtool: 'source-map',
20 |
21 | mode: 'production',
22 |
23 | target: 'electron-main',
24 |
25 | entry: {
26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'),
27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
28 | },
29 |
30 | output: {
31 | path: webpackPaths.distMainPath,
32 | filename: '[name].js',
33 | library: {
34 | type: 'umd',
35 | },
36 | },
37 |
38 | optimization: {
39 | minimizer: [
40 | new TerserPlugin({
41 | parallel: true,
42 | }),
43 | ],
44 | },
45 |
46 | plugins: [
47 | new BundleAnalyzerPlugin({
48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
49 | analyzerPort: 8888,
50 | }),
51 |
52 | /**
53 | * Create global constants which can be configured at compile time.
54 | *
55 | * Useful for allowing different behaviour between development builds and
56 | * release builds
57 | *
58 | * NODE_ENV should be production so that modules do not perform certain
59 | * development checks
60 | */
61 | new webpack.EnvironmentPlugin({
62 | NODE_ENV: 'production',
63 | DEBUG_PROD: false,
64 | START_MINIMIZED: false,
65 | }),
66 |
67 | new webpack.DefinePlugin({
68 | 'process.type': '"browser"',
69 | }),
70 | ],
71 |
72 | /**
73 | * Disables webpack processing of __dirname and __filename.
74 | * If you run the bundle in node.js it falls back to these values of node.js.
75 | * https://github.com/webpack/webpack/issues/2010
76 | */
77 | node: {
78 | __dirname: false,
79 | __filename: false,
80 | },
81 | };
82 |
83 | export default merge(baseConfig, configuration);
84 |
--------------------------------------------------------------------------------
/g4l-ui/src/main/preload.ts:
--------------------------------------------------------------------------------
1 | // Disable no-unused-vars, broken for spread args
2 | /* eslint no-unused-vars: off */
3 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
4 |
5 | export type Channels =
6 | | 'ipc-example'
7 | | 'save-audio-file'
8 | | 'audio-file-saved'
9 | | 'ondragstart'
10 | | 'toggle-guide'; // Add this line
11 |
12 | const electronHandler = {
13 | ipcRenderer: {
14 | sendMessage(channel: Channels, ...args: unknown[]) {
15 | ipcRenderer.send(channel, ...args);
16 | },
17 | saveAudioFile: (base64Data: string) => {
18 | console.log('Preload sending save-audio-file with data:', base64Data);
19 | ipcRenderer.send('save-audio-file', base64Data);
20 | },
21 | onAudioFileSaved: (callback: (filePath: string) => void) => {
22 | console.log('Preload setting listener for audio-file-saved');
23 | ipcRenderer.on('audio-file-saved', (_event, filePath) =>
24 | callback(filePath),
25 | );
26 | },
27 | startDrag: (fileName: string) => {
28 | ipcRenderer.send('ondragstart', fileName);
29 | },
30 | on(channel: Channels, func: (...args: unknown[]) => void) {
31 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
32 | func(...args);
33 | ipcRenderer.on(channel, subscription);
34 |
35 | return () => {
36 | ipcRenderer.removeListener(channel, subscription);
37 | };
38 | },
39 | once(channel: Channels, func: (...args: unknown[]) => void) {
40 | ipcRenderer.once(channel, (_event, ...args) => func(...args));
41 | },
42 | },
43 | };
44 |
45 | const api = {
46 | send: (channel: Channels, data: any) => {
47 | console.log(`Preload sending ${channel} with data:`, data);
48 | ipcRenderer.send(channel, data);
49 | },
50 | receive: (channel: Channels, func: (data: any) => void) => {
51 | console.log(`Preload receiving on channel: ${channel}`);
52 | ipcRenderer.on(channel, (_, data) => {
53 | console.log(`Data received on channel ${channel}:`, data);
54 | func(data);
55 | });
56 | },
57 | remove: (channel: Channels, func: (...args: any[]) => void) => {
58 | console.log(`Preload removing listener on channel: ${channel}`);
59 | ipcRenderer.removeListener(channel, func);
60 | },
61 | };
62 |
63 | contextBridge.exposeInMainWorld('electron', electronHandler);
64 | contextBridge.exposeInMainWorld('api', api);
65 |
66 | export type ElectronHandler = typeof electronHandler;
67 | export type Api = typeof api;
68 |
--------------------------------------------------------------------------------
/g4l-ui/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '44 16 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 |
--------------------------------------------------------------------------------
/g4l-ui/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
24 |
--------------------------------------------------------------------------------
/g4l-ui/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at electronreactboilerplate@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.config.renderer.prod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Build config for electron renderer process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import HtmlWebpackPlugin from 'html-webpack-plugin';
8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
11 | import { merge } from 'webpack-merge';
12 | import TerserPlugin from 'terser-webpack-plugin';
13 | import baseConfig from './webpack.config.base';
14 | import webpackPaths from './webpack.paths';
15 | import checkNodeEnv from '../scripts/check-node-env';
16 | import deleteSourceMaps from '../scripts/delete-source-maps';
17 |
18 | checkNodeEnv('production');
19 | deleteSourceMaps();
20 |
21 | const configuration: webpack.Configuration = {
22 | devtool: 'source-map',
23 |
24 | mode: 'production',
25 |
26 | target: ['web', 'electron-renderer'],
27 |
28 | entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
29 |
30 | output: {
31 | path: webpackPaths.distRendererPath,
32 | publicPath: './',
33 | filename: 'renderer.js',
34 | library: {
35 | type: 'umd',
36 | },
37 | },
38 |
39 | module: {
40 | rules: [
41 | {
42 | test: /\.s?(a|c)ss$/,
43 | use: [
44 | MiniCssExtractPlugin.loader,
45 | {
46 | loader: 'css-loader',
47 | options: {
48 | modules: true,
49 | sourceMap: true,
50 | importLoaders: 1,
51 | },
52 | },
53 | 'sass-loader',
54 | ],
55 | include: /\.module\.s?(c|a)ss$/,
56 | },
57 | {
58 | test: /\.s?(a|c)ss$/,
59 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
60 | exclude: /\.module\.s?(c|a)ss$/,
61 | },
62 | // Fonts
63 | {
64 | test: /\.(woff|woff2|eot|ttf|otf)$/i,
65 | type: 'asset/resource',
66 | },
67 | // Images
68 | {
69 | test: /\.(png|jpg|jpeg|gif)$/i,
70 | type: 'asset/resource',
71 | },
72 | // SVG
73 | {
74 | test: /\.svg$/,
75 | use: [
76 | {
77 | loader: '@svgr/webpack',
78 | options: {
79 | prettier: false,
80 | svgo: false,
81 | svgoConfig: {
82 | plugins: [{ removeViewBox: false }],
83 | },
84 | titleProp: true,
85 | ref: true,
86 | },
87 | },
88 | 'file-loader',
89 | ],
90 | },
91 | ],
92 | },
93 |
94 | optimization: {
95 | minimize: true,
96 | minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
97 | },
98 |
99 | plugins: [
100 | /**
101 | * Create global constants which can be configured at compile time.
102 | *
103 | * Useful for allowing different behaviour between development builds and
104 | * release builds
105 | *
106 | * NODE_ENV should be production so that modules do not perform certain
107 | * development checks
108 | */
109 | new webpack.EnvironmentPlugin({
110 | NODE_ENV: 'production',
111 | DEBUG_PROD: false,
112 | }),
113 |
114 | new MiniCssExtractPlugin({
115 | filename: 'style.css',
116 | }),
117 |
118 | new BundleAnalyzerPlugin({
119 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
120 | analyzerPort: 8889,
121 | }),
122 |
123 | new HtmlWebpackPlugin({
124 | filename: 'index.html',
125 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
126 | minify: {
127 | collapseWhitespace: true,
128 | removeAttributeQuotes: true,
129 | removeComments: true,
130 | },
131 | isBrowser: false,
132 | isDevelopment: false,
133 | }),
134 |
135 | new webpack.DefinePlugin({
136 | 'process.type': '"renderer"',
137 | }),
138 | ],
139 | };
140 |
141 | export default merge(baseConfig, configuration);
142 |
--------------------------------------------------------------------------------
/g4l-ui/src/renderer/NotificationOverlay.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { X } from 'lucide-react';
3 |
4 | interface NotificationOverlayProps {
5 | maxConnection: boolean;
6 | backendConnection: boolean;
7 | errorMessage?: {
8 | type: string;
9 | message: string;
10 | } | null;
11 | }
12 |
13 | const NotificationOverlay: React.FC = ({
14 | maxConnection,
15 | backendConnection,
16 | errorMessage
17 | }) => {
18 | const [isVisible, setIsVisible] = useState(false);
19 | const [message, setMessage] = useState('');
20 | const [isError, setIsError] = useState(false);
21 | const [showBackendWarning, setShowBackendWarning] = useState(false);
22 | const [isPersistent, setIsPersistent] = useState(false);
23 |
24 | useEffect(() => {
25 | // Handle initial connection attempts
26 | if (maxConnection) {
27 | // Give backend some time to connect before showing warning
28 | const backendTimeout = setTimeout(() => {
29 | if (!backendConnection) {
30 | setMessage("still not connected to backend. is your wifi on homie? try pressing '3' in m4l again.");
31 | setIsError(true);
32 | setIsVisible(true);
33 | setShowBackendWarning(true);
34 | setIsPersistent(false);
35 | }
36 | }, 5000);
37 |
38 | // Show initial max connection message
39 | if (!showBackendWarning) {
40 | setMessage(backendConnection
41 | ? 'connected to max for live & backend 🎵'
42 | : 'connected to max for live 🎵'
43 | );
44 | setIsError(false);
45 | setIsVisible(true);
46 | setIsPersistent(false);
47 | }
48 |
49 | return () => clearTimeout(backendTimeout);
50 | }
51 | }, [maxConnection, backendConnection]);
52 |
53 | // Handle error messages
54 | useEffect(() => {
55 | if (errorMessage) {
56 | // Check if this is an error that should show Discord link
57 | const shouldShowDiscord = (
58 | (errorMessage.type === 'transform' && errorMessage.message.includes('discord.gg')) ||
59 | (errorMessage.type === 'ffmpeg' && errorMessage.message.includes('discord'))
60 | );
61 |
62 | setMessage(errorMessage.message);
63 | setIsError(true);
64 | setIsVisible(true);
65 | setIsPersistent(shouldShowDiscord);
66 | }
67 | }, [errorMessage]);
68 |
69 | // Auto-hide for non-persistent notifications
70 | useEffect(() => {
71 | if (isVisible && !isPersistent) {
72 | const hideTimer = setTimeout(() => {
73 | setIsVisible(false);
74 | if (isError) {
75 | setShowBackendWarning(false);
76 | }
77 | }, 3000);
78 | return () => clearTimeout(hideTimer);
79 | }
80 | }, [isVisible, isError, isPersistent]);
81 |
82 | if (!isVisible) return null;
83 |
84 | // Function to parse message and create elements with clickable link
85 | const renderMessage = (text: string) => {
86 | if (text.includes('discord.gg')) {
87 | const parts = text.split('https://discord.gg/');
88 | const discordLink = `https://discord.gg/${parts[1]}`;
89 |
90 | return (
91 | <>
92 | {parts[0]}
93 |
99 | join discord
100 |
101 | >
102 | );
103 | }
104 | return text;
105 | };
106 |
107 | return (
108 |
131 | {isPersistent && (
132 |
147 | )}
148 |
149 | {renderMessage(message)}
150 |
151 |
152 | );
153 | };
154 |
155 | export default NotificationOverlay;
--------------------------------------------------------------------------------
/g4l-ui/src/renderer/GuideNumber.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 |
3 | interface GuideNumberProps {
4 | number: number;
5 | blurb: string;
6 | isTransformButton?: boolean; // Add this prop
7 | isBottomButton?: boolean;
8 | }
9 |
10 | const GuideNumber: React.FC = ({ number, blurb, isTransformButton, isBottomButton }) => {
11 | const [showTooltip, setShowTooltip] = useState(false);
12 | const tooltipRef = useRef(null);
13 |
14 | useEffect(() => {
15 | if (showTooltip && tooltipRef.current) {
16 | const tooltip = tooltipRef.current;
17 | tooltip.style.visibility = 'hidden';
18 | tooltip.style.display = 'block';
19 |
20 | setTimeout(() => {
21 | const tooltipRect = tooltip.getBoundingClientRect();
22 | const guideNumberRect = tooltip.parentElement?.getBoundingClientRect();
23 |
24 | // Reset styles
25 | tooltip.style.left = 'auto';
26 | tooltip.style.right = 'auto';
27 | tooltip.style.transform = 'none';
28 | tooltip.style.top = '';
29 | tooltip.style.maxHeight = '';
30 | tooltip.style.overflowY = '';
31 |
32 | // Special handling for transform buttons
33 | if (isTransformButton) {
34 | // Position the guide number to the left of the button
35 | tooltip.style.right = 'calc(100% + 10px)'; // Position left with spacing
36 | tooltip.style.left = 'auto';
37 | tooltip.style.transform = 'none'; // Remove any previous transforms
38 |
39 | // Vertical centering
40 | if (guideNumberRect) {
41 | const buttonHeight = guideNumberRect.height;
42 | const tooltipHeight = tooltipRect.height;
43 | tooltip.style.top = `${(buttonHeight - tooltipHeight) / 2}px`;
44 | }
45 |
46 | // Ensure tooltip doesn't go off screen to the left
47 | const leftEdge = tooltipRect.left;
48 | if (leftEdge < 0) {
49 | tooltip.style.right = 'auto';
50 | tooltip.style.left = '-10px'; // Small negative offset to avoid button overlap
51 | }
52 |
53 | } else if (isBottomButton) {
54 | // Force tooltip to appear above the guide number
55 | tooltip.style.bottom = '100%';
56 | tooltip.style.top = 'auto';
57 | tooltip.style.left = '50%';
58 | tooltip.style.transform = 'translateX(-50%)';
59 |
60 | // Check if tooltip would go off left/right edges
61 | const updatedTooltipRect = tooltip.getBoundingClientRect();
62 | if (updatedTooltipRect.right > window.innerWidth) {
63 | tooltip.style.left = 'auto';
64 | tooltip.style.right = '0';
65 | tooltip.style.transform = 'none';
66 | }
67 | if (updatedTooltipRect.left < 0) {
68 | tooltip.style.left = '0';
69 | tooltip.style.right = 'auto';
70 | tooltip.style.transform = 'none';
71 | }
72 |
73 | } else {
74 | // Original positioning logic for other guide numbers
75 | tooltip.style.left = '50%';
76 | tooltip.style.transform = 'translateX(-50%)';
77 |
78 | const spaceAbove = guideNumberRect ? guideNumberRect.top : 0;
79 | const spaceBelow = guideNumberRect
80 | ? window.innerHeight - guideNumberRect.bottom
81 | : 0;
82 |
83 | if (spaceBelow >= tooltipRect.height) {
84 | tooltip.style.top = '100%';
85 | } else if (spaceAbove >= tooltipRect.height) {
86 | tooltip.style.top = `-${tooltipRect.height}px`;
87 | } else {
88 | if (spaceBelow >= spaceAbove) {
89 | tooltip.style.top = '100%';
90 | tooltip.style.maxHeight = `${spaceBelow}px`;
91 | tooltip.style.overflowY = 'auto';
92 | } else {
93 | tooltip.style.top = `-${spaceAbove}px`;
94 | tooltip.style.maxHeight = `${spaceAbove}px`;
95 | tooltip.style.overflowY = 'auto';
96 | }
97 | }
98 |
99 | // Edge detection for non-transform buttons
100 | const updatedTooltipRect = tooltip.getBoundingClientRect();
101 | if (updatedTooltipRect.right > window.innerWidth) {
102 | tooltip.style.left = 'auto';
103 | tooltip.style.right = '0';
104 | tooltip.style.transform = 'none';
105 | }
106 | if (updatedTooltipRect.left < 0) {
107 | tooltip.style.left = '0';
108 | tooltip.style.right = 'auto';
109 | tooltip.style.transform = 'none';
110 | }
111 | }
112 |
113 | tooltip.style.visibility = 'visible';
114 | }, 10);
115 | }
116 | }, [showTooltip, isTransformButton]);
117 |
118 | return (
119 | setShowTooltip(true)}
122 | onMouseLeave={() => setShowTooltip(false)}
123 | >
124 | {number}
125 | {showTooltip && (
126 |
131 | {blurb}
132 |
133 | )}
134 |
135 | );
136 | };
137 |
138 | export default GuideNumber;
--------------------------------------------------------------------------------
/g4l-ui/.erb/configs/webpack.config.renderer.dev.ts:
--------------------------------------------------------------------------------
1 | import 'webpack-dev-server';
2 | import path from 'path';
3 | import fs from 'fs';
4 | import webpack from 'webpack';
5 | import HtmlWebpackPlugin from 'html-webpack-plugin';
6 | import chalk from 'chalk';
7 | import { merge } from 'webpack-merge';
8 | import { execSync, spawn } from 'child_process';
9 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
10 | import baseConfig from './webpack.config.base';
11 | import webpackPaths from './webpack.paths';
12 | import checkNodeEnv from '../scripts/check-node-env';
13 |
14 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
15 | // at the dev webpack config is not accidentally run in a production environment
16 | if (process.env.NODE_ENV === 'production') {
17 | checkNodeEnv('development');
18 | }
19 |
20 | const port = process.env.PORT || 1212;
21 | const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json');
22 | const skipDLLs =
23 | module.parent?.filename.includes('webpack.config.renderer.dev.dll') ||
24 | module.parent?.filename.includes('webpack.config.eslint');
25 |
26 | /**
27 | * Warn if the DLL is not built
28 | */
29 | if (
30 | !skipDLLs &&
31 | !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest))
32 | ) {
33 | console.log(
34 | chalk.black.bgYellow.bold(
35 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"',
36 | ),
37 | );
38 | execSync('npm run postinstall');
39 | }
40 |
41 | const configuration: webpack.Configuration = {
42 | devtool: 'inline-source-map',
43 |
44 | mode: 'development',
45 |
46 | target: ['web', 'electron-renderer'],
47 |
48 | entry: [
49 | `webpack-dev-server/client?http://localhost:${port}/dist`,
50 | 'webpack/hot/only-dev-server',
51 | path.join(webpackPaths.srcRendererPath, 'index.tsx'),
52 | ],
53 |
54 | output: {
55 | path: webpackPaths.distRendererPath,
56 | publicPath: '/',
57 | filename: 'renderer.dev.js',
58 | library: {
59 | type: 'umd',
60 | },
61 | },
62 |
63 | module: {
64 | rules: [
65 | {
66 | test: /\.s?(c|a)ss$/,
67 | use: [
68 | 'style-loader',
69 | {
70 | loader: 'css-loader',
71 | options: {
72 | modules: true,
73 | sourceMap: true,
74 | importLoaders: 1,
75 | },
76 | },
77 | 'sass-loader',
78 | ],
79 | include: /\.module\.s?(c|a)ss$/,
80 | },
81 | {
82 | test: /\.s?css$/,
83 | use: ['style-loader', 'css-loader', 'sass-loader'],
84 | exclude: /\.module\.s?(c|a)ss$/,
85 | },
86 | // Fonts
87 | {
88 | test: /\.(woff|woff2|eot|ttf|otf)$/i,
89 | type: 'asset/resource',
90 | },
91 | // Images
92 | {
93 | test: /\.(png|jpg|jpeg|gif)$/i,
94 | type: 'asset/resource',
95 | },
96 | // SVG
97 | {
98 | test: /\.svg$/,
99 | use: [
100 | {
101 | loader: '@svgr/webpack',
102 | options: {
103 | prettier: false,
104 | svgo: false,
105 | svgoConfig: {
106 | plugins: [{ removeViewBox: false }],
107 | },
108 | titleProp: true,
109 | ref: true,
110 | },
111 | },
112 | 'file-loader',
113 | ],
114 | },
115 | ],
116 | },
117 | plugins: [
118 | ...(skipDLLs
119 | ? []
120 | : [
121 | new webpack.DllReferencePlugin({
122 | context: webpackPaths.dllPath,
123 | manifest: require(manifest),
124 | sourceType: 'var',
125 | }),
126 | ]),
127 |
128 | new webpack.NoEmitOnErrorsPlugin(),
129 |
130 | /**
131 | * Create global constants which can be configured at compile time.
132 | *
133 | * Useful for allowing different behaviour between development builds and
134 | * release builds
135 | *
136 | * NODE_ENV should be production so that modules do not perform certain
137 | * development checks
138 | *
139 | * By default, use 'development' as NODE_ENV. This can be overriden with
140 | * 'staging', for example, by changing the ENV variables in the npm scripts
141 | */
142 | new webpack.EnvironmentPlugin({
143 | NODE_ENV: 'development',
144 | }),
145 |
146 | new webpack.LoaderOptionsPlugin({
147 | debug: true,
148 | }),
149 |
150 | new ReactRefreshWebpackPlugin(),
151 |
152 | new HtmlWebpackPlugin({
153 | filename: path.join('index.html'),
154 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
155 | minify: {
156 | collapseWhitespace: true,
157 | removeAttributeQuotes: true,
158 | removeComments: true,
159 | },
160 | isBrowser: false,
161 | env: process.env.NODE_ENV,
162 | isDevelopment: process.env.NODE_ENV !== 'production',
163 | nodeModules: webpackPaths.appNodeModulesPath,
164 | }),
165 | ],
166 |
167 | node: {
168 | __dirname: false,
169 | __filename: false,
170 | },
171 |
172 | devServer: {
173 | port,
174 | compress: true,
175 | hot: true,
176 | headers: { 'Access-Control-Allow-Origin': '*' },
177 | static: {
178 | publicPath: '/',
179 | },
180 | historyApiFallback: {
181 | verbose: true,
182 | },
183 | setupMiddlewares(middlewares) {
184 | console.log('Starting preload.js builder...');
185 | const preloadProcess = spawn('npm', ['run', 'start:preload'], {
186 | shell: true,
187 | stdio: 'inherit',
188 | })
189 | .on('close', (code: number) => process.exit(code!))
190 | .on('error', (spawnError) => console.error(spawnError));
191 |
192 | console.log('Starting Main Process...');
193 | let args = ['run', 'start:main'];
194 | if (process.env.MAIN_ARGS) {
195 | args = args.concat(
196 | ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat(),
197 | );
198 | }
199 | spawn('npm', args, {
200 | shell: true,
201 | stdio: 'inherit',
202 | })
203 | .on('close', (code: number) => {
204 | preloadProcess.kill();
205 | process.exit(code!);
206 | })
207 | .on('error', (spawnError) => console.error(spawnError));
208 | return middlewares;
209 | },
210 | },
211 | };
212 |
213 | export default merge(baseConfig, configuration);
214 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gary4live
2 |
3 | this is gary4live. gary iterates on input audio using max for live and musicgen.
4 |
5 | there is an installer for pc as well as for mac, but if you would rather build it yourself, it's really easy.
6 |
7 | ## update july_2_2025
8 | ### gary's now got two lil buddies lol...jerry and terry
9 |
10 | 
11 |
12 | jerry is just our nickname for the stable-audio-open-small model. it generates 12 seconds of audio in under a second when using a gpu. it makes great input audio for gary's continuations.
13 |
14 | https://huggingface.co/stabilityai/stable-audio-open-small
15 |
16 | meta's new melodyflow model (terry) can be used inside this device now to transform input audio. it generates audio the same length as the input. you can have terry transform your recorded buffer or the outputs gary gives back (up to ~40 secs). terry's got 'presets' you can choose from.
17 |
18 | https://huggingface.co/spaces/facebook/melodyflow (you can play with terry here too)
19 |
20 | ## installation
21 |
22 | this repo was designed for the root directory to be placed in `C:\g4l` for maximum simplicity. dynamic filepaths are a pain in the ass in the max for live gui. sry.
23 |
24 | these paths can be changed with some effort (more instructions at the bottom)
25 |
26 | ### prerequisites
27 |
28 | **node.js** - install first:
29 | https://nodejs.org/en/download/
30 |
31 | **ffmpeg** - required for audio cropping:
32 |
33 | 1. download ffmpeg for windows: https://ffmpeg.org/download.html
34 | 2. extract the downloaded folder
35 | 3. **rename the extracted folder to `ffmpeg`**
36 | 4. **move the entire `ffmpeg` folder to `C:\g4l\ffmpeg`**
37 | 5. the final structure should be `C:\g4l\ffmpeg\bin\ffmpeg.exe`
38 |
39 | ### setup
40 |
41 | clone this repository:
42 | ```bash
43 | git clone https://github.com/betweentwomidnights/gary4live
44 | ```
45 |
46 | rename the directory:
47 | ```bash
48 | mv gary4live g4l
49 | ```
50 |
51 | **install backend dependencies** (required for max for live communication):
52 | ```bash
53 | cd C:\g4l
54 | npm install
55 | npm audit fix
56 | ```
57 |
58 | navigate to the frontend directory:
59 | ```bash
60 | cd C:\g4l\g4l-ui
61 | ```
62 |
63 | **install frontend dependencies:**
64 | ```bash
65 | npm install
66 | npm install @types/react@latest @types/react-dom@latest
67 | npm install sass-loader@latest
68 | ```
69 |
70 | ## security note
71 |
72 | when running `npm audit`, you may see 2 moderate vulnerabilities:
73 |
74 | - **electron <28.3.2**: affects image processing (we don't process user images)
75 | - **webpack-dev-server**: affects development mode only (not production builds)
76 |
77 | **we intentionally don't fix these because:**
78 | 1. `npm audit fix --force` breaks the electron app (requires major refactoring)
79 | 2. our app only communicates locally with ableton live (no internet access)
80 | 3. the vulnerabilities don't apply to our specific use case
81 |
82 | for a local music production tool, these pose minimal security risk.
83 |
84 | ## running the app
85 |
86 | to start the application in development mode:
87 | ```bash
88 | npm run start
89 | ```
90 |
91 | to create the executable that the '2(launch electron)' button triggers in gary4live:
92 | ```bash
93 | npm run package
94 | ```
95 |
96 | ## using with ableton
97 |
98 | in the ableton browser, add the g4l folder.
99 |
100 | `GARY.amxd` is the device.
101 |
102 | ## backend configuration
103 |
104 | by default, gary4live connects to our free hosted backend at `g4l.thecollabagepatch.com`. we run this for free and collect zero user data cuz that would be too much work.
105 |
106 | ### using localhost backend
107 |
108 | if you want to run the backend locally (requires **6gb+ gpu ram**, preferably 12gb+), you can build it from: https://github.com/betweentwomidnights/gary-backend-combined
109 |
110 | **to switch to localhost:**
111 |
112 | 1. open `commentedout.js` in your `C:\g4l` directory
113 | 2. find this configuration section:
114 |
115 | ```javascript
116 | // =============================================================================
117 | // CONFIGURATION - Change these URLs when building from source with localhost
118 | // =============================================================================
119 |
120 | // For localhost development with our github repo https://github.com/betweentwomidnights/gary-backend-combined
121 | // Uncomment these lines and comment out the production URLs below:
122 | // const BACKEND_URL = 'http://localhost:8000';
123 | // const STABLE_AUDIO_HOST = 'localhost';
124 | // const STABLE_AUDIO_PORT = 8005;
125 | // const STABLE_AUDIO_PATH = '/generate'; // Direct to service
126 | // const USE_HTTPS = false;
127 |
128 | // Production URLs (default):
129 | const BACKEND_URL = 'https://g4l.thecollabagepatch.com';
130 | const STABLE_AUDIO_HOST = 'g4l.thecollabagepatch.com';
131 | const STABLE_AUDIO_PORT = 443;
132 | const STABLE_AUDIO_PATH = '/audio/generate'; // Through Caddy proxy
133 | const USE_HTTPS = true;
134 | ```
135 |
136 | 3. **comment out the production urls and uncomment the localhost urls**
137 |
138 | ## troubleshooting
139 |
140 | **ffmpeg not found error:**
141 | - make sure ffmpeg is extracted to `C:\g4l\ffmpeg\bin\ffmpeg.exe`
142 | - alternatively, install ffmpeg system-wide via chocolatey: `choco install ffmpeg`
143 |
144 | **generations stuck at 80%:**
145 | - check file permissions on `myBuffer.wav` and `myOutput.wav`
146 | - avoid manually editing these files
147 |
148 | **electron app won't start:**
149 | - make sure you ran the updated npm install commands
150 | - don't run `npm audit fix --force` as it breaks the app
151 |
152 | **mac users:**
153 | - there's a separate repository for the mac front-end: https://github.com/betweentwomidnights/gary-mac
154 |
155 | ## advanced usage
156 |
157 | if you want to change filepaths, the two buttons in the m4l gui need to be changed, as well as all the filepaths for `myBuffer.wav` and `myOutput.wav` in 'electron-communication.js' and 'commentedout.js'
158 |
159 | warning: you are going to give yourself headaches. max rly likes to gaslight.
160 |
161 | the final remaining edge cases revolve around these two files: `myBuffer.wav` and `myOutput.wav`. if generations get stuck at 80%, or you do not see the 'write' button actually changing `myBuffer.wav`, you have hit this issue with permissions.
162 |
163 | **do NOT:**
164 | - drag them into the timeline (the 'drag me' option in electron is fine. that's a copy of myOutput.wav)
165 | - manually change `myBuffer.wav` to a 6 minute audio file. it should only be changed by our write function and should be a maximum of 30 seconds
166 |
167 | ## support
168 |
169 | if you rly wanna learn how to use this thing, head to discord https://discord.gg/VECkyXEnAd and yell at me @kev, or go to https://youtube.com/@thepatch_dev
170 |
171 | ## related repositories
172 |
173 | - backend: https://github.com/betweentwomidnights/gary-backend-combined
174 | - mac frontend: https://github.com/betweentwomidnights/gary-mac
--------------------------------------------------------------------------------
/g4l-ui/src/main/main.ts:
--------------------------------------------------------------------------------
1 | /* eslint global-require: off, no-console: off, promise/always-return: off */
2 |
3 | /**
4 | * This module executes inside of electron's main process. You can start
5 | * electron renderer process from here and communicate with the other processes
6 | * through IPC.
7 | *
8 | * When running `npm run build` or `npm run build:main`, this file is compiled to
9 | * `./src/main.js` using webpack. This gives us some performance wins.
10 | */
11 | import path from 'path';
12 | import * as fs from 'fs';
13 | import { app, BrowserWindow, shell, ipcMain } from 'electron';
14 | import { autoUpdater } from 'electron-updater';
15 | import log from 'electron-log';
16 | import WebSocket, { WebSocketServer } from 'ws';
17 | import MenuBuilder from './menu';
18 | import { resolveHtmlPath } from './util';
19 |
20 | class AppUpdater {
21 | constructor() {
22 | log.transports.file.level = 'info';
23 | autoUpdater.logger = log;
24 | autoUpdater.checkForUpdatesAndNotify();
25 | }
26 | }
27 |
28 | let mainWindow: BrowserWindow | null = null;
29 | const wss = new WebSocketServer({ port: 8080 });
30 | let connectedClient: WebSocket | null = null;
31 |
32 | wss.on('connection', function connection(ws) {
33 | console.log('WebSocket server: Client connected');
34 | connectedClient = ws;
35 |
36 | // Convert string to Uint8Array before sending
37 | const connectionMessage = Buffer.from(JSON.stringify({
38 | action: 'connection_status',
39 | data: true
40 | }));
41 |
42 | if (mainWindow) {
43 | mainWindow.webContents.send('fromNodeScript', connectionMessage);
44 | }
45 |
46 | ws.on('message', function incoming(message) {
47 | console.log('received: %s', message);
48 | if (mainWindow) {
49 | mainWindow.webContents.send('fromNodeScript', message);
50 | }
51 | });
52 |
53 | ws.on('close', () => {
54 | console.log('WebSocket server: Client disconnected');
55 | connectedClient = null;
56 |
57 | // Convert string to Uint8Array for disconnect message
58 | const disconnectionMessage = Buffer.from(JSON.stringify({
59 | action: 'connection_status',
60 | data: false
61 | }));
62 |
63 | if (mainWindow) {
64 | mainWindow.webContents.send('fromNodeScript', disconnectionMessage);
65 | }
66 | });
67 | });
68 |
69 | function sendMessageToNodeScript(data: { action: string; data?: any }) {
70 | if (connectedClient && connectedClient.readyState === WebSocket.OPEN) {
71 | connectedClient.send(JSON.stringify(data)); // Send data as stringified JSON
72 | }
73 | }
74 |
75 | ipcMain.on('send-to-node-script', (_, payload) => {
76 | sendMessageToNodeScript(payload); // Ensure that `payload` is treated as an object within `sendMessageToNodeScript`
77 | });
78 |
79 | ipcMain.on('ipc-example', async (event, arg) => {
80 | const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
81 | console.log(msgTemplate(arg));
82 | event.reply('ipc-example', msgTemplate('pong'));
83 | });
84 |
85 | // Function to ensure directory exists
86 | const ensureDirectoryExists = (filePath: string) => {
87 | const dir = path.dirname(filePath);
88 | if (!fs.existsSync(dir)) {
89 | fs.mkdirSync(dir, { recursive: true });
90 | }
91 | };
92 |
93 | // Function to convert base64 to a file
94 | const saveBase64AudioToFile = (base64Data: string, filePath: string) => {
95 | const buffer = Buffer.from(base64Data, 'base64');
96 | ensureDirectoryExists(filePath); // Ensure directory exists
97 | try {
98 | fs.writeFileSync(filePath, buffer);
99 | console.log(`File saved to ${filePath}`);
100 | } catch (err) {
101 | console.error('Error saving file:', err);
102 | }
103 | };
104 |
105 | // Generate a unique filename using a timestamp
106 | const generateUniqueFilename = (basePath: string, extension: string) => {
107 | const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); // Format timestamp
108 | return path.join(basePath, `audio_${timestamp}.${extension}`);
109 | };
110 |
111 | // Listen for the 'save-audio-file' IPC message
112 | ipcMain.on('save-audio-file', (event, base64Data) => {
113 | const projectPath = app.isPackaged ? process.resourcesPath : path.join(__dirname, '../../');
114 | const filePath = generateUniqueFilename(path.join(projectPath, 'saved-audio'), 'wav'); // Generate unique filename
115 | console.log(`Saving file to ${filePath}`);
116 | saveBase64AudioToFile(base64Data, filePath);
117 | event.sender.send('audio-file-saved', filePath);
118 | });
119 |
120 | if (process.env.NODE_ENV === 'production') {
121 | const sourceMapSupport = require('source-map-support');
122 | sourceMapSupport.install();
123 | }
124 |
125 | const isDebug =
126 | process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
127 |
128 | if (isDebug) {
129 | require('electron-debug')();
130 | }
131 |
132 | const installExtensions = async () => {
133 | const installer = require('electron-devtools-installer');
134 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
135 | const extensions = ['REACT_DEVELOPER_TOOLS'];
136 |
137 | return installer
138 | .default(
139 | extensions.map((name) => installer[name]),
140 | forceDownload,
141 | )
142 | .catch(console.log);
143 | };
144 |
145 | const createWindow = async () => {
146 | if (isDebug) {
147 | await installExtensions();
148 | }
149 |
150 | const RESOURCES_PATH = app.isPackaged
151 | ? path.join(process.resourcesPath, 'assets')
152 | : path.join(__dirname, '../../assets');
153 |
154 | const getAssetPath = (...paths: string[]): string => {
155 | return path.join(RESOURCES_PATH, ...paths);
156 | };
157 |
158 | mainWindow = new BrowserWindow({
159 | show: false,
160 | width: 670,
161 | height: 480,
162 | icon: getAssetPath('icon.png'),
163 | webPreferences: {
164 | nodeIntegration: true, // Enable Node.js integration
165 | preload: app.isPackaged
166 | ? path.join(__dirname, 'preload.js')
167 | : path.join(__dirname, '../../.erb/dll/preload.js'),
168 | },
169 | alwaysOnTop: true,
170 | });
171 |
172 | mainWindow.loadURL(resolveHtmlPath('index.html'));
173 |
174 | mainWindow.on('ready-to-show', () => {
175 | if (!mainWindow) {
176 | throw new Error('"mainWindow" is not defined');
177 | }
178 | if (process.env.START_MINIMIZED) {
179 | mainWindow.minimize();
180 | } else {
181 | mainWindow.show();
182 | }
183 | });
184 |
185 | mainWindow.on('closed', () => {
186 | mainWindow = null;
187 | });
188 |
189 | ipcMain.on('ondragstart', (event, filePath) => {
190 | console.log(`Dragging file from ${filePath}`);
191 | const iconPath = getAssetPath('icon.ico');
192 | console.log(`Using icon at ${iconPath}`);
193 |
194 | if (!fs.existsSync(filePath)) {
195 | console.error(`File does not exist at ${filePath}`);
196 | return;
197 | }
198 |
199 | if (!fs.existsSync(iconPath)) {
200 | console.error(`Icon does not exist at ${iconPath}`);
201 | return;
202 | }
203 |
204 | try {
205 | event.sender.startDrag({
206 | file: filePath,
207 | icon: iconPath,
208 | });
209 | console.log('Drag operation started successfully.');
210 | } catch (error) {
211 | console.error('Error starting drag operation:', error);
212 | }
213 | });
214 |
215 | const menuBuilder = new MenuBuilder(mainWindow);
216 | menuBuilder.buildMenu();
217 |
218 | mainWindow.webContents.setWindowOpenHandler((edata) => {
219 | shell.openExternal(edata.url);
220 | return { action: 'deny' };
221 | });
222 | // eslint-disable-next-line no-new
223 | new AppUpdater();
224 | };
225 |
226 | app.on('window-all-closed', () => {
227 | if (process.platform !== 'darwin') {
228 | app.quit();
229 | }
230 | });
231 |
232 | app
233 | .whenReady()
234 | .then(() => {
235 | createWindow();
236 | app.on('activate', () => {
237 | if (mainWindow === null) createWindow();
238 | });
239 | })
240 | .catch(console.log);
241 |
--------------------------------------------------------------------------------
/g4l-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "gary4live - musicgen continuations inside ableton",
3 | "keywords": [
4 | "electron",
5 | "max-for-live",
6 | "music production",
7 | "ableton",
8 | "interactive"
9 | ],
10 | "homepage": "https://thecollabagepatch.com",
11 | "bugs": {
12 | "url": "https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/electron-react-boilerplate/electron-react-boilerplate.git"
17 | },
18 | "license": "MIT",
19 | "author": {
20 | "name": "Kevin Griffing",
21 | "email": "kev@thecollabagepatch.com",
22 | "url": "https://thecollabagepatch.com"
23 | },
24 | "contributors": [
25 | {
26 | "name": "Amila Welihinda",
27 | "email": "amilajack@gmail.com",
28 | "url": "https://github.com/amilajack"
29 | }
30 | ],
31 | "main": "./src/main/main.ts",
32 | "scripts": {
33 | "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
34 | "build:dll": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts",
35 | "build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
36 | "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
37 | "postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && npm run build:dll",
38 | "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
39 | "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never && npm run build:dll",
40 | "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
41 | "start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
42 | "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .",
43 | "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
44 | "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
45 | "test": "jest"
46 | },
47 | "browserslist": [],
48 | "prettier": {
49 | "singleQuote": true,
50 | "overrides": [
51 | {
52 | "files": [
53 | ".prettierrc",
54 | ".eslintrc"
55 | ],
56 | "options": {
57 | "parser": "json"
58 | }
59 | }
60 | ]
61 | },
62 | "jest": {
63 | "moduleDirectories": [
64 | "node_modules",
65 | "release/app/node_modules",
66 | "src"
67 | ],
68 | "moduleFileExtensions": [
69 | "js",
70 | "jsx",
71 | "ts",
72 | "tsx",
73 | "json"
74 | ],
75 | "moduleNameMapper": {
76 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.erb/mocks/fileMock.js",
77 | "\\.(css|less|sass|scss)$": "identity-obj-proxy"
78 | },
79 | "setupFiles": [
80 | "./.erb/scripts/check-build-exists.ts"
81 | ],
82 | "testEnvironment": "jsdom",
83 | "testEnvironmentOptions": {
84 | "url": "http://localhost/"
85 | },
86 | "testPathIgnorePatterns": [
87 | "release/app/dist",
88 | ".erb/dll"
89 | ],
90 | "transform": {
91 | "\\.(ts|tsx|js|jsx)$": "ts-jest"
92 | }
93 | },
94 | "dependencies": {
95 | "@fortawesome/fontawesome-svg-core": "^6.5.2",
96 | "@fortawesome/free-solid-svg-icons": "^6.5.2",
97 | "@fortawesome/react-fontawesome": "^0.2.1",
98 | "electron-debug": "^3.2.0",
99 | "electron-log": "^4.4.8",
100 | "electron-updater": "^6.1.4",
101 | "lucide-react": "^0.471.0",
102 | "react": "^18.2.0",
103 | "react-dom": "^18.2.0",
104 | "react-router-dom": "^6.16.0",
105 | "wavesurfer.js": "^7.7.14",
106 | "ws": "^8.17.0"
107 | },
108 | "devDependencies": {
109 | "@electron/notarize": "^2.1.0",
110 | "@electron/rebuild": "^3.3.0",
111 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
112 | "@svgr/webpack": "^8.1.0",
113 | "@teamsupercell/typings-for-css-modules-loader": "^2.5.2",
114 | "@testing-library/jest-dom": "^6.1.3",
115 | "@testing-library/react": "^14.0.0",
116 | "@types/electron": "^1.6.10",
117 | "@types/fluent-ffmpeg": "^2.1.24",
118 | "@types/jest": "^29.5.5",
119 | "@types/node": "20.6.2",
120 | "@types/react": "^19.1.8",
121 | "@types/react-dom": "^19.1.6",
122 | "@types/react-test-renderer": "^18.0.1",
123 | "@types/terser-webpack-plugin": "^5.0.4",
124 | "@types/webpack-bundle-analyzer": "^4.6.0",
125 | "@typescript-eslint/eslint-plugin": "^6.7.0",
126 | "@typescript-eslint/parser": "^6.7.0",
127 | "browserslist-config-erb": "^0.0.3",
128 | "chalk": "^4.1.2",
129 | "concurrently": "^8.2.1",
130 | "core-js": "^3.32.2",
131 | "cross-env": "^7.0.3",
132 | "css-loader": "^6.8.1",
133 | "css-minimizer-webpack-plugin": "^5.0.1",
134 | "detect-port": "^1.5.1",
135 | "electron": "^26.2.1",
136 | "electron-builder": "^24.13.3",
137 | "electron-devtools-installer": "^3.2.0",
138 | "electronmon": "^2.0.2",
139 | "eslint": "^8.49.0",
140 | "eslint-config-airbnb-base": "^15.0.0",
141 | "eslint-config-erb": "^4.1.0-0",
142 | "eslint-import-resolver-typescript": "^3.6.0",
143 | "eslint-import-resolver-webpack": "^0.13.7",
144 | "eslint-plugin-compat": "^4.2.0",
145 | "eslint-plugin-import": "^2.28.1",
146 | "eslint-plugin-jest": "^27.4.0",
147 | "eslint-plugin-jsx-a11y": "^6.7.1",
148 | "eslint-plugin-promise": "^6.1.1",
149 | "eslint-plugin-react": "^7.33.2",
150 | "eslint-plugin-react-hooks": "^4.6.0",
151 | "file-loader": "^6.2.0",
152 | "html-webpack-plugin": "^5.5.3",
153 | "identity-obj-proxy": "^3.0.0",
154 | "jest": "^29.7.0",
155 | "jest-environment-jsdom": "^29.7.0",
156 | "mini-css-extract-plugin": "^2.7.6",
157 | "prettier": "^3.0.3",
158 | "react-refresh": "^0.14.0",
159 | "react-test-renderer": "^18.2.0",
160 | "rimraf": "^5.0.1",
161 | "sass": "^1.67.0",
162 | "sass-loader": "^16.0.5",
163 | "style-loader": "^3.3.3",
164 | "terser-webpack-plugin": "^5.3.9",
165 | "ts-jest": "^29.1.1",
166 | "ts-loader": "^9.4.4",
167 | "ts-node": "^10.9.1",
168 | "tsconfig-paths-webpack-plugin": "^4.1.0",
169 | "typescript": "^5.2.2",
170 | "url-loader": "^4.1.1",
171 | "webpack": "^5.88.2",
172 | "webpack-bundle-analyzer": "^4.9.1",
173 | "webpack-cli": "^5.1.4",
174 | "webpack-dev-server": "^4.15.1",
175 | "webpack-merge": "^5.9.0"
176 | },
177 | "build": {
178 | "productName": "gary4live",
179 | "appId": "com.thecollabagepatch.gary4live",
180 | "asar": true,
181 | "asarUnpack": "**\\*.{node,dll}",
182 | "files": [
183 | "dist",
184 | "node_modules",
185 | "package.json"
186 | ],
187 | "afterSign": ".erb/scripts/notarize.js",
188 | "mac": {
189 | "target": {
190 | "target": "default",
191 | "arch": [
192 | "arm64",
193 | "x64"
194 | ]
195 | },
196 | "type": "distribution",
197 | "hardenedRuntime": true,
198 | "entitlements": "assets/entitlements.mac.plist",
199 | "entitlementsInherit": "assets/entitlements.mac.plist",
200 | "gatekeeperAssess": false
201 | },
202 | "dmg": {
203 | "contents": [
204 | {
205 | "x": 130,
206 | "y": 220
207 | },
208 | {
209 | "x": 410,
210 | "y": 220,
211 | "type": "link",
212 | "path": "/Applications"
213 | }
214 | ]
215 | },
216 | "win": {
217 | "target": [
218 | "nsis"
219 | ]
220 | },
221 | "linux": {
222 | "target": [
223 | "AppImage"
224 | ],
225 | "category": "Development"
226 | },
227 | "directories": {
228 | "app": "release/app",
229 | "buildResources": "assets",
230 | "output": "release/build"
231 | },
232 | "extraResources": [
233 | "./assets/**"
234 | ],
235 | "publish": {
236 | "provider": "github",
237 | "owner": "betweentwomidnights",
238 | "repo": "gary4live-frontend"
239 | }
240 | },
241 | "collective": {
242 | "url": "https://thecollabagepatch.com"
243 | },
244 | "devEngines": {
245 | "node": ">=14.x",
246 | "npm": ">=7.x"
247 | },
248 | "electronmon": {
249 | "patterns": [
250 | "!**/**",
251 | "src/main/**"
252 | ],
253 | "logLevel": "quiet"
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/g4l-ui/src/main/menu.ts:
--------------------------------------------------------------------------------
1 | import {
2 | app,
3 | Menu,
4 | shell,
5 | BrowserWindow,
6 | MenuItemConstructorOptions,
7 | MenuItem,
8 | } from 'electron';
9 |
10 | interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
11 | selector?: string;
12 | submenu?: DarwinMenuItemConstructorOptions[] | Menu;
13 | }
14 |
15 | export default class MenuBuilder {
16 | mainWindow: BrowserWindow;
17 |
18 | constructor(mainWindow: BrowserWindow) {
19 | this.mainWindow = mainWindow;
20 | }
21 |
22 | buildMenu(): Menu {
23 | if (
24 | process.env.NODE_ENV === 'development' ||
25 | process.env.DEBUG_PROD === 'true'
26 | ) {
27 | this.setupDevelopmentEnvironment();
28 | }
29 |
30 | const template: MenuItemConstructorOptions[] =
31 | process.platform === 'darwin'
32 | ? this.buildDarwinTemplate()
33 | : this.buildDefaultTemplate();
34 |
35 | const menu = Menu.buildFromTemplate(template);
36 | Menu.setApplicationMenu(menu);
37 |
38 | return menu;
39 | }
40 |
41 | setupDevelopmentEnvironment(): void {
42 | this.mainWindow.webContents.on('context-menu', (_, props) => {
43 | const { x, y } = props;
44 |
45 | Menu.buildFromTemplate([
46 | {
47 | label: 'Inspect element',
48 | click: () => {
49 | this.mainWindow.webContents.inspectElement(x, y);
50 | },
51 | },
52 | ]).popup({ window: this.mainWindow });
53 | });
54 | }
55 |
56 | buildDarwinTemplate(): MenuItemConstructorOptions[] {
57 | const subMenuAbout: DarwinMenuItemConstructorOptions = {
58 | label: 'Electron',
59 | submenu: [
60 | {
61 | label: 'About ElectronReact',
62 | selector: 'orderFrontStandardAboutPanel:',
63 | },
64 | { type: 'separator' },
65 | { label: 'Services', submenu: [] },
66 | { type: 'separator' },
67 | {
68 | label: 'Hide ElectronReact',
69 | accelerator: 'Command+H',
70 | selector: 'hide:',
71 | },
72 | {
73 | label: 'Hide Others',
74 | accelerator: 'Command+Shift+H',
75 | selector: 'hideOtherApplications:',
76 | },
77 | { label: 'Show All', selector: 'unhideAllApplications:' },
78 | { type: 'separator' },
79 | {
80 | label: 'Quit',
81 | accelerator: 'Command+Q',
82 | click: () => {
83 | app.quit();
84 | },
85 | },
86 | ],
87 | };
88 | const subMenuEdit: DarwinMenuItemConstructorOptions = {
89 | label: 'Edit',
90 | submenu: [
91 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
92 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
93 | { type: 'separator' },
94 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
95 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
96 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
97 | {
98 | label: 'Select All',
99 | accelerator: 'Command+A',
100 | selector: 'selectAll:',
101 | },
102 | ],
103 | };
104 | const subMenuViewDev: MenuItemConstructorOptions = {
105 | label: 'View',
106 | submenu: [
107 | {
108 | label: 'Reload',
109 | accelerator: 'Command+R',
110 | click: () => {
111 | this.mainWindow.webContents.reload();
112 | },
113 | },
114 | {
115 | label: 'Toggle Full Screen',
116 | accelerator: 'Ctrl+Command+F',
117 | click: () => {
118 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
119 | },
120 | },
121 | {
122 | label: 'Toggle Developer Tools',
123 | accelerator: 'Alt+Command+I',
124 | click: () => {
125 | this.mainWindow.webContents.toggleDevTools();
126 | },
127 | },
128 | ],
129 | };
130 | const subMenuViewProd: MenuItemConstructorOptions = {
131 | label: 'View',
132 | submenu: [
133 | {
134 | label: 'Toggle Full Screen',
135 | accelerator: 'Ctrl+Command+F',
136 | click: () => {
137 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
138 | },
139 | },
140 | ],
141 | };
142 | const subMenuWindow: DarwinMenuItemConstructorOptions = {
143 | label: 'Window',
144 | submenu: [
145 | {
146 | label: 'Minimize',
147 | accelerator: 'Command+M',
148 | selector: 'performMiniaturize:',
149 | },
150 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
151 | { type: 'separator' },
152 | { label: 'Bring All to Front', selector: 'arrangeInFront:' },
153 | ],
154 | };
155 | const subMenuHelp: MenuItemConstructorOptions = {
156 | label: 'Help',
157 | submenu: [
158 | {
159 | label: 'show guide',
160 | type: 'checkbox',
161 | checked: false,
162 | click: (menuItem) => {
163 | const isChecked = menuItem.checked;
164 | this.mainWindow.webContents.send('toggle-guide', isChecked);
165 | },
166 | },
167 | {
168 | label: 'hear stuff made using this',
169 | click() {
170 | shell.openExternal('https://youtube.com/@thecollabagepatch');
171 | },
172 | },
173 | {
174 | label: 'github',
175 | click() {
176 | shell.openExternal(
177 | 'https://github.com/betweentwomidnights/gary4live',
178 | );
179 | },
180 | },
181 | {
182 | label: 'community - discord',
183 | click() {
184 | shell.openExternal('https://discord.gg/VECkyXEnAd');
185 | },
186 | },
187 | // {
188 | // label: 'Search Issues',
189 | // click() {
190 | // shell.openExternal('https://github.com/electron/electron/issues');
191 | // },
192 | // },
193 | ],
194 | };
195 |
196 | const subMenuView =
197 | process.env.NODE_ENV === 'development' ||
198 | process.env.DEBUG_PROD === 'true'
199 | ? subMenuViewDev
200 | : subMenuViewProd;
201 |
202 | return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
203 | }
204 |
205 | buildDefaultTemplate() {
206 | const templateDefault = [
207 | {
208 | label: '&File',
209 | submenu: [
210 | {
211 | label: '&Open',
212 | accelerator: 'Ctrl+O',
213 | },
214 | {
215 | label: '&Close',
216 | accelerator: 'Ctrl+W',
217 | click: () => {
218 | this.mainWindow.close();
219 | },
220 | },
221 | ],
222 | },
223 | {
224 | label: '&View',
225 | submenu:
226 | process.env.NODE_ENV === 'development' ||
227 | process.env.DEBUG_PROD === 'true'
228 | ? [
229 | {
230 | label: '&Reload',
231 | accelerator: 'Ctrl+R',
232 | click: () => {
233 | this.mainWindow.webContents.reload();
234 | },
235 | },
236 | {
237 | label: 'Toggle &Full Screen',
238 | accelerator: 'F11',
239 | click: () => {
240 | this.mainWindow.setFullScreen(
241 | !this.mainWindow.isFullScreen(),
242 | );
243 | },
244 | },
245 | {
246 | label: 'Toggle &Developer Tools',
247 | accelerator: 'Alt+Ctrl+I',
248 | click: () => {
249 | this.mainWindow.webContents.toggleDevTools();
250 | },
251 | },
252 | ]
253 | : [
254 | {
255 | label: 'Toggle &Full Screen',
256 | accelerator: 'F11',
257 | click: () => {
258 | this.mainWindow.setFullScreen(
259 | !this.mainWindow.isFullScreen(),
260 | );
261 | },
262 | },
263 | ],
264 | },
265 | {
266 | label: 'Help',
267 | submenu: [
268 | {
269 | label: 'show guide',
270 | type: 'checkbox',
271 | checked: false,
272 | click: (
273 | menuItem: MenuItem,
274 | _browserWindow: BrowserWindow,
275 | _event: KeyboardEvent
276 | ) => {
277 | const isChecked = menuItem.checked;
278 | this.mainWindow.webContents.send('toggle-guide', isChecked);
279 | },
280 | },
281 | {
282 | label: 'hear stuff made using this',
283 | click() {
284 | shell.openExternal('https://youtube.com/@thecollabagepatch');
285 | },
286 | },
287 | {
288 | label: 'github',
289 | click() {
290 | shell.openExternal(
291 | 'https://github.com/betweentwomidnights/gary4live',
292 | );
293 | },
294 | },
295 | {
296 | label: 'community - discord',
297 | click() {
298 | shell.openExternal('https://discord.gg/VECkyXEnAd');
299 | },
300 | },
301 | // {
302 | // label: 'Search Issues',
303 | // click() {
304 | // shell.openExternal('https://github.com/electron/electron/issues');
305 | // },
306 | // },
307 | ],
308 | },
309 | ];
310 |
311 | return templateDefault;
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/g4l-ui/src/renderer/App.css:
--------------------------------------------------------------------------------
1 | /*
2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules
3 | * See https://github.com/webpack-contrib/sass-loader#imports
4 | */
5 |
6 | .logo.spin img {
7 | animation: spin 10s linear infinite; /* Adjust the duration as needed */
8 | }
9 |
10 | @keyframes spin {
11 | from {
12 | transform: rotate(0deg);
13 | }
14 | to {
15 | transform: rotate(360deg);
16 | }
17 | }
18 |
19 | body {
20 | position: relative;
21 | color: white;
22 | height: 95vh;
23 | background: black;
24 | font-family: 'Arial Rounded MT';
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | overflow-x: hidden; /* Prevent horizontal scrolling */
29 | overflow-y: hidden;
30 | }
31 |
32 | button {
33 | background-color: black;
34 | color: white;
35 | padding: 0px 2px;
36 | border-radius: 5px;
37 | border-color: red;
38 | border: none;
39 | appearance: none;
40 | font-size: 1.3rem;
41 | font-family: 'Agency FB';
42 | box-shadow: none;
43 | transition: all ease-in 0.1s;
44 | cursor: pointer;
45 | opacity: 0.9;
46 | }
47 |
48 | button:hover {
49 | transform: scale(1.05);
50 | opacity: 1;
51 | }
52 |
53 | /* Add this CSS to your App.css for disabled button styling */
54 | button:disabled {
55 | opacity: 0.4;
56 | cursor: not-allowed;
57 | transform: none !important; /* Prevent hover scale effect */
58 | }
59 |
60 | button:disabled:hover {
61 | opacity: 0.4; /* Keep same opacity on hover */
62 | transform: none !important;
63 | }
64 |
65 | li {
66 | list-style: none;
67 | }
68 |
69 | a {
70 | text-decoration: none;
71 | height: fit-content;
72 | width: fit-content;
73 | margin: 2px;
74 | }
75 |
76 | a:hover {
77 | opacity: 1;
78 | text-decoration: none;
79 | }
80 |
81 | .Hello {
82 | display: flex;
83 | justify-content: center;
84 | align-items: center;
85 | margin: 1px 0;
86 | }
87 |
88 | .indicator {
89 | margin-top: 10px;
90 | }
91 |
92 | .recording-indicator {
93 | width: 21px;
94 | height: 21px;
95 | background-color: red;
96 | border-radius: 50%;
97 | }
98 |
99 | .idle-indicator {
100 | width: 21px;
101 | height: 21px;
102 | background-color: grey;
103 | border-radius: 50%;
104 | }
105 |
106 | /* Container styling for centering */
107 | .logo {
108 | position: absolute; /* Take it out of the normal flow */
109 | left: 50%; /* Center horizontally */
110 | transform: translateX(-50%); /* Offset by its own width */
111 | top: 30px; /* Position from top */
112 | margin: 0;
113 | z-index: -1; /* Place behind other elements */
114 | }
115 |
116 | .logo img {
117 | width: 150px; /* Set fixed width */
118 | height: auto;
119 | display: block;
120 | animation: rotate 20s linear infinite;
121 | }
122 |
123 | .timeline {
124 | display: flex;
125 | justify-content: space-between;
126 | align-items: center;
127 | margin-top: 0px;
128 | margin-bottom: 5px;
129 | position: relative;
130 | height: 15px; /* Adjust height for better visibility of ticks */
131 | }
132 |
133 | .timeline-tick {
134 | position: relative;
135 | flex-grow: 1;
136 | text-align: center;
137 | }
138 |
139 | .timeline-label {
140 | font-size: 12px;
141 | color: white; /* Change label color to white */
142 | position: absolute;
143 | bottom: -15px;
144 | left: 50%;
145 | transform: translateX(-50%);
146 | }
147 |
148 | .timeline-mark {
149 | height: 10px;
150 | width: 1px;
151 | background-color: white; /* Change tick mark color to white */
152 | display: inline-block;
153 | position: absolute;
154 | bottom: 0;
155 | left: 50%;
156 | transform: translateX(-50%);
157 | }
158 |
159 | .timeline-tick::before {
160 | content: '';
161 | position: absolute;
162 | bottom: 0;
163 | left: 50%;
164 | transform: translateX(-50%);
165 | height: 5px; /* Height for tick marks for seconds */
166 | width: 1px;
167 | background-color: white; /* Change tick mark color to white */
168 | }
169 |
170 | .timeline-tick:nth-child(5n + 1)::before {
171 | height: 10px; /* Longer tick marks for every 5 seconds */
172 | }
173 |
174 |
175 | .waveform {
176 | margin-bottom: 5px; /* Adjust spacing between first waveform and timeline */
177 | }
178 |
179 | .waveform-out {
180 | margin-top: 5px; /* Adjust spacing between timeline and second waveform */
181 | }
182 |
183 | /* Control Wrapper */
184 | .control-wrapper {
185 | position: relative;
186 | display: inline-block;
187 | margin: 2px;
188 | }
189 |
190 | /* Indicator Wrapper */
191 | .indicator-wrapper {
192 | position: relative;
193 | display: inline-block;
194 | margin-top: 70px;
195 | z-index: 12000;
196 | }
197 |
198 | /* Guide Number */
199 | .guide-number {
200 | position: absolute;
201 | top: -10px;
202 | right: -10px;
203 | background-color: rgba(0, 0, 0, 0.479);
204 | color: white;
205 | border: 2px solid rgb(255, 0, 0);
206 | padding: 5px;
207 | cursor: pointer;
208 | font-weight: bold;
209 | z-index: 1000;
210 | }
211 |
212 | /* Tooltip */
213 | .tooltip {
214 | position: absolute;
215 | background-color: #000000;
216 | color: white;
217 | padding: 5px;
218 | border-radius: 3px;
219 | white-space: pre-wrap;
220 | width: 250px;
221 | z-index: 4020;
222 | font-size: 12px;
223 |
224 | max-height: 400px; /* Optional default max height */
225 | }
226 |
227 |
228 | .backend-status {
229 | margin-top: 20px;
230 | text-align: center;
231 | font-size: 14px;
232 | }
233 |
234 | .backend-status a {
235 | color: #b42020;
236 | text-decoration: none;
237 | }
238 |
239 | .backend-status a:hover {
240 | text-decoration: underline;
241 | }
242 |
243 | .gary-toggle-wrapper {
244 | position: relative;
245 | display: inline-block;
246 | margin: 2px;
247 | margin-left: 2px; /* Keep the reduced margin from before */
248 | margin-right: 0px; /* Add some space to the right but not too much */
249 | margin-bottom: -80px;
250 | }
251 |
252 | /* Style for the toggle button itself */
253 | .gary-toggle {
254 | border: 1px solid red; /* Match your existing button style */
255 | background-color: black;
256 | color: white;
257 | transition: all 0.2s ease;
258 | }
259 |
260 | /* Style for when Gary is tamed */
261 | .gary-toggle.active {
262 | background-color: #b42020; /* Match your link hover color */
263 | color: white;
264 | }
265 |
266 | .top-row {
267 | display: flex;
268 | justify-content: flex-start; /* Changed from space-between to align left */
269 | align-items: center;
270 | width: 100%;
271 | margin-bottom: 10px;
272 | margin-top: 40px;
273 | }
274 |
275 | .left-controls {
276 | display: flex;
277 | align-items: center;
278 | gap: 0px; /* Reduced gap to bring fix_toggle closer to indicator */
279 | }
280 |
281 | .gary-toggle-wrapper {
282 | margin-left: 8px; /* Reduced margin to bring tame gary closer */
283 | }
284 |
285 | /* Transform buttons group */
286 | .transform-controls {
287 | display: flex;
288 | align-items: center;
289 | gap: 8px;
290 | margin-left: auto; /* This pushes it to the right */
291 | position: relative; /* Add this */
292 | }
293 |
294 | /* Add these new classes */
295 | .transform-button,
296 | .undo-transform-button,
297 | .reset-transform-button {
298 | position: relative; /* Add this */
299 | }
300 |
301 | .transform-button .guide-number,
302 | .undo-transform-button .guide-number,
303 | .reset-transform-button .guide-number {
304 | top: -25px; /* Adjust as needed */
305 | right: -20px;
306 | margin-right: 20px;
307 | z-index: 9000;
308 | }
309 |
310 | /* Progress and variation select row */
311 | .progress-row {
312 | display: flex;
313 | align-items: center;
314 | justify-content: space-between;
315 | width: 100%;
316 | margin-top: 5px; /* Pull it up closer to the top controls */
317 | margin-bottom: 10px;
318 | position: relative;
319 | z-index: 1;
320 | }
321 |
322 | /* Style the dropdown differently */
323 | .variation-select {
324 | background-color: #b42020; /* Solid red background */
325 | color: white;
326 | border: 1px solid red;
327 | padding: 4px 4px;
328 | border-radius: 5px;
329 | font-family: 'Agency FB';
330 | font-size: 1.3rem;
331 | cursor: pointer;
332 | z-index: 1;
333 | margin-top: -20px;
334 | }
335 |
336 | .variation-select:hover {
337 | background-color: #d42525; /* Slightly lighter red on hover */
338 | }
339 |
340 | .transform-button {
341 | border: 1px solid red; /* Match your existing button style */
342 | }
343 |
344 | .flowstep-control {
345 | display: flex;
346 | flex-direction: column;
347 | align-items: center;
348 | margin-bottom: 0rem;
349 | width: 100%;
350 | }
351 |
352 | .flowstep-slider {
353 | width: 100%;
354 | height: 4px;
355 | -webkit-appearance: none;
356 | background: #444;
357 | border-radius: 2px;
358 | outline: none;
359 | margin: 5px 0;
360 | }
361 |
362 | .flowstep-slider::-webkit-slider-thumb {
363 | -webkit-appearance: none;
364 | appearance: none;
365 | width: 10px;
366 | height: 10px;
367 | border-radius: 50%;
368 | background: #ff0000;
369 | cursor: pointer;
370 | transition: background 0.15s ease;
371 | }
372 |
373 | .flowstep-slider::-webkit-slider-thumb:hover {
374 | background: #cc0000;
375 | }
376 |
377 | .flowstep-label {
378 | font-size: 0.7rem;
379 | color: #666;
380 | margin-top: 4px;
381 | }
382 |
383 | /* Ensure transform section maintains position */
384 | .transform-section {
385 | display: flex;
386 | flex-direction: column;
387 | align-items: flex-end;
388 | margin-left: auto;
389 | gap: 10px;
390 | position: relative; /* Ensure it stays above the logo */
391 | z-index: 10000;
392 | }
393 |
394 | .prompt-wrapper {
395 | display: flex;
396 | flex-direction: column;
397 | gap: 5px;
398 | margin-bottom: 50px;
399 | position: absolute;
400 | }
401 |
402 | .prompt-label {
403 | font-size: 10px;
404 | color: #a5a5a5;
405 | font-weight: bold;
406 | }
407 |
408 | .prompt-input {
409 | padding: 6px 8px;
410 | border: 1px solid #ccc;
411 | border-radius: 4px;
412 | font-size: 12px;
413 | width: 200px;
414 | background: #fff;
415 | }
416 |
417 | .prompt-input:focus {
418 | outline: none;
419 | border-color: #007bff;
420 | box-shadow: 0 0 3px rgba(0, 123, 255, 0.3);
421 | }
422 |
423 | .generate-button {
424 | padding: 6px 12px;
425 | background: #ec0000;
426 | color: white;
427 | border: none;
428 | border-radius: 4px;
429 | font-size: 12px;
430 | cursor: pointer;
431 | font-weight: bold;
432 | }
433 |
434 | .generate-button:hover {
435 | background: #d40000;
436 | }
437 |
438 | .generate-button:active {
439 | background: #eb0800;
440 | }
441 |
442 | .waveform-highlighted {
443 | border: 3px solid #ffffff !important;
444 | border-radius: 4px;
445 | box-shadow: 0 0 10px rgb(255, 255, 255);
446 | background-color: rgba(0, 123, 255, 0.1);
447 | transition: all 0.2s ease-in-out;
448 | }
449 |
450 | /* Optional: Make the transition smoother */
451 | .waveform, .waveform-out {
452 | transition: all 0.2s ease-in-out;
453 | border: 3px solid transparent; /* Invisible border to prevent layout shift */
454 | }
455 |
456 | /* Optional: Different colors for top vs bottom */
457 | .waveform.waveform-highlighted {
458 | border-color: #ffbebe; /* Green for input/top */
459 | box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
460 | background-color: rgba(40, 167, 69, 0.1);
461 | }
462 |
463 | .waveform-out.waveform-highlighted {
464 | border-color: #f8b9b9; /* Blue for output/bottom */
465 | box-shadow: 0 0 10px rgba(0, 123, 255, 0.5);
466 | background-color: rgba(0, 123, 255, 0.1);
467 | }
468 |
469 | /* Add to your App.css */
470 |
471 | .waveform-container {
472 | position: relative;
473 | /* inherits size from existing waveform */
474 | }
475 |
476 | .recording-progress-overlay {
477 | position: absolute;
478 | top: 0;
479 | left: 0;
480 | right: 0;
481 | bottom: 0;
482 | pointer-events: none; /* Don't interfere with waveform interaction */
483 | z-index: 2;
484 | }
485 |
486 | .recording-progress-outline {
487 | position: absolute;
488 | top: 0;
489 | left: 0;
490 | right: 0;
491 | bottom: 0;
492 | border: 2px dashed rgba(255, 0, 0, 0.4); /* Red dotted outline */
493 | border-radius: 4px;
494 | background:
495 | /* Fake waveform pattern using CSS gradients */
496 | repeating-linear-gradient(
497 | 90deg,
498 | transparent 0px,
499 | transparent 8px,
500 | rgba(255, 0, 0, 0.1) 8px,
501 | rgba(255, 0, 0, 0.1) 12px,
502 | transparent 12px,
503 | transparent 20px,
504 | rgba(255, 0, 0, 0.2) 20px,
505 | rgba(255, 0, 0, 0.2) 24px,
506 | transparent 24px,
507 | transparent 32px,
508 | rgba(255, 0, 0, 0.15) 32px,
509 | rgba(255, 0, 0, 0.15) 36px,
510 | transparent 36px,
511 | transparent 44px
512 | );
513 | }
514 |
515 | .recording-progress-fill {
516 | position: absolute;
517 | top: 0;
518 | left: 0;
519 | bottom: 0;
520 | background:
521 | /* Filled version of the same pattern */
522 | repeating-linear-gradient(
523 | 90deg,
524 | transparent 0px,
525 | transparent 8px,
526 | rgba(255, 0, 0, 0.3) 8px,
527 | rgba(255, 0, 0, 0.3) 12px,
528 | transparent 12px,
529 | transparent 20px,
530 | rgba(255, 0, 0, 0.5) 20px,
531 | rgba(255, 0, 0, 0.5) 24px,
532 | transparent 24px,
533 | transparent 32px,
534 | rgba(255, 0, 0, 0.4) 32px,
535 | rgba(255, 0, 0, 0.4) 36px,
536 | transparent 36px,
537 | transparent 44px
538 | );
539 | border-right: 2px solid rgba(255, 0, 0, 0.8); /* Progress line */
540 | transition: width 0.05s linear; /* Smooth fill animation */
541 | border-radius: 2px 0 0 2px;
542 | }
543 |
544 | /* Alternative: More waveform-like pattern */
545 | .recording-progress-fill.waveform-style {
546 | background:
547 | /* Simulated waveform peaks and valleys */
548 | linear-gradient(180deg,
549 | transparent 0%,
550 | transparent 20%,
551 | rgba(255, 0, 0, 0.2) 20%,
552 | rgba(255, 0, 0, 0.4) 30%,
553 | rgba(255, 0, 0, 0.6) 40%,
554 | rgba(255, 0, 0, 0.4) 50%,
555 | rgba(255, 0, 0, 0.6) 60%,
556 | rgba(255, 0, 0, 0.4) 70%,
557 | rgba(255, 0, 0, 0.2) 80%,
558 | transparent 80%,
559 | transparent 100%
560 | ),
561 | /* Add horizontal bars to simulate waveform lines */
562 | repeating-linear-gradient(
563 | 90deg,
564 | rgba(255, 0, 0, 0.1) 0px,
565 | rgba(255, 0, 0, 0.1) 1px,
566 | transparent 1px,
567 | transparent 4px
568 | );
569 | }
570 |
571 | /* Pulse animation when recording */
572 | .recording-progress-overlay.recording {
573 | animation: recordingPulse 2s ease-in-out infinite;
574 | }
575 |
576 | @keyframes recordingPulse {
577 | 0%, 100% {
578 | opacity: 1;
579 | }
580 | 50% {
581 | opacity: 0.7;
582 | }
583 | }
584 |
585 | /* Alternative: Steady glow without pulse (if pulse is too distracting) */
586 | .save-buffer-button {
587 | /* Your existing button styles */
588 | transition: all 0.3s ease;
589 | }
590 |
591 | .save-buffer-button.recording-highlight {
592 | /* White highlight during recording - STEADY VERSION */
593 | border: 2px solid white !important;
594 | box-shadow:
595 | 0 0 12px rgba(255, 255, 255, 0.7),
596 | 0 0 24px rgba(255, 255, 255, 0.5);
597 | background-color: rgba(255, 255, 255, 0.15);
598 | /* No animation for steady glow */
599 | }
600 |
601 | @keyframes saveBufferPulse {
602 | 0%, 100% {
603 | box-shadow:
604 | 0 0 10px rgba(255, 255, 255, 0.6),
605 | 0 0 20px rgba(255, 255, 255, 0.4),
606 | inset 0 0 5px rgba(255, 255, 255, 0.2);
607 | }
608 | 50% {
609 | box-shadow:
610 | 0 0 15px rgba(255, 255, 255, 0.8),
611 | 0 0 30px rgba(255, 255, 255, 0.6),
612 | inset 0 0 10px rgba(255, 255, 255, 0.3);
613 | }
614 | }
615 |
616 | /* Alternative: Steady glow without pulse (if pulse is too distracting) */
617 | .save-buffer-button.recording-highlight.steady {
618 | border: 2px solid white !important;
619 | box-shadow:
620 | 0 0 1px rgba(255, 255, 255, 0.7),
621 | 0 0 2px rgba(255, 255, 255, 0.5);
622 | background-color: rgba(255, 255, 255, 0.15);
623 | animation: none; /* Remove pulse for steady glow */
624 | }
625 |
626 | .jerry-control-wrapper {
627 | position: absolute;
628 | display: inline-block;
629 | margin: 2px;
630 | margin-bottom: 137px;
631 | margin-left: 112px;
632 | z-index: 9000;
633 | }
634 |
635 | .jerry-control-wrapper-2 {
636 | position: absolute;
637 | display: inline-block;
638 | margin: 2px;
639 | margin-bottom: 137px;
640 | margin-left: 168px;
641 | z-index: 9000;
642 | }
643 |
644 | .jerry-input {
645 | width: 32px; /* Make them narrow - adjust as needed */
646 | padding: 6px 8px;
647 | border: 1px solid #ccc;
648 | height: 6px;
649 | font-size: 12px;
650 | background: #fff;
651 | text-align: center; /* Center the numbers */
652 | }
653 |
654 | .jerry-input:focus {
655 | outline: none;
656 | border-color: #ff0000;
657 | box-shadow: 0 0 3px rgba(110, 0, 0, 0.3);
658 | }
659 |
660 | .jerry-label {
661 | font-size: 10px;
662 | color: #adacac;
663 | font-weight: bold;
664 | text-align: center;
665 | margin-bottom: 4px;
666 | width: 32px; /* Match the input width */
667 | }
668 |
669 | .backend-connection-status {
670 | margin-top: 20px;
671 | text-align: center;
672 | font-size: 14px;
673 | display: flex;
674 | align-items: center;
675 | justify-content: center;
676 | gap: 8px;
677 | transition: all 0.3s ease;
678 | }
679 |
680 | .connection-dot {
681 | width: 8px;
682 | height: 8px;
683 | border-radius: 50%;
684 | transition: background-color 0.3s ease;
685 | }
686 |
687 | .backend-connection-status.connected {
688 | color: #4ade80; /* Green text */
689 | }
690 |
691 | .backend-connection-status.connected .connection-dot {
692 | background-color: #4ade80; /* Green dot */
693 | box-shadow: 0 0 6px rgba(74, 222, 128, 0.6);
694 | }
695 |
696 | .backend-connection-status.disconnected {
697 | color: #ef4444; /* Red text */
698 | }
699 |
700 | .backend-connection-status.disconnected .connection-dot {
701 | background-color: #ef4444; /* Red dot */
702 | box-shadow: 0 0 6px rgba(239, 68, 68, 0.6);
703 | }
704 |
--------------------------------------------------------------------------------
/electron-communication.js:
--------------------------------------------------------------------------------
1 | const Max = require('max-api');
2 | const WebSocket = require('ws');
3 | const fs = require('fs');
4 | const ffmpeg = require('fluent-ffmpeg');
5 | const path = require('path');
6 | const { execSync } = require('child_process');
7 |
8 | function checkFfmpegInPath() {
9 | try {
10 | // Try to execute ffmpeg -version
11 | execSync('ffmpeg -version', { stdio: 'ignore' });
12 | return true;
13 | } catch (error) {
14 | return false;
15 | }
16 | }
17 |
18 | function findFfmpegPath() {
19 | // Define all possible FFmpeg locations in order of preference
20 | const possiblePaths = [
21 | // 1. Natural extraction location (where ffmpeg usually ends up)
22 | 'C:\\g4l\\ffmpeg\\bin\\ffmpeg.exe',
23 |
24 | // 2. Manual copy location (current expectation)
25 | 'C:\\g4l\\ffmpeg\\ffmpeg.exe',
26 |
27 | // 3. Relative to this script (if bundled with app)
28 | path.join(__dirname, 'ffmpeg', 'bin', 'ffmpeg.exe'),
29 | path.join(__dirname, 'ffmpeg', 'ffmpeg.exe'),
30 | path.join(__dirname, '..', 'ffmpeg', 'bin', 'ffmpeg.exe'),
31 |
32 | // 4. Common user installation locations
33 | 'C:\\ffmpeg\\bin\\ffmpeg.exe',
34 | 'C:\\tools\\ffmpeg\\bin\\ffmpeg.exe',
35 |
36 | // 5. Program Files locations
37 | 'C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe',
38 | 'C:\\Program Files (x86)\\ffmpeg\\bin\\ffmpeg.exe',
39 |
40 | // 6. Chocolatey installation
41 | 'C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe',
42 | 'C:\\tools\\ffmpeg\\ffmpeg.exe',
43 |
44 | // 7. User AppData (winget/scoop installations)
45 | path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'winget', 'Packages', 'Gyan.FFmpeg_Microsoft.Winget.Source*', 'ffmpeg-*', 'bin', 'ffmpeg.exe'),
46 | ];
47 |
48 | // Check each path
49 | for (const testPath of possiblePaths) {
50 | Max.post(`Checking FFmpeg at: ${testPath}`);
51 |
52 | if (checkFfmpegAtPath(testPath)) {
53 | Max.post(`✅ Found working FFmpeg at: ${testPath}`);
54 | return testPath;
55 | }
56 | }
57 |
58 | return null;
59 | }
60 |
61 | function setupFfmpegPath() {
62 | Max.post('🔍 Searching for FFmpeg...');
63 |
64 | // First check if ffmpeg is already accessible in PATH
65 | if (checkFfmpegInPath()) {
66 | Max.post('✅ FFmpeg found in system PATH, using system FFmpeg');
67 | return true;
68 | }
69 |
70 | // If not in PATH, search common installation locations
71 | const ffmpegPath = findFfmpegPath();
72 |
73 | if (ffmpegPath) {
74 | try {
75 | ffmpeg.setFfmpegPath(ffmpegPath);
76 | Max.post(`✅ Successfully configured FFmpeg: ${ffmpegPath}`);
77 | return true;
78 | } catch (error) {
79 | Max.post(`❌ Error setting FFmpeg path: ${error.message}`);
80 | }
81 | }
82 |
83 | // If we get here, FFmpeg wasn't found anywhere
84 | Max.post('❌ FFmpeg not found in any expected location!');
85 | Max.post('📋 To fix this, you can:');
86 | Max.post(' 1. Install FFmpeg via chocolatey: choco install ffmpeg');
87 | Max.post(' 2. Install FFmpeg via winget: winget install ffmpeg');
88 | Max.post(' 3. Download from https://ffmpeg.org and extract to C:\\g4l\\ffmpeg\\');
89 | Max.post(' 4. Add FFmpeg to your system PATH');
90 |
91 | return false;
92 | }
93 |
94 | // Call this function when initializing your application
95 | setupFfmpegPath();
96 |
97 | const ws = new WebSocket('ws://localhost:8080');
98 |
99 | let lastErrorMessage = null; // Track the last error message
100 | let isProcessing = false;
101 |
102 | // Define our variations array in the same order as the Max umenu
103 | const VARIATIONS = [
104 | 'accordion_folk',
105 | 'banjo_bluegrass',
106 | 'piano_classical',
107 | 'celtic',
108 | 'strings_quartet',
109 | 'synth_retro',
110 | 'synth_modern',
111 | 'synth_edm',
112 | 'lofi_chill',
113 | 'synth_bass',
114 | 'rock_band',
115 | 'cinematic_epic',
116 | 'retro_rpg',
117 | 'chiptune',
118 | 'steel_drums',
119 | 'gamelan_fusion',
120 | 'music_box',
121 | 'trap_808',
122 | 'lo_fi_drums',
123 | 'boom_bap',
124 | 'percussion_ensemble',
125 | 'future_bass',
126 | 'synthwave_retro',
127 | 'melodic_techno',
128 | 'dubstep_wobble',
129 | 'glitch_hop',
130 | 'digital_disruption',
131 | 'circuit_bent',
132 | 'orchestral_glitch',
133 | 'vapor_drums',
134 | 'industrial_textures',
135 | 'jungle_breaks',
136 |
137 |
138 |
139 | // ... all other variations in the same order as Max umenu
140 | ];
141 |
142 | ws.on('open', function open() {
143 | Max.post("Connected to Electron WebSocket server");
144 | });
145 |
146 | ws.on('close', () => Max.post("Disconnected from Electron WebSocket server"));
147 |
148 | Max.addHandler('set_flowstep', (value) => {
149 | if (ws.readyState === WebSocket.OPEN) {
150 | ws.send(JSON.stringify({
151 | action: 'update_flowstep',
152 | data: value
153 | }));
154 | Max.post(`Sent flowstep update to WebSocket server: ${value}`);
155 | }
156 | });
157 |
158 | Max.addHandler('reconnect', () => {
159 | lastErrorMessage = null; // Clear any stored error message
160 | if (ws.readyState === WebSocket.OPEN) {
161 | // Only send the connection status
162 | ws.send(JSON.stringify({
163 | action: 'backend_connection_status',
164 | data: true
165 | }));
166 | Max.post('Sent reconnection status to WebSocket server');
167 | }
168 | });
169 |
170 | Max.addHandler('backend_connection', (...args) => {
171 |
172 | Max.post('Full backend_connection message data:');
173 | Max.post(JSON.stringify({
174 | args: args,
175 | firstArg: args[0],
176 | type: typeof args[0]
177 | }));
178 |
179 | if (ws.readyState === WebSocket.OPEN) {
180 | // Now we can properly check the value we receive
181 | const status = args[0] === 1;
182 |
183 | ws.send(JSON.stringify({
184 | action: 'backend_connection_status',
185 | data: status
186 | }));
187 | Max.post(`Sent backend connection status to WebSocket server: ${status}`);
188 | }
189 | });
190 |
191 | Max.addHandler('error_message', (data) => {
192 | // Store this error message
193 | lastErrorMessage = data;
194 |
195 | if (ws.readyState === WebSocket.OPEN) {
196 | const [type, message] = data.split('|');
197 |
198 | ws.send(JSON.stringify({
199 | action: 'error_message',
200 | data: {
201 | type,
202 | message
203 | }
204 | }));
205 | Max.post(`Sent error message to WebSocket server - Type: ${type}, Message: ${message}`);
206 | }
207 | });
208 |
209 | // Handler for receiving variation changes from Max
210 | Max.addHandler('set_variation', (value) => {
211 | if (ws.readyState === WebSocket.OPEN) {
212 | // If value is a number (index)
213 | if (typeof value === 'number') {
214 | const variationName = VARIATIONS[value];
215 | if (variationName) {
216 | ws.send(JSON.stringify({
217 | action: 'update_variation',
218 | data: variationName
219 | }));
220 | Max.post(`Sent variation update to WebSocket server: ${variationName}`);
221 | } else {
222 | Max.post(`Invalid variation index: ${value}`);
223 | }
224 | }
225 | // If value is a string (variation name)
226 | else if (typeof value === 'string') {
227 | if (VARIATIONS.includes(value)) {
228 | ws.send(JSON.stringify({
229 | action: 'update_variation',
230 | data: value
231 | }));
232 | Max.post(`Sent variation update to WebSocket server: ${value}`);
233 | } else {
234 | Max.post(`Invalid variation name: ${value}`);
235 | }
236 | }
237 | }
238 | });
239 |
240 | Max.addHandler('tame_gary', (value) => {
241 | if (ws.readyState === WebSocket.OPEN) {
242 | ws.send(JSON.stringify({ garyToggle: value }));
243 | Max.post(`Sent Gary state to WebSocket server: ${value}`);
244 | }
245 | });
246 |
247 | // Max handler to forward progress updates to the Electron app
248 | Max.addHandler('progress_update', (progress) => {
249 | if (ws.readyState === WebSocket.OPEN) {
250 | ws.send(JSON.stringify({
251 | action: 'progress_update',
252 | data: progress
253 | }));
254 | Max.post(`Sent progress update to WebSocket server: ${progress}`);
255 | }
256 | });
257 |
258 | Max.addHandler('number', (value) => {
259 | if (ws.readyState === WebSocket.OPEN) {
260 | ws.send(JSON.stringify({ toggle: value }));
261 | Max.post(`Sent to WebSocket server: { toggle: ${value} }`);
262 | }
263 | });
264 |
265 | const sendAudioData = (filePath, actionType) => {
266 | fs.readFile(filePath, { encoding: 'base64' }, (err, base64Data) => {
267 | if (err) {
268 | Max.post(`Error reading file: ${err.message}`);
269 | return;
270 | }
271 | if (ws.readyState === WebSocket.OPEN) {
272 | ws.send(JSON.stringify({ action: actionType, data: base64Data }));
273 | Max.post(`Sent ${actionType} back to Electron from ${filePath}.`);
274 | }
275 | });
276 | };
277 |
278 | // Function to handle the crop_audio action from Electron
279 | const handleCropAudio = (end) => {
280 | const tempFilePath = 'C:/g4l/tempAudio.wav';
281 | const croppedFilePath = 'C:/g4l/myOutput.wav';
282 |
283 |
284 |
285 | try {
286 | fs.copyFileSync('C:/g4l/myOutput.wav', tempFilePath); // Copy the original file to tempFilePath
287 |
288 | ffmpeg(tempFilePath)
289 | .setStartTime(0)
290 | .setDuration(Number(end))
291 | .output(croppedFilePath)
292 | .on('start', (cmdline) => {
293 | Max.post(`Started ffmpeg with command: ${cmdline}`);
294 | })
295 | .on('end', () => {
296 | Max.post('Audio cropping successful.');
297 | fs.unlinkSync(tempFilePath);
298 |
299 | // First, notify Max that crop was successful
300 | Max.outlet('crop_audio', 'success');
301 |
302 | // Then trigger the replace_output
303 | Max.outlet('replace_output');
304 |
305 | // Finally, send the new audio data back to Electron
306 | sendAudioData(croppedFilePath, 'audio_data_output');
307 |
308 | // Reset processing flag
309 | isProcessing = false;
310 | })
311 | .on('error', (err) => {
312 | Max.post('Error cropping audio: ' + err.message);
313 | fs.unlinkSync(tempFilePath);
314 | Max.outlet('crop_audio', 'error');
315 | isProcessing = false;
316 |
317 | // Notify Electron of the error
318 | if (ws.readyState === WebSocket.OPEN) {
319 | ws.send(JSON.stringify({
320 | action: 'error_message',
321 | data: {
322 | type: 'crop',
323 | message: 'Failed to crop audio: ' + err.message
324 | }
325 | }));
326 | }
327 | })
328 | .run();
329 | } catch (error) {
330 | Max.post('Error in crop operation: ' + error.message);
331 | isProcessing = false;
332 | Max.outlet('crop_audio', 'error');
333 | }
334 | };
335 |
336 | // Function to handle 'crop' message from Max
337 | Max.addHandler('crop', (end) => {
338 | if (!isProcessing) {
339 | isProcessing = true;
340 | ws.send(JSON.stringify({ action: 'crop', data: end }));
341 | Max.post(`Sent crop action with end: ${end}`);
342 | } else {
343 | Max.post('Processing already in progress.');
344 | }
345 | });
346 |
347 | Max.addHandler('audio_processed', () => {
348 | if (ws.readyState === WebSocket.OPEN) {
349 | ws.send(JSON.stringify({
350 | action: 'audio_processed'
351 | }));
352 | Max.post('Sent audio_processed to WebSocket server');
353 | }
354 | });
355 |
356 | Max.addHandler('music_continued', () => {
357 | if (ws.readyState === WebSocket.OPEN) {
358 | ws.send(JSON.stringify({
359 | action: 'music_continued'
360 | }));
361 | Max.post('Sent music_continued to WebSocket server');
362 | }
363 | });
364 |
365 | Max.addHandler('music_retried', () => {
366 | if (ws.readyState === WebSocket.OPEN) {
367 | ws.send(JSON.stringify({
368 | action: 'music_retried'
369 | }));
370 | Max.post('Sent music_retried to WebSocket server');
371 | }
372 | });
373 |
374 | Max.addHandler('audio_transformed', () => {
375 | if (ws.readyState === WebSocket.OPEN) {
376 | ws.send(JSON.stringify({
377 | action: 'audio_transformed'
378 | }));
379 | Max.post('Sent audio_transformed to WebSocket server');
380 | }
381 | });
382 |
383 | Max.addHandler('update_cropped_audio_complete', () => {
384 | if (ws.readyState === WebSocket.OPEN) {
385 | ws.send(JSON.stringify({
386 | action: 'update_cropped_audio_complete'
387 | }));
388 | Max.post('Sent update_cropped_audio_complete to WebSocket server');
389 | }
390 | });
391 |
392 | Max.addHandler('stable_audio_generated', () => {
393 | if (ws.readyState === WebSocket.OPEN) {
394 | ws.send(JSON.stringify({
395 | action: 'stable_audio_generated'
396 | }));
397 | Max.post('Sent stable_audio_generated to WebSocket server');
398 | }
399 | });
400 |
401 | ws.on('message', function incoming(data) {
402 | try {
403 | const command = JSON.parse(data);
404 | Max.post(`Received command: ${JSON.stringify(command)}`); // Debugging log
405 | switch (command.action) {
406 | case 'write_buffer':
407 | Max.outlet('write_buffer');
408 | setTimeout(() => sendAudioData('C:\\g4l\\myBuffer.wav', 'audio_data_buffer'), 1000);
409 | break;
410 | case 'load_output':
411 | // First pause any ongoing playback
412 | Max.outlet('pause');
413 | // Wait a brief moment for pause to take effect
414 | setTimeout(() => {
415 | // Reset the playback position
416 | Max.outlet('reset');
417 | // Replace the audio file
418 | Max.outlet('replace_output');
419 | // Send the audio data back to Electron
420 | sendAudioData('C:\\g4l\\myOutput.wav', 'audio_data_output');
421 | // Notify Electron that we've reset the playback state
422 | ws.send(JSON.stringify({
423 | action: 'playback_state_update',
424 | data: {
425 | isPlaying: false,
426 | position: 0
427 | }
428 | }));
429 | }, 100);
430 | break;
431 | case 'play':
432 | Max.outlet('play'); // Assumes there's a Max outlet configured to handle this
433 | break;
434 | case 'fix_toggle':
435 | Max.outlet('fix_toggle'); // Assumes there's a Max outlet configured to handle this
436 | break;
437 | case 'pause':
438 | Max.outlet('pause'); // Assumes there's a Max outlet configured to handle this
439 | break;
440 | case 'reset':
441 | Max.outlet('reset'); // Assumes there's a Max outlet configured to handle this
442 | break;
443 | case 'bang':
444 | Max.outlet('bang'); // Assumes there's a Max outlet configured to handle this
445 | break;
446 | case 'continue':
447 | Max.outlet('continue'); // Assumes there's a Max outlet configured to handle this
448 | break;
449 | case 'retry':
450 | Max.outlet('retry'); // Assumes there's a Max outlet configured to handle this
451 | break;
452 | case 'update_model_path':
453 | console.log('Updating model path with:', command.data);
454 | Max.outlet('forward_model_path', command.data);
455 | break;
456 | case 'update_prompt_duration':
457 | console.log('Updating prompt_duration with:', command.data);
458 | Max.outlet('update_prompt_duration', command.data);
459 | break;
460 | case 'crop':
461 | const { data } = command;
462 | handleCropAudio(data);
463 | break;
464 | case 'update_gary_state':
465 | const garyState = command.data.garyToggle;
466 | Max.outlet('update_gary_state', garyState); // This will connect to a route object in Max
467 | break;
468 | // When receiving variation updates from Electron
469 | case 'update_variation':
470 | // Find the index of the variation in our array
471 | const index = VARIATIONS.indexOf(command.data);
472 | if (index !== -1) {
473 | Max.outlet('update_variation', index); // Send the index instead of the string
474 | }
475 | break;
476 | case 'transform':
477 | Max.outlet('transform');
478 | break;
479 | case 'undo_transform':
480 | Max.outlet('undo_transform');
481 | break;
482 | case 'reset_transform':
483 | Max.outlet('reset_transform');
484 | // Then handle the syncing of current state
485 | if (command.data && typeof command.data === 'object') {
486 | // Update model path if provided
487 | if (command.data.modelPath) {
488 | Max.outlet('forward_model_path', command.data.modelPath);
489 | }
490 | // Update prompt duration if provided
491 | if (command.data.promptDuration) {
492 | Max.outlet('update_prompt_duration', command.data.promptDuration);
493 | }
494 | // Update variation if provided
495 | if (command.data.variation) {
496 | const index = VARIATIONS.indexOf(command.data.variation);
497 | if (index !== -1) {
498 | Max.outlet('update_variation', index);
499 | }
500 | }
501 | }
502 | break;
503 | case 'backend_connection_status':
504 | Max.outlet('backend_connection_status', command.data);
505 | break;
506 | case 'update_flowstep':
507 | console.log('Updating flowstep with:', command.data);
508 | Max.outlet('update_flowstep', command.data); // This will connect to our live.dial
509 | break;
510 | case 'update_prompt':
511 | let cleanData = command.data;
512 | if (typeof cleanData === 'string' && cleanData.startsWith('"') && cleanData.endsWith('"')) {
513 | cleanData = cleanData.slice(1, -1); // Remove first and last characters
514 | }
515 | Max.outlet('forward_prompt', cleanData);
516 | break;
517 | case 'generate_stable_audio':
518 | console.log('Triggering stable audio generation');
519 | Max.outlet('generate_stable_audio');
520 | break;
521 | case 'update_jerry_cfg':
522 | console.log('Updating Jerry CFG with:', command.data);
523 | Max.outlet('forward_jerry_cfg', command.data);
524 | break;
525 |
526 | case 'update_jerry_steps':
527 | console.log('Updating Jerry steps with:', command.data);
528 | Max.outlet('forward_jerry_steps', command.data);
529 | break;
530 | default:
531 | Max.post(`Unhandled action: ${command.action}`);
532 | }
533 | } catch (error) {
534 | console.error('Error parsing incoming data:', error);
535 | Max.post('Error parsing incoming data: ' + error.message);
536 | }
537 | });
538 |
539 | function initSocketConnection() {
540 | ws.on('open', function open() {
541 | Max.post('Connected to Electron WebSocket server.');
542 | });
543 |
544 | ws.on('close', () => {
545 | Max.post('Disconnected from Electron WebSocket server.');
546 | });
547 |
548 | ws.on('error', (error) => {
549 | Max.post('WebSocket error: ' + error.message);
550 | });
551 | }
552 |
553 | // Initialize WebSocket connection and setup event listeners
554 | initSocketConnection();
555 |
--------------------------------------------------------------------------------
/g4l-ui/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.1.0
2 |
3 | - Migrate to `css-minifier-webpack-plugin`
4 |
5 | # 2.0.1
6 |
7 | ## Fixes
8 |
9 | - Fix broken css linking in production build
10 |
11 | # 2.0.0
12 |
13 | ## Breaking Changes
14 |
15 | - drop redux
16 | - remove counter example app
17 | - simplify directory structure
18 | - move `dll` dir to `.erb` dir
19 | - fix icon/font import paths
20 | - migrate to `react-refresh` from `react-hot-loader`
21 | - migrate to webpack@5
22 | - migrate to electron@11
23 | - remove e2e tests and testcafe integration
24 | - rename `app` dir to more conventional `src` dir
25 | - rename `resources` dir to `assets`
26 | - simplify npm scripts
27 | - drop stylelint
28 | - simplify styling of boilerplate app
29 | - remove `START_HOT` env variable
30 | - notarize support
31 | - landing page boilerplate
32 | - docs updates
33 | - restore removed debugging support
34 |
35 | # 1.4.0
36 |
37 | - Migrate to `eslint-config-erb@2`
38 | - Rename `dev` npm script to `start`
39 | - GitHub Actions: only publish GitHub releases when on master branch
40 |
41 | # 1.3.1
42 |
43 | - Fix sass building bug ([#2540](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2540))
44 | - Fix CI bug related to E2E tests and network timeouts
45 | - Move automated dependency PRs to `next` ([#2554](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2554))
46 | - Bump dependencies to patch semver
47 |
48 | # 1.3.0
49 |
50 | - Fixes E2E tests ([#2516](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2516))
51 | - Fixes preload entrypoint ([#2503](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2503))
52 | - Downgrade to `electron@8`
53 | - Bump dependencies to latest semver
54 |
55 | # 1.2.0
56 |
57 | - Migrate to redux toolkit
58 | - Lazy load routes with react suspense
59 | - Drop support for azure-pipelines and use only github actions
60 | - Bump all deps to latest semver
61 | - Remove `test-e2e` script from tests (blocked on release of https://github.com/DevExpress/testcafe-browser-provider-electron/pull/65)
62 | - Swap `typed-css-modules-webpack-plugin` for `typings-for-css-modules-loader`
63 | - Use latest version of `eslint-config-erb`
64 | - Remove unnecessary file extensions from ts exclude
65 | - Add experimental support for vscode debugging
66 | - Revert https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2365 as default for users, provide as opt in option
67 |
68 | # 1.1.0
69 |
70 | - Fix #2402
71 | - Simplify configs (https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2406)
72 |
73 | # 1.0.0
74 |
75 | - Migrate to TypeScript from Flow ([#2363](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2363))
76 | - Use browserslist for `@babel/preset-env` targets ([#2368](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2368))
77 | - Use preload script, disable `nodeIntegration` in renderer process for [improved security](https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content) ([#2365](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2365))
78 | - Add support for azure pipelines ([#2369](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2369))
79 | - Disable sourcemaps in production
80 |
81 | # 0.18.1 (2019.12.12)
82 |
83 | - Fix HMR env bug ([#2343](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2343))
84 | - Bump all deps to latest semver
85 | - Bump to `electron@7`
86 |
87 | # 0.18.0 (2019.11.19)
88 |
89 | - Bump electron to `electron@6` (`electron@7` introduces breaking changes to testcafe end to end tests)
90 | - Revert back to [two `package.json` structure](https://www.electron.build/tutorials/two-package-structure)
91 | - Bump all deps to latest semver
92 |
93 | # 0.17.1 (2018.11.20)
94 |
95 | - Fix `yarn test-e2e` and testcafe for single package.json structure
96 | - Fixes incorrect path in `yarn start` script
97 | - Bumped deps
98 | - Bump g++ in travis
99 | - Change clone arguments to clone only master
100 | - Change babel config to target current electron version
101 |
102 | For full change list, see https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/2021
103 |
104 | # 0.17.0 (2018.10.30)
105 |
106 | - upgraded to `babel@7` (thanks to @vikr01 🎉🎉🎉)
107 | - migrated from [two `package.json` structure](https://www.electron.build/tutorials/two-package-structure) (thanks to @HyperSprite!)
108 | - initial auto update support (experimental)
109 | - migrate from greenkeeper to [renovate](https://renovatebot.com)
110 | - added issue template
111 | - use `babel-preset-env` to target current electron version
112 | - add [opencollective](https://opencollective.com/electron-react-boilerplate-594) banner message display in postinstall script (help support ERB 🙏)
113 | - fix failing ci issues
114 |
115 | # 0.16.0 (2018.10.3)
116 |
117 | - removed unused dependencies
118 | - migrate from `react-redux-router` to `connect-react-router`
119 | - move webpack configs to `./webpack` dir
120 | - use `g++` on travis when testing linux
121 | - migrate from `spectron` to `testcafe` for e2e tests
122 | - add linting support for config styles
123 | - changed stylelint config
124 | - temporarily disabled flow in appveyor to make ci pass
125 | - added necessary infra to publish releases from ci
126 |
127 | # 0.15.0 (2018.8.25)
128 |
129 | - Performance: cache webpack uglify results
130 | - Feature: add start minimized feature
131 | - Feature: lint and fix styles with prettier and stylelint
132 | - Feature: add greenkeeper support
133 |
134 | # 0.14.0 (2018.5.24)
135 |
136 | - Improved CI timings
137 | - Migrated README commands to yarn from npm
138 | - Improved vscode config
139 | - Updated all dependencies to latest semver
140 | - Fix `electron-rebuild` script bug
141 | - Migrated to `mini-css-extract-plugin` from `extract-text-plugin`
142 | - Added `optimize-css-assets-webpack-plugin`
143 | - Run `prettier` on json, css, scss, and more filetypes
144 |
145 | # 0.13.3 (2018.5.24)
146 |
147 | - Add git precommit hook, when git commit will use `prettier` to format git add code
148 | - Add format code function in `lint-fix` npm script which can use `prettier` to format project js code
149 |
150 | # 0.13.2 (2018.1.31)
151 |
152 | - Hot Module Reload (HMR) fixes
153 | - Bumped all dependencies to latest semver
154 | - Prevent error propagation of `CheckNativeDeps` script
155 |
156 | # 0.13.1 (2018.1.13)
157 |
158 | - Hot Module Reload (HMR) fixes
159 | - Bumped all dependencies to latest semver
160 | - Fixed electron-rebuild script
161 | - Fixed tests scripts to run on all platforms
162 | - Skip redux logs in console in test ENV
163 |
164 | # 0.13.0 (2018.1.6)
165 |
166 | #### Additions
167 |
168 | - Add native dependencies check on postinstall
169 | - Updated all dependencies to latest semver
170 |
171 | # 0.12.0 (2017.7.8)
172 |
173 | #### Misc
174 |
175 | - Removed `babel-polyfill`
176 | - Renamed and alphabetized npm scripts
177 |
178 | #### Breaking
179 |
180 | - Changed node dev `__dirname` and `__filename` to node built in fn's (https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/1035)
181 | - Renamed `src/bundle.js` to `src/renderer.prod.js` for consistency
182 | - Renamed `dll/vendor.js` to `dll/renderer.dev.dll.js` for consistency
183 |
184 | #### Additions
185 |
186 | - Enable node_modules cache on CI
187 |
188 | # 0.11.2 (2017.5.1)
189 |
190 | Yay! Another patch release. This release mostly includes refactorings and router bug fixes. Huge thanks to @anthonyraymond!
191 |
192 | ⚠️ Windows electron builds are failing because of [this issue](https://github.com/electron/electron/issues/9321). This is not an issue with the boilerplate ⚠️
193 |
194 | #### Breaking
195 |
196 | - **Renamed `./src/main.development.js` => `./src/main.{dev,prod}.js`:** [#963](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/963)
197 |
198 | #### Fixes
199 |
200 | - **Fixed reloading when not on `/` path:** [#958](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/958) [#949](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/949)
201 |
202 | #### Additions
203 |
204 | - **Added support for stylefmt:** [#960](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/960)
205 |
206 | # 0.11.1 (2017.4.23)
207 |
208 | You can now debug the production build with devtools like so:
209 |
210 | ```
211 | DEBUG_PROD=true npm run package
212 | ```
213 |
214 | 🎉🎉🎉
215 |
216 | #### Additions
217 |
218 | - **Added support for debugging production build:** [#fab245a](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/941/commits/fab245a077d02a09630f74270806c0c534a4ff95)
219 |
220 | #### Bug Fixes
221 |
222 | - **Fixed bug related to importing native dependencies:** [#933](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/933)
223 |
224 | #### Improvements
225 |
226 | - **Updated all deps to latest semver**
227 |
228 | # 0.11.0 (2017.4.19)
229 |
230 | Here's the most notable changes since `v0.10.0`. Its been about a year since a release has been pushed. Expect a new release to be published every 3-4 weeks.
231 |
232 | #### Breaking Changes
233 |
234 | - **Dropped support for node < 6**
235 | - **Refactored webpack config files**
236 | - **Migrate to two-package.json project structure**
237 | - **Updated all devDeps to latest semver**
238 | - **Migrated to Jest:** [#768](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/768)
239 | - **Migrated to `react-router@4`**
240 | - **Migrated to `electron-builder@4`**
241 | - **Migrated to `webpack@2`**
242 | - **Migrated to `react-hot-loader@3`**
243 | - **Changed default live reload server PORT to `1212` from `3000`**
244 |
245 | #### Additions
246 |
247 | - **Added support for Yarn:** [#451](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/451)
248 | - **Added support for Flow:** [#425](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/425)
249 | - **Added support for stylelint:** [#911](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/911)
250 | - **Added support for electron-builder:** [#876](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/876)
251 | - **Added optional support for SASS:** [#880](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/880)
252 | - **Added support for eslint-plugin-flowtype:** [#911](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/911)
253 | - **Added support for appveyor:** [#280](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/280)
254 | - **Added support for webpack dlls:** [#860](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/860)
255 | - **Route based code splitting:** [#884](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/884)
256 | - **Added support for Webpack Bundle Analyzer:** [#922](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/922)
257 |
258 | #### Improvements
259 |
260 | - **Parallelize renderer and main build processes when running `npm run build`**
261 | - **Dynamically generate electron app menu**
262 | - **Improved vscode integration:** [#856](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/856)
263 |
264 | #### Bug Fixes
265 |
266 | - **Fixed hot module replacement race condition bug:** [#917](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/917) [#920](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/920)
267 |
268 | # 0.10.0 (2016.4.18)
269 |
270 | #### Improvements
271 |
272 | - **Use Babel in main process with Webpack build:** [#201](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/201)
273 | - **Change targets to built-in support by webpack:** [#197](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/197)
274 | - **use es2015 syntax for webpack configs:** [#195](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/195)
275 | - **Open application when webcontent is loaded:** [#192](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/192)
276 | - **Upgraded dependencies**
277 |
278 | #### Bug fixed
279 |
280 | - **Fix `npm list electron-prebuilt` in package.js:** [#188](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/188)
281 |
282 | # 0.9.0 (2016.3.23)
283 |
284 | #### Improvements
285 |
286 | - **Added [redux-logger](https://github.com/fcomb/redux-logger)**
287 | - **Upgraded [react-router-redux](https://github.com/reactjs/react-router-redux) to v4**
288 | - **Upgraded dependencies**
289 | - **Added `npm run dev` command:** [#162](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/162)
290 | - **electron to v0.37.2**
291 |
292 | #### Breaking Changes
293 |
294 | - **css module as default:** [#154](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/154).
295 | - **set default NODE_ENV to production:** [#140](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/140)
296 |
297 | # 0.8.0 (2016.2.17)
298 |
299 | #### Bug fixed
300 |
301 | - **Fix lint errors**
302 | - **Fix Webpack publicPath for production builds**: [#119](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/119).
303 | - **package script now chooses correct OS icon extension**
304 |
305 | #### Improvements
306 |
307 | - **babel 6**
308 | - **Upgrade Dependencies**
309 | - **Enable CSS source maps**
310 | - **Add json-loader**: [#128](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/128).
311 | - **react-router 2.0 and react-router-redux 3.0**
312 |
313 | # 0.7.1 (2015.12.27)
314 |
315 | #### Bug fixed
316 |
317 | - **Fixed npm script on windows 10:** [#103](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/103).
318 | - **history and react-router version bump**: [#109](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/109), [#110](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/110).
319 |
320 | #### Improvements
321 |
322 | - **electron 0.36**
323 |
324 | # 0.7.0 (2015.12.16)
325 |
326 | #### Bug fixed
327 |
328 | - **Fixed process.env.NODE_ENV variable in webpack:** [#74](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/74).
329 | - **add missing object-assign**: [#76](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/76).
330 | - **packaging in npm@3:** [#77](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/77).
331 | - **compatibility in windows:** [#100](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/100).
332 | - **disable chrome debugger in production env:** [#102](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/102).
333 |
334 | #### Improvements
335 |
336 | - **redux**
337 | - **css-modules**
338 | - **upgrade to react-router 1.x**
339 | - **unit tests**
340 | - **e2e tests**
341 | - **travis-ci**
342 | - **upgrade to electron 0.35.x**
343 | - **use es2015**
344 | - **check dev engine for node and npm**
345 |
346 | # 0.6.5 (2015.11.7)
347 |
348 | #### Improvements
349 |
350 | - **Bump style-loader to 0.13**
351 | - **Bump css-loader to 0.22**
352 |
353 | # 0.6.4 (2015.10.27)
354 |
355 | #### Improvements
356 |
357 | - **Bump electron-debug to 0.3**
358 |
359 | # 0.6.3 (2015.10.26)
360 |
361 | #### Improvements
362 |
363 | - **Initialize ExtractTextPlugin once:** [#64](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/64).
364 |
365 | # 0.6.2 (2015.10.18)
366 |
367 | #### Bug fixed
368 |
369 | - **Babel plugins production env not be set properly:** [#57](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/57).
370 |
371 | # 0.6.1 (2015.10.17)
372 |
373 | #### Improvements
374 |
375 | - **Bump electron to v0.34.0**
376 |
377 | # 0.6.0 (2015.10.16)
378 |
379 | #### Breaking Changes
380 |
381 | - **From react-hot-loader to react-transform**
382 |
383 | # 0.5.2 (2015.10.15)
384 |
385 | #### Improvements
386 |
387 | - **Run tests with babel-register:** [#29](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/29).
388 |
389 | # 0.5.1 (2015.10.12)
390 |
391 | #### Bug fixed
392 |
393 | - **Fix #51:** use `path.join(__dirname` instead of `./`.
394 |
395 | # 0.5.0 (2015.10.11)
396 |
397 | #### Improvements
398 |
399 | - **Simplify webpack config** see [#50](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/50).
400 |
401 | #### Breaking Changes
402 |
403 | - **webpack configs**
404 | - **port changed:** changed default port from 2992 to 3000.
405 | - **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`.
406 |
407 | # 0.4.3 (2015.9.22)
408 |
409 | #### Bug fixed
410 |
411 | - **Fix #45 zeromq crash:** bump version of `electron-prebuilt`.
412 |
413 | # 0.4.2 (2015.9.15)
414 |
415 | #### Bug fixed
416 |
417 | - **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1`
418 |
419 | # 0.4.1 (2015.9.11)
420 |
421 | #### Improvements
422 |
423 | - **use electron-prebuilt version for packaging (#33)**
424 |
425 | # 0.4.0 (2015.9.5)
426 |
427 | #### Improvements
428 |
429 | - **update dependencies**
430 |
431 | # 0.3.0 (2015.8.31)
432 |
433 | #### Improvements
434 |
435 | - **eslint-config-airbnb**
436 |
437 | # 0.2.10 (2015.8.27)
438 |
439 | #### Features
440 |
441 | - **custom placeholder icon**
442 |
443 | #### Improvements
444 |
445 | - **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer)
446 |
447 | # 0.2.9 (2015.8.18)
448 |
449 | #### Bug fixed
450 |
451 | - **Fix hot-reload**
452 |
453 | # 0.2.8 (2015.8.13)
454 |
455 | #### Improvements
456 |
457 | - **bump electron-debug**
458 | - **babelrc**
459 | - **organize webpack scripts**
460 |
461 | # 0.2.7 (2015.7.9)
462 |
463 | #### Bug fixed
464 |
465 | - **defaultProps:** fix typos.
466 |
467 | # 0.2.6 (2015.7.3)
468 |
469 | #### Features
470 |
471 | - **menu**
472 |
473 | #### Bug fixed
474 |
475 | - **package.js:** include webpack build.
476 |
477 | # 0.2.5 (2015.7.1)
478 |
479 | #### Features
480 |
481 | - **NPM Script:** support multi-platform
482 | - **package:** `--all` option
483 |
484 | # 0.2.4 (2015.6.9)
485 |
486 | #### Bug fixed
487 |
488 | - **Eslint:** typo, [#17](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/17) and improve `.eslintrc`
489 |
490 | # 0.2.3 (2015.6.3)
491 |
492 | #### Features
493 |
494 | - **Package Version:** use latest release electron version as default
495 | - **Ignore Large peerDependencies**
496 |
497 | #### Bug fixed
498 |
499 | - **Npm Script:** typo, [#6](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/6)
500 | - **Missing css:** [#7](https://github.com/electron-react-boilerplate/electron-react-boilerplate/pull/7)
501 |
502 | # 0.2.2 (2015.6.2)
503 |
504 | #### Features
505 |
506 | - **electron-debug**
507 |
508 | #### Bug fixed
509 |
510 | - **Webpack:** add `.json` and `.node` to extensions for imitating node require.
511 | - **Webpack:** set `node_modules` to externals for native module support.
512 |
513 | # 0.2.1 (2015.5.30)
514 |
515 | #### Bug fixed
516 |
517 | - **Webpack:** #1, change build target to `atom`.
518 |
519 | # 0.2.0 (2015.5.30)
520 |
521 | #### Features
522 |
523 | - **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`.
524 | - **Support asar**
525 | - **Support icon**
526 |
527 | # 0.1.0 (2015.5.27)
528 |
529 | #### Features
530 |
531 | - **Webpack:** babel, react-hot, ...
532 | - **Flux:** actions, api, components, containers, stores..
533 | - **Package:** darwin (osx), linux and win32 (windows) platform.
534 |
--------------------------------------------------------------------------------
/g4l-ui/.erb/img/erb-banner.svg:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/commentedout.js:
--------------------------------------------------------------------------------
1 | const Max = require('max-api');
2 | const fs = require('fs');
3 | const io = require('socket.io-client');
4 |
5 | const https = require('https');
6 | const http = require('http');
7 |
8 | // =============================================================================
9 | // CONFIGURATION - Change these URLs when building from source with localhost
10 | // =============================================================================
11 |
12 | // For localhost development with our github repo https://github.com/betweentwomidnights/gary-backend-combined
13 | // Uncomment these lines and comment out the production URLs below:
14 | // const BACKEND_URL = 'http://localhost:8000';
15 | // const STABLE_AUDIO_HOST = 'localhost';
16 | // const STABLE_AUDIO_PORT = 8005;
17 | // const STABLE_AUDIO_PATH = '/generate'; // Direct to service
18 | // const USE_HTTPS = false;
19 |
20 | // Production URLs (default):
21 | const BACKEND_URL = 'https://g4l.thecollabagepatch.com';
22 | const STABLE_AUDIO_HOST = 'g4l.thecollabagepatch.com';
23 | const STABLE_AUDIO_PORT = 443;
24 | const STABLE_AUDIO_PATH = '/audio/generate'; // Through Caddy proxy
25 | const USE_HTTPS = true;
26 |
27 | // =============================================================================
28 |
29 | const socket = io(BACKEND_URL, {
30 | transports: ['websocket'], // Force WebSocket usage
31 | reconnection: true, // Enable auto-reconnection
32 | reconnectionAttempts: Infinity, // Unlimited reconnection attempts
33 | reconnectionDelay: 1000, // Wait 1 second before attempting to reconnect
34 | reconnectionDelayMax: 5000, // Maximum delay between reconnections
35 | randomizationFactor: 0.5,
36 | timeout: 300000, // Connection timeout in milliseconds
37 | pingTimeout: 240000, // How many ms without a pong packet to consider the connection closed
38 | pingInterval: 120000 // How many ms before sending a new ping packet
39 | });
40 | const path = require('path');
41 |
42 | // New state variables for stable-audio
43 | let currentBPM = 120; // Default BPM
44 | let stableAudioPrompt = "aggressive techno"; // Default prompt
45 | let isStableAudioProcessing = false;
46 |
47 | let stableCFG = 1.0; // Default CFG
48 | let stableSteps = 8; // Default steps
49 |
50 | // Handler to receive BPM from Live's transport
51 | Max.addHandler('set_bpm', (bpm) => {
52 | if (typeof bpm === 'number' && bpm > 0) {
53 | currentBPM = Math.round(bpm);
54 | Max.post(`BPM updated to: ${currentBPM}`);
55 | }
56 | });
57 |
58 | Max.addHandler('set_cfg', (cfg) => {
59 | if (typeof cfg === 'number' && cfg >= 0.5 && cfg <= 2.0) {
60 | // Quantize to nearest 0.1 increment
61 | stableCFG = Math.round(cfg * 10) / 10;
62 | Max.post(`Jerry CFG updated to: ${stableCFG} (from raw: ${cfg})`);
63 | }
64 | });
65 |
66 | // Handler for steps changes
67 | Max.addHandler('set_steps', (steps) => {
68 | if (typeof steps === 'number' && steps >= 4 && steps <= 16) {
69 | stableSteps = Math.round(steps); // Ensure integer
70 | Max.post(`Jerry steps updated to: ${stableSteps}`);
71 | }
72 | });
73 |
74 | // Handler to set the prompt text
75 | Max.addHandler('set_prompt', (prompt) => {
76 | if (typeof prompt === 'string') {
77 | // Remove 'text' prefix that textedit adds
78 | let cleanPrompt = prompt.trim();
79 | if (cleanPrompt.startsWith('text ')) {
80 | cleanPrompt = cleanPrompt.substring(5); // Remove 'text ' (5 characters)
81 | }
82 | stableAudioPrompt = cleanPrompt;
83 | Max.post(`Prompt updated to: ${stableAudioPrompt}`);
84 | }
85 | });
86 |
87 | // Handler for stable-audio generation
88 | Max.addHandler('generate_stable_audio', () => {
89 | if (isStableAudioProcessing) {
90 | Max.post('Stable audio generation already in progress...');
91 | return;
92 | }
93 |
94 | isStableAudioProcessing = true;
95 | Max.post(`Generating stable audio: "${stableAudioPrompt}" at ${currentBPM} BPM (CFG: ${stableCFG}, Steps: ${stableSteps})`);
96 |
97 | // Construct the full prompt with BPM
98 | const fullPrompt = `${stableAudioPrompt} ${currentBPM}bpm`;
99 |
100 | // Prepare the request payload with new parameters
101 | const requestData = JSON.stringify({
102 | prompt: fullPrompt,
103 | steps: stableSteps, // Use user-selected steps
104 | cfg_scale: stableCFG, // Use user-selected CFG
105 | return_format: "base64",
106 | seed: -1 // Random seed
107 | });
108 |
109 | // Configure the HTTP request using the configuration variables
110 | const options = {
111 | hostname: STABLE_AUDIO_HOST,
112 | port: STABLE_AUDIO_PORT,
113 | path: STABLE_AUDIO_PATH, // Use the configurable path
114 | method: 'POST',
115 | headers: {
116 | 'Content-Type': 'application/json',
117 | 'Content-Length': Buffer.byteLength(requestData)
118 | }
119 | };
120 |
121 | // Use the appropriate HTTP module based on configuration
122 | const httpModule = USE_HTTPS ? https : http;
123 |
124 | // Make the HTTP request
125 | const req = httpModule.request(options, (res) => {
126 | let responseData = '';
127 |
128 | res.on('data', (chunk) => {
129 | responseData += chunk;
130 | });
131 |
132 | res.on('end', () => {
133 | try {
134 | const response = JSON.parse(responseData);
135 |
136 | if (response.success && response.audio_base64) {
137 | // Convert base64 to buffer and save as myOutput.wav
138 | const audioBuffer = Buffer.from(response.audio_base64, 'base64');
139 | fs.writeFileSync('C:/g4l/myOutput.wav', audioBuffer);
140 |
141 | Max.post('Stable audio generation successful!');
142 | Max.post(`Generated: ${response.metadata.generation_time}s (${response.metadata.realtime_factor}x RT)`);
143 |
144 | // Notify Max that audio is ready
145 | Max.outlet('stable_audio_generated');
146 |
147 | // Clear any existing session since this is new audio
148 | sessionID = null;
149 |
150 | } else {
151 | Max.post(`Stable audio generation failed: ${response.error || 'Unknown error'}`);
152 | Max.outlet('error_message', `stable_audio|${response.error || 'Generation failed'}`);
153 | }
154 | } catch (error) {
155 | Max.post(`Error parsing stable audio response: ${error.message}`);
156 | Max.outlet('error_message', `stable_audio|Failed to parse response`);
157 | }
158 |
159 | isStableAudioProcessing = false;
160 | });
161 | });
162 |
163 | req.on('error', (error) => {
164 | Max.post(`Stable audio request error: ${error.message}`);
165 | Max.outlet('error_message', `stable_audio|${error.message}`);
166 | isStableAudioProcessing = false;
167 | });
168 |
169 | // Send the request
170 | req.write(requestData);
171 | req.end();
172 | });
173 |
174 |
175 |
176 | let modelPath = 'thepatch/vanya_ai_dnb_0.1'; // Default model path
177 | let sessionID = null; // Variable to store the session ID
178 | let isProcessing = false;
179 | let promptDuration = 6; // Default prompt duration
180 | let tameTheGaryEnabled = false; // New state variable for the toggle
181 | let variationName = 'accordion_folk'; // Default variation
182 |
183 | let isBackendConnected = false;
184 |
185 | let currentFlowstep = 0.13; // Default flowstep value
186 | let useMidpointSolver = false; // New state variable for the solver toggle
187 |
188 | let lastProgressUpdate = 0;
189 | let lastProgressTime = Date.now();
190 | let progressTimeout = null;
191 |
192 | Max.addHandler('set_solver', (value) => {
193 | useMidpointSolver = value === 1;
194 | Max.post(`Solver set to: ${useMidpointSolver ? 'midpoint' : 'euler (default)'}`);
195 | });
196 |
197 | Max.addHandler('set_flowstep', (value) => {
198 | // Convert the 0-127 range to 0.05-0.15 range
199 | if (typeof value === 'number') {
200 | currentFlowstep = 0.05 + (value / 127) * 0.10;
201 | currentFlowstep = Math.round(currentFlowstep * 1000) / 1000; // Round to 3 decimal places
202 | Max.post(`Flowstep updated to: ${currentFlowstep}`);
203 | }
204 | });
205 |
206 | // Add handler for variation text input
207 | Max.addHandler('set_variation', (newVariation) => {
208 | if (typeof newVariation === 'string') {
209 | variationName = newVariation.trim();
210 | Max.post(`Variation updated to: ${variationName}`);
211 | }
212 | });
213 |
214 | // Update transform handler to use the variationName variable
215 | Max.addHandler('transform', () => {
216 | if (!isProcessing) {
217 | isProcessing = true;
218 |
219 | const audioPath = sessionID ? 'C:/g4l/myOutput.wav' : 'C:/g4l/myBuffer.wav';
220 |
221 | // Add memory monitoring
222 | const memBefore = process.memoryUsage();
223 | Max.post(`Memory before transform: ${Math.round(memBefore.heapUsed / 1024 / 1024)}MB`);
224 |
225 | fs.readFile(audioPath, (err, data) => {
226 | if (err) {
227 | Max.post(`Error reading audio file: ${err}`);
228 | isProcessing = false;
229 | return;
230 | }
231 |
232 | try {
233 | // Check file size before processing
234 | const fileSizeMB = data.length / 1024 / 1024;
235 | Max.post(`Audio file size: ${fileSizeMB.toFixed(2)}MB`);
236 |
237 | if (fileSizeMB > 50) { // 50MB limit
238 | Max.post('Audio file too large for transform');
239 | isProcessing = false;
240 | return;
241 | }
242 |
243 | const audioData_base64 = data.toString('base64');
244 |
245 | const request = {
246 | audio_data: audioData_base64,
247 | variation: variationName,
248 | session_id: sessionID,
249 | flowstep: currentFlowstep
250 | };
251 |
252 | // Only add solver parameter if midpoint is selected
253 | if (useMidpointSolver) {
254 | request.solver = 'midpoint';
255 | Max.post('Using midpoint solver (faster, 64 steps)');
256 | } else {
257 | Max.post('Using default euler solver (higher quality, 25 steps)');
258 | }
259 |
260 | socket.emit('transform_audio_request', request);
261 |
262 | // Force garbage collection hint
263 | if (global.gc) {
264 | global.gc();
265 | }
266 |
267 | // Log memory after processing
268 | const memAfter = process.memoryUsage();
269 | Max.post(`Memory after transform: ${Math.round(memAfter.heapUsed / 1024 / 1024)}MB`);
270 |
271 | } catch (error) {
272 | Max.post(`Error processing transform: ${error.message}`);
273 | isProcessing = false;
274 | }
275 | });
276 |
277 | setTimeout(() => {
278 | isProcessing = false;
279 | }, timeoutDuration);
280 | } else {
281 | Max.post('Processing already in progress.');
282 | }
283 | });
284 |
285 | // Add handler for undo button
286 | Max.addHandler('undo_transform', () => {
287 | if (!isProcessing && sessionID) {
288 | isProcessing = true;
289 |
290 | const request = {
291 | session_id: sessionID
292 | };
293 |
294 | socket.emit('undo_transform_request', request);
295 |
296 | setTimeout(() => {
297 | isProcessing = false;
298 | }, timeoutDuration);
299 | } else {
300 | Max.post('Either processing in progress or no session available for undo.');
301 | }
302 | });
303 |
304 | Max.addHandler('tame_gary', (value) => {
305 | tameTheGaryEnabled = value === 1;
306 | Max.post(`Tame the Gary ${tameTheGaryEnabled ? 'enabled' : 'disabled'}`);
307 | });
308 |
309 | // Timeout duration in milliseconds
310 | const timeoutDuration = 500; // .5 seconds
311 |
312 | // Initialize WebSocket connection and setup event listeners
313 | function initSocketConnection() {
314 | socket.on('connect', () => {
315 | isBackendConnected = true;
316 | Max.post('Connected to WebSocket server.');
317 | Max.outlet('backend_connection', true);
318 | if (sessionID) {
319 | socket.emit('verify_session', { session_id: sessionID });
320 | }
321 | });
322 |
323 | // Add these socket listeners to handle queue events
324 | socket.on('queue_status', (data) => {
325 | // Always post the message
326 | Max.post(data.message);
327 |
328 | // Send queue metrics to Max outlets
329 | Max.outlet('queue_status', data.position, data.total_queued);
330 | Max.outlet('time_estimate', data.estimated_seconds);
331 |
332 | // Additional debug logging
333 | Max.post(`Debug - Position: ${data.position}, Total: ${data.total_queued}, Wait: ${data.estimated_seconds}s`);
334 | });
335 |
336 | socket.on('processing_started', (data) => {
337 | Max.post('Request is now being processed');
338 | // No need to change isProcessing, it should stay true
339 | });
340 |
341 | socket.on('queue_update', (data) => {
342 | Max.post(data.message);
343 | Max.outlet('queue_update', data.position, data.total_queued);
344 | if (data.estimated_seconds) {
345 | Max.outlet('time_update', data.estimated_seconds);
346 | }
347 | });
348 |
349 | socket.on('reconnect_attempt', () => {
350 | Max.post('Attempting to reconnect to WebSocket server...');
351 | });
352 |
353 | Max.addHandler('reconnect', () => {
354 | // Only send the connection status
355 | Max.outlet('backend_connection', true);
356 | // Reset any error states if needed
357 | Max.post('Reconnected and reset states');
358 | });
359 |
360 | socket.on('reconnect_error', (error) => {
361 | Max.post('Reconnection error: ' + error.message);
362 | });
363 |
364 | socket.on('reconnect_failed', () => {
365 | Max.post('Failed to reconnect to WebSocket server.');
366 | });
367 |
368 | socket.on('connect_error', (error) => {
369 | isBackendConnected = false;
370 | Max.post('Connection error: ' + error.message);
371 | Max.outlet('backend_connection', false); // Inform about lost backend connection
372 | });
373 |
374 | socket.on('disconnect', (reason) => {
375 | isBackendConnected = false;
376 | Max.post('Disconnected from WebSocket server: ' + reason);
377 | Max.outlet('backend_connection', false); // Inform about lost backend connection
378 | if (reason === 'io server disconnect') {
379 | socket.connect();
380 | }
381 | });
382 |
383 | socket.on('audio_processed', (data) => {
384 | isProcessing = false; // Reset flag when audio processing is complete
385 | Max.post('Audio processing successful.');
386 | sessionID = data.session_id; // Store the session ID
387 | const outputBuffer = Buffer.from(data.audio_data, 'base64');
388 | fs.writeFileSync('C:/g4l/myOutput.wav', outputBuffer);
389 | Max.outlet('audio_processed');
390 | Max.outlet('progress_update', 100);
391 | });
392 |
393 | socket.on('music_continued', (data) => {
394 | isProcessing = false; // Reset flag when audio processing is complete
395 | Max.post('Music continuation successful.');
396 | sessionID = data.session_id; // Update the session ID
397 | const outputBuffer = Buffer.from(data.audio_data, 'base64');
398 | fs.writeFileSync('C:/g4l/myOutput.wav', outputBuffer);
399 | Max.outlet('music_continued');
400 | Max.outlet('progress_update', 100); // Force the progress to 100% on completion
401 | });
402 |
403 | socket.on('music_retried', (data) => {
404 | isProcessing = false; // Reset flag when audio processing is complete
405 | Max.post('Music retry successful.');
406 | const outputBuffer = Buffer.from(data.audio_data, 'base64');
407 | fs.writeFileSync('C:/g4l/myOutput.wav', outputBuffer);
408 | Max.outlet('music_retried');
409 | Max.outlet('progress_update', 100); // Force the progress to 100% on completion
410 | });
411 |
412 | socket.on('progress_update', (data) => {
413 | clearTimeout(progressTimeout);
414 | lastProgressUpdate = data.progress;
415 | lastProgressTime = Date.now();
416 | Max.post(`progress update: ${data.progress}%`);
417 | Max.outlet('progress_update', data.progress);
418 |
419 | // Set timeout for next expected update
420 | progressTimeout = setTimeout(() => {
421 | if (Date.now() - lastProgressTime > 5000 && sessionID) { // Only if sessionID exists
422 | // Request progress status
423 | socket.emit('request_progress_status', {
424 | session_id: sessionID,
425 | last_progress: lastProgressUpdate
426 | });
427 | }
428 | }, 5000);
429 | });
430 |
431 | socket.on('error', (data) => {
432 | isProcessing = false;
433 | Max.post('Error from WebSocket server: ' + data.message);
434 |
435 | // Check if the error message includes the specific connection error
436 | if (data.message.includes('Connection refused') ||
437 | data.message.includes('Failed to establish a new connection')) {
438 | Max.outlet('error_message', 'transform|terry is asleep right now. he uses alot of gpu ram. you can learn how to spin up your own terry container by bugging kevin in the discord tho: https://discord.gg/VECkyXEnAd');
439 | } else {
440 | Max.outlet('error', data.message);
441 | }
442 | });
443 |
444 | socket.on('update_cropped_audio_complete', (data) => {
445 | sessionID = data.session_id; // Update the session ID
446 | Max.post('Cropped audio updated successfully.');
447 |
448 | // NEW: Forward session event to electron
449 | Max.outlet('update_cropped_audio_complete');
450 | });
451 |
452 | // Add new socket listener for transform response
453 | socket.on('audio_transformed', (data) => {
454 | isProcessing = false;
455 | Max.post('Audio transformation successful.');
456 | sessionID = data.session_id; // Update session ID
457 | const outputBuffer = Buffer.from(data.audio_data, 'base64');
458 | fs.writeFileSync('C:/g4l/myOutput.wav', outputBuffer);
459 | Max.outlet('audio_transformed');
460 | Max.outlet('progress_update', 100);
461 | });
462 | // Add socket listener for undo response
463 | socket.on('transform_undone', (data) => {
464 | isProcessing = false;
465 | Max.post('Transform undo successful.');
466 | sessionID = data.session_id;
467 | const outputBuffer = Buffer.from(data.audio_data, 'base64');
468 | fs.writeFileSync('C:/g4l/myOutput.wav', outputBuffer);
469 | Max.outlet('transform_undone');
470 | });
471 | }
472 |
473 | // Function to handle 'bang' message from Max
474 | Max.addHandler('bang', () => {
475 | if (!isProcessing) {
476 | isProcessing = true;
477 | // Optional: Send a cleanup request if sessionID exists
478 | if (sessionID) {
479 | socket.emit('cleanup_session_request', { session_id: sessionID });
480 | }
481 |
482 | Max.post('Sending audio processing request to WebSocket server with a new session.');
483 | processAudio('C:/g4l/myBuffer.wav');
484 | sessionID = null; // Reset the session ID after sending the cleanup request
485 |
486 | // Add timeout to reset isProcessing
487 | setTimeout(() => {
488 | isProcessing = false;
489 | }, timeoutDuration);
490 | } else {
491 | Max.post('Processing already in progress.');
492 | }
493 | });
494 |
495 | // Function to handle 'continue' message from Max
496 | Max.addHandler('continue', () => {
497 | if (isProcessing) {
498 | Max.outlet('error_message', 'processing|Still processing previous request, wait a sec...');
499 | Max.post('Processing already in progress.');
500 | return;
501 | }
502 |
503 | if (!sessionID) {
504 | // NEW: Check if we have output audio to continue from (Jerry case)
505 | const outputAudioPath = 'C:/g4l/myOutput.wav';
506 |
507 | if (fs.existsSync(outputAudioPath)) {
508 | Max.post('No session found, but output audio exists. Creating session and continuing...');
509 |
510 | // Read the output audio and send as new session
511 | fs.readFile(outputAudioPath, (err, data) => {
512 | if (err) {
513 | Max.post(`Error reading output file for continue: ${err}`);
514 | Max.outlet('error_message', 'no_session|no audio available to continue from.');
515 | return;
516 | }
517 |
518 | const audioData_base64 = data.toString('base64');
519 |
520 | const request = {
521 | audio_data: audioData_base64, // Send the Jerry audio
522 | model_name: modelPath,
523 | prompt_duration: promptDuration
524 | // No session_id - will be auto-created
525 | };
526 |
527 | // Add Gary-taming parameters if enabled
528 | if (tameTheGaryEnabled) {
529 | request.top_k = 150;
530 | request.cfg_coef = 5;
531 | request.description = "drums, percussion";
532 | }
533 |
534 | socket.emit('continue_music_request', request);
535 | isProcessing = true;
536 |
537 | setTimeout(() => {
538 | isProcessing = false;
539 | }, timeoutDuration);
540 | });
541 |
542 | return;
543 | } else {
544 | // No session and no output audio - show original error
545 | Max.outlet('error_message', 'no_session|no audio in the session to continue yet, homie. press bang first.');
546 | Max.post('No session available - need to press bang first.');
547 | return;
548 | }
549 | }
550 |
551 | // Existing logic for when session exists
552 | isProcessing = true;
553 | continueMusic();
554 |
555 | setTimeout(() => {
556 | isProcessing = false;
557 | }, timeoutDuration);
558 | });
559 |
560 | // Function to handle 'retry' message from Max
561 | Max.addHandler('retry', () => {
562 | if (isProcessing) {
563 | Max.outlet('error_message', 'processing|Still processing previous request, wait a sec...');
564 | Max.post('Processing already in progress.');
565 | return;
566 | }
567 |
568 | if (!sessionID) {
569 | // Using the same delimiter format as continue
570 | Max.outlet('error_message', 'no_continuation|no continued audio to retry bro. press continue first.');
571 | Max.post('No continued audio available - need to press continue first.');
572 | return;
573 | }
574 |
575 | isProcessing = true;
576 |
577 | const request = {
578 | session_id: sessionID,
579 | model_name: modelPath,
580 | prompt_duration: promptDuration
581 | };
582 |
583 | // Add Gary-taming parameters if enabled
584 | if (tameTheGaryEnabled) {
585 | request.top_k = 150;
586 | request.cfg_coef = 5;
587 | request.description = "drums, percussion";
588 | }
589 |
590 | socket.emit('retry_music_request', request);
591 |
592 | setTimeout(() => {
593 | isProcessing = false;
594 | }, timeoutDuration);
595 | });
596 |
597 | // This handler will directly update the model path when text changes
598 | Max.addHandler('text', (newModelPath) => {
599 | if (typeof newModelPath === 'string') {
600 | modelPath = newModelPath.trim(); // Ensure to trim any extra whitespace
601 | Max.post(`Model path updated directly to: ${modelPath}`);
602 | }
603 | });
604 |
605 | // Handler for receiving the prompt duration value
606 | Max.addHandler('prompt_duration', (value) => {
607 | if (typeof value === 'number' && value >= 1 && value <= 15) {
608 | promptDuration = value;
609 | Max.post(`Prompt duration set to: ${promptDuration}`);
610 | } else {
611 | Max.post('Invalid prompt duration value. It should be between 1 and 15.');
612 | }
613 | });
614 |
615 | // Handler for the 'crop_audio' event
616 | Max.addHandler('crop_audio', () => {
617 | const outputAudioPath = 'C:/g4l/myOutput.wav';
618 | fs.readFile(outputAudioPath, (err, data) => {
619 | if (err) {
620 | Max.post(`Error reading output file for cropping: ${err}`);
621 | return;
622 | }
623 | const audioData_base64 = data.toString('base64');
624 | socket.emit('update_cropped_audio', { session_id: sessionID, audio_data: audioData_base64 });
625 | Max.post('Sent cropped audio data to backend');
626 | });
627 | });
628 |
629 | // Add handler for reset_transform
630 | Max.addHandler('reset_transform', () => {
631 | Max.post('[commentedout] Reset transform handler triggered');
632 |
633 | // If we have an active session, clean it up
634 | if (sessionID) {
635 | Max.post('[commentedout] Cleaning up session: ' + sessionID);
636 | socket.emit('cleanup_session_request', { session_id: sessionID });
637 | sessionID = null;
638 | } else {
639 | Max.post('[commentedout] No active session to clean up');
640 | }
641 |
642 | Max.post('[commentedout] Reset complete');
643 | });
644 |
645 | // Function to process audio
646 | function processAudio(inputAudioPath) {
647 | fs.readFile(inputAudioPath, (err, data) => {
648 | if (err) {
649 | Max.post(`Error reading audio file: ${err}`);
650 | isProcessing = false;
651 | Max.outlet('error', err.toString());
652 | return;
653 | }
654 | const audioData_base64 = data.toString('base64');
655 |
656 | // Base request object
657 | const request = {
658 | audio_data: audioData_base64,
659 | model_name: modelPath,
660 | prompt_duration: promptDuration
661 | };
662 |
663 | // Add Gary-taming parameters if enabled
664 | if (tameTheGaryEnabled) {
665 | request.top_k = 100;
666 | request.cfg_coef = 5;
667 | request.description = "drums, percussion";
668 | }
669 |
670 | socket.emit('process_audio_request', request);
671 | });
672 | }
673 |
674 | // Function to continue music
675 | function continueMusic() {
676 | const outputAudioPath = 'C:/g4l/myOutput.wav';
677 | fs.readFile(outputAudioPath, (err, data) => {
678 | if (err) {
679 | Max.post(`Error reading output file: ${err}`);
680 | isProcessing = false;
681 | Max.outlet('error', err.toString());
682 | return;
683 | }
684 | const audioData_base64 = data.toString('base64');
685 |
686 | // Base request object
687 | const request = {
688 | audio_data: audioData_base64,
689 | model_name: modelPath,
690 | session_id: sessionID,
691 | prompt_duration: promptDuration
692 | };
693 |
694 | // Add Gary-taming parameters if enabled
695 | if (tameTheGaryEnabled) {
696 | request.top_k = 150;
697 | request.cfg_coef = 5;
698 | request.description = "drums, percussion";
699 | }
700 |
701 | socket.emit('continue_music_request', request);
702 | });
703 | }
704 |
705 | // Start the WebSocket connection
706 | initSocketConnection();
707 |
--------------------------------------------------------------------------------