Simple monitor for displaying MIDI status for digital pianos.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

195 regels
4.9 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 { _ } from './services/messages'
  7. import getKeyName from './services/getKeyName'
  8. import getNaturalKeyCount from './services/getNaturalKeyCount'
  9. import Config from './services/Config'
  10. // @ts-ignore
  11. import electronIsDev = require('electron-is-dev')
  12. // @ts-ignore
  13. import midi = require('midi')
  14. // @ts-ignore
  15. const WINDOW_HEIGHT = 100
  16. let config: Config = {
  17. startKey: 21,
  18. endKey: 108,
  19. scaleFactor: 1,
  20. }
  21. const createWindow = () => {
  22. const KEYS_WIDTH = getNaturalKeyCount(config.startKey, config.endKey) * 20
  23. const WINDOW_WIDTH = KEYS_WIDTH + 207
  24. const win = new BrowserWindow({
  25. width: WINDOW_WIDTH,
  26. height: WINDOW_HEIGHT,
  27. useContentSize: true,
  28. backgroundColor: '#000000',
  29. resizable: false,
  30. minimizable: false,
  31. maximizable: false,
  32. fullscreenable: false,
  33. frame: false,
  34. webPreferences: {
  35. devTools: electronIsDev,
  36. nodeIntegration: true,
  37. },
  38. })
  39. const baseUrl: string = (
  40. electronIsDev
  41. ? 'http://localhost:3000'
  42. : `file:///${path.join(__dirname, '../build/index.html')}`
  43. )
  44. const search = new URLSearchParams(config as Record<string, string>)
  45. const url = new URL(baseUrl)
  46. url.search = search.toString()
  47. win.loadURL(url.toString())
  48. if (electronIsDev) {
  49. win.webContents.openDevTools()
  50. }
  51. const input = new midi.Input()
  52. if (input.getPortCount() > 0) {
  53. input.openPort(0)
  54. }
  55. input.on('message', (deltaTime: number, message: unknown[]) => {
  56. const [type, data1, data2] = message
  57. switch (type) {
  58. case 144:
  59. win.webContents.send('note', data1 + ':' + data2)
  60. break
  61. case 176:
  62. win.webContents.send('pedal', data1 + ':' + data2)
  63. break
  64. }
  65. })
  66. }
  67. app
  68. .whenReady()
  69. .then(() => {
  70. try {
  71. const configJsonRaw = fs.readFileSync('./config.json').toString('utf-8')
  72. config = JSON.parse(configJsonRaw)
  73. } catch (e) {
  74. config = {
  75. startKey: 21,
  76. endKey: 108,
  77. scaleFactor: 1,
  78. }
  79. }
  80. const platformMenu = Menu.buildFromTemplate([
  81. ...(
  82. process.platform === 'darwin'
  83. ? [
  84. {
  85. label: app.name,
  86. submenu: [
  87. { role: 'about' },
  88. { type: 'separator' },
  89. { role: 'services' },
  90. { type: 'separator' },
  91. { role: 'hide' },
  92. { role: 'hideothers' },
  93. { role: 'unhide' },
  94. { type: 'separator' },
  95. { role: 'quit' }
  96. ],
  97. }
  98. ]
  99. : []
  100. ) as object[],
  101. {
  102. label: _('VIEW'),
  103. submenu: [
  104. {
  105. label: _('SPAN'),
  106. submenu: SPANS.map(s => ({
  107. label: `${getKeyName(s.startKey)}–${getKeyName(s.endKey)}`,
  108. sublabel: `${s.endKey - s.startKey + 1}-key`,
  109. type: 'radio',
  110. checked: config.startKey === s.startKey && config.endKey === s.endKey,
  111. click: () => {
  112. config.startKey = s.startKey
  113. config.endKey = s.endKey
  114. app.relaunch({
  115. args: process.argv.slice(1).concat([
  116. `--startKey=${config.startKey}`,
  117. `--endKey=${config.endKey}`,
  118. `--scaleFactor=${config.scaleFactor}`,
  119. ])
  120. })
  121. app.exit(0)
  122. },
  123. }))
  124. },
  125. {
  126. label: _('DETAIL_SCALE_FACTOR'),
  127. submenu: SCALE_FACTORS.map(s => ({
  128. label: `${s}×`,
  129. type: 'radio',
  130. checked: config.scaleFactor === s,
  131. click: () => {
  132. config.scaleFactor = s
  133. app.relaunch({
  134. args: process.argv.slice(1).concat([
  135. `--startKey=${config.startKey}`,
  136. `--endKey=${config.endKey}`,
  137. `--scaleFactor=${config.scaleFactor}`,
  138. ])
  139. })
  140. app.exit(0)
  141. },
  142. })),
  143. },
  144. ]
  145. },
  146. ...(
  147. electronIsDev
  148. ? [
  149. {
  150. label: _('DEBUG'),
  151. submenu: [
  152. { role: 'forceReload' },
  153. { role: 'toggleDevTools' },
  154. ]
  155. }
  156. ]
  157. : []
  158. ) as object[]
  159. ])
  160. Menu.setApplicationMenu(platformMenu)
  161. createWindow()
  162. ipcMain.on('quit', () => {
  163. app.quit()
  164. })
  165. })
  166. app.on('quit', () => {
  167. fs.writeFileSync('./config.json', JSON.stringify(config))
  168. })
  169. app.on('window-all-closed', () => {
  170. app.quit()
  171. })
  172. app.on('activate', () => {
  173. if (BrowserWindow.getAllWindows().length === 0) {
  174. createWindow()
  175. }
  176. })