@hraness/dawg 0.6.0 → 0.7.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 +126 -0
- package/DAWG.md +655 -141
- package/README.md +27 -25
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +759 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +182 -99
- package/core/expression.ts +158 -6
- package/core/fx.ts +354 -32
- package/core/granular.ts +91 -0
- package/core/instruments.ts +180 -0
- package/core/keys.ts +573 -4
- package/core/loop.ts +5 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/resonators.ts +306 -4
- package/core/routing.ts +165 -0
- package/core/score.ts +1181 -16
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +354 -6
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +3160 -69
- package/core/sections.ts +298 -24
- package/core/sing.ts +815 -0
- package/core/strings.ts +237 -2
- 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/core/winds.ts +652 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +30 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -6
- package/guides/chords.md +16 -14
- package/guides/effects.md +17 -15
- 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 +12 -6
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/performance.md +15 -10
- package/guides/play.md +21 -13
- package/guides/project.md +25 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +32 -0
- package/guides/rhythm.md +17 -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 -12
- package/guides/styles.md +31 -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 +5 -2
- package/src/agent/agent.ts +131 -13
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/chord-tools.ts +153 -1
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +369 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +91 -0
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +15 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +13 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +5 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/resample-tool.ts +131 -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 +307 -37
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +16 -6
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +58 -3
- 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/doctor.ts +86 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +171 -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/dsp/shift.ts +256 -0
- package/src/audio/effects/chain.ts +21 -4
- package/src/audio/effects/common.ts +57 -0
- package/src/audio/effects/convolution.ts +113 -5
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/effects/gaze.ts +354 -0
- package/src/audio/engine.ts +206 -33
- package/src/audio/fit.ts +80 -3
- package/src/audio/granular.ts +352 -21
- package/src/audio/instrument-check.ts +67 -47
- package/src/audio/instruments.ts +36 -3
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +427 -0
- package/src/audio/keys/engine.ts +156 -20
- package/src/audio/keys/organ.ts +1335 -0
- package/src/audio/keys/piano.ts +72 -8
- package/src/audio/keys/sympathetic.ts +127 -0
- package/src/audio/kits.ts +135 -6
- package/src/audio/live-worker.ts +3 -1
- package/src/audio/live.ts +247 -26
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +45 -3
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +286 -0
- package/src/audio/resonators.ts +10 -2
- package/src/audio/sampler.ts +252 -24
- package/src/audio/samples.ts +46 -3
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/body.ts +29 -16
- package/src/audio/strings/bow.ts +699 -0
- package/src/audio/strings/engine.ts +274 -22
- package/src/audio/strings/measure.test-helpers.ts +2 -0
- 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 +608 -85
- package/src/audio/winds/engine.ts +306 -0
- package/src/audio/winds/filters.ts +153 -0
- package/src/audio/winds/pitch.ts +104 -0
- package/src/audio/winds/trim.ts +104 -0
- package/src/audio/winds/trims.ts +917 -0
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +492 -0
- 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 +44 -29
- 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 +104 -23
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +105 -33
- package/src/commands/grammar.ts +558 -0
- package/src/commands/granular.ts +7 -2
- package/src/commands/help.ts +652 -339
- package/src/commands/history.ts +18 -0
- package/src/commands/keys.ts +293 -17
- package/src/commands/modal.ts +67 -4
- 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 +130 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/resample.ts +281 -0
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +8 -25
- package/src/commands/sample.ts +49 -2
- package/src/commands/shift.ts +119 -0
- package/src/commands/sing.ts +478 -0
- package/src/commands/string.ts +28 -2
- package/src/commands/strum.ts +485 -0
- 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 +616 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +244 -0
- package/src/fs/durable.ts +50 -0
- package/src/lang/glossary.ts +493 -0
- package/src/launch-args.ts +163 -0
- package/src/main.ts +1521 -255
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +26 -3
- 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 +36 -1
- package/src/session/daemon.ts +3 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +154 -0
- package/src/session/port.ts +22 -4
- package/src/session/presence.ts +50 -7
- package/src/session/protocol.ts +5 -1
- package/src/session/rebase.ts +18 -5
- package/src/session/receipt.ts +258 -0
- package/src/session/store.ts +65 -43
- package/src/tui/arrange-menu.ts +65 -31
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +18 -13
- package/src/tui/fader.ts +228 -41
- package/src/tui/granular-menu.ts +2 -4
- 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 +1377 -193
- package/src/tui/modal-menu.ts +38 -11
- package/src/tui/performance-menu.ts +46 -1
- package/src/tui/play-chords.ts +108 -5
- package/src/tui/play-mode.ts +16 -1
- package/src/tui/play-session.ts +81 -16
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +144 -0
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +274 -40
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +144 -0
- package/tui/drawer.ts +70 -22
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +112 -63
- package/tui/guide.ts +42 -4
- package/tui/highway.ts +269 -25
- package/tui/hints.ts +192 -0
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/play-strip.ts +68 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +144 -14
- package/tui/theme.ts +27 -0
package/core/loop.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type Track,
|
|
11
11
|
} from "./score.ts";
|
|
12
12
|
import type { SongMaster } from "./master.ts";
|
|
13
|
+
import type { SongStyle } from "./style-provenance.ts";
|
|
13
14
|
import type { Tuning } from "./tuning.ts";
|
|
14
15
|
|
|
15
16
|
export const LOOP_FORMAT = "track.loop/v1" as const;
|
|
@@ -30,6 +31,8 @@ export type TrackLoopV1 = Readonly<{
|
|
|
30
31
|
notes: readonly Note[];
|
|
31
32
|
/** Song master (dawg 0.5); absent means none. */
|
|
32
33
|
master?: SongMaster;
|
|
34
|
+
/** Which style and seed made the song (quality-08); absent means none. */
|
|
35
|
+
style?: SongStyle;
|
|
33
36
|
/** Song sections (0.5); omitted when the song has none. */
|
|
34
37
|
sections?: readonly Section[];
|
|
35
38
|
/** Song form (0.5); omitted when empty. */
|
|
@@ -53,11 +56,13 @@ export function encodeLoopDocument(score: TrackScore): TrackLoopV1 {
|
|
|
53
56
|
tracks: score.tracks,
|
|
54
57
|
notes: score.notes,
|
|
55
58
|
...(score.master ? { master: score.master } : {}),
|
|
59
|
+
...(score.style ? { style: score.style } : {}),
|
|
56
60
|
...(score.sections.length > 0 ? { sections: score.sections } : {}),
|
|
57
61
|
...(score.form.length > 0 ? { form: score.form } : {}),
|
|
58
62
|
...(score.loopSection === undefined
|
|
59
63
|
? {}
|
|
60
64
|
: { loopSection: score.loopSection }),
|
|
65
|
+
...(score.calibration ? { calibration: score.calibration } : {}),
|
|
61
66
|
});
|
|
62
67
|
}
|
|
63
68
|
|
package/core/lyrics.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lyric grammar and syllable assignment (0.7) shared by `/lyrics`,
|
|
3
|
+
* `set_lyrics` and the SDK's `lyrics()`. No imports: the SDK carries a copy
|
|
4
|
+
* between marker lines (`bun core/sdk/sync-lyrics.ts`).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ---- lyrics (copied into core/sdk/v1.ts)
|
|
8
|
+
|
|
9
|
+
/** Longest lyric on one note (SCORE_LIMITS.maxLyricLength). */
|
|
10
|
+
export const LYRIC_LIMIT = 32;
|
|
11
|
+
|
|
12
|
+
/** One lyric token: a syllable, a held note (`_`) or a skipped note (`~`). */
|
|
13
|
+
export type LyricToken = Readonly<{
|
|
14
|
+
syl: string;
|
|
15
|
+
/** Index of the word the token belongs to. */
|
|
16
|
+
word: number;
|
|
17
|
+
/** First syllable of its word. */
|
|
18
|
+
first: boolean;
|
|
19
|
+
kind: "syl" | "hold" | "rest";
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The lyric grammar: words split by spaces, syllables by `-`, `_` holds the
|
|
24
|
+
* previous syllable over the next note (melisma), `~` skips a note.
|
|
25
|
+
* "sun-lit morn-ing _ glow" gives sun lit morn ing _ glow.
|
|
26
|
+
*/
|
|
27
|
+
export function parseLyric(text: string): LyricToken[] {
|
|
28
|
+
const out: LyricToken[] = [];
|
|
29
|
+
let word = -1;
|
|
30
|
+
for (const raw of text.trim().split(/\s+/u)) {
|
|
31
|
+
if (raw === "") continue;
|
|
32
|
+
if (raw === "_") out.push({ syl: "_", word, first: false, kind: "hold" });
|
|
33
|
+
else if (raw === "~")
|
|
34
|
+
out.push({ syl: "~", word, first: false, kind: "rest" });
|
|
35
|
+
else {
|
|
36
|
+
word += 1;
|
|
37
|
+
raw
|
|
38
|
+
.split("-")
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.forEach((syl, index) =>
|
|
41
|
+
out.push({ syl, word, first: index === 0, kind: "syl" }),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const VOWEL = /[aeiouàáâäèéêëìíîïòóôöùúûü]/u;
|
|
49
|
+
/** Consonant pairs that sound as one consonant and are never split. */
|
|
50
|
+
const SYL_DIGRAPHS = new Set(["th", "sh", "ch", "ph", "wh", "ng", "ck", "gh"]);
|
|
51
|
+
/** Digraphs that end a syllable (no English word starts with them). */
|
|
52
|
+
const SYL_CODA_ONLY = new Set(["ng", "ck", "gh", "x"]);
|
|
53
|
+
/** Consonant clusters a syllable may start with (maximal onset). */
|
|
54
|
+
const SYL_ONSETS = new Set(
|
|
55
|
+
(
|
|
56
|
+
"bl br cl cr dr fl fr gl gr pl pr sc sk sl sm sn sp st sw tr tw dw " +
|
|
57
|
+
"thr shr chr phr phl spl spr str scr squ skr"
|
|
58
|
+
).split(" "),
|
|
59
|
+
);
|
|
60
|
+
/** Common words ending in a silent `e` that start compounds (some-thing). */
|
|
61
|
+
const SYL_SILENT_E_HEADS = (
|
|
62
|
+
"some home life time love fire side care where there here more one " +
|
|
63
|
+
"make lone like name game base wide grace face place space stone bone"
|
|
64
|
+
).split(" ");
|
|
65
|
+
/** Suffixes kept whole after a silent `e` (love-ly, care-ful). */
|
|
66
|
+
const SYL_SUFFIXES = ["ly", "ful", "less", "ness", "ment"];
|
|
67
|
+
/** Unstressed endings that close a short vowel before them (nev-er). */
|
|
68
|
+
const SYL_CLOSING_ENDINGS = new Set([
|
|
69
|
+
"er",
|
|
70
|
+
"en",
|
|
71
|
+
"el",
|
|
72
|
+
"et",
|
|
73
|
+
"ed",
|
|
74
|
+
"es",
|
|
75
|
+
"est",
|
|
76
|
+
"ing",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Syllables of a word typed without hyphens: a guess for English, which a
|
|
81
|
+
* hyphen always overrides (`nev-er`). Each run of vowels (and `y` after a
|
|
82
|
+
* consonant) is one syllable; a final silent `e` does not count, but a
|
|
83
|
+
* consonant plus `le` is its own syllable (lit-tle, ta-ble). Consonant
|
|
84
|
+
* pairs that sound as one (th sh ch ph wh ng ck gh) never split. Between
|
|
85
|
+
* vowels a cluster gives the next syllable the longest onset English
|
|
86
|
+
* allows (mon-ster, chil-dren); one consonant goes with the next vowel
|
|
87
|
+
* (ba-by, to-night) unless the previous vowel is short before an
|
|
88
|
+
* unstressed ending (nev-er, sing-ing). "something" gives some thing,
|
|
89
|
+
* "forever" for ev er.
|
|
90
|
+
*/
|
|
91
|
+
export function autoSyllabify(word: string): string[] {
|
|
92
|
+
const w = word.toLowerCase();
|
|
93
|
+
// Compounds and suffixes after a silent e: some-thing, love-ly.
|
|
94
|
+
if (w.length >= 6) {
|
|
95
|
+
for (const head of SYL_SILENT_E_HEADS)
|
|
96
|
+
if (w.startsWith(head) && VOWEL.test(w.slice(head.length)))
|
|
97
|
+
return [
|
|
98
|
+
word.slice(0, head.length),
|
|
99
|
+
...autoSyllabify(word.slice(head.length)),
|
|
100
|
+
];
|
|
101
|
+
for (const suffix of SYL_SUFFIXES) {
|
|
102
|
+
const stem = w.slice(0, -suffix.length);
|
|
103
|
+
if (
|
|
104
|
+
w.endsWith(suffix) &&
|
|
105
|
+
stem.length >= 3 &&
|
|
106
|
+
stem.endsWith("e") &&
|
|
107
|
+
!VOWEL.test(stem[stem.length - 2]!)
|
|
108
|
+
)
|
|
109
|
+
return [
|
|
110
|
+
...autoSyllabify(word.slice(0, stem.length)),
|
|
111
|
+
word.slice(stem.length),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Letters into units: a vowel, a consonant, a digraph, or `qu`.
|
|
116
|
+
type Unit = { at: number; text: string; vowel: boolean };
|
|
117
|
+
const units: Unit[] = [];
|
|
118
|
+
for (let i = 0; i < w.length;) {
|
|
119
|
+
const pair = w.slice(i, i + 2);
|
|
120
|
+
if (pair === "qu" || SYL_DIGRAPHS.has(pair)) {
|
|
121
|
+
units.push({ at: i, text: pair, vowel: false });
|
|
122
|
+
i += 2;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const ch = w[i]!;
|
|
126
|
+
const prev = units.at(-1);
|
|
127
|
+
const vowel =
|
|
128
|
+
VOWEL.test(ch) || (ch === "y" && prev !== undefined && !prev.vowel);
|
|
129
|
+
units.push({ at: i, text: ch, vowel });
|
|
130
|
+
i += 1;
|
|
131
|
+
}
|
|
132
|
+
// Vowel groups as [first unit, last unit].
|
|
133
|
+
const groups: [number, number][] = [];
|
|
134
|
+
for (let u = 0; u < units.length;) {
|
|
135
|
+
if (units[u]!.vowel) {
|
|
136
|
+
let v = u;
|
|
137
|
+
while (v + 1 < units.length && units[v + 1]!.vowel) v += 1;
|
|
138
|
+
groups.push([u, v]);
|
|
139
|
+
u = v + 1;
|
|
140
|
+
} else u += 1;
|
|
141
|
+
}
|
|
142
|
+
const lastUnit = units.length - 1;
|
|
143
|
+
const finalLe =
|
|
144
|
+
w.endsWith("le") &&
|
|
145
|
+
units.length >= 3 &&
|
|
146
|
+
units[lastUnit - 1]!.text === "l" &&
|
|
147
|
+
!units[lastUnit - 2]!.vowel;
|
|
148
|
+
const last = groups.at(-1);
|
|
149
|
+
if (
|
|
150
|
+
groups.length > 1 &&
|
|
151
|
+
last &&
|
|
152
|
+
last[0] === lastUnit &&
|
|
153
|
+
last[1] === lastUnit &&
|
|
154
|
+
units[lastUnit]!.text === "e" &&
|
|
155
|
+
!units[lastUnit - 1]!.vowel &&
|
|
156
|
+
!finalLe
|
|
157
|
+
)
|
|
158
|
+
groups.pop();
|
|
159
|
+
if (groups.length <= 1) return [word];
|
|
160
|
+
const cuts: number[] = [];
|
|
161
|
+
for (let g = 1; g < groups.length; g += 1) {
|
|
162
|
+
const prev = groups[g - 1]!;
|
|
163
|
+
const next = groups[g]!;
|
|
164
|
+
const cluster = units.slice(prev[1] + 1, next[0]);
|
|
165
|
+
const n = cluster.length;
|
|
166
|
+
const isLast = g === groups.length - 1;
|
|
167
|
+
let onset: number; // units of the cluster that start the next syllable
|
|
168
|
+
if (isLast && finalLe && n >= 2)
|
|
169
|
+
onset = cluster[n - 2]!.text === "ck" ? 1 : 2;
|
|
170
|
+
else if (n === 1) {
|
|
171
|
+
const unit = cluster[0]!.text;
|
|
172
|
+
const prevText = units
|
|
173
|
+
.slice(prev[0], prev[1] + 1)
|
|
174
|
+
.map((u) => u.text)
|
|
175
|
+
.join("");
|
|
176
|
+
const ending = w.slice(units[next[0]]!.at);
|
|
177
|
+
const short = prevText.length === 1 && "eiou".includes(prevText);
|
|
178
|
+
const closes =
|
|
179
|
+
SYL_CODA_ONLY.has(unit) ||
|
|
180
|
+
(short && isLast && SYL_CLOSING_ENDINGS.has(ending)) ||
|
|
181
|
+
(unit === "r" && short && units[next[0]]!.text === "e");
|
|
182
|
+
onset = closes ? 0 : 1;
|
|
183
|
+
} else {
|
|
184
|
+
onset = 1;
|
|
185
|
+
for (let k = n - 1; k >= 2; k -= 1)
|
|
186
|
+
if (
|
|
187
|
+
SYL_ONSETS.has(
|
|
188
|
+
cluster
|
|
189
|
+
.slice(n - k)
|
|
190
|
+
.map((u) => u.text)
|
|
191
|
+
.join(""),
|
|
192
|
+
)
|
|
193
|
+
) {
|
|
194
|
+
onset = k;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
if (SYL_CODA_ONLY.has(cluster[n - 1]!.text)) onset = 0;
|
|
198
|
+
}
|
|
199
|
+
const first = units[next[0] - onset]!;
|
|
200
|
+
cuts.push(onset === 0 ? units[next[0]]!.at : first.at);
|
|
201
|
+
}
|
|
202
|
+
const out: string[] = [];
|
|
203
|
+
let at = 0;
|
|
204
|
+
for (const cut of cuts) {
|
|
205
|
+
out.push(word.slice(at, cut));
|
|
206
|
+
at = cut;
|
|
207
|
+
}
|
|
208
|
+
out.push(word.slice(at));
|
|
209
|
+
return out.filter(Boolean);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** What `assignLyrics` put on each note, and what did not fit. */
|
|
213
|
+
export type LyricAssignment = Readonly<{
|
|
214
|
+
/** Note id to its lyric (`_` holds); notes `~` skipped are absent. */
|
|
215
|
+
lyrics: ReadonlyMap<string, string>;
|
|
216
|
+
/** Syllables left over after the last note. */
|
|
217
|
+
dropped: readonly string[];
|
|
218
|
+
/** Words split automatically. */
|
|
219
|
+
split: readonly string[];
|
|
220
|
+
}>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Lyrics onto `notes` in time order. Hyphens split syllables as typed;
|
|
224
|
+
* when the text has fewer syllables than there are notes, words typed
|
|
225
|
+
* whole are split by `autoSyllabify`, and any notes still left hold the
|
|
226
|
+
* last syllable (melisma) instead of failing. Syllables past the last note
|
|
227
|
+
* are reported in `dropped`. Notes sharing an onset take one token, on
|
|
228
|
+
* the top note, with `_` on the others. Each lyric is cut to the 32-character limit.
|
|
229
|
+
*/
|
|
230
|
+
export function assignLyrics(
|
|
231
|
+
text: string,
|
|
232
|
+
notes: readonly Readonly<{ id: string; startTick: number; pitch: number }>[],
|
|
233
|
+
): LyricAssignment {
|
|
234
|
+
const sorted = [...notes].sort(
|
|
235
|
+
(a, b) => a.startTick - b.startTick || b.pitch - a.pitch,
|
|
236
|
+
);
|
|
237
|
+
// Notes sharing an onset (a chord or a doubled note) take one token: the
|
|
238
|
+
// top note carries it and the rest hold.
|
|
239
|
+
const ordered: (typeof sorted)[number][] = [];
|
|
240
|
+
const under = new Map<string, string[]>();
|
|
241
|
+
for (const note of sorted) {
|
|
242
|
+
const top = ordered.at(-1);
|
|
243
|
+
if (top && top.startTick === note.startTick)
|
|
244
|
+
under.get(top.id)!.push(note.id);
|
|
245
|
+
else {
|
|
246
|
+
ordered.push(note);
|
|
247
|
+
under.set(note.id, []);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
let tokens = parseLyric(text);
|
|
251
|
+
const split: string[] = [];
|
|
252
|
+
if (tokens.length < ordered.length) {
|
|
253
|
+
// Split every word typed whole; keep the split only if it still fits.
|
|
254
|
+
const out: LyricToken[] = [];
|
|
255
|
+
const words: string[] = [];
|
|
256
|
+
for (const token of tokens) {
|
|
257
|
+
const whole =
|
|
258
|
+
token.kind === "syl" &&
|
|
259
|
+
token.first &&
|
|
260
|
+
!tokens.some((t) => t.word === token.word && !t.first);
|
|
261
|
+
if (!whole) {
|
|
262
|
+
out.push(token);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const parts = autoSyllabify(token.syl);
|
|
266
|
+
if (parts.length > 1) words.push(token.syl);
|
|
267
|
+
parts.forEach((syl, index) =>
|
|
268
|
+
out.push({ syl, word: token.word, first: index === 0, kind: "syl" }),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (out.length <= ordered.length) {
|
|
272
|
+
tokens = out;
|
|
273
|
+
split.push(...words);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const lyrics = new Map<string, string>();
|
|
277
|
+
const limit = LYRIC_LIMIT;
|
|
278
|
+
ordered.forEach((note, index) => {
|
|
279
|
+
const token = tokens[index];
|
|
280
|
+
if (!token) {
|
|
281
|
+
if (tokens.length > 0)
|
|
282
|
+
for (const id of [note.id, ...under.get(note.id)!]) lyrics.set(id, "_");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (token.kind === "rest") return;
|
|
286
|
+
lyrics.set(
|
|
287
|
+
note.id,
|
|
288
|
+
token.kind === "hold" ? "_" : token.syl.slice(0, limit),
|
|
289
|
+
);
|
|
290
|
+
for (const id of under.get(note.id)!) lyrics.set(id, "_");
|
|
291
|
+
});
|
|
292
|
+
const dropped = tokens
|
|
293
|
+
.slice(ordered.length)
|
|
294
|
+
.filter((token) => token.kind === "syl")
|
|
295
|
+
.map((token) => token.syl);
|
|
296
|
+
return { lyrics, dropped, split };
|
|
297
|
+
}
|
package/core/master.ts
CHANGED
|
@@ -105,10 +105,10 @@ export const MASTER_SPECS: Readonly<Record<MasterUnit, MasterUnitSpec>> =
|
|
|
105
105
|
low: db(-12, 12, 0, "low shelf gain"),
|
|
106
106
|
lowfreq: hz(20, 1000, 100, "low shelf corner"),
|
|
107
107
|
bell1: db(-12, 12, 0, "first bell gain"),
|
|
108
|
-
bell1freq: hz(40, 16_000, 400, "first bell
|
|
108
|
+
bell1freq: hz(40, 16_000, 400, "first bell center"),
|
|
109
109
|
bell1q: q("first bell width: higher is narrower"),
|
|
110
110
|
bell2: db(-12, 12, 0, "second bell gain"),
|
|
111
|
-
bell2freq: hz(200, 18_000, 3_000, "second bell
|
|
111
|
+
bell2freq: hz(200, 18_000, 3_000, "second bell center"),
|
|
112
112
|
bell2q: q("second bell width: higher is narrower"),
|
|
113
113
|
high: db(-12, 12, 0, "high shelf gain"),
|
|
114
114
|
highfreq: hz(1_000, 20_000, 10_000, "high shelf corner"),
|
|
@@ -222,7 +222,7 @@ export const MASTER_SPECS: Readonly<Record<MasterUnit, MasterUnitSpec>> =
|
|
|
222
222
|
default: 120,
|
|
223
223
|
step: 10,
|
|
224
224
|
unit: "Hz",
|
|
225
|
-
doc: "below this the mix is mono (keeps bass
|
|
225
|
+
doc: "below this the mix is mono (keeps bass centered); 0 is off",
|
|
226
226
|
},
|
|
227
227
|
},
|
|
228
228
|
},
|