Separate styles from template, use png images for better look and feel.master
@@ -0,0 +1,3 @@ | |||||
appId: 'sh.modal.apps.pianomidimonitor' | |||||
productName: 'Piano MIDI Monitor' | |||||
copyright: 'Copyright © 2020 TheoryOfNekomata' |
@@ -7,59 +7,12 @@ | |||||
http-equiv="Content-Security-Policy" | http-equiv="Content-Security-Policy" | ||||
content="script-src 'self' 'unsafe-inline';" | content="script-src 'self' 'unsafe-inline';" | ||||
/> | /> | ||||
<style> | |||||
html { | |||||
height: 100%; | |||||
width: 100%; | |||||
} | |||||
body { | |||||
margin: 0; | |||||
display: flex; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
svg { | |||||
fill: currentColor; | |||||
} | |||||
.natural { | |||||
background-color: #eee; | |||||
border: 1px solid; | |||||
height: 100%; | |||||
box-sizing: border-box; | |||||
background-image: url('./piano/natural.png'); | |||||
} | |||||
.accidental { | |||||
background-color: #222; | |||||
border: 1px solid; | |||||
height: 65%; | |||||
box-sizing: border-box; | |||||
background-image: url('./piano/accidental.png'); | |||||
} | |||||
#pedals { | |||||
display: flex; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
width: 0; | |||||
flex: auto; | |||||
padding: 0 1rem; | |||||
box-sizing: border-box; | |||||
background-color: black; | |||||
color: white; | |||||
height: 100%; | |||||
} | |||||
</style> | |||||
<link rel="stylesheet" href="./style.css"> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<div | <div | ||||
id="keyboard" | id="keyboard" | ||||
style="width: 944px; height: 100%; position: relative;" | |||||
style="width: 1008px; height: 100%; position: relative;" | |||||
></div> | ></div> | ||||
<div id="pedals"> | <div id="pedals"> | ||||
<svg | <svg | ||||
@@ -126,105 +79,6 @@ | |||||
</svg> | </svg> | ||||
</div> | </div> | ||||
<script> | |||||
const X_OFFSET = 0 | |||||
const NATURAL_KEY_WIDTH = 15; | |||||
const ACCIDENTAL_KEY_WIDTH = 9; | |||||
const accidental_key_x_offsets = [ | |||||
10.0 / 112.0, | |||||
28.0 / 112.0, | |||||
57.0 / 112.0, | |||||
75.0 / 112.0, | |||||
93.0 / 112.0, | |||||
] | |||||
const { ipcRenderer, } = require('electron') | |||||
const renderKeys = () => { | |||||
let midi_note = 12 | |||||
let element | |||||
const keyboard = window.document.getElementById('keyboard') | |||||
for (let octave = 0; octave < 9; octave += 1) { | |||||
for (let natural_key = 0; natural_key < 7; natural_key += 1) { | |||||
element = window.document.createElement('div') | |||||
element.setAttribute('id', 'n' + midi_note) | |||||
element.classList.add('natural') | |||||
element.style.position = 'absolute' | |||||
element.style.top = 0 | |||||
element.style.left = X_OFFSET + (octave * NATURAL_KEY_WIDTH * 7) + (natural_key * NATURAL_KEY_WIDTH - 1) + | |||||
'px' | |||||
element.style.width = NATURAL_KEY_WIDTH + 1 + 'px' | |||||
keyboard.appendChild(element) | |||||
switch (natural_key) { | |||||
case 0: | |||||
case 1: | |||||
case 3: | |||||
case 4: | |||||
case 5: | |||||
midi_note += 2; | |||||
break; | |||||
case 2: | |||||
case 6: | |||||
midi_note += 1; | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
midi_note -= 11; | |||||
for (let accidental_key = 0; accidental_key < 5; accidental_key += 1) { | |||||
element = window.document.createElement('div') | |||||
element.classList.add('accidental') | |||||
element.setAttribute('id', 'n' + midi_note) | |||||
element.style.position = 'absolute' | |||||
element.style.top = 0 | |||||
element.style.left = X_OFFSET + (octave * NATURAL_KEY_WIDTH * 7) + (NATURAL_KEY_WIDTH * 7 * | |||||
accidental_key_x_offsets[accidental_key]) + 'px' | |||||
element.style.width = ACCIDENTAL_KEY_WIDTH + 'px' | |||||
keyboard.appendChild(element) | |||||
switch (accidental_key) { | |||||
case 0: | |||||
case 2: | |||||
case 3: | |||||
midi_note += 2; | |||||
break; | |||||
case 1: | |||||
case 4: | |||||
midi_note += 3; | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
midi_note -= 1; | |||||
} | |||||
} | |||||
ipcRenderer.on('note', (event, message) => { | |||||
const [key, value, ] = message.split(':') | |||||
window.document.getElementById('n' + key).style.backgroundColor = value > 0 ? 'red' : null | |||||
}) | |||||
renderKeys(null, null) | |||||
ipcRenderer.on('pedal', (event, message) => { | |||||
console.log(message) | |||||
const [which, value, ] = message.split(':') | |||||
const pedalIds = { | |||||
64: 'sustain', | |||||
66: 'sostenuto', | |||||
67: 'unacorda', | |||||
} | |||||
console.log(window.document.getElementById(pedalIds[which])) | |||||
window.document.getElementById(pedalIds[which]).style.opacity = value / 127 * 0.75 + 0.25 | |||||
}) | |||||
</script> | |||||
<script src="./script.js"></script> | |||||
</body> | </body> | ||||
</html> | </html> |
@@ -1,19 +1,26 @@ | |||||
const { app, BrowserWindow } = require('electron') | const { app, BrowserWindow } = require('electron') | ||||
const midi = require('midi') | const midi = require('midi') | ||||
const SCREEN_HEIGHT = 96 | |||||
const SCREEN_WIDTH = 1152 | |||||
const SCREEN_HEIGHT = 64 | |||||
const SCREEN_WIDTH = 1200 | |||||
const createWindow = () => { | const createWindow = () => { | ||||
const win = new BrowserWindow({ | const win = new BrowserWindow({ | ||||
width: SCREEN_WIDTH, | width: SCREEN_WIDTH, | ||||
height: SCREEN_HEIGHT, | height: SCREEN_HEIGHT, | ||||
useContentSize: true, | |||||
backgroundColor: '#000000', | |||||
resizable: false, | |||||
minimizable: false, | |||||
maximizable: false, | |||||
fullscreenable: false, | |||||
webPreferences: { | webPreferences: { | ||||
nodeIntegration: true | |||||
//devTools: false, | |||||
nodeIntegration: true, | |||||
} | } | ||||
}) | }) | ||||
win.loadFile('pages/piano.html') | |||||
win.loadFile('index.html') | |||||
const input = new midi.Input() | const input = new midi.Input() | ||||
@@ -7,11 +7,13 @@ | |||||
"license": "MIT", | "license": "MIT", | ||||
"private": true, | "private": true, | ||||
"scripts": { | "scripts": { | ||||
"start": "electron ." | |||||
"start": "electron .", | |||||
"pack": "electron-builder --dir", | |||||
"build": "electron-builder" | |||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"electron": "^9.1.2", | "electron": "^9.1.2", | ||||
"electron-rebuild": "^1.11.0" | |||||
"electron-builder": "^22.8.0" | |||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"midi": "^1.0.0" | "midi": "^1.0.0" | ||||
@@ -0,0 +1,112 @@ | |||||
((window, electron) => { | |||||
const X_OFFSET = 0 | |||||
const NATURAL_KEY_WIDTH = 16; | |||||
const ACCIDENTAL_KEY_WIDTH = 11; | |||||
const accidental_key_x_offsets = [ | |||||
10.0 / 112.0, | |||||
28.0 / 112.0, | |||||
57.0 / 112.0, | |||||
75.0 / 112.0, | |||||
93.0 / 112.0, | |||||
] | |||||
const { ipcRenderer, } = electron | |||||
const renderKeys = () => { | |||||
let midi_note = 12 | |||||
const elements = {} | |||||
let element | |||||
const keyboard = window.document.getElementById('keyboard') | |||||
for (let octave = 0; octave < 9; octave += 1) { | |||||
for (let natural_key = 0; natural_key < 7; natural_key += 1) { | |||||
element = window.document.createElement('div') | |||||
element.classList.add('natural') | |||||
element.classList.add('key') | |||||
element.style.position = 'absolute' | |||||
element.style.top = 0 | |||||
element.style.left = X_OFFSET + (octave * NATURAL_KEY_WIDTH * 7) + (natural_key * NATURAL_KEY_WIDTH) + | |||||
'px' | |||||
element.style.width = NATURAL_KEY_WIDTH + 'px' | |||||
element.appendChild(window.document.createElement('div')) | |||||
elements[midi_note] = element | |||||
keyboard.appendChild(element) | |||||
switch (natural_key) { | |||||
case 0: | |||||
case 1: | |||||
case 3: | |||||
case 4: | |||||
case 5: | |||||
midi_note += 2; | |||||
break; | |||||
case 2: | |||||
case 6: | |||||
midi_note += 1; | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
midi_note -= 11; | |||||
for (let accidental_key = 0; accidental_key < 5; accidental_key += 1) { | |||||
element = window.document.createElement('div') | |||||
element.classList.add('accidental') | |||||
element.classList.add('key') | |||||
element.style.position = 'absolute' | |||||
element.style.top = 0 | |||||
element.style.left = X_OFFSET + (octave * NATURAL_KEY_WIDTH * 7) + (NATURAL_KEY_WIDTH * 7 * | |||||
accidental_key_x_offsets[accidental_key]) + 'px' | |||||
element.style.width = ACCIDENTAL_KEY_WIDTH + 'px' | |||||
element.appendChild(window.document.createElement('div')) | |||||
elements[midi_note] = element | |||||
keyboard.appendChild(element) | |||||
switch (accidental_key) { | |||||
case 0: | |||||
case 2: | |||||
case 3: | |||||
midi_note += 2; | |||||
break; | |||||
case 1: | |||||
case 4: | |||||
midi_note += 3; | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
midi_note -= 1; | |||||
} | |||||
return elements | |||||
} | |||||
const elements = renderKeys(null, null) | |||||
ipcRenderer.on('note', (event, message) => { | |||||
const [key, value, ] = message.split(':') | |||||
if (value > 0) { | |||||
elements[key].classList.add('pressed') | |||||
} else { | |||||
elements[key].classList.remove('pressed') | |||||
} | |||||
}) | |||||
ipcRenderer.on('pedal', (event, message) => { | |||||
console.log(message) | |||||
const [which, value, ] = message.split(':') | |||||
const pedalIds = { | |||||
64: 'sustain', | |||||
66: 'sostenuto', | |||||
67: 'unacorda', | |||||
} | |||||
console.log(window.document.getElementById(pedalIds[which])) | |||||
window.document.getElementById(pedalIds[which]).style.opacity = value / 127 * 0.75 + 0.25 | |||||
}) | |||||
})(window, require('electron')) |
@@ -0,0 +1,83 @@ | |||||
html { | |||||
height: 100%; | |||||
width: 100%; | |||||
} | |||||
body { | |||||
margin: 0; | |||||
display: flex; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
svg { | |||||
fill: currentColor; | |||||
} | |||||
.natural { | |||||
height: 100%; | |||||
box-sizing: border-box; | |||||
background-image: url('./natural.png'); | |||||
background-color: black; | |||||
z-index: 0; | |||||
} | |||||
.accidental { | |||||
height: calc(41 / 64 * 100%); | |||||
box-sizing: border-box; | |||||
background-image: url('./accidental.png'); | |||||
z-index: 2; | |||||
} | |||||
.key > * { | |||||
width: 100%; | |||||
height: 100%; | |||||
opacity: 0; | |||||
background-color: lime; | |||||
border-radius: 0 0 1px 1px; | |||||
background-clip: content-box; | |||||
box-sizing: border-box; | |||||
} | |||||
.natural.key > * { | |||||
padding: 2px 0 0 1px; | |||||
mix-blend-mode: multiply; | |||||
} | |||||
.accidental.key > * { | |||||
padding: 2px 0 1px 1px; | |||||
mix-blend-mode: hard-light; | |||||
} | |||||
.natural.key.pressed { | |||||
z-index: 1; | |||||
} | |||||
.accidental.key.pressed { | |||||
z-index: 3; | |||||
} | |||||
.natural.key.pressed > * { | |||||
opacity: 0.85; | |||||
} | |||||
.accidental.key.pressed > * { | |||||
opacity: 0.5; | |||||
} | |||||
#pedals { | |||||
display: flex; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
width: 0; | |||||
flex: auto; | |||||
padding: 0 1rem; | |||||
box-sizing: border-box; | |||||
background-color: black; | |||||
color: white; | |||||
height: 100%; | |||||
} |