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" | |||
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> | |||
<body> | |||
<div | |||
id="keyboard" | |||
style="width: 944px; height: 100%; position: relative;" | |||
style="width: 1008px; height: 100%; position: relative;" | |||
></div> | |||
<div id="pedals"> | |||
<svg | |||
@@ -126,105 +79,6 @@ | |||
</svg> | |||
</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> | |||
</html> |
@@ -1,19 +1,26 @@ | |||
const { app, BrowserWindow } = require('electron') | |||
const midi = require('midi') | |||
const SCREEN_HEIGHT = 96 | |||
const SCREEN_WIDTH = 1152 | |||
const SCREEN_HEIGHT = 64 | |||
const SCREEN_WIDTH = 1200 | |||
const createWindow = () => { | |||
const win = new BrowserWindow({ | |||
width: SCREEN_WIDTH, | |||
height: SCREEN_HEIGHT, | |||
useContentSize: true, | |||
backgroundColor: '#000000', | |||
resizable: false, | |||
minimizable: false, | |||
maximizable: false, | |||
fullscreenable: false, | |||
webPreferences: { | |||
nodeIntegration: true | |||
//devTools: false, | |||
nodeIntegration: true, | |||
} | |||
}) | |||
win.loadFile('pages/piano.html') | |||
win.loadFile('index.html') | |||
const input = new midi.Input() | |||
@@ -7,11 +7,13 @@ | |||
"license": "MIT", | |||
"private": true, | |||
"scripts": { | |||
"start": "electron ." | |||
"start": "electron .", | |||
"pack": "electron-builder --dir", | |||
"build": "electron-builder" | |||
}, | |||
"devDependencies": { | |||
"electron": "^9.1.2", | |||
"electron-rebuild": "^1.11.0" | |||
"electron-builder": "^22.8.0" | |||
}, | |||
"dependencies": { | |||
"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%; | |||
} |