Dogalog is a realtime Prolog-based livecoding music environment where you write logic rules to create algorithmic rhythmic patterns and melodies.
- Livecoding: Auto-evaluation with visual feedback - code changes apply automatically
- State Preservation: Cycle counters and cooldowns persist across updates
- Interactive Tutorial: Built-in 13-step guided tutorial for learning
- PWA Support: Install as an app, works offline
- Mobile-First: Touch-friendly UI optimized for all devices
- Modular Architecture: Clean separation of concerns, all files <100 lines
- Comprehensive Testing: 123 tests with 88%+ coverage
Open the dev server URL, click Tutorial to learn, or click Start and begin livecoding!
Rules are Prolog-like statements that define when sounds should play:
% Kick drum on every beat
event(kick, 60, 80, T) :- beat(T, 1).
% Snare on beats 2 and 4
event(snare, 60, 90, T) :- beat(T, 2).
% Hi-hats every 8th note
event(hat, 60, 70, T) :- every(T, 0.5).The system asks event(Voice, Pitch, Velocity, Time) on every step and plays all matching results.
beat(T, N)- Trigger on beat N (1 = downbeat, 2 = halfway, 4 = quarter notes)every(T, Step)- Trigger at regular intervals (0.5 = twice per beat)phase(T, N, K)- Trigger on phase K of N divisionseuc(T, K, N, B, R)- Euclidean rhythm (K hits over N steps)
prob(P)- Succeed with probability P (0.0-1.0)choose(List, X)- Pick random elementcycle(List, X)- Cycle through elements sequentially (stateful!)pick(List, X)- Backtrack through all elementsrand(Min, Max, X)- Random floatrandint(Min, Max, X)- Random integer
scale(Root, Mode, Degree, Oct, Midi)- Convert scale degree to MIDI notechord(Root, Quality, Oct, Midi)- Generate chord tonestranspose(Note, Offset, Out)- Transpose by semitones
within(T, Start, End)- Time range check (for song structure)cooldown(Now, Last, Gap)- Prevent rapid retriggeringeq(A, B),lt(A, B),gt(A, B)- Comparisonsdistinct(List)- Check all elements are uniqueadd(A, B, C)- Arithmeticrange(Start, End, Step, X)- Generate number sequencesrotate(List, Shift, OutList)- Rotate lists
kick- Synthesized kick drumsnare- Noise-based snarehat- Noise-based hi-hatsine- Sine wave monosynth (use MIDI pitch)
Four-on-the-floor with backbeat:
event(kick, 60, 100, T) :- euc(T, 4, 16, 4, 0).
event(snare, 60, 90, T) :- euc(T, 2, 16, 4, 4).Complex polyrhythm:
event(kick, 60, 100, T) :- euc(T, 3, 8, 0.5, 0).
event(snare, 60, 80, T) :- euc(T, 5, 8, 0.5, 2).Pentatonic melody:
note(N) :- cycle([1, 3, 4, 5, 8], D), scale(60, minor_pent, D, 0, N).
event(sine, N, 70, T) :- every(T, 0.5), note(N).Available modes: major, minor, dorian, phrygian, lydian, mixolydian, locrian, minor_pent, major_pent, blues, whole_tone, chromatic
arp(N) :- chord(60, minor, 0, Notes), choose(Notes, N).
event(sine, N, 70, T) :- every(T, 0.25), arp(N).Chord qualities: maj, min, dim, aug, sus2, sus4, maj7, min7, dom7, dim7
Random velocities:
vel(V) :- choose([60, 80, 100], V).
event(hat, 60, V, T) :- every(T, 0.25), vel(V).Sparse pattern:
event(snare, 60, 80, T) :- beat(T, 2), prob(0.3).% Intro: just kick (beats 0-8)
event(kick, 60, 100, T) :- beat(T, 1).
% Verse: add snare (beats 8-16)
event(snare, 60, 80, T) :- beat(T, 2), within(T, 8, 16).
% Chorus: add melody (beats 16-24)
melody(N) :- scale(60, major, D, 0, N), cycle([1,3,5,8], D).
event(sine, N, 70, T) :- every(T, 0.5), melody(N), within(T, 16, 24).% Regular pattern
event(kick, 60, 100, T) :- beat(T, 1).
% Fill every 4+ bars
fill(T) :- beat(T, 1), cooldown(T, last_fill, 4).
event(snare, 60, V, T) :- fill(T), every(T, 0.25), choose([80,90,100], V).npm install # Install dependencies
npm run dev # Start dev server (http://localhost:5173)
npm run build # Build for production
npm run preview # Preview production build
npm test # Run tests
npm run test:ui # Run tests with UI
npm run test:coverage # Generate coverage report
npm run docs:html # Build manual and cheatsheetsrc/
├── prolog/ # Prolog engine
│ ├── builtins/ # Builtin predicates (modular)
│ ├── parser.js # Parser
│ ├── resolution.js # SLD resolution with generators
│ ├── tokenizer.js # Tokenizer
│ ├── unify.js # Unification
│ └── terms.js # Term constructors
├── audio/ # WebAudio synthesis
│ └── audioEngine.js
├── scheduler/ # Timing and execution
│ ├── scheduler.js
│ ├── stateManager.js
│ └── transitionManager.js
├── livecoding/ # Auto-evaluation
│ ├── codeValidator.js
│ └── liveEvaluator.js
├── tutorial/ # Tutorial system
│ ├── tutorialManager.js
│ ├── tutorialOverlay.js
│ └── steps.js
├── ui/ # User interface
│ ├── components/ # Reusable components
│ ├── template.js
│ ├── controls.js
│ └── validationIndicator.js
├── help/ # Documentation
│ └── builtinDocs.js
├── config/ # Configuration
│ └── defaults.js
├── app.js # Application orchestrator
└── main.js # Entry point
- Prolog Engine: Custom implementation with SLD resolution using ES6 generators for backtracking
- Livecoding: Debounced auto-evaluation (300ms) with syntax validation
- State Management: Centralized state for cycle counters and cooldowns (persists across code updates)
- Scheduler: Time-grid based (16th notes) with swing and lookahead support
- Audio: WebAudio synthesis without samples - all sounds generated in real-time
- UI: Mobile-first, progressive enhancement, DOM-based components
- 123 tests across 16 test files
- Coverage: 88.52% statements, 90.42% functions
- Integration tests for livecoding, state preservation, example loading
- UI component tests with vitest + jsdom
- Installable on mobile and desktop
- Offline support with service worker
- Caches all assets and documentation
- Manifest with icons and theme colors
- Interactive Tutorial: Click "Tutorial" button in the app for 13 guided steps
- Full Tutorial: Comprehensive guide teaching both Prolog and livecoding
- Manual: Complete reference for all built-ins and syntax
- Cheatsheet: Quick reference for common patterns
- Live Demo: GitHub Pages
- User edits code in editor
- After 300ms debounce, code is validated
- If valid: parse → compile → swap program (with smooth transition)
- State (cycles, cooldowns) persists across updates
- Visual indicator shows validation state (green/red/yellow)
% This pattern's state persists when you edit other code:
drums(D) :- cycle([kick, snare, hat], D).
event(D, 60, 80, T) :- beat(T, 1), drums(D).
% Editing this won't reset the cycle counter!Euclidean rhythms distribute K hits as evenly as possible over N steps using the Euclidean algorithm. The result is musically interesting patterns used in music worldwide:
euc(T, 3, 8, 0.5, 0)- Tresillo pattern (Cuban music)euc(T, 5, 8, 0.5, 0)- Cinquillo patterneuc(T, 5, 12, 0.5, 0)- Common rock beat
- Modern browsers with WebAudio API
- Chrome/Edge 90+
- Firefox 88+
- Safari 14.1+
- Mobile browsers (iOS Safari, Chrome Android)
MIT
Built with vanilla JavaScript, CodeMirror 6, and WebAudio API. Inspired by TidalCycles, Sonic Pi, and Datalog.