This document provides a detailed overview of the Tick Tack Timer architecture, design decisions, and implementation details.
- System Overview
- Architecture Layers
- Audio Processing Pipeline
- Component Architecture
- Data Flow
- State Management
- PWA Architecture
- Performance Considerations
- Security Considerations
Tick Tack Timer is a Progressive Web Application that uses advanced audio processing to detect and count mechanical clock ticks in real-time. The architecture is designed for:
- Low latency (<100ms audio processing)
- High performance (WASM for critical code)
- Offline capability (Service Worker caching)
- Responsive design (320px-768px screens)
- Maintainability (modular, testable code)
| Technology | Purpose | Rationale |
|---|---|---|
| Vue 3 | UI Framework | Composition API for better code organization |
| TypeScript | Type Safety | Catch errors at compile time |
| Vite | Build Tool | Fast development and optimized builds |
| AudioWorklet | Audio Processing | Non-blocking, low-latency audio |
| WebAssembly | Tick Detection | High-performance computation |
| AssemblyScript | WASM Source | TypeScript-like syntax for WASM |
| Vitest | Testing | Fast, Vite-native test runner |
| fast-check | Property Testing | Comprehensive input coverage |
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Vue Components, Router, UI State) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Application Layer │
│ (Composables, Business Logic, State Management) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Audio Processing Layer │
│ (AudioManager, AudioWorklet, WASM) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ PWA Layer │
│ (Service Worker, Manifest, Offline Support) │
└─────────────────────────────────────────────────────────┘
Components:
App.vue- Root component with navigationSettingsPage.vue- Microphone selectionCalibrationPage.vue- Clock size calibrationMeasurementPage.vue- Tick counting and displayErrorDisplay.vue- Error messages
Responsibilities:
- Render UI based on state
- Handle user interactions
- Display real-time updates
- Provide visual feedback
Composables:
useAudio- Microphone and audio managementuseCalibration- Calibration logic and persistenceuseCounter- Tick counting and idle detectionuseSession- Session lifecycle and timing
Responsibilities:
- Business logic
- State management
- Data persistence (localStorage)
- Coordinate between layers
Components:
AudioManager- Audio system coordinatorTickProcessorWorklet- Real-time audio processingtick-detector.wasm- High-performance tick detection
Responsibilities:
- Capture microphone input
- Process audio in real-time
- Detect tick events
- Maintain low latency (<100ms)
Components:
sw.js- Service Worker for offline supportmanifest.json- PWA metadata- Icons and assets
Responsibilities:
- Cache app shell and assets
- Enable offline functionality
- Provide installability
- Handle updates
┌──────────────┐
│ Microphone │
└──────┬───────┘
│ getUserMedia()
↓
┌──────────────────┐
│ AudioContext │
│ (Main Thread) │
└──────┬───────────┘
│ createMediaStreamSource()
↓
┌──────────────────────────┐
│ AudioWorkletNode │
│ (Dedicated Thread) │
│ - Receives 128 samples │
│ - Non-blocking │
└──────┬───────────────────┘
│ process()
↓
┌──────────────────────────┐
│ WASM Tick Detector │
│ - RMS calculation │
│ - High-pass filter │
│ - Threshold comparison │
└──────┬───────────────────┘
│ detectTick()
↓
┌──────────────────────────┐
│ Duplicate Detection │
│ (50ms window) │
└──────┬───────────────────┘
│ postMessage()
↓
┌──────────────────────────┐
│ AudioManager │
│ (Main Thread) │
└──────┬───────────────────┘
│ onTickDetected()
↓
┌──────────────────────────┐
│ Counter Composable │
│ - Increment count │
│ - Update UI │
└──────────────────────────┘
AudioWorklet runs in a dedicated high-priority thread, ensuring:
- Non-blocking: Main thread remains responsive
- Low latency: Guaranteed <100ms processing
- Consistent timing: No jitter from main thread activity
WASM provides:
- Performance: Near-native speed for audio processing
- Predictability: Consistent execution time
- Small size: 453 bytes compiled module
Purpose: Coordinate audio system initialization and communication
Key Methods:
class AudioManager {
async initialize(deviceId?: string): Promise<void>
async loadWorklet(): Promise<void>
async loadWasm(): Promise<WebAssembly.Module>
setCalibration(sensitivity: number, threshold: number): void
start(): void
stop(): void
onTickDetected(callback: () => void): void
cleanup(): void
}Responsibilities:
- Initialize Web Audio API
- Load AudioWorklet processor
- Load and instantiate WASM module
- Connect audio graph
- Forward tick events to application
State:
{
audioContext: Ref<AudioContext | null>
selectedDevice: Ref<string | null>
availableDevices: Ref<MediaDeviceInfo[]>
isInitialized: Ref<boolean>
permissionGranted: Ref<boolean>
}Key Methods:
requestPermission()- Request microphone accessenumerateDevices()- List audio input devicesselectDevice()- Activate specific microphoneinitializeWorklet()- Set up AudioManagerstartProcessing()/stopProcessing()- Control audio
State:
{
clockSize: Ref<ClockSize>
sensitivity: Ref<number>
threshold: Ref<number>
isCalibrating: Ref<boolean>
calibrationProgress: Ref<number>
}Key Methods:
startCalibration()- Begin calibrationstopCalibration()- Cancel calibrationsaveCalibration()- Persist to localStorageloadCalibration()- Restore from localStorage
State:
{
count: Ref<number>
lastTickTimestamp: Ref<number>
isIdle: Ref<boolean>
}Key Methods:
increment()- Increase countreset()- Clear count- Automatic idle detection (5 seconds)
State:
{
isActive: Ref<boolean>
duration: Ref<number>
startTime: Ref<number | null>
}Key Methods:
start()- Begin sessionstop()- End sessionreset()- Clear session- Automatic duration tracking
User clicks "Start Session"
↓
MeasurementPage.handleStart()
↓
useSession.start() → Reset counter
↓
useAudio.initializeWorklet() → Load AudioWorklet & WASM
↓
useAudio.setCalibration() → Send settings to worklet
↓
useAudio.startProcessing() → Connect audio graph
↓
[Audio flows through pipeline]
↓
Tick detected in WASM
↓
AudioWorklet posts message to main thread
↓
AudioManager receives message
↓
AudioManager calls onTickDetected callback
↓
useCounter.increment()
↓
Vue reactivity updates UI
↓
MeasurementPage shows new count + visual feedback
User selects clock size
↓
CalibrationPage updates clockSize
↓
User clicks "Start Calibration"
↓
useCalibration.startCalibration()
↓
useAudio.initializeWorklet()
↓
useAudio.startProcessing()
↓
[Ticks detected during calibration]
↓
useCalibration collects samples
↓
After 10+ ticks collected
↓
useCalibration calculates sensitivity & threshold
↓
useCalibration.saveCalibration() → localStorage
↓
User navigates to Measurement page
All composables use Vue's ref() for reactive state:
- Automatic UI updates when state changes
- No manual DOM manipulation needed
- Efficient change detection
Persisted data:
- Microphone selection:
tick-tack-audio-device - Calibration settings:
tick-tack-calibration - Error logs:
tick-tack-errors
Composables use singleton pattern:
- Single instance shared across components
- Consistent state throughout app
- Efficient memory usage
Cache-First Strategy:
// Install: Cache app shell
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
'/',
'/manifest.json',
'/tick-detector.wasm',
'/tick-processor.worklet.js',
// ... other assets
])
})
)
})
// Fetch: Serve from cache, fallback to network
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request)
})
)
})Benefits:
- Fast loading (serve from cache)
- Offline functionality
- Reduced bandwidth usage
{
"name": "Tick Tack Timer",
"short_name": "TickTack",
"display": "standalone",
"orientation": "any",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192" },
{ "src": "/icons/icon-512.png", "sizes": "512x512" }
]
}Features:
- Installable to home screen
- Standalone mode (no browser UI)
- Custom icons and splash screen
- Code splitting: Routes loaded on demand
- Tree shaking: Unused code removed
- Minification: Reduced file sizes
- Gzip compression: Smaller transfer sizes
Results:
- Total: 756 KB
- Vue vendor: 87.19 KB (gzipped: 34.00 KB)
- WASM: 453 bytes
- AudioWorklet: Dedicated thread, no main thread blocking
- WASM: Near-native performance for tick detection
- Efficient algorithms: RMS calculation, high-pass filter
- Duplicate detection: 50ms window prevents false positives
- Vue 3 reactivity: Efficient change detection
- Virtual DOM: Minimal DOM updates
- CSS animations: Hardware-accelerated
- Debouncing: Prevent excessive updates
- HTTPS required: getUserMedia only works on secure origins
- User permission: Explicit permission request
- Permission revocation: Graceful handling if revoked
- No recording: Audio processed in real-time, not stored
- No inline scripts: All scripts in separate files
- No eval(): No dynamic code execution
- HTTPS only: All resources loaded over HTTPS
- Local storage only: No data sent to servers
- No analytics: No tracking or telemetry
- No cookies: No user tracking
- Open source: Code is auditable
- Permission Errors: Microphone access denied
- Initialization Errors: AudioWorklet or WASM load failure
- Calibration Errors: Timeout or insufficient samples
- Runtime Errors: Audio stream interruption
- Automatic retry: For transient errors
- Graceful degradation: Continue with reduced functionality
- User guidance: Clear error messages with resolution steps
- Error logging: Store last 50 errors for debugging
- Test individual functions and components
- Mock external dependencies
- Focus on specific scenarios
- Test universal properties
- Generate random inputs (100+ iterations)
- Validate correctness properties
- Test component interactions
- Verify data flow
- Test full workflows
Coverage: 344 tests, 100% passing
- Multiple clock tracking: Count ticks from multiple clocks
- Export functionality: Export session data as CSV
- Statistics: Average tick rate, session history
- Themes: Dark mode, custom colors
- Accessibility: Screen reader support, keyboard navigation
- Modular architecture: Easy to add new features
- Composable pattern: Reusable logic
- Test coverage: Ensures stability during changes
- Documentation: Comprehensive guides for contributors
For more information, see:
- CONTRIBUTING.md - Contribution guidelines
- DEPLOYMENT.md - Deployment instructions
- TESTING.md - Testing guidelines