Skip to main content
Glama

MCP MIDI Bridge

by tezza1971
main.js30.6 kB
"use strict"; const electron = require("electron"); const path = require("path"); const fs = require("fs"); const express = require("express"); const cors = require("cors"); const easymidi = require("easymidi"); const events = require("events"); class MidiManager extends events.EventEmitter { constructor() { super(); this.virtualOutput = null; this.isPlaying = false; this.activeChannels = /* @__PURE__ */ new Set(); this.currentPlaybackTimeout = null; this.initialize(); } /** * Initialize the MIDI manager */ initialize() { try { const outputs = easymidi.getOutputs(); console.log("Available MIDI outputs:", outputs); if (!outputs.includes("MCP MIDI Bridge")) { this.virtualOutput = new easymidi.Output("MCP MIDI Bridge", true); console.log("Created virtual MIDI output: MCP MIDI Bridge"); } else { this.virtualOutput = new easymidi.Output("MCP MIDI Bridge"); console.log("Connected to existing virtual MIDI output: MCP MIDI Bridge"); } this.emit("midi-connection-status", { connected: true, deviceName: "MCP MIDI Bridge" }); } catch (error) { console.error("Failed to initialize MIDI:", error); this.emit("midi-connection-status", { connected: false, error }); } } /** * Get available MIDI outputs * @returns {string[]} - Array of MIDI output names */ getOutputs() { return easymidi.getOutputs(); } /** * Send a program change to set the instrument for a channel * @param {number} channel - MIDI channel (0-15) * @param {number} program - Program number (0-127) */ setProgramForChannel(channel, program) { if (!this.virtualOutput) return; this.virtualOutput.send("program", { channel, number: program }); console.log(`Set channel ${channel} to program ${program}`); } /** * Stop current playback */ stopPlayback() { this.isPlaying = false; if (this.currentPlaybackTimeout) { clearTimeout(this.currentPlaybackTimeout); this.currentPlaybackTimeout = null; } if (this.virtualOutput) { for (let channel = 0; channel < 16; channel++) { for (let note = 0; note < 128; note++) { this.virtualOutput.send("noteoff", { note, velocity: 0, channel }); } } } } /** * Play a NoteSequence through the virtual MIDI output * @param {NoteSequence} noteSequence - The NoteSequence to play * @param {Function} progressCallback - Callback for playback progress updates * @returns {Promise<PlayMidiResult>} - Result of the playback */ async playNoteSequence(noteSequence, progressCallback = null) { var _a; if (!this.virtualOutput) { return { success: false, error: "MIDI output not available" }; } if (this.isPlaying) { return { success: false, error: "Already playing" }; } if (!noteSequence || !noteSequence.notes || noteSequence.notes.length === 0) { return { success: false, error: "Invalid or empty note sequence" }; } try { this.isPlaying = true; this.activeChannels.clear(); const sortedNotes = [...noteSequence.notes].sort((a, b) => a.startTime - b.startTime); const totalDuration = noteSequence.totalTime || Math.max(...sortedNotes.map((note) => note.endTime)) || 0; const activeNotes = /* @__PURE__ */ new Map(); const channelsUsed = new Set(sortedNotes.map((note) => note.instrument || 0)); for (const channel of channelsUsed) { const firstNoteForChannel = sortedNotes.find((note) => (note.instrument || 0) === channel); if (firstNoteForChannel) { const program = firstNoteForChannel.program || 0; this.setProgramForChannel(channel, program); this.activeChannels.add(channel); } } const startTime = Date.now(); for (let i = 0; i < sortedNotes.length && this.isPlaying; i++) { const note = sortedNotes[i]; const channel = note.instrument || 0; const pitch = note.pitch; const velocity = note.velocity || 80; const noteStartTime = note.startTime * 1e3; const noteEndTime = note.endTime * 1e3; await new Promise((resolve) => { const timeToWait = Math.max(0, startTime + noteStartTime - Date.now()); this.currentPlaybackTimeout = setTimeout(resolve, timeToWait); }); if (!this.isPlaying) break; this.virtualOutput.send("noteon", { note: pitch, velocity, channel }); if (!activeNotes.has(channel)) { activeNotes.set(channel, /* @__PURE__ */ new Set()); } (_a = activeNotes.get(channel)) == null ? void 0 : _a.add(pitch); this.currentPlaybackTimeout = setTimeout(() => { var _a2; if (this.virtualOutput) { this.virtualOutput.send("noteoff", { note: pitch, velocity: 0, channel }); } if (activeNotes.has(channel)) { (_a2 = activeNotes.get(channel)) == null ? void 0 : _a2.delete(pitch); } }, Math.max(0, startTime + noteEndTime - Date.now())); if (progressCallback && totalDuration > 0) { const progress = note.startTime / totalDuration; progressCallback({ current: note.startTime, total: totalDuration, progress }); } } if (this.isPlaying) { await new Promise((resolve) => { const timeToWait = Math.max(0, startTime + totalDuration * 1e3 - Date.now()) + 100; this.currentPlaybackTimeout = setTimeout(resolve, timeToWait); }); } for (const [channel, notes] of activeNotes.entries()) { for (const pitch of notes) { if (this.virtualOutput) { this.virtualOutput.send("noteoff", { note: pitch, velocity: 0, channel }); } } } if (progressCallback) { progressCallback({ current: totalDuration, total: totalDuration, progress: 1 }); } this.isPlaying = false; return { success: true }; } catch (error) { this.isPlaying = false; console.error("Error playing note sequence:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } /** * Send a control change message * @param {number} channel - MIDI channel (0-15) * @param {number} controller - Controller number (0-127) * @param {number} value - Controller value (0-127) */ sendControlChange(channel, controller, value) { if (!this.virtualOutput) return; this.virtualOutput.send("cc", { controller, value, channel }); } /** * Send all notes off for a specific channel * @param {number} channel - MIDI channel (0-15) */ allNotesOff(channel) { if (!this.virtualOutput) return; this.sendControlChange(channel, 123, 0); } /** * Send all sound off for a specific channel * @param {number} channel - MIDI channel (0-15) */ allSoundOff(channel) { if (!this.virtualOutput) return; this.sendControlChange(channel, 120, 0); } /** * Reset all controllers for a specific channel * @param {number} channel - MIDI channel (0-15) */ resetAllControllers(channel) { if (!this.virtualOutput) return; this.sendControlChange(channel, 121, 0); } /** * Get General MIDI instrument names * @returns {MidiInstrument[]} - Array of instrument information */ getGeneralMidiInstruments() { return [ // Piano { name: "Acoustic Grand Piano", family: "Piano" }, { name: "Bright Acoustic Piano", family: "Piano" }, { name: "Electric Grand Piano", family: "Piano" }, { name: "Honky-tonk Piano", family: "Piano" }, { name: "Electric Piano 1", family: "Piano" }, { name: "Electric Piano 2", family: "Piano" }, { name: "Harpsichord", family: "Piano" }, { name: "Clavi", family: "Piano" }, // Chromatic Percussion { name: "Celesta", family: "Chromatic Percussion" }, { name: "Glockenspiel", family: "Chromatic Percussion" }, { name: "Music Box", family: "Chromatic Percussion" }, { name: "Vibraphone", family: "Chromatic Percussion" }, { name: "Marimba", family: "Chromatic Percussion" }, { name: "Xylophone", family: "Chromatic Percussion" }, { name: "Tubular Bells", family: "Chromatic Percussion" }, { name: "Dulcimer", family: "Chromatic Percussion" }, // Organ { name: "Drawbar Organ", family: "Organ" }, { name: "Percussive Organ", family: "Organ" }, { name: "Rock Organ", family: "Organ" }, { name: "Church Organ", family: "Organ" }, { name: "Reed Organ", family: "Organ" }, { name: "Accordion", family: "Organ" }, { name: "Harmonica", family: "Organ" }, { name: "Tango Accordion", family: "Organ" }, // Guitar { name: "Acoustic Guitar (nylon)", family: "Guitar" }, { name: "Acoustic Guitar (steel)", family: "Guitar" }, { name: "Electric Guitar (jazz)", family: "Guitar" }, { name: "Electric Guitar (clean)", family: "Guitar" }, { name: "Electric Guitar (muted)", family: "Guitar" }, { name: "Overdriven Guitar", family: "Guitar" }, { name: "Distortion Guitar", family: "Guitar" }, { name: "Guitar harmonics", family: "Guitar" }, // Bass { name: "Acoustic Bass", family: "Bass" }, { name: "Electric Bass (finger)", family: "Bass" }, { name: "Electric Bass (pick)", family: "Bass" }, { name: "Fretless Bass", family: "Bass" }, { name: "Slap Bass 1", family: "Bass" }, { name: "Slap Bass 2", family: "Bass" }, { name: "Synth Bass 1", family: "Bass" }, { name: "Synth Bass 2", family: "Bass" }, // Strings { name: "Violin", family: "Strings" }, { name: "Viola", family: "Strings" }, { name: "Cello", family: "Strings" }, { name: "Contrabass", family: "Strings" }, { name: "Tremolo Strings", family: "Strings" }, { name: "Pizzicato Strings", family: "Strings" }, { name: "Orchestral Harp", family: "Strings" }, { name: "Timpani", family: "Strings" }, // Ensemble { name: "String Ensemble 1", family: "Ensemble" }, { name: "String Ensemble 2", family: "Ensemble" }, { name: "SynthStrings 1", family: "Ensemble" }, { name: "SynthStrings 2", family: "Ensemble" }, { name: "Choir Aahs", family: "Ensemble" }, { name: "Voice Oohs", family: "Ensemble" }, { name: "Synth Voice", family: "Ensemble" }, { name: "Orchestra Hit", family: "Ensemble" }, // Brass { name: "Trumpet", family: "Brass" }, { name: "Trombone", family: "Brass" }, { name: "Tuba", family: "Brass" }, { name: "Muted Trumpet", family: "Brass" }, { name: "French Horn", family: "Brass" }, { name: "Brass Section", family: "Brass" }, { name: "SynthBrass 1", family: "Brass" }, { name: "SynthBrass 2", family: "Brass" }, // Reed { name: "Soprano Sax", family: "Reed" }, { name: "Alto Sax", family: "Reed" }, { name: "Tenor Sax", family: "Reed" }, { name: "Baritone Sax", family: "Reed" }, { name: "Oboe", family: "Reed" }, { name: "English Horn", family: "Reed" }, { name: "Bassoon", family: "Reed" }, { name: "Clarinet", family: "Reed" }, // Pipe { name: "Piccolo", family: "Pipe" }, { name: "Flute", family: "Pipe" }, { name: "Recorder", family: "Pipe" }, { name: "Pan Flute", family: "Pipe" }, { name: "Blown Bottle", family: "Pipe" }, { name: "Shakuhachi", family: "Pipe" }, { name: "Whistle", family: "Pipe" }, { name: "Ocarina", family: "Pipe" }, // Synth Lead { name: "Lead 1 (square)", family: "Synth Lead" }, { name: "Lead 2 (sawtooth)", family: "Synth Lead" }, { name: "Lead 3 (calliope)", family: "Synth Lead" }, { name: "Lead 4 (chiff)", family: "Synth Lead" }, { name: "Lead 5 (charang)", family: "Synth Lead" }, { name: "Lead 6 (voice)", family: "Synth Lead" }, { name: "Lead 7 (fifths)", family: "Synth Lead" }, { name: "Lead 8 (bass + lead)", family: "Synth Lead" }, // Synth Pad { name: "Pad 1 (new age)", family: "Synth Pad" }, { name: "Pad 2 (warm)", family: "Synth Pad" }, { name: "Pad 3 (polysynth)", family: "Synth Pad" }, { name: "Pad 4 (choir)", family: "Synth Pad" }, { name: "Pad 5 (bowed)", family: "Synth Pad" }, { name: "Pad 6 (metallic)", family: "Synth Pad" }, { name: "Pad 7 (halo)", family: "Synth Pad" }, { name: "Pad 8 (sweep)", family: "Synth Pad" }, // Synth Effects { name: "FX 1 (rain)", family: "Synth Effects" }, { name: "FX 2 (soundtrack)", family: "Synth Effects" }, { name: "FX 3 (crystal)", family: "Synth Effects" }, { name: "FX 4 (atmosphere)", family: "Synth Effects" }, { name: "FX 5 (brightness)", family: "Synth Effects" }, { name: "FX 6 (goblins)", family: "Synth Effects" }, { name: "FX 7 (echoes)", family: "Synth Effects" }, { name: "FX 8 (sci-fi)", family: "Synth Effects" }, // Ethnic { name: "Sitar", family: "Ethnic" }, { name: "Banjo", family: "Ethnic" }, { name: "Shamisen", family: "Ethnic" }, { name: "Koto", family: "Ethnic" }, { name: "Kalimba", family: "Ethnic" }, { name: "Bag pipe", family: "Ethnic" }, { name: "Fiddle", family: "Ethnic" }, { name: "Shanai", family: "Ethnic" }, // Percussive { name: "Tinkle Bell", family: "Percussive" }, { name: "Agogo", family: "Percussive" }, { name: "Steel Drums", family: "Percussive" }, { name: "Woodblock", family: "Percussive" }, { name: "Taiko Drum", family: "Percussive" }, { name: "Melodic Tom", family: "Percussive" }, { name: "Synth Drum", family: "Percussive" }, { name: "Reverse Cymbal", family: "Percussive" }, // Sound Effects { name: "Guitar Fret Noise", family: "Sound Effects" }, { name: "Breath Noise", family: "Sound Effects" }, { name: "Seashore", family: "Sound Effects" }, { name: "Bird Tweet", family: "Sound Effects" }, { name: "Telephone Ring", family: "Sound Effects" }, { name: "Helicopter", family: "Sound Effects" }, { name: "Applause", family: "Sound Effects" }, { name: "Gunshot", family: "Sound Effects" } ]; } /** * Get General MIDI drum names (Channel 10) * @returns {MidiDrum[]} - Array of drum information */ getGeneralMidiDrums() { return [ { name: "Acoustic Bass Drum" }, // 35 { name: "Bass Drum 1" }, // 36 { name: "Side Stick" }, // 37 { name: "Acoustic Snare" }, // 38 { name: "Hand Clap" }, // 39 { name: "Electric Snare" }, // 40 { name: "Low Floor Tom" }, // 41 { name: "Closed Hi Hat" }, // 42 { name: "High Floor Tom" }, // 43 { name: "Pedal Hi-Hat" }, // 44 { name: "Low Tom" }, // 45 { name: "Open Hi-Hat" }, // 46 { name: "Low-Mid Tom" }, // 47 { name: "Hi Mid Tom" }, // 48 { name: "Crash Cymbal 1" }, // 49 { name: "High Tom" }, // 50 { name: "Ride Cymbal 1" }, // 51 { name: "Chinese Cymbal" }, // 52 { name: "Ride Bell" }, // 53 { name: "Tambourine" }, // 54 { name: "Splash Cymbal" }, // 55 { name: "Cowbell" }, // 56 { name: "Crash Cymbal 2" }, // 57 { name: "Vibraslap" }, // 58 { name: "Ride Cymbal 2" }, // 59 { name: "Hi Bongo" }, // 60 { name: "Low Bongo" }, // 61 { name: "Mute Hi Conga" }, // 62 { name: "Open Hi Conga" }, // 63 { name: "Low Conga" }, // 64 { name: "High Timbale" }, // 65 { name: "Low Timbale" }, // 66 { name: "High Agogo" }, // 67 { name: "Low Agogo" }, // 68 { name: "Cabasa" }, // 69 { name: "Maracas" }, // 70 { name: "Short Whistle" }, // 71 { name: "Long Whistle" }, // 72 { name: "Short Guiro" }, // 73 { name: "Long Guiro" }, // 74 { name: "Claves" }, // 75 { name: "Hi Wood Block" }, // 76 { name: "Low Wood Block" }, // 77 { name: "Mute Cuica" }, // 78 { name: "Open Cuica" }, // 79 { name: "Mute Triangle" }, // 80 { name: "Open Triangle" } // 81 ]; } /** * Close the MIDI output */ close() { if (this.virtualOutput) { this.virtualOutput.close(); this.virtualOutput = null; } } } class SongCache { /** * Create a new SongCache * @param {string} cacheDir - Directory to store song cache files */ constructor(cacheDir) { this.cacheDir = cacheDir; this.ensureCacheDirectory(); } /** * Ensure the cache directory exists */ ensureCacheDirectory() { if (!fs.existsSync(this.cacheDir)) { fs.mkdirSync(this.cacheDir, { recursive: true }); } } /** * Save a NoteSequence to the cache * @param {NoteSequence} noteSequence - The NoteSequence to save * @returns {SaveSongResult} - Information about the saved file */ saveSong(noteSequence) { try { if (!noteSequence || !noteSequence.notes) { throw new Error("Invalid NoteSequence format"); } const timestamp = Date.now(); const filename = `song_${timestamp}.json`; const filepath = path.join(this.cacheDir, filename); fs.writeFileSync(filepath, JSON.stringify(noteSequence, null, 2)); return { success: true, filepath, timestamp, filename }; } catch (error) { console.error("Error saving song:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } /** * Get the most recent song from the cache * @returns {NoteSequence|null} - The most recent NoteSequence or null if none found */ getLatestSong() { try { const files = fs.readdirSync(this.cacheDir).filter((file) => file.startsWith("song_") && file.endsWith(".json")).sort().reverse(); if (files.length === 0) { return null; } const latestFile = files[0]; const filepath = path.join(this.cacheDir, latestFile); const content = fs.readFileSync(filepath, "utf8"); return JSON.parse(content); } catch (error) { console.error("Error getting latest song:", error); return null; } } /** * Get a list of all songs in the cache * @returns {SongInfo[]} - Array of song information objects */ getSongList() { try { const files = fs.readdirSync(this.cacheDir).filter((file) => file.startsWith("song_") && file.endsWith(".json")).sort().reverse(); return files.map((file) => { const filepath = path.join(this.cacheDir, file); const stats = fs.statSync(filepath); const timestamp = parseInt(file.replace("song_", "").replace(".json", ""), 10); return { filename: file, filepath, timestamp, created: stats.birthtime, size: stats.size }; }); } catch (error) { console.error("Error getting song list:", error); return []; } } /** * Get a specific song by filename * @param {string} filename - The filename of the song to retrieve * @returns {NoteSequence|null} - The NoteSequence or null if not found */ getSong(filename) { try { const filepath = path.join(this.cacheDir, filename); if (!fs.existsSync(filepath)) { return null; } const content = fs.readFileSync(filepath, "utf8"); return JSON.parse(content); } catch (error) { console.error(`Error getting song ${filename}:`, error); return null; } } /** * Delete a song from the cache * @param {string} filename - The filename of the song to delete * @returns {boolean} - Whether the deletion was successful */ deleteSong(filename) { try { const filepath = path.join(this.cacheDir, filename); if (!fs.existsSync(filepath)) { return false; } fs.unlinkSync(filepath); return true; } catch (error) { console.error(`Error deleting song ${filename}:`, error); return false; } } } let mainWindow = null; let midiManager; let songCache; let mcpServer; let mcpServerInstance = null; let config = { mcpPort: 8002, pythonPort: 5e3, lastUsedSong: null }; const isDev = process.env.NODE_ENV === "development"; function createWindow() { mainWindow = new electron.BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, "preload.js") } }); if (isDev) { mainWindow.loadURL("http://localhost:8080"); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, "../dist/index.html")); } mainWindow.on("closed", () => { mainWindow = null; }); } function initializeComponents() { try { midiManager = new MidiManager(); console.log("MIDI Manager initialized"); midiManager.on("midi-connection-status", (status) => { if (mainWindow) { mainWindow.webContents.send("midi-connection-status", status); } }); const cacheDir = path.join(electron.app.getPath("userData"), "song_cache"); songCache = new SongCache(cacheDir); console.log("Song Cache initialized"); if (isDev) { } initializeMcpServer(); } catch (error) { console.error("Failed to initialize components:", error); } } function initializeMcpServer() { mcpServer = express(); mcpServer.use(cors()); mcpServer.use(express.json({ limit: "10mb" })); mcpServer.get("/health", (req, res) => { res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }); }); mcpServer.post("/song", (req, res) => { try { const noteSequence = req.body; if (!noteSequence || !noteSequence.notes) { return res.status(400).json({ error: "Invalid NoteSequence format" }); } const saveResult = songCache.saveSong(noteSequence); if (saveResult.success) { if (mainWindow) { mainWindow.webContents.send("song-updated", noteSequence); } res.json({ success: true, message: "Song received and cached", filename: saveResult.filename, timestamp: saveResult.timestamp }); } else { res.status(500).json({ error: saveResult.error }); } } catch (error) { console.error("Error processing song:", error); res.status(500).json({ error: "Internal server error" }); } }); mcpServer.get("/song", (req, res) => { try { const latestSong = songCache.getLatestSong(); if (latestSong) { res.json(latestSong); } else { res.status(404).json({ error: "No songs found" }); } } catch (error) { console.error("Error getting song:", error); res.status(500).json({ error: "Internal server error" }); } }); mcpServer.get("/songs", (req, res) => { try { const songs = songCache.getSongList(); res.json(songs); } catch (error) { console.error("Error getting song list:", error); res.status(500).json({ error: "Internal server error" }); } }); mcpServer.post("/play", async (req, res) => { try { const noteSequence = req.body; if (!noteSequence || !noteSequence.notes) { return res.status(400).json({ error: "Invalid NoteSequence format" }); } const result = await midiManager.playNoteSequence(noteSequence); res.json(result); } catch (error) { console.error("Error playing song:", error); res.status(500).json({ error: "Internal server error" }); } }); try { mcpServerInstance = mcpServer.listen(config.mcpPort, () => { console.log(`MCP API server running on port ${config.mcpPort}`); }); } catch (error) { console.error("Failed to start MCP server:", error); } } function createMenu() { const template = [ { label: "File", submenu: [ { label: "Import MIDI", click: () => { } }, { type: "separator" }, { role: "quit" } ] }, { label: "Edit", submenu: [ { role: "undo" }, { role: "redo" }, { type: "separator" }, { role: "cut" }, { role: "copy" }, { role: "paste" } ] }, { label: "View", submenu: [ { role: "reload" }, { role: "forceReload" }, { role: "toggleDevTools" }, { type: "separator" }, { role: "resetZoom" }, { role: "zoomIn" }, { role: "zoomOut" }, { type: "separator" }, { role: "togglefullscreen" } ] }, { label: "Window", submenu: [ { role: "minimize" }, { role: "close" } ] }, { label: "Help", submenu: [ { label: "About MCP MIDI Bridge", click: () => { if (mainWindow) { mainWindow.webContents.send("show-about"); } } } ] } ]; const menu = electron.Menu.buildFromTemplate(template); electron.Menu.setApplicationMenu(menu); } function setupIpcHandlers() { electron.ipcMain.handle("get-latest-song", async () => { try { return songCache.getLatestSong(); } catch (error) { console.error("Error getting latest song:", error); return null; } }); electron.ipcMain.handle("get-song-list", async () => { try { return songCache.getSongList(); } catch (error) { console.error("Error getting song list:", error); return []; } }); electron.ipcMain.handle("play-midi", async (event, noteSequence) => { try { return await midiManager.playNoteSequence(noteSequence, (progress) => { if (mainWindow) { mainWindow.webContents.send("playback-progress", progress); } }); } catch (error) { console.error("Error playing MIDI:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } }); electron.ipcMain.handle("stop-midi", async () => { try { midiManager.stopPlayback(); } catch (error) { console.error("Error stopping MIDI:", error); } }); electron.ipcMain.handle("import-midi-file", async (event, filePath) => { try { return { success: false, error: "MIDI import not yet implemented" }; } catch (error) { console.error("Error importing MIDI file:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } }); electron.ipcMain.handle("update-config", async (event, newConfig) => { try { config = { ...config, ...newConfig }; const configPath = path.join(electron.app.getPath("userData"), "config.json"); fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); if (mcpServerInstance && newConfig.mcpPort !== config.mcpPort) { mcpServerInstance.close(); config.mcpPort = newConfig.mcpPort; initializeMcpServer(); } return { success: true }; } catch (error) { console.error("Error updating config:", error); return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } }); electron.ipcMain.handle("get-mcp-status", async () => { return { running: mcpServerInstance !== null, port: config.mcpPort }; }); electron.ipcMain.handle("get-current-song", async () => { try { return songCache.getLatestSong(); } catch (error) { console.error("Error getting current song:", error); return null; } }); electron.ipcMain.handle("get-config", async () => { return config; }); electron.ipcMain.handle("get-midi-outputs", async () => { try { return midiManager.getOutputs(); } catch (error) { console.error("Error getting MIDI outputs:", error); return []; } }); electron.ipcMain.handle("get-midi-instruments", async () => { try { const instruments = midiManager.getGeneralMidiInstruments(); const result = {}; instruments.forEach((instrument, index) => { result[index] = { name: instrument.name, family: "General MIDI" }; }); return result; } catch (error) { console.error("Error getting MIDI instruments:", error); return {}; } }); electron.ipcMain.handle("get-midi-drums", async () => { try { const drums = midiManager.getGeneralMidiDrums(); const result = {}; drums.forEach((drum, index) => { result[index + 35] = { name: drum.name }; }); return result; } catch (error) { console.error("Error getting MIDI drums:", error); return {}; } }); } function loadConfig() { try { const configPath = path.join(electron.app.getPath("userData"), "config.json"); if (fs.existsSync(configPath)) { const configData = fs.readFileSync(configPath, "utf8"); config = { ...config, ...JSON.parse(configData) }; } } catch (error) { console.error("Error loading config:", error); } } electron.app.whenReady().then(() => { loadConfig(); initializeComponents(); createWindow(); createMenu(); setupIpcHandlers(); electron.app.on("activate", () => { if (electron.BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); electron.app.on("window-all-closed", () => { if (mcpServerInstance) { mcpServerInstance.close(); } if (process.platform !== "darwin") { electron.app.quit(); } }); electron.app.on("before-quit", () => { if (mcpServerInstance) { mcpServerInstance.close(); } }); //# sourceMappingURL=main.js.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tezza1971/mcp-midi'

If you have feedback or need assistance with the MCP directory API, please join our Discord server