@hraness/dawg 0.6.1 → 0.8.0
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 +118 -0
- package/DAWG.md +454 -140
- package/README.md +30 -26
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +184 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/ids.ts +52 -0
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +8 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/range.ts +581 -0
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +955 -14
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +287 -5
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +2132 -46
- package/core/sections.ts +480 -35
- package/core/sing.ts +815 -0
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +31 -0
- package/guides/audio.md +31 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -7
- package/guides/chords.md +15 -15
- package/guides/effects.md +17 -16
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +15 -7
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/panes.md +32 -0
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +26 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +18 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -13
- package/guides/styles.md +31 -0
- package/guides/tape.md +32 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +6 -2
- package/src/agent/agent.ts +130 -18
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +378 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +1 -1
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +27 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +25 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +4 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/range-tools.ts +216 -0
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +110 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +13 -10
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +49 -5
- package/src/audio/audio-command.ts +218 -0
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/devices.ts +264 -0
- package/src/audio/doctor.ts +157 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +10 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/effects/chain.ts +3 -1
- package/src/audio/effects/common.ts +43 -0
- package/src/audio/effects/convolution.ts +7 -4
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/engine.ts +360 -40
- package/src/audio/fit.ts +35 -3
- package/src/audio/instrument-check.ts +59 -43
- package/src/audio/instruments.ts +4 -0
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +8 -1
- package/src/audio/keys/engine.ts +13 -1
- package/src/audio/keys/piano.ts +22 -2
- package/src/audio/kits.ts +135 -6
- package/src/audio/live.ts +114 -20
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +30 -2
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +2 -1
- package/src/audio/sampler.ts +85 -4
- package/src/audio/samples.ts +20 -2
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/bow.ts +48 -5
- package/src/audio/strings/engine.ts +8 -2
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +500 -77
- package/src/audio/winds/engine.ts +5 -1
- package/src/audio/winds/trim.ts +28 -4
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +15 -2
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +82 -32
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +1 -1
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +101 -32
- package/src/commands/grammar.ts +560 -0
- package/src/commands/help.ts +774 -366
- package/src/commands/history.ts +139 -10
- package/src/commands/keys.ts +25 -8
- package/src/commands/modal.ts +1 -1
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +135 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/range.ts +763 -0
- package/src/commands/resample.ts +13 -10
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sample.ts +11 -1
- package/src/commands/sing.ts +474 -0
- package/src/commands/strum.ts +13 -1
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +617 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +2 -2
- package/src/fs/durable.ts +50 -0
- package/src/identity/actor.ts +127 -0
- package/src/lang/glossary.ts +511 -0
- package/src/launch-args.ts +267 -0
- package/src/main.ts +2508 -308
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +21 -2
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +20 -1
- package/src/session/client.ts +107 -3
- package/src/session/clipboard.ts +79 -0
- package/src/session/daemon.ts +199 -5
- package/src/session/live-host.ts +232 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +174 -0
- package/src/session/port.ts +109 -14
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +365 -7
- package/src/session/rebase.ts +63 -12
- package/src/session/receipt.ts +265 -0
- package/src/session/shared-live.ts +131 -0
- package/src/session/store.ts +126 -45
- package/src/tui/arrange-menu.ts +225 -23
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +113 -27
- package/src/tui/fader.ts +282 -42
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/knob-fields.ts +107 -0
- package/src/tui/knob-map.ts +198 -0
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +987 -179
- package/src/tui/modal-menu.ts +6 -6
- package/src/tui/performance-menu.ts +5 -2
- package/src/tui/play-chords.ts +4 -2
- package/src/tui/play-mode.ts +15 -1
- package/src/tui/play-session.ts +243 -28
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/tape-mode.ts +580 -0
- package/src/tui/tape-view.ts +263 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +3 -3
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +491 -84
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +193 -0
- package/tui/drawer.ts +233 -27
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +143 -65
- package/tui/guide.ts +50 -5
- package/tui/highway.ts +309 -30
- package/tui/hints.ts +197 -0
- package/tui/hits.ts +7 -1
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/knobs.ts +268 -0
- package/tui/play-strip.ts +80 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +151 -18
- package/tui/tape.ts +439 -0
- package/tui/text.ts +35 -2
- package/tui/theme.ts +46 -1
package/core/diff.ts
CHANGED
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
* canonical order, so insertion order never matters).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
NOTE_EXPRESSION_FIELDS,
|
|
15
|
-
TRACK_PERFORMANCE_FIELDS,
|
|
16
|
-
} from "./expression.ts";
|
|
17
13
|
import {
|
|
18
14
|
applyScoreOperation,
|
|
19
15
|
TrackScore,
|
|
@@ -31,34 +27,109 @@ export class DiffError extends Error {
|
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
]
|
|
30
|
+
/**
|
|
31
|
+
* How each field diffs: `same` is identity (never patched), `set` patches
|
|
32
|
+
* the new value, `clear` patches the new value or null, `zero` treats
|
|
33
|
+
* absent as 0, `flag` treats absent as false and `lane` treats absent as
|
|
34
|
+
* []. The records are keyed by every field of `Note` and `Track`, so a new
|
|
35
|
+
* field fails typechecking until it is classified here.
|
|
36
|
+
*/
|
|
37
|
+
type FieldRule = "same" | "set" | "clear" | "zero" | "flag" | "lane";
|
|
38
|
+
|
|
39
|
+
const NOTE_FIELDS: { readonly [K in keyof Note]-?: FieldRule } = {
|
|
40
|
+
id: "same",
|
|
41
|
+
trackId: "same",
|
|
42
|
+
startTick: "set",
|
|
43
|
+
durationTicks: "set",
|
|
44
|
+
pitch: "set",
|
|
45
|
+
velocity: "set",
|
|
46
|
+
cents: "zero",
|
|
47
|
+
lyric: "clear",
|
|
48
|
+
vowel: "clear",
|
|
49
|
+
articulation: "clear",
|
|
50
|
+
glide: "clear",
|
|
51
|
+
bend: "clear",
|
|
52
|
+
vibrato: "clear",
|
|
53
|
+
humanize: "clear",
|
|
54
|
+
drift: "clear",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const TRACK_FIELDS: { readonly [K in keyof Track]-?: FieldRule } = {
|
|
58
|
+
id: "same",
|
|
59
|
+
name: "set",
|
|
60
|
+
instrument: "set",
|
|
61
|
+
muted: "set",
|
|
62
|
+
volume: "set",
|
|
63
|
+
pan: "set",
|
|
64
|
+
solo: "flag",
|
|
65
|
+
volumeAutomation: "lane",
|
|
66
|
+
panAutomation: "lane",
|
|
67
|
+
filterAutomation: "lane",
|
|
68
|
+
resonanceAutomation: "lane",
|
|
69
|
+
delayFeedbackAutomation: "lane",
|
|
70
|
+
delayMixAutomation: "lane",
|
|
71
|
+
wtAutomation: "lane",
|
|
72
|
+
filter: "clear",
|
|
73
|
+
delay: "clear",
|
|
74
|
+
reverb: "clear",
|
|
75
|
+
fx: "clear",
|
|
76
|
+
fxAutomation: "clear",
|
|
77
|
+
synth: "clear",
|
|
78
|
+
wavetable: "clear",
|
|
79
|
+
sampler: "clear",
|
|
80
|
+
rhythm: "clear",
|
|
81
|
+
kit: "clear",
|
|
82
|
+
time: "clear",
|
|
83
|
+
tuning: "clear",
|
|
84
|
+
string: "clear",
|
|
85
|
+
granular: "clear",
|
|
86
|
+
keys: "clear",
|
|
87
|
+
modal: "clear",
|
|
88
|
+
softPedal: "clear",
|
|
89
|
+
sostenuto: "clear",
|
|
90
|
+
guitar: "clear",
|
|
91
|
+
wind: "clear",
|
|
92
|
+
sing: "clear",
|
|
93
|
+
vocoder: "clear",
|
|
94
|
+
clips: "clear",
|
|
95
|
+
takes: "clear",
|
|
96
|
+
autotune: "clear",
|
|
97
|
+
glide: "clear",
|
|
98
|
+
pedal: "clear",
|
|
99
|
+
velocityCurve: "clear",
|
|
100
|
+
humanize: "clear",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** Field names of `Note` and `Track`, for tests that check completeness. */
|
|
104
|
+
export const NOTE_FIELD_NAMES = Object.freeze(
|
|
105
|
+
Object.keys(NOTE_FIELDS),
|
|
106
|
+
) as readonly (keyof Note)[];
|
|
107
|
+
export const TRACK_FIELD_NAMES = Object.freeze(
|
|
108
|
+
Object.keys(TRACK_FIELDS),
|
|
109
|
+
) as readonly (keyof Track)[];
|
|
110
|
+
|
|
111
|
+
function fieldPatch(
|
|
112
|
+
rules: Readonly<Record<string, FieldRule>>,
|
|
113
|
+
a: Readonly<Record<string, unknown>>,
|
|
114
|
+
b: Readonly<Record<string, unknown>>,
|
|
115
|
+
): Record<string, unknown> | undefined {
|
|
116
|
+
const patch: Record<string, unknown> = {};
|
|
117
|
+
for (const [key, rule] of Object.entries(rules)) {
|
|
118
|
+
const before = a[key];
|
|
119
|
+
const after = b[key];
|
|
120
|
+
if (rule === "same") continue;
|
|
121
|
+
if (rule === "set") {
|
|
122
|
+
if (!deepEqual(before, after)) patch[key] = after;
|
|
123
|
+
} else if (rule === "zero") {
|
|
124
|
+
if (!deepEqual(before ?? 0, after ?? 0)) patch[key] = after ?? 0;
|
|
125
|
+
} else if (rule === "flag") {
|
|
126
|
+
if ((before ?? false) !== (after ?? false)) patch[key] = after ?? false;
|
|
127
|
+
} else if (rule === "lane") {
|
|
128
|
+
if (!deepEqual(before ?? [], after ?? [])) patch[key] = after ?? [];
|
|
129
|
+
} else if (!deepEqual(before, after)) patch[key] = after ?? null;
|
|
130
|
+
}
|
|
131
|
+
return Object.keys(patch).length > 0 ? patch : undefined;
|
|
132
|
+
}
|
|
62
133
|
|
|
63
134
|
/** Operations turning `a` into `b`; empty when they are equal. */
|
|
64
135
|
export function diffScores(
|
|
@@ -69,31 +140,7 @@ export function diffScores(
|
|
|
69
140
|
throw new DiffError(
|
|
70
141
|
`ticksPerBeat differs (${a.ticksPerBeat} → ${b.ticksPerBeat}); the session resolution is fixed`,
|
|
71
142
|
);
|
|
72
|
-
const ops: ScoreOperation[] = [];
|
|
73
|
-
if (a.beatsPerBar !== b.beatsPerBar)
|
|
74
|
-
ops.push({ type: "setMeter", beatsPerBar: b.beatsPerBar });
|
|
75
|
-
if (a.bars !== b.bars) ops.push({ type: "setBars", bars: b.bars });
|
|
76
|
-
if (a.tempoBpm !== b.tempoBpm)
|
|
77
|
-
ops.push({ type: "setTempo", tempoBpm: b.tempoBpm });
|
|
78
|
-
if (a.key !== b.key) ops.push({ type: "setKey", key: b.key });
|
|
79
|
-
if (!deepEqual(a.time, b.time))
|
|
80
|
-
ops.push({ type: "setTime", time: b.time ?? null });
|
|
81
|
-
if (!deepEqual(a.tuning, b.tuning))
|
|
82
|
-
ops.push({ type: "setTuning", tuning: b.tuning ?? null });
|
|
83
|
-
if (!deepEqual(a.master, b.master))
|
|
84
|
-
ops.push({ type: "setMaster", master: b.master ?? null });
|
|
85
|
-
if (
|
|
86
|
-
!deepEqual(a.sections, b.sections) ||
|
|
87
|
-
!deepEqual(a.form, b.form) ||
|
|
88
|
-
a.loopSection !== b.loopSection
|
|
89
|
-
)
|
|
90
|
-
ops.push({
|
|
91
|
-
type: "setSections",
|
|
92
|
-
sections: b.sections,
|
|
93
|
-
form: b.form,
|
|
94
|
-
...(b.loopSection === undefined ? {} : { loopSection: b.loopSection }),
|
|
95
|
-
});
|
|
96
|
-
|
|
143
|
+
const ops: ScoreOperation[] = [...songOperations(a, b)];
|
|
97
144
|
const aTracks = new Map(a.tracks.map((track) => [track.id, track]));
|
|
98
145
|
const bTracks = new Map(b.tracks.map((track) => [track.id, track]));
|
|
99
146
|
const aNotes = new Map(a.notes.map((note) => [note.id, note]));
|
|
@@ -101,32 +148,26 @@ export function diffScores(
|
|
|
101
148
|
|
|
102
149
|
const updates: ScoreOperation[] = [];
|
|
103
150
|
const additions: ScoreOperation[] = [];
|
|
151
|
+
// Notes that leave `a`: dropped with their track, or removed one by one
|
|
152
|
+
// (a note may name a track the score lacks, so check `a` too).
|
|
153
|
+
const gone = new Set<string>();
|
|
104
154
|
for (const note of a.notes) {
|
|
105
|
-
if (!bTracks.has(note.trackId))
|
|
155
|
+
if (aTracks.has(note.trackId) && !bTracks.has(note.trackId)) {
|
|
156
|
+
gone.add(note.id); // removed with its track
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
106
159
|
const next = bNotes.get(note.id);
|
|
107
160
|
if (!next || next.trackId !== note.trackId) {
|
|
108
161
|
ops.push({ type: "removeNote", noteId: note.id });
|
|
162
|
+
gone.add(note.id);
|
|
109
163
|
continue;
|
|
110
164
|
}
|
|
111
165
|
const patch = notePatch(note, next);
|
|
112
166
|
if (patch) updates.push({ type: "updateNote", noteId: note.id, patch });
|
|
113
167
|
}
|
|
114
|
-
for (const note of b.notes)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
previous &&
|
|
118
|
-
previous.trackId === note.trackId &&
|
|
119
|
-
bTracks.has(note.trackId)
|
|
120
|
-
)
|
|
121
|
-
continue;
|
|
122
|
-
if (
|
|
123
|
-
previous &&
|
|
124
|
-
previous.trackId === note.trackId &&
|
|
125
|
-
!aTracks.has(note.trackId)
|
|
126
|
-
)
|
|
127
|
-
continue;
|
|
128
|
-
additions.push({ type: "addNote", note });
|
|
129
|
-
}
|
|
168
|
+
for (const note of b.notes)
|
|
169
|
+
if (!aNotes.has(note.id) || gone.has(note.id))
|
|
170
|
+
additions.push({ type: "addNote", note });
|
|
130
171
|
|
|
131
172
|
for (const track of a.tracks)
|
|
132
173
|
if (!bTracks.has(track.id))
|
|
@@ -156,6 +197,71 @@ export function diffScores(
|
|
|
156
197
|
return Object.freeze(ops);
|
|
157
198
|
}
|
|
158
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Song settings, ordered so every step is valid on its own: constraints
|
|
202
|
+
* that go away are dropped first (sections before meter changes, fermatas
|
|
203
|
+
* before the tempo or meter that time them) and constraints that arrive
|
|
204
|
+
* are added last. Steps are checked on a copy without tracks or notes,
|
|
205
|
+
* because song settings never depend on them.
|
|
206
|
+
*/
|
|
207
|
+
function songOperations(a: TrackScore, b: TrackScore): ScoreOperation[] {
|
|
208
|
+
const ops: ScoreOperation[] = [];
|
|
209
|
+
let song = new TrackScore({ ...a.toJSON(), tracks: [], notes: [] });
|
|
210
|
+
const emit = (operation: ScoreOperation): void => {
|
|
211
|
+
song = applyScoreOperation(song, operation);
|
|
212
|
+
ops.push(operation);
|
|
213
|
+
};
|
|
214
|
+
const sectionsDiffer = (): boolean =>
|
|
215
|
+
!deepEqual(song.sections, b.sections) ||
|
|
216
|
+
!deepEqual(song.form, b.form) ||
|
|
217
|
+
song.loopSection !== b.loopSection;
|
|
218
|
+
const setSections = (): void =>
|
|
219
|
+
emit({
|
|
220
|
+
type: "setSections",
|
|
221
|
+
sections: b.sections,
|
|
222
|
+
form: b.form,
|
|
223
|
+
...(b.loopSection === undefined ? {} : { loopSection: b.loopSection }),
|
|
224
|
+
});
|
|
225
|
+
if (b.sections.length === 0 && sectionsDiffer()) setSections();
|
|
226
|
+
const retimed =
|
|
227
|
+
song.beatsPerBar !== b.beatsPerBar ||
|
|
228
|
+
song.bars !== b.bars ||
|
|
229
|
+
song.tempoBpm !== b.tempoBpm;
|
|
230
|
+
if (retimed && song.time?.fermatas && !deepEqual(song.time, b.time)) {
|
|
231
|
+
const { fermatas: _fermatas, ...rest } = b.time ?? {};
|
|
232
|
+
const interim = Object.keys(rest).length > 0 ? rest : null;
|
|
233
|
+
if (!deepEqual(song.time, interim ?? undefined))
|
|
234
|
+
emit({ type: "setTime", time: interim });
|
|
235
|
+
} else if (retimed && song.time?.fermatas) {
|
|
236
|
+
// Same fermatas before and after, but timed by a new tempo or meter.
|
|
237
|
+
const { fermatas: _fermatas, ...rest } = song.time;
|
|
238
|
+
emit({
|
|
239
|
+
type: "setTime",
|
|
240
|
+
time: Object.keys(rest).length > 0 ? rest : null,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (song.beatsPerBar !== b.beatsPerBar)
|
|
244
|
+
emit({ type: "setMeter", beatsPerBar: b.beatsPerBar });
|
|
245
|
+
if (song.bars !== b.bars) emit({ type: "setBars", bars: b.bars });
|
|
246
|
+
if (song.tempoBpm !== b.tempoBpm)
|
|
247
|
+
emit({ type: "setTempo", tempoBpm: b.tempoBpm });
|
|
248
|
+
if (song.key !== b.key) emit({ type: "setKey", key: b.key });
|
|
249
|
+
if (!deepEqual(song.time, b.time))
|
|
250
|
+
emit({ type: "setTime", time: b.time ?? null });
|
|
251
|
+
if (!deepEqual(song.tuning, b.tuning))
|
|
252
|
+
emit({ type: "setTuning", tuning: b.tuning ?? null });
|
|
253
|
+
if (!deepEqual(song.master, b.master))
|
|
254
|
+
emit({ type: "setMaster", master: b.master ?? null });
|
|
255
|
+
if ((song.calibration ?? 0) !== (b.calibration ?? 0))
|
|
256
|
+
emit({ type: "setCalibration", calibration: b.calibration ?? null });
|
|
257
|
+
if (!deepEqual(song.style, b.style))
|
|
258
|
+
emit({ type: "setStyle", style: b.style ?? null });
|
|
259
|
+
if (sectionsDiffer()) setSections();
|
|
260
|
+
if (!deepEqual(song.loop, b.loop))
|
|
261
|
+
emit({ type: "setLoop", loop: b.loop ?? null });
|
|
262
|
+
return ops;
|
|
263
|
+
}
|
|
264
|
+
|
|
159
265
|
/** Applies operations in order; the inverse of `diffScores`. */
|
|
160
266
|
export function applyScoreOperations(
|
|
161
267
|
score: TrackScore,
|
|
@@ -165,43 +271,17 @@ export function applyScoreOperations(
|
|
|
165
271
|
}
|
|
166
272
|
|
|
167
273
|
function notePatch(a: Note, b: Note): NotePatch | undefined {
|
|
168
|
-
|
|
169
|
-
for (const key of [
|
|
170
|
-
"startTick",
|
|
171
|
-
"durationTicks",
|
|
172
|
-
"pitch",
|
|
173
|
-
"velocity",
|
|
174
|
-
] as const)
|
|
175
|
-
if (a[key] !== b[key]) patch[key] = b[key];
|
|
176
|
-
for (const key of NOTE_EXPRESSION_FIELDS)
|
|
177
|
-
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
178
|
-
if ((a.cents ?? 0) !== (b.cents ?? 0)) patch.cents = b.cents ?? 0;
|
|
179
|
-
return Object.keys(patch).length > 0 ? (patch as NotePatch) : undefined;
|
|
274
|
+
return fieldPatch(NOTE_FIELDS, a, b) as NotePatch | undefined;
|
|
180
275
|
}
|
|
181
276
|
|
|
182
277
|
function trackPatch(a: Track, b: Track): TrackPatch | undefined {
|
|
183
|
-
|
|
184
|
-
for (const key of ["name", "instrument", "muted", "volume", "pan"] as const)
|
|
185
|
-
if (a[key] !== b[key]) patch[key] = b[key];
|
|
186
|
-
if ((a.solo ?? false) !== (b.solo ?? false)) patch.solo = b.solo ?? false;
|
|
187
|
-
for (const key of EFFECTS)
|
|
188
|
-
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
189
|
-
for (const key of [
|
|
190
|
-
"rhythm",
|
|
191
|
-
"kit",
|
|
192
|
-
"time",
|
|
193
|
-
"tuning",
|
|
194
|
-
...TRACK_PERFORMANCE_FIELDS,
|
|
195
|
-
] as const)
|
|
196
|
-
if (!deepEqual(a[key], b[key])) patch[key] = b[key] ?? null;
|
|
197
|
-
for (const key of LANES)
|
|
198
|
-
if (!deepEqual(a[key] ?? [], b[key] ?? [])) patch[key] = b[key] ?? [];
|
|
199
|
-
return Object.keys(patch).length > 0 ? (patch as TrackPatch) : undefined;
|
|
278
|
+
return fieldPatch(TRACK_FIELDS, a, b) as TrackPatch | undefined;
|
|
200
279
|
}
|
|
201
280
|
|
|
202
281
|
/** Structural equality for plain JSON-like data. */
|
|
203
282
|
export function deepEqual(a: unknown, b: unknown): boolean {
|
|
204
|
-
|
|
283
|
+
// === so -0 equals 0: a printed song.ts reads -0 back as 0.
|
|
284
|
+
if (a === b) return true;
|
|
205
285
|
if (
|
|
206
286
|
typeof a !== "object" ||
|
|
207
287
|
typeof b !== "object" ||
|
package/core/expression.ts
CHANGED
|
@@ -125,6 +125,11 @@ export type NoteExpression = Readonly<{
|
|
|
125
125
|
* track's humanize seed, 1 when it has none). `{}` keeps the note exact.
|
|
126
126
|
*/
|
|
127
127
|
humanize?: NoteHumanize;
|
|
128
|
+
/**
|
|
129
|
+
* Optional (0.7): on an autotune guide note, the share of slow pitch drift
|
|
130
|
+
* removed (0..1), overriding the track's `autotune.drift` for this note.
|
|
131
|
+
*/
|
|
132
|
+
drift?: number;
|
|
128
133
|
}>;
|
|
129
134
|
|
|
130
135
|
/** Per-note humanize: amounts as in `Humanize`, absent ones 0. */
|
|
@@ -141,6 +146,7 @@ export type NoteExpressionPatch = Readonly<{
|
|
|
141
146
|
bend?: readonly BendPoint[] | null;
|
|
142
147
|
vibrato?: NoteVibrato | null;
|
|
143
148
|
humanize?: NoteHumanize | null;
|
|
149
|
+
drift?: number | null;
|
|
144
150
|
}>;
|
|
145
151
|
|
|
146
152
|
export const NOTE_EXPRESSION_FIELDS = [
|
|
@@ -149,6 +155,7 @@ export const NOTE_EXPRESSION_FIELDS = [
|
|
|
149
155
|
"bend",
|
|
150
156
|
"vibrato",
|
|
151
157
|
"humanize",
|
|
158
|
+
"drift",
|
|
152
159
|
] as const;
|
|
153
160
|
|
|
154
161
|
export const GLIDE_MODES = ["legato", "mono", "poly"] as const;
|
|
@@ -325,6 +332,12 @@ export function normalizeVibrato(value: unknown): NoteVibrato | undefined {
|
|
|
325
332
|
return Object.freeze({ rate, depth, ...(delay > 0 ? { delay } : {}) });
|
|
326
333
|
}
|
|
327
334
|
|
|
335
|
+
/** A guide note's autotune drift share (0.7), 0..1. */
|
|
336
|
+
export function normalizeNoteDrift(value: unknown): number | undefined {
|
|
337
|
+
if (value === undefined || value === null) return undefined;
|
|
338
|
+
return finite(value, "note drift", 0, 1);
|
|
339
|
+
}
|
|
340
|
+
|
|
328
341
|
/** Validates a note's expression fields; only the set ones are returned. */
|
|
329
342
|
export function normalizeNoteExpression(
|
|
330
343
|
input: Readonly<Record<string, unknown>>,
|
|
@@ -334,12 +347,14 @@ export function normalizeNoteExpression(
|
|
|
334
347
|
const bend = normalizeBend(input.bend);
|
|
335
348
|
const vibrato = normalizeVibrato(input.vibrato);
|
|
336
349
|
const humanize = normalizeNoteHumanize(input.humanize);
|
|
350
|
+
const drift = normalizeNoteDrift(input.drift);
|
|
337
351
|
return {
|
|
338
352
|
...(articulation ? { articulation } : {}),
|
|
339
353
|
...(glide !== undefined ? { glide } : {}),
|
|
340
354
|
...(bend ? { bend } : {}),
|
|
341
355
|
...(vibrato ? { vibrato } : {}),
|
|
342
356
|
...(humanize ? { humanize } : {}),
|
|
357
|
+
...(drift !== undefined ? { drift } : {}),
|
|
343
358
|
};
|
|
344
359
|
}
|
|
345
360
|
|
package/core/fx.ts
CHANGED
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { MODAL_LANE_PARAMS, WIND_LANE_PARAMS } from "./resonators.ts";
|
|
16
|
+
import { VOCODER_LANE_PARAMS } from "./vocoder.ts";
|
|
16
17
|
import { SYNTH_LANE_PARAMS } from "./synth.ts";
|
|
17
18
|
import { STRING_LANE_PARAMS } from "./strings.ts";
|
|
18
19
|
import { KEYS_LANE_PARAMS } from "./keys.ts";
|
|
19
20
|
import { GRANULAR_LANE_PARAMS } from "./granular.ts";
|
|
21
|
+
import { SING_LANE_PARAMS } from "./sing.ts";
|
|
20
22
|
import {
|
|
21
23
|
FxValidationError,
|
|
22
24
|
normalizeParam,
|
|
@@ -49,6 +51,8 @@ export const FX_CHAIN = Object.freeze([
|
|
|
49
51
|
"filter",
|
|
50
52
|
"djf",
|
|
51
53
|
"autofilter",
|
|
54
|
+
// 0.7 formant shift at constant pitch (src/audio/effects/formant.ts).
|
|
55
|
+
"formant",
|
|
52
56
|
"vowel",
|
|
53
57
|
"crush",
|
|
54
58
|
"distort",
|
|
@@ -110,7 +114,7 @@ const sync = (fallback: number, strudel?: readonly string[]): NumberParam => ({
|
|
|
110
114
|
default: fallback,
|
|
111
115
|
step: 0.25,
|
|
112
116
|
unit: "beats",
|
|
113
|
-
doc: "LFO
|
|
117
|
+
doc: "LFO period in beats (tempo-synced); 0 uses rate in Hz",
|
|
114
118
|
...(strudel ? { strudel } : {}),
|
|
115
119
|
});
|
|
116
120
|
const mix = (fallback: number, strudel?: readonly string[]): NumberParam => ({
|
|
@@ -124,6 +128,25 @@ const mix = (fallback: number, strudel?: readonly string[]): NumberParam => ({
|
|
|
124
128
|
...(strudel ? { strudel } : {}),
|
|
125
129
|
});
|
|
126
130
|
|
|
131
|
+
/** Vowels of the `vowel` effect (src/audio/dsp/formant.ts tables). */
|
|
132
|
+
export const VOWEL_VALUES = Object.freeze([
|
|
133
|
+
"a",
|
|
134
|
+
"e",
|
|
135
|
+
"i",
|
|
136
|
+
"o",
|
|
137
|
+
"u",
|
|
138
|
+
"ae",
|
|
139
|
+
"aa",
|
|
140
|
+
"oe",
|
|
141
|
+
"ue",
|
|
142
|
+
"y",
|
|
143
|
+
"uh",
|
|
144
|
+
"un",
|
|
145
|
+
"en",
|
|
146
|
+
"an",
|
|
147
|
+
"on",
|
|
148
|
+
] as const);
|
|
149
|
+
|
|
127
150
|
/**
|
|
128
151
|
* Effects stored under `track.fx`. Every number is bounded; enabling an
|
|
129
152
|
* effect stores all of its parameters so a document says exactly what plays.
|
|
@@ -131,7 +154,7 @@ const mix = (fallback: number, strudel?: readonly string[]): NumberParam => ({
|
|
|
131
154
|
export const FX_SPECS = Object.freeze({
|
|
132
155
|
autofilter: {
|
|
133
156
|
label: "auto filter",
|
|
134
|
-
doc: "filter whose cutoff an LFO sweeps around a
|
|
157
|
+
doc: "filter whose cutoff an LFO sweeps around a center, optionally opened by the input level",
|
|
135
158
|
simple: ["type", "cutoff", "depth", "sync", "shape"],
|
|
136
159
|
strudel:
|
|
137
160
|
"none built in (Strudel fakes it with lpf(sine.range(..)).seg(n)); names follow lpf/lpq",
|
|
@@ -151,7 +174,7 @@ export const FX_SPECS = Object.freeze({
|
|
|
151
174
|
step: "log",
|
|
152
175
|
unit: "Hz",
|
|
153
176
|
automate: true,
|
|
154
|
-
doc: "
|
|
177
|
+
doc: "center cutoff",
|
|
155
178
|
strudel: ["lpf", "cutoff"],
|
|
156
179
|
},
|
|
157
180
|
resonance: {
|
|
@@ -172,7 +195,7 @@ export const FX_SPECS = Object.freeze({
|
|
|
172
195
|
step: 0.25,
|
|
173
196
|
unit: "oct",
|
|
174
197
|
automate: true,
|
|
175
|
-
doc: "sweep width in octaves around the
|
|
198
|
+
doc: "sweep width in octaves around the center",
|
|
176
199
|
},
|
|
177
200
|
sync: sync(4),
|
|
178
201
|
rate: rate(0.5),
|
|
@@ -216,11 +239,30 @@ export const FX_SPECS = Object.freeze({
|
|
|
216
239
|
default: 0.5,
|
|
217
240
|
step: 0.02,
|
|
218
241
|
automate: true,
|
|
219
|
-
doc: "0 dark … 0.5 open … 1 thin",
|
|
242
|
+
doc: "0 dark (20 Hz low-pass) … 0.5 open … 1 thin (10 kHz high-pass)",
|
|
220
243
|
strudel: ["djf"],
|
|
221
244
|
},
|
|
222
245
|
},
|
|
223
246
|
},
|
|
247
|
+
formant: {
|
|
248
|
+
label: "formant",
|
|
249
|
+
doc: "formant shift at constant pitch: moves the spectral envelope (throat or gender knob), any source",
|
|
250
|
+
simple: ["shift", "mix"],
|
|
251
|
+
strudel: "none (not a Strudel concept)",
|
|
252
|
+
params: {
|
|
253
|
+
shift: {
|
|
254
|
+
kind: "number",
|
|
255
|
+
min: -12,
|
|
256
|
+
max: 12,
|
|
257
|
+
default: 0,
|
|
258
|
+
step: 0.5,
|
|
259
|
+
unit: "st",
|
|
260
|
+
automate: true,
|
|
261
|
+
doc: "semitones the formants move (negative deeper, positive smaller); pitch stays",
|
|
262
|
+
},
|
|
263
|
+
mix: mix(1),
|
|
264
|
+
},
|
|
265
|
+
},
|
|
224
266
|
vowel: {
|
|
225
267
|
label: "vowel",
|
|
226
268
|
doc: "formant filter bank: five band-passes per vowel",
|
|
@@ -229,28 +271,30 @@ export const FX_SPECS = Object.freeze({
|
|
|
229
271
|
params: {
|
|
230
272
|
vowel: {
|
|
231
273
|
kind: "enum",
|
|
232
|
-
values:
|
|
233
|
-
"a",
|
|
234
|
-
"e",
|
|
235
|
-
"i",
|
|
236
|
-
"o",
|
|
237
|
-
"u",
|
|
238
|
-
"ae",
|
|
239
|
-
"aa",
|
|
240
|
-
"oe",
|
|
241
|
-
"ue",
|
|
242
|
-
"y",
|
|
243
|
-
"uh",
|
|
244
|
-
"un",
|
|
245
|
-
"en",
|
|
246
|
-
"an",
|
|
247
|
-
"on",
|
|
248
|
-
],
|
|
274
|
+
values: VOWEL_VALUES,
|
|
249
275
|
default: "a",
|
|
250
276
|
doc: "vowel formants (a e i o u plus the extended set)",
|
|
251
277
|
strudel: ["vowel"],
|
|
252
278
|
},
|
|
253
279
|
mix: mix(1),
|
|
280
|
+
// 0.7: optional morph target; absent keeps the static bank exactly.
|
|
281
|
+
to: {
|
|
282
|
+
kind: "enum",
|
|
283
|
+
values: VOWEL_VALUES,
|
|
284
|
+
default: "a",
|
|
285
|
+
optional: true,
|
|
286
|
+
doc: "vowel to morph toward (absent: no morph)",
|
|
287
|
+
},
|
|
288
|
+
morph: {
|
|
289
|
+
kind: "number",
|
|
290
|
+
min: 0,
|
|
291
|
+
max: 1,
|
|
292
|
+
default: 0,
|
|
293
|
+
step: 0.05,
|
|
294
|
+
optional: true,
|
|
295
|
+
automate: true,
|
|
296
|
+
doc: "position between vowel (0) and to (1), log-frequency formant morph",
|
|
297
|
+
},
|
|
254
298
|
},
|
|
255
299
|
},
|
|
256
300
|
crush: {
|
|
@@ -467,7 +511,7 @@ export const FX_SPECS = Object.freeze({
|
|
|
467
511
|
step: 1,
|
|
468
512
|
unit: "dB",
|
|
469
513
|
optional: true,
|
|
470
|
-
doc: "noise gate threshold before the amp (absent: no gate)",
|
|
514
|
+
doc: "noise gate threshold before the amp (absent: no gate; 0 dB gates all)",
|
|
471
515
|
},
|
|
472
516
|
level: {
|
|
473
517
|
kind: "number",
|
|
@@ -498,7 +542,7 @@ export const FX_SPECS = Object.freeze({
|
|
|
498
542
|
max: 1,
|
|
499
543
|
default: 0.3,
|
|
500
544
|
step: 0.05,
|
|
501
|
-
doc: "microphone position: 0
|
|
545
|
+
doc: "microphone position: 0 center (bright) .. 1 edge (dark)",
|
|
502
546
|
},
|
|
503
547
|
mix: mix(1),
|
|
504
548
|
},
|
|
@@ -628,7 +672,7 @@ export const FX_SPECS = Object.freeze({
|
|
|
628
672
|
max: 1,
|
|
629
673
|
default: 0.6,
|
|
630
674
|
step: 0.05,
|
|
631
|
-
doc: "0
|
|
675
|
+
doc: "0 centered .. 1 the two takes hard left and right",
|
|
632
676
|
},
|
|
633
677
|
},
|
|
634
678
|
},
|
|
@@ -771,7 +815,7 @@ export const FX_SPECS = Object.freeze({
|
|
|
771
815
|
default: 1000,
|
|
772
816
|
step: "log",
|
|
773
817
|
unit: "Hz",
|
|
774
|
-
doc: "sweep
|
|
818
|
+
doc: "sweep center",
|
|
775
819
|
strudel: ["phasercenter", "phc"],
|
|
776
820
|
},
|
|
777
821
|
sweep: {
|
|
@@ -955,7 +999,7 @@ export const TRACK_EFFECT_SPECS = Object.freeze({
|
|
|
955
999
|
step: "log",
|
|
956
1000
|
unit: "Hz",
|
|
957
1001
|
automate: true,
|
|
958
|
-
doc: "cutoff (lpf/hpf) or
|
|
1002
|
+
doc: "cutoff (lpf/hpf) or center (bpf) frequency",
|
|
959
1003
|
strudel: [
|
|
960
1004
|
"lpf",
|
|
961
1005
|
"cutoff",
|
|
@@ -1221,7 +1265,7 @@ export function normalizeFx(input: unknown): TrackFx | undefined {
|
|
|
1221
1265
|
|
|
1222
1266
|
/** Automation lane name of an `fx` parameter, e.g. `distort-drive`. */
|
|
1223
1267
|
export type FxLane =
|
|
1224
|
-
`${FxName | "reverb" | "synth" | "string" | "keys" | "modal" | "grain" | "wind"}-${string}`;
|
|
1268
|
+
`${FxName | "reverb" | "synth" | "string" | "keys" | "modal" | "grain" | "wind" | "sing" | "vocoder"}-${string}`;
|
|
1225
1269
|
|
|
1226
1270
|
/** Every automatable `fx` parameter as `{ lane, effect, param, spec }`. */
|
|
1227
1271
|
export const FX_LANES: readonly Readonly<{
|
|
@@ -1234,7 +1278,9 @@ export const FX_LANES: readonly Readonly<{
|
|
|
1234
1278
|
| "keys"
|
|
1235
1279
|
| "modal"
|
|
1236
1280
|
| "grain"
|
|
1237
|
-
| "wind"
|
|
1281
|
+
| "wind"
|
|
1282
|
+
| "sing"
|
|
1283
|
+
| "vocoder";
|
|
1238
1284
|
param: string;
|
|
1239
1285
|
spec: NumberParam;
|
|
1240
1286
|
}>[] = Object.freeze([
|
|
@@ -1309,6 +1355,24 @@ export const FX_LANES: readonly Readonly<{
|
|
|
1309
1355
|
spec,
|
|
1310
1356
|
}),
|
|
1311
1357
|
),
|
|
1358
|
+
// Sing parameters (core/sing.ts, 0.7), read every 32-sample control tick.
|
|
1359
|
+
...SING_LANE_PARAMS.map(({ param, spec }) =>
|
|
1360
|
+
Object.freeze({
|
|
1361
|
+
lane: `sing-${param}` as FxLane,
|
|
1362
|
+
effect: "sing" as const,
|
|
1363
|
+
param,
|
|
1364
|
+
spec,
|
|
1365
|
+
}),
|
|
1366
|
+
),
|
|
1367
|
+
// Vocoder (core/vocoder.ts, 0.7), read per 32-sample block on the song grid.
|
|
1368
|
+
...VOCODER_LANE_PARAMS.map(({ param, spec }) =>
|
|
1369
|
+
Object.freeze({
|
|
1370
|
+
lane: `vocoder-${param}` as FxLane,
|
|
1371
|
+
effect: "vocoder" as const,
|
|
1372
|
+
param,
|
|
1373
|
+
spec,
|
|
1374
|
+
}),
|
|
1375
|
+
),
|
|
1312
1376
|
]);
|
|
1313
1377
|
|
|
1314
1378
|
/**
|
|
@@ -1355,6 +1419,12 @@ export const FX_PRESETS: Readonly<
|
|
|
1355
1419
|
resonance: 0.6,
|
|
1356
1420
|
},
|
|
1357
1421
|
},
|
|
1422
|
+
formant: {
|
|
1423
|
+
deep: { shift: -4 },
|
|
1424
|
+
giant: { shift: -8 },
|
|
1425
|
+
bright: { shift: 3 },
|
|
1426
|
+
tiny: { shift: 7 },
|
|
1427
|
+
},
|
|
1358
1428
|
vowel: { a: { vowel: "a" }, o: { vowel: "o" }, ee: { vowel: "i" } },
|
|
1359
1429
|
crush: {
|
|
1360
1430
|
"8-bit": { bits: 8, coarse: 1 },
|