Spaces:
Running
Running
<html><head><base href="https://piano.midi/webmidi@latest/flex-horizontal/use-event.note.name+event.note.octave__not.value/in_getKeys_just_do_straight_comparison/whiteKeys_and_blackKeys_are_nodeLists_not_arrays/position_fix_keyboard_above_text/playable-with-mouse-too/hide%3Ccomments%3Eand%3Clinks%3E/drop_piano_2_octaves/abnormify_sound/push_audio_creativity/explore_tonejs_instruments/drop_piano_decibels_by_10/add_higher_octave/hush_comments"><title>Sonic Artistry</title> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
background: radial-gradient(circle at center, #111, #333); | |
color: #fff; | |
font-family: monospace; | |
overflow: hidden; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
height: 100vh; | |
} | |
#instrumentSelector { | |
position: absolute; | |
top: 10px; | |
left: 10px; | |
background-color: rgba(255, 255, 255, 0.1); | |
padding: 10px; | |
border-radius: 5px; | |
z-index: 1; | |
} | |
#keyboardContainer { | |
display: flex; | |
flex-direction: row; | |
align-items: flex-end; | |
margin-bottom: 20px; | |
} | |
.key { | |
width: 50px; | |
height: 200px; | |
background: linear-gradient(to bottom, #eee, #ccc); | |
border: 1px solid #000; | |
box-sizing: border-box; | |
cursor: pointer; | |
transition: background-color 0.2s, transform 0.2s; | |
position: relative; | |
transform-style: preserve-3d; | |
display: flex; | |
justify-content: center; | |
align-items: flex-end; | |
padding-bottom: 10px; | |
font-size: 12px; | |
} | |
.key::after { | |
content: attr(data-note); | |
} | |
.key.black { | |
width: 30px; | |
height: 120px; | |
background: linear-gradient(to bottom, #555, #222); | |
margin-right: -15px; | |
margin-left: -15px; | |
z-index: 1; | |
} | |
.key:hover { | |
background: linear-gradient(to bottom, #ddd, #bbb); | |
} | |
.key.black:hover { | |
background: linear-gradient(to bottom, #777, #444); | |
} | |
.key.pressed { | |
background: linear-gradient(to bottom, #ccc, #aaa); | |
transform: translateZ(20px); | |
} | |
.key.black.pressed { | |
background: linear-gradient(to bottom, #666, #333); | |
transform: translateZ(20px); | |
} | |
.key::before { | |
content: ""; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
width: 0; | |
height: 0; | |
background-color: rgba(255, 255, 255, 0.5); | |
border-radius: 50%; | |
opacity: 0; | |
transition: width 0.2s, height 0.2s, opacity 0.2s; | |
} | |
.key.pressed::before { | |
width: 100px; | |
height: 100px; | |
opacity: 1; | |
} | |
.key.black::before { | |
background-color: rgba(0, 0, 0, 0.5); | |
} | |
</style> | |
</head> | |
<body> | |
<div id="instrumentSelector"> | |
<label for="instrumentSelect">Select Instrument:</label> | |
<select id="instrumentSelect"> | |
<option value="AMSynth">AM Synth</option> | |
<option value="FMSynth">FM Synth</option> | |
<option value="DuoSynth">Duo Synth</option> | |
<option value="MonoSynth">Mono Synth</option> | |
<option value="PluckSynth">Pluck Synth</option> | |
<option value="Membrane">Membrane Synth</option> | |
<option value="MetalSynth">Metal Synth</option> | |
<option value="NoiseSynth">Noise Synth</option> | |
</select> | |
</div> | |
<div id="keyboardContainer"> | |
<div class="key white" data-note="C2" data-midi="36"></div> | |
<div class="key black" data-note="C#2" data-midi="37"></div> | |
<div class="key white" data-note="D2" data-midi="38"></div> | |
<div class="key black" data-note="D#2" data-midi="39"></div> | |
<div class="key white" data-note="E2" data-midi="40"></div> | |
<div class="key white" data-note="F2" data-midi="41"></div> | |
<div class="key black" data-note="F#2" data-midi="42"></div> | |
<div class="key white" data-note="G2" data-midi="43"></div> | |
<div class="key black" data-note="G#2" data-midi="44"></div> | |
<div class="key white" data-note="A2" data-midi="45"></div> | |
<div class="key black" data-note="A#2" data-midi="46"></div> | |
<div class="key white" data-note="B2" data-midi="47"></div> | |
<div class="key white" data-note="C3" data-midi="48"></div> | |
<div class="key black" data-note="C#3" data-midi="49"></div> | |
<div class="key white" data-note="D3" data-midi="50"></div> | |
<div class="key black" data-note="D#3" data-midi="51"></div> | |
<div class="key white" data-note="E3" data-midi="52"></div> | |
<div class="key white" data-note="F3" data-midi="53"></div> | |
<div class="key black" data-note="F#3" data-midi="54"></div> | |
<div class="key white" data-note="G3" data-midi="55"></div> | |
<div class="key black" data-note="G#3" data-midi="56"></div> | |
<div class="key white" data-note="A3" data-midi="57"></div> | |
<div class="key black" data-note="A#3" data-midi="58"></div> | |
<div class="key white" data-note="B3" data-midi="59"></div> | |
<div class="key white" data-note="C4" data-midi="60"></div> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/Tone.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/webmidi@latest/dist/iife/webmidi.iife.min.js"></script> | |
<script> | |
const whiteKeys = document.querySelectorAll('.key.white'); | |
const blackKeys = document.querySelectorAll('.key.black'); | |
const instrumentSelect = document.getElementById('instrumentSelect'); | |
let synth; | |
const reverb = new Tone.Reverb({ decay: 5, wet: 0.5 }).toDestination(); | |
const delay = new Tone.FeedbackDelay({ delayTime: 0.5, feedback: 0.25, wet: 0.25 }).toDestination(); | |
function initSynth(instrumentType) { | |
if (synth) { | |
synth.disconnect(); | |
} | |
switch (instrumentType) { | |
case 'AMSynth': | |
synth = new Tone.PolySynth(Tone.AMSynth, { polyphony: 32, volume: -10 }).toDestination(); | |
break; | |
case 'FMSynth': | |
synth = new Tone.PolySynth(Tone.FMSynth, { polyphony: 32, volume: -10 }).toDestination(); | |
break; | |
case 'DuoSynth': | |
synth = new Tone.PolySynth(Tone.DuoSynth, { polyphony: 32, volume: -10 }).toDestination(); | |
break; | |
case 'MonoSynth': | |
synth = new Tone.MonoSynth({ volume: -10 }).toDestination(); | |
break; | |
case 'PluckSynth': | |
synth = new Tone.PluckSynth({ volume: -10 }).toDestination(); | |
break; | |
case 'Membrane': | |
synth = new Tone.PolySynth(Tone.MembraneSynth, { polyphony: 32, volume: -10 }).toDestination(); | |
break; | |
case 'MetalSynth': | |
synth = new Tone.PolySynth(Tone.MetalSynth, { polyphony: 32, volume: -10 }).toDestination(); | |
break; | |
case 'NoiseSynth': | |
synth = new Tone.NoiseSynth({ volume: -10 }).toDestination(); | |
break; | |
default: | |
synth = new Tone.PolySynth(Tone.AMSynth, { polyphony: 32, volume: -10 }).toDestination(); | |
} | |
synth.connect(reverb); | |
synth.connect(delay); | |
} | |
initSynth(instrumentSelect.value); | |
instrumentSelect.addEventListener('change', (event) => { | |
initSynth(event.target.value); | |
}); | |
let midiAccess, midiInput; | |
WebMidi.enable(err => { | |
if (err) { | |
console.error('Failed to enable WebMIDI:', err); | |
return; | |
} | |
console.log('WebMIDI enabled!'); | |
midiInput = WebMidi.inputs[0]; | |
if (!midiInput) { | |
console.warn('No MIDI input device detected.'); | |
return; | |
} | |
console.log(`Connected to MIDI input device: ${midiInput.name}`); | |
midiInput.addListener('noteon', 'all', event => { | |
const midiNoteValue = `${event.note.name}${event.note.octave}`; | |
handleNoteOn(midiNoteValue, event.velocity / 127); | |
}); | |
midiInput.addListener('noteoff', 'all', event => { | |
const midiNoteValue = `${event.note.name}${event.note.octave}`; | |
handleNoteOff(midiNoteValue); | |
}); | |
}); | |
function handleNoteOn(midiNoteValue, velocity) { | |
const whiteKey = Array.from(whiteKeys).find(key => key.dataset.note === midiNoteValue); | |
const blackKey = Array.from(blackKeys).find(key => key.dataset.note === midiNoteValue); | |
if (whiteKey) { | |
whiteKey.classList.add('pressed'); | |
synth.triggerAttack(noteToFrequency(whiteKey.dataset.midi), velocity); | |
} else if (blackKey) { | |
blackKey.classList.add('pressed'); | |
synth.triggerAttack(noteToFrequency(blackKey.dataset.midi), velocity); | |
} | |
} | |
function handleNoteOff(midiNoteValue) { | |
const whiteKey = Array.from(whiteKeys).find(key => key.dataset.note === midiNoteValue); | |
const blackKey = Array.from(blackKeys).find(key => key.dataset.note === midiNoteValue); | |
if (whiteKey) { | |
whiteKey.classList.remove('pressed'); | |
synth.triggerRelease(noteToFrequency(whiteKey.dataset.midi)); | |
} else if (blackKey) { | |
blackKey.classList.remove('pressed'); | |
synth.triggerRelease(noteToFrequency(blackKey.dataset.midi)); | |
} | |
} | |
function whiteKeyMouseDown(event) { | |
const key = event.target; | |
key.classList.add('pressed'); | |
synth.triggerAttack(noteToFrequency(key.dataset.midi), 0.5); | |
} | |
function whiteKeyMouseUp(event) { | |
const key = event.target; | |
key.classList.remove('pressed'); | |
synth.triggerRelease(noteToFrequency(key.dataset.midi)); | |
} | |
function blackKeyMouseDown(event) { | |
const key = event.target; | |
key.classList.add('pressed'); | |
synth.triggerAttack(noteToFrequency(key.dataset.midi), 0.5); | |
} | |
function blackKeyMouseUp(event) { | |
const key = event.target; | |
key.classList.remove('pressed'); | |
synth.triggerRelease(noteToFrequency(key.dataset.midi)); | |
} | |
whiteKeys.forEach(key => { | |
key.addEventListener('mousedown', whiteKeyMouseDown); | |
key.addEventListener('mouseup', whiteKeyMouseUp); | |
key.addEventListener('mouseleave', whiteKeyMouseUp); | |
}); | |
blackKeys.forEach(key => { | |
key.addEventListener('mousedown', blackKeyMouseDown); | |
key.addEventListener('mouseup', blackKeyMouseUp); | |
key.addEventListener('mouseleave', blackKeyMouseUp); | |
}); | |
function noteToFrequency(note) { | |
const baseFrequency = 440; | |
return baseFrequency * Math.pow(2, (note - 69) / 12); | |
} | |
</script> | |
</body> | |
</html> |