@hraness/dawg 0.4.0 → 0.5.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 +86 -0
- package/DAWG.md +300 -35
- package/README.md +4 -4
- package/core/chords.ts +288 -7
- package/core/diff.ts +37 -12
- package/core/expression.ts +1241 -0
- package/core/loop.ts +23 -0
- package/core/master.ts +455 -0
- package/core/midi.ts +452 -0
- package/core/rhythm.ts +7 -2
- package/core/score.ts +536 -56
- package/core/sdk/eval-child.ts +38 -3
- package/core/sdk/print.ts +445 -14
- package/core/sdk/v1.ts +1709 -23
- package/core/sections.ts +2046 -0
- package/core/synth.ts +11 -1
- package/core/tempo.ts +1318 -0
- package/core/tuning.ts +1180 -0
- package/guides/audition.md +26 -0
- package/guides/automation.md +26 -0
- package/guides/chords.md +28 -0
- package/guides/effects.md +27 -0
- package/guides/faders.md +29 -0
- package/guides/files.md +28 -0
- package/guides/getting-started.md +26 -0
- package/guides/index.ts +75 -0
- package/guides/keys.md +27 -0
- package/guides/media.md +27 -0
- package/guides/mix.md +23 -0
- package/guides/music.md +14 -0
- package/guides/notes.md +26 -0
- package/guides/performance.md +26 -0
- package/guides/play.md +24 -0
- package/guides/project.md +13 -0
- package/guides/providers.md +25 -0
- package/guides/rhythm.md +26 -0
- package/guides/sessions.md +21 -0
- package/guides/sound.md +15 -0
- package/guides/sounds.md +25 -0
- package/guides/tempo.md +26 -0
- package/guides/tracks.md +25 -0
- package/guides/web-search.md +21 -0
- package/package.json +3 -1
- package/src/agent/agent.ts +2 -0
- package/src/agent/brief.ts +77 -3
- package/src/agent/chord-tools.ts +9 -1
- package/src/agent/expression-tools.ts +336 -0
- package/src/agent/master-tools.ts +299 -0
- package/src/agent/models.ts +4 -4
- package/src/agent/ops.ts +29 -4
- package/src/agent/planner.ts +45 -2
- package/src/agent/preview-tool.ts +21 -3
- package/src/agent/section-tools.ts +411 -0
- package/src/agent/time-tools.ts +290 -0
- package/src/agent/tools.ts +36 -2
- package/src/agent/tuning-tools.ts +301 -0
- package/src/agent/xcb-agent.ts +2 -0
- package/src/audio/arrange.ts +471 -0
- package/src/audio/audition.ts +16 -3
- package/src/audio/click.ts +113 -1
- package/src/audio/clock.ts +71 -5
- package/src/audio/effects/bus.ts +5 -4
- package/src/audio/effects/chain.ts +12 -2
- package/src/audio/effects/common.ts +30 -1
- package/src/audio/effects/convolution.ts +43 -5
- package/src/audio/effects/dynamics.ts +2 -1
- package/src/audio/effects/filter.ts +5 -3
- package/src/audio/effects/modulation.ts +5 -1
- package/src/audio/effects/space.ts +197 -50
- package/src/audio/engine.ts +175 -20
- package/src/audio/live.ts +33 -6
- package/src/audio/loudness.ts +551 -0
- package/src/audio/master.ts +660 -0
- package/src/audio/measure-worker.ts +45 -0
- package/src/audio/measure.ts +110 -0
- package/src/audio/player.ts +11 -6
- package/src/audio/preview.ts +87 -16
- package/src/audio/render-worker.ts +6 -1
- package/src/audio/renderer.ts +7 -1
- package/src/audio/sampler.ts +108 -19
- package/src/audio/synth/voice.ts +60 -13
- package/src/audio/synth/zzfx.ts +10 -4
- package/src/audio/warp.ts +86 -0
- package/src/audio/wav.ts +448 -59
- package/src/commands/arrange.ts +949 -0
- package/src/commands/expression.ts +934 -0
- package/src/commands/help.ts +281 -24
- package/src/commands/master.ts +361 -0
- package/src/commands/music.ts +6 -1
- package/src/commands/synth.ts +11 -1
- package/src/commands/time.ts +964 -0
- package/src/commands/tuning.ts +490 -0
- package/src/main.ts +826 -47
- package/src/render.ts +109 -7
- package/src/session/daemon.ts +18 -8
- package/src/session/naming.ts +8 -1
- package/src/session/rebase.ts +21 -0
- package/src/tui/arrange-menu.ts +390 -0
- package/src/tui/audition.ts +58 -5
- package/src/tui/euclid.ts +50 -6
- package/src/tui/fader.ts +409 -0
- package/src/tui/menu-time.ts +401 -0
- package/src/tui/menu.ts +656 -16
- package/src/tui/performance-menu.ts +235 -0
- package/src/tui/play-chords.ts +95 -1
- package/src/tui/play-mode.ts +150 -6
- package/src/tui/play-session.ts +414 -41
- package/tui/app.ts +262 -13
- package/tui/arrange-strip.ts +174 -0
- package/tui/drawer.ts +478 -0
- package/tui/grammar.ts +79 -12
- package/tui/guide.ts +351 -0
- package/tui/highway.ts +87 -4
- package/tui/hits.ts +68 -0
- package/tui/input.ts +7 -0
- package/tui/keys.ts +72 -0
package/src/audio/clock.ts
CHANGED
|
@@ -1,7 +1,40 @@
|
|
|
1
|
+
import { loopTicksOf, timeMapFor, type TimeScore } from "../../core/tempo.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Beats and seconds through a song's tempo map, one loop at a time: the
|
|
5
|
+
* transport runs in score beats, and the map says how long each loop pass
|
|
6
|
+
* takes. Absent, the clock keeps its constant-tempo arithmetic.
|
|
7
|
+
*/
|
|
8
|
+
export type TransportMap = Readonly<{
|
|
9
|
+
loopBeats: number;
|
|
10
|
+
loopSeconds: number;
|
|
11
|
+
/** Seconds into a loop pass at `beat` (0..loopBeats). */
|
|
12
|
+
seconds(beat: number): number;
|
|
13
|
+
/** Beat inside a loop pass at `seconds` (0..loopSeconds). */
|
|
14
|
+
beat(seconds: number): number;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
/** The transport map for a score, or undefined at a constant tempo. */
|
|
18
|
+
export function transportMapFor(score: TimeScore): TransportMap | undefined {
|
|
19
|
+
const map = timeMapFor(score);
|
|
20
|
+
if (!map) return undefined;
|
|
21
|
+
const tpb = score.ticksPerBeat;
|
|
22
|
+
const loopBeats = loopTicksOf(score) / tpb;
|
|
23
|
+
const loopSeconds = map.seconds(loopTicksOf(score));
|
|
24
|
+
if (!(loopBeats > 0) || !(loopSeconds > 0)) return undefined;
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
loopBeats,
|
|
27
|
+
loopSeconds,
|
|
28
|
+
seconds: (beat: number) => map.seconds(beat * tpb),
|
|
29
|
+
beat: (seconds: number) => map.tick(seconds) / tpb,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
1
33
|
export class TransportClock {
|
|
2
34
|
private startedAtMs: number | undefined;
|
|
3
35
|
private pausedBeat = 0;
|
|
4
36
|
private bpm: number;
|
|
37
|
+
private map: TransportMap | undefined;
|
|
5
38
|
|
|
6
39
|
constructor(bpm = 120) {
|
|
7
40
|
this.bpm = bpm;
|
|
@@ -24,6 +57,7 @@ export class TransportClock {
|
|
|
24
57
|
|
|
25
58
|
beatAt(nowMs = performance.now()): number {
|
|
26
59
|
if (this.startedAtMs === undefined) return this.pausedBeat;
|
|
60
|
+
if (this.map) return Math.max(0, this.msToBeat(nowMs - this.startedAtMs));
|
|
27
61
|
return Math.max(0, (nowMs - this.startedAtMs) / this.beatToMs(1));
|
|
28
62
|
}
|
|
29
63
|
|
|
@@ -40,6 +74,25 @@ export class TransportClock {
|
|
|
40
74
|
else this.pausedBeat = beat;
|
|
41
75
|
}
|
|
42
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Follow a song's tempo map (`transportMapFor`), keeping the current
|
|
79
|
+
* beat; undefined returns to the constant `bpm`.
|
|
80
|
+
*/
|
|
81
|
+
setTimeMap(map: TransportMap | undefined, nowMs = performance.now()): void {
|
|
82
|
+
if (map === this.map) return;
|
|
83
|
+
const beat = this.beatAt(nowMs);
|
|
84
|
+
this.map = map;
|
|
85
|
+
if (this.startedAtMs !== undefined)
|
|
86
|
+
this.startedAtMs = nowMs - this.beatToMs(beat);
|
|
87
|
+
else this.pausedBeat = beat;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Follow a score's start tempo and tempo map. */
|
|
91
|
+
follow(score: TimeScore, nowMs = performance.now()): void {
|
|
92
|
+
this.setTempo(score.tempoBpm, nowMs);
|
|
93
|
+
this.setTimeMap(transportMapFor(score), nowMs);
|
|
94
|
+
}
|
|
95
|
+
|
|
43
96
|
/** Converge to a timestamped transport event from another TUI process. */
|
|
44
97
|
sync(
|
|
45
98
|
beat: number,
|
|
@@ -50,10 +103,10 @@ export class TransportClock {
|
|
|
50
103
|
): void {
|
|
51
104
|
if (!Number.isFinite(beat) || beat < 0) return;
|
|
52
105
|
if (!Number.isFinite(eventAtMs) || !Number.isFinite(nowWallMs)) return;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
106
|
+
const elapsedMs = playing ? Math.max(0, nowWallMs - eventAtMs) : 0;
|
|
107
|
+
const currentBeat = this.map
|
|
108
|
+
? this.msToBeat(this.beatToMs(beat) + elapsedMs)
|
|
109
|
+
: beat + elapsedMs / this.beatToMs(1);
|
|
57
110
|
if (playing) {
|
|
58
111
|
this.startedAtMs = nowMonotonicMs - this.beatToMs(currentBeat);
|
|
59
112
|
} else {
|
|
@@ -63,6 +116,19 @@ export class TransportClock {
|
|
|
63
116
|
}
|
|
64
117
|
|
|
65
118
|
private beatToMs(beats: number): number {
|
|
66
|
-
|
|
119
|
+
const map = this.map;
|
|
120
|
+
if (!map) return (beats * 60_000) / this.bpm;
|
|
121
|
+
const loops = Math.floor(beats / map.loopBeats);
|
|
122
|
+
const inner = beats - loops * map.loopBeats;
|
|
123
|
+
return (loops * map.loopSeconds + map.seconds(inner)) * 1000;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private msToBeat(ms: number): number {
|
|
127
|
+
const map = this.map;
|
|
128
|
+
if (!map) return ms / this.beatToMs(1);
|
|
129
|
+
const seconds = ms / 1000;
|
|
130
|
+
const loops = Math.floor(seconds / map.loopSeconds);
|
|
131
|
+
const inner = seconds - loops * map.loopSeconds;
|
|
132
|
+
return loops * map.loopBeats + map.beat(inner);
|
|
67
133
|
}
|
|
68
134
|
}
|
package/src/audio/effects/bus.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { Track } from "../../../core/score.ts";
|
|
|
17
17
|
import {
|
|
18
18
|
CONTROL_SAMPLES,
|
|
19
19
|
interpolateAutomation,
|
|
20
|
+
tickAtSample,
|
|
20
21
|
type EffectContext,
|
|
21
22
|
} from "./common.ts";
|
|
22
23
|
import { applyDelay, applyReverb } from "./space.ts";
|
|
@@ -58,13 +59,13 @@ function send(
|
|
|
58
59
|
stemR: Float64Array,
|
|
59
60
|
base: number,
|
|
60
61
|
lane: readonly Readonly<{ tick: number; value: number }>[],
|
|
61
|
-
|
|
62
|
+
context: Pick<EffectContext, "samplesPerTick" | "warp">,
|
|
62
63
|
): void {
|
|
63
64
|
if (base <= 0 && lane.length === 0) return;
|
|
64
65
|
let level = base;
|
|
65
66
|
for (let index = 0; index < outL.length; index += 1) {
|
|
66
67
|
if (lane.length > 0 && index % CONTROL_SAMPLES === 0)
|
|
67
|
-
level = interpolateAutomation(lane, index
|
|
68
|
+
level = interpolateAutomation(lane, tickAtSample(context, index), base);
|
|
68
69
|
outL[index]! += stemL[index]! * level;
|
|
69
70
|
outR[index]! += stemR[index]! * level;
|
|
70
71
|
}
|
|
@@ -89,7 +90,7 @@ export function sendToBus(
|
|
|
89
90
|
stemR,
|
|
90
91
|
track.delay.mix,
|
|
91
92
|
track.delayMixAutomation ?? [],
|
|
92
|
-
context
|
|
93
|
+
context,
|
|
93
94
|
);
|
|
94
95
|
}
|
|
95
96
|
if (track.reverb) {
|
|
@@ -101,7 +102,7 @@ export function sendToBus(
|
|
|
101
102
|
stemR,
|
|
102
103
|
track.reverb.mix,
|
|
103
104
|
track.fxAutomation?.["reverb-mix"] ?? [],
|
|
104
|
-
context
|
|
105
|
+
context,
|
|
105
106
|
);
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -21,7 +21,15 @@ import {
|
|
|
21
21
|
import { applyChorus, applyLeslie, applyPhaser } from "./modulation.ts";
|
|
22
22
|
import { applyDelay, applyReverb } from "./space.ts";
|
|
23
23
|
|
|
24
|
-
export {
|
|
24
|
+
export {
|
|
25
|
+
addReverbWet,
|
|
26
|
+
delayTailFor,
|
|
27
|
+
reverbActive,
|
|
28
|
+
reverbImpulse,
|
|
29
|
+
reverbTailFor,
|
|
30
|
+
reverbWet,
|
|
31
|
+
type ReverbWet,
|
|
32
|
+
} from "./space.ts";
|
|
25
33
|
export { interpolateAutomation, type EffectContext } from "./common.ts";
|
|
26
34
|
|
|
27
35
|
type MonoStage = (
|
|
@@ -77,6 +85,7 @@ export function applyMonoChain(
|
|
|
77
85
|
/**
|
|
78
86
|
* Stereo stages, in chain order, after pan. A track on a shared orbit bus
|
|
79
87
|
* (`sends: false`) skips delay and reverb: the bus applies them to the sum.
|
|
88
|
+
* `reverb: false` stops before the reverb (the stem cache runs it apart).
|
|
80
89
|
*/
|
|
81
90
|
export function applyStereoChain(
|
|
82
91
|
left: Float64Array,
|
|
@@ -84,12 +93,13 @@ export function applyStereoChain(
|
|
|
84
93
|
track: Track,
|
|
85
94
|
context: EffectContext,
|
|
86
95
|
sends = true,
|
|
96
|
+
reverb = sends,
|
|
87
97
|
): void {
|
|
88
98
|
for (const stage of FX_CHAIN.slice(PAN_INDEX + 1)) {
|
|
89
99
|
if (stage === "delay") {
|
|
90
100
|
if (sends) applyDelay(left, right, track, context);
|
|
91
101
|
} else if (stage === "reverb") {
|
|
92
|
-
if (
|
|
102
|
+
if (reverb) applyReverb(left, right, track, context);
|
|
93
103
|
} else {
|
|
94
104
|
const values = track.fx?.[stage as FxName];
|
|
95
105
|
const apply = STEREO[stage as FxName];
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import type { AutomationPoint, Track } from "../../../core/score.ts";
|
|
13
13
|
import type { FxLane, FxValues } from "../../../core/fx.ts";
|
|
14
14
|
import type { DecodedSample } from "../samples.ts";
|
|
15
|
+
import type { SampleWarp } from "../warp.ts";
|
|
15
16
|
|
|
16
17
|
/** Effect parameters are refreshed at this sample interval. */
|
|
17
18
|
export const CONTROL_SAMPLES = 32;
|
|
@@ -22,8 +23,34 @@ export type EffectContext = Readonly<{
|
|
|
22
23
|
tempoBpm: number;
|
|
23
24
|
/** Decoded `reverb.ir` samples by track id (built-in impulses need none). */
|
|
24
25
|
irs?: ReadonlyMap<string, DecodedSample>;
|
|
26
|
+
/** The song's tempo map in samples; absent: constant tempo. */
|
|
27
|
+
warp?: SampleWarp;
|
|
25
28
|
}>;
|
|
26
29
|
|
|
30
|
+
/** Score tick at sample `index`, through the tempo map when there is one. */
|
|
31
|
+
export function tickAtSample(
|
|
32
|
+
context: Pick<EffectContext, "samplesPerTick" | "warp">,
|
|
33
|
+
index: number,
|
|
34
|
+
): number {
|
|
35
|
+
return context.warp
|
|
36
|
+
? context.warp.tick(index)
|
|
37
|
+
: index / context.samplesPerTick;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tempo at sample `index` for tempo-synced LFOs and delays: through the
|
|
42
|
+
* tempo map when there is one (ramps, steps and fermata holds included),
|
|
43
|
+
* else the song's constant tempo.
|
|
44
|
+
*/
|
|
45
|
+
export function tempoAtSample(
|
|
46
|
+
context: Pick<EffectContext, "tempoBpm" | "warp">,
|
|
47
|
+
index: number,
|
|
48
|
+
): number {
|
|
49
|
+
return context.warp
|
|
50
|
+
? context.warp.bpm(context.warp.tick(index))
|
|
51
|
+
: context.tempoBpm;
|
|
52
|
+
}
|
|
53
|
+
|
|
27
54
|
/** Resolve a piecewise-linear automation lane, holding the static value before its first point. */
|
|
28
55
|
export function interpolateAutomation(
|
|
29
56
|
points: readonly AutomationPoint[],
|
|
@@ -58,6 +85,7 @@ export class Param {
|
|
|
58
85
|
readonly fallback: number,
|
|
59
86
|
points: readonly AutomationPoint[] | undefined,
|
|
60
87
|
private readonly samplesPerTick: number,
|
|
88
|
+
private readonly warp?: SampleWarp,
|
|
61
89
|
) {
|
|
62
90
|
this.points = points ?? [];
|
|
63
91
|
this.automated = this.points.length > 0;
|
|
@@ -67,7 +95,7 @@ export class Param {
|
|
|
67
95
|
return this.automated
|
|
68
96
|
? interpolateAutomation(
|
|
69
97
|
this.points,
|
|
70
|
-
index / this.samplesPerTick,
|
|
98
|
+
this.warp ? this.warp.tick(index) : index / this.samplesPerTick,
|
|
71
99
|
this.fallback,
|
|
72
100
|
)
|
|
73
101
|
: this.fallback;
|
|
@@ -93,6 +121,7 @@ export function fxReader(
|
|
|
93
121
|
values[name] as number,
|
|
94
122
|
track.fxAutomation?.[`${effect}-${name}` as FxLane],
|
|
95
123
|
context.samplesPerTick,
|
|
124
|
+
context.warp,
|
|
96
125
|
);
|
|
97
126
|
},
|
|
98
127
|
};
|
|
@@ -119,9 +119,48 @@ export function convolveStereo(
|
|
|
119
119
|
gain: number,
|
|
120
120
|
gainAt?: (index: number) => number,
|
|
121
121
|
): void {
|
|
122
|
+
const rawL = new Float64Array(input.length);
|
|
123
|
+
const rawR = new Float64Array(input.length);
|
|
124
|
+
const scale = convolveRaw(input, impulse, rawL, rawR);
|
|
125
|
+
if (scale === 0) return;
|
|
126
|
+
addConvolved(rawL, rawR, scale, outL, outR, gain, gainAt);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Adds the unscaled convolution from `convolveRaw` at `gain` (or `gainAt`
|
|
131
|
+
* per sample): `out += raw · (gain · scale)`, the order `convolveStereo`
|
|
132
|
+
* always used, so a cached raw signal mixes to the same samples.
|
|
133
|
+
*/
|
|
134
|
+
export function addConvolved(
|
|
135
|
+
rawL: Float64Array,
|
|
136
|
+
rawR: Float64Array,
|
|
137
|
+
scale: number,
|
|
138
|
+
outL: Float64Array,
|
|
139
|
+
outR: Float64Array,
|
|
140
|
+
gain: number,
|
|
141
|
+
gainAt?: (index: number) => number,
|
|
142
|
+
): void {
|
|
143
|
+
for (let index = 0; index < rawL.length; index += 1) {
|
|
144
|
+
const g = (gainAt ? gainAt(index) : gain) * scale;
|
|
145
|
+
outL[index]! += rawL[index]! * g;
|
|
146
|
+
outR[index]! += rawR[index]! * g;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The convolution input ∗ impulse into `rawL`/`rawR` before its inverse-FFT
|
|
152
|
+
* scale, which it returns. Samples past the input are left as they were
|
|
153
|
+
* (zero for fresh arrays), as is everything when the impulse is empty.
|
|
154
|
+
*/
|
|
155
|
+
export function convolveRaw(
|
|
156
|
+
input: Float64Array,
|
|
157
|
+
impulse: Impulse,
|
|
158
|
+
rawL: Float64Array,
|
|
159
|
+
rawR: Float64Array,
|
|
160
|
+
): number {
|
|
122
161
|
const length = input.length;
|
|
123
162
|
const irLength = impulse.left.length;
|
|
124
|
-
if (length === 0 || irLength === 0) return;
|
|
163
|
+
if (length === 0 || irLength === 0) return 0;
|
|
125
164
|
const block = partitionSize(irLength);
|
|
126
165
|
const n = block * 2;
|
|
127
166
|
const partitions = Math.ceil(irLength / block);
|
|
@@ -179,12 +218,11 @@ export function convolveStereo(
|
|
|
179
218
|
const outStart = k * block;
|
|
180
219
|
const count = Math.min(block, length - outStart);
|
|
181
220
|
for (let i = 0; i < count; i += 1) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
outL[index]! += accRe[block + i]! * g;
|
|
185
|
-
outR[index]! += accIm[block + i]! * g;
|
|
221
|
+
rawL[outStart + i] = accRe[block + i]!;
|
|
222
|
+
rawR[outStart + i] = accIm[block + i]!;
|
|
186
223
|
}
|
|
187
224
|
}
|
|
225
|
+
return scale;
|
|
188
226
|
}
|
|
189
227
|
|
|
190
228
|
/** Built-in impulse names (`builtin:<name>`). */
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
fxReader,
|
|
11
11
|
lfo,
|
|
12
12
|
lfoHz,
|
|
13
|
+
tempoAtSample,
|
|
13
14
|
type EffectContext,
|
|
14
15
|
} from "./common.ts";
|
|
15
16
|
|
|
@@ -42,7 +43,7 @@ export function applyTremolo(
|
|
|
42
43
|
for (let index = 0; index < buffer.length; index += 1) {
|
|
43
44
|
if (index % CONTROL_SAMPLES === 0) {
|
|
44
45
|
phasor.setHz(
|
|
45
|
-
lfoHz(sync, rate.at(index), context
|
|
46
|
+
lfoHz(sync, rate.at(index), tempoAtSample(context, index)),
|
|
46
47
|
context.sampleRate,
|
|
47
48
|
);
|
|
48
49
|
if (depth.automated) amount = depth.at(index);
|
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
interpolateAutomation,
|
|
16
16
|
lfo,
|
|
17
17
|
lfoHz,
|
|
18
|
+
tempoAtSample,
|
|
18
19
|
resonanceQ,
|
|
20
|
+
tickAtSample,
|
|
19
21
|
type EffectContext,
|
|
20
22
|
type FilterType,
|
|
21
23
|
} from "./common.ts";
|
|
@@ -99,7 +101,7 @@ export function applyTrackFilter(
|
|
|
99
101
|
const resonanceLane = track.resonanceAutomation ?? [];
|
|
100
102
|
if (!track.filter && automation.length === 0 && resonanceLane.length === 0)
|
|
101
103
|
return;
|
|
102
|
-
const { sampleRate
|
|
104
|
+
const { sampleRate } = context;
|
|
103
105
|
const staticCutoff = track.filter?.cutoff ?? SCORE_LIMITS.maxFilterCutoff;
|
|
104
106
|
const staticResonance = track.filter?.resonance ?? 0;
|
|
105
107
|
const automated = automation.length > 0 || resonanceLane.length > 0;
|
|
@@ -111,7 +113,7 @@ export function applyTrackFilter(
|
|
|
111
113
|
filter.update(staticCutoff, staticResonance);
|
|
112
114
|
for (let index = 0; index < buffer.length; index += 1) {
|
|
113
115
|
if (automated && index % CONTROL_SAMPLES === 0) {
|
|
114
|
-
const tick = index
|
|
116
|
+
const tick = tickAtSample(context, index);
|
|
115
117
|
filter.update(
|
|
116
118
|
interpolateAutomation(automation, tick, staticCutoff),
|
|
117
119
|
interpolateAutomation(resonanceLane, tick, staticResonance),
|
|
@@ -205,7 +207,7 @@ export function applyAutoFilter(
|
|
|
205
207
|
: release * envelope + (1 - release) * level;
|
|
206
208
|
if (index % CONTROL_SAMPLES === 0) {
|
|
207
209
|
phasor.setHz(
|
|
208
|
-
lfoHz(sync, rate.at(index), context
|
|
210
|
+
lfoHz(sync, rate.at(index), tempoAtSample(context, index)),
|
|
209
211
|
context.sampleRate,
|
|
210
212
|
);
|
|
211
213
|
followOctaves = follow.at(index);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
clamp,
|
|
10
10
|
fxReader,
|
|
11
11
|
lfoHz,
|
|
12
|
+
tempoAtSample,
|
|
12
13
|
readFractional,
|
|
13
14
|
type EffectContext,
|
|
14
15
|
} from "./common.ts";
|
|
@@ -57,7 +58,10 @@ export function applyPhaser(
|
|
|
57
58
|
};
|
|
58
59
|
for (let index = 0; index < left.length; index += 1) {
|
|
59
60
|
if (index % CONTROL_SAMPLES === 0) {
|
|
60
|
-
phasor.setHz(
|
|
61
|
+
phasor.setHz(
|
|
62
|
+
lfoHz(sync, rate.at(index), tempoAtSample(context, index)),
|
|
63
|
+
sampleRate,
|
|
64
|
+
);
|
|
61
65
|
if (depth.automated) amount = depth.at(index);
|
|
62
66
|
const frequency = clamp(
|
|
63
67
|
center + (sweep / 2) * Math.sin(2 * Math.PI * phasor.phase),
|