Building ColDAW from 0 to 1

By Joe Deng | Mar 2026


What is ColDAW

ColDAW is a collaborative music production platform that helps music creators and teams keep project versions organized and collaborate across tools, so they spend less time managing files and more time creating.

How It Works Technically

ColDAW parses Ableton .als files into a tool-agnostic schema, then layers Git-style versioning, structural diffs, branching, and merging on top.

Components:

  1. a React 18 + TypeScript web app

  2. a C++/JUCE VST3 plugin

  3. a Node.js + PostgreSQL + Redis backend


Part I - Ideation

The Problem

Music creators waste hours on organizing files like final_v2_REAL_final.als.

Insights

  1. Shouldn't ask users to leave their workstation: a web interface + a plugin

  2. The tooling framework as a product: build an API instead of an app

  3. Cross ecosystem integration: support most major workstations

Solution


Part II - R&D from 0 to 1

Tech Stack

Layer

Tech

Plugin

C++ / JUCE / VST3

Backend

Node.js, Express, TypeScript, Socket.io

Database

PostgreSQL + Redis

Frontend

React 18, TypeScript, Vite, Zustand, styled-components

File storage

Alibaba Cloud OSS

Deployment

Railway, Docker


Decision Walk-Through

Parser

.als is a gzipped XML file with an undocumented schema that drifts across Ableton versions. If this fails, nothing else could work.

// services/abletonParser.ts
export const parseAbletonProject = async (filePath: string) => {
  const compressed = await fs.readFile(filePath);
  const xml = gunzipSync(compressed).toString('utf-8');
  const parsed = await new xml2js.Parser().parseStringPromise(xml);
  const liveset = parsed.Ableton.LiveSet[0];

  return {
    tempo: extractTempo(liveset),
    timeSignature: extractTimeSignature(liveset),
    tracks: extractTracks(liveset),
    automation: extractAutomation(liveset),
  };
};
// services/abletonParser.ts
export const parseAbletonProject = async (filePath: string) => {
  const compressed = await fs.readFile(filePath);
  const xml = gunzipSync(compressed).toString('utf-8');
  const parsed = await new xml2js.Parser().parseStringPromise(xml);
  const liveset = parsed.Ableton.LiveSet[0];

  return {
    tempo: extractTempo(liveset),
    timeSignature: extractTimeSignature(liveset),
    tracks: extractTracks(liveset),
    automation: extractAutomation(liveset),
  };
};

Five categories we learned from how producers actually describe changes:

Category

Contents

Track structure

Ordering, type, grouping

Clip contents

Start, length, MIDI notes, sample refs

Automation

Parameter-keyed envelopes

Global parameters

Tempo, time sig, loop region

Device & routing

Inserts, sends, signal flow


Versioning

Design Decision

Auto-apply non-conflicting changes.


Development Decision

A versioning API with the same shape as Git:

Commit { id, projectId, message, parentCommitId, snapshotPath, timestamp }
Branch { id, name, projectId, headCommitId }
Commit { id, projectId, message, parentCommitId, snapshotPath, timestamp }
Branch { id, name, projectId, headCommitId }

A snapshot is the serialized symbolic representation, stored on disk, referenced by the commit. Merge walks both snapshots' track trees:

const diffSnapshots = (a: Snapshot, b: Snapshot) => {
  const changes = [];
  for (const track of b.tracks) {
    const prev = a.tracks.find(t => t.id === track.id);
    if (!prev) changes.push({ type: 'added', track });
    else if (track.muted !== prev.muted)
      changes.push({ type: 'modified', field: 'muted', track });
    // clips, automation, devices...
  }
  return changes;
};
const diffSnapshots = (a: Snapshot, b: Snapshot) => {
  const changes = [];
  for (const track of b.tracks) {
    const prev = a.tracks.find(t => t.id === track.id);
    if (!prev) changes.push({ type: 'added', track });
    else if (track.muted !== prev.muted)
      changes.push({ type: 'modified', field: 'muted', track });
    // clips, automation, devices...
  }
  return changes;
};


VST3 Plugin

Design Decision

One-click upoad and in-plugin audio assets inspector, allowing for upload directly within local workstation without having to open website.

Development Decision

Built in JUCE/C++. Loads as a normal audio plugin, authenticates with the backend, detects the open project, hashes referenced samples, dedupes against the server, and uploads only the diff.

void uploadProjectSamples(const String& projectId, const String& alsPath) {
  auto audioFiles = collectReferencedAudioFiles(alsPath);
  
  std::vector<FileInfo> infos;
  for (auto& file : audioFiles)
    infos.push_back({ file, calculateSHA256(file) });
  
  auto existing = checkFilesExist(projectId, extractHashes(infos));
  
  for (auto& info : infos)
    if (!existing.contains(info.hash))
      uploadAudioFile(projectId, info.filePath);
}
void uploadProjectSamples(const String& projectId, const String& alsPath) {
  auto audioFiles = collectReferencedAudioFiles(alsPath);
  
  std::vector<FileInfo> infos;
  for (auto& file : audioFiles)
    infos.push_back({ file, calculateSHA256(file) });
  
  auto existing = checkFilesExist(projectId, extractHashes(infos));
  
  for (auto& info : infos)
    if (!existing.contains(info.hash))
      uploadAudioFile(projectId, info.filePath);
}


