Simple monitor for displaying MIDI status for digital pianos.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

262 Zeilen
7.1 KiB

  1. import { app, BrowserWindow, Menu, ipcMain, } from 'electron'
  2. import path from 'path'
  3. import fs from 'fs'
  4. import SPANS from './services/spans.json'
  5. import SCALE_FACTORS from './services/scaleFactors.json'
  6. import THEMES from './services/themes.json'
  7. import COLORS from './services/colors.json'
  8. import { _ } from './services/messages'
  9. import getKeyName from './services/getKeyName'
  10. import getNaturalKeyCount from './services/getNaturalKeyCount'
  11. import Config from './services/Config'
  12. // @ts-ignore
  13. import electronIsDev = require('electron-is-dev')
  14. // @ts-ignore
  15. import midi = require('midi')
  16. // @ts-ignore
  17. const WINDOW_HEIGHT = 100
  18. let config: Config = {
  19. startKey: 21,
  20. endKey: 108,
  21. scaleFactor: 1,
  22. colorNaturalKey: undefined,
  23. colorAccidentalKey: undefined,
  24. colorHighlight: undefined,
  25. }
  26. const createWindow = () => {
  27. const KEYS_WIDTH = getNaturalKeyCount(config.startKey, config.endKey) * 20
  28. const WINDOW_WIDTH = KEYS_WIDTH + 207
  29. const win = new BrowserWindow({
  30. width: WINDOW_WIDTH,
  31. height: WINDOW_HEIGHT,
  32. useContentSize: true,
  33. backgroundColor: '#000000',
  34. resizable: false,
  35. minimizable: false,
  36. maximizable: false,
  37. fullscreenable: false,
  38. frame: false,
  39. webPreferences: {
  40. devTools: electronIsDev,
  41. nodeIntegration: true,
  42. },
  43. })
  44. const baseUrl: string = (
  45. electronIsDev
  46. ? 'http://localhost:3000'
  47. : `file:///${path.join(__dirname, '../build/index.html')}`
  48. )
  49. const search = new URLSearchParams(config as Record<string, string>)
  50. const url = new URL(baseUrl)
  51. url.search = search.toString()
  52. win.loadURL(url.toString())
  53. if (electronIsDev) {
  54. win.webContents.openDevTools()
  55. }
  56. const input = new midi.Input()
  57. if (input.getPortCount() > 0) {
  58. input.openPort(0)
  59. }
  60. input.on('message', (deltaTime: number, message: unknown[]) => {
  61. const [type, data1, data2] = message
  62. switch (type) {
  63. case 144:
  64. win.webContents.send('note', data1 + ':' + data2)
  65. break
  66. case 176:
  67. win.webContents.send('pedal', data1 + ':' + data2)
  68. break
  69. }
  70. })
  71. }
  72. const reload = () => {
  73. app.relaunch({
  74. args: process.argv.slice(1).concat([
  75. `--startKey=${config.startKey}`,
  76. `--endKey=${config.endKey}`,
  77. `--scaleFactor=${config.scaleFactor}`,
  78. `--colorNaturalKey=${config.colorNaturalKey}`,
  79. `--colorAccidentalKey=${config.colorAccidentalKey}`,
  80. `--colorHighlight=${config.colorHighlight}`,
  81. ])
  82. })
  83. app.exit(0)
  84. }
  85. app
  86. .whenReady()
  87. .then(() => {
  88. try {
  89. const configJsonRaw = fs.readFileSync(path.join(app.getPath('userData'), 'config.json')).toString('utf-8')
  90. config = JSON.parse(configJsonRaw)
  91. } catch (e) {
  92. config = {
  93. startKey: 21,
  94. endKey: 108,
  95. scaleFactor: 1,
  96. }
  97. }
  98. const platformMenu = Menu.buildFromTemplate([
  99. ...(
  100. process.platform === 'darwin'
  101. ? [
  102. {
  103. label: app.name,
  104. submenu: [
  105. { role: 'about' },
  106. { type: 'separator' },
  107. { role: 'services' },
  108. { type: 'separator' },
  109. { role: 'hide' },
  110. { role: 'hideothers' },
  111. { role: 'unhide' },
  112. { type: 'separator' },
  113. { role: 'quit' }
  114. ],
  115. }
  116. ]
  117. : []
  118. ) as object[],
  119. {
  120. label: _('VIEW'),
  121. submenu: [
  122. {
  123. label: _('SPAN'),
  124. submenu: SPANS.map(s => ({
  125. label: `${getKeyName(s.startKey)}–${getKeyName(s.endKey)}`,
  126. sublabel: `${s.endKey - s.startKey + 1}-key`,
  127. type: 'radio',
  128. checked: config.startKey === s.startKey && config.endKey === s.endKey,
  129. click: () => {
  130. config.startKey = s.startKey
  131. config.endKey = s.endKey
  132. reload()
  133. },
  134. }))
  135. },
  136. {
  137. label: _('DETAIL_SCALE_FACTOR'),
  138. submenu: SCALE_FACTORS.map(s => ({
  139. label: `${s}×`,
  140. type: 'radio',
  141. checked: config.scaleFactor === s,
  142. click: () => {
  143. config.scaleFactor = s
  144. reload()
  145. },
  146. })),
  147. },
  148. {
  149. label: _('THEME'),
  150. submenu: [
  151. {
  152. label: _('BASE'),
  153. submenu: [
  154. {
  155. label: _('DEFAULT'),
  156. type: 'radio',
  157. checked: (
  158. typeof config.colorAccidentalKey === 'undefined'
  159. && typeof config.colorNaturalKey === 'undefined'
  160. ),
  161. click: () => {
  162. config.colorNaturalKey = undefined
  163. config.colorAccidentalKey = undefined
  164. reload()
  165. }
  166. },
  167. ...Object.entries(THEMES).map(([key, [colorNaturalKey, colorAccidentalKey]]) => ({
  168. label: _(key),
  169. type: 'radio',
  170. checked: (
  171. config.colorAccidentalKey === colorAccidentalKey
  172. && config.colorNaturalKey === colorNaturalKey
  173. ),
  174. click: () => {
  175. config.colorNaturalKey = colorNaturalKey
  176. config.colorAccidentalKey = colorAccidentalKey
  177. reload()
  178. }
  179. }))
  180. ]
  181. },
  182. {
  183. label: _('HIGHLIGHT'),
  184. submenu: [
  185. {
  186. label: _('NONE'),
  187. type: 'radio',
  188. checked: typeof config.colorHighlight === 'undefined',
  189. click: () => {
  190. config.colorHighlight = undefined
  191. reload()
  192. }
  193. },
  194. ...Object.entries(COLORS).map(([key, colorHighlight]) => ({
  195. label: _(key),
  196. sublabel: colorHighlight,
  197. type: 'radio',
  198. checked: config.colorHighlight === colorHighlight,
  199. click: () => {
  200. config.colorHighlight = colorHighlight
  201. reload()
  202. }
  203. }))
  204. ]
  205. }
  206. ]
  207. }
  208. ]
  209. },
  210. ...(
  211. electronIsDev
  212. ? [
  213. {
  214. label: _('DEBUG'),
  215. submenu: [
  216. { role: 'forceReload' },
  217. { role: 'toggleDevTools' },
  218. ]
  219. }
  220. ]
  221. : []
  222. ) as object[]
  223. ])
  224. Menu.setApplicationMenu(platformMenu)
  225. createWindow()
  226. ipcMain.on('quit', () => {
  227. app.quit()
  228. })
  229. })
  230. app.on('quit', () => {
  231. if (app.commandLine.hasSwitch('discardConfig')) {
  232. return
  233. }
  234. fs.writeFileSync(path.join(app.getPath('userData'), 'config.json'), JSON.stringify(config))
  235. })
  236. app.on('window-all-closed', () => {
  237. app.quit()
  238. })
  239. app.on('activate', () => {
  240. if (BrowserWindow.getAllWindows().length === 0) {
  241. createWindow()
  242. }
  243. })