loukai-app 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/README.md +558 -0
  2. package/bin/loukai.js +32 -0
  3. package/package.json +243 -0
  4. package/src/main/appState.js +250 -0
  5. package/src/main/audioEngine.js +478 -0
  6. package/src/main/creator/conversionService.js +503 -0
  7. package/src/main/creator/downloadManager.js +1128 -0
  8. package/src/main/creator/ffmpegService.js +487 -0
  9. package/src/main/creator/installLogger.js +51 -0
  10. package/src/main/creator/keyDetection.js +212 -0
  11. package/src/main/creator/llmService.js +370 -0
  12. package/src/main/creator/lrclibService.js +340 -0
  13. package/src/main/creator/python/crepe_runner.py +189 -0
  14. package/src/main/creator/python/demucs_runner.py +158 -0
  15. package/src/main/creator/python/whisper_runner.py +172 -0
  16. package/src/main/creator/pythonRunner.js +268 -0
  17. package/src/main/creator/stemBuilder.js +491 -0
  18. package/src/main/creator/systemChecker.js +474 -0
  19. package/src/main/handlers/appHandlers.js +45 -0
  20. package/src/main/handlers/audioHandlers.js +33 -0
  21. package/src/main/handlers/autotuneHandlers.js +28 -0
  22. package/src/main/handlers/canvasHandlers.js +84 -0
  23. package/src/main/handlers/creatorHandlers.js +159 -0
  24. package/src/main/handlers/editorHandlers.js +98 -0
  25. package/src/main/handlers/effectsHandlers.js +100 -0
  26. package/src/main/handlers/fileHandlers.js +45 -0
  27. package/src/main/handlers/index.js +78 -0
  28. package/src/main/handlers/libraryHandlers.js +96 -0
  29. package/src/main/handlers/mixerHandlers.js +64 -0
  30. package/src/main/handlers/playerHandlers.js +39 -0
  31. package/src/main/handlers/preferencesHandlers.js +46 -0
  32. package/src/main/handlers/queueHandlers.js +81 -0
  33. package/src/main/handlers/rendererHandlers.js +63 -0
  34. package/src/main/handlers/settingsHandlers.js +42 -0
  35. package/src/main/handlers/webServerHandlers.js +105 -0
  36. package/src/main/main.js +2351 -0
  37. package/src/main/preload.js +252 -0
  38. package/src/main/settingsManager.js +139 -0
  39. package/src/main/statePersistence.js +193 -0
  40. package/src/main/utils/pathValidator.js +112 -0
  41. package/src/main/webServer.js +2535 -0
  42. package/src/native/autotune.js +417 -0
  43. package/src/renderer/adapters/ElectronBridge.js +677 -0
  44. package/src/renderer/canvas.html +80 -0
  45. package/src/renderer/components/App.jsx +303 -0
  46. package/src/renderer/components/AppRoot.jsx +37 -0
  47. package/src/renderer/components/AudioDeviceSettings.jsx +145 -0
  48. package/src/renderer/components/EffectsPanelWrapper.jsx +267 -0
  49. package/src/renderer/components/MixerTab.jsx +233 -0
  50. package/src/renderer/components/MixerTabWrapper.jsx +31 -0
  51. package/src/renderer/components/PortalSelect.jsx +239 -0
  52. package/src/renderer/components/QueueTab.jsx +116 -0
  53. package/src/renderer/components/RequestsListWrapper.jsx +78 -0
  54. package/src/renderer/components/ServerTab.jsx +472 -0
  55. package/src/renderer/components/SongInfoBarWrapper.jsx +77 -0
  56. package/src/renderer/components/StatusBar.jsx +92 -0
  57. package/src/renderer/components/TabNavigation.jsx +77 -0
  58. package/src/renderer/components/TransportControlsWrapper.jsx +69 -0
  59. package/src/renderer/components/creator/CreateTab.jsx +1236 -0
  60. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js +2 -0
  61. package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +1 -0
  62. package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js +2 -0
  63. package/src/renderer/dist/assets/microphoneEngine-BaCUhhQc.js.map +1 -0
  64. package/src/renderer/dist/assets/player-DVrqp7N5.js +3 -0
  65. package/src/renderer/dist/assets/player-DVrqp7N5.js.map +1 -0
  66. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +2 -0
  67. package/src/renderer/dist/assets/songLoaders-BaTgGib4.js.map +1 -0
  68. package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js +2 -0
  69. package/src/renderer/dist/assets/webrtcManager-BhCHWceK.js.map +1 -0
  70. package/src/renderer/dist/js/autoTuneWorklet.js +224 -0
  71. package/src/renderer/dist/js/micPitchDetectorWorklet.js +137 -0
  72. package/src/renderer/dist/js/musicAnalysisWorklet.js +216 -0
  73. package/src/renderer/dist/js/phaseVocoderWorklet.js +341 -0
  74. package/src/renderer/dist/js/soundtouch-worklet.js +1395 -0
  75. package/src/renderer/dist/renderer.css +1 -0
  76. package/src/renderer/dist/renderer.js +62 -0
  77. package/src/renderer/dist/renderer.js.map +1 -0
  78. package/src/renderer/dist/renderer.woff2 +0 -0
  79. package/src/renderer/hooks/useKeyboardShortcuts.js +154 -0
  80. package/src/renderer/index.html +24 -0
  81. package/src/renderer/index.html.backup +372 -0
  82. package/src/renderer/js/PlayerInterface.js +267 -0
  83. package/src/renderer/js/autoTuneWorklet.js +224 -0
  84. package/src/renderer/js/butterchurnVerify.js +46 -0
  85. package/src/renderer/js/canvas-app.js +114 -0
  86. package/src/renderer/js/cdgPlayer.js +685 -0
  87. package/src/renderer/js/kaiPlayer.js +1200 -0
  88. package/src/renderer/js/karaokeRenderer.js +3392 -0
  89. package/src/renderer/js/micPitchDetectorWorklet.js +137 -0
  90. package/src/renderer/js/microphoneEngine.js +656 -0
  91. package/src/renderer/js/musicAnalysisWorklet.js +216 -0
  92. package/src/renderer/js/phaseVocoderWorklet.js +341 -0
  93. package/src/renderer/js/player.js +232 -0
  94. package/src/renderer/js/referencePitchTracker.js +130 -0
  95. package/src/renderer/js/songLoaders.js +334 -0
  96. package/src/renderer/js/soundtouch-worklet.js +1395 -0
  97. package/src/renderer/js/webrtcManager.js +511 -0
  98. package/src/renderer/lib/butterchurn.min.js +6739 -0
  99. package/src/renderer/lib/butterchurnPresets.min.js +1 -0
  100. package/src/renderer/lib/cdgraphics-wrapper.js +16 -0
  101. package/src/renderer/lib/cdgraphics.js +299 -0
  102. package/src/renderer/public/js/autoTuneWorklet.js +224 -0
  103. package/src/renderer/public/js/micPitchDetectorWorklet.js +137 -0
  104. package/src/renderer/public/js/musicAnalysisWorklet.js +216 -0
  105. package/src/renderer/public/js/phaseVocoderWorklet.js +341 -0
  106. package/src/renderer/public/js/soundtouch-worklet.js +1395 -0
  107. package/src/renderer/react-entry.jsx +44 -0
  108. package/src/renderer/styles/tailwind.css +106 -0
  109. package/src/renderer/utils/qrCodeGenerator.js +98 -0
  110. package/src/renderer/vite.config.js +31 -0
  111. package/src/shared/adapters/BridgeInterface.js +195 -0
  112. package/src/shared/components/EffectsPanel.jsx +177 -0
  113. package/src/shared/components/LibraryPanel.jsx +701 -0
  114. package/src/shared/components/LineDetailCanvas.jsx +167 -0
  115. package/src/shared/components/LyricLine.jsx +505 -0
  116. package/src/shared/components/LyricRejection.jsx +84 -0
  117. package/src/shared/components/LyricSuggestion.jsx +80 -0
  118. package/src/shared/components/LyricsEditorCanvas.jsx +271 -0
  119. package/src/shared/components/MixerPanel.jsx +94 -0
  120. package/src/shared/components/PlayerControls.jsx +206 -0
  121. package/src/shared/components/PortalSelect.jsx +239 -0
  122. package/src/shared/components/QueueList.jsx +365 -0
  123. package/src/shared/components/QuickSearch.jsx +126 -0
  124. package/src/shared/components/RequestsList.jsx +121 -0
  125. package/src/shared/components/SongEditor.jsx +1362 -0
  126. package/src/shared/components/SongInfoBar.jsx +81 -0
  127. package/src/shared/components/ThemeToggle.jsx +106 -0
  128. package/src/shared/components/Toast.jsx +30 -0
  129. package/src/shared/components/VisualizationSettings.jsx +243 -0
  130. package/src/shared/constants.js +95 -0
  131. package/src/shared/context/BridgeContext.jsx +32 -0
  132. package/src/shared/contexts/AudioContext.jsx +37 -0
  133. package/src/shared/contexts/PlayerContext.jsx +66 -0
  134. package/src/shared/contexts/SettingsContext.jsx +50 -0
  135. package/src/shared/defaults.js +158 -0
  136. package/src/shared/formatUtils.js +59 -0
  137. package/src/shared/formatUtils.test.js +207 -0
  138. package/src/shared/hooks/useAppState.js +97 -0
  139. package/src/shared/hooks/useAudioEngine.js +264 -0
  140. package/src/shared/hooks/usePlayer.js +89 -0
  141. package/src/shared/hooks/useSettingsPersistence.js +74 -0
  142. package/src/shared/hooks/useWebRTC.js +118 -0
  143. package/src/shared/ipcContracts.js +299 -0
  144. package/src/shared/package.json +3 -0
  145. package/src/shared/services/creatorService.js +373 -0
  146. package/src/shared/services/creatorService.test.js +413 -0
  147. package/src/shared/services/editorService.js +213 -0
  148. package/src/shared/services/editorService.test.js +219 -0
  149. package/src/shared/services/effectsService.js +271 -0
  150. package/src/shared/services/effectsService.test.js +418 -0
  151. package/src/shared/services/libraryService.js +438 -0
  152. package/src/shared/services/libraryService.test.js +474 -0
  153. package/src/shared/services/mixerService.js +172 -0
  154. package/src/shared/services/mixerService.test.js +399 -0
  155. package/src/shared/services/playerService.js +221 -0
  156. package/src/shared/services/playerService.test.js +357 -0
  157. package/src/shared/services/preferencesService.js +219 -0
  158. package/src/shared/services/queueService.js +226 -0
  159. package/src/shared/services/queueService.test.js +430 -0
  160. package/src/shared/services/requestsService.js +155 -0
  161. package/src/shared/services/requestsService.test.js +362 -0
  162. package/src/shared/services/serverSettingsService.js +151 -0
  163. package/src/shared/services/settingsService.js +257 -0
  164. package/src/shared/services/settingsService.test.js +295 -0
  165. package/src/shared/state/StateManager.js +263 -0
  166. package/src/shared/utils/audio.js +42 -0
  167. package/src/shared/utils/format.js +32 -0
  168. package/src/shared/utils/lyricsUtils.js +162 -0
  169. package/src/test/setup.js +40 -0
  170. package/src/utils/cdgLoader.js +180 -0
  171. package/src/utils/m4aLoader.js +333 -0
  172. package/src/web/App.jsx +578 -0
  173. package/src/web/adapters/WebBridge.js +428 -0
  174. package/src/web/components/PlayerSettingsPanel.jsx +231 -0
  175. package/src/web/components/SongSearch.jsx +180 -0
  176. package/src/web/dist/assets/index-0H-RnRrV.js +51 -0
  177. package/src/web/dist/assets/index-0H-RnRrV.js.map +1 -0
  178. package/src/web/dist/assets/index-DYW2zB0u.css +1 -0
  179. package/src/web/dist/index.html +15 -0
  180. package/src/web/index.html +14 -0
  181. package/src/web/main.jsx +10 -0
  182. package/src/web/package-lock.json +1765 -0
  183. package/src/web/pages/SongRequestPage.jsx +619 -0
  184. package/src/web/styles/tailwind.css +68 -0
  185. package/src/web/vite.config.js +27 -0
  186. package/static/fonts/material-icons.woff2 +0 -0
  187. package/static/images/butterchurn-screenshots/Aderrasi - Potion of Spirits.png +0 -0
  188. package/static/images/butterchurn-screenshots/Aderrasi - Songflower _Moss Posy_.png +0 -0
  189. package/static/images/butterchurn-screenshots/Aderrasi - Storm of the Eye _Thunder_ - mash0000 - quasi pseudo meta concentrics.png +0 -0
  190. package/static/images/butterchurn-screenshots/Aderrasi _ Geiss - Airhandler _Kali Mix_ - Canvas Mix.png +0 -0
  191. package/static/images/butterchurn-screenshots/An AdamFX n Martin Infusion 2 flexi - Why The Sky Looks Diffrent Today - AdamFx n Martin Infusion - Tack Tile Disfunction B.png +0 -0
  192. package/static/images/butterchurn-screenshots/Cope - The Neverending Explosion of Red Liquid Fire.png +0 -0
  193. proton lights __Krash_s beat code_ _Phat_remix02b.png +0 -0
  194. package/static/images/butterchurn-screenshots/Eo_S_ _ Phat - cubetrace - v2.png +0 -0
  195. package/static/images/butterchurn-screenshots/Eo_S_ _ Zylot - skylight _Stained Glass Majesty mix_.png +0 -0
  196. package/static/images/butterchurn-screenshots/Flexi - alien fish pond.png +0 -0
  197. package/static/images/butterchurn-screenshots/Flexi - area 51.png +0 -0
  198. package/static/images/butterchurn-screenshots/Flexi - infused with the spiral.png +0 -0
  199. package/static/images/butterchurn-screenshots/Flexi - mindblob _shiny mix_.png +0 -0
  200. package/static/images/butterchurn-screenshots/Flexi - mindblob mix.png +0 -0
  201. package/static/images/butterchurn-screenshots/Flexi - predator-prey-spirals.png +0 -0
  202. package/static/images/butterchurn-screenshots/Flexi - smashing fractals _acid etching mix_.png +0 -0
  203. package/static/images/butterchurn-screenshots/Flexi - truly soft piece of software - this is generic texturing _Jelly_ .png +0 -0
  204. package/static/images/butterchurn-screenshots/Flexi _ Martin - astral projection.png +0 -0
  205. package/static/images/butterchurn-screenshots/Flexi _ Martin - cascading decay swing.png +0 -0
  206. package/static/images/butterchurn-screenshots/Flexi _ amandio c - piercing 05 - Kopie _2_ - Kopie.png +0 -0
  207. package/static/images/butterchurn-screenshots/Flexi _ stahlregen - jelly showoff parade.png +0 -0
  208. package/static/images/butterchurn-screenshots/Flexi_ fishbrain_ Geiss _ Martin - tokamak witchery.png +0 -0
  209. package/static/images/butterchurn-screenshots/Flexi_ martin _ geiss - dedicated to the sherwin maxawow.png +0 -0
  210. package/static/images/butterchurn-screenshots/Fumbling_Foo _ Flexi_ Martin_ Orb_ Unchained - Star Nova v7b.png +0 -0
  211. package/static/images/butterchurn-screenshots/Geiss - Cauldron - painterly 2 _saturation remix_.png +0 -0
  212. package/static/images/butterchurn-screenshots/Geiss - Reaction Diffusion 2.png +0 -0
  213. package/static/images/butterchurn-screenshots/Geiss - Spiral Artifact.png +0 -0
  214. package/static/images/butterchurn-screenshots/Geiss - Thumb Drum.png +0 -0
  215. package/static/images/butterchurn-screenshots/Geiss _ Flexi _ Martin - disconnected.png +0 -0
  216. package/static/images/butterchurn-screenshots/Geiss_ Flexi _ Stahlregen - Thumbdrum Tokamak _crossfiring aftermath jelly mashup_.png +0 -0
  217. package/static/images/butterchurn-screenshots/Goody - The Wild Vort.png +0 -0
  218. package/static/images/butterchurn-screenshots/Idiot - Star Of Annon.png +0 -0
  219. package/static/images/butterchurn-screenshots/Krash _ Illusion - Spiral Movement.png +0 -0
  220. package/static/images/butterchurn-screenshots/Martin - QBikal - Surface Turbulence IIb.png +0 -0
  221. package/static/images/butterchurn-screenshots/Martin - acid wiring.png +0 -0
  222. package/static/images/butterchurn-screenshots/Martin - charisma.png +0 -0
  223. package/static/images/butterchurn-screenshots/Martin - liquid arrows.png +0 -0
  224. package/static/images/butterchurn-screenshots/Milk Artist At our Best - FED - SlowFast Ft AdamFX n Martin - HD CosmoFX.png +0 -0
  225. package/static/images/butterchurn-screenshots/ORB - Waaa.png +0 -0
  226. package/static/images/butterchurn-screenshots/Phat_fiShbRaiN_Eo_S_Mandala_Chasers_remix.png +0 -0
  227. package/static/images/butterchurn-screenshots/Rovastar - Oozing Resistance.png +0 -0
  228. package/static/images/butterchurn-screenshots/Rovastar _ Loadus _ Geiss - FractalDrop _Triple Mix_.png +0 -0
  229. package/static/images/butterchurn-screenshots/TonyMilkdrop - Leonardo Da Vinci_s Balloon _Flexi - merry-go-round _ techstyle_.png +0 -0
  230. package/static/images/butterchurn-screenshots/TonyMilkdrop - Magellan_s Nebula _Flexi - you enter first _ multiverse_.png +0 -0
  231. package/static/images/butterchurn-screenshots/Unchained - Rewop.png +0 -0
  232. package/static/images/butterchurn-screenshots/Unchained - Unified Drag 2.png +0 -0
  233. package/static/images/butterchurn-screenshots/Unchained _ Rovastar - Wormhole Pillars _Hall of Shadows mix_.png +0 -0
  234. package/static/images/butterchurn-screenshots/Zylot - Paint Spill _Music Reactive Paint Mix_.png +0 -0
  235. package/static/images/butterchurn-screenshots/Zylot - Star Ornament.png +0 -0
  236. package/static/images/butterchurn-screenshots/Zylot - True Visionary _Final Mix_.png +0 -0
  237. package/static/images/butterchurn-screenshots/_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz _Geiss color mix_.png +0 -0
  238. package/static/images/butterchurn-screenshots/_Geiss - Artifact 01.png +0 -0
  239. package/static/images/butterchurn-screenshots/_Geiss - Desert Rose 2.png +0 -0
  240. package/static/images/butterchurn-screenshots/_Geiss - untitled.png +0 -0
  241. package/static/images/butterchurn-screenshots/_Mig_049.png +0 -0
  242. package/static/images/butterchurn-screenshots/_Mig_085.png +0 -0
  243. package/static/images/butterchurn-screenshots/_Rovastar _ Geiss - Hurricane Nightmare _Posterize Mix_.png +0 -0
  244. package/static/images/butterchurn-screenshots/___ Royal - Mashup _197_.png +0 -0
  245. package/static/images/butterchurn-screenshots/___ Royal - Mashup _220_.png +0 -0
  246. package/static/images/butterchurn-screenshots/___ Royal - Mashup _431_.png +0 -0
  247. package/static/images/butterchurn-screenshots/cope _ martin - mother-of-pearl.png +0 -0
  248. package/static/images/butterchurn-screenshots/fiShbRaiN _ Flexi - witchcraft 2_0.png +0 -0
  249. package/static/images/butterchurn-screenshots/flexi - bouncing balls _double mindblob neon mix_.png +0 -0
  250. package/static/images/butterchurn-screenshots/flexi - mom_ why the sky looks different today.png +0 -0
  251. package/static/images/butterchurn-screenshots/flexi - patternton_ district of media_ capitol of the united abstractions of fractopia.png +0 -0
  252. package/static/images/butterchurn-screenshots/flexi - swing out on the spiral.png +0 -0
  253. package/static/images/butterchurn-screenshots/flexi - what is the matrix.png +0 -0
  254. package/static/images/butterchurn-screenshots/flexi _ amandio c - organic _random mashup_.png +0 -0
  255. package/static/images/butterchurn-screenshots/flexi _ amandio c - organic12-3d-2_milk.png +0 -0
  256. package/static/images/butterchurn-screenshots/flexi _ fishbrain - neon mindblob grafitti.png +0 -0
  257. package/static/images/butterchurn-screenshots/flexi _ geiss - pogo cubes vs_ tokamak vs_ game of life _stahls jelly 4_5 finish_.png +0 -0
  258. package/static/images/butterchurn-screenshots/high-altitude basket unraveling - singh grooves nitrogen argon nz_.png +0 -0
  259. package/static/images/butterchurn-screenshots/martin - The Bridge of Khazad-Dum.png +0 -0
  260. package/static/images/butterchurn-screenshots/martin - angel flight.png +0 -0
  261. package/static/images/butterchurn-screenshots/martin - another kind of groove.png +0 -0
  262. package/static/images/butterchurn-screenshots/martin - bombyx mori.png +0 -0
  263. package/static/images/butterchurn-screenshots/martin - castle in the air.png +0 -0
  264. package/static/images/butterchurn-screenshots/martin - chain breaker.png +0 -0
  265. package/static/images/butterchurn-screenshots/martin - disco mix 4.png +0 -0
  266. package/static/images/butterchurn-screenshots/martin - extreme heat.png +0 -0
  267. package/static/images/butterchurn-screenshots/martin - frosty caves 2.png +0 -0
  268. package/static/images/butterchurn-screenshots/martin - fruit machine.png +0 -0
  269. package/static/images/butterchurn-screenshots/martin - ghost city.png +0 -0
  270. package/static/images/butterchurn-screenshots/martin - glass corridor.png +0 -0
  271. package/static/images/butterchurn-screenshots/martin - infinity _2010 update_.png +0 -0
  272. package/static/images/butterchurn-screenshots/martin - mandelbox explorer - high speed demo version.png +0 -0
  273. package/static/images/butterchurn-screenshots/martin - mucus cervix.png +0 -0
  274. package/static/images/butterchurn-screenshots/martin - reflections on black tiles.png +0 -0
  275. package/static/images/butterchurn-screenshots/martin - stormy sea _2010 update_.png +0 -0
  276. package/static/images/butterchurn-screenshots/martin - witchcraft reloaded.png +0 -0
  277. package/static/images/butterchurn-screenshots/martin _ flexi - diamond cutter _prismaticvortex_com_ - camille - i wish i wish i wish i was constrained.png +0 -0
  278. package/static/images/butterchurn-screenshots/martin _shadow harlequins shape code_ - fata morgana.png +0 -0
  279. package/static/images/butterchurn-screenshots/martin_ flexi_ fishbrain _ sto - enterstate _random mashup_.png +0 -0
  280. package/static/images/butterchurn-screenshots/sawtooth grin roam.png +0 -0
  281. package/static/images/butterchurn-screenshots/shifter - dark tides bdrv mix 2.png +0 -0
  282. package/static/images/butterchurn-screenshots/suksma - Rovastar - Sunflower Passion _Enlightment Mix__Phat_edit _ flexi und martin shaders - circumflex in character classes in regular expression.png +0 -0
  283. package/static/images/butterchurn-screenshots/suksma - heretical crosscut playpen.png +0 -0
  284. package/static/images/butterchurn-screenshots/suksma - uninitialized variabowl _hydroponic chronic_.png +0 -0
  285. package/static/images/butterchurn-screenshots/suksma - vector exp 1 - couldn_t not.png +0 -0
  286. package/static/images/butterchurn-screenshots/yin - 191 - Temporal singularities.png +0 -0
  287. package/static/images/logo-512.png +0 -0
  288. package/static/images/logo.png +0 -0
  289. package/static/loukai-logo.png +0 -0
  290. package/static/screenshot-generator.html +610 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * SettingsContext - Application settings/preferences
