Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2

updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
commit-message:
prefix: chore
reviewers:
- steady41

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
commit-message:
prefix: ci
reviewers:
- steady41
30 changes: 28 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ name: CI

on:
push:
branches: [main]
branches: ['**']
pull_request:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
check:
runs-on: ubuntu-latest
Expand All @@ -30,9 +34,31 @@ jobs:
- name: Build
run: npm run build

- name: Unit tests (node:test, headless engine + wav)
run: npm test

- name: Syntax-check every compiled module
run: |
fail=0
for f in $(find dist -name '*.js' -type f); do
node --check "$f" || fail=1
done
exit $fail

- name: Verify build output
run: |
test -f dist/main.js
test -f dist/audio/engine.js
test -d dist/ui
echo "dist/ sanity check passed"
echo "dist/ sanity check passed"

- name: Verify index.html asset links resolve
run: |
missing=0
for ref in $(grep -oE '(src|href)="[^"]+"' index.html | sed -E 's/(src|href)="([^"]+)"/\2/' | grep -v '^https\?:'); do
if [ ! -f "$ref" ]; then
echo "::error::index.html references missing file: $ref"
missing=1
fi
done
exit $missing
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Prepare dist for Pages
run: |
cp index.html dist/
sed -i 's|src/styles/|styles/|g; s|src/main.js|main.js|' dist/index.html
sed -i 's|src/styles/|styles/|g; s|dist/main.js|main.js|' dist/index.html
cp -r src/styles dist/

- name: Upload artifact
Expand Down
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# VOIDSTATION

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![CI](https://github.com/steady41/voidstation/actions/workflows/ci.yml/badge.svg)](https://github.com/steady41/voidstation/actions/workflows/ci.yml)

An analog-hardware-styled web DAW built with TypeScript and the Web Audio
API. No backend, no dependencies: patterns, samples, effects, the playlist
Expand All @@ -10,8 +11,10 @@ The interface is a canvas-style studio room — drag hardware modules (step
sequencer, piano roll, playlist, mixer, transport) around a 3400×2800 wall,
zoom with `Ctrl`+wheel, and arrange a full track: 16-step sequencing,
piano-roll notes, per-track reverb/delay/EQ, a synth editor (waveform +
ADSR), sample trimming, loop regions, swing/groove, a master bus with
limiter, WAV export, and gesture-based undo/redo.
ADSR), sample trimming and pitch, loop regions, swing/groove, a master bus
with limiter, WAV + stem export, computer-keyboard / Web MIDI live input,
a MODULES dock for arranging the studio wall, sample trim/reverse/pitch,
and gesture-based undo/redo.

The old UI is archived in git tag `legacy-vscode-ui`.

Expand All @@ -34,6 +37,13 @@ The main interface is a canvas-style "studio room": a large world
their brushed-metal headers. Pan with native scroll, zoom with
`Ctrl`+wheel (or the HUD `— / % / + / ⌂` buttons).

Every module is also listed in the **MODULES dock** — a slide-out sidebar on
the left edge (`MODULES` tab or the HUD `▤` button). Drag a chip onto the
wall to place its module, drag an on-wall chip to relocate it, click a chip
to auto-pan to it, and use the `⏏` button in a header to put a unit away.
Double-clicking a header collapses/expands the body; docked/collapsed state
persists with the wall layout (`voidstation-wall-v1`).

Modules (one feature = one file in `src/ui/`):

| Module | Description |
Expand All @@ -45,6 +55,7 @@ Modules (one feature = one file in `src/ui/`):
| Mixer | 5 analog channels: pan knob, LED meter, mute/solo, volume fader, channel click selects the piano-roll track |
| Synth editor | per-track waveform (sine/tri/sqr/saw/noise) + ADSR envelope sliders with note preview |
| Master section | master volume, brickwall limiter (toggle + threshold), real-time output LED meter |
| Live controller | play the selected track from the computer keyboard (`Z S X D C V G B H N J M…`, `-`/`=` octave) or a Web MIDI device, with held-note voices |

Module positions persist across reloads (`voidstation-wall-v1`).

Expand All @@ -57,7 +68,8 @@ Module positions persist across reloads (`voidstation-wall-v1`).
delay (feedback loop), 3-band EQ (lowshelf 250 Hz / peaking 1 kHz / highshelf
4 kHz) — per-track, live and in export.
- **Samples**: load a sample per track, canvas waveform with draggable
start/end trim handles, preview by clicking the wave.
start/end trim handles, preview by clicking the wave, tape-style pitch
(-24..+12 semitones) and a REVERSE toggle.
- **Velocity accents**: each sequencer step carries a velocity (1.0 / 0.7 /
0.45). Right-click (or `Shift`+click) an active pad to cycle its accent;
dimmer pads play softer. Applied to both sample triggers and synth notes,
Expand All @@ -72,6 +84,17 @@ Module positions persist across reloads (`voidstation-wall-v1`).
(`DynamicsCompressor`, toggle + threshold). A real-time output LED meter is
fed by an `AnalyserNode` tapped after the limiter. All audible live and in
the WAV export.
- **Live controller**: held-note synth voices (attack → decay → sustain until
release) from the computer keyboard or any Web MIDI input device; octave
shift, on-screen keys, device hot-plug.
- **Pattern copy/paste**: COPY grabs every track of the current pattern into
an in-memory clipboard, PASTE overwrites the active pattern (single undo
entry).
- **Sample pitch**: per-track tape-style playback rate in semitones
(-24..+12), applied live and in exports; a fresh sample load resets it.
- **Stem export**: `EXPORT STEMS` renders every track that plays anything in
the export region to its own WAV file (`voidstation-stem-01-kick.wav`, …),
fx chains and master bus included.
- **Piano roll**: draw / resize / erase notes, real-time playback preview.
- **Playlist**: patterns, clip drag & drop (move/swap), loop region rendered
into the WAV export.
Expand Down Expand Up @@ -105,8 +128,11 @@ src/ui/fx.ts — per-track reverb/delay/EQ rack
src/ui/sampler.ts — sample load/trim/preview
src/ui/instrument.ts — per-track synth editor (waveform + ADSR)
src/ui/master.ts — master bus: volume, limiter, output meter
src/ui/keys.ts — live controller: computer keyboard + Web MIDI
src/ui/app.ts — assembly: engine + wall + modules + wiring
src/styles/ — theme/base/racks/topbar/transport/drawer/sequencer/pianoroll/playlist/mixer/fx/sampler/instrument/master
tests/ — node:test suites (headless engine + WAV encoder)
run via `npm test` against the compiled dist/
src/styles/ — theme/base/racks/topbar/transport/drawer/sequencer/pianoroll/playlist/mixer/fx/sampler/instrument/master/keys/dock
```

`dist/` (compiled JS) and `node_modules/` are git-ignored; the engine
Expand Down
4 changes: 3 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
<link rel="stylesheet" href="src/styles/sampler.css">
<link rel="stylesheet" href="src/styles/instrument.css">
<link rel="stylesheet" href="src/styles/master.css">
<link rel="stylesheet" href="src/styles/keys.css">
<link rel="stylesheet" href="src/styles/dock.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="src/main.js"></script>
<script type="module" src="dist/main.js"></script>
</body>
</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"test": "node --test tests/*.test.mjs",
"serve": "python3 -m http.server 8000"
},
"devDependencies": {
Expand Down
Loading
Loading