Thank you for your interest in contributing to OpenStudio! This document provides guidelines and information to help you contribute effectively.
- Code of Conduct
- Getting Started
- Development Workflow
- Project Architecture
- Coding Standards
- Testing Requirements
- Documentation Standards
- Pull Request Process
- Issue Reporting
- Communication
OpenStudio is committed to providing a welcoming and harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
Positive behaviors include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Unacceptable behaviors include:
- Harassment, trolling, or inflammatory comments
- Public or private harassment
- Publishing others' private information
- Other conduct which could reasonably be considered inappropriate
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated promptly and fairly.
Before contributing, ensure you have:
- Node.js 18+ installed
- Docker and Docker Compose installed
- Git configured with your name and email
- A GitHub account
- Familiarity with JavaScript (ES modules), WebRTC, or Web Audio (depending on contribution area)
# 1. Fork the repository on GitHub
# 2. Clone your fork
git clone https://github.com/YOUR_USERNAME/openstudio.git
cd openstudio
# 3. Add upstream remote
git remote add upstream https://github.com/msitarzewski/openstudio.git
# 4. Create development environment
cp .env.example .env
cd server && npm install && cd ..
cd web && npm install && cd ..
# 5. Start development infrastructure
./dev.sh
# 6. In another terminal, start web client
cd web && python3 -m http.server 8086
# 7. Verify everything works
curl http://localhost:6736/health # Should return {"status":"ok"}
open http://localhost:8086 # Browser should load studio interface-
Read the Memory Bank documentation:
- Start with
memory-bank/toc.mdfor an overview - Read
memory-bank/projectbrief.mdfor vision and goals - Review
memory-bank/systemPatterns.mdfor architecture patterns - Check
memory-bank/activeContext.mdfor current priorities
- Start with
-
Review existing code:
- Signaling server:
server/directory - Web client:
web/js/directory - Tests:
tests/andserver/test-*.js
- Signaling server:
-
Run the automated tests:
./run-pre-validation.sh # All 6 tests should pass
- Check Current Priorities: Read
memory-bank/activeContext.md - Browse Open Issues: Look for issues labeled
good first issueorhelp wanted - Review the Roadmap: See
README.mdfor planned features - Ask Questions: Open a discussion if you're unsure where to start
# Always work on a feature branch
git checkout -b feature/your-feature-name
# Keep your branch up to date
git fetch upstream
git rebase upstream/main
# When ready, push to your fork
git push origin feature/your-feature-nameOpenStudio supports two development workflows:
Docker Mode (default, recommended for most contributors):
- All services run in Docker containers
- Production parity (same environment as deployment)
- Best for: Feature work, integration testing, deployment validation
Local Mode (for signaling server development):
- Signaling server runs locally with
--watch(auto-restart on file changes) - Icecast and coturn remain in Docker
- Faster iteration cycles
- Best for: Backend development, debugging, rapid prototyping
# Switch between modes
./dev-switch.sh # Interactive mode switcher
# OR manually edit .env
# Set DEV_MODE=docker or DEV_MODE=localWe follow the Conventional Commits specification:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentation onlystyle: Code style (formatting, no logic change)refactor: Code refactoringperf: Performance improvementtest: Adding or updating testschore: Maintenance tasks (dependencies, configs)
Examples:
feat(audio): implement per-participant gain controls
Add UI sliders (0-200% range) with smooth AudioParam ramping to prevent
audio clicks. Mute buttons toggle with visual state changes.
Closes #42
---
fix(signaling): prevent message spoofing
Validate "from" field matches registered peer ID to prevent
impersonation attacks. Reject messages with mismatched IDs.
Fixes #58
---
docs(readme): update installation instructions
Add troubleshooting section for common port conflicts and
Docker service issues.
Before submitting a pull request:
# 1. Run automated tests
./run-pre-validation.sh
# All 6 tests must pass
# 2. Test manually
# - Create a room, join from multiple browsers
# - Verify your feature works as expected
# - Check browser console for errors
# 3. Test on a clean system (if infrastructure changes)
# - Use a fresh VM or Docker container
# - Follow README setup from scratch
# - Verify <5 minute setup time
# 4. Check for regressions
# - Test existing functionality still works
# - Verify no new console errors or warnings- Reuse Over Creation: Search for existing functionality before creating new files
- Event-Driven Design: Use EventTarget for loose coupling between modules
- ES Modules: All JavaScript uses ES modules (type: "module")
- Zero Commercial Dependencies: MIT/BSD/GPL licenses only
- Browser Native: Leverage platform APIs (Web Audio, WebRTC, MediaRecorder)
openstudio/
├── server/ # Signaling server (Node.js)
│ ├── server.js # Main entry point
│ ├── lib/ # Server modules
│ └── test-*.js # Server tests
├── web/ # Web studio client
│ ├── index.html # Main HTML
│ ├── css/ # Stylesheets
│ └── js/ # Client-side JavaScript
├── tests/ # Playwright end-to-end tests
├── docs/ # User and developer documentation
├── memory-bank/ # Project knowledge base
│ ├── releases/ # Release planning and task tracking
│ └── *.md # Core documentation files
├── icecast/ # Custom Icecast Dockerfile
├── docker-compose.yml # Infrastructure orchestration
└── station-manifest.sample.json # Configuration template
Signaling Server:
websocket-server.js: WebSocket wrapper with ping/pongsignaling-protocol.js: Peer registry and message relayroom-manager.js: Room lifecycle and auto-cleanupmessage-validator.js: Anti-spoofing validation
Web Studio Client:
signaling-client.js: WebSocket client with auto-reconnectionrtc-manager.js: RTCPeerConnection managerconnection-manager.js: Perfect Negotiation and retry logicaudio-graph.js: Per-participant routing with Web Audio APImix-minus.js: Phase-inversion algorithm for professional audioprogram-bus.js: Unified stereo mixingmain.js: Application orchestration
ES Modules:
// ✅ Good
import { SomeClass } from './some-module.js';
export class MyClass extends EventTarget { }
// ❌ Bad
const SomeClass = require('./some-module');
module.exports = MyClass;Event-Driven Architecture:
// ✅ Good - Extend EventTarget for loose coupling
class SignalingClient extends EventTarget {
connect() {
this.dispatchEvent(new CustomEvent('connected', { detail: { peerId } }));
}
}
// ❌ Bad - Direct callbacks create tight coupling
class SignalingClient {
connect(onConnected) {
onConnected(peerId);
}
}Clear, Descriptive Names:
// ✅ Good
function createMixMinusBus(peerId, compressorNode) { }
const isPolite = this.peerId < remotePeerId;
// ❌ Bad
function createBus(p, c) { }
const pol = this.peerId < remotePeerId;Single Responsibility:
// ✅ Good - Each class has one job
class AudioContextManager { } // Manages AudioContext lifecycle
class AudioGraph { } // Routes participant audio
class MixMinusManager { } // Calculates mix-minus buses
// ❌ Bad - God class doing everything
class AudioManager {
manageContext() { }
routeAudio() { }
calculateMixMinus() { }
playReturnFeeds() { }
}Error Handling:
// ✅ Good - Handle errors, log useful context
try {
const answer = await this.rtcManager.handleOffer(remotePeerId, sdp);
this.signalingClient.sendAnswer(remotePeerId, answer);
} catch (error) {
console.error(`[ConnectionManager] Failed to handle offer from ${remotePeerId}:`, error);
this.setConnectionState(remotePeerId, { status: 'failed' });
}
// ❌ Bad - Silent failures
try {
await this.rtcManager.handleOffer(remotePeerId, sdp);
} catch (error) {
// Ignore
}JSDoc for Public APIs:
/**
* Add return feed track to peer connection
* This triggers the 'negotiationneeded' event which handles renegotiation automatically
*
* @param {string} remotePeerId - Remote peer identifier
* @param {MediaStream} mixMinusStream - Mix-minus audio stream (all participants except remotePeerId)
* @throws {Error} If peer connection not found or stream is null
*/
addReturnFeedTrack(remotePeerId, mixMinusStream) {
// Implementation
}Clear Comments for Complex Logic:
// Perfect Negotiation: Detect glare collision
// Collision occurs if we're making an offer OR peer connection isn't stable
const offerCollision = state.makingOffer ||
this.rtcManager.peerConnections.get(remotePeerId)?.signalingState !== 'stable';
if (offerCollision) {
if (!isPolite) {
// Impolite peer ignores incoming offer (other peer will roll back)
return;
} else {
// Polite peer rolls back and accepts incoming offer
console.log('[ConnectionManager] We are polite, rolling back our offer');
}
}One Class Per File:
// ✅ Good
// audio-graph.js
export class AudioGraph { }
// mix-minus.js
export class MixMinusManager { }
// ❌ Bad
// audio.js
export class AudioGraph { }
export class MixMinusManager { }
export class ProgramBus { }Logical Grouping:
web/js/
├── signaling-client.js # WebSocket communication
├── rtc-manager.js # WebRTC peer connections
├── connection-manager.js # Connection orchestration
├── audio-context-manager.js # AudioContext lifecycle
├── audio-graph.js # Participant routing
├── mix-minus.js # Mix-minus calculation
├── program-bus.js # Program bus mixing
└── main.js # Application entry point
All new features must include:
- Unit Tests (if applicable): Test individual functions/classes
- Integration Tests: Test interaction between modules
- End-to-End Tests: Test complete user workflows
Playwright Tests (for client-side features):
// Example: tests/test-your-feature.mjs
import { chromium } from 'playwright';
async function runTest() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
// Enable console logging
page.on('console', msg => {
console.log(`[Browser] ${msg.text()}`);
});
// Test your feature
await page.goto('http://localhost:8086/');
// ... test steps ...
await browser.close();
}
runTest().catch(console.error);Server Tests (for signaling server):
// Example: server/test-your-feature.js
import WebSocket from 'ws';
async function testYourFeature() {
const ws = new WebSocket('ws://localhost:6736');
return new Promise((resolve, reject) => {
ws.on('message', (data) => {
const message = JSON.parse(data);
// Assert expected behavior
console.log('✅ Test passed');
ws.close();
resolve();
});
ws.on('open', () => {
// Send test message
ws.send(JSON.stringify({ type: 'test' }));
});
});
}# Run all automated tests
./run-pre-validation.sh
# Run individual test
node tests/test-your-feature.mjs
# Run server tests
node server/test-your-feature.js- Always: Update documentation when changing behavior
- New Features: Add usage examples and API documentation
- Breaking Changes: Document migration path in CHANGELOG
- Bug Fixes: Update troubleshooting guide if applicable
| File | When to Update |
|---|---|
README.md |
Major features, setup process changes |
CHANGELOG.md |
Every pull request (add to [Unreleased] section) |
docs/TROUBLESHOOTING.md |
New common issues discovered |
docs/ARCHITECTURE-IMPLEMENTATION.md |
Architectural changes |
memory-bank/systemPatterns.md |
New architectural patterns |
memory-bank/activeContext.md |
Change in priorities or focus |
Clear, Concise Headers:
## Feature Name
Brief description of what it does and why it matters.
### Usage
Code example showing typical usage.
### Configuration
Available options and their defaults.
### Troubleshooting
Common issues and solutions.- All automated tests pass (
./run-pre-validation.sh) - Code follows project coding standards
- New features have tests
- Documentation updated
- Commit messages follow Conventional Commits format
- Branch rebased on latest
main
-
Push to Your Fork:
git push origin feature/your-feature-name
-
Create Pull Request on GitHub:
- Use a clear, descriptive title
- Reference related issues (
Closes #42,Fixes #58) - Describe what changed and why
- Include screenshots/videos for UI changes
- List any breaking changes
-
PR Template:
## Description Brief summary of changes ## Related Issues Closes #42 ## Type of Change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Testing - [ ] All automated tests pass - [ ] Tested manually with [describe scenarios] - [ ] No regressions found ## Screenshots (if applicable) [Add screenshots here] ## Checklist - [ ] Code follows project style guidelines - [ ] Self-reviewed code - [ ] Commented complex logic - [ ] Updated documentation - [ ] Added tests - [ ] CHANGELOG.md updated
- Automated Checks: CI runs tests and linting
- Code Review: Maintainer reviews code quality and architecture
- Discussion: Address feedback and requested changes
- Approval: Once approved, maintainer merges
- Your contribution will be included in the next release
- You'll be credited in
CHANGELOG.mdand release notes - Thank you for contributing! 🎉
- Search Existing Issues: Check if already reported
- Try Latest Version: Ensure you're on current
mainbranch - Read Documentation: Check troubleshooting guide
- Reproduce: Verify issue is reproducible
Use this template:
## Bug Description
Clear, concise description of the bug
## Steps to Reproduce
1. Start OpenStudio with...
2. Click on...
3. Observe error...
## Expected Behavior
What you expected to happen
## Actual Behavior
What actually happened
## Environment
- OS: [macOS 14.1 / Windows 11 / Ubuntu 22.04]
- Node.js Version: [18.x.x]
- Docker Version: [24.x.x]
- Browser: [Chrome 120 / Firefox 121 / Safari 17]
- OpenStudio Version: [commit SHA or v0.1.0]
## LogsPaste relevant logs here (browser console, server logs, Docker logs)
## Screenshots (if applicable)
[Add screenshots]
## Additional Context
Any other relevant information
Use this template:
## Feature Description
Clear, concise description of proposed feature
## Use Case
Explain the problem this feature would solve
## Proposed Solution
How you envision this working
## Alternatives Considered
Other approaches you've thought about
## Additional Context
Any other relevant information, mockups, or examples- GitHub Issues: Bug reports and feature requests
- GitHub Discussions: Questions and community support
- Discord/Matrix: Real-time chat (links coming soon)
- Maintainers: Aim to respond within 3-5 business days
- Community: Often responds faster!
- Critical Bugs: Prioritized and addressed ASAP
- Check Documentation First: Most answers are in the docs
- Search Issues: Someone may have asked before
- Ask in Discussions: Community is friendly and helpful
- Be Patient: Maintainers are volunteers
Contributors are recognized in:
CHANGELOG.mdfor each release- GitHub release notes
- Project README (for significant contributions)
Top contributors may be invited to become maintainers.
By contributing to OpenStudio, you agree that your contributions will be licensed under the MIT License.
If anything in this guide is unclear, please:
- Open a GitHub Discussion
- Ask in Discord/Matrix (when available)
- Open an issue labeled
documentation
We're here to help you contribute successfully!
Every contribution, no matter how small, helps build a better alternative to commercial broadcast platforms.
Together, we're taking back the airwaves. 🎙️