@hraness/dawg 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +138 -0
- package/DAWG.md +608 -39
- package/README.md +4 -4
- package/core/chords.ts +1724 -0
- package/core/diff.ts +13 -1
- package/core/euclid.ts +670 -0
- package/core/fx.ts +1065 -0
- package/core/kits.ts +320 -0
- package/core/params.ts +111 -0
- package/core/rhythm.ts +287 -0
- package/core/score.ts +735 -18
- package/core/sdk/eval-child.ts +34 -22
- package/core/sdk/eval.ts +4 -1
- package/core/sdk/print.ts +304 -22
- package/core/sdk/sync-chords.ts +54 -0
- package/core/sdk/v1.ts +3226 -21
- package/core/synth.ts +1001 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +38 -3
- package/src/agent/brief.ts +29 -4
- package/src/agent/chord-tools.ts +357 -0
- package/src/agent/drum-tools.ts +135 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/pack-tools.ts +369 -0
- package/src/agent/planner.ts +17 -2
- package/src/agent/preview-tool.ts +270 -0
- package/src/agent/rhythm-tools.ts +145 -0
- package/src/agent/tools.ts +308 -14
- package/src/agent/xcb-agent.ts +8 -2
- package/src/audio/audition.ts +93 -0
- package/src/audio/cache.ts +160 -0
- package/src/audio/effects/bus.ts +147 -0
- package/src/audio/effects/chain.ts +109 -0
- package/src/audio/effects/common.ts +251 -0
- package/src/audio/effects/convolution.ts +339 -0
- package/src/audio/effects/drive.ts +142 -0
- package/src/audio/effects/duck.ts +122 -0
- package/src/audio/effects/dynamics.ts +121 -0
- package/src/audio/effects/filter.ts +319 -0
- package/src/audio/effects/modulation.ts +174 -0
- package/src/audio/effects/space.ts +394 -0
- package/src/audio/engine.ts +112 -4
- package/src/audio/kits.ts +200 -0
- package/src/audio/packs.ts +1787 -0
- package/src/audio/preview.ts +481 -0
- package/src/audio/random.ts +15 -0
- package/src/audio/sampler.ts +157 -29
- package/src/audio/samples.ts +386 -44
- package/src/audio/synth/oscillators.ts +268 -0
- package/src/audio/synth/voice.ts +555 -0
- package/src/audio/synth/zzfx.ts +137 -0
- package/src/audio/wav.ts +411 -295
- package/src/audio/wavetable-maker.ts +717 -0
- package/src/audio/wavetable.ts +624 -0
- package/src/commands/drums.ts +299 -0
- package/src/commands/edit.ts +26 -4
- package/src/commands/fx.ts +360 -0
- package/src/commands/help.ts +298 -10
- package/src/commands/music.ts +10 -8
- package/src/commands/pack.ts +423 -0
- package/src/commands/rhythm.ts +230 -0
- package/src/commands/sample.ts +162 -2
- package/src/commands/synth.ts +229 -0
- package/src/commands/wavetable.ts +370 -0
- package/src/main.ts +1222 -30
- package/src/media/cli.ts +15 -2
- package/src/media/tools.ts +106 -1
- package/src/media/wavetable.ts +202 -0
- package/src/render.ts +22 -1
- package/src/session/naming.ts +3 -1
- package/src/tui/audition.ts +521 -0
- package/src/tui/euclid.ts +516 -0
- package/src/tui/menu.ts +1305 -244
- package/src/tui/play-chords.ts +630 -0
- package/src/tui/play-session.ts +364 -16
- package/src/tui/sketch.ts +108 -0
- package/src/web/search.ts +4 -1
- package/tui/app.ts +161 -39
- package/tui/grammar.ts +286 -0
- package/tui/highway.ts +7 -1
- package/tui/play-strip.ts +59 -14
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drum kits: the synthesized kits from `core/kits.ts` rendered sample by
|
|
3
|
+
* sample, and the one catalog `/kit`, `/menu` and the agent list.
|
|
4
|
+
*
|
|
5
|
+
* The renderer calls `kitDrumSample` only for a track with `kit` set, so the
|
|
6
|
+
* default voices in `wav.ts` stay byte-identical. Everything here is plain
|
|
7
|
+
* arithmetic on the seeded noise `wav.ts` passes in, so a kit renders the
|
|
8
|
+
* same buffer on every run.
|
|
9
|
+
*/
|
|
10
|
+
import type { DrumVoice } from "../../core/drums.ts";
|
|
11
|
+
import {
|
|
12
|
+
SYNTH_KITS,
|
|
13
|
+
type DrumBodyParams,
|
|
14
|
+
type HatParams,
|
|
15
|
+
type SynthKit,
|
|
16
|
+
synthKit,
|
|
17
|
+
} from "../../core/kits.ts";
|
|
18
|
+
import type { TrackScore } from "../../core/score.ts";
|
|
19
|
+
import { DEFAULT_KITS } from "./packs.ts";
|
|
20
|
+
|
|
21
|
+
/** Per-hit oscillator and filter state. */
|
|
22
|
+
export type KitVoiceState = {
|
|
23
|
+
phase: number;
|
|
24
|
+
band: number;
|
|
25
|
+
dark: number;
|
|
26
|
+
metalPrevious: number;
|
|
27
|
+
holdLeft: number;
|
|
28
|
+
held: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function newKitVoiceState(): KitVoiceState {
|
|
32
|
+
return {
|
|
33
|
+
phase: 0,
|
|
34
|
+
band: 0,
|
|
35
|
+
dark: 0,
|
|
36
|
+
metalPrevious: 0,
|
|
37
|
+
holdLeft: 0,
|
|
38
|
+
held: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 808 hi-hat oscillator frequencies (six detuned squares). */
|
|
43
|
+
const METAL_HZ = [205.3, 304.4, 369.6, 522.7, 540, 800] as const;
|
|
44
|
+
|
|
45
|
+
function body(
|
|
46
|
+
params: DrumBodyParams,
|
|
47
|
+
t: number,
|
|
48
|
+
noise: number,
|
|
49
|
+
state: KitVoiceState,
|
|
50
|
+
sampleRate: number,
|
|
51
|
+
): number {
|
|
52
|
+
const frequency =
|
|
53
|
+
params.base + params.sweep * Math.exp(-t * params.sweepRate);
|
|
54
|
+
state.phase += frequency / sampleRate;
|
|
55
|
+
return (
|
|
56
|
+
Math.sin(2 * Math.PI * state.phase) *
|
|
57
|
+
Math.exp(-t * params.decay) *
|
|
58
|
+
params.level +
|
|
59
|
+
noise * params.click * Math.exp(-t * params.clickDecay)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hat(
|
|
64
|
+
params: HatParams,
|
|
65
|
+
t: number,
|
|
66
|
+
noise: number,
|
|
67
|
+
bright: number,
|
|
68
|
+
state: KitVoiceState,
|
|
69
|
+
): number {
|
|
70
|
+
state.dark += 0.3 * (noise - state.dark);
|
|
71
|
+
const noisy = params.tone * bright + (1 - params.tone) * state.dark * 0.8;
|
|
72
|
+
let source = noisy;
|
|
73
|
+
if (params.metal > 0) {
|
|
74
|
+
let square = 0;
|
|
75
|
+
for (const hz of METAL_HZ) square += (t * hz) % 1 < 0.5 ? 1 : -1;
|
|
76
|
+
// First difference: keep the metallic top end, drop the low beating.
|
|
77
|
+
const metal = (square / METAL_HZ.length - state.metalPrevious) * 1.6;
|
|
78
|
+
state.metalPrevious = square / METAL_HZ.length;
|
|
79
|
+
source = params.metal * metal + (1 - params.metal) * noisy;
|
|
80
|
+
}
|
|
81
|
+
return source * params.level * Math.exp(-t * params.decay);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** One sample of `voice` in `kit` at `t` seconds after the hit. */
|
|
85
|
+
export function kitDrumSample(
|
|
86
|
+
kit: SynthKit,
|
|
87
|
+
voice: DrumVoice,
|
|
88
|
+
t: number,
|
|
89
|
+
noise: number,
|
|
90
|
+
bright: number,
|
|
91
|
+
state: KitVoiceState,
|
|
92
|
+
sampleRate: number,
|
|
93
|
+
): number {
|
|
94
|
+
let sample: number;
|
|
95
|
+
if (voice === "kick") sample = body(kit.kick, t, noise, state, sampleRate);
|
|
96
|
+
else if (voice === "tom") sample = body(kit.tom, t, noise, state, sampleRate);
|
|
97
|
+
else if (voice === "snare") {
|
|
98
|
+
const p = kit.snare;
|
|
99
|
+
state.phase += p.tone / sampleRate;
|
|
100
|
+
const wires = p.snap * bright + (1 - p.snap) * noise;
|
|
101
|
+
sample =
|
|
102
|
+
Math.sin(2 * Math.PI * state.phase) *
|
|
103
|
+
p.toneLevel *
|
|
104
|
+
Math.exp(-t * p.toneDecay) +
|
|
105
|
+
wires * p.noise * Math.exp(-t * p.noiseDecay);
|
|
106
|
+
} else if (voice === "clap") {
|
|
107
|
+
const p = kit.clap;
|
|
108
|
+
state.band += p.band * (bright - state.band);
|
|
109
|
+
const span = p.gap * 3;
|
|
110
|
+
const burst = t < span ? (Math.floor(t / p.gap) % 2 === 0 ? 1 : 0.35) : 0;
|
|
111
|
+
sample =
|
|
112
|
+
state.band *
|
|
113
|
+
p.level *
|
|
114
|
+
(burst + 0.8 * Math.exp(-(t - span) * p.tail) * +(t >= span));
|
|
115
|
+
} else if (voice === "hat") sample = hat(kit.hat, t, noise, bright, state);
|
|
116
|
+
else if (voice === "openhat")
|
|
117
|
+
sample = hat(kit.openhat, t, noise, bright, state);
|
|
118
|
+
else {
|
|
119
|
+
const p = kit.rim;
|
|
120
|
+
sample =
|
|
121
|
+
(Math.sin(2 * Math.PI * p.high * t) * 0.6 +
|
|
122
|
+
Math.sin(2 * Math.PI * p.low * t) * 0.4) *
|
|
123
|
+
Math.exp(-t * p.decay) *
|
|
124
|
+
p.level;
|
|
125
|
+
}
|
|
126
|
+
if (kit.drive > 0) {
|
|
127
|
+
const amount = 1 + kit.drive * 4;
|
|
128
|
+
sample = Math.tanh(sample * amount) / Math.tanh(amount);
|
|
129
|
+
}
|
|
130
|
+
if (kit.bits > 0) {
|
|
131
|
+
const steps = 2 ** (kit.bits - 1);
|
|
132
|
+
sample = Math.round(sample * steps) / steps;
|
|
133
|
+
}
|
|
134
|
+
if (kit.hold > 1) {
|
|
135
|
+
if (state.holdLeft <= 0) {
|
|
136
|
+
state.held = sample;
|
|
137
|
+
state.holdLeft = kit.hold;
|
|
138
|
+
}
|
|
139
|
+
state.holdLeft -= 1;
|
|
140
|
+
sample = state.held;
|
|
141
|
+
}
|
|
142
|
+
return sample * kit.gain;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Longest synth-kit one-shot in the score; 0 without kits (so renders without kits are unchanged). */
|
|
146
|
+
export function kitTailSeconds(score: TrackScore): number {
|
|
147
|
+
let seconds = 0;
|
|
148
|
+
for (const track of score.tracks)
|
|
149
|
+
seconds = Math.max(seconds, synthKit(track.kit)?.seconds ?? 0);
|
|
150
|
+
return seconds;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── catalog ────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* One entry of the kit catalog. `synth` kits render offline from the
|
|
157
|
+
* parameters in `core/kits.ts`; `sample` kits are pack banks fetched on
|
|
158
|
+
* first use (`src/audio/packs.ts`). New kinds (a user's own folder of
|
|
159
|
+
* samples, more packs) slot in here without touching the pickers.
|
|
160
|
+
*/
|
|
161
|
+
export type KitEntry = Readonly<{
|
|
162
|
+
name: string;
|
|
163
|
+
kind: "synth" | "sample";
|
|
164
|
+
label: string;
|
|
165
|
+
detail: string;
|
|
166
|
+
/** The prompt command that puts this kit on the focused track. */
|
|
167
|
+
command: string;
|
|
168
|
+
}>;
|
|
169
|
+
|
|
170
|
+
/** The default synth voices, listed first. */
|
|
171
|
+
export const DEFAULT_SYNTH_KIT = "default";
|
|
172
|
+
|
|
173
|
+
export function kitCatalog(): readonly KitEntry[] {
|
|
174
|
+
const synth: KitEntry[] = [
|
|
175
|
+
{
|
|
176
|
+
name: DEFAULT_SYNTH_KIT,
|
|
177
|
+
kind: "synth",
|
|
178
|
+
label: "Default (synth)",
|
|
179
|
+
detail: "the built-in voices",
|
|
180
|
+
command: `/kit ${DEFAULT_SYNTH_KIT}`,
|
|
181
|
+
},
|
|
182
|
+
...SYNTH_KITS.map((entry): KitEntry => ({
|
|
183
|
+
name: entry.name,
|
|
184
|
+
kind: "synth",
|
|
185
|
+
label: entry.label,
|
|
186
|
+
detail: entry.description,
|
|
187
|
+
command: `/kit ${entry.name}`,
|
|
188
|
+
})),
|
|
189
|
+
];
|
|
190
|
+
const sample = Object.entries(DEFAULT_KITS).map(
|
|
191
|
+
([name, entry]): KitEntry => ({
|
|
192
|
+
name,
|
|
193
|
+
kind: "sample",
|
|
194
|
+
label: `${name} ${entry.bank || entry.pack}`,
|
|
195
|
+
detail: `${entry.pack} samples (fetched once)`,
|
|
196
|
+
command: `/kit ${name}`,
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
return Object.freeze([...synth, ...sample]);
|
|
200
|
+
}
|