first commit
This commit is contained in:
commit
96a49d7c57
268
index.html
Normal file
268
index.html
Normal file
@ -0,0 +1,268 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Orgue</title>
|
||||
<link rel="icon" href="thumbnail.png">
|
||||
<link rel="stylesheet" href="keyboard.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="sliders">
|
||||
<label><input type="range" id="volRange" min="0" max="1" step="any" value="0.400" orient="vertical" oninput="onvolumeinput()"/>Vol.</label>
|
||||
<label><input type="range" id="modRange" min="0" max="200" step="any" value="20" orient="vertical" oninput="onmodinput()" />Mod.</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.200" orient="vertical" oninput="onpartialinput()"/>1f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.150" orient="vertical" oninput="onpartialinput()"/>2f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.050" orient="vertical" oninput="onpartialinput()"/>3f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.135" orient="vertical" oninput="onpartialinput()"/>4f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.000" orient="vertical" oninput="onpartialinput()"/>5f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.127" orient="vertical" oninput="onpartialinput()"/>6f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.000" orient="vertical" oninput="onpartialinput()"/>7f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.040" orient="vertical" oninput="onpartialinput()"/>8f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.000" orient="vertical" oninput="onpartialinput()"/>9f</label>
|
||||
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.040" orient="vertical" oninput="onpartialinput()"/>10f</label>
|
||||
</div>
|
||||
<div class="keyboard">
|
||||
<button type="button" class="white key" data-note="33"><</button>
|
||||
<button type="button" class="black key" data-note="34">q</button>
|
||||
<button type="button" class="white key" data-note="35">w</button>
|
||||
<button type="button" class="white key" data-note="36">x</button>
|
||||
<button type="button" class="black key" data-note="37">d</button>
|
||||
<button type="button" class="white key" data-note="38">c</button>
|
||||
<button type="button" class="black key" data-note="39">f</button>
|
||||
<button type="button" class="white key" data-note="40">v</button>
|
||||
<button type="button" class="white key" data-note="41">b</button>
|
||||
<button type="button" class="black key" data-note="42">h</button>
|
||||
<button type="button" class="white key" data-note="43">n</button>
|
||||
<button type="button" class="black key" data-note="44">j</button>
|
||||
<button type="button" class="white key" data-note="45">>,a</button>
|
||||
<button type="button" class="black key" data-note="46">Qké</button>
|
||||
<button type="button" class="white key" data-note="47">W;z</button>
|
||||
<button type="button" class="white key" data-note="48">X:e</button>
|
||||
<button type="button" class="black key" data-note="49">Dm'</button>
|
||||
<button type="button" class="white key" data-note="50">C!r</button>
|
||||
<button type="button" class="black key" data-note="51">Fù(</button>
|
||||
<button type="button" class="white key" data-note="52">Vt</button>
|
||||
<button type="button" class="white key" data-note="53">By</button>
|
||||
<button type="button" class="black key" data-note="54">Hè</button>
|
||||
<button type="button" class="white key" data-note="55">Nu</button>
|
||||
<button type="button" class="black key" data-note="56">J_</button>
|
||||
<button type="button" class="white key" data-note="57">A?i</button>
|
||||
<button type="button" class="black key" data-note="58">2Kç</button>
|
||||
<button type="button" class="white key" data-note="59">Z.o</button>
|
||||
<button type="button" class="white key" data-note="60">E/p</button>
|
||||
<button type="button" class="black key" data-note="61">4)M</button>
|
||||
<button type="button" class="white key" data-note="62">R§</button>
|
||||
<button type="button" class="black key" data-note="63">5%</button>
|
||||
<button type="button" class="white key" data-note="64">T$</button>
|
||||
<button type="button" class="white key" data-note="65">Y*</button>
|
||||
<button type="button" class="black key" data-note="66">7</button>
|
||||
<button type="button" class="white key" data-note="67">U</button>
|
||||
<button type="button" class="black key" data-note="68">8</button>
|
||||
<button type="button" class="white key" data-note="69">I</button>
|
||||
<button type="button" class="black key" data-note="70">9</button>
|
||||
<button type="button" class="white key" data-note="71">O</button>
|
||||
<button type="button" class="white key" data-note="72">P</button>
|
||||
</div>
|
||||
|
||||
<label>Utiliser un périphérique MIDI <select id="midiInputSelect" onmousedown="detectMidiInputs()"><option value="">Aucun</option></select></label>
|
||||
|
||||
<script>
|
||||
const frequencies = [
|
||||
// C C♯ / D♭ D D♯ / E♭ E F F♯ / G♭ G G♯ / A♭ A A♯ / B♭ B
|
||||
16.35, 17.32, 18.35, 19.45, 20.6, 21.83, 23.12, 24.5, 25.96, 27.5, 29.14, 30.87,
|
||||
32.7, 34.65, 36.71, 38.89, 41.2, 43.65, 46.25, 49, 51.91, 55, 58.27, 61.74,
|
||||
65.41, 69.3, 73.42, 77.78, 82.41, 87.31, 92.5, 98, 103.83, 110, 116.54, 123.47,
|
||||
130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185, 196, 207.65, 220, 233.08, 246.94,
|
||||
261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392, 415.3, 440, 466.16, 493.88,
|
||||
523.25, 554.37, 587.33, 622.25, 659.26, 698.46, 739.99, 783.99, 830.61, 880, 932.33, 987.77,
|
||||
1046.5, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760, 1864.66, 1975.53,
|
||||
2093, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520, 3729.31, 3951.07,
|
||||
4186.01, 4434.92, 4698.64, 4978.03, 5274.04, 5587.65, 5919.91, 6271.93, 6644.88, 7040, 7458.62, 7902.13,
|
||||
]
|
||||
|
||||
var keyboard = {}
|
||||
let sustain = false
|
||||
let sustainedKeys = new Set()
|
||||
let oscillators = {}
|
||||
var midiIputs = {}
|
||||
|
||||
window.onload = function (event) {
|
||||
for (const key of window.document.getElementsByClassName("key")) {
|
||||
key.pressed = false
|
||||
key.innerText.split("").forEach(k => keyboard[k] = key)
|
||||
|
||||
key.onmousedown = function(event) {
|
||||
if (!key.pressed) {
|
||||
event.preventDefault()
|
||||
onkeydown(key)
|
||||
}
|
||||
}
|
||||
|
||||
key.onmouseup = function(event) {
|
||||
event.preventDefault()
|
||||
onkeyup(key)
|
||||
key.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function detectMidiInputs() {
|
||||
navigator.requestMIDIAccess().then(
|
||||
midiAccess => {
|
||||
midiInputSelect.onmousedown = null
|
||||
for (const entry of midiAccess.inputs) {
|
||||
const input = entry[1]
|
||||
midiIputs[input.id] = input
|
||||
var option = document.createElement("option")
|
||||
option.value = input.id
|
||||
option.innerText = input.name
|
||||
midiInputSelect.appendChild(option)
|
||||
input.onmidimessage = null
|
||||
}
|
||||
|
||||
midiInputSelect.oninput = () => {
|
||||
for (const id in midiIputs) midiIputs[id].onmidimessage = null
|
||||
if (midiInputSelect.value) midiIputs[midiInputSelect.value].onmidimessage = onMIDIMessage
|
||||
}
|
||||
},
|
||||
error => console.log(error)
|
||||
)
|
||||
}
|
||||
|
||||
function onMIDIMessage(event) {
|
||||
let [code, note, velocity] = event.data
|
||||
|
||||
if (144 <= code && code <= 159) {
|
||||
playNote(note, velocity / 128)
|
||||
document.querySelector(`[data-note="${note}"]`)?.classList.add("pressed")
|
||||
} else if (128 <= code && code <= 143) {
|
||||
stopNote(note)
|
||||
document.querySelector(`[data-note="${note}"]`)?.classList.remove("pressed")
|
||||
}
|
||||
}
|
||||
|
||||
var context
|
||||
var volume
|
||||
var wave
|
||||
var mod
|
||||
var depth
|
||||
var compressor
|
||||
|
||||
function init() {
|
||||
context = new AudioContext()
|
||||
|
||||
compressor = context.createDynamicsCompressor()
|
||||
compressor.threshold.setValueAtTime(-50, context.currentTime)
|
||||
compressor.knee.setValueAtTime(40, context.currentTime)
|
||||
compressor.ratio.setValueAtTime(12, context.currentTime)
|
||||
compressor.attack.setValueAtTime(0, context.currentTime)
|
||||
compressor.release.setValueAtTime(0.25, context.currentTime)
|
||||
compressor.connect(context.destination)
|
||||
|
||||
volume = context.createGain()
|
||||
onvolumeinput()
|
||||
volume.connect(compressor)
|
||||
|
||||
mod = context.createOscillator() // the modulating oscillator
|
||||
depth = context.createGain() // the modulator amplifier
|
||||
onmodinput()
|
||||
mod.frequency.value = 6
|
||||
mod.connect(depth)
|
||||
mod.start()
|
||||
|
||||
onpartialinput()
|
||||
}
|
||||
|
||||
function onvolumeinput() {
|
||||
if (!volume) init()
|
||||
else volume.gain.linearRampToValueAtTime(volRange.value, context.currentTime)
|
||||
}
|
||||
|
||||
function onmodinput() {
|
||||
if (!mod) init()
|
||||
else depth.gain.value = modRange.value
|
||||
}
|
||||
|
||||
function onpartialinput() {
|
||||
if (!context) init()
|
||||
|
||||
wave = context.createPeriodicWave(
|
||||
[0].concat(Array.from(document.querySelectorAll(".partial")).map(range => range.value)),
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
{disableNormalization: false,}
|
||||
)
|
||||
}
|
||||
|
||||
function onkeydown(key) {
|
||||
if (key.pressed) return
|
||||
|
||||
key.pressed = true
|
||||
key.classList.add("pressed")
|
||||
|
||||
playNote(key.getAttribute("data-note"))
|
||||
}
|
||||
|
||||
function playNote(note, velocity=0.7) {
|
||||
if(oscillators[note]) return
|
||||
|
||||
if (!context) init()
|
||||
var oscillator = context.createOscillator()
|
||||
oscillator.frequency.value = frequencies[note]
|
||||
oscillator.setPeriodicWave(wave)
|
||||
|
||||
oscillator.velocity = context.createGain()
|
||||
oscillator.velocity.gain.value = 0
|
||||
oscillator.velocity.gain.linearRampToValueAtTime(velocity, context.currentTime + 0.05)
|
||||
oscillator.connect(oscillator.velocity)
|
||||
oscillator.start()
|
||||
oscillator.velocity.connect(volume)
|
||||
|
||||
depth.connect(oscillator.detune)
|
||||
|
||||
oscillators[note] = oscillator
|
||||
}
|
||||
|
||||
function onkeyup(key) {
|
||||
if (sustain) {
|
||||
sustainedKeys.add(key)
|
||||
} else {
|
||||
stopNote(key.getAttribute("data-note"))
|
||||
}
|
||||
key.pressed = false
|
||||
key.classList.remove("pressed")
|
||||
}
|
||||
function stopNote(note) {
|
||||
if(!oscillators[note]) return
|
||||
|
||||
velocity = oscillators[note].velocity.gain.value
|
||||
oscillators[note].velocity.gain.setValueCurveAtTime([velocity, velocity/10, velocity/20, 0], context.currentTime + 0.1, 0.5)
|
||||
delete(oscillators[note])
|
||||
}
|
||||
|
||||
window.document.onkeydown = function(event) {
|
||||
if (event.key in keyboard) {
|
||||
event.preventDefault()
|
||||
onkeydown(keyboard[event.key])
|
||||
} else if (event.key == " ") {
|
||||
sustain = true
|
||||
}
|
||||
}
|
||||
|
||||
window.document.onkeyup = function(event) {
|
||||
if (event.key in keyboard) {
|
||||
event.preventDefault()
|
||||
onkeyup(keyboard[event.key])
|
||||
} else if (event.key == " ") {
|
||||
sustain = false
|
||||
sustainedKeys.forEach(key => {
|
||||
key.oscillator?.stop()
|
||||
})
|
||||
sustainedKeys.clear()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
79
keyboard.css
Normal file
79
keyboard.css
Normal file
@ -0,0 +1,79 @@
|
||||
body {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
.keyboard {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.key{
|
||||
display: flex;
|
||||
border-width: 3px;
|
||||
border-radius: 5px;
|
||||
justify-content: end;
|
||||
padding: 10px;
|
||||
color: gray;
|
||||
writing-mode: vertical-lr;
|
||||
text-orientation: upright;
|
||||
align-items: center;
|
||||
letter-spacing: -.15em;
|
||||
}
|
||||
|
||||
.key:not([data-note*="#"]),
|
||||
.white.key {
|
||||
width: 50px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.key[data-note*="#"],
|
||||
.black.key {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 150px;
|
||||
background-color: #111;
|
||||
border-color: black;
|
||||
top: -6px;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.key.pressed {
|
||||
padding-left: 13px;
|
||||
padding-top: 13px;
|
||||
padding-right: 7px;
|
||||
padding-bottom: 7px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.key:not([data-note*="#"]).pressed,
|
||||
.white.key.pressed {
|
||||
margin-left: 1px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.key[data-note*="#"].pressed,
|
||||
.black.key.pressed {
|
||||
margin-left: -19px;
|
||||
margin-right: -21px;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.sliders label {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[orient="vertical"] {
|
||||
-webkit-appearance: slider-vertical;
|
||||
}
|
BIN
thumbnail.png
Normal file
BIN
thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
Loading…
x
Reference in New Issue
Block a user