Contributing
How to contribute to Ozen-web
Getting Started
Thank you for considering contributing to Ozen-web! This guide will help you get set up and understand our development workflow.
Prerequisites
Before contributing, ensure you have:
- Node.js 18 or later
- npm 9 or later
- Git
- Text editor (VS Code recommended with Svelte extension)
- Basic familiarity with TypeScript, Svelte, and Canvas API
Initial Setup
Fork the repository on GitHub
Clone your fork:
git clone https://github.com/YOUR-USERNAME/Ozen-web.git cd ozen-webAdd upstream remote:
git remote add upstream https://github.com/ucpresearch/ozen-web.gitInstall dependencies:
npm installCopy WASM backend (optional, for local backend):
mkdir -p static/wasm/praatfan # Copy from praatfan-core-clean or use CDN backendStart development server:
npm run devOpen http://localhost:5173 and verify the app works
For detailed setup instructions, see Development Setup.
Development Workflow
Creating a Feature Branch
# Update your fork
git checkout master
git pull upstream master
# Create feature branch
git checkout -b feature/your-feature-nameBranch naming conventions: - feature/description - New features - fix/description - Bug fixes - refactor/description - Code refactoring - docs/description - Documentation changes
Making Changes
Write code following our style guidelines (see below)
Test thoroughly (manual testing checklist below)
Commit with clear messages:
git add file1.ts file2.svelte git commit -m "Add pitch smoothing toggle to settings panel"Push to your fork:
git push origin feature/your-feature-nameOpen a pull request on GitHub
Commit Message Guidelines
Format:
<type>: <description>
[optional body]
[optional footer]
Types: - feat: New feature - fix: Bug fix - refactor: Code refactoring - docs: Documentation changes - style: Code style changes (formatting, no logic change) - test: Adding or updating tests - chore: Maintenance tasks
Examples:
Good:
feat: Add keyboard shortcut (S) for toggling spectrogram
Add S key to show/hide spectrogram overlay. Updates keyboard
shortcuts documentation and adds visual feedback on toggle.
Closes #123
Bad:
fixed stuff
Code Style Guidelines
TypeScript
Use strict typing:
// ✅ GOOD: Explicit types
function computePitch(sound: any, timeStep: number, floor: number, ceiling: number): any {
return sound.to_pitch_ac(timeStep, floor, ceiling);
}
// ❌ BAD: Implicit any everywhere
function computePitch(sound, timeStep, floor, ceiling) {
return sound.to_pitch_ac(timeStep, floor, ceiling);
}Prefer interfaces over types for objects:
// ✅ GOOD
export interface DataPoint {
id: number;
time: number;
frequency: number;
}
// ⚠️ OK but prefer interface
export type DataPoint = {
id: number;
time: number;
frequency: number;
};Use const for immutable values:
// ✅ GOOD
const MAX_FREQUENCY = 10000;
const defaultColors = { cursor: '#ff0000' };
// ❌ BAD
let MAX_FREQUENCY = 10000;Svelte Components
Component structure:
<script lang="ts">
// 1. Imports
import { onMount } from 'svelte';
import { audioBuffer } from '$lib/stores/audio';
// 2. Props
export let height: number = 600;
export let showCursor: boolean = true;
// 3. Local state
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D | null = null;
// 4. Reactive statements
$: if ($audioBuffer && ctx) {
redraw();
}
// 5. Functions
function redraw() {
// ...
}
// 6. Lifecycle
onMount(() => {
ctx = canvas.getContext('2d');
});
</script>
<!-- 7. Template -->
<canvas bind:this={canvas} {height} />
<!-- 8. Styles -->
<style>
canvas {
cursor: crosshair;
}
</style>
Naming conventions:
- Components:
PascalCase.svelte - Props:
camelCase - Local variables:
camelCase - Constants:
UPPER_SNAKE_CASE
Prefer reactive statements over direct subscriptions:
<!-- ✅ GOOD: Reactive statement -->
<script>
import { cursorPosition } from '$lib/stores/view';
$: console.log('Cursor at', $cursorPosition);
</script>
<!-- ❌ BAD: Manual subscription -->
<script>
import { cursorPosition } from '$lib/stores/view';
import { onMount, onDestroy } from 'svelte';
let position = 0;
let unsubscribe;
onMount(() => {
unsubscribe = cursorPosition.subscribe(p => position = p);
});
onDestroy(() => {
unsubscribe();
});
$: console.log('Cursor at', position);
</script>
File Organization
Store functions:
// src/lib/stores/myStore.ts
/**
* Module docstring explaining what this store manages.
*/
import { writable } from 'svelte/store';
// 1. Type definitions
export interface MyData {
field: string;
}
// 2. Store exports
export const myStore = writable<MyData[]>([]);
// 3. Helper functions (non-exported)
function helperFunction() {
// ...
}
// 4. Public API functions
export function addItem(item: MyData): void {
myStore.update(items => [...items, item]);
}Formatting
Use Prettier defaults (if configured):
- 2 spaces for indentation
- Single quotes for strings (except avoid escaping)
- No semicolons (Prettier will add them)
- Trailing commas where valid
Manual formatting (if no Prettier):
// ✅ GOOD: Consistent spacing
const obj = { a: 1, b: 2 };
if (condition) {
doSomething();
}
// ❌ BAD: Inconsistent spacing
const obj={a:1,b:2};
if(condition){doSomething();}Component Patterns
Store Usage
<script lang="ts">
import { audioBuffer } from '$lib/stores/audio';
import { cursorPosition } from '$lib/stores/view';
// ✅ GOOD: Use reactive statements
$: if ($audioBuffer) {
console.log('Audio loaded');
}
// ✅ GOOD: Direct binding in template
</script>
<div>Cursor: {$cursorPosition.toFixed(3)}s</div>
Canvas Rendering
<script lang="ts">
import { onMount } from 'svelte';
import { audioBuffer } from '$lib/stores/audio';
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D | null = null;
// Reactive redraw
$: if ($audioBuffer && ctx) {
renderWaveform();
}
function renderWaveform() {
if (!ctx || !$audioBuffer) return;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw waveform
// ...
}
onMount(() => {
ctx = canvas.getContext('2d');
});
</script>
<canvas bind:this={canvas} width={800} height={200} />
WASM Memory Management
// ✅ GOOD: Always free WASM objects
export async function analyzeAudio() {
const sound = createSound($audioBuffer, $sampleRate);
try {
const pitch = computePitch(sound, 0.01, 75, 600);
try {
// Use pitch...
} finally {
pitch.free();
}
} finally {
sound.free();
}
}
// ❌ BAD: Memory leak
export async function analyzeAudio() {
const sound = createSound($audioBuffer, $sampleRate);
const pitch = computePitch(sound, 0.01, 75, 600);
// Objects never freed
}Testing
Manual Testing Checklist
Before submitting a pull request, test the following:
Audio Loading: - [ ] Drag & drop WAV file - [ ] Drag & drop MP3 file - [ ] File picker - [ ] Microphone recording - [ ] URL parameter loading (?audio=...)
Visualization: - [ ] Waveform displays correctly - [ ] Spectrogram renders - [ ] Zoom in/out with scroll wheel - [ ] Pan left/right - [ ] Cursor updates on click - [ ] Selection via drag
Overlays: - [ ] Toggle pitch overlay - [ ] Toggle formants overlay - [ ] Toggle intensity overlay - [ ] Toggle HNR, CoG, spectral tilt, A1-P0
Annotations: - [ ] Add annotation tier - [ ] Remove tier - [ ] Add boundary (double-click) - [ ] Remove boundary (right-click) - [ ] Move boundary (drag) - [ ] Edit interval text - [ ] Undo/redo (Ctrl+Z / Ctrl+Y)
Data Points: - [ ] Add data point (double-click spectrogram) - [ ] Move data point (drag) - [ ] Remove data point (right-click) - [ ] Export TSV
Playback: - [ ] Play selection (Space) - [ ] Play visible window (Tab) - [ ] Pause (Space) - [ ] Stop (Escape) - [ ] Cursor tracks during playback
File I/O: - [ ] Export TextGrid - [ ] Import TextGrid - [ ] Save audio as WAV - [ ] Export data points TSV
Browser Compatibility: - [ ] Chrome - [ ] Firefox - [ ] Safari - [ ] Edge
Mobile Viewer: - [ ] URL loading works - [ ] Touch gestures (tap, drag, pinch, pan) - [ ] Settings drawer - [ ] Play button
Testing Long Audio
If your change affects long audio handling (>60s):
Regression Testing
Before submitting, verify you didn’t break:
Pull Request Process
Before Submitting
Update your branch:
git checkout master git pull upstream master git checkout feature/your-feature git rebase masterRun build:
npm run buildEnsure it builds without errors.
Run type checking:
npm run checkFix all TypeScript errors.
Test manually using checklist above
Write good commit messages following guidelines
Opening the PR
- Go to https://github.com/ucpresearch/ozen-web
- Click “New Pull Request”
- Select your fork and branch
- Fill out the PR template:
Title: Clear, concise description
Description:
## What
Brief description of what this PR does.
## Why
Explain the motivation or fix.
## How
Technical details if complex.
## Testing
- [x] Tested on Chrome
- [x] Tested on Firefox
- [x] Ran manual test checklist
- [x] Build passes
- [x] Type check passes
## Screenshots
(If UI change, include before/after screenshots)
Closes #123Code Review
Expect feedback: - Reviewers may request changes - Respond to comments promptly - Push changes to the same branch (PR updates automatically)
Be respectful: - Accept constructive criticism - Explain your reasoning if you disagree - Don’t take feedback personally
After approval: - Maintainer will merge your PR - Delete your feature branch: bash git branch -d feature/your-feature git push origin --delete feature/your-feature
Reporting Issues
Bug Reports
Use the GitHub issue template and include:
Description: What’s wrong?
Steps to reproduce:
1. Load audio file 2. Click on spectrogram 3. Observe error in consoleExpected behavior: What should happen?
Actual behavior: What actually happens?
Environment:
- Browser: Chrome 120
- OS: Windows 11
- Audio file: short.wav (WAV, 16-bit, 16 kHz, 5s)
Screenshots: If applicable
Console errors: Copy full error messages
Feature Requests
Include:
- Use case: Why is this needed?
- Proposed solution: How might it work?
- Alternatives: Other approaches considered?
- Additional context: Examples, mockups, etc.
Documentation
When to Update Docs
Update documentation when you:
- Add a new feature
- Change existing behavior
- Add/remove keyboard shortcuts
- Modify API functions
- Fix significant bugs
Documentation Structure
- User docs:
docs/(Quarto .html files)- Tutorial: Step-by-step guides
- Features: Feature descriptions
- Reference: Technical details
- Developer docs:
docs/development/(this section) - Code comments: Inline JSDoc and comments
Writing Documentation
Use clear, concise language:
<!-- ✅ GOOD -->
## Adding Data Points
Double-click on the spectrogram to add a data point.
<!-- ❌ BAD -->
## Data Point Addition Methodology
In order to facilitate the instantiation of a data collection
point entity, the user should utilize a double-click interaction
modality upon the spectrogram visualization interface.Include code examples:
## Usage
\`\`\`typescript
import { computePitch } from '$lib/wasm/acoustic';
const sound = createSound(samples, sampleRate);
const pitch = computePitch(sound, 0.01, 75, 600);
// ...
pitch.free();
sound.free();
\`\`\`Use screenshots for UI features:
## Settings Panel

Click the gear icon to open settings.Best Practices
Performance
- Debounce expensive operations (analysis during zoom)
- Free WASM objects immediately after use
- Avoid unnecessary re-renders (check reactive dependencies)
- Use
requestAnimationFramefor smooth animations
Accessibility
- Keyboard navigation for all features
- Semantic HTML where possible
- ARIA labels for canvas elements (future)
- Focus indicators visible
Security
- No eval() or unsafe dynamic code
- Validate file inputs (check MIME types)
- Sanitize TextGrid import (no code execution)
- Use CORS for remote audio loading
Compatibility
- Support modern browsers (Chrome 90+, Firefox 88+, Safari 14+)
- Graceful degradation for missing features
- Test on multiple platforms (Windows, Mac, Linux)
Questions?
- GitHub Discussions: For questions and ideas
- GitHub Issues: For bugs and feature requests
- Documentation: Check Development section first
License
By contributing to Ozen-web, you agree that your contributions will be licensed under the MIT License.
Thank you for contributing to Ozen-web! 🎉
See Also
- Setup - Development environment setup
- Architecture - System design
- Stores - State management patterns
- WASM Integration - WASM backend development
Comments and Documentation
Use JSDoc for public APIs:
Inline comments for complex logic: