Architecture Overview 架构概览
SXSEditor is a multi-window Electron application. The main process owns all heavy resources (ONNX sessions, audio output, file system), while renderer processes run isolated UI on top of an HTML5 Canvas. All cross-process communication is funneled through a single preload-exposed bridge.
SXSEditor 是一个多窗口 Electron 应用。主进程持有所有重量级资源(ONNX 会话、音频输出、文件系统),渲染进程在隔离的 HTML5 Canvas 之上运行 UI。所有跨进程通信都通过单一的 preload 桥接进行。
Process Model at a Glance 进程模型一览
┌─────────────────────────────── Main Process (src/main.js) ───────────────────────────────┐
│ windowManager modelDownload svsIpc pitchMidiIpc │
│ singerIpc audioIpc dialogIpc resourceManagerIpc │
│ settingsIpc themeIpc webnnIpc splashManager │
│ security.js settings.js gpuInfo.js modelDir.js locale.js │
│ │
│ OnnxSVSPipeline ──► onnxruntime-node (DirectML / CPU) │
│ WebNN requests ──► forwarded to main window renderer via ipcMain ↔ ipcRenderer │
└──────────────────────────────────────────────────────────────────────────────────────────┘
▲ IPC (ipcMain.handle / ipcRenderer.invoke) ▼
│ │
┌── Preload (src/preload.js) ──── contextBridge.exposeInMainWorld('electronAPI', {...}) ───┐
│ All renderer code can ONLY touch window.electronAPI — no Node.js, no require. │
└───────────────────────────────────────────────────────────────────────────────────────────┘
▲ ▼
┌──────────────── Renderer Processes (one per window) ──────────────────────────────────────┐
│ main_window (src/renderer/) Multi-track timeline, project management │
│ fragment_editor (src/fragmentEditor/) Piano-roll editor, SVS preview │
│ singer_creator (src/singerCreator.js) Singer creation wizard │
│ singer_market (src/singerMarket.js) Singer marketplace UI │
│ audio_preprocess (src/audioPreprocess/) F0 / MIDI extraction UI │
│ settings (src/settings.js) Device, inference, audio config │
│ model_download (src/modelDownload.js) Chunked parallel download from ModelScope │
│ resource_manager (src/resourceManager.js) GPU/VRAM monitor, model load/unload │
│ splash (src/splash.js) Startup splash (packaged builds only) │
└───────────────────────────────────────────────────────────────────────────────────────────┘
Main Process Responsibilities 主进程职责
The main process (src/main.js) is the single owner of privileged resources:
主进程(src/main.js)是所有特权资源的唯一持有者:
- Window management — creates and tracks all BrowserWindows via
main/windowManager.js.窗口管理— 通过main/windowManager.js创建并跟踪所有 BrowserWindow。 - IPC handlers — registers all
ipcMain.handle/ipcMain.onendpoints (see IPC pattern below).IPC 处理器— 注册所有ipcMain.handle/ipcMain.on端点(见下文 IPC 模式)。 - Model loading — owns
OnnxSVSPipelinesessions and DirectML device enumeration (enumerateDMLDevices).模型加载— 持有OnnxSVSPipeline会话与 DirectML 设备枚举(enumerateDMLDevices)。 - Audio output — WASAPI playback through
decibriviamain/audioIpc.js.音频输出— 通过main/audioIpc.js使用decibri进行 WASAPI 播放。 - File I/O — all reads/writes pass through path validation in
main/security.js.文件 I/O— 所有读写都经过main/security.js的路径校验。 - Settings persistence —
main/settings.jsreads/writessettings.jsonin the userData directory.设置持久化—main/settings.js读写 userData 目录下的settings.json。 - Hardware detection — one-shot GPU/NPU/DML enumeration on startup, cached for the lifetime of the app.硬件检测— 启动时一次性枚举 GPU/NPU/DML,结果在应用生命周期内缓存复用。
- Custom protocol — registers
onnx://scheme so renderers can fetch model files safely.自定义协议— 注册onnx://scheme,使渲染进程可以安全读取模型文件。
Renderer Process Responsibilities 渲染进程职责
Each renderer is a self-contained Vanilla JS app communicating only through window.electronAPI. Renderers never touch Node.js APIs directly.
每个渲染进程都是独立的原生 JS 应用,仅通过 window.electronAPI 通信。渲染进程绝不直接调用 Node.js API。
- Canvas rendering —
editor/pianoRoll.jsandrenderer/timelineRenderer.jsdraw the piano roll and multi-track timeline.Canvas 渲染—editor/pianoRoll.js与renderer/timelineRenderer.js绘制钢琴卷帘与多轨时间线。 - User interaction — mouse/keyboard handlers in
fragmentEditor/eventHandlers.jsandrenderer/eventHandlers.js.用户交互—fragmentEditor/eventHandlers.js与renderer/eventHandlers.js中的鼠标/键盘处理。 - Audio playback control — renderers issue
audioPlay/audioStoprequests; the main process owns the actual WASAPI session.音频播放控制— 渲染进程发起audioPlay/audioStop请求,主进程持有真正的 WASAPI 会话。 - State management —
renderer/state.jswraps TrackManager + HistoryManager;fragmentEditor/state.jsholds editor state.状态管理—renderer/state.js封装 TrackManager + HistoryManager;fragmentEditor/state.js持有编辑器状态。 - WebNN host — the main window renderer is the only window that registers WebNN request handlers and runs
onnxruntime-websessions.WebNN 宿主— 主窗口渲染进程是唯一注册 WebNN 请求处理器并运行onnxruntime-web会话的窗口。
Preload Bridge Preload 桥接
src/preload.js uses contextBridge.exposeInMainWorld('electronAPI', {...}) to publish a curated API surface. Every IPC call in renderer code goes through this object — there is no direct access to ipcRenderer, require, or Node.js modules.
src/preload.js 通过 contextBridge.exposeInMainWorld('electronAPI', {...}) 发布经过筛选的 API 表面。渲染进程中的每一次 IPC 调用都经由该对象——无法直接访问 ipcRenderer、require 或 Node.js 模块。
Example members exposed on window.electronAPI:
window.electronAPI 上暴露的示例成员:
// Request-response (Promise-based)
electronAPI.initSVSPipeline() // svs:init
electronAPI.synthesizeSVS(data) // svs:synthesize
electronAPI.getSettings() // settings:getSettings
electronAPI.saveSettings(settings) // settings:saveSettings
electronAPI.audioPlay(audioData, options) // audio:play
// Fire-and-forget listeners (return unsubscribe fn)
electronAPI.onSVSProgress(p => console.log(p)) // svs:progress
electronAPI.onAudioEnded(() => replay()) // audio:ended
electronAPI.onModelDownloadProgress(p => updateUI(p))
// WebNN — channel names whitelisted in preload to prevent arbitrary IPC
electronAPI.webnnRespond(responseChannel, result)
electronAPI.webnnProgress(progressChannel, data)
// Theme sub-API
electronAPI.themeAPI.apply(themeId, { scope }) // theme:apply
electronAPI.themeAPI.list() // theme:list
WebNN response/progress channels (webnnRespond, webnnProgress, webnnChunk) validate the channel prefix against a whitelist before invoking ipcRenderer. This prevents compromised renderer code from sending arbitrary IPC messages.
WebNN 的响应/进度通道(webnnRespond、webnnProgress、webnnChunk)在调用 ipcRenderer 前会按白名单校验通道前缀。这可以防止被攻破的渲染进程发送任意 IPC 消息。
IPC Pattern IPC 模式
SXSEditor uses two IPC styles consistently:
SXSEditor 一致地使用两种 IPC 风格:
- Request-response —
ipcRenderer.invoke↔ipcMain.handle. Used for almost every API call (file I/O, SVS synthesis, settings, theme ops).请求-响应—ipcRenderer.invoke↔ipcMain.handle。几乎所有 API 调用都采用此模式(文件 I/O、SVS 合成、设置、主题操作)。 - Fire-and-forget —
ipcRenderer.send↔ipcMain.on, or main → renderer viawebContents.send. Used for progress events (svs:progress,model-download:progress,audio:ended).即发即弃—ipcRenderer.send↔ipcMain.on,或主进程通过webContents.send推送给渲染进程。用于进度事件(svs:progress、model-download:progress、audio:ended)。
All channel names are centralized in src/shared/ipcChannels.js as IPC_CHANNELS constants (e.g. SVS_SYNTHESIZE = 'svs:synthesize') to prevent typos and enable refactoring.
所有通道名都集中在 src/shared/ipcChannels.js 中作为 IPC_CHANNELS 常量(例如 SVS_SYNTHESIZE = 'svs:synthesize'),以避免拼写错误并便于重构。
Binary audio data is transferred as Float32Array for low latency — the renderer ships PCM samples to the main process, which feeds them into the WASAPI output manager.
二进制音频数据以 Float32Array 传输以降低延迟——渲染进程将 PCM 采样发送到主进程,再由主进程送入 WASAPI 输出管理器。
IPC channel groupsIPC 通道分组
| Group分组 | Example channels示例通道 | Registrar注册模块 |
|---|---|---|
| Dialog对话框 | dialog:showSaveDialog, dialog:showOpenDialog |
main/dialogIpc.js |
| File文件 | file:saveFile, file:readFile, file:authorizePath |
main/dialogIpc.js |
| SVSSVS | svs:init, svs:synthesize, svs:cancel, fragment-svs:synthesize |
main/svsIpc.js |
| Pitch / MIDI音高 / MIDI | extractF0:onnx, extractF0:basicPitch, midi:import |
main/pitchMidiIpc.js |
| Settings设置 | settings:getSettings, settings:saveSettings, settings:getDMLDevices |
main/settingsIpc.js |
| Audio音频 | audio:play, audio:stop, audio:ended |
main/audioIpc.js |
| Model download模型下载 | model-download:start, model-download:progress, model-download:complete |
main/modelDownload.js |
| WebNN / NPUWebNN / NPU | webnn:detectNPU, webnn:loadModel, webnn:runInference |
main/webnnIpc.js |
| Theme主题 | theme:bootstrap, theme:list, theme:apply, theme:changed |
main/themeIpc.js |
| Resource manager资源管理器 | resmgr:getGPUInfo, resmgr:loadModel, resmgr:loadGroup |
main/resourceManagerIpc.js |
| Locale语言 | save-locale, get-locale, locale-changed |
main.js |
| Singer Market歌手市场 | singer-market:fetch, singer-market:download |
main/singerMarketIpc.js |
| Accompaniment伴奏 | accompaniment:import, accompaniment:export-mix |
main/audioIpc.js |
| Lyrics / LRC歌词 / LRC | lrc:export |
main/svsIpc.js |
Security Model 安全模型
Security is enforced through a layered design in src/main/security.js and the main process bootstrap:
安全通过 src/main/security.js 与主进程启动代码的分层设计实现:
- contextIsolation: true and sandbox: true on every BrowserWindow — renderer JS cannot reach Node.js primitives.每个 BrowserWindow 都启用 contextIsolation: true 与 sandbox: true— 渲染进程 JS 无法触及 Node.js 原语。
- nodeIntegration disabled — renderers load only via webpack bundles, no
requirein page context.禁用 nodeIntegration— 渲染进程仅通过 webpack bundle 加载,页面上下文中没有require。 - Path validation —
authorizePath/isPathAllowedrestrict file operations touserData,documents,desktop,home,temp, plus dialog-authorized paths (tracked in a bounded Set with a 1000-entry cap).路径校验—authorizePath/isPathAllowed将文件操作限制在userData、documents、desktop、home、temp,以及通过对话框授权的路径(保存在一个上限 1000 条的 Set 中)。 - System path guard —
isSystemPathblocks writes toC:\Windows,C:\Program Files,C:\ProgramDataon Windows, and/etc,/root,/sys,/proc,/dev,/boot,/System,/Libraryon Unix.系统路径保护—isSystemPath拦截对 WindowsC:\Windows、C:\Program Files、C:\ProgramData的写入,以及 Unix 下/etc、/root、/sys、/proc、/dev、/boot、/System、/Library的写入。 - onnx:// protocol handler — restricts model file fetches to the resolved model directory and only serves
.onnx/.onnx.datafiles.onnx:// 协议处理器— 将模型文件读取限制在解析后的模型目录内,且仅提供.onnx/.onnx.data文件。 - Content Security Policy — set via
session.defaultSession.webRequest.onHeadersReceived:default-src 'self',connect-srclimited to'self'andhttps://modelscope.cn(plus dev-server WS in dev mode). Cross-origin isolation headers (COOP: same-origin,COEP: require-corp) enableSharedArrayBufferfor multi-threaded WASM.内容安全策略— 通过session.defaultSession.webRequest.onHeadersReceived设置:default-src 'self',connect-src限制为'self'与https://modelscope.cn(开发模式额外允许开发服务器 WS)。跨域隔离头(COOP: same-origin、COEP: require-corp)启用SharedArrayBuffer以支持多线程 WASM。 - Electron Fuses — packaged builds enable ASAR integrity validation, cookie encryption, and disable
NODE_OPTIONS/--inspectat runtime.Electron Fuses— 打包构建启用 ASAR 完整性校验、Cookie 加密,并在运行时禁用NODE_OPTIONS/--inspect。 - WebNN channel whitelist —
webnnRespond/webnnProgress/webnnChunkin preload reject channel names that do not match expected prefixes.WebNN 通道白名单— preload 中的webnnRespond/webnnProgress/webnnChunk会拒绝不符合预期前缀的通道名。
Window Types 窗口类型
Each window is registered as a webpack entry point in forge.config.js and has its own HTML + JS bundle. All windows share the same preload (src/preload.js), except the splash window which uses src/splashPreload.js.
每个窗口在 forge.config.js 中注册为 webpack 入口,并拥有独立的 HTML + JS bundle。所有窗口共享同一个 preload(src/preload.js),启动画面窗口除外,它使用 src/splashPreload.js。
| Window窗口 | Entry Point入口 | Purpose用途 |
|---|---|---|
main_window |
src/renderer/index.js |
Multi-track timeline, project management, singer list. Also hosts WebNN request handlers.多轨时间线、项目管理、歌手列表。同时承载 WebNN 请求处理器。 |
fragment_editor_window |
src/fragmentEditor/index.js |
Piano-roll editor for an individual fragment; previews SVS synthesis.单个分片的钢琴卷帘编辑器,可预览 SVS 合成结果。 |
singer_creator_window |
src/singerCreator.js |
Custom-singer creation wizard from reference WAV.从参考 WAV 创建自定义歌手的向导。 |
singer_market_window |
src/singerMarket.js |
Browse, download, and share community-created singers via a marketplace UI.通过市场界面浏览、下载和分享社区创建的歌手。 |
audio_preprocess_window |
src/audioPreprocess/index.js |
F0 (RMVPE) and MIDI (Basic Pitch) extraction UI.F0(RMVPE)与 MIDI(Basic Pitch)提取界面。 |
settings_window |
src/settings.js |
Device selection, inference params, audio config, theme picker.设备选择、推理参数、音频配置、主题选择。 |
model_download_window |
src/modelDownload.js |
Chunked parallel download of ONNX models from ModelScope.从 ModelScope 分片并行下载 ONNX 模型。 |
resource_manager_window |
src/resourceManager.js |
GPU/VRAM monitor, model load/unload controls.GPU/显存监控、模型加载/卸载控制。 |
splash_window |
src/splash.js |
Startup splash (packaged builds only; skipped in npm start).启动画面(仅打包构建使用;npm start 时跳过)。 |
Startup Flow 启动流程
- 1
src/main.jssetsWebMachineLearningNeuralNetworkfeature flag, registers theonnx://protocol scheme, and applies CSP headers.src/main.js设置WebMachineLearningNeuralNetwork特性开关,注册onnx://协议 scheme,并应用 CSP 头。 - 2In packaged builds the splash window is created first; in dev mode the main window is created with
show: false.打包构建时先创建启动画面窗口;开发模式下以show: false创建主窗口。 - 3All IPC registrars modules (
registerWindowIpc,registerSvsIpc, ...) are called beforeapp.whenReady()resolves, so handlers exist when renderers load.所有 IPC 注册模块(registerWindowIpc、registerSvsIpc等)在app.whenReady()完成前就被调用,确保渲染进程加载时处理器已就绪。 - 4On
did-finish-loadthe main window is revealed (immediately in dev, after splash paint in packaged mode).在did-finish-load时显示主窗口(开发模式立即显示,打包模式等待启动画面绘制后再显示)。 - 5Background hardware detection (
startGPUPreload+detectAllHardware+enumerateDMLDevices) runs once and caches results — it never blocks window reveal.后台硬件检测(startGPUPreload+detectAllHardware+enumerateDMLDevices)一次性执行并缓存结果——绝不阻塞窗口显示。 - 6
checkAndDownloadModels()fires next, prompting the user to download missing models on first launch.随后触发checkAndDownloadModels(),首次启动时提示用户下载缺失的模型。
The CLI debug mode (--cli) intercepts startup before any window is created. It calls app.whenReady(), runs the requested command, and exits with app.exit(code) — see Testing & CLI.
CLI 调试模式(--cli)在任何窗口创建之前拦截启动流程。它调用 app.whenReady(),执行请求的命令,并通过 app.exit(code) 退出——见 测试与命令行。
New Modules (post-v1.0.8) 新模块(v1.0.8 之后)
src/inference/fcpeDetector.js— FCPE (Fast Context-Free Pitch Estimator) ONNX-based F0 extraction. Now the default MIDI extraction tool, replacing Basic Pitch.src/inference/fcpeDetector.js— 基于 ONNX 的 FCPE(Fast Context-Free Pitch Estimator)F0 提取。现为默认 MIDI 提取工具,替代 Basic Pitch。src/audio/lrcExport.js— LRC lyrics file export module. Exports project lyrics as a timed.lrcfile synchronized with note timings.src/audio/lrcExport.js— LRC 歌词文件导出模块。将项目歌词导出为与音符时间同步的.lrc文件。src/editor/accompanimentTrack.js— Accompaniment track management: import audio files, multi-channel support, per-track volume control, and drag-to-move positioning on the timeline.src/editor/accompanimentTrack.js— 伴奏轨道管理:导入音频文件、多通道支持、逐轨音量控制,以及时间线上的拖拽移动定位。
Behavior Changes (post-v1.0.8) 行为变更(v1.0.8 之后)
- Audio sample rate — the default playback sample rate changed from 24 kHz to 48 kHz. Export sample rate is now user-selectable (24 / 44.1 / 48 / 96 kHz).音频采样率— 默认播放采样率从 24 kHz 改为 48 kHz。导出采样率现可选(24 / 44.1 / 48 / 96 kHz)。
- Synthesis cancellation — replaces
worker.terminate()with cooperativeAbortController-based cancellation. GPU inference exits at safe checkpoints between diffusion steps.合成取消— 用基于AbortController的协作式取消替代worker.terminate()。GPU 推理在扩散步之间的安全检查点退出。 - Timeline canvas — now uses a viewport-sized backing store with scroll transform instead of a single oversized canvas, reducing memory usage for long projects.时间线画布— 改用视口大小的 backing store 配合滚动变换,替代单个超大画布,降低长项目的内存占用。
- autoShift granularity — per-segment autoShift was replaced with per-fragment global autoShift to prevent pitch discontinuities at segment boundaries.autoShift 粒度— 逐段 autoShift 被替换为逐分片全局 autoShift,以防止分段边界处的音高不连续。