A browser-based IDE for the Csound audio programming language — write, run, and share Csound projects entirely in the browser, or install it as a native desktop app via Electron.
Read the user guide for project setup and tools. To change an article, see the guide's Markdown source.
- Overview
- Features
- Lisp editing and live evaluation
- Prerequisites
- Getting Started
- Available Scripts
- Environment Variables
- Project Structure
- Architecture Overview
- Deployment
- Contributing
- License
- Acknowledgements
Csound is a sound and music computing system with a long history in computer music. Csound Web IDE brings Csound into the browser: projects are stored in the cloud, the audio engine runs via WebAssembly, and collaboration happens through shareable project URLs — no local installation required. The architecture in one line: WebAssembly Csound engine + React frontend + Firebase backend.
- Real-time Csound audio synthesis executed in-browser via a WebAssembly engine
- Syntax-highlighted code editor (CodeMirror 6 with a dedicated Csound language plugin)
- Multi-file project tree — CSD, ORC, SCO, UDO, and binary audio assets in one place
- Write and preview Markdown notes alongside your Csound files
- Edit Lisp files with Clojure highlighting, rainbow parentheses, and live evaluation
- Cloud persistence — projects saved to Firestore and audio files to Firebase Storage
- User profiles with followers, project discovery, and public/private project visibility
- Shareable editor URLs with injected Open Graph metadata for social previews
- Embeddable player for public projects, with playback and audio downloads
- Built-in Csound manual viewer accessible directly from the IDE
- Optional Electron desktop app for offline use without a browser
Files ending in .clj* or .mal use CodeMirror's Clojure mode with rainbow
parentheses. Brackets in strings and comments do not affect the colors.
Start a CSD or ORC target that loads your Lisp runtime include. That include
must define LispEval(source:S):i: return 0 for success and a nonzero value
for failure.
The runtime receives the exact selected text and owns its environment, reader, and evaluator. It must accept multiple forms for file or region evaluation. Use the existing Csound console to show results and errors.
While the project plays, use the Edit menu or these keys (Cmd on macOS, Ctrl elsewhere):
| Action | Shortcut | Lisp behavior |
|---|---|---|
| Eval Selection / Form | Cmd/Ctrl+E | Evaluate the selection, or the form at the cursor. |
| Eval Block | Cmd/Ctrl+Enter | Evaluate the enclosing top-level form. |
| Eval File | Cmd/Ctrl+Shift+Enter | Evaluate the whole file. |
Eval File also supports .orc, .udo, and .sco files. In a .csd file,
use Eval Block to evaluate an instrument or score statement.
The evaluated region flashes on success or turns red on failure, including a
missing LispEval opcode. WebMCP's csound_evaluate_region uses the same path.
The IDE passes source through a string channel, so quotes, newlines, Unicode,
and Csound macro characters stay intact. It queues evaluations in each engine
to preserve their order. The project's include supplies the Lisp runtime.
Open an audio file in the file tree to see its waveform and file details. Click the waveform to seek, or drag to select a range. The player supports keyboard seeking, volume, mute, and playback speed.
Buttons beside the waveform and table values open editing or analysis tools in that file's editor tab. Trim keeps the selected range; Cut / splice removes it or replaces it with another clip. Inserted clips must match the source's sample rate and channel count. Changes update after a short delay. Download the result or use Add to project to create a separate file, then use its upload button to save it. Returning to File details closes the tool preview and shows the unchanged source.
File properties come from encoded headers. Signal levels use decoded samples, which the browser may resample for some formats. WAV/AIFF instrument and loop settings and broadcast-WAV metadata appear when present. Missing fields say "Not reported". Waveform editing uses the existing 64 MB, eight-channel and 16-million-sample limits; native playback remains available if inspection fails. The metadata parser runs in an on-demand worker. Csound binaries load only when an operation needs them.
| Tool | Required | Notes |
|---|---|---|
| Node.js | 24.x | Local builds and CI use the version in .nvmrc |
| npm | >= 11 | Bundled with Node 24 |
| Firebase CLI | Optional | Required for local Functions emulation and deployment |
| Electron | Optional | Required for desktop builds only |
Install the Firebase CLI globally when needed:
npm install -g firebase-tools
firebase loginNixOS users: a development shell with Electron patched for NixOS is provided in shell.nix.
-
Clone the repository
git clone https://github.com/csound/web-ide.git cd web-ide -
Install dependencies
npm install
-
Configure environment variables
Create a
.envfile in the project root (see Environment Variables for all keys). A minimal example:REACT_APP_DATABASE=DEV VITE_FIREBASE_API_KEY=your-api-key VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com VITE_FIREBASE_DATABASE_URL=https://your-project.firebaseio.com VITE_FIREBASE_PROJECT_ID=your-project-id VITE_FIREBASE_STORAGE_BUCKET=your-project.appspot.com VITE_FIREBASE_MESSAGING_SENDER_ID=000000000000 VITE_FIREBASE_APP_ID=1:000000000000:web:abc123
-
Start the development server
npm run start
The app opens automatically at
http://localhost:3000.
| Script | What it does | When to use it |
|---|---|---|
npm run start |
Vite dev server targeting DEV Firebase | Day-to-day local development |
npm run start:prod |
Vite dev server targeting PROD Firebase | Testing against production data |
npm run build |
Type-check + production Vite build (PROD) | Creating a release artifact |
npm run build:dev |
Type-check + Vite build (DEV) | Staging / preview deploys |
npm run test |
Vitest in watch mode | TDD / local test iteration |
npm run test:ci |
Vitest single run (no watch) | CI pipelines |
npm run lint |
ESLint with zero-warnings policy | Pre-commit / CI quality gate |
npm run lint:fix |
ESLint with auto-fix | Cleaning up lint issues quickly |
npm run format |
Prettier write across src/ |
Auto-formatting source files |
npm run format:check |
Prettier check (no write) | CI formatting gate |
npm run typecheck |
tsc --noEmit strict type check |
Validating types without building |
npm run electron:dev |
Launches Electron against the local dev server | Desktop app development |
npm run deploy |
Firebase deploy to default (production) project | Production release |
npm run deploy:dev |
Firebase deploy to develop project | Staging release |
| Variable | Required | Description | Example |
|---|---|---|---|
REACT_APP_DATABASE |
Yes | Selects Firebase project config (DEV or PROD) |
DEV |
FIREBASE_TOKEN |
CI only | Pre-authenticated Firebase token for headless deploys | — |
VITE_FIREBASE_API_KEY |
Yes | Firebase web API key | AIzaSy... |
VITE_FIREBASE_AUTH_DOMAIN |
Yes | Firebase Auth domain | project.firebaseapp.com |
VITE_FIREBASE_DATABASE_URL |
Yes | Realtime Database URL | https://project.firebaseio.com |
VITE_FIREBASE_PROJECT_ID |
Yes | Firebase project identifier | csound-ide |
VITE_FIREBASE_STORAGE_BUCKET |
Yes | Cloud Storage bucket | project.appspot.com |
VITE_FIREBASE_MESSAGING_SENDER_ID |
Yes | FCM sender ID | 123456789 |
VITE_FIREBASE_APP_ID |
Yes | Firebase App ID | 1:123:web:abc |
The Firebase credentials for the DEV project are embedded in source for local development convenience (public/read-only project). For production, supply real credentials via environment variables and CI secrets.
Cloud Functions use a separate functions/.env file; see functions/.env.example for the
required STORAGE_BUCKET_URL key.
web-ide/
├── src/ # React application source (TypeScript)
│ ├── components/ # Feature and UI components (editor, projects, profile, …)
│ ├── config/ # Firebase client initialisation
│ ├── db/ # Firestore collection references and types
│ ├── elements/ # Shared UI primitives (icons, MIDI piano, scrollbar)
│ ├── store/ # Redux store, slices, and root reducer
│ └── styles/ # Emotion themes and global styles
├── functions/ # Firebase Cloud Functions (TypeScript, Node 22)
│ └── src/ # Function handlers (auth, search, counters, SSR host)
├── public/ # Static assets and Electron entry point
├── config/ # Legacy webpack/build helpers (unused in Vite flow)
├── docs/ # Site guide, developer notes, and architecture documentation
├── scripts/ # Utility build scripts
├── search/ # Experimental standalone search service (inactive)
├── .github/workflows/ # CI/CD pipelines (lint, test, deploy)
├── firebase.json # Firebase Hosting and Functions configuration
├── vite.config.ts # Vite build configuration
└── tsconfig.json # TypeScript compiler settings
The frontend is a single-page application built with Vite and React 18. Redux Toolkit manages all
shared application state — auth session, open projects, editor documents, and playback status.
The code editor uses CodeMirror 6 with @hlolli/codemirror-lang-csound for Csound-specific
syntax highlighting and opcode completion. Audio execution is handled by @csound/browser, a
WebAssembly build of the Csound engine; it can optionally run inside a Web Worker when
SharedArrayBuffer is available (the /editor/** route sets the required COEP / COOP
security headers). Firebase provides authentication, Firestore stores project metadata and file
references, and Cloud Storage holds binary audio assets. A dedicated Firebase Cloud Function
(host) serves the SPA and injects Open Graph metadata into HTML responses for social media
crawlers. An Electron wrapper (public/electron.js) reuses the same Vite build to deliver an
offline-capable desktop app.
GitHub Actions handles website deployment.
The dev workflow compares the prepared function files, dependencies, environment
and deploy settings with an artifact from a successful deployment. It skips
function updates only when those inputs and the live function hashes/revisions
match. Backend changes deploy all functions; a change to the built index.html
deploys only host, which serves that file. Manual-only changes can therefore
publish Hosting without rebuilding functions in Google Cloud.
Missing or expired artifacts cause a full function deploy. Unhealthy or changed live functions also deploy again. The artifact contains hashes, not source or environment values, and is saved only after Hosting and browser checks pass. Use Redeploy all functions even when their inputs match when running the dev workflow manually after external changes, such as rotating a secret. This check does not audit every setting changed through the Google Cloud console.
Dev Hosting pins the verified Cloud Run service behind host directly. This
keeps rollback support without Firebase implicitly deploying host again during
--only hosting. Transient API and quota failures get bounded retries; permission
and authentication failures stop the job. Production deployment is unchanged.
Keep develop and master in the same Git history. Merge changes made directly
on master back into develop before the next release. Promote develop with
a merge or fast-forward into master, then use a normal push. Resetting master
to develop and force-pushing can remove features that shipped only on master,
even when the build and deployment pass.
Serialized Hosting deploys keep replaced, hashed files under /assets/ for 30 days.
An open editor can still load its old JavaScript, CSS, workers and WASM during
that time. Tools stay lazy, and deployment does not reload the page or discard
edits. A service worker cannot protect a tool it has never downloaded without
preloading it, so this protection runs on the server.
The Hosting predeploy hook reads the live release and copies its retired assets
into the new build. /deployment-assets.json records when each file was replaced;
later deploys preserve that date. The first deploy also keeps the live release's
assets, even though that release has no inventory. Downloads check Firebase's
ETag against its file list and stop deployment on a missing or changed response.
The hook needs the Functions dependencies and the same Firebase credentials used
for deployment. After a failed deploy, rebuild before retrying the hook.
HTML and the inventory must revalidate; hashed assets can stay cached. Firebase's
pinTag setting keeps the host function's HTML paired with its Hosting release,
including rollbacks. Pinning takes effect once this config reaches the live site.
Each deployment workflow runs one release at a time for its site, from preparation
through publication. This only orders runs in that GitHub Actions concurrency
group; it does not lock Firebase against local CLI deploys, other workflows,
channel clones, or console rollbacks. The hook's final snapshot check can detect
a release during preparation, but a publisher can still change Hosting after
that check and before this deploy publishes. Such an overlapping release can lose
assets needed by its open tabs. Retention therefore requires one serialized
publishing path per site. The repository does not set IAM restrictions to enforce
that rule.
Keep the home, editor, and profile paths in one grouped host rewrite. Firebase
CLI tags once per rewrite without grouping by service. Separate pinned rewrites
for the same service send concurrent updates with the same Cloud Run resource
version, causing Hosting publication to fail with a version conflict. Browser
access checks use explicit paths in scripts/callable-access.json so they still
check all three routes.
Use the site's deployment workflow and rebuild an old commit for a rollback so it also retains assets needed by newer open tabs. Firebase console rollbacks and channel clones cannot run this retention hook. A lock used only by our scripts would not protect against those paths, and the Hosting release API does not document a precondition for the current live release.
Run node --test scripts/retain-hosting-assets.test.mjs for the deploy checks.
The deployment-assets browser suite builds two releases, switches between them,
and checks that an open page can load an unused tool without losing its edits.
Start a local development session with Electron:
npm run electron:devThis command launches the Vite dev server and opens an Electron window once the server is
ready on port 3000. For packaging a distributable, use electron-builder (already listed as
a dev dependency); refer to its documentation for platform-specific build targets.
Contributions are welcome. The default branch is develop — all pull requests should target
develop, not master.
- Fork the repository and create a branch from
develop. - Make focused, well-scoped changes.
- Commit your work. A Husky pre-commit hook automatically runs
lint,typecheck, andformat:check— fix any failures before pushing. - Open a pull request against
developwith a clear description of the change.
-
npm run lintpasses with zero warnings -
npm run typecheckpasses -
npm run format:checkpasses -
npm run test:cipasses - New behaviour is covered by tests where applicable
Browse open issues and feature requests on the Issues tab.
Distributed under the GNU Lesser General Public License v2.1.
Csound Web IDE is built on the work of the global Csound community — composers, researchers, and developers who have maintained and extended Csound for over three decades.
Browser agents can read and edit Csound source, switch tabs, choose targets, play, stop, render, and read the console through 16 WebMCP tools. Click WebMCP ready in an open project for setup and the tool guide, or read WebMCP usage.
Make the project public, then open Share Project and copy HTML embed code. Paste it into a page that allows iframes:
<iframe
src="https://ide.csound.com/embed/PROJECT_ID"
title="Csound project"
width="100%"
height="360"
style="border: 0;"
loading="lazy"
allow="autoplay"
></iframe>The /embed/:projectId player loads the latest saved files without requiring a
login. It starts with the saved default target (the first file for a playlist),
or falls back to project.csd, default.csd, another CSD, then an ORC. Listeners
can choose a file, play, pause, stop, or render audio and download it. Projects
with file output in CsOptions offer Render audio and an audio player for the
result. Nothing plays until the listener clicks a control.
Private, missing, and empty projects show a message in the player. Links to the
IDE open in a new tab. The embed route needs no cross-origin isolation headers
or SharedArrayBuffer. Serve the parent page over HTTPS. For projects that use
live input, add microphone to the iframe's allow attribute; the listener must
still grant microphone access. A parent site's Permissions Policy can further
restrict these features.
To test playback and a WAV download in a cross-origin iframe against the local DEV server:
cd puppeteer-tests
RUN_EMBED=1 TARGET=local node --test tests/embed.jsSet EMBED_BASE_URL when the server runs on a port other than 3000.
