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)是所有特权资源的唯一持有者:

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。

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 调用都经由该对象——无法直接访问 ipcRendererrequire 或 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 的响应/进度通道(webnnRespondwebnnProgresswebnnChunk)在调用 ipcRenderer 前会按白名单校验通道前缀。这可以防止被攻破的渲染进程发送任意 IPC 消息。

IPC Pattern IPC 模式

SXSEditor uses two IPC styles consistently:

SXSEditor 一致地使用两种 IPC 风格:

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 与主进程启动代码的分层设计实现:

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. 1src/main.js sets WebMachineLearningNeuralNetwork feature flag, registers the onnx:// protocol scheme, and applies CSP headers.src/main.js 设置 WebMachineLearningNeuralNetwork 特性开关,注册 onnx:// 协议 scheme,并应用 CSP 头。
  2. 2In packaged builds the splash window is created first; in dev mode the main window is created with show: false.打包构建时先创建启动画面窗口;开发模式下以 show: false 创建主窗口。
  3. 3All IPC registrars modules (registerWindowIpc, registerSvsIpc, ...) are called before app.whenReady() resolves, so handlers exist when renderers load.所有 IPC 注册模块(registerWindowIpcregisterSvsIpc 等)在 app.whenReady() 完成前就被调用,确保渲染进程加载时处理器已就绪。
  4. 4On did-finish-load the main window is revealed (immediately in dev, after splash paint in packaged mode).did-finish-load 时显示主窗口(开发模式立即显示,打包模式等待启动画面绘制后再显示)。
  5. 5Background hardware detection (startGPUPreload + detectAllHardware + enumerateDMLDevices) runs once and caches results — it never blocks window reveal.后台硬件检测(startGPUPreload + detectAllHardware + enumerateDMLDevices)一次性执行并缓存结果——绝不阻塞窗口显示。
  6. 6checkAndDownloadModels() 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 之后)

Behavior Changes (post-v1.0.8) 行为变更(v1.0.8 之后)