diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index 46253096c..26efd6e71 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -134,6 +134,7 @@ "@iconify-json/vscode-icons": "^1.2.34", "@iconify/utils": "^3.0.2", "@intlify/unplugin-vue-i18n": "^11.0.1", + "@nekopaw/tempora": "0.3.1-alpha.1", "@proj-airi/lobe-icons": "^1.0.14", "@proj-airi/stage-shared": "workspace:^", "@proj-airi/ui-transitions": "workspace:^", diff --git a/apps/stage-tamagotchi/src/main/windows/beat-sync/index.ts b/apps/stage-tamagotchi/src/main/windows/beat-sync/index.ts index 1f44fdec1..bde0f3fb3 100644 --- a/apps/stage-tamagotchi/src/main/windows/beat-sync/index.ts +++ b/apps/stage-tamagotchi/src/main/windows/beat-sync/index.ts @@ -3,9 +3,22 @@ import { fileURLToPath } from 'node:url' import { defineInvoke, defineInvokeHandler } from '@moeru/eventa' import { createContext } from '@moeru/eventa/adapters/electron/main' -import { beatSyncRequestSignalBeat, beatSyncSignalBeat } from '@proj-airi/stage-shared/beat-sync/eventa' +import { + beatSyncBeatSignaledInvokeEventa, + beatSyncGetStateInvokeEventa, + beatSyncStateChangedInvokeEventa, + beatSyncToggleInvokeEventa, + beatSyncUpdateParametersInvokeEventa, +} from '@proj-airi/stage-shared/beat-sync' import { BrowserWindow, ipcMain } from 'electron' +import { + beatSyncElectronChangeState, + beatSyncElectronGetState, + beatSyncElectronSignalBeat, + beatSyncElectronToggle, + beatSyncElectronUpdateParameters, +} from '../../../shared/eventa' import { baseUrl, getElectronMainDirname, load } from '../../libs/electron/location' export async function setupBeatSync() { @@ -16,16 +29,35 @@ export async function setupBeatSync() { sandbox: false, }, }) + const context = createContext(ipcMain, window).context + + // TODO(Makito): Revisit and improve + // [main] -> [renderer] beat-sync + const toggle = defineInvoke(context, beatSyncElectronToggle) as (enabled: boolean) => Promise // TODO: Better type + const getState = defineInvoke(context, beatSyncElectronGetState) + const updateParameters = defineInvoke(context, beatSyncElectronUpdateParameters) await load(window, baseUrl(resolve(getElectronMainDirname(), '..', 'renderer'), 'beat-sync.html')) return { window, dispatchTo: (window: BrowserWindow) => { const context = createContext(ipcMain, window).context - const signalBeat = defineInvoke(context, beatSyncSignalBeat) - const removeHandler = defineInvokeHandler(context, beatSyncRequestSignalBeat, async e => signalBeat(e)) - window.on('closed', () => removeHandler()) - return removeHandler + + 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)), + ] + const removeHandlers = () => removeHandlerFns.forEach(fn => fn()) + window.on('closed', () => removeHandlers()) + return removeHandlers }, } } diff --git a/apps/stage-tamagotchi/src/renderer/beat-sync.main.ts b/apps/stage-tamagotchi/src/renderer/beat-sync.main.ts index d4cd24a65..6f6a9603c 100644 --- a/apps/stage-tamagotchi/src/renderer/beat-sync.main.ts +++ b/apps/stage-tamagotchi/src/renderer/beat-sync.main.ts @@ -1,12 +1,23 @@ import { defineInvoke, defineInvokeHandler } from '@moeru/eventa' import { createContext } from '@moeru/eventa/adapters/electron/renderer' -import { createBeatSyncDetector, StageEnvironment } from '@proj-airi/stage-shared' -import { beatSyncRequestSignalBeat, beatSyncToggle } from '@proj-airi/stage-shared/beat-sync/eventa' +import { StageEnvironment } from '@proj-airi/stage-shared' +import { createBeatSyncDetector } from '@proj-airi/stage-shared/beat-sync/browser' + +import { + beatSyncElectronChangeState, + beatSyncElectronGetState, + beatSyncElectronSignalBeat, + beatSyncElectronToggle, + beatSyncElectronUpdateParameters, +} from '../shared/eventa' const { ipcRenderer } = window.electron const context = createContext(ipcRenderer).context -const requestSignalBeat = defineInvoke(context, beatSyncRequestSignalBeat) + +// [renderer] beat-sync -> [main] -> [renderer] index +const changeState = defineInvoke(context, beatSyncElectronChangeState) +const signalBeat = defineInvoke(context, beatSyncElectronSignalBeat) function enableLoopbackAudio() { // electron-audio-loopback currently registers this handler internally @@ -24,9 +35,16 @@ const detector = createBeatSyncDetector({ disableLoopbackAudio, }) -detector.on('beat', e => requestSignalBeat(e)) +detector.on('stateChange', state => changeState(state)) +detector.on('beat', (e) => { + // eslint-disable-next-line no-console + console.debug('[beat]', e) // This could be noisy. + signalBeat(e) +}) -defineInvokeHandler(context, beatSyncToggle, async (enabled) => { +defineInvokeHandler(context, beatSyncElectronToggle, async (enabled) => { + // eslint-disable-next-line no-console + console.log('[toggle]', enabled) if (enabled) { detector.startScreenCapture() } @@ -34,3 +52,9 @@ defineInvokeHandler(context, beatSyncToggle, async (enabled) => { detector.stop() } }) +defineInvokeHandler(context, beatSyncElectronGetState, async () => detector.state) +defineInvokeHandler(context, beatSyncElectronUpdateParameters, async (params) => { + // eslint-disable-next-line no-console + console.log('[update-params]', params) + detector.updateParameters(params) +}) diff --git a/apps/stage-tamagotchi/src/shared/eventa.ts b/apps/stage-tamagotchi/src/shared/eventa.ts index ea5379384..055d0aa7f 100644 --- a/apps/stage-tamagotchi/src/shared/eventa.ts +++ b/apps/stage-tamagotchi/src/shared/eventa.ts @@ -1,3 +1,6 @@ +import type { AnalyserBeatEvent, AnalyserWorkletParameters } from '@nekopaw/tempora' +import type { BeatSyncDetectorState } from '@proj-airi/stage-shared/beat-sync' + import { defineEventa, defineInvokeEventa } from '@moeru/eventa' export const electronStartTrackMousePosition = defineInvokeEventa('eventa:invoke:electron:start-tracking-mouse-position') @@ -71,4 +74,10 @@ 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 }>('eventa:event:electron:windows:widgets:update') +export const beatSyncElectronSignalBeat = defineInvokeEventa('eventa:event:electron:beat-sync:electron:signal-beat') +export const beatSyncElectronChangeState = defineInvokeEventa('eventa:event:electron:beat-sync:electron:change-state') +export const beatSyncElectronGetState = defineInvokeEventa('eventa:event:electron:beat-sync:electron:get-state') +export const beatSyncElectronToggle = defineInvokeEventa('eventa:event:electron:beat-sync:electron:toggle') +export const beatSyncElectronUpdateParameters = defineInvokeEventa>('eventa:event:electron:beat-sync:electron:update-parameters') + export { electron } from './electron' diff --git a/packages/stage-pages/package.json b/packages/stage-pages/package.json index ad97d80b0..547460628 100644 --- a/packages/stage-pages/package.json +++ b/packages/stage-pages/package.json @@ -45,6 +45,7 @@ "vue-router": "^4.6.3" }, "devDependencies": { + "@proj-airi/stage-shared": "workspace:^", "@types/three": "^0.181.0", "vue-tsc": "^3.1.4" } diff --git a/packages/stage-pages/src/pages/settings/modules/beat-sync.vue b/packages/stage-pages/src/pages/settings/modules/beat-sync.vue index c580f63da..412604f95 100644 --- a/packages/stage-pages/src/pages/settings/modules/beat-sync.vue +++ b/packages/stage-pages/src/pages/settings/modules/beat-sync.vue @@ -1,16 +1,24 @@