3
+ *
4
+ * Manages device preferences, waveform settings, autotune settings
5
+ * Uses unified defaults from shared/defaults.js
6
+ */
7
+
8
+ import { createContext, useContext, useState } from 'react';
9
+ import { AUDIO_DEVICE_DEFAULTS, WAVEFORM_DEFAULTS, AUTOTUNE_DEFAULTS } from '../defaults.js';
10
+
11
+ const SettingsContext = createContext(null);
12
+
13
+ export function SettingsProvider({ children }) {
14
+ const [devicePreferences, setDevicePreferences] = useState({
15
+ ...AUDIO_DEVICE_DEFAULTS,
16
+ });
17
+
18
+ const [waveformPreferences, setWaveformPreferences] = useState({
19
+ ...WAVEFORM_DEFAULTS,
20
+ micToSpeakers: true,
21
+ enableMic: true,
22
+ disabledEffects: [],
23
+ });
24
+
25
+ const [autoTunePreferences, setAutoTunePreferences] = useState({
26
+ ...AUTOTUNE_DEFAULTS,
27
+ });
28
+
29
+ const value = {
30
+ // Settings
31
+ devicePreferences,
32
+ waveformPreferences,
33
+ autoTunePreferences,
34
+
35
+ // Actions
36
+ setDevicePreferences,
37
+ setWaveformPreferences,
38
+ setAutoTunePreferences,
39
+ };
40
+
41
+ return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>;
42
+ }
43
+
44
+ export function useSettings() {
45
+ const context = useContext(SettingsContext);
46
+ if (!context) {
47
+ throw new Error('useSettings must be used within SettingsProvider');
48
+ }
49
+ return context;
50
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Unified defaults for all settings
3
+ * Single source of truth - import this everywhere instead of defining defaults inline
4
+ */
5
+
6
+ export const MIXER_DEFAULTS = {
7
+ PA: { gain: 0, muted: false, mono: false },
8
+ IEM: { gain: 0, muted: true, mono: true },
9
+ mic: { gain: 0, muted: true },
10
+ stems: {
11
+ vocals: { gain: 0, muted: false },
12
+ instrumental: { gain: 0, muted: false },
13
+ bass: { gain: 0, muted: false },
14
+ drums: { gain: 0, muted: false },
15
+ },
16
+ };
17
+
18
+ export const EFFECTS_DEFAULTS = {
19
+ current: null,
20
+ disabled: [],
21
+ enableWaveforms: false,
22
+ enableEffects: true,
23
+ randomEffectOnSong: false,
24
+ overlayOpacity: 0.7,
25
+ showUpcomingLyrics: true,
26
+ };
27
+
28
+ export const AUTOTUNE_DEFAULTS = {
29
+ enabled: false,
30
+ strength: 50,
31
+ speed: 20,
32
+ preferVocals: false,
33
+ };
34
+
35
+ export const MICROPHONE_DEFAULTS = {
36
+ enabled: false,
37
+ gain: 1.0,
38
+ toSpeakers: true,
39
+ };
40
+
41
+ export const AUDIO_DEVICE_DEFAULTS = {
42
+ PA: { id: 'default', name: 'Default Output' },
43
+ IEM: { id: 'default', name: 'Default Output' },
44
+ input: { id: 'default', name: 'Default Input' },
45
+ };
46
+
47
+ export const SERVER_DEFAULTS = {
48
+ requireKJApproval: true,
49
+ allowSongRequests: true,
50
+ serverName: 'Loukai Karaoke',
51
+ port: 3069,
52
+ maxRequestsPerIP: 10,
53
+ showQrCode: true,
54
+ displayQueue: true,
55
+ };
56
+
57
+ export const LLM_DEFAULTS = {
58
+ enabled: true,
59
+ provider: 'lmstudio',
60
+ model: '',
61
+ apiKey: '',
62
+ baseUrl: 'http://localhost:1234/v1',
63
+ };
64
+
65
+ export const CREATOR_DEFAULTS = {
66
+ outputToSongsFolder: false,
67
+ whisperModel: 'large-v3-turbo',
68
+ enableCrepe: true,
69
+ llm: LLM_DEFAULTS,
70
+ };
71
+
72
+ // Waveform preferences (alias for effects for backward compatibility)
73
+ export const WAVEFORM_DEFAULTS = {
74
+ enableWaveforms: EFFECTS_DEFAULTS.enableWaveforms,
75
+ enableEffects: EFFECTS_DEFAULTS.enableEffects,
76
+ randomEffectOnSong: EFFECTS_DEFAULTS.randomEffectOnSong,
77
+ overlayOpacity: EFFECTS_DEFAULTS.overlayOpacity,
78
+ showUpcomingLyrics: EFFECTS_DEFAULTS.showUpcomingLyrics,
79
+ };
80
+
81
+ // UI defaults
82
+ export const UI_DEFAULTS = {
83
+ sidebarCollapsed: true, // Start with sidebar collapsed for less overwhelming first experience
84
+ };
85
+
86
+ // All defaults in one object
87
+ export const ALL_DEFAULTS = {
88
+ mixer: MIXER_DEFAULTS,
89
+ effects: EFFECTS_DEFAULTS,
90
+ autoTune: AUTOTUNE_DEFAULTS,
91
+ microphone: MICROPHONE_DEFAULTS,
92
+ audioDevices: AUDIO_DEVICE_DEFAULTS,
93
+ server: SERVER_DEFAULTS,
94
+ creator: CREATOR_DEFAULTS,
95
+ waveformPreferences: WAVEFORM_DEFAULTS,
96
+ autoTunePreferences: AUTOTUNE_DEFAULTS,
97
+ ui: UI_DEFAULTS,
98
+ sidebarCollapsed: UI_DEFAULTS.sidebarCollapsed,
99
+ iemMonoVocals: true,
100
+ songsFolder: null,
101
+ lastOpenedFile: null,
102
+ windowBounds: null,
103
+ };
104
+
105
+ /**
106
+ * Deep merge saved settings over defaults
107
+ * @param {Object} saved - Saved settings from disk
108
+ * @param {Object} defaults - Default values
109
+ * @returns {Object} Merged settings
110
+ */
111
+ export function mergeWithDefaults(saved, defaults = ALL_DEFAULTS) {
112
+ if (!saved) return { ...defaults };
113
+
114
+ const result = {};
115
+ for (const key of Object.keys(defaults)) {
116
+ const defaultValue = defaults[key];
117
+ const savedValue = saved[key];
118
+
119
+ if (
120
+ defaultValue !== null &&
121
+ typeof defaultValue === 'object' &&
122
+ !Array.isArray(defaultValue) &&
123
+ savedValue !== null &&
124
+ typeof savedValue === 'object' &&
125
+ !Array.isArray(savedValue)
126
+ ) {
127
+ // Deep merge objects
128
+ result[key] = { ...defaultValue, ...savedValue };
129
+ } else if (savedValue !== undefined) {
130
+ result[key] = savedValue;
131
+ } else {
132
+ result[key] = defaultValue;
133
+ }
134
+ }
135
+
136
+ // Include any extra keys from saved that aren't in defaults
137
+ for (const key of Object.keys(saved)) {
138
+ if (!(key in result)) {
139
+ result[key] = saved[key];
140
+ }
141
+ }
142
+
143
+ return result;
144
+ }
145
+
146
+ export default {
147
+ MIXER_DEFAULTS,
148
+ EFFECTS_DEFAULTS,
149
+ AUTOTUNE_DEFAULTS,
150
+ MICROPHONE_DEFAULTS,
151
+ AUDIO_DEVICE_DEFAULTS,
152
+ SERVER_DEFAULTS,
153
+ LLM_DEFAULTS,
154
+ CREATOR_DEFAULTS,
155
+ WAVEFORM_DEFAULTS,
156
+ ALL_DEFAULTS,
157
+ mergeWithDefaults,
158
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Shared utility functions for formatting and display
3
+ * Used across renderer, web UI, and main process
4
+ */
5
+
6
+ export function getFormatIcon(format) {
7
+ switch (format) {
8
+ case 'm4a-stems':
9
+ return '⚡'; // M4A stems format
10
+ case 'cdg-archive':
11
+ case 'cdg-pair':
12
+ return '💿';
13
+ default:
14
+ return '🎵'; // Default music icon
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Format duration in seconds to MM:SS format
20
+ * @param {number} seconds - Duration in seconds
21
+ * @returns {string} Formatted duration (e.g., "3:45")
22
+ */
23
+ export function formatDuration(seconds) {
24
+ if (!seconds || seconds <= 0) return '-';
25
+
26
+ const minutes = Math.floor(seconds / 60);
27
+ const remainingSeconds = Math.floor(seconds % 60);
28
+
29
+ return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
30
+ }
31
+
32
+ /**
33
+ * Format time in seconds to MM:SS.T format (with tenths)
34
+ * @param {number} seconds - Time in seconds
35
+ * @returns {string} Formatted time (e.g., "3:45.7")
36
+ */
37
+ export function formatTime(seconds) {
38
+ if (!seconds || isNaN(seconds)) return '0:00.0';
39
+
40
+ const mins = Math.floor(seconds / 60);
41
+ const secs = Math.floor(seconds % 60);
42
+ const tenths = Math.floor((seconds % 1) * 10);
43
+ return `${mins}:${secs.toString().padStart(2, '0')}.${tenths}`;
44
+ }
45
+
46
+ /**
47
+ * Format file size in bytes to human-readable format
48
+ * @param {number} bytes - File size in bytes
49
+ * @returns {string} Formatted size (e.g., "1.2 MB")
50
+ */
51
+ export function formatFileSize(bytes) {
52
+ if (bytes === 0) return '0 B';
53
+
54
+ const k = 1024;
55
+ const sizes = ['B', 'KB', 'MB', 'GB'];
56
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
57
+
58
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
59
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Format Utils Tests
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { getFormatIcon, formatDuration, formatTime, formatFileSize } from './formatUtils.js';
7
+
8
+ describe('formatUtils', () => {
9
+ describe('getFormatIcon', () => {
10
+ it('should return lightning icon for m4a-stems format', () => {
11
+ expect(getFormatIcon('m4a-stems')).toBe('⚡');
12
+ });
13
+
14
+ it('should return disc icon for cdg-archive format', () => {
15
+ expect(getFormatIcon('cdg-archive')).toBe('💿');
16
+ });
17
+
18
+ it('should return disc icon for cdg-pair format', () => {
19
+ expect(getFormatIcon('cdg-pair')).toBe('💿');
20
+ });
21
+
22
+ it('should return default music icon for unknown format', () => {
23
+ expect(getFormatIcon('unknown')).toBe('🎵');
24
+ expect(getFormatIcon('mp3')).toBe('🎵');
25
+ expect(getFormatIcon('')).toBe('🎵');
26
+ });
27
+
28
+ it('should return default icon for null/undefined', () => {
29
+ expect(getFormatIcon(null)).toBe('🎵');
30
+ expect(getFormatIcon(undefined)).toBe('🎵');
31
+ });
32
+ });
33
+
34
+ describe('formatDuration', () => {
35
+ it('should format zero seconds as dash', () => {
36
+ expect(formatDuration(0)).toBe('-');
37
+ });
38
+
39
+ it('should format null/undefined as dash', () => {
40
+ expect(formatDuration(null)).toBe('-');
41
+ expect(formatDuration(undefined)).toBe('-');
42
+ });
43
+
44
+ it('should format negative seconds as dash', () => {
45
+ expect(formatDuration(-10)).toBe('-');
46
+ });
47
+
48
+ it('should format seconds less than 1 minute', () => {
49
+ expect(formatDuration(5)).toBe('0:05');
50
+ expect(formatDuration(30)).toBe('0:30');
51
+ expect(formatDuration(59)).toBe('0:59');
52
+ });
53
+
54
+ it('should format exactly 1 minute', () => {
55
+ expect(formatDuration(60)).toBe('1:00');
56
+ });
57
+
58
+ it('should format minutes and seconds', () => {
59
+ expect(formatDuration(65)).toBe('1:05');
60
+ expect(formatDuration(125)).toBe('2:05');
61
+ expect(formatDuration(185)).toBe('3:05');
62
+ });
63
+
64
+ it('should pad single digit seconds with zero', () => {
65
+ expect(formatDuration(61)).toBe('1:01');
66
+ expect(formatDuration(121)).toBe('2:01');
67
+ });
68
+
69
+ it('should format long durations', () => {
70
+ expect(formatDuration(600)).toBe('10:00');
71
+ expect(formatDuration(3600)).toBe('60:00');
72
+ expect(formatDuration(3665)).toBe('61:05');
73
+ });
74
+
75
+ it('should handle decimal seconds by flooring', () => {
76
+ expect(formatDuration(65.7)).toBe('1:05');
77
+ expect(formatDuration(125.9)).toBe('2:05');
78
+ });
79
+
80
+ it('should format typical song durations', () => {
81
+ expect(formatDuration(180)).toBe('3:00'); // 3 minutes
82
+ expect(formatDuration(225)).toBe('3:45'); // 3:45
83
+ expect(formatDuration(270)).toBe('4:30'); // 4:30
84
+ });
85
+ });
86
+
87
+ describe('formatTime', () => {
88
+ it('should format zero as 0:00.0', () => {
89
+ expect(formatTime(0)).toBe('0:00.0');
90
+ });
91
+
92
+ it('should format null/undefined as 0:00.0', () => {
93
+ expect(formatTime(null)).toBe('0:00.0');
94
+ expect(formatTime(undefined)).toBe('0:00.0');
95
+ });
96
+
97
+ it('should format NaN as 0:00.0', () => {
98
+ expect(formatTime(NaN)).toBe('0:00.0');
99
+ });
100
+
101
+ it('should format seconds less than 1 minute with tenths', () => {
102
+ expect(formatTime(5.0)).toBe('0:05.0');
103
+ expect(formatTime(5.5)).toBe('0:05.5');
104
+ expect(formatTime(5.9)).toBe('0:05.9');
105
+ });
106
+
107
+ it('should format exactly 1 minute', () => {
108
+ expect(formatTime(60)).toBe('1:00.0');
109
+ });
110
+
111
+ it('should format minutes, seconds, and tenths', () => {
112
+ expect(formatTime(65.5)).toBe('1:05.5');
113
+ expect(formatTime(125.7)).toBe('2:05.7');
114
+ });
115
+
116
+ it('should pad single digit seconds with zero', () => {
117
+ expect(formatTime(61.5)).toBe('1:01.5');
118
+ expect(formatTime(121.2)).toBe('2:01.2');
119
+ });
120
+
121
+ it('should extract tenths of seconds', () => {
122
+ expect(formatTime(1.1)).toBe('0:01.1');
123
+ expect(formatTime(1.5)).toBe('0:01.5');
124
+ expect(formatTime(1.9)).toBe('0:01.9');
125
+ });
126
+
127
+ it('should floor tenths (not round)', () => {
128
+ expect(formatTime(1.19)).toBe('0:01.1');
129
+ expect(formatTime(1.99)).toBe('0:01.9');
130
+ });
131
+
132
+ it('should handle long durations', () => {
133
+ expect(formatTime(3665.5)).toBe('61:05.5');
134
+ });
135
+ });
136
+
137
+ describe('formatFileSize', () => {
138
+ it('should format zero bytes', () => {
139
+ expect(formatFileSize(0)).toBe('0 B');
140
+ });
141
+
142
+ it('should format bytes (< 1 KB)', () => {
143
+ expect(formatFileSize(1)).toBe('1 B');
144
+ expect(formatFileSize(500)).toBe('500 B');
145
+ expect(formatFileSize(1023)).toBe('1023 B');
146
+ });
147
+
148
+ it('should format kilobytes', () => {
149
+ expect(formatFileSize(1024)).toBe('1 KB');
150
+ expect(formatFileSize(2048)).toBe('2 KB');
151
+ expect(formatFileSize(1536)).toBe('1.5 KB');
152
+ expect(formatFileSize(10240)).toBe('10 KB');
153
+ });
154
+
155
+ it('should format megabytes', () => {
156
+ expect(formatFileSize(1048576)).toBe('1 MB'); // 1024 * 1024
157
+ expect(formatFileSize(2097152)).toBe('2 MB');
158
+ expect(formatFileSize(5242880)).toBe('5 MB');
159
+ expect(formatFileSize(10485760)).toBe('10 MB');
160
+ });
161
+
162
+ it('should format gigabytes', () => {
163
+ expect(formatFileSize(1073741824)).toBe('1 GB'); // 1024^3
164
+ expect(formatFileSize(2147483648)).toBe('2 GB');
165
+ expect(formatFileSize(5368709120)).toBe('5 GB');
166
+ });
167
+
168
+ it('should round to 1 decimal place', () => {
169
+ expect(formatFileSize(1536)).toBe('1.5 KB');
170
+ expect(formatFileSize(1587200)).toBe('1.5 MB');
171
+ expect(formatFileSize(1610612736)).toBe('1.5 GB');
172
+ });
173
+
174
+ it('should handle fractional sizes', () => {
175
+ expect(formatFileSize(1126)).toBe('1.1 KB');
176
+ expect(formatFileSize(1152921)).toBe('1.1 MB');
177
+ });
178
+
179
+ it('should format typical audio file sizes', () => {
180
+ expect(formatFileSize(3145728)).toBe('3 MB'); // ~3 MB MP3
181
+ expect(formatFileSize(10485760)).toBe('10 MB'); // ~10 MB M4A file
182
+ expect(formatFileSize(52428800)).toBe('50 MB'); // ~50 MB high quality
183
+ });
184
+ });
185
+
186
+ describe('integration scenarios', () => {
187
+ it('should format a complete song info', () => {
188
+ const duration = formatDuration(225); // 3:45
189
+ const position = formatTime(125.5); // 2:05.5
190
+ const size = formatFileSize(5242880); // 5 MB
191
+ const icon = getFormatIcon('m4a-stems');
192
+
193
+ expect(duration).toBe('3:45');
194
+ expect(position).toBe('2:05.5');
195
+ expect(size).toBe('5 MB');
196
+ expect(icon).toBe('⚡');
197
+ });
198
+
199
+ it('should handle edge cases consistently', () => {
200
+ // All functions should handle null/undefined/0 gracefully
201
+ expect(formatDuration(0)).toBe('-');
202
+ expect(formatTime(0)).toBe('0:00.0');
203
+ expect(formatFileSize(0)).toBe('0 B');
204
+ expect(getFormatIcon('')).toBe('🎵');
205
+ });
206
+ });
207
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * useAppState - React hook for subscribing to AppState changes
3
+ *
4
+ * This hook subscribes to state changes for a specific domain via the Bridge
5
+ * and automatically re-renders when that state changes.
6
+ *
7
+ * Usage:
8
+ * const mixerState = useAppState('mixer');
9
+ * const queueState = useAppState('queue');
10
+ * const playbackState = useAppState('playback');
11
+ *
12
+ * The bridge handles the platform-specific subscription mechanism:
13
+ * - ElectronBridge: IPC listeners
14
+ * - WebBridge: Socket.IO listeners
15
+ */
16
+
17
+ import { useState, useEffect } from 'react';
18
+ import { useBridge } from '../context/BridgeContext.jsx';
19
+
20
+ export function useAppState(domain, initialState = null) {
21
+ const bridge = useBridge();
22
+ const [state, setState] = useState(initialState);
23
+ const [loading, setLoading] = useState(true);
24
+ const [error, setError] = useState(null);
25
+
26
+ useEffect(() => {
27
+ let mounted = true;
28
+
29
+ // Fetch initial state
30
+ const fetchInitialState = async () => {
31
+ try {
32
+ let initialData;
33
+
34
+ // Fetch initial state based on domain
35
+ switch (domain) {
36
+ case 'mixer':
37
+ initialData = await bridge.getMixerState();
38
+ break;
39
+ case 'queue':
40
+ initialData = await bridge.getQueue();
41
+ break;
42
+ case 'playback':
43
+ initialData = await bridge.getPlaybackState();
44
+ break;
45
+ case 'effects':
46
+ initialData = await bridge.getEffects();
47
+ break;
48
+ case 'preferences':
49
+ initialData = await bridge.getPreferences();
50
+ break;
51
+ case 'requests':
52
+ initialData = await bridge.getRequests();
53
+ break;
54
+ default:
55
+ console.warn(`No initial fetch method for domain: ${domain}`);
56
+ initialData = null;
57
+ }
58
+
59
+ if (mounted) {
60
+ setState(initialData);
61
+ setLoading(false);
62
+ }
63
+ } catch (err) {
64
+ console.error(`Error fetching initial ${domain} state:`, err);
65
+ if (mounted) {
66
+ setError(err);
67
+ setLoading(false);
68
+ }
69
+ }
70
+ };
71
+
72
+ fetchInitialState();
73
+
74
+ // Subscribe to updates
75
+ const unsubscribe = bridge.onStateChange(domain, (newState) => {
76
+ if (mounted) {
77
+ setState(newState);
78
+ }
79
+ });
80
+
81
+ // Cleanup
82
+ return () => {
83
+ mounted = false;
84
+ unsubscribe();
85
+ };
86
+ }, [domain, bridge]);
87
+
88
+ return { state, loading, error };
89
+ }
90
+
91
+ /**
92
+ * Simplified version that just returns the state (no loading/error)
93
+ */
94
+ export function useAppStateSimple(domain, initialState = null) {
95
+ const { state } = useAppState(domain, initialState);
96
+ return state;
97
+ }