refactor(stage-tamagotchi,stage-pages,stage-shared,stage-ui): migrate beat-sync to use broadcast channel-based transport
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import type { BrowserWindow } from 'electron'
|
||||
|
||||
import { env, platform } from 'node:process'
|
||||
|
||||
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||
@@ -79,12 +77,6 @@ app.whenReady().then(async () => {
|
||||
|
||||
// BeatSync will create a background window to capture and process audio.
|
||||
const beatSync = injeca.provide('windows:beat-sync', () => setupBeatSync())
|
||||
const beatSyncDispatchTo = injeca.provide('windows:beat-sync:dispatch-to', {
|
||||
dependsOn: { beatSync },
|
||||
build: async ({ dependsOn }) => (window: BrowserWindow) => {
|
||||
return dependsOn.beatSync.dispatchTo(window)
|
||||
},
|
||||
})
|
||||
|
||||
const chatWindow = injeca.provide('windows:chat', {
|
||||
dependsOn: { widgetsManager },
|
||||
@@ -92,17 +84,13 @@ app.whenReady().then(async () => {
|
||||
})
|
||||
|
||||
const settingsWindow = injeca.provide('windows:settings', {
|
||||
dependsOn: { widgetsManager, beatSync, beatSyncDispatchTo },
|
||||
build: async ({ dependsOn }) => {
|
||||
return setupSettingsWindowReusableFunc({ ...dependsOn, onWindowCreated: window => dependsOn.beatSyncDispatchTo(window) })
|
||||
},
|
||||
dependsOn: { widgetsManager, beatSync },
|
||||
build: async ({ dependsOn }) => setupSettingsWindowReusableFunc(dependsOn),
|
||||
})
|
||||
|
||||
const mainWindow = injeca.provide('windows:main', {
|
||||
dependsOn: { settingsWindow, chatWindow, widgetsManager, noticeWindow, beatSync, beatSyncDispatchTo },
|
||||
build: async ({ dependsOn }) => {
|
||||
return setupMainWindow({ ...dependsOn, onWindowCreated: window => dependsOn.beatSyncDispatchTo(window) })
|
||||
},
|
||||
dependsOn: { settingsWindow, chatWindow, widgetsManager, noticeWindow, beatSync },
|
||||
build: async ({ dependsOn }) => setupMainWindow(dependsOn),
|
||||
})
|
||||
|
||||
const captionWindow = injeca.provide('windows:caption', {
|
||||
|
||||
@@ -54,7 +54,7 @@ export function setupTray(params: {
|
||||
...is.dev || env.MAIN_APP_DEBUG || env.APP_DEBUG
|
||||
? [
|
||||
{ type: 'header', label: 'DevTools' },
|
||||
{ label: 'Troubleshoot BeatSync...', click: () => params.beatSyncBgWindow.window.webContents.openDevTools() },
|
||||
{ label: 'Troubleshoot BeatSync...', click: () => params.beatSyncBgWindow.webContents.openDevTools() },
|
||||
{ type: 'separator' },
|
||||
] as const // :(
|
||||
: [],
|
||||
|
||||
@@ -1,26 +1,8 @@
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { createContext } from '@moeru/eventa/adapters/electron/main'
|
||||
import {
|
||||
beatSyncBeatSignaledInvokeEventa,
|
||||
beatSyncGetInputByteFrequencyDataInvokeEventa,
|
||||
beatSyncGetStateInvokeEventa,
|
||||
beatSyncStateChangedInvokeEventa,
|
||||
beatSyncToggleInvokeEventa,
|
||||
beatSyncUpdateParametersInvokeEventa,
|
||||
} from '@proj-airi/stage-shared/beat-sync'
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import { BrowserWindow } from 'electron'
|
||||
|
||||
import {
|
||||
beatSyncElectronChangeState,
|
||||
beatSyncElectronGetInputByteFrequencyData,
|
||||
beatSyncElectronGetState,
|
||||
beatSyncElectronSignalBeat,
|
||||
beatSyncElectronToggle,
|
||||
beatSyncElectronUpdateParameters,
|
||||
} from '../../../shared/eventa'
|
||||
import { baseUrl, getElectronMainDirname, load } from '../../libs/electron/location'
|
||||
|
||||
export async function setupBeatSync() {
|
||||
@@ -31,37 +13,7 @@ export async function setupBeatSync() {
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
const context = createContext(ipcMain, window).context
|
||||
|
||||
// TODO(Makito): Bypass here with the broadcast channel-based transport when it is released.
|
||||
// [main] -> [renderer] beat-sync
|
||||
const toggle = defineInvoke(context, beatSyncElectronToggle) as (enabled: boolean) => Promise<void> // TODO: Better type
|
||||
const getState = defineInvoke(context, beatSyncElectronGetState)
|
||||
const updateParameters = defineInvoke(context, beatSyncElectronUpdateParameters)
|
||||
const getInputByteFrequencyData = defineInvoke(context, beatSyncElectronGetInputByteFrequencyData)
|
||||
|
||||
await load(window, baseUrl(resolve(getElectronMainDirname(), '..', 'renderer'), 'beat-sync.html'))
|
||||
return {
|
||||
window,
|
||||
dispatchTo: (window: BrowserWindow) => {
|
||||
const context = createContext(ipcMain, window).context
|
||||
|
||||
const stateChanged = defineInvoke(context, beatSyncStateChangedInvokeEventa)
|
||||
const beatSignaled = defineInvoke(context, beatSyncBeatSignaledInvokeEventa)
|
||||
const removeHandlerFns = [
|
||||
// [renderer] beat-sync -> [main] -> [renderer] index (Events)
|
||||
defineInvokeHandler(context, beatSyncElectronChangeState, async e => stateChanged(e)),
|
||||
defineInvokeHandler(context, beatSyncElectronSignalBeat, async e => beatSignaled(e)),
|
||||
|
||||
// [renderer] index -> [main] -> [renderer] beat-sync (Functions)
|
||||
defineInvokeHandler(context, beatSyncToggleInvokeEventa, async enabled => toggle(enabled)),
|
||||
defineInvokeHandler(context, beatSyncGetStateInvokeEventa, async () => getState()),
|
||||
defineInvokeHandler(context, beatSyncUpdateParametersInvokeEventa, async params => updateParameters(params)),
|
||||
defineInvokeHandler(context, beatSyncGetInputByteFrequencyDataInvokeEventa, async () => getInputByteFrequencyData()),
|
||||
]
|
||||
const removeHandlers = () => removeHandlerFns.forEach(fn => fn())
|
||||
window.on('closed', () => removeHandlers())
|
||||
return removeHandlers
|
||||
},
|
||||
}
|
||||
return window
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { createContext } from '@moeru/eventa/adapters/electron/renderer'
|
||||
import { StageEnvironment } from '@proj-airi/stage-shared'
|
||||
import { createBeatSyncDetector } from '@proj-airi/stage-shared/beat-sync/browser'
|
||||
|
||||
import {
|
||||
beatSyncElectronChangeState,
|
||||
beatSyncElectronGetInputByteFrequencyData,
|
||||
beatSyncElectronGetState,
|
||||
beatSyncElectronSignalBeat,
|
||||
beatSyncElectronToggle,
|
||||
beatSyncElectronUpdateParameters,
|
||||
} from '../shared/eventa'
|
||||
beatSyncBeatSignaledInvokeEventa,
|
||||
beatSyncGetInputByteFrequencyDataInvokeEventa,
|
||||
beatSyncGetStateInvokeEventa,
|
||||
beatSyncStateChangedInvokeEventa,
|
||||
beatSyncToggleInvokeEventa,
|
||||
beatSyncUpdateParametersInvokeEventa,
|
||||
createBeatSyncDetector,
|
||||
createContext,
|
||||
} from '@proj-airi/stage-shared/beat-sync'
|
||||
|
||||
const { ipcRenderer } = window.electron
|
||||
|
||||
const context = createContext(ipcRenderer).context
|
||||
const context = createContext()
|
||||
|
||||
// [renderer] beat-sync -> [main] -> [renderer] index
|
||||
const changeState = defineInvoke(context, beatSyncElectronChangeState)
|
||||
const signalBeat = defineInvoke(context, beatSyncElectronSignalBeat)
|
||||
const changeState = defineInvoke(context, beatSyncStateChangedInvokeEventa)
|
||||
const signalBeat = defineInvoke(context, beatSyncBeatSignaledInvokeEventa)
|
||||
|
||||
function enableLoopbackAudio() {
|
||||
// electron-audio-loopback currently registers this handler internally
|
||||
@@ -43,7 +41,7 @@ detector.on('beat', (e) => {
|
||||
signalBeat(e)
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, beatSyncElectronToggle, async (enabled) => {
|
||||
defineInvokeHandler(context, beatSyncToggleInvokeEventa, async (enabled) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[toggle]', enabled)
|
||||
if (enabled) {
|
||||
@@ -53,13 +51,13 @@ defineInvokeHandler(context, beatSyncElectronToggle, async (enabled) => {
|
||||
detector.stop()
|
||||
}
|
||||
})
|
||||
defineInvokeHandler(context, beatSyncElectronGetState, async () => detector.state)
|
||||
defineInvokeHandler(context, beatSyncElectronUpdateParameters, async (params) => {
|
||||
defineInvokeHandler(context, beatSyncGetStateInvokeEventa, async () => detector.state)
|
||||
defineInvokeHandler(context, beatSyncUpdateParametersInvokeEventa, async (params) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[update-params]', params)
|
||||
detector.updateParameters(params)
|
||||
})
|
||||
defineInvokeHandler(context, beatSyncElectronGetInputByteFrequencyData, async () => {
|
||||
defineInvokeHandler(context, beatSyncGetInputByteFrequencyDataInvokeEventa, async () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('[get-input-byte-frequency-data]') // This could be noisy.
|
||||
return detector.getInputByteFrequencyData()
|
||||
|
||||
@@ -74,11 +74,4 @@ export const widgetsRemoveEvent = defineEventa<{ id: string }>('eventa:event:ele
|
||||
export const widgetsClearEvent = defineEventa('eventa:event:electron:windows:widgets:clear')
|
||||
export const widgetsUpdateEvent = defineEventa<{ id: string, componentProps?: Record<string, any> }>('eventa:event:electron:windows:widgets:update')
|
||||
|
||||
export const beatSyncElectronSignalBeat = defineInvokeEventa<void, AnalyserBeatEvent>('eventa:event:electron:beat-sync:electron:signal-beat')
|
||||
export const beatSyncElectronChangeState = defineInvokeEventa<void, BeatSyncDetectorState>('eventa:event:electron:beat-sync:electron:change-state')
|
||||
export const beatSyncElectronGetState = defineInvokeEventa<BeatSyncDetectorState>('eventa:event:electron:beat-sync:electron:get-state')
|
||||
export const beatSyncElectronToggle = defineInvokeEventa<void, boolean>('eventa:event:electron:beat-sync:electron:toggle')
|
||||
export const beatSyncElectronUpdateParameters = defineInvokeEventa<void, Partial<AnalyserWorkletParameters>>('eventa:event:electron:beat-sync:electron:update-parameters')
|
||||
export const beatSyncElectronGetInputByteFrequencyData = defineInvokeEventa<Uint8Array<ArrayBuffer>>('eventa:event:electron:beat-sync:electron:get-input-byte-frequency-data')
|
||||
|
||||
export { electron } from './electron'
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
listenBeatSyncStateChange,
|
||||
toggleBeatSync,
|
||||
updateBeatSyncParameters,
|
||||
} from '@proj-airi/stage-shared/beat-sync/browser'
|
||||
} from '@proj-airi/stage-shared/beat-sync'
|
||||
import { Alert, AudioSpectrumVisualizer } from '@proj-airi/stage-ui/components'
|
||||
import { Button, FieldCheckbox, FieldRange } from '@proj-airi/ui'
|
||||
import { createTimeline } from 'animejs'
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./beat-sync": "./src/beat-sync/index.ts",
|
||||
"./beat-sync/browser": "./src/beat-sync/browser/index.ts",
|
||||
"./electron-renderer": "./src/electron-renderer.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { createContext as createWebContext } from '@moeru/eventa'
|
||||
import { createContext as createElectronRendererContext } from '@moeru/eventa/adapters/electron/renderer'
|
||||
import { isElectronWindow } from '@proj-airi/stage-shared'
|
||||
|
||||
export function createContext() {
|
||||
if (isElectronWindow(window)) {
|
||||
// TODO(Makito): Replace this with the broadcast channel-based transport when it is released.
|
||||
return createElectronRendererContext(window.electron.ipcRenderer).context
|
||||
}
|
||||
else {
|
||||
return createWebContext()
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './detector'
|
||||
export * from './eventa'
|
||||
+3
-5
@@ -1,14 +1,14 @@
|
||||
import type { EventContext } from '@moeru/eventa'
|
||||
import type { Analyser, AnalyserBeatEvent, AnalyserWorkletParameters } from '@nekopaw/tempora'
|
||||
|
||||
import type { BeatSyncDetectorEventMap, BeatSyncDetectorState } from '../types'
|
||||
import type { BeatSyncDetectorEventMap, BeatSyncDetectorState } from './types'
|
||||
|
||||
import analyserWorklet from '@nekopaw/tempora/worklet?url'
|
||||
|
||||
import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { startAnalyser as startTemporaAnalyser } from '@nekopaw/tempora'
|
||||
|
||||
import { isStageTamagotchi, isStageWeb, StageEnvironment } from '../../environment'
|
||||
import { isStageTamagotchi, isStageWeb, StageEnvironment } from '../environment'
|
||||
import {
|
||||
beatSyncBeatSignaledInvokeEventa,
|
||||
beatSyncGetInputByteFrequencyDataInvokeEventa,
|
||||
@@ -16,8 +16,6 @@ import {
|
||||
beatSyncStateChangedInvokeEventa,
|
||||
beatSyncToggleInvokeEventa,
|
||||
beatSyncUpdateParametersInvokeEventa,
|
||||
} from '../eventa'
|
||||
import {
|
||||
createContext,
|
||||
} from './eventa'
|
||||
|
||||
@@ -260,7 +258,7 @@ export function toggleBeatSync(enabled: boolean) {
|
||||
}
|
||||
|
||||
if (isStageTamagotchi()) {
|
||||
const toggleFn = defineInvoke(getContext(), beatSyncToggleInvokeEventa) as (enabled: boolean) => Promise<void> // TODO: Better type
|
||||
const toggleFn = defineInvoke(getContext(), beatSyncToggleInvokeEventa)
|
||||
return toggleFn(enabled)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import type { AnalyserBeatEvent, AnalyserWorkletParameters } from '@nekopaw/temp
|
||||
|
||||
import type { BeatSyncDetectorState } from './types'
|
||||
|
||||
import { defineInvokeEventa } from '@moeru/eventa'
|
||||
import { createContext as createWebContext, defineInvokeEventa } from '@moeru/eventa'
|
||||
import { createContext as createBroadcastChannelContext } from '@moeru/eventa/adapters/broadcast-channel'
|
||||
import { isElectronWindow } from '@proj-airi/stage-shared'
|
||||
|
||||
// Functions
|
||||
export const beatSyncToggleInvokeEventa = defineInvokeEventa<void, boolean>('eventa:invoke:electron:beat-sync:toggle')
|
||||
@@ -13,3 +15,22 @@ export const beatSyncGetInputByteFrequencyDataInvokeEventa = defineInvokeEventa<
|
||||
// Events
|
||||
export const beatSyncStateChangedInvokeEventa = defineInvokeEventa<void, BeatSyncDetectorState>('eventa:event:electron:beat-sync:state-changed')
|
||||
export const beatSyncBeatSignaledInvokeEventa = defineInvokeEventa<void, AnalyserBeatEvent>('eventa:event:electron:beat-sync:beat-signaled')
|
||||
|
||||
let _broadcastChannel: BroadcastChannel | undefined
|
||||
function getBroadcastChannel() {
|
||||
if (!_broadcastChannel) {
|
||||
_broadcastChannel = new BroadcastChannel('airi::beat-sync')
|
||||
_broadcastChannel.onmessage = (event) => {
|
||||
}
|
||||
}
|
||||
return _broadcastChannel
|
||||
}
|
||||
|
||||
export function createContext() {
|
||||
if (isElectronWindow(window)) {
|
||||
return createBroadcastChannelContext(getBroadcastChannel()).context
|
||||
}
|
||||
else {
|
||||
return createWebContext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './detector'
|
||||
export * from './eventa'
|
||||
export * from './types'
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Application } from '@pixi/app'
|
||||
|
||||
import type { PixiLive2DInternalModel } from '../../../composables/live2d'
|
||||
|
||||
import { listenBeatSyncBeatSignal } from '@proj-airi/stage-shared/beat-sync/browser'
|
||||
import { listenBeatSyncBeatSignal } from '@proj-airi/stage-shared/beat-sync'
|
||||
import { useTheme } from '@proj-airi/ui'
|
||||
import { breakpointsTailwind, until, useBreakpoints, useDebounceFn } from '@vueuse/core'
|
||||
import { formatHex } from 'culori'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BeatSyncDetectorState } from '@proj-airi/stage-shared/beat-sync'
|
||||
|
||||
import { getBeatSyncState, listenBeatSyncStateChange } from '@proj-airi/stage-shared/beat-sync/browser'
|
||||
import { getBeatSyncState, listenBeatSyncStateChange } from '@proj-airi/stage-shared/beat-sync'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user