@xsynaptic/sonic-ui 0.23.0 → 0.24.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 +10 -0
- package/dist/default/lib/waveform-bands.js +37 -24
- package/dist/default/lib/waveform-scene.js +12 -1
- package/dist/dev/elements/waveform.d.ts +3 -0
- package/dist/dev/lib/waveform-bands.js +37 -24
- package/dist/dev/lib/waveform-bands.js.map +1 -1
- package/dist/dev/lib/waveform-scene.d.ts +3 -0
- package/dist/dev/lib/waveform-scene.js +12 -1
- package/dist/dev/lib/waveform-scene.js.map +1 -1
- package/dist/styles/waveform.css +21 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @xsynaptic/sonic-ui
|
|
2
2
|
|
|
3
|
+
## 0.24.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Waveform: three number tokens shape the band mix, each watched, so a change repaints with no `repaint()`. Unset, the tint is unchanged.
|
|
8
|
+
|
|
9
|
+
- `--sonic-waveform-band-normalize`, 0 to 1 (default 1): at 0 the mix is the weighted average of the band colours and stays inside the palette
|
|
10
|
+
- `--sonic-waveform-band-contrast` (default 1): the exponent on each band's weight
|
|
11
|
+
- `--sonic-waveform-band-tilt` (default 0): decibels of gain per band above the lowest
|
|
12
|
+
|
|
3
13
|
## 0.23.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { clampProportion } from "./math.js";
|
|
2
|
+
|
|
1
3
|
//#region src/lib/waveform-bands.ts
|
|
2
4
|
function fullScaleOf({ fullScale, levels }) {
|
|
3
5
|
if (fullScale !== void 0) return fullScale;
|
|
@@ -32,33 +34,44 @@ function bandTable(bands) {
|
|
|
32
34
|
weightOf: (level) => weights[level] ?? 0
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
255
|
|
57
|
-
|
|
37
|
+
function groupMixer({ bandCount, levels, weightOf }, colours, { contrast, normalize, tilt }) {
|
|
38
|
+
const gains = Array.from({ length: bandCount }, (_gain, band) => 10 ** (tilt * band / 20));
|
|
39
|
+
const amount = clampProportion(normalize);
|
|
40
|
+
return (fromFrame, toFrame) => {
|
|
41
|
+
let red = 0;
|
|
42
|
+
let green = 0;
|
|
43
|
+
let blue = 0;
|
|
44
|
+
let total = 0;
|
|
45
|
+
for (let band = 0; band < bandCount; band += 1) {
|
|
46
|
+
const colour = colours[band];
|
|
47
|
+
if (!colour) continue;
|
|
48
|
+
let weight = 0;
|
|
49
|
+
for (let bandFrame = fromFrame; bandFrame < toFrame; bandFrame += 1) weight += weightOf(levels[bandFrame * bandCount + band] ?? 0);
|
|
50
|
+
weight *= gains[band] ?? 1;
|
|
51
|
+
if (contrast !== 1) weight **= contrast;
|
|
52
|
+
red += weight * colour[0];
|
|
53
|
+
green += weight * colour[1];
|
|
54
|
+
blue += weight * colour[2];
|
|
55
|
+
total += weight;
|
|
56
|
+
}
|
|
57
|
+
if (total === 0) return;
|
|
58
|
+
const scale = 255 / Math.max(red, green, blue, Number.MIN_VALUE) * amount + (1 - amount) / total;
|
|
59
|
+
return [
|
|
60
|
+
red * scale,
|
|
61
|
+
green * scale,
|
|
62
|
+
blue * scale,
|
|
63
|
+
255
|
|
64
|
+
];
|
|
65
|
+
};
|
|
58
66
|
}
|
|
59
67
|
function bandStrip(table, colours, view) {
|
|
60
68
|
if (view.pixelsPerSecond <= 0 || !colours.some(Boolean)) return void 0;
|
|
61
69
|
const { bandCount, framesPerSecond, levels } = table;
|
|
70
|
+
const mixGroup = groupMixer(table, colours, view.mix ?? {
|
|
71
|
+
contrast: 1,
|
|
72
|
+
normalize: 1,
|
|
73
|
+
tilt: 0
|
|
74
|
+
});
|
|
62
75
|
const pxPerFrame = view.pixelsPerSecond / framesPerSecond;
|
|
63
76
|
const frames = Math.max(1, Math.round(1 / pxPerFrame));
|
|
64
77
|
const groupWidth = frames * pxPerFrame;
|
|
@@ -70,7 +83,7 @@ function bandStrip(table, colours, view) {
|
|
|
70
83
|
const pixels = new Uint8ClampedArray(count * 4);
|
|
71
84
|
for (let index = 0; index < count; index += 1) {
|
|
72
85
|
const fromFrame = (first + index) * frames;
|
|
73
|
-
const mixed = mixGroup(
|
|
86
|
+
const mixed = mixGroup(fromFrame, fromFrame + frames);
|
|
74
87
|
if (mixed) pixels.set(mixed, index * 4);
|
|
75
88
|
}
|
|
76
89
|
return {
|
|
@@ -14,7 +14,10 @@ const waveformColours = {
|
|
|
14
14
|
wave: "--_sonic-waveform-wave"
|
|
15
15
|
};
|
|
16
16
|
const waveformNumbers = {
|
|
17
|
+
bandContrast: "--_sonic-waveform-band-contrast",
|
|
18
|
+
bandNormalize: "--_sonic-waveform-band-normalize",
|
|
17
19
|
bandOpacity: "--_sonic-waveform-band-opacity",
|
|
20
|
+
bandTilt: "--_sonic-waveform-band-tilt",
|
|
18
21
|
edgeShade: "--_sonic-waveform-edge-shade"
|
|
19
22
|
};
|
|
20
23
|
const amplitudeMargin = .94;
|
|
@@ -71,7 +74,15 @@ function bandChannels(strip, colour) {
|
|
|
71
74
|
];
|
|
72
75
|
}
|
|
73
76
|
function paintTint(context, scene, tint) {
|
|
74
|
-
const
|
|
77
|
+
const { bandContrast, bandNormalize, bandTilt } = scene.numbers;
|
|
78
|
+
const strip = bandStrip(tint.table, tint.colours, {
|
|
79
|
+
...scene,
|
|
80
|
+
mix: {
|
|
81
|
+
contrast: bandContrast,
|
|
82
|
+
normalize: bandNormalize,
|
|
83
|
+
tilt: bandTilt
|
|
84
|
+
}
|
|
85
|
+
});
|
|
75
86
|
if (!strip) return;
|
|
76
87
|
const { canvas } = tint.strip;
|
|
77
88
|
const { count } = strip;
|
|
@@ -65,7 +65,10 @@ export declare class SonicWaveform extends SonicWaveElement<Colour, never, Numer
|
|
|
65
65
|
};
|
|
66
66
|
protected readonly lengths: {};
|
|
67
67
|
protected readonly numbers: {
|
|
68
|
+
readonly bandContrast: "--_sonic-waveform-band-contrast";
|
|
69
|
+
readonly bandNormalize: "--_sonic-waveform-band-normalize";
|
|
68
70
|
readonly bandOpacity: "--_sonic-waveform-band-opacity";
|
|
71
|
+
readonly bandTilt: "--_sonic-waveform-band-tilt";
|
|
69
72
|
readonly edgeShade: "--_sonic-waveform-edge-shade";
|
|
70
73
|
};
|
|
71
74
|
protected readonly sheet = "waveform.css";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { clampProportion } from "./math.js";
|
|
2
|
+
|
|
1
3
|
//#region src/lib/waveform-bands.ts
|
|
2
4
|
function fullScaleOf({ fullScale, levels }) {
|
|
3
5
|
if (fullScale !== void 0) return fullScale;
|
|
@@ -32,33 +34,44 @@ function bandTable(bands) {
|
|
|
32
34
|
weightOf: (level) => weights[level] ?? 0
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
255
|
|
57
|
-
|
|
37
|
+
function groupMixer({ bandCount, levels, weightOf }, colours, { contrast, normalize, tilt }) {
|
|
38
|
+
const gains = Array.from({ length: bandCount }, (_gain, band) => 10 ** (tilt * band / 20));
|
|
39
|
+
const amount = clampProportion(normalize);
|
|
40
|
+
return (fromFrame, toFrame) => {
|
|
41
|
+
let red = 0;
|
|
42
|
+
let green = 0;
|
|
43
|
+
let blue = 0;
|
|
44
|
+
let total = 0;
|
|
45
|
+
for (let band = 0; band < bandCount; band += 1) {
|
|
46
|
+
const colour = colours[band];
|
|
47
|
+
if (!colour) continue;
|
|
48
|
+
let weight = 0;
|
|
49
|
+
for (let bandFrame = fromFrame; bandFrame < toFrame; bandFrame += 1) weight += weightOf(levels[bandFrame * bandCount + band] ?? 0);
|
|
50
|
+
weight *= gains[band] ?? 1;
|
|
51
|
+
if (contrast !== 1) weight **= contrast;
|
|
52
|
+
red += weight * colour[0];
|
|
53
|
+
green += weight * colour[1];
|
|
54
|
+
blue += weight * colour[2];
|
|
55
|
+
total += weight;
|
|
56
|
+
}
|
|
57
|
+
if (total === 0) return;
|
|
58
|
+
const scale = 255 / Math.max(red, green, blue, Number.MIN_VALUE) * amount + (1 - amount) / total;
|
|
59
|
+
return [
|
|
60
|
+
red * scale,
|
|
61
|
+
green * scale,
|
|
62
|
+
blue * scale,
|
|
63
|
+
255
|
|
64
|
+
];
|
|
65
|
+
};
|
|
58
66
|
}
|
|
59
67
|
function bandStrip(table, colours, view) {
|
|
60
68
|
if (view.pixelsPerSecond <= 0 || !colours.some(Boolean)) return void 0;
|
|
61
69
|
const { bandCount, framesPerSecond, levels } = table;
|
|
70
|
+
const mixGroup = groupMixer(table, colours, view.mix ?? {
|
|
71
|
+
contrast: 1,
|
|
72
|
+
normalize: 1,
|
|
73
|
+
tilt: 0
|
|
74
|
+
});
|
|
62
75
|
const pxPerFrame = view.pixelsPerSecond / framesPerSecond;
|
|
63
76
|
const frames = Math.max(1, Math.round(1 / pxPerFrame));
|
|
64
77
|
const groupWidth = frames * pxPerFrame;
|
|
@@ -70,7 +83,7 @@ function bandStrip(table, colours, view) {
|
|
|
70
83
|
const pixels = new Uint8ClampedArray(count * 4);
|
|
71
84
|
for (let index = 0; index < count; index += 1) {
|
|
72
85
|
const fromFrame = (first + index) * frames;
|
|
73
|
-
const mixed = mixGroup(
|
|
86
|
+
const mixed = mixGroup(fromFrame, fromFrame + frames);
|
|
74
87
|
if (mixed) pixels.set(mixed, index * 4);
|
|
75
88
|
}
|
|
76
89
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waveform-bands.js","names":[],"sources":["../../../src/lib/waveform-bands.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"waveform-bands.js","names":[],"sources":["../../../src/lib/waveform-bands.ts"],"sourcesContent":["import { clampProportion } from '#lib/math.ts';\n\nexport interface WaveformBands {\n\t/** Bands per frame, interleaved in `levels`, lowest first */\n\tbandCount: number;\n\tframesPerSecond: number;\n\t/** Unset follows the array: 255, 65535 or 1 */\n\tfullScale?: number;\n\tlevels: Float32Array | Uint8Array | Uint16Array;\n\t/** Reads a level as decibels, from this just above zero up to 0 at full scale; unset means linear */\n\tminDecibels?: number;\n}\n\nexport type BandChannels = readonly [number, number, number];\n\nexport interface BandTable {\n\tbandCount: number;\n\tframesPerSecond: number;\n\tlevels: WaveformBands['levels'];\n\tweightOf: (level: number) => number;\n}\n\nexport interface BandMix {\n\tcontrast: number;\n\tnormalize: number;\n\ttilt: number;\n}\n\ninterface BandView {\n\tmix?: BandMix;\n\tpixelsPerSecond: number;\n\tstartSeconds: number;\n\twidth: number;\n}\n\ninterface BandStrip {\n\tcount: number;\n\tgroupWidth: number;\n\tpixels: Uint8ClampedArray<ArrayBuffer>;\n\tx: number;\n}\n\nfunction fullScaleOf({ fullScale, levels }: WaveformBands): number {\n\tif (fullScale !== undefined) return fullScale;\n\tif (levels instanceof Uint8Array) return 255;\n\n\treturn levels instanceof Uint16Array ? 65_535 : 1;\n}\n\nfunction amplitudeReader(bands: WaveformBands): (level: number) => number {\n\tconst fullScale = fullScaleOf(bands);\n\tconst minDecibels = Math.min(0, bands.minDecibels ?? 0);\n\n\treturn (level) => {\n\t\tconst proportion = Math.min(1, level / fullScale);\n\t\tif (!(proportion > 0)) return 0;\n\n\t\treturn minDecibels < 0 ? 10 ** ((minDecibels * (1 - proportion)) / 20) : proportion;\n\t};\n}\n\nexport function bandTable(bands: undefined | WaveformBands): BandTable | undefined {\n\tif (!bands) return undefined;\n\n\tconst { bandCount, framesPerSecond, levels } = bands;\n\tif (!Number.isSafeInteger(bandCount) || bandCount < 1 || !(framesPerSecond > 0)) return undefined;\n\n\tconst amplitudeOf = amplitudeReader(bands);\n\tif (levels instanceof Float32Array) {\n\t\treturn { bandCount, framesPerSecond, levels, weightOf: amplitudeOf };\n\t}\n\n\tconst weights = Float32Array.from(\n\t\t{ length: levels instanceof Uint8Array ? 256 : 65_536 },\n\t\t(_weight, level) => amplitudeOf(level),\n\t);\n\n\treturn { bandCount, framesPerSecond, levels, weightOf: (level) => weights[level] ?? 0 };\n}\n\n// Loudness is the envelope's height, so the mix is hue: scaled to its strongest channel, or averaged to stay inside the palette\nfunction groupMixer(\n\t{ bandCount, levels, weightOf }: BandTable,\n\tcolours: ReadonlyArray<BandChannels | undefined>,\n\t{ contrast, normalize, tilt }: BandMix,\n): (fromFrame: number, toFrame: number) => [number, number, number, number] | undefined {\n\tconst gains = Array.from({ length: bandCount }, (_gain, band) => 10 ** ((tilt * band) / 20));\n\tconst amount = clampProportion(normalize);\n\n\treturn (fromFrame, toFrame) => {\n\t\tlet red = 0;\n\t\tlet green = 0;\n\t\tlet blue = 0;\n\t\tlet total = 0;\n\n\t\tfor (let band = 0; band < bandCount; band += 1) {\n\t\t\tconst colour = colours[band];\n\t\t\tif (!colour) continue;\n\n\t\t\tlet weight = 0;\n\n\t\t\tfor (let bandFrame = fromFrame; bandFrame < toFrame; bandFrame += 1) {\n\t\t\t\tweight += weightOf(levels[bandFrame * bandCount + band] ?? 0);\n\t\t\t}\n\t\t\tweight *= gains[band] ?? 1;\n\t\t\tif (contrast !== 1) weight **= contrast;\n\t\t\tred += weight * colour[0];\n\t\t\tgreen += weight * colour[1];\n\t\t\tblue += weight * colour[2];\n\t\t\ttotal += weight;\n\t\t}\n\t\tif (total === 0) return;\n\n\t\tconst full = 255 / Math.max(red, green, blue, Number.MIN_VALUE);\n\t\tconst scale = full * amount + (1 - amount) / total;\n\n\t\treturn [red * scale, green * scale, blue * scale, 255];\n\t};\n}\n\n// Keyed to absolute frames, as buckets are to pairs; keyed to the window, the colours shimmer as it scrolls\nexport function bandStrip(\n\ttable: BandTable,\n\tcolours: ReadonlyArray<BandChannels | undefined>,\n\tview: BandView,\n): BandStrip | undefined {\n\tif (view.pixelsPerSecond <= 0 || !colours.some(Boolean)) return undefined;\n\n\tconst { bandCount, framesPerSecond, levels } = table;\n\tconst mixGroup = groupMixer(table, colours, view.mix ?? { contrast: 1, normalize: 1, tilt: 0 });\n\tconst pxPerFrame = view.pixelsPerSecond / framesPerSecond;\n\tconst frames = Math.max(1, Math.round(1 / pxPerFrame));\n\tconst groupWidth = frames * pxPerFrame;\n\tconst openingFrame = view.startSeconds * framesPerSecond;\n\tconst groupsTotal = Math.ceil(levels.length / bandCount / frames);\n\t// A group past each end, so the smoothing at the image's own edges falls outside the window\n\tconst first = Math.max(0, Math.floor(openingFrame / frames) - 1);\n\tconst last = Math.min(\n\t\tgroupsTotal,\n\t\tMath.ceil((openingFrame + view.width / pxPerFrame) / frames) + 1,\n\t);\n\tconst count = last - first;\n\tif (count < 1) return undefined;\n\n\tconst pixels = new Uint8ClampedArray(count * 4);\n\n\tfor (let index = 0; index < count; index += 1) {\n\t\tconst fromFrame = (first + index) * frames;\n\t\tconst mixed = mixGroup(fromFrame, fromFrame + frames);\n\n\t\tif (mixed) pixels.set(mixed, index * 4);\n\t}\n\n\treturn { count, groupWidth, pixels, x: (first * frames - openingFrame) * pxPerFrame };\n}\n"],"mappings":"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"}
|
|
@@ -9,7 +9,10 @@ export declare const waveformColours: {
|
|
|
9
9
|
readonly wave: "--_sonic-waveform-wave";
|
|
10
10
|
};
|
|
11
11
|
export declare const waveformNumbers: {
|
|
12
|
+
readonly bandContrast: "--_sonic-waveform-band-contrast";
|
|
13
|
+
readonly bandNormalize: "--_sonic-waveform-band-normalize";
|
|
12
14
|
readonly bandOpacity: "--_sonic-waveform-band-opacity";
|
|
15
|
+
readonly bandTilt: "--_sonic-waveform-band-tilt";
|
|
13
16
|
readonly edgeShade: "--_sonic-waveform-edge-shade";
|
|
14
17
|
};
|
|
15
18
|
//#endregion
|
|
@@ -14,7 +14,10 @@ const waveformColours = {
|
|
|
14
14
|
wave: "--_sonic-waveform-wave"
|
|
15
15
|
};
|
|
16
16
|
const waveformNumbers = {
|
|
17
|
+
bandContrast: "--_sonic-waveform-band-contrast",
|
|
18
|
+
bandNormalize: "--_sonic-waveform-band-normalize",
|
|
17
19
|
bandOpacity: "--_sonic-waveform-band-opacity",
|
|
20
|
+
bandTilt: "--_sonic-waveform-band-tilt",
|
|
18
21
|
edgeShade: "--_sonic-waveform-edge-shade"
|
|
19
22
|
};
|
|
20
23
|
const amplitudeMargin = .94;
|
|
@@ -71,7 +74,15 @@ function bandChannels(strip, colour) {
|
|
|
71
74
|
];
|
|
72
75
|
}
|
|
73
76
|
function paintTint(context, scene, tint) {
|
|
74
|
-
const
|
|
77
|
+
const { bandContrast, bandNormalize, bandTilt } = scene.numbers;
|
|
78
|
+
const strip = bandStrip(tint.table, tint.colours, {
|
|
79
|
+
...scene,
|
|
80
|
+
mix: {
|
|
81
|
+
contrast: bandContrast,
|
|
82
|
+
normalize: bandNormalize,
|
|
83
|
+
tilt: bandTilt
|
|
84
|
+
}
|
|
85
|
+
});
|
|
75
86
|
if (!strip) return;
|
|
76
87
|
const { canvas } = tint.strip;
|
|
77
88
|
const { count } = strip;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waveform-scene.js","names":[],"sources":["../../../src/lib/waveform-scene.ts"],"sourcesContent":["import type { View } from '#lib/frame-timeline.ts';\nimport type { BandChannels, BandTable } from '#lib/waveform-bands.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { isKind } from '#lib/marker-band.ts';\nimport { clamp } from '#lib/math.ts';\nimport { bandStrip } from '#lib/waveform-bands.ts';\nimport { waveformBuckets } from '#lib/waveform-buckets.ts';\n\ntype Colour = keyof typeof waveformColours;\n\ntype Numeric = keyof typeof waveformNumbers;\n\ninterface Scene extends View {\n\tcolours: Record<Colour, string>;\n\tnumbers: Record<Numeric, number>;\n}\n\nexport interface WaveformTint {\n\tcolours: ReadonlyArray<BandChannels | undefined>;\n\tstrip: CanvasRenderingContext2D;\n\ttable: BandTable;\n}\n\ninterface Drawn {\n\tbands: undefined | WaveformTint;\n\tkindColours: ReadonlyMap<string, string>;\n\tmarkers: ReadonlyArray<{ dimmed?: boolean; kind?: string; start: number }>;\n\tpeaks: undefined | WaveformPeaks;\n\trange: [number, number];\n}\n\nexport const waveformColours = {\n\tends: '--_sonic-waveform-ends',\n\tgrid: '--_sonic-waveform-grid',\n\tmarker: '--_sonic-waveform-marker',\n\tplaceholder: '--_sonic-waveform-placeholder',\n\tshade: '--_sonic-waveform-shade',\n\tshadeClear: '--_sonic-waveform-shade-clear',\n\twave: '--_sonic-waveform-wave',\n} as const;\n\nexport const waveformNumbers = {\n\tbandOpacity: '--_sonic-waveform-band-opacity',\n\tedgeShade: '--_sonic-waveform-edge-shade',\n} as const;\n\nconst amplitudeMargin = 0.94;\n\nconst hatchPitchPx = 8;\nconst placeholderWavelengthPx = 48;\nconst placeholderAmplitude = 0.2;\n\nfunction paintHatch(\n\tcontext: CanvasRenderingContext2D,\n\tscene: Scene,\n\t[from, to]: [number, number],\n): void {\n\tif (to <= from) return;\n\n\tconst { dpr, height } = scene;\n\tconst pitch = Math.max(2, Math.round(hatchPitchPx * dpr));\n\tconst offset = scene.startSeconds * scene.pixelsPerSecond;\n\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, height);\n\tcontext.clip();\n\tcontext.beginPath();\n\tfor (let x = Math.floor((from - height + offset) / pitch) * pitch - offset; x < to; x += pitch) {\n\t\tcontext.moveTo(x, height);\n\t\tcontext.lineTo(x + height, 0);\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.ends;\n\tcontext.stroke();\n\tcontext.restore();\n}\n\nfunction paintPlaceholders(context: CanvasRenderingContext2D, scene: Scene): void {\n\tif (scene.pending.length === 0) return;\n\n\tconst { dpr, height, pixelsPerSecond, startSeconds } = scene;\n\tconst wavelength = placeholderWavelengthPx * dpr;\n\tconst center = height / 2;\n\tconst origin = startSeconds * pixelsPerSecond;\n\tconst waveY = (x: number): number =>\n\t\tcenter -\n\t\tMath.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) *\n\t\t\tcenter *\n\t\t\tplaceholderAmplitude;\n\n\tcontext.beginPath();\n\tfor (const [from, to] of scene.pending) {\n\t\tconst opening = (from - startSeconds) * pixelsPerSecond;\n\t\tconst closing = (to - startSeconds) * pixelsPerSecond;\n\n\t\tcontext.moveTo(opening, waveY(opening));\n\t\tfor (let x = opening + dpr; x < closing; x += dpr) context.lineTo(x, waveY(x));\n\t\tcontext.lineTo(closing, waveY(closing));\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.placeholder;\n\tcontext.stroke();\n}\n\n// A painted pixel is the one reading that resolves any colour syntax to channels\nexport function bandChannels(\n\tstrip: CanvasRenderingContext2D,\n\tcolour: string,\n): BandChannels | undefined {\n\tstrip.clearRect(0, 0, 1, 1);\n\tstrip.fillStyle = colour;\n\tstrip.fillRect(0, 0, 1, 1);\n\n\tconst [red = 0, green = 0, blue = 0, alpha = 0] = strip.getImageData(0, 0, 1, 1).data;\n\n\treturn alpha === 0 ? undefined : [red, green, blue];\n}\n\nfunction paintTint(context: CanvasRenderingContext2D, scene: Scene, tint: WaveformTint): void {\n\tconst strip = bandStrip(tint.table, tint.colours, scene);\n\tif (!strip) return;\n\n\tconst { canvas } = tint.strip;\n\tconst { count } = strip;\n\n\tif (canvas.width < count) canvas.width = count;\n\ttint.strip.putImageData(new ImageData(strip.pixels, count, 1), 0, 0);\n\tcontext.globalAlpha = scene.numbers.bandOpacity;\n\tcontext.drawImage(canvas, 0, 0, count, 1, strip.x, 0, count * strip.groupWidth, scene.height);\n}\n\nfunction paintEdgeShade(context: CanvasRenderingContext2D, scene: Scene): void {\n\tconst { shade, shadeClear } = scene.colours;\n\tconst gradient = context.createLinearGradient(0, 0, 0, scene.height);\n\n\tgradient.addColorStop(0, shade);\n\tgradient.addColorStop(0.5, shadeClear);\n\tgradient.addColorStop(1, shade);\n\tcontext.globalAlpha = scene.numbers.edgeShade;\n\tcontext.fillStyle = gradient;\n\tcontext.fillRect(0, 0, scene.width, scene.height);\n}\n\nfunction paintWave(context: CanvasRenderingContext2D, scene: Scene, drawn: Drawn): void {\n\tconst buckets = drawn.peaks ? waveformBuckets(drawn.peaks, scene) : [];\n\tconst [first] = buckets;\n\tif (!first) return;\n\n\tconst center = scene.height / 2;\n\tconst amplitudePx = center * amplitudeMargin;\n\tconst { bandOpacity, edgeShade } = scene.numbers;\n\n\tcontext.fillStyle = scene.colours.wave;\n\tcontext.beginPath();\n\tcontext.moveTo(first.x, center);\n\tfor (const bucket of buckets) context.lineTo(bucket.x, center - bucket.high * amplitudePx);\n\tfor (const bucket of buckets.toReversed())\n\t\tcontext.lineTo(bucket.x, center - bucket.low * amplitudePx);\n\tcontext.closePath();\n\tcontext.fill();\n\t// The flat envelope is the mask: from here only the pixels it covers take colour\n\tcontext.globalCompositeOperation = 'source-atop';\n\tif (drawn.bands && bandOpacity > 0) paintTint(context, scene, drawn.bands);\n\tif (edgeShade > 0) paintEdgeShade(context, scene);\n\tcontext.globalAlpha = 1;\n}\n\nfunction paintMarkers(context: CanvasRenderingContext2D, scene: Scene, drawn: Drawn): void {\n\tconst line = Math.max(1, Math.round(scene.dpr));\n\n\tfor (const { dimmed, kind, start } of drawn.markers) {\n\t\tconst x = (start - scene.startSeconds) * scene.pixelsPerSecond;\n\n\t\tcontext.fillStyle =\n\t\t\t(isKind(kind) ? drawn.kindColours.get(kind) : undefined) ?? scene.colours.marker;\n\t\tcontext.globalAlpha = dimmed === true ? 0.5 : 1;\n\t\tcontext.fillRect(x - line / 2, 0, line, scene.height);\n\t}\n\tcontext.globalAlpha = 1;\n}\n\nexport function paintWaveform(context: CanvasRenderingContext2D, scene: Scene, drawn: Drawn): void {\n\tconst { dpr, height, pixelsPerSecond, startSeconds, width } = scene;\n\tconst x = (seconds: number): number => (seconds - startSeconds) * pixelsPerSecond;\n\tconst { ends, grid } = scene.colours;\n\tconst line = Math.max(1, Math.round(dpr));\n\tconst [opening, closing] = [x(drawn.range[0]), x(drawn.range[1])];\n\tconst [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];\n\n\tcontext.clearRect(0, 0, width, height);\n\tpaintWave(context, scene, drawn);\n\t// Laid under the wave, so the nearest is painted first\n\tcontext.globalCompositeOperation = 'destination-over';\n\tpaintPlaceholders(context, scene);\n\tcontext.fillStyle = grid;\n\tif (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);\n\tpaintHatch(context, scene, [0, from]);\n\tpaintHatch(context, scene, [to, width]);\n\tcontext.globalCompositeOperation = 'source-over';\n\t// Scrolling lines sit at fractional x; rounding them judders\n\tcontext.fillStyle = ends;\n\tfor (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);\n\tpaintMarkers(context, scene, drawn);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"waveform-scene.js","names":[],"sources":["../../../src/lib/waveform-scene.ts"],"sourcesContent":["import type { View } from '#lib/frame-timeline.ts';\nimport type { BandChannels, BandTable } from '#lib/waveform-bands.ts';\nimport type { WaveformPeaks } from '#lib/waveform-buckets.ts';\n\nimport { isKind } from '#lib/marker-band.ts';\nimport { clamp } from '#lib/math.ts';\nimport { bandStrip } from '#lib/waveform-bands.ts';\nimport { waveformBuckets } from '#lib/waveform-buckets.ts';\n\ntype Colour = keyof typeof waveformColours;\n\ntype Numeric = keyof typeof waveformNumbers;\n\ninterface Scene extends View {\n\tcolours: Record<Colour, string>;\n\tnumbers: Record<Numeric, number>;\n}\n\nexport interface WaveformTint {\n\tcolours: ReadonlyArray<BandChannels | undefined>;\n\tstrip: CanvasRenderingContext2D;\n\ttable: BandTable;\n}\n\ninterface Drawn {\n\tbands: undefined | WaveformTint;\n\tkindColours: ReadonlyMap<string, string>;\n\tmarkers: ReadonlyArray<{ dimmed?: boolean; kind?: string; start: number }>;\n\tpeaks: undefined | WaveformPeaks;\n\trange: [number, number];\n}\n\nexport const waveformColours = {\n\tends: '--_sonic-waveform-ends',\n\tgrid: '--_sonic-waveform-grid',\n\tmarker: '--_sonic-waveform-marker',\n\tplaceholder: '--_sonic-waveform-placeholder',\n\tshade: '--_sonic-waveform-shade',\n\tshadeClear: '--_sonic-waveform-shade-clear',\n\twave: '--_sonic-waveform-wave',\n} as const;\n\nexport const waveformNumbers = {\n\tbandContrast: '--_sonic-waveform-band-contrast',\n\tbandNormalize: '--_sonic-waveform-band-normalize',\n\tbandOpacity: '--_sonic-waveform-band-opacity',\n\tbandTilt: '--_sonic-waveform-band-tilt',\n\tedgeShade: '--_sonic-waveform-edge-shade',\n} as const;\n\nconst amplitudeMargin = 0.94;\n\nconst hatchPitchPx = 8;\nconst placeholderWavelengthPx = 48;\nconst placeholderAmplitude = 0.2;\n\nfunction paintHatch(\n\tcontext: CanvasRenderingContext2D,\n\tscene: Scene,\n\t[from, to]: [number, number],\n): void {\n\tif (to <= from) return;\n\n\tconst { dpr, height } = scene;\n\tconst pitch = Math.max(2, Math.round(hatchPitchPx * dpr));\n\tconst offset = scene.startSeconds * scene.pixelsPerSecond;\n\n\tcontext.save();\n\tcontext.beginPath();\n\tcontext.rect(from, 0, to - from, height);\n\tcontext.clip();\n\tcontext.beginPath();\n\tfor (let x = Math.floor((from - height + offset) / pitch) * pitch - offset; x < to; x += pitch) {\n\t\tcontext.moveTo(x, height);\n\t\tcontext.lineTo(x + height, 0);\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.ends;\n\tcontext.stroke();\n\tcontext.restore();\n}\n\nfunction paintPlaceholders(context: CanvasRenderingContext2D, scene: Scene): void {\n\tif (scene.pending.length === 0) return;\n\n\tconst { dpr, height, pixelsPerSecond, startSeconds } = scene;\n\tconst wavelength = placeholderWavelengthPx * dpr;\n\tconst center = height / 2;\n\tconst origin = startSeconds * pixelsPerSecond;\n\tconst waveY = (x: number): number =>\n\t\tcenter -\n\t\tMath.sin(((x + origin) / wavelength + scene.phase) * 2 * Math.PI) *\n\t\t\tcenter *\n\t\t\tplaceholderAmplitude;\n\n\tcontext.beginPath();\n\tfor (const [from, to] of scene.pending) {\n\t\tconst opening = (from - startSeconds) * pixelsPerSecond;\n\t\tconst closing = (to - startSeconds) * pixelsPerSecond;\n\n\t\tcontext.moveTo(opening, waveY(opening));\n\t\tfor (let x = opening + dpr; x < closing; x += dpr) context.lineTo(x, waveY(x));\n\t\tcontext.lineTo(closing, waveY(closing));\n\t}\n\tcontext.lineWidth = Math.max(1, Math.round(dpr));\n\tcontext.strokeStyle = scene.colours.placeholder;\n\tcontext.stroke();\n}\n\n// A painted pixel is the one reading that resolves any colour syntax to channels\nexport function bandChannels(\n\tstrip: CanvasRenderingContext2D,\n\tcolour: string,\n): BandChannels | undefined {\n\tstrip.clearRect(0, 0, 1, 1);\n\tstrip.fillStyle = colour;\n\tstrip.fillRect(0, 0, 1, 1);\n\n\tconst [red = 0, green = 0, blue = 0, alpha = 0] = strip.getImageData(0, 0, 1, 1).data;\n\n\treturn alpha === 0 ? undefined : [red, green, blue];\n}\n\nfunction paintTint(context: CanvasRenderingContext2D, scene: Scene, tint: WaveformTint): void {\n\tconst { bandContrast, bandNormalize, bandTilt } = scene.numbers;\n\tconst strip = bandStrip(tint.table, tint.colours, {\n\t\t...scene,\n\t\tmix: { contrast: bandContrast, normalize: bandNormalize, tilt: bandTilt },\n\t});\n\tif (!strip) return;\n\n\tconst { canvas } = tint.strip;\n\tconst { count } = strip;\n\n\tif (canvas.width < count) canvas.width = count;\n\ttint.strip.putImageData(new ImageData(strip.pixels, count, 1), 0, 0);\n\tcontext.globalAlpha = scene.numbers.bandOpacity;\n\tcontext.drawImage(canvas, 0, 0, count, 1, strip.x, 0, count * strip.groupWidth, scene.height);\n}\n\nfunction paintEdgeShade(context: CanvasRenderingContext2D, scene: Scene): void {\n\tconst { shade, shadeClear } = scene.colours;\n\tconst gradient = context.createLinearGradient(0, 0, 0, scene.height);\n\n\tgradient.addColorStop(0, shade);\n\tgradient.addColorStop(0.5, shadeClear);\n\tgradient.addColorStop(1, shade);\n\tcontext.globalAlpha = scene.numbers.edgeShade;\n\tcontext.fillStyle = gradient;\n\tcontext.fillRect(0, 0, scene.width, scene.height);\n}\n\nfunction paintWave(context: CanvasRenderingContext2D, scene: Scene, drawn: Drawn): void {\n\tconst buckets = drawn.peaks ? waveformBuckets(drawn.peaks, scene) : [];\n\tconst [first] = buckets;\n\tif (!first) return;\n\n\tconst center = scene.height / 2;\n\tconst amplitudePx = center * amplitudeMargin;\n\tconst { bandOpacity, edgeShade } = scene.numbers;\n\n\tcontext.fillStyle = scene.colours.wave;\n\tcontext.beginPath();\n\tcontext.moveTo(first.x, center);\n\tfor (const bucket of buckets) context.lineTo(bucket.x, center - bucket.high * amplitudePx);\n\tfor (const bucket of buckets.toReversed())\n\t\tcontext.lineTo(bucket.x, center - bucket.low * amplitudePx);\n\tcontext.closePath();\n\tcontext.fill();\n\t// The flat envelope is the mask: from here only the pixels it covers take colour\n\tcontext.globalCompositeOperation = 'source-atop';\n\tif (drawn.bands && bandOpacity > 0) paintTint(context, scene, drawn.bands);\n\tif (edgeShade > 0) paintEdgeShade(context, scene);\n\tcontext.globalAlpha = 1;\n}\n\nfunction paintMarkers(context: CanvasRenderingContext2D, scene: Scene, drawn: Drawn): void {\n\tconst line = Math.max(1, Math.round(scene.dpr));\n\n\tfor (const { dimmed, kind, start } of drawn.markers) {\n\t\tconst x = (start - scene.startSeconds) * scene.pixelsPerSecond;\n\n\t\tcontext.fillStyle =\n\t\t\t(isKind(kind) ? drawn.kindColours.get(kind) : undefined) ?? scene.colours.marker;\n\t\tcontext.globalAlpha = dimmed === true ? 0.5 : 1;\n\t\tcontext.fillRect(x - line / 2, 0, line, scene.height);\n\t}\n\tcontext.globalAlpha = 1;\n}\n\nexport function paintWaveform(context: CanvasRenderingContext2D, scene: Scene, drawn: Drawn): void {\n\tconst { dpr, height, pixelsPerSecond, startSeconds, width } = scene;\n\tconst x = (seconds: number): number => (seconds - startSeconds) * pixelsPerSecond;\n\tconst { ends, grid } = scene.colours;\n\tconst line = Math.max(1, Math.round(dpr));\n\tconst [opening, closing] = [x(drawn.range[0]), x(drawn.range[1])];\n\tconst [from, to] = [clamp(opening, 0, width), clamp(closing, 0, width)];\n\n\tcontext.clearRect(0, 0, width, height);\n\tpaintWave(context, scene, drawn);\n\t// Laid under the wave, so the nearest is painted first\n\tcontext.globalCompositeOperation = 'destination-over';\n\tpaintPlaceholders(context, scene);\n\tcontext.fillStyle = grid;\n\tif (to > from) context.fillRect(from, Math.round((height - line) / 2), to - from, line);\n\tpaintHatch(context, scene, [0, from]);\n\tpaintHatch(context, scene, [to, width]);\n\tcontext.globalCompositeOperation = 'source-over';\n\t// Scrolling lines sit at fractional x; rounding them judders\n\tcontext.fillStyle = ends;\n\tfor (const edge of [opening, closing]) context.fillRect(edge - line / 2, 0, line, height);\n\tpaintMarkers(context, scene, drawn);\n}\n"],"mappings":"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"}
|
package/dist/styles/waveform.css
CHANGED
|
@@ -54,6 +54,24 @@
|
|
|
54
54
|
initial-value: 1;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
@property --_sonic-waveform-band-normalize {
|
|
58
|
+
syntax: '<number>';
|
|
59
|
+
inherits: true;
|
|
60
|
+
initial-value: 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@property --_sonic-waveform-band-contrast {
|
|
64
|
+
syntax: '<number>';
|
|
65
|
+
inherits: true;
|
|
66
|
+
initial-value: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@property --_sonic-waveform-band-tilt {
|
|
70
|
+
syntax: '<number>';
|
|
71
|
+
inherits: true;
|
|
72
|
+
initial-value: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
@property --_sonic-waveform-grid {
|
|
58
76
|
syntax: '<color>';
|
|
59
77
|
inherits: true;
|
|
@@ -122,6 +140,9 @@
|
|
|
122
140
|
--_sonic-waveform-band-opacity: calc(
|
|
123
141
|
var(--sonic-waveform-band-opacity, 1) * var(--_sonic-waveform-band-dim, 1)
|
|
124
142
|
);
|
|
143
|
+
--_sonic-waveform-band-normalize: var(--sonic-waveform-band-normalize, 1);
|
|
144
|
+
--_sonic-waveform-band-contrast: var(--sonic-waveform-band-contrast, 1);
|
|
145
|
+
--_sonic-waveform-band-tilt: var(--sonic-waveform-band-tilt, 0);
|
|
125
146
|
--_sonic-waveform-grid: var(--sonic-waveform-grid, var(--_sonic-etch));
|
|
126
147
|
--_sonic-waveform-ends: var(--sonic-waveform-ends, var(--_sonic-etch));
|
|
127
148
|
--_sonic-waveform-placeholder: var(--sonic-waveform-placeholder, var(--_sonic-etch));
|