first commit

This commit is contained in:
Adrien MALINGREY 2024-08-20 23:22:34 +02:00
commit 96a49d7c57
3 changed files with 347 additions and 0 deletions

268
index.html Normal file
View 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">&lt;</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">&gt;,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"></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"></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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB