271 lines
14 KiB
HTML
271 lines
14 KiB
HTML
<!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()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>Vol.</label>
|
|
<label><input type="range" id="modRange" min="0" max="200" step="any" value="20" orient="vertical" oninput="onmodinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>Mod.</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.200" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>1f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.150" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>2f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.050" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>3f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.135" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>4f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.000" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>5f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.127" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>6f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.000" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>7f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.040" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>8f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.000" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>9f</label>
|
|
<label><input type="range" class="partial" min="0" max="0.2" step="any" value="0.040" orient="vertical" oninput="onpartialinput()" onmousedown="playNote(57)" onmouseup="stopNote(57)"/>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,}
|
|
)
|
|
for (const note in oscillators) {
|
|
oscillators[note].setPeriodicWave(wave)
|
|
}
|
|
}
|
|
|
|
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> |