Browse Source

improvements

master
Stephanie Gredell 4 weeks ago
parent
commit
05e17af748
  1. 174
      js/main.js

174
js/main.js

@ -1,18 +1,14 @@ @@ -1,18 +1,14 @@
// Constants
var DELAY = 0; // play one note every quarter second
var VELOCITY = 127; // how hard the note hits
var ACTIVE_FEEDBACK_DURATION = 200; // milliseconds
var NOTE_DURATION = 750; // milliseconds - how long a note plays
var NOTE_OFF_DELAY = 0.75; // seconds - delay before note stops
// Cached DOM elements and key mappings
var pianoElement = null;
var keyElementsMap = new Map(); // Maps note number to DOM element
var activeNotes = new Set(); // Track currently playing notes to prevent duplicates
var pressedKeys = new Set(); // Track currently pressed keyboard keys to prevent repeat
var activeTimeouts = new Map(); // Track timeouts for cleanup
// Web MIDI API support
var midiAccess = null;
var midiInputs = new Map(); // Track connected MIDI input devices
var midiDeviceStatus = {
@ -21,18 +17,6 @@ var midiDeviceStatus = { @@ -21,18 +17,6 @@ var midiDeviceStatus = {
deviceCount: 0
};
// Keyboard mapping: lowercase key -> MIDI note (normalized in parseAction)
var KEYBOARD_MAP = {
'q': 60, // C4
'w': 62, // D4
'e': 64, // E4
'r': 65, // F4
't': 67, // G4
'y': 69, // A4
'u': 71, // B4
'i': 72 // C5
};
/**
* Validate MIDI note number
* @param {number} note - The MIDI note number
@ -141,18 +125,23 @@ function playNoteInternal(note, velocity) { @@ -141,18 +125,23 @@ function playNoteInternal(note, velocity) {
// Use provided velocity or default
var noteVelocity = (velocity !== undefined && velocity >= 0 && velocity <= 127) ? velocity : VELOCITY;
// Clear any existing timeout for this note
// Cancel any pending noteOff timeout for this note
clearNoteTimeout(note);
// Stop any currently playing note of the same pitch immediately
// This ensures rapid repeats stop the previous note and start fresh
MIDI.noteOff(0, note, 0);
// Start the new note
MIDI.setVolume(0, 127);
MIDI.noteOn(0, note, noteVelocity, DELAY);
MIDI.noteOff(0, note, DELAY + NOTE_OFF_DELAY);
// Track timeout for cleanup
// Schedule noteOff for this note
var timeoutId = setTimeout(function() {
MIDI.noteOff(0, note, 0);
activeNotes.delete(note);
activeTimeouts.delete(note);
}, NOTE_DURATION);
}, (DELAY + NOTE_OFF_DELAY) * 1000);
activeTimeouts.set(note, timeoutId);
}
@ -165,16 +154,13 @@ function handlePianoClick(event) { @@ -165,16 +154,13 @@ function handlePianoClick(event) {
var note = getNoteFromElement(event.target);
if (note && isValidNote(note)) {
// Prevent duplicate triggers for the same note
if (!activeNotes.has(note)) {
activeNotes.add(note);
// Get cached key element
var keyElement = keyElementsMap.get(note);
addKeyFeedback(keyElement);
playNoteInternal(note);
}
activeNotes.delete(note);
activeNotes.add(note);
var keyElement = keyElementsMap.get(note);
addKeyFeedback(keyElement);
playNoteInternal(note);
}
}
@ -190,22 +176,20 @@ function handleTouchStart(event) { @@ -190,22 +176,20 @@ function handleTouchStart(event) {
var note = getNoteFromElement(target);
if (note && isValidNote(note)) {
// Prevent duplicate triggers for the same note
if (!activeNotes.has(note)) {
activeNotes.add(note);
// Get cached key element
var keyElement = keyElementsMap.get(note);
addKeyFeedback(keyElement);
playNoteInternal(note);
}
activeNotes.delete(note);
activeNotes.add(note);
// Get cached key element
var keyElement = keyElementsMap.get(note);
addKeyFeedback(keyElement);
playNoteInternal(note);
}
}
}
/**
* @method assignHandlers creates the click, keydown and keyup event handlers when the font is loaded
* @method assignHandlers creates the click and touch event handlers when the font is loaded
*/
function assignHandlers() {
if (!pianoElement) {
@ -213,84 +197,11 @@ function assignHandlers() { @@ -213,84 +197,11 @@ function assignHandlers() {
return;
}
// Handle clicks on piano keys (both white and black keys)
pianoElement.addEventListener('click', handlePianoClick);
// Handle touch events for mobile devices
pianoElement.addEventListener('touchstart', handleTouchStart, { passive: false });
// Keyboard event handlers
document.addEventListener('keydown', parseAction);
document.addEventListener('keyup', releaseAction);
}
/**
* @method releaseAction executes whenever the user triggers a keyup event.
* @param {KeyboardEvent} event
*/
function releaseAction(event) {
var key = event.key ? event.key.toLowerCase() : null;
// Only process if it's a mapped key
if (key && key in KEYBOARD_MAP) {
pressedKeys.delete(key);
// Remove active class from the specific key element
var note = KEYBOARD_MAP[key];
var keyElement = keyElementsMap.get(note);
if (keyElement) {
keyElement.classList.remove('active');
}
// Remove from active notes set
activeNotes.delete(note);
clearNoteTimeout(note);
}
}
/**
* @method parseAction handles keydown events by detecting the user's key and playing the proper note.
* @param {KeyboardEvent} event
*/
function parseAction(event) {
// Normalize key to lowercase
var key = event.key ? event.key.toLowerCase() : null;
// Prevent browser shortcuts and handle key repeat
if (key && key in KEYBOARD_MAP) {
event.preventDefault();
// Prevent key repeat - only trigger if key wasn't already pressed
if (!pressedKeys.has(key)) {
pressedKeys.add(key);
var note = KEYBOARD_MAP[key];
triggerAction(note);
}
}
}
/**
* @method triggerAction triggers UI change to make the key look pressed and to play the note.
* @param {number} note - The MIDI note number
*/
function triggerAction(note) {
if (!isValidNote(note)) {
return;
}
// Get cached key element
var keyElement = keyElementsMap.get(note);
if (keyElement) {
addKeyFeedback(keyElement);
}
// Prevent duplicate triggers
if (!activeNotes.has(note)) {
activeNotes.add(note);
playNoteInternal(note);
}
}
/**
* @method playNote plays a single note (public API).
@ -302,17 +213,14 @@ function playNote(note) { @@ -302,17 +213,14 @@ function playNote(note) {
return;
}
// Get cached key element for visual feedback
var keyElement = keyElementsMap.get(note);
if (keyElement) {
addKeyFeedback(keyElement);
}
// Prevent duplicate triggers
if (!activeNotes.has(note)) {
activeNotes.add(note);
playNoteInternal(note);
}
activeNotes.delete(note);
activeNotes.add(note);
playNoteInternal(note);
}
/**
@ -322,7 +230,6 @@ function playNote(note) { @@ -322,7 +230,6 @@ function playNote(note) {
* @param {number} fifth - The fifth note
*/
function multinotes(root, third, fifth) {
// Validate all notes
if (!isValidNote(root) || !isValidNote(third) || !isValidNote(fifth)) {
console.warn('Invalid MIDI note in chord:', root, third, fifth);
return;
@ -494,19 +401,19 @@ function handleMIDIMessage(event) { @@ -494,19 +401,19 @@ function handleMIDIMessage(event) {
if (command === 0x90 && velocity > 0) {
// Note On
if (isValidNote(note)) {
// Prevent duplicate triggers
if (!activeNotes.has(note)) {
activeNotes.add(note);
// Get cached key element for visual feedback
var keyElement = keyElementsMap.get(note);
if (keyElement) {
addKeyFeedback(keyElement);
}
// Play note with velocity from MIDI keyboard
playNoteInternal(note, velocity);
// Remove from activeNotes so we can play the same note again
// This allows rapid repeats - each press stops the previous and starts new
activeNotes.delete(note);
activeNotes.add(note);
// Get cached key element for visual feedback
var keyElement = keyElementsMap.get(note);
if (keyElement) {
addKeyFeedback(keyElement);
}
// Play note with velocity from MIDI keyboard
playNoteInternal(note, velocity);
}
} else if (command === 0x80 || (command === 0x90 && velocity === 0)) {
// Note Off
@ -649,7 +556,6 @@ window.addEventListener('beforeunload', function() { @@ -649,7 +556,6 @@ window.addEventListener('beforeunload', function() {
});
activeTimeouts.clear();
activeNotes.clear();
pressedKeys.clear();
// Disconnect MIDI inputs
midiInputs.forEach(function(input) {

Loading…
Cancel
Save