Web App UI

Design decision

Editor: An interface feels similar to music producers, muti-color color pallete, and actual clip waveform and midi pitch and vvelocity redering, refering time-based media editing softwares.

Inspector & Sidebar view: gives users a similar UXUI feel of figma and real DAW interface. Allows for inspecting parameters and exporting per-track formats.


Development Decision

React 18 + TypeScript + Vite + Zustand for state, Socket.io for real-time.

The arrangement view renders per-clip change highlighting, color-coded against the previous commit.

<MainContent style={{
  marginRight: inspectorOpen ? 320 
             : audioSidebarOpen ? 240 
             : 0
}}>
<MainContent style={{
  marginRight: inspectorOpen ? 320 
             : audioSidebarOpen ? 240 
             : 0
}}>


Collaboration

Design decision

Last-write-wins on parameter edits with optimistic client rendering. Edits appear instantly.

Development Decision

Socket.io

socket.emit('join-project', { projectId, userName, userId });

socket.on('project-update', (changes) => applyChanges(changes));
socket.on('user-joined', ({ collaborator }) => addCollaborator(collaborator));
socket.on('cursor-update', ({ socketId, x, y }) => updateCursor(socketId, x, y));
socket.emit('join-project', { projectId, userName, userId });

socket.on('project-update', (changes) => applyChanges(changes));
socket.on('user-joined', ({ collaborator }) => addCollaborator(collaborator));
socket.on('cursor-update', ({ socketId, x, y }) => updateCursor(socketId, x, y));


Cross-tool Translation

Example: TouchDesigner
// server
GET /api/projects/:id/touchdesigner
{ tempo, tracks: [{ name, volume, clips: [...] }], automation }
// server
GET /api/projects/:id/touchdesigner
{ tempo, tracks: [{ name, volume, clips: [...] }], automation }
# TouchDesigner client
def onFrameStart(frame):
    if frame % 30 == 0:
        r = requests.get(f'{API}/projects/{ID}/touchdesigner', headers=AUTH)
        op('tempo').par.value0 = r.json()['tempo']
# TouchDesigner client
def onFrameStart(frame):
    if frame % 30 == 0:
        r = requests.get(f'{API}/projects/{ID}/touchdesigner', headers=AUTH)
        op('tempo').par.value0 = r.json()['tempo']


Storage

First deploy lost user data on every push (Railway containers are ephemeral). Fix was a DATA_DIR env var so the same code runs against local dev, Railway volumes, or S3.

const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '..', 'data');
const uploadDir = path.join(DATA_DIR, 'uploads');
const projectsDir = path.join(DATA_DIR, 'projects');
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '..', 'data');
const uploadDir = path.join(DATA_DIR, 'uploads');
const projectsDir = path.join(DATA_DIR, 'projects');
Pipeline
  • Audio samples → Alibaba Cloud OSS.

  • Project metadata → PostgreSQL.

  • Sessions → Redis with TTLs


Validation


Team and Operating

8-person team across NYU Tisch and Georgia Tech Music Tech and CS. Weekly sprints in Lark, GitHub, Figma. I run product strategy and hold the founder seat across all three groups (engineering, design, GTM).


Building ColDAW from 0 to 1

By Joe Deng | Mar 2026


What is ColDAW

ColDAW is a collaborative music production platform that helps music creators and teams keep project versions organized and collaborate across tools, so they spend less time managing files and more time creating.

How It Works Technically

ColDAW parses Ableton .als files into a tool-agnostic schema, then layers Git-style versioning, structural diffs, branching, and merging on top.

Components:

  1. a React 18 + TypeScript web app

  2. a C++/JUCE VST3 plugin

  3. a Node.js + PostgreSQL + Redis backend


Part I - Ideation

The Problem

Music creators waste hours on organizing files like final_v2_REAL_final.als.

Insights

  1. Shouldn't ask users to leave their workstation: a web interface + a plugin

  2. The tooling framework as a product: build an API instead of an app

  3. Cross ecosystem integration: support most major workstations

Solution


Part II - R&D from 0 to 1

Tech Stack

Layer

Tech

Plugin

C++ / JUCE / VST3

Backend

Node.js, Express, TypeScript, Socket.io

Database

PostgreSQL + Redis

Frontend

React 18, TypeScript, Vite, Zustand, styled-components

File storage

Alibaba Cloud OSS

Deployment

Railway, Docker


Decision Walk-Through

Parser

.als is a gzipped XML file with an undocumented schema that drifts across Ableton versions. If this fails, nothing else could work.

// services/abletonParser.ts
export const parseAbletonProject = async (filePath: string) => {
  const compressed = await fs.readFile(filePath);
  const xml = gunzipSync(compressed).toString('utf-8');
  const parsed = await new xml2js.Parser().parseStringPromise(xml);
  const liveset = parsed.Ableton.LiveSet[0];

  return {
    tempo: extractTempo(liveset),
    timeSignature: extractTimeSignature(liveset),
    tracks: extractTracks(liveset),
    automation: extractAutomation(liveset),
  };
};

