I recently faced a challenge when trying to connect Madmapper to a web application. Since Madmapper only accepts OSC (Open Sound Control) messages, integrating it directly with today's web and mobile technologies was tricky. To overcome this limitation, I built a workaround using Firebase and Node.js — and I'm sharing this guide in case anyone else runs into the same problem.
The Problem
Madmapper excels in projection mapping but communicates solely via OSC messages. OSC is fantastic for real-time performance environments, yet it doesn't play well with HTTP-based web or mobile applications.
In my case, the task was to control Madmapper from an Android app on a tablet during a museum presentation. I needed a way to bridge the gap between a web-based interface (and the mobile app that drives it) and Madmapper's OSC-only input.
Finding the Right Solution
After exploring various options, I landed on a combination that worked surprisingly well:
- Firebase as a real-time message broker
- Node.js server as a protocol translator
- Simple web interface for control
- OSC messages for final communication
This setup solved several key challenges:
- Real-time control requirements
- Multiple device support
- Reliable message delivery
- User-friendly interface
The Implementation
Here's how I put it all together:
1. Setting Up the Bridge
First, I created a Node.js server that would act as our translator:
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const osc = require('node-osc');
const { initializeApp } = require('firebase/app');
const { getDatabase, ref, onValue } = require('firebase/database');
// Initialize Firebase and OSC client
const app = initializeApp(firebaseConfig);
const database = getDatabase(app);
const oscClient = new osc.Client(getLocalIpAddress(), 8010);
2. Creating the Communication Flow
The system works in three main steps:
Step 1: Web interface sends command
async function assignVideo(videoId) {
await fetch('/assign-video', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ videoId })
});
}
Step 2: Server updates Firebase
app.post('/assign-video', async (req, res) => {
const { videoId } = req.body;
await set(ref(database, 'currentVideo'), {
playVideo: true,
videoId
});
res.json({ success: true });
});
Step 3: Firebase listener sends OSC
onValue(ref(database, 'currentVideo'), async (snapshot) => {
const data = snapshot.val();
if (data?.playVideo) {
await sendOSCMessage(videoOscMap[data.videoId], 1);
}
});
Network Matters
Some key considerations for network stability:
- Local network stability is crucial
- Always implement error handling for network issues
- Provide clear feedback when communication fails
Here's an example of robust error handling:
async function sendOSCMessage(address, value) {
try {
await new Promise((resolve, reject) => {
oscClient.send(address, value, (err) => {
if (err) reject(err);
else resolve();
});
});
console.log(`OSC message sent successfully to ${address}`);
} catch (error) {
console.error(`Failed to send OSC message: ${error.message}`);
throw error;
}
}
Conclusion
This solution has been running successfully in production for several months now. While it might seem like a complex setup at first, it's actually quite straightforward once you understand the pieces involved. The key is to keep the implementation simple and focused on reliability.
For those interested in trying this approach, I've made the complete code available on GitHub. Feel free to adapt it to your needs or suggest improvements.
Questions or suggestions? Feel free to reach out through GitHub issues or comments below.
Top comments (0)