/**
* Lightweight browser-compatible event emitter.
* Provides publish/subscribe functionality for MIDI events
* without external dependencies. Implements standard event
* emitter pattern: register listeners with on(), emit events
* with emit(), remove listeners with off(). Listener cleanup
* is automatic via returned unsubscribe functions.
*/
export class EventEmitter {
constructor() {
this.events = new Map()
}
/**
* Register an event listener
* @param {string} event - Event name
* @param {Function} handler - Event handler function
* @returns {Function} Unsubscribe function
*/
on(event, handler) {
if (!this.events.has(event)) {
this.events.set(event, [])
}
this.events.get(event).push(handler)
return () => this.off(event, handler)
}
/**
* Register a one-time event listener
* @param {string} event - Event name
* @param {Function} handler - Event handler function
*/
once(event, handler) {
const onceHandler = (...args) => {
handler(...args)
this.off(event, onceHandler)
}
this.on(event, onceHandler)
}
/**
* Remove an event listener
* @param {string} event - Event name
* @param {Function} handler - Event handler function
*/
off(event, handler) {
if (!this.events.has(event)) return
const handlers = this.events.get(event)
const index = handlers.indexOf(handler)
if (index > -1) {
handlers.splice(index, 1)
}
if (handlers.length === 0) {
this.events.delete(event)
}
}
/**
* Emit an event
* @param {string} event - Event name
* @param {*} data - Event data
*/
emit(event, data) {
if (!this.events.has(event)) return
// Create a copy to avoid modification during iteration
const handlers = [...this.events.get(event)]
handlers.forEach((handler) => {
try {
handler(data)
} catch (err) {
console.error(`Error in event handler for "${event}":`, err)
}
})
}
/**
* Remove all event listeners
* @param {string} [event] - Optional event name to clear specific event
*/
removeAllListeners(event) {
if (event) {
this.events.delete(event)
} else {
this.events.clear()
}
}
}