Five categories we learned from how producers actually describe changes:

Category

Contents

Track structure

Ordering, type, grouping

Clip contents

Start, length, MIDI notes, sample refs

Automation

Parameter-keyed envelopes

Global parameters

Tempo, time sig, loop region

Device & routing

Inserts, sends, signal flow


Versioning

Design Decision

Auto-apply non-conflicting changes.


Development Decision

A versioning API with the same shape as Git:

Commit { id, projectId, message, parentCommitId, snapshotPath, timestamp }
Branch { id, name, projectId, headCommitId }

A snapshot is the serialized symbolic representation, stored on disk, referenced by the commit. Merge walks both snapshots' track trees:

const diffSnapshots = (a: Snapshot, b: Snapshot) => {
  const changes = [];
  for (const track of b.tracks) {
    const prev = a.tracks.find(t => t.id === track.id);
    if (!prev) changes.push({ type: 'added', track });
    else if (track.muted !== prev.muted)
      changes.push({ type: 'modified', field: 'muted', track });
    // clips, automation, devices...
  }
  return changes;
};


VST3 Plugin

Design Decision

One-click upoad and in-plugin audio assets inspector, allowing for upload directly within local workstation without having to open website.

Development Decision

Built in JUCE/C++. Loads as a normal audio plugin, authenticates with the backend, detects the open project, hashes referenced samples, dedupes against the server, and uploads only the diff.

void uploadProjectSamples(const String& projectId, const String& alsPath) {
  auto audioFiles = collectReferencedAudioFiles(alsPath);
  
  std::vector<FileInfo> infos;
  for (auto& file : audioFiles)
    infos.push_back({ file, calculateSHA256(file) });
  
  auto existing = checkFilesExist(projectId, extractHashes(infos));
  
  for (auto& info : infos)
    if (!existing.contains(info.hash))
      uploadAudioFile(projectId, info.filePath);
}


Web App UI

Design decision

Editor: An interface feels similar to music producers, muti-color color pallete, and actual clip waveform and midi pitch and vvelocity redering, refering time-based media editing softwares.

Inspector & Sidebar view: gives users a similar UXUI feel of figma and real DAW interface. Allows for inspecting parameters and exporting per-track formats.


Development Decision

React 18 + TypeScript + Vite + Zustand for state, Socket.io for real-time.

The arrangement view renders per-clip change highlighting, color-coded against the previous commit.

<MainContent style={{
  marginRight: inspectorOpen ? 320 
             : audioSidebarOpen ? 240 
             : 0
}}>


Collaboration

Design decision

Last-write-wins on parameter edits with optimistic client rendering. Edits appear instantly.

Development Decision

Socket.io

socket.emit('join-project', { projectId, userName, userId });

socket.on('project-update', (changes) => applyChanges(changes));
socket.on('user-joined', ({ collaborator }) => addCollaborator(collaborator));
socket.on('cursor-update', ({ socketId, x, y }) => updateCursor(socketId, x, y));


Cross-tool Translation

Example: TouchDesigner
// server
GET /api/projects/:id/touchdesigner
{ tempo, tracks: [{ name, volume, clips: [...] }], automation }
# TouchDesigner client
def onFrameStart(frame):
    if frame % 30 == 0:
        r = requests.get(f'{API}/projects/{ID}/touchdesigner', headers=AUTH)
        op('tempo').par.value0 = r.json()['tempo']


Storage

First deploy lost user data on every push (Railway containers are ephemeral). Fix was a DATA_DIR env var so the same code runs against local dev, Railway volumes, or S3.

const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, '..', 'data');
const uploadDir = path.join(DATA_DIR, 'uploads');
const projectsDir = path.join(DATA_DIR, 'projects');
Pipeline
  • Audio samples → Alibaba Cloud OSS.

  • Project metadata → PostgreSQL.

  • Sessions → Redis with TTLs


Validation


Team and Operating

8-person team across NYU Tisch and Georgia Tech Music Tech and CS. Weekly sprints in Lark, GitHub, Figma. I run product strategy and hold the founder seat across all three groups (engineering, design, GTM).


ColDAW Deck

ColDAW is a digital studio that makes it easy for music creatives and multimedia collaborators to share, manage, and collaborate on music projects across different software. ColDAW lets collaborators work in parallel across various audio workstations and multimedia software, and automatically keeps everything in sync.

Year

2025

Year

2025

Role

Founder

Role

Founder

Client

This is a self-lead project.

Client

This is a self-lead project.

Timeline

5 month

Timeline

5 month

ColDAW Deck

ColDAW is a digital studio that makes it easy for music creatives and multimedia collaborators to share, manage, and collaborate on music projects across different software. ColDAW lets collaborators work in parallel across various audio workstations and multimedia software, and automatically keeps everything in sync.

Year

2025

Role

Founder

Client

This is a self-lead project.

Timeline

5 month