polyphon 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +374 -2
- package/README.zh-TW.md +375 -0
- package/dist/engine/automation.d.ts +24 -0
- package/dist/engine/automation.d.ts.map +1 -0
- package/dist/engine/automation.js +55 -0
- package/dist/engine/automation.js.map +1 -0
- package/dist/engine/compile.d.ts +36 -0
- package/dist/engine/compile.d.ts.map +1 -0
- package/dist/engine/compile.js +103 -0
- package/dist/engine/compile.js.map +1 -0
- package/dist/engine/jukebox.d.ts +113 -0
- package/dist/engine/jukebox.d.ts.map +1 -0
- package/dist/engine/jukebox.js +287 -0
- package/dist/engine/jukebox.js.map +1 -0
- package/dist/engine/mixer.d.ts +29 -0
- package/dist/engine/mixer.d.ts.map +1 -0
- package/dist/engine/mixer.js +141 -0
- package/dist/engine/mixer.js.map +1 -0
- package/dist/engine/output.d.ts +14 -0
- package/dist/engine/output.d.ts.map +1 -0
- package/dist/engine/output.js +51 -0
- package/dist/engine/output.js.map +1 -0
- package/dist/engine/playback.d.ts +97 -0
- package/dist/engine/playback.d.ts.map +1 -0
- package/dist/engine/playback.js +311 -0
- package/dist/engine/playback.js.map +1 -0
- package/dist/engine/prepare.d.ts +6 -0
- package/dist/engine/prepare.d.ts.map +1 -0
- package/dist/engine/prepare.js +12 -0
- package/dist/engine/prepare.js.map +1 -0
- package/dist/engine/render.d.ts +14 -0
- package/dist/engine/render.d.ts.map +1 -0
- package/dist/engine/render.js +60 -0
- package/dist/engine/render.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/instruments/bowed.d.ts +357 -0
- package/dist/instruments/bowed.d.ts.map +1 -0
- package/dist/instruments/bowed.js +502 -0
- package/dist/instruments/bowed.js.map +1 -0
- package/dist/instruments/builtin.d.ts +261 -0
- package/dist/instruments/builtin.d.ts.map +1 -0
- package/dist/instruments/builtin.js +160 -0
- package/dist/instruments/builtin.js.map +1 -0
- package/dist/instruments/definition.d.ts +46 -0
- package/dist/instruments/definition.d.ts.map +1 -0
- package/dist/instruments/definition.js +5 -0
- package/dist/instruments/definition.js.map +1 -0
- package/dist/instruments/dsp/ambience.d.ts +17 -0
- package/dist/instruments/dsp/ambience.d.ts.map +1 -0
- package/dist/instruments/dsp/ambience.js +146 -0
- package/dist/instruments/dsp/ambience.js.map +1 -0
- package/dist/instruments/dsp/core.d.ts +22 -0
- package/dist/instruments/dsp/core.d.ts.map +1 -0
- package/dist/instruments/dsp/core.js +100 -0
- package/dist/instruments/dsp/core.js.map +1 -0
- package/dist/instruments/dsp/drums.d.ts +112 -0
- package/dist/instruments/dsp/drums.d.ts.map +1 -0
- package/dist/instruments/dsp/drums.js +566 -0
- package/dist/instruments/dsp/drums.js.map +1 -0
- package/dist/instruments/dsp/modal.d.ts +30 -0
- package/dist/instruments/dsp/modal.d.ts.map +1 -0
- package/dist/instruments/dsp/modal.js +54 -0
- package/dist/instruments/dsp/modal.js.map +1 -0
- package/dist/instruments/dsp/piano.d.ts +9 -0
- package/dist/instruments/dsp/piano.d.ts.map +1 -0
- package/dist/instruments/dsp/piano.js +230 -0
- package/dist/instruments/dsp/piano.js.map +1 -0
- package/dist/instruments/dsp/pluck.d.ts +53 -0
- package/dist/instruments/dsp/pluck.d.ts.map +1 -0
- package/dist/instruments/dsp/pluck.js +195 -0
- package/dist/instruments/dsp/pluck.js.map +1 -0
- package/dist/instruments/dsp/reverb.d.ts +16 -0
- package/dist/instruments/dsp/reverb.d.ts.map +1 -0
- package/dist/instruments/dsp/reverb.js +47 -0
- package/dist/instruments/dsp/reverb.js.map +1 -0
- package/dist/instruments/dsp/staccato.d.ts +18 -0
- package/dist/instruments/dsp/staccato.d.ts.map +1 -0
- package/dist/instruments/dsp/staccato.js +147 -0
- package/dist/instruments/dsp/staccato.js.map +1 -0
- package/dist/instruments/play.d.ts +21 -0
- package/dist/instruments/play.d.ts.map +1 -0
- package/dist/instruments/play.js +140 -0
- package/dist/instruments/play.js.map +1 -0
- package/dist/instruments/registry.d.ts +63 -0
- package/dist/instruments/registry.d.ts.map +1 -0
- package/dist/instruments/registry.js +50 -0
- package/dist/instruments/registry.js.map +1 -0
- package/dist/instruments/samples.d.ts +30 -0
- package/dist/instruments/samples.d.ts.map +1 -0
- package/dist/instruments/samples.js +109 -0
- package/dist/instruments/samples.js.map +1 -0
- package/dist/instruments/voices.d.ts +76 -0
- package/dist/instruments/voices.d.ts.map +1 -0
- package/dist/instruments/voices.js +1061 -0
- package/dist/instruments/voices.js.map +1 -0
- package/dist/midi.d.ts +15 -0
- package/dist/midi.d.ts.map +1 -0
- package/dist/midi.js +280 -0
- package/dist/midi.js.map +1 -0
- package/dist/notation/format.d.ts +12 -0
- package/dist/notation/format.d.ts.map +1 -0
- package/dist/notation/format.js +102 -0
- package/dist/notation/format.js.map +1 -0
- package/dist/notation/harmony.d.ts +83 -0
- package/dist/notation/harmony.d.ts.map +1 -0
- package/dist/notation/harmony.js +191 -0
- package/dist/notation/harmony.js.map +1 -0
- package/dist/notation/melody.d.ts +25 -0
- package/dist/notation/melody.d.ts.map +1 -0
- package/dist/notation/melody.js +138 -0
- package/dist/notation/melody.js.map +1 -0
- package/dist/notation/part.d.ts +34 -0
- package/dist/notation/part.d.ts.map +1 -0
- package/dist/notation/part.js +15 -0
- package/dist/notation/part.js.map +1 -0
- package/dist/notation/transform.d.ts +28 -0
- package/dist/notation/transform.d.ts.map +1 -0
- package/dist/notation/transform.js +67 -0
- package/dist/notation/transform.js.map +1 -0
- package/dist/song.d.ts +140 -0
- package/dist/song.d.ts.map +1 -0
- package/dist/song.js +45 -0
- package/dist/song.js.map +1 -0
- package/dist/tempo.d.ts +36 -0
- package/dist/tempo.d.ts.map +1 -0
- package/dist/tempo.js +81 -0
- package/dist/tempo.js.map +1 -0
- package/dist/theory.d.ts +70 -0
- package/dist/theory.d.ts.map +1 -0
- package/dist/theory.js +183 -0
- package/dist/theory.js.map +1 -0
- package/dist/transcribe.d.ts +81 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/transcribe.js +433 -0
- package/dist/transcribe.js.map +1 -0
- package/package.json +57 -4
- package/src/engine/automation.ts +65 -0
- package/src/engine/compile.ts +124 -0
- package/src/engine/jukebox.ts +322 -0
- package/src/engine/mixer.ts +166 -0
- package/src/engine/output.ts +59 -0
- package/src/engine/playback.ts +349 -0
- package/src/engine/prepare.ts +15 -0
- package/src/engine/render.ts +71 -0
- package/src/index.ts +28 -0
- package/src/instruments/bowed.ts +660 -0
- package/src/instruments/builtin.ts +180 -0
- package/src/instruments/definition.ts +52 -0
- package/src/instruments/dsp/ambience.ts +142 -0
- package/src/instruments/dsp/core.ts +97 -0
- package/src/instruments/dsp/drums.ts +565 -0
- package/src/instruments/dsp/modal.ts +76 -0
- package/src/instruments/dsp/piano.ts +226 -0
- package/src/instruments/dsp/pluck.ts +240 -0
- package/src/instruments/dsp/reverb.ts +56 -0
- package/src/instruments/dsp/staccato.ts +141 -0
- package/src/instruments/play.ts +151 -0
- package/src/instruments/registry.ts +100 -0
- package/src/instruments/samples.ts +119 -0
- package/src/instruments/voices.ts +1168 -0
- package/src/midi.ts +285 -0
- package/src/notation/format.ts +96 -0
- package/src/notation/harmony.ts +233 -0
- package/src/notation/melody.ts +133 -0
- package/src/notation/part.ts +42 -0
- package/src/notation/transform.ts +78 -0
- package/src/song.ts +170 -0
- package/src/tempo.ts +108 -0
- package/src/theory.ts +208 -0
- package/src/transcribe.ts +487 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type ChordInfo, type Pitch } from '../theory.ts';
|
|
2
|
+
import { type Meter, type Part } from './part.ts';
|
|
3
|
+
/** A chord placed in time (steps) */
|
|
4
|
+
export interface Chord extends ChordInfo {
|
|
5
|
+
time: number;
|
|
6
|
+
duration: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Parse a chord progression. Bars are separated by |, and the chords in a bar share it equally;
|
|
10
|
+
* lengths can also be written out ('C:12 G:4'). '%' holds the previous chord for one more bar.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* chords('Am | F | C G | E7')
|
|
14
|
+
* chords('Dm:12 A7:4 | %')
|
|
15
|
+
*/
|
|
16
|
+
export declare function chords(source: string, meter?: Meter): Chord[];
|
|
17
|
+
/** Total length of a progression, in steps */
|
|
18
|
+
export declare const chordsDuration: (progression: Chord[]) => number;
|
|
19
|
+
export interface PadOptions {
|
|
20
|
+
/** The register the voicing centers on (default C4) */
|
|
21
|
+
center?: Pitch;
|
|
22
|
+
/** Notes per chord (default: every chord tone) */
|
|
23
|
+
size?: number;
|
|
24
|
+
velocity?: number;
|
|
25
|
+
/** Steps of silence before each chord change */
|
|
26
|
+
gap?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Sustained chords (pads, string sections, choirs, organ), voiced to move smoothly */
|
|
29
|
+
export declare function pad(progression: Chord[], { center, size, velocity, gap }?: PadOptions): Part;
|
|
30
|
+
export interface ArpeggioOptions {
|
|
31
|
+
/** Steps per pattern character (default 2: eighth notes) */
|
|
32
|
+
step?: number;
|
|
33
|
+
/** The register the voicing centers on (default E4) */
|
|
34
|
+
center?: Pitch;
|
|
35
|
+
/** Notes per chord voicing (default: every chord tone) */
|
|
36
|
+
size?: number;
|
|
37
|
+
velocity?: number;
|
|
38
|
+
/** Accent every Nth note */
|
|
39
|
+
accent?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Arpeggio: the pattern picks chord tones by digit (0 is the lowest; past the number of chord tones it climbs an octave);
|
|
43
|
+
* '.' is a rest, '-' holds the previous note. The pattern restarts on every chord.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* arpeggio(chords('C | Am'), '0 1 2 1')
|
|
47
|
+
* arpeggio(chords('C | Am'), '0-12 3.21', { step: 1, center: 'C5' })
|
|
48
|
+
*/
|
|
49
|
+
export declare function arpeggio(progression: Chord[], pattern: string, { step, center, size, velocity, accent }?: ArpeggioOptions): Part;
|
|
50
|
+
export interface RootsOptions {
|
|
51
|
+
/** Where the bass sits: notes land within a fifth below to a fifth above this pitch (default C2) */
|
|
52
|
+
register?: Pitch;
|
|
53
|
+
/** Stack one more chord tone on top (usually an open fifth) */
|
|
54
|
+
fifth?: boolean;
|
|
55
|
+
velocity?: number;
|
|
56
|
+
}
|
|
57
|
+
/** Sustained bass notes: one per chord, held for the whole chord; a repeated bass note continues without re-attacking */
|
|
58
|
+
export declare function roots(progression: Chord[], { register, fifth, velocity }?: RootsOptions): Part;
|
|
59
|
+
export interface BasslineOptions {
|
|
60
|
+
/** Where the bass sits: the chord's bass lands within a fifth below to a fifth above this pitch (default C2) */
|
|
61
|
+
register?: Pitch;
|
|
62
|
+
velocity?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Bass line: the rhythm is a rhythm string that repeats every bar ('-' holds), and the line lists the scale degrees used in turn:
|
|
66
|
+
* 1 the chord's bass, 3 the chord's third, 5 the fifth, 8 an octave up, -8 an octave down, b7 / 7 / 2 / 4 / 6, b2 (Phrygian), -5 / -4 below.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* bassline(chords('C | Am | F | G'), 'x.x.x.x.x.x.x.x.', '1 5 8 5')
|
|
70
|
+
*/
|
|
71
|
+
export declare function bassline(progression: Chord[], rhythm: string, line?: string, { register, velocity }?: BasslineOptions): Part;
|
|
72
|
+
export interface StrumOptions {
|
|
73
|
+
/** The register the voicing centers on (default Bb3) */
|
|
74
|
+
center?: Pitch;
|
|
75
|
+
/** Strings per strum (default 5) */
|
|
76
|
+
size?: number;
|
|
77
|
+
/** Seconds between strings (default 0.014) */
|
|
78
|
+
spread?: number;
|
|
79
|
+
velocity?: number;
|
|
80
|
+
}
|
|
81
|
+
/** Strummed chords: the rhythm repeats every bar, x down-stroke, X heavy, o soft, '-' holds */
|
|
82
|
+
export declare function strum(progression: Chord[], rhythm: string, { center, size, spread, velocity }?: StrumOptions): Part;
|
|
83
|
+
//# sourceMappingURL=harmony.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harmony.d.ts","sourceRoot":"","sources":["../../src/notation/harmony.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAyB,KAAK,KAAK,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAEpF,qCAAqC;AACrC,MAAM,WAAW,KAAM,SAAQ,SAAS;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,KAAU,GAAG,KAAK,EAAE,CAmBjE;AAED,8CAA8C;AAC9C,eAAO,MAAM,cAAc,gBAAiB,KAAK,EAAE,KAAG,MAGrD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,uFAAuF;AACvF,wBAAgB,GAAG,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,MAAW,EAAE,IAAI,EAAE,QAA0B,EAAE,GAAO,EAAE,GAAE,UAAe,GAAG,IAAI,CAQ3H;AAED,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAQ,EAAE,MAAW,EAAE,IAAI,EAAE,QAA0B,EAAE,MAAM,EAAE,GAAE,eAAoB,GAAG,IAAI,CAuB/J;AAYD,MAAM,WAAW,YAAY;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,yHAAyH;AACzH,wBAAgB,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,QAAa,EAAE,KAAa,EAAE,QAAc,EAAE,GAAE,YAAiB,GAAG,IAAI,CAiBrH;AAED,MAAM,WAAW,eAAe;IAC9B,gHAAgH;IAChH,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,SAAM,EAAE,EAAE,QAAa,EAAE,QAA0B,EAAE,GAAE,eAAoB,GAAG,IAAI,CA8BpJ;AAUD,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,+FAA+F;AAC/F,wBAAgB,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,MAAW,EAAE,IAAQ,EAAE,MAAc,EAAE,QAA0B,EAAE,GAAE,YAAiB,GAAG,IAAI,CAkB1J"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Chord progressions, and the parts that grow out of them: pads, arpeggios, bass lines, sustained roots and strums.
|
|
2
|
+
import { midi, parseChord, voiceChord } from '../theory.js';
|
|
3
|
+
import { stepsPerBar, VELOCITY } from './part.js';
|
|
4
|
+
/**
|
|
5
|
+
* Parse a chord progression. Bars are separated by |, and the chords in a bar share it equally;
|
|
6
|
+
* lengths can also be written out ('C:12 G:4'). '%' holds the previous chord for one more bar.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* chords('Am | F | C G | E7')
|
|
10
|
+
* chords('Dm:12 A7:4 | %')
|
|
11
|
+
*/
|
|
12
|
+
export function chords(source, meter = 16) {
|
|
13
|
+
const bar = stepsPerBar(meter);
|
|
14
|
+
const out = [];
|
|
15
|
+
let t = 0;
|
|
16
|
+
for (const cell of source.split('|')) {
|
|
17
|
+
const toks = cell.trim().split(/\s+/).filter(Boolean);
|
|
18
|
+
if (!toks.length)
|
|
19
|
+
continue;
|
|
20
|
+
const explicit = toks.some((x) => x.includes(':'));
|
|
21
|
+
const each = bar / toks.length;
|
|
22
|
+
for (const tok of toks) {
|
|
23
|
+
const [sym, len] = tok.split(':');
|
|
24
|
+
const d = explicit ? Number(len ?? 0) || each : each;
|
|
25
|
+
if (sym === '%') {
|
|
26
|
+
if (out.length)
|
|
27
|
+
out[out.length - 1].duration += d;
|
|
28
|
+
}
|
|
29
|
+
else
|
|
30
|
+
out.push({ ...parseChord(sym), time: t, duration: d });
|
|
31
|
+
t += d;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/** Total length of a progression, in steps */
|
|
37
|
+
export const chordsDuration = (progression) => {
|
|
38
|
+
const last = progression[progression.length - 1];
|
|
39
|
+
return last ? last.time + last.duration : 0;
|
|
40
|
+
};
|
|
41
|
+
/** Sustained chords (pads, string sections, choirs, organ), voiced to move smoothly */
|
|
42
|
+
export function pad(progression, { center = 60, size, velocity = VELOCITY.normal, gap = 0 } = {}) {
|
|
43
|
+
let prev;
|
|
44
|
+
const notes = progression.map((c) => {
|
|
45
|
+
const m = voiceChord(c, { center, previous: prev, size });
|
|
46
|
+
prev = m;
|
|
47
|
+
return { time: c.time, duration: c.duration - gap, pitches: m, velocity };
|
|
48
|
+
});
|
|
49
|
+
return { notes, duration: chordsDuration(progression) };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Arpeggio: the pattern picks chord tones by digit (0 is the lowest; past the number of chord tones it climbs an octave);
|
|
53
|
+
* '.' is a rest, '-' holds the previous note. The pattern restarts on every chord.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* arpeggio(chords('C | Am'), '0 1 2 1')
|
|
57
|
+
* arpeggio(chords('C | Am'), '0-12 3.21', { step: 1, center: 'C5' })
|
|
58
|
+
*/
|
|
59
|
+
export function arpeggio(progression, pattern, { step = 2, center = 64, size, velocity = VELOCITY.normal, accent } = {}) {
|
|
60
|
+
const pat = pattern.replace(/\s+/g, '');
|
|
61
|
+
const notes = [];
|
|
62
|
+
let prev;
|
|
63
|
+
for (const c of progression) {
|
|
64
|
+
const vc = voiceChord(c, { center, previous: prev, size });
|
|
65
|
+
prev = vc;
|
|
66
|
+
let k = 0;
|
|
67
|
+
for (let s = 0; s < c.duration - 1e-6; s += step, k++) {
|
|
68
|
+
const ch = pat[k % pat.length];
|
|
69
|
+
if (ch === '.')
|
|
70
|
+
continue;
|
|
71
|
+
if (ch === '-') {
|
|
72
|
+
if (notes.length)
|
|
73
|
+
notes[notes.length - 1].duration += step;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const idx = parseInt(ch, 36);
|
|
77
|
+
const n = vc.length;
|
|
78
|
+
const m = vc[idx % n] + 12 * Math.floor(idx / n);
|
|
79
|
+
const acc = accent && k % accent === 0 ? 1.15 : 1;
|
|
80
|
+
notes.push({ time: c.time + s, duration: Math.min(step, c.duration - s), pitches: [m], velocity: Math.min(1, velocity * acc) });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { notes, duration: chordsDuration(progression) };
|
|
84
|
+
}
|
|
85
|
+
/** What to stack above the bass: prefer a perfect fifth; if there is none (diminished chord, inversion, passing bass), look in turn for a sixth, a fourth, a third */
|
|
86
|
+
const ABOVE = [7, 8, 9, 5, 4, 3, 6];
|
|
87
|
+
/** The chord's bass note, placed within a fifth below to a fifth above the register */
|
|
88
|
+
function bassNote(c, register) {
|
|
89
|
+
let low = register + ((c.bass - (register % 12) + 12) % 12);
|
|
90
|
+
if (low > register + 7)
|
|
91
|
+
low -= 12;
|
|
92
|
+
return low;
|
|
93
|
+
}
|
|
94
|
+
/** Sustained bass notes: one per chord, held for the whole chord; a repeated bass note continues without re-attacking */
|
|
95
|
+
export function roots(progression, { register = 36, fifth = false, velocity = 0.8 } = {}) {
|
|
96
|
+
const reg = midi(register);
|
|
97
|
+
const notes = [];
|
|
98
|
+
for (const c of progression) {
|
|
99
|
+
// Passing bass (a slash bass that is not a chord tone): just follows the bass, with no fifth stacked (stacking it would clash with the chord)
|
|
100
|
+
const pcs = new Set(c.intervals.map((i) => (c.root + i) % 12));
|
|
101
|
+
const low = bassNote(c, reg);
|
|
102
|
+
const m = [low];
|
|
103
|
+
if (fifth && pcs.has(c.bass)) {
|
|
104
|
+
const up = ABOVE.find((k) => pcs.has((c.bass + k) % 12));
|
|
105
|
+
if (up !== undefined)
|
|
106
|
+
m.push(low + up);
|
|
107
|
+
}
|
|
108
|
+
const last = notes[notes.length - 1];
|
|
109
|
+
if (last && last.pitches.join() === m.join() && last.time + last.duration === c.time)
|
|
110
|
+
last.duration += c.duration;
|
|
111
|
+
else
|
|
112
|
+
notes.push({ time: c.time, duration: c.duration, pitches: m, velocity });
|
|
113
|
+
}
|
|
114
|
+
return { notes, duration: chordsDuration(progression) };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Bass line: the rhythm is a rhythm string that repeats every bar ('-' holds), and the line lists the scale degrees used in turn:
|
|
118
|
+
* 1 the chord's bass, 3 the chord's third, 5 the fifth, 8 an octave up, -8 an octave down, b7 / 7 / 2 / 4 / 6, b2 (Phrygian), -5 / -4 below.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* bassline(chords('C | Am | F | G'), 'x.x.x.x.x.x.x.x.', '1 5 8 5')
|
|
122
|
+
*/
|
|
123
|
+
export function bassline(progression, rhythm, line = '1', { register = 36, velocity = VELOCITY.normal } = {}) {
|
|
124
|
+
const rh = rhythm.replace(/[\s|]/g, '');
|
|
125
|
+
const steps = line.trim().split(/\s+/);
|
|
126
|
+
const reg = midi(register);
|
|
127
|
+
const notes = [];
|
|
128
|
+
let li = 0;
|
|
129
|
+
for (const c of progression) {
|
|
130
|
+
const low = bassNote(c, reg);
|
|
131
|
+
// Other degrees are counted from the root, placed at the nearest position above the bass
|
|
132
|
+
const rootAbove = low + ((c.root - c.bass + 12) % 12);
|
|
133
|
+
for (let s = 0; s < c.duration; s++) {
|
|
134
|
+
const ch = rh[(c.time + s) % rh.length];
|
|
135
|
+
if (ch === '-') {
|
|
136
|
+
if (notes.length)
|
|
137
|
+
notes[notes.length - 1].duration += 1;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (ch !== 'x' && ch !== 'X' && ch !== 'o')
|
|
141
|
+
continue;
|
|
142
|
+
const deg = steps[li++ % steps.length];
|
|
143
|
+
let m;
|
|
144
|
+
if (deg === '1')
|
|
145
|
+
m = low;
|
|
146
|
+
else if (deg === '8')
|
|
147
|
+
m = low + 12;
|
|
148
|
+
else if (deg === '-8')
|
|
149
|
+
m = low - 12;
|
|
150
|
+
else {
|
|
151
|
+
m = rootAbove + degreeOffset(deg, c);
|
|
152
|
+
if (!deg.startsWith('-'))
|
|
153
|
+
while (m < low)
|
|
154
|
+
m += 12;
|
|
155
|
+
}
|
|
156
|
+
notes.push({ time: c.time + s, duration: 1, pitches: [m], velocity: ch === 'X' ? 1 : ch === 'o' ? 0.5 : velocity });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { notes, duration: chordsDuration(progression) };
|
|
160
|
+
}
|
|
161
|
+
function degreeOffset(deg, c) {
|
|
162
|
+
const fifth = c.intervals.includes(6) && !c.intervals.includes(7) ? 6 : c.intervals.includes(8) ? 8 : 7;
|
|
163
|
+
const map = { '1': 0, b2: 1, '2': 2, b3: 3, '3': c.third || 4, '4': 5, b5: 6, '5': fifth, b6: 8, '6': 9, b7: 10, '7': 11, '8': 12, '10': 12 + (c.third || 4), '-5': -5, '-4': -7, '-b7': -2, '-7': -1, '-8': -12 };
|
|
164
|
+
const v = map[deg];
|
|
165
|
+
if (v === undefined)
|
|
166
|
+
throw new Error(`Unrecognized bass scale degree: ${deg}`);
|
|
167
|
+
return v;
|
|
168
|
+
}
|
|
169
|
+
/** Strummed chords: the rhythm repeats every bar, x down-stroke, X heavy, o soft, '-' holds */
|
|
170
|
+
export function strum(progression, rhythm, { center = 58, size = 5, spread = 0.014, velocity = VELOCITY.normal } = {}) {
|
|
171
|
+
const rh = rhythm.replace(/[\s|]/g, '');
|
|
172
|
+
const notes = [];
|
|
173
|
+
let prev;
|
|
174
|
+
for (const c of progression) {
|
|
175
|
+
const vc = voiceChord(c, { center, previous: prev, size });
|
|
176
|
+
prev = vc;
|
|
177
|
+
for (let s = 0; s < c.duration; s++) {
|
|
178
|
+
const ch = rh[(c.time + s) % rh.length];
|
|
179
|
+
if (ch === '-') {
|
|
180
|
+
if (notes.length)
|
|
181
|
+
notes[notes.length - 1].duration += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (ch !== 'x' && ch !== 'X' && ch !== 'o')
|
|
185
|
+
continue;
|
|
186
|
+
notes.push({ time: c.time + s, duration: 1, pitches: vc, velocity: ch === 'X' ? 1 : ch === 'o' ? 0.5 : velocity, strum: spread });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { notes, duration: chordsDuration(progression) };
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=harmony.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harmony.js","sourceRoot":"","sources":["../../src/notation/harmony.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Pitch } from '../theory.ts';
|
|
2
|
+
import { type Meter, type Part } from './part.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a melody string.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* melody('C5:4 E5 G5 C6 | B5:8 G5:8 |')
|
|
8
|
+
* melody('G4:6 A4:2 B4:4 | D5:12 |', '3/4')
|
|
9
|
+
*/
|
|
10
|
+
export declare function melody(source: string, meter?: Meter): Part;
|
|
11
|
+
export interface RhythmOptions {
|
|
12
|
+
/** Steps per character (default 1: each character is a sixteenth note) */
|
|
13
|
+
step?: number;
|
|
14
|
+
/** The pitch every hit plays (tuned drums, or any instrument); default: unpitched */
|
|
15
|
+
pitch?: Pitch;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse a rhythm: one character per step. X accent, x normal, o soft, . or - rest.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* rhythm('X...x...X..xx...') // a kick pattern
|
|
22
|
+
* rhythm('x.x.x.x.', { pitch: 'G2' }) // a pitched pulse
|
|
23
|
+
*/
|
|
24
|
+
export declare function rhythm(pattern: string, { step, pitch }?: RhythmOptions): Part;
|
|
25
|
+
//# sourceMappingURL=melody.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"melody.d.ts","sourceRoot":"","sources":["../../src/notation/melody.ts"],"names":[],"mappings":"AAcA,OAAO,EAAQ,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAyB,KAAK,KAAK,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAEpF;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,KAAU,GAAG,IAAI,CAqE9D;AAcD,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAQ,EAAE,KAAK,EAAE,GAAE,aAAkB,GAAG,IAAI,CAWrF"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Melodies and rhythms written as short strings.
|
|
2
|
+
//
|
|
3
|
+
// Melody:
|
|
4
|
+
// 'E5:4 D5:2 C5:2 | B4:8 r:8 |' note:length in sixteenth-note steps, r is a rest, | is a bar line (each bar's length is checked)
|
|
5
|
+
// 'E5:4 D5 C5 B4' leave out the length to reuse the previous one
|
|
6
|
+
// 'C4+E4+G4:16' a chord (pitches sounding together)
|
|
7
|
+
// '!E5:4 ?E5:2' ! accent, ? soft (ghost note)
|
|
8
|
+
// 'E5:4> G5:4' > glide into the next note (instruments that support glides)
|
|
9
|
+
// 'G4:8~ | G4:4' ~ ties into the next note of the same pitch, across bar lines too
|
|
10
|
+
// 'E5:4/3' fractional length (a triplet eighth note is 4/3)
|
|
11
|
+
// '(E5:2 D5:2)*4 R*2' parenthesized groups repeat; R is a full bar of rest; . is the same as r
|
|
12
|
+
//
|
|
13
|
+
// Rhythm: one character per step. X accent, x normal, o soft, . or - rest; spaces and | are ignored.
|
|
14
|
+
import { midi } from '../theory.js';
|
|
15
|
+
import { stepsPerBar, VELOCITY } from './part.js';
|
|
16
|
+
/**
|
|
17
|
+
* Parse a melody string.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* melody('C5:4 E5 G5 C6 | B5:8 G5:8 |')
|
|
21
|
+
* melody('G4:6 A4:2 B4:4 | D5:12 |', '3/4')
|
|
22
|
+
*/
|
|
23
|
+
export function melody(source, meter = 16) {
|
|
24
|
+
const bar = stepsPerBar(meter);
|
|
25
|
+
const notes = [];
|
|
26
|
+
let t = 0;
|
|
27
|
+
let dur = 4;
|
|
28
|
+
let barStart = 0;
|
|
29
|
+
let barNo = 1;
|
|
30
|
+
let lastR = false;
|
|
31
|
+
/** The note a ~ holds on into the next one */
|
|
32
|
+
let tied;
|
|
33
|
+
for (const raw of expandRepeats(source).split(/\s+/)) {
|
|
34
|
+
if (!raw)
|
|
35
|
+
continue;
|
|
36
|
+
if (raw === '|') {
|
|
37
|
+
// R is a complete bar by itself, so a bar line right after it needs no further check (the "R*3 |" style)
|
|
38
|
+
if (lastR && Math.abs(t - barStart) < 1e-6) {
|
|
39
|
+
lastR = false;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (Math.abs(t - barStart - bar) > 1e-6)
|
|
43
|
+
throw new Error(`Bar ${barNo} is ${+(t - barStart).toFixed(3)} steps long, expected ${bar}: ...${source.slice(0, 60)}`);
|
|
44
|
+
barStart = t;
|
|
45
|
+
barNo++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (raw === 'R') {
|
|
49
|
+
if (tied)
|
|
50
|
+
throw new Error(`A tie (~) must lead into the same note, not a rest: ${raw}`);
|
|
51
|
+
const atBar = Math.abs(t - barStart) < 1e-6;
|
|
52
|
+
t += bar;
|
|
53
|
+
if (atBar) {
|
|
54
|
+
barStart = t;
|
|
55
|
+
barNo++;
|
|
56
|
+
}
|
|
57
|
+
lastR = atBar;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
lastR = false;
|
|
61
|
+
let tok = raw;
|
|
62
|
+
let v = VELOCITY.normal;
|
|
63
|
+
if (tok[0] === '!') {
|
|
64
|
+
v = VELOCITY.accent;
|
|
65
|
+
tok = tok.slice(1);
|
|
66
|
+
}
|
|
67
|
+
else if (tok[0] === '?') {
|
|
68
|
+
v = VELOCITY.ghost;
|
|
69
|
+
tok = tok.slice(1);
|
|
70
|
+
}
|
|
71
|
+
const tie = tok.endsWith('~');
|
|
72
|
+
if (tie)
|
|
73
|
+
tok = tok.slice(0, -1);
|
|
74
|
+
let glide = false;
|
|
75
|
+
if (tok.endsWith('>')) {
|
|
76
|
+
glide = true;
|
|
77
|
+
tok = tok.slice(0, -1);
|
|
78
|
+
}
|
|
79
|
+
const [pitch, len] = tok.split(':');
|
|
80
|
+
if (len) {
|
|
81
|
+
const [a, b] = len.split('/');
|
|
82
|
+
dur = b ? Number(a) / Number(b) : Number(a);
|
|
83
|
+
if (!(dur > 0))
|
|
84
|
+
throw new Error(`Invalid duration: ${raw}`);
|
|
85
|
+
}
|
|
86
|
+
if (pitch === 'r' || pitch === '.') {
|
|
87
|
+
if (tied || tie)
|
|
88
|
+
throw new Error(`A tie (~) joins two notes of the same pitch, not a rest: ${raw}`);
|
|
89
|
+
}
|
|
90
|
+
else if (tied) {
|
|
91
|
+
if (pitch.split('+').map(midi).join() !== tied.pitches.join())
|
|
92
|
+
throw new Error(`A tie (~) must lead into the same pitch: ${raw}`);
|
|
93
|
+
tied.duration += dur;
|
|
94
|
+
if (glide)
|
|
95
|
+
tied.glide = true;
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
notes.push({ time: t, duration: dur, pitches: pitch.split('+').map(midi), velocity: v, glide: glide || undefined });
|
|
99
|
+
tied = tie ? notes[notes.length - 1] : undefined;
|
|
100
|
+
t += dur;
|
|
101
|
+
}
|
|
102
|
+
if (tied)
|
|
103
|
+
throw new Error('A tie (~) at the end of a melody has no note to lead into');
|
|
104
|
+
return { notes, duration: t };
|
|
105
|
+
}
|
|
106
|
+
function expandRepeats(src) {
|
|
107
|
+
let s = src.replace(/\|/g, ' | ');
|
|
108
|
+
// Expand parentheses from the inside out
|
|
109
|
+
for (let guard = 0; guard < 50 && s.includes('('); guard++) {
|
|
110
|
+
s = s.replace(/\(([^()]*)\)\*(\d+)/g, (_, body, n) => Array(Number(n)).fill(body).join(' '));
|
|
111
|
+
s = s.replace(/\(([^()]*)\)(?!\*)/g, (_, body) => body);
|
|
112
|
+
}
|
|
113
|
+
// Repeat a single token: R*4, E5:2*3
|
|
114
|
+
s = s.replace(/(\S+)\*(\d+)/g, (_, tok, n) => Array(Number(n)).fill(tok).join(' '));
|
|
115
|
+
return s;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parse a rhythm: one character per step. X accent, x normal, o soft, . or - rest.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* rhythm('X...x...X..xx...') // a kick pattern
|
|
122
|
+
* rhythm('x.x.x.x.', { pitch: 'G2' }) // a pitched pulse
|
|
123
|
+
*/
|
|
124
|
+
export function rhythm(pattern, { step = 1, pitch } = {}) {
|
|
125
|
+
const m = pitch === undefined ? undefined : midi(pitch);
|
|
126
|
+
const notes = [];
|
|
127
|
+
let t = 0;
|
|
128
|
+
for (const ch of pattern) {
|
|
129
|
+
if (ch === ' ' || ch === '|' || ch === '\n')
|
|
130
|
+
continue;
|
|
131
|
+
const v = ch === 'X' ? VELOCITY.accent : ch === 'x' ? VELOCITY.normal : ch === 'o' ? VELOCITY.ghost : 0;
|
|
132
|
+
if (v)
|
|
133
|
+
notes.push({ time: t, duration: step, pitches: m === undefined ? [] : [m], velocity: v });
|
|
134
|
+
t += step;
|
|
135
|
+
}
|
|
136
|
+
return { notes, duration: t };
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=melody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"melody.js","sourceRoot":"","sources":["../../src/notation/melody.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** One note (or chord) in a part. Times are in steps: one step is a sixteenth note. */
|
|
2
|
+
export interface Note {
|
|
3
|
+
/** Start, in steps from the start of the part */
|
|
4
|
+
time: number;
|
|
5
|
+
/** Length, in steps */
|
|
6
|
+
duration: number;
|
|
7
|
+
/** MIDI pitches sounding together; unpitched drums may leave this empty */
|
|
8
|
+
pitches: number[];
|
|
9
|
+
/** Loudness 0-1 */
|
|
10
|
+
velocity: number;
|
|
11
|
+
/** Glide into the next note (instruments that support glides) */
|
|
12
|
+
glide?: boolean;
|
|
13
|
+
/** Strum a chord: delay in seconds between successive pitches */
|
|
14
|
+
strum?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface Part {
|
|
17
|
+
notes: Note[];
|
|
18
|
+
/** Total length, in steps */
|
|
19
|
+
duration: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A time signature, or the number of sixteenth-note steps in one bar.
|
|
23
|
+
* '4/4' = 16, '3/4' = 12, '6/8' = 12, '7/8' = 14, '5/4' = 20.
|
|
24
|
+
*/
|
|
25
|
+
export type Meter = number | `${number}/${number}`;
|
|
26
|
+
/** Steps in one bar of a meter (default 4/4 = 16) */
|
|
27
|
+
export declare function stepsPerBar(meter?: Meter): number;
|
|
28
|
+
/** Velocities for the three dynamics the notation can write */
|
|
29
|
+
export declare const VELOCITY: {
|
|
30
|
+
readonly normal: 0.78;
|
|
31
|
+
readonly accent: 1;
|
|
32
|
+
readonly ghost: 0.42;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=part.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"part.d.ts","sourceRoot":"","sources":["../../src/notation/part.ts"],"names":[],"mappings":"AAGA,uFAAuF;AACvF,MAAM,WAAW,IAAI;IACnB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAEnD,qDAAqD;AACrD,wBAAgB,WAAW,CAAC,KAAK,GAAE,KAAU,GAAG,MAAM,CAMrD;AAED,+DAA+D;AAC/D,eAAO,MAAM,QAAQ;aAAK,MAAM,EAAE,IAAI;aAAE,MAAM,EAAE,CAAC;aAAE,KAAK,EAAE,IAAI;CAAW,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The data every notation function produces: a Part is a list of notes on a grid of sixteenth-note steps.
|
|
2
|
+
// It is plain data, so any code that returns { notes, duration } can feed a track.
|
|
3
|
+
/** Steps in one bar of a meter (default 4/4 = 16) */
|
|
4
|
+
export function stepsPerBar(meter = 16) {
|
|
5
|
+
if (typeof meter === 'number')
|
|
6
|
+
return meter;
|
|
7
|
+
const [beats, unit] = meter.split('/').map(Number);
|
|
8
|
+
const steps = (beats * 16) / unit;
|
|
9
|
+
if (!(steps > 0) || !Number.isFinite(steps))
|
|
10
|
+
throw new Error(`Invalid meter: ${meter}`);
|
|
11
|
+
return steps;
|
|
12
|
+
}
|
|
13
|
+
/** Velocities for the three dynamics the notation can write */
|
|
14
|
+
export const VELOCITY = { normal: 0.78, accent: 1, ghost: 0.42 };
|
|
15
|
+
//# sourceMappingURL=part.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"part.js","sourceRoot":"","sources":["../../src/notation/part.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,mFAAmF;AA8BnF,qDAAqD;AACrD,MAAM,UAAU,WAAW,CAAC,KAAK,GAAU,EAAE;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IAClC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC;IACxF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAW,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Chord } from './harmony.ts';
|
|
2
|
+
import { type Meter, type Part } from './part.ts';
|
|
3
|
+
/** Play parts one after another (strings are read as 4/4 melodies) */
|
|
4
|
+
export declare function sequence(...parts: (Part | string)[]): Part;
|
|
5
|
+
/** Play parts at the same time; the result is as long as the longest */
|
|
6
|
+
export declare function stack(...parts: Part[]): Part;
|
|
7
|
+
/** Play a part n times in a row */
|
|
8
|
+
export declare function repeat(part: Part, times: number): Part;
|
|
9
|
+
/** Silence for a number of bars */
|
|
10
|
+
export declare const rest: (bars: number, meter?: Meter) => Part;
|
|
11
|
+
/** Shift every pitch by a number of semitones */
|
|
12
|
+
export declare function transpose(part: Part, semitones: number): Part;
|
|
13
|
+
/** Rewrite every pitch (for mode changes, inversions and so on) */
|
|
14
|
+
export declare function mapPitch(part: Part, fn: (midi: number) => number): Part;
|
|
15
|
+
/** Multiply every velocity (0.5 = half as loud; capped at 1) */
|
|
16
|
+
export declare function scaleVelocity(part: Part, factor: number): Part;
|
|
17
|
+
/** Cut out the steps [from, to); notes that run past the end are shortened */
|
|
18
|
+
export declare function slice(part: Part, from: number, to: number): Part;
|
|
19
|
+
/**
|
|
20
|
+
* The sustain pedal: every note rings on until the next pedal change, or until its key is struck again.
|
|
21
|
+
* Changes are given as a step interval (16: once a bar), a list of steps, or a chord progression (change with each chord).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* pedal(arpeggio(harmony, '0 1 2 1'), harmony) // re-pedal on every chord
|
|
25
|
+
* pedal(melody('...'), 16) // once a bar
|
|
26
|
+
*/
|
|
27
|
+
export declare function pedal(part: Part, changes: number | number[] | Chord[]): Part;
|
|
28
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/notation/transform.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAe,KAAK,KAAK,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAI1E,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,GAAG,MAAM,CAAC,EAAE,GAAG,IAAI,CAQ1D;AAED,wEAAwE;AACxE,wBAAgB,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAE5C;AAED,mCAAmC;AACnC,wBAAgB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED,mCAAmC;AACnC,eAAO,MAAM,IAAI,SAAU,MAAM,UAAS,KAAK,KAAQ,IAA4D,CAAC;AAEpH,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAE7D;AAED,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAEvE;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED,8EAA8E;AAC9E,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAKhE;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,CAc5E"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Combine and reshape parts. Every function returns a new part; nothing is modified in place.
|
|
2
|
+
import { melody } from './melody.js';
|
|
3
|
+
import { stepsPerBar } from './part.js';
|
|
4
|
+
const asPart = (p) => (typeof p === 'string' ? melody(p) : p);
|
|
5
|
+
/** Play parts one after another (strings are read as 4/4 melodies) */
|
|
6
|
+
export function sequence(...parts) {
|
|
7
|
+
const notes = [];
|
|
8
|
+
let t = 0;
|
|
9
|
+
for (const p of parts.map(asPart)) {
|
|
10
|
+
for (const n of p.notes)
|
|
11
|
+
notes.push({ ...n, time: n.time + t });
|
|
12
|
+
t += p.duration;
|
|
13
|
+
}
|
|
14
|
+
return { notes, duration: t };
|
|
15
|
+
}
|
|
16
|
+
/** Play parts at the same time; the result is as long as the longest */
|
|
17
|
+
export function stack(...parts) {
|
|
18
|
+
return { notes: parts.flatMap((p) => p.notes).sort((a, b) => a.time - b.time), duration: Math.max(0, ...parts.map((p) => p.duration)) };
|
|
19
|
+
}
|
|
20
|
+
/** Play a part n times in a row */
|
|
21
|
+
export function repeat(part, times) {
|
|
22
|
+
return sequence(...Array(times).fill(part));
|
|
23
|
+
}
|
|
24
|
+
/** Silence for a number of bars */
|
|
25
|
+
export const rest = (bars, meter = 16) => ({ notes: [], duration: bars * stepsPerBar(meter) });
|
|
26
|
+
/** Shift every pitch by a number of semitones */
|
|
27
|
+
export function transpose(part, semitones) {
|
|
28
|
+
return mapPitch(part, (m) => m + semitones);
|
|
29
|
+
}
|
|
30
|
+
/** Rewrite every pitch (for mode changes, inversions and so on) */
|
|
31
|
+
export function mapPitch(part, fn) {
|
|
32
|
+
return { notes: part.notes.map((n) => ({ ...n, pitches: n.pitches.map(fn) })), duration: part.duration };
|
|
33
|
+
}
|
|
34
|
+
/** Multiply every velocity (0.5 = half as loud; capped at 1) */
|
|
35
|
+
export function scaleVelocity(part, factor) {
|
|
36
|
+
return { notes: part.notes.map((n) => ({ ...n, velocity: Math.min(1, n.velocity * factor) })), duration: part.duration };
|
|
37
|
+
}
|
|
38
|
+
/** Cut out the steps [from, to); notes that run past the end are shortened */
|
|
39
|
+
export function slice(part, from, to) {
|
|
40
|
+
return {
|
|
41
|
+
notes: part.notes.filter((n) => n.time >= from && n.time < to).map((n) => ({ ...n, time: n.time - from, duration: Math.min(n.duration, to - n.time) })),
|
|
42
|
+
duration: to - from,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The sustain pedal: every note rings on until the next pedal change, or until its key is struck again.
|
|
47
|
+
* Changes are given as a step interval (16: once a bar), a list of steps, or a chord progression (change with each chord).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* pedal(arpeggio(harmony, '0 1 2 1'), harmony) // re-pedal on every chord
|
|
51
|
+
* pedal(melody('...'), 16) // once a bar
|
|
52
|
+
*/
|
|
53
|
+
export function pedal(part, changes) {
|
|
54
|
+
const points = typeof changes === 'number'
|
|
55
|
+
? Array.from({ length: Math.ceil(part.duration / changes) }, (_, i) => (i + 1) * changes)
|
|
56
|
+
: changes.map((c) => (typeof c === 'number' ? c : c.time)).sort((a, b) => a - b);
|
|
57
|
+
const nextChange = (t) => points.find((p) => p > t + 1e-9) ?? part.duration;
|
|
58
|
+
const nextStrike = (t, pitches) => {
|
|
59
|
+
const later = part.notes.filter((n) => n.time > t + 1e-9 && n.pitches.some((m) => pitches.includes(m)));
|
|
60
|
+
return later.length ? Math.min(...later.map((n) => n.time)) : Infinity;
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
notes: part.notes.map((n) => ({ ...n, duration: Math.max(n.duration, Math.min(nextChange(n.time), nextStrike(n.time, n.pitches)) - n.time) })),
|
|
64
|
+
duration: part.duration,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=transform.js.map
|