wavesurfer.js 7.0.0-alpha.46 → 7.0.0-alpha.48
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/README.md +30 -9
- package/dist/decoder.d.ts +1 -1
- package/dist/decoder.js +1 -1
- package/dist/fetcher.d.ts +4 -3
- package/dist/fetcher.js +5 -4
- package/dist/plugins/envelope.d.ts +16 -10
- package/dist/plugins/envelope.js +178 -167
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/minimap.js +1 -0
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/multitrack.min.js +1 -1
- package/dist/plugins/regions.d.ts +1 -1
- package/dist/plugins/regions.js +4 -4
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/timeline.js +4 -2
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/renderer.d.ts +3 -0
- package/dist/renderer.js +40 -5
- package/dist/wavesurfer.d.ts +2 -2
- package/dist/wavesurfer.js +10 -3
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Or, as a script tag which exposes `WaveSurfer` as a global variable:
|
|
|
22
22
|
|
|
23
23
|
To import a plugin, e.g. the Timeline plugin:
|
|
24
24
|
```
|
|
25
|
-
import Timeline from 'https://unpkg.com/wavesurfer.js@alpha/plugins/timeline.js'
|
|
25
|
+
import Timeline from 'https://unpkg.com/wavesurfer.js@alpha/dist/plugins/timeline.js'
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
TypeScript types are included in the package, so there's no need to install `@types/wavesurfer.js`.
|
|
@@ -38,12 +38,33 @@ wavesurfer.js v7 (aka wavesurfer.ts) brings several improvements:
|
|
|
38
38
|
## Plugins
|
|
39
39
|
The "official" plugins have been completely rewritten and enhanced:
|
|
40
40
|
|
|
41
|
-
* [Regions](https://wavesurfer
|
|
42
|
-
* [Timeline](https://wavesurfer
|
|
43
|
-
* [Minimap](https://wavesurfer
|
|
44
|
-
* [Envelope](https://wavesurfer
|
|
45
|
-
* [Record](https://wavesurfer
|
|
46
|
-
* [Spectrogram](https://wavesurfer
|
|
41
|
+
* [Regions](https://wavesurfer.pages.dev/examples/#regions.js) – now also replaces the old Markers plugin
|
|
42
|
+
* [Timeline](https://wavesurfer.pages.dev/examples/#timeline.js) – displays notches and time labels below the waveform
|
|
43
|
+
* [Minimap](https://wavesurfer.pages.dev/examples/#minimap.js) – a small waveform that serves as a scrollbar for the main waveform
|
|
44
|
+
* [Envelope](https://wavesurfer.pages.dev/examples/#envelope.js) – a graphical interface to add fade-in and -out effects and control volume
|
|
45
|
+
* [Record](https://wavesurfer.pages.dev/examples/#record.js) – records audio from the microphone and renders a waveform
|
|
46
|
+
* [Spectrogram](https://wavesurfer.pages.dev/examples/#spectrogram.js) – visualization of an audio frequency spectrum
|
|
47
|
+
|
|
48
|
+
## CSS styling
|
|
49
|
+
|
|
50
|
+
wavesurfer.js v7 is rendered into a Shadow DOM tree. This isolates its CSS from the rest of the web page.
|
|
51
|
+
However, it's still possible to style various wavesurfer.js elements via CSS using the `::part()` pseudo-selector.
|
|
52
|
+
For example:
|
|
53
|
+
|
|
54
|
+
```css
|
|
55
|
+
#waveform ::part(cursor):before {
|
|
56
|
+
content: '🏄';
|
|
57
|
+
}
|
|
58
|
+
#waveform ::part(region) {
|
|
59
|
+
font-family: fantasy;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You can see which elements you can style in the DOM inspector – they will have a `part` attribute.
|
|
64
|
+
|
|
65
|
+
<img width="466" alt="DOM inspector screenshot" src="https://github.com/katspaugh/wavesurfer.ts/assets/381895/fcfb4e4d-9572-4931-811f-9615b7e3aa85">
|
|
66
|
+
|
|
67
|
+
See [this example](https://wavesurfer.pages.dev/examples/#styling.js) for play around with styling.
|
|
47
68
|
|
|
48
69
|
## Documentation
|
|
49
70
|
See the documentation on wavesurfer.js [methods](https://wavesurfer-ts.pages.dev/docs/classes/wavesurfer.WaveSurfer), [options](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferOptions) and [events](https://wavesurfer-ts.pages.dev/docs/types/wavesurfer.WaveSurferEvents) on our website.
|
|
@@ -53,7 +74,7 @@ See the documentation on wavesurfer.js [methods](https://wavesurfer-ts.pages.dev
|
|
|
53
74
|
Most options, events, and methods are similar to those in previous versions.
|
|
54
75
|
|
|
55
76
|
### Notable differences
|
|
56
|
-
* The `backend` option is removed – HTML5 audio (or video) is the only playback mechanism. However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer
|
|
77
|
+
* The `backend` option is removed – HTML5 audio (or video) is the only playback mechanism. However, you can still connect wavesurfer to Web Audio via `MediaElementSourceNode`. See this [example](https://wavesurfer.pages.dev/examples/#webaudio.js).
|
|
57
78
|
* The Markers plugin is removed – use the Regions plugin with `startTime` equal to `endTime`.
|
|
58
79
|
* No Microphone plugn – superseded by the new Record plugin with more features.
|
|
59
80
|
* No Cursor and Playhead plugins yet – to be done.
|
|
@@ -68,7 +89,7 @@ Most options, events, and methods are similar to those in previous versions.
|
|
|
68
89
|
* `toggleMute` is now called `setMuted(true | false)`
|
|
69
90
|
* `setHeight`, `setWaveColor`, `setCursorColor`, etc. – use `setOptions` with the corresponding params instead. E.g., `wavesurfer.setOptions({ height: 300, waveColor: '#abc' })`
|
|
70
91
|
|
|
71
|
-
See the complete [documentation of the new API](https://wavesurfer-ts.pages.dev/docs/
|
|
92
|
+
See the complete [documentation of the new API](https://wavesurfer-ts.pages.dev/docs/modules/wavesurfer).
|
|
72
93
|
|
|
73
94
|
## Development
|
|
74
95
|
|
package/dist/decoder.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Decode an array buffer into an audio buffer */
|
|
2
|
-
declare function decode(audioData: ArrayBuffer, sampleRate
|
|
2
|
+
declare function decode(audioData: ArrayBuffer, sampleRate: number): Promise<AudioBuffer>;
|
|
3
3
|
/** Create an audio buffer from pre-decoded audio data */
|
|
4
4
|
declare function createBuffer(channelData: Float32Array[] | Array<number[]>, duration: number): AudioBuffer;
|
|
5
5
|
declare const Decoder: {
|
package/dist/decoder.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Decode an array buffer into an audio buffer */
|
|
2
|
-
async function decode(audioData, sampleRate
|
|
2
|
+
async function decode(audioData, sampleRate) {
|
|
3
3
|
const audioCtx = new AudioContext({ sampleRate });
|
|
4
4
|
const decode = audioCtx.decodeAudioData(audioData);
|
|
5
5
|
decode.finally(() => audioCtx.close());
|
package/dist/fetcher.d.ts
CHANGED
package/dist/fetcher.js
CHANGED
|
@@ -25,14 +25,15 @@ declare const defaultOptions: {
|
|
|
25
25
|
dragPointFill: string;
|
|
26
26
|
dragPointStroke: string;
|
|
27
27
|
};
|
|
28
|
+
type Options = EnvelopePluginOptions & typeof defaultOptions;
|
|
28
29
|
export type EnvelopePluginEvents = {
|
|
29
30
|
'fade-in-change': [time: number];
|
|
30
31
|
'fade-out-change': [time: number];
|
|
31
32
|
'volume-change': [volume: number];
|
|
32
33
|
};
|
|
33
34
|
declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePluginOptions> {
|
|
34
|
-
protected options:
|
|
35
|
-
private
|
|
35
|
+
protected options: Options;
|
|
36
|
+
private polyline;
|
|
36
37
|
private audioContext;
|
|
37
38
|
private gainNode;
|
|
38
39
|
private volume;
|
|
@@ -41,24 +42,29 @@ declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePl
|
|
|
41
42
|
private readonly naturalVolumeExponent;
|
|
42
43
|
constructor(options: EnvelopePluginOptions);
|
|
43
44
|
static create(options: EnvelopePluginOptions): EnvelopePlugin;
|
|
45
|
+
destroy(): void;
|
|
44
46
|
/** Called by wavesurfer, don't call manually */
|
|
45
47
|
onInit(): void;
|
|
46
|
-
private makeDraggable;
|
|
47
|
-
private renderPolyline;
|
|
48
48
|
private initSvg;
|
|
49
|
-
|
|
49
|
+
private renderPolyline;
|
|
50
50
|
private initWebAudio;
|
|
51
51
|
private invertNaturalVolume;
|
|
52
52
|
private naturalVolume;
|
|
53
53
|
private setGainValue;
|
|
54
|
-
private onVolumeDrag;
|
|
55
54
|
private initFadeEffects;
|
|
56
55
|
/** Get the current audio volume */
|
|
57
56
|
getCurrentVolume(): number;
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Set the fade-in start time.
|
|
59
|
+
* @param time The time (in seconds) to set the fade-in start time to
|
|
60
|
+
* @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
|
|
61
|
+
*/
|
|
62
|
+
setStartTime(time: number, moveFadeInEnd?: boolean): void;
|
|
63
|
+
/** Set the fade-in end time.
|
|
64
|
+
* @param time The time (in seconds) to set the fade-in end time to
|
|
65
|
+
* @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
|
|
66
|
+
*/
|
|
67
|
+
setEndTime(time: number, moveFadeOutStart?: boolean): void;
|
|
62
68
|
/** Set the volume of the audio */
|
|
63
69
|
setVolume(volume: number): void;
|
|
64
70
|
}
|
package/dist/plugins/envelope.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
|
|
3
3
|
*/
|
|
4
4
|
import BasePlugin from '../base-plugin.js';
|
|
5
|
+
import EventEmitter from '../event-emitter.js';
|
|
5
6
|
const defaultOptions = {
|
|
6
7
|
fadeInStart: 0,
|
|
7
8
|
fadeOutEnd: 0,
|
|
@@ -13,58 +14,85 @@ const defaultOptions = {
|
|
|
13
14
|
dragPointFill: 'rgba(255, 255, 255, 0.8)',
|
|
14
15
|
dragPointStroke: 'rgba(255, 255, 255, 0.8)',
|
|
15
16
|
};
|
|
16
|
-
class
|
|
17
|
-
constructor(options) {
|
|
18
|
-
super(
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
class Polyline extends EventEmitter {
|
|
18
|
+
constructor(options, wrapper) {
|
|
19
|
+
super();
|
|
20
|
+
this.top = 0;
|
|
21
|
+
// An padding to make the envelope fit into the SVG
|
|
22
|
+
this.padding = options.dragPointSize / 2 + 1;
|
|
23
|
+
// SVG element
|
|
24
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
25
|
+
svg.setAttribute('width', '100%');
|
|
26
|
+
svg.setAttribute('height', '100%');
|
|
27
|
+
svg.setAttribute('viewBox', `0 0 ${wrapper.clientWidth} ${wrapper.clientHeight}`);
|
|
28
|
+
svg.setAttribute('preserveAspectRatio', 'none');
|
|
29
|
+
svg.setAttribute('style', 'position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;');
|
|
30
|
+
svg.setAttribute('part', 'envelope');
|
|
31
|
+
this.svg = svg;
|
|
32
|
+
// A polyline representing the envelope
|
|
33
|
+
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
34
|
+
polyline.setAttribute('points', '0,0 0,0 0,0 0,0');
|
|
35
|
+
polyline.setAttribute('stroke', options.lineColor);
|
|
36
|
+
polyline.setAttribute('stroke-width', options.lineWidth);
|
|
37
|
+
polyline.setAttribute('fill', 'none');
|
|
38
|
+
polyline.setAttribute('style', 'pointer-events: none;');
|
|
39
|
+
polyline.setAttribute('part', 'polyline');
|
|
40
|
+
svg.appendChild(polyline);
|
|
41
|
+
// Draggable top line
|
|
42
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
43
|
+
line.setAttribute('stroke', 'transparent');
|
|
44
|
+
line.setAttribute('stroke-width', (options.lineWidth * 3).toString());
|
|
45
|
+
line.setAttribute('style', 'cursor: ns-resize; pointer-events: all;');
|
|
46
|
+
line.setAttribute('part', 'line');
|
|
47
|
+
svg.appendChild(line);
|
|
48
|
+
[0, 1].forEach(() => {
|
|
49
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
50
|
+
circle.setAttribute('r', (options.dragPointSize / 2).toString());
|
|
51
|
+
circle.setAttribute('fill', options.dragPointFill);
|
|
52
|
+
circle.setAttribute('stroke', options.dragPointStroke || options.dragPointFill);
|
|
53
|
+
circle.setAttribute('stroke-width', '2');
|
|
54
|
+
circle.setAttribute('style', 'cursor: ew-resize; pointer-events: all;');
|
|
55
|
+
circle.setAttribute('part', 'circle');
|
|
56
|
+
svg.appendChild(circle);
|
|
57
|
+
});
|
|
58
|
+
wrapper.appendChild(svg);
|
|
59
|
+
// Init dtagging
|
|
60
|
+
{
|
|
61
|
+
// On top line drag
|
|
62
|
+
const onDragY = (dy) => {
|
|
63
|
+
const newTop = this.top + dy;
|
|
64
|
+
const { height } = svg.viewBox.baseVal;
|
|
65
|
+
if (newTop < -0.5 || newTop > height)
|
|
66
|
+
return;
|
|
67
|
+
const relativeY = Math.min(1, Math.max(0, (height - newTop) / height));
|
|
68
|
+
this.emit('line-move', relativeY);
|
|
69
|
+
};
|
|
70
|
+
// On points drag
|
|
71
|
+
const onDragX = (index, dx) => {
|
|
72
|
+
const point = polyline.points.getItem(index);
|
|
73
|
+
const newX = point.x + dx;
|
|
74
|
+
const { width } = svg.viewBox.baseVal;
|
|
75
|
+
this.emit('point-move', index, newX / width);
|
|
76
|
+
};
|
|
77
|
+
// Draggable top line of the polyline
|
|
78
|
+
this.makeDraggable(line, (_, dy) => onDragY(dy));
|
|
79
|
+
// Make each point draggable
|
|
80
|
+
const draggables = this.svg.querySelectorAll('circle');
|
|
81
|
+
Array.from(draggables).forEach((draggable, index) => {
|
|
82
|
+
this.makeDraggable(draggable, (dx) => onDragX(index + 1, dx));
|
|
83
|
+
});
|
|
40
84
|
}
|
|
41
|
-
this.subscriptions.push(this.wavesurfer.once('decode', (duration) => {
|
|
42
|
-
this.options.fadeInStart = this.options.fadeInStart || 0;
|
|
43
|
-
this.options.fadeOutEnd = this.options.fadeOutEnd || duration;
|
|
44
|
-
this.options.fadeInEnd = this.options.fadeInEnd || this.options.fadeInStart;
|
|
45
|
-
this.options.fadeOutStart = this.options.fadeOutStart || this.options.fadeOutEnd;
|
|
46
|
-
this.initWebAudio();
|
|
47
|
-
this.initSvg();
|
|
48
|
-
this.initFadeEffects();
|
|
49
|
-
}));
|
|
50
|
-
let delay;
|
|
51
|
-
this.subscriptions.push(this.wavesurfer.on('zoom', () => {
|
|
52
|
-
if (delay)
|
|
53
|
-
clearTimeout(delay);
|
|
54
|
-
delay = setTimeout(() => {
|
|
55
|
-
this.svg?.remove();
|
|
56
|
-
this.initSvg();
|
|
57
|
-
}, 100);
|
|
58
|
-
}));
|
|
59
85
|
}
|
|
60
86
|
makeDraggable(draggable, onDrag) {
|
|
87
|
+
draggable.addEventListener('click', (e) => {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
});
|
|
61
91
|
draggable.addEventListener('mousedown', (e) => {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
e.stopPropagation();
|
|
62
94
|
let x = e.clientX;
|
|
63
95
|
let y = e.clientY;
|
|
64
|
-
const wasInteractive = this.wavesurfer?.options.interact || true;
|
|
65
|
-
let delay;
|
|
66
|
-
// Make the wavesurfer ignore clicks when we're dragging
|
|
67
|
-
this.wavesurfer?.toggleInteraction(false);
|
|
68
96
|
const move = (e) => {
|
|
69
97
|
const dx = e.clientX - x;
|
|
70
98
|
const dy = e.clientY - y;
|
|
@@ -75,145 +103,124 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
75
103
|
const up = () => {
|
|
76
104
|
document.removeEventListener('mousemove', move);
|
|
77
105
|
document.removeEventListener('mouseup', up);
|
|
78
|
-
// Restore interactive state
|
|
79
|
-
if (delay)
|
|
80
|
-
clearTimeout(delay);
|
|
81
|
-
delay = setTimeout(() => {
|
|
82
|
-
this.wavesurfer?.toggleInteraction(wasInteractive);
|
|
83
|
-
}, 100);
|
|
84
106
|
};
|
|
85
107
|
document.addEventListener('mousemove', move);
|
|
86
108
|
document.addEventListener('mouseup', up);
|
|
87
|
-
e.preventDefault();
|
|
88
|
-
e.stopPropagation();
|
|
89
109
|
});
|
|
90
110
|
}
|
|
91
|
-
|
|
92
|
-
if (!this.svg || !this.wavesurfer)
|
|
93
|
-
return;
|
|
94
|
-
const polyline = this.svg.querySelector('polyline');
|
|
95
|
-
const points = polyline.points;
|
|
111
|
+
update({ x1, x2, x3, x4, y }) {
|
|
96
112
|
const width = this.svg.clientWidth;
|
|
97
113
|
const height = this.svg.clientHeight;
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
114
|
+
this.top = height - y * height;
|
|
115
|
+
const top = Math.max(this.padding, Math.min(this.top, height - this.padding));
|
|
116
|
+
this.svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
117
|
+
const polyline = this.svg.querySelector('polyline');
|
|
118
|
+
const { points } = polyline;
|
|
119
|
+
points.getItem(0).x = x1 * width;
|
|
120
|
+
points.getItem(0).y = height;
|
|
121
|
+
points.getItem(1).x = x2 * width;
|
|
101
122
|
points.getItem(1).y = top;
|
|
123
|
+
points.getItem(2).x = x3 * width;
|
|
102
124
|
points.getItem(2).y = top;
|
|
103
|
-
points.getItem(
|
|
104
|
-
points.getItem(3).
|
|
125
|
+
points.getItem(3).x = x4 * width;
|
|
126
|
+
points.getItem(3).y = height;
|
|
105
127
|
const line = this.svg.querySelector('line');
|
|
106
128
|
line.setAttribute('x1', points.getItem(1).x.toString());
|
|
107
129
|
line.setAttribute('x2', points.getItem(2).x.toString());
|
|
108
130
|
line.setAttribute('y1', top.toString());
|
|
109
131
|
line.setAttribute('y2', top.toString());
|
|
110
132
|
const circles = this.svg.querySelectorAll('circle');
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const point = polyline.points.getItem(i + 1);
|
|
133
|
+
Array.from(circles).forEach((circle, i) => {
|
|
134
|
+
const point = points.getItem(i + 1);
|
|
114
135
|
circle.setAttribute('cx', point.x.toString());
|
|
115
|
-
circle.setAttribute('cy',
|
|
136
|
+
circle.setAttribute('cy', point.y.toString());
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
destroy() {
|
|
140
|
+
this.svg.remove();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
class EnvelopePlugin extends BasePlugin {
|
|
144
|
+
constructor(options) {
|
|
145
|
+
super(options);
|
|
146
|
+
this.polyline = null;
|
|
147
|
+
this.audioContext = null;
|
|
148
|
+
this.gainNode = null;
|
|
149
|
+
this.volume = 1;
|
|
150
|
+
this.isFadingIn = false;
|
|
151
|
+
this.isFadingOut = false;
|
|
152
|
+
// Adjust the exponent to change the curve of the volume control
|
|
153
|
+
this.naturalVolumeExponent = 1.5;
|
|
154
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
155
|
+
this.options.lineColor = this.options.lineColor || defaultOptions.lineColor;
|
|
156
|
+
this.options.dragPointFill = this.options.dragPointFill || defaultOptions.dragPointFill;
|
|
157
|
+
this.options.dragPointStroke = this.options.dragPointStroke || defaultOptions.dragPointStroke;
|
|
158
|
+
this.volume = this.options.volume ?? 1;
|
|
159
|
+
}
|
|
160
|
+
static create(options) {
|
|
161
|
+
return new EnvelopePlugin(options);
|
|
162
|
+
}
|
|
163
|
+
destroy() {
|
|
164
|
+
this.polyline?.destroy();
|
|
165
|
+
super.destroy();
|
|
166
|
+
}
|
|
167
|
+
/** Called by wavesurfer, don't call manually */
|
|
168
|
+
onInit() {
|
|
169
|
+
if (!this.wavesurfer) {
|
|
170
|
+
throw Error('WaveSurfer is not initialized');
|
|
116
171
|
}
|
|
172
|
+
this.initWebAudio();
|
|
173
|
+
this.initSvg();
|
|
174
|
+
this.initFadeEffects();
|
|
175
|
+
this.subscriptions.push(this.wavesurfer.on('decode', (duration) => {
|
|
176
|
+
this.options.fadeInStart = this.options.fadeInStart || 0;
|
|
177
|
+
this.options.fadeOutEnd = this.options.fadeOutEnd || duration;
|
|
178
|
+
this.options.fadeInEnd = this.options.fadeInEnd || this.options.fadeInStart;
|
|
179
|
+
this.options.fadeOutStart = this.options.fadeOutStart || this.options.fadeOutEnd;
|
|
180
|
+
this.renderPolyline();
|
|
181
|
+
}));
|
|
182
|
+
this.subscriptions.push(this.wavesurfer.on('redraw', () => this.renderPolyline()));
|
|
117
183
|
}
|
|
118
184
|
initSvg() {
|
|
119
185
|
if (!this.wavesurfer)
|
|
120
186
|
return;
|
|
121
187
|
const wrapper = this.wavesurfer.getWrapper();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
130
|
-
svg.setAttribute('preserveAspectRatio', 'none');
|
|
131
|
-
svg.setAttribute('style', 'position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;');
|
|
132
|
-
this.svg = svg;
|
|
133
|
-
// A polyline representing the envelope
|
|
134
|
-
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
135
|
-
polyline.setAttribute('points', '0,0 0,0 0,0 0,0');
|
|
136
|
-
polyline.setAttribute('stroke', this.options.lineColor);
|
|
137
|
-
polyline.setAttribute('stroke-width', this.options.lineWidth);
|
|
138
|
-
polyline.setAttribute('fill', 'none');
|
|
139
|
-
polyline.setAttribute('style', 'pointer-events: none');
|
|
140
|
-
svg.appendChild(polyline);
|
|
141
|
-
// Draggable top line
|
|
142
|
-
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
143
|
-
line.setAttribute('stroke', 'none');
|
|
144
|
-
line.setAttribute('stroke-width', (this.options.lineWidth * 3).toString());
|
|
145
|
-
line.setAttribute('style', 'cursor: ns-resize; pointer-events: all;');
|
|
146
|
-
svg.appendChild(line);
|
|
147
|
-
const points = polyline.points;
|
|
148
|
-
const offset = this.options.dragPointSize / 2;
|
|
149
|
-
points.getItem(0).x = (this.options.fadeInStart / duration) * width;
|
|
150
|
-
points.getItem(0).y = height;
|
|
151
|
-
points.getItem(1).x = (this.options.fadeInEnd / duration) * width;
|
|
152
|
-
points.getItem(1).y = 0;
|
|
153
|
-
points.getItem(2).x = (this.options.fadeOutStart / duration) * width;
|
|
154
|
-
points.getItem(2).y = 0;
|
|
155
|
-
points.getItem(3).x = (this.options.fadeOutEnd / duration) * width;
|
|
156
|
-
points.getItem(3).y = height;
|
|
157
|
-
// Drag points
|
|
158
|
-
const dragPoints = [1, 2];
|
|
159
|
-
dragPoints.forEach(() => {
|
|
160
|
-
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
161
|
-
circle.setAttribute('r', (this.options.dragPointSize / 2).toString());
|
|
162
|
-
circle.setAttribute('fill', this.options.dragPointFill);
|
|
163
|
-
circle.setAttribute('stroke', this.options.dragPointStroke || this.options.dragPointFill);
|
|
164
|
-
circle.setAttribute('stroke-width', '2');
|
|
165
|
-
circle.setAttribute('style', 'cursor: ew-resize; pointer-events: all;');
|
|
166
|
-
svg.appendChild(circle);
|
|
167
|
-
});
|
|
168
|
-
this.wavesurfer.getWrapper().appendChild(svg);
|
|
169
|
-
// Initial polyline
|
|
170
|
-
this.renderPolyline();
|
|
171
|
-
// On top line drag
|
|
172
|
-
const onDragY = (dy) => {
|
|
173
|
-
const newTop = points.getItem(1).y + dy - offset;
|
|
174
|
-
if (newTop < -0.5 || newTop > height)
|
|
175
|
-
return;
|
|
176
|
-
const newVolume = Math.min(1, Math.max(0, (height - newTop) / height));
|
|
177
|
-
this.onVolumeDrag(newVolume);
|
|
178
|
-
};
|
|
179
|
-
// On points drag
|
|
180
|
-
const onDragX = (dx, dy, index) => {
|
|
181
|
-
const point = polyline.points.getItem(index);
|
|
182
|
-
const newX = point.x + dx;
|
|
183
|
-
const newTime = (newX / width) * duration;
|
|
184
|
-
if ((index === 1 && newTime > this.options.fadeOutStart) || newTime < this.options.fadeInStart)
|
|
185
|
-
return;
|
|
186
|
-
if ((index === 2 && newTime < this.options.fadeInEnd) || newTime > this.options.fadeOutEnd)
|
|
187
|
-
return;
|
|
188
|
-
point.x = newX;
|
|
188
|
+
this.polyline = new Polyline(this.options, wrapper);
|
|
189
|
+
this.subscriptions.push(this.polyline.on('line-move', (relativeY) => {
|
|
190
|
+
this.setVolume(this.naturalVolume(relativeY));
|
|
191
|
+
}), this.polyline.on('point-move', (index, relativeX) => {
|
|
192
|
+
const duration = this.wavesurfer?.getDuration() || 0;
|
|
193
|
+
const newTime = relativeX * duration;
|
|
194
|
+
// Fade-in end point
|
|
189
195
|
if (index === 1) {
|
|
196
|
+
if (newTime < this.options.fadeInStart || newTime > this.options.fadeOutStart)
|
|
197
|
+
return;
|
|
190
198
|
this.options.fadeInEnd = newTime;
|
|
191
199
|
this.emit('fade-in-change', newTime);
|
|
192
200
|
}
|
|
193
201
|
else if (index === 2) {
|
|
202
|
+
// Fade-out start point
|
|
203
|
+
if (newTime > this.options.fadeOutEnd || newTime < this.options.fadeInEnd)
|
|
204
|
+
return;
|
|
194
205
|
this.options.fadeOutStart = newTime;
|
|
195
206
|
this.emit('fade-out-change', newTime);
|
|
196
207
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
onDragY(dy);
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
this.renderPolyline();
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
// Draggable top line of the polyline
|
|
206
|
-
this.makeDraggable(line, (_, dy) => onDragY(dy));
|
|
207
|
-
// Make each point draggable
|
|
208
|
-
const draggables = svg.querySelectorAll('circle');
|
|
209
|
-
for (let i = 0; i < draggables.length; i++) {
|
|
210
|
-
const index = i + 1;
|
|
211
|
-
this.makeDraggable(draggables[i], (dx, dy) => onDragX(dx, dy, index));
|
|
212
|
-
}
|
|
208
|
+
this.renderPolyline();
|
|
209
|
+
}));
|
|
213
210
|
}
|
|
214
|
-
|
|
215
|
-
this.
|
|
216
|
-
|
|
211
|
+
renderPolyline() {
|
|
212
|
+
if (!this.polyline || !this.wavesurfer)
|
|
213
|
+
return;
|
|
214
|
+
const duration = this.wavesurfer.getDuration();
|
|
215
|
+
if (!duration)
|
|
216
|
+
return;
|
|
217
|
+
this.polyline.update({
|
|
218
|
+
x1: this.options.fadeInStart / duration,
|
|
219
|
+
x2: this.options.fadeInEnd / duration,
|
|
220
|
+
x3: this.options.fadeOutStart / duration,
|
|
221
|
+
x4: this.options.fadeOutEnd / duration,
|
|
222
|
+
y: this.invertNaturalVolume(this.volume),
|
|
223
|
+
});
|
|
217
224
|
}
|
|
218
225
|
initWebAudio() {
|
|
219
226
|
const audio = this.wavesurfer?.getMediaElement();
|
|
@@ -249,9 +256,6 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
249
256
|
this.gainNode.gain.value = this.volume;
|
|
250
257
|
}
|
|
251
258
|
}
|
|
252
|
-
onVolumeDrag(volume) {
|
|
253
|
-
this.setVolume(this.naturalVolume(volume));
|
|
254
|
-
}
|
|
255
259
|
initFadeEffects() {
|
|
256
260
|
if (!this.audioContext || !this.wavesurfer)
|
|
257
261
|
return;
|
|
@@ -305,22 +309,29 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
305
309
|
getCurrentVolume() {
|
|
306
310
|
return this.gainNode ? this.gainNode.gain.value : this.volume;
|
|
307
311
|
}
|
|
308
|
-
/**
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Set the fade-in start time.
|
|
314
|
+
* @param time The time (in seconds) to set the fade-in start time to
|
|
315
|
+
* @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
|
|
316
|
+
*/
|
|
317
|
+
setStartTime(time, moveFadeInEnd = false) {
|
|
318
|
+
if (moveFadeInEnd) {
|
|
319
|
+
const rampLength = this.options.fadeInEnd - this.options.fadeInStart;
|
|
313
320
|
this.options.fadeInEnd = time + rampLength;
|
|
314
321
|
}
|
|
322
|
+
this.options.fadeInStart = time;
|
|
315
323
|
this.renderPolyline();
|
|
316
324
|
}
|
|
317
|
-
/** Set the fade-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
325
|
+
/** Set the fade-in end time.
|
|
326
|
+
* @param time The time (in seconds) to set the fade-in end time to
|
|
327
|
+
* @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
|
|
328
|
+
*/
|
|
329
|
+
setEndTime(time, moveFadeOutStart = false) {
|
|
330
|
+
if (moveFadeOutStart) {
|
|
331
|
+
const rampLength = this.options.fadeOutEnd - this.options.fadeOutStart;
|
|
322
332
|
this.options.fadeOutStart = time - rampLength;
|
|
323
333
|
}
|
|
334
|
+
this.options.fadeOutEnd = time;
|
|
324
335
|
this.renderPolyline();
|
|
325
336
|
}
|
|
326
337
|
/** Set the volume of the audio */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Envelope=e():t.Envelope=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={284:(t,e,i)=>{i.d(e,{Z:()=>o});var s=i(139);class n extends s.Z{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}}const o=n},139:(t,e,i)=>{i.d(e,{Z:()=>s});const s=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),s=this.on(t,(()=>{i(),s()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}}},e={};function i(s){var n=e[s];if(void 0!==n)return n.exports;var o=e[s]={exports:{}};return t[s](o,o.exports,i),o.exports}i.d=(t,e)=>{for(var s in e)i.o(e,s)&&!i.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var s={};return(()=>{i.d(s,{default:()=>
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Envelope=e():t.Envelope=e()}(WaveSurfer,(()=>(()=>{"use strict";var t={284:(t,e,i)=>{i.d(e,{Z:()=>o});var s=i(139);class n extends s.Z{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.subscriptions.forEach((t=>t()))}}const o=n},139:(t,e,i)=>{i.d(e,{Z:()=>s});const s=class{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),s=this.on(t,(()=>{i(),s()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}}},e={};function i(s){var n=e[s];if(void 0!==n)return n.exports;var o=e[s]={exports:{}};return t[s](o,o.exports,i),o.exports}i.d=(t,e)=>{for(var s in e)i.o(e,s)&&!i.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var s={};return(()=>{i.d(s,{default:()=>a});var t=i(284),e=i(139);const n={fadeInStart:0,fadeOutEnd:0,fadeInEnd:0,fadeOutStart:0,lineWidth:4,lineColor:"rgba(0, 0, 255, 0.5)",dragPointSize:10,dragPointFill:"rgba(255, 255, 255, 0.8)",dragPointStroke:"rgba(255, 255, 255, 0.8)"};class o extends e.Z{constructor(t,e){super(),this.top=0,this.padding=t.dragPointSize/2+1;const i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("width","100%"),i.setAttribute("height","100%"),i.setAttribute("viewBox",`0 0 ${e.clientWidth} ${e.clientHeight}`),i.setAttribute("preserveAspectRatio","none"),i.setAttribute("style","position: absolute; left: 0; top: 0; z-index: 4; pointer-events: none;"),i.setAttribute("part","envelope"),this.svg=i;const s=document.createElementNS("http://www.w3.org/2000/svg","polyline");s.setAttribute("points","0,0 0,0 0,0 0,0"),s.setAttribute("stroke",t.lineColor),s.setAttribute("stroke-width",t.lineWidth),s.setAttribute("fill","none"),s.setAttribute("style","pointer-events: none;"),s.setAttribute("part","polyline"),i.appendChild(s);const n=document.createElementNS("http://www.w3.org/2000/svg","line");n.setAttribute("stroke","transparent"),n.setAttribute("stroke-width",(3*t.lineWidth).toString()),n.setAttribute("style","cursor: ns-resize; pointer-events: all;"),n.setAttribute("part","line"),i.appendChild(n),[0,1].forEach((()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","circle");e.setAttribute("r",(t.dragPointSize/2).toString()),e.setAttribute("fill",t.dragPointFill),e.setAttribute("stroke",t.dragPointStroke||t.dragPointFill),e.setAttribute("stroke-width","2"),e.setAttribute("style","cursor: ew-resize; pointer-events: all;"),e.setAttribute("part","circle"),i.appendChild(e)})),e.appendChild(i);{const t=t=>{const e=this.top+t,{height:s}=i.viewBox.baseVal;if(e<-.5||e>s)return;const n=Math.min(1,Math.max(0,(s-e)/s));this.emit("line-move",n)},e=(t,e)=>{const n=s.points.getItem(t).x+e,{width:o}=i.viewBox.baseVal;this.emit("point-move",t,n/o)};this.makeDraggable(n,((e,i)=>t(i)));const o=this.svg.querySelectorAll("circle");Array.from(o).forEach(((t,i)=>{this.makeDraggable(t,(t=>e(i+1,t)))}))}}makeDraggable(t,e){t.addEventListener("click",(t=>{t.preventDefault(),t.stopPropagation()})),t.addEventListener("mousedown",(t=>{t.preventDefault(),t.stopPropagation();let i=t.clientX,s=t.clientY;const n=t=>{const n=t.clientX-i,o=t.clientY-s;i=t.clientX,s=t.clientY,e(n,o)},o=()=>{document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",o)};document.addEventListener("mousemove",n),document.addEventListener("mouseup",o)}))}update({x1:t,x2:e,x3:i,x4:s,y:n}){const o=this.svg.clientWidth,r=this.svg.clientHeight;this.top=r-n*r;const a=Math.max(this.padding,Math.min(this.top,r-this.padding));this.svg.setAttribute("viewBox",`0 0 ${o} ${r}`);const h=this.svg.querySelector("polyline"),{points:u}=h;u.getItem(0).x=t*o,u.getItem(0).y=r,u.getItem(1).x=e*o,u.getItem(1).y=a,u.getItem(2).x=i*o,u.getItem(2).y=a,u.getItem(3).x=s*o,u.getItem(3).y=r;const d=this.svg.querySelector("line");d.setAttribute("x1",u.getItem(1).x.toString()),d.setAttribute("x2",u.getItem(2).x.toString()),d.setAttribute("y1",a.toString()),d.setAttribute("y2",a.toString());const l=this.svg.querySelectorAll("circle");Array.from(l).forEach(((t,e)=>{const i=u.getItem(e+1);t.setAttribute("cx",i.x.toString()),t.setAttribute("cy",i.y.toString())}))}destroy(){this.svg.remove()}}class r extends t.Z{constructor(t){super(t),this.polyline=null,this.audioContext=null,this.gainNode=null,this.volume=1,this.isFadingIn=!1,this.isFadingOut=!1,this.naturalVolumeExponent=1.5,this.options=Object.assign({},n,t),this.options.lineColor=this.options.lineColor||n.lineColor,this.options.dragPointFill=this.options.dragPointFill||n.dragPointFill,this.options.dragPointStroke=this.options.dragPointStroke||n.dragPointStroke,this.volume=this.options.volume??1}static create(t){return new r(t)}destroy(){this.polyline?.destroy(),super.destroy()}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.initWebAudio(),this.initSvg(),this.initFadeEffects(),this.subscriptions.push(this.wavesurfer.on("decode",(t=>{this.options.fadeInStart=this.options.fadeInStart||0,this.options.fadeOutEnd=this.options.fadeOutEnd||t,this.options.fadeInEnd=this.options.fadeInEnd||this.options.fadeInStart,this.options.fadeOutStart=this.options.fadeOutStart||this.options.fadeOutEnd,this.renderPolyline()}))),this.subscriptions.push(this.wavesurfer.on("redraw",(()=>this.renderPolyline())))}initSvg(){if(!this.wavesurfer)return;const t=this.wavesurfer.getWrapper();this.polyline=new o(this.options,t),this.subscriptions.push(this.polyline.on("line-move",(t=>{this.setVolume(this.naturalVolume(t))})),this.polyline.on("point-move",((t,e)=>{const i=e*(this.wavesurfer?.getDuration()||0);if(1===t){if(i<this.options.fadeInStart||i>this.options.fadeOutStart)return;this.options.fadeInEnd=i,this.emit("fade-in-change",i)}else if(2===t){if(i>this.options.fadeOutEnd||i<this.options.fadeInEnd)return;this.options.fadeOutStart=i,this.emit("fade-out-change",i)}this.renderPolyline()})))}renderPolyline(){if(!this.polyline||!this.wavesurfer)return;const t=this.wavesurfer.getDuration();t&&this.polyline.update({x1:this.options.fadeInStart/t,x2:this.options.fadeInEnd/t,x3:this.options.fadeOutStart/t,x4:this.options.fadeOutEnd/t,y:this.invertNaturalVolume(this.volume)})}initWebAudio(){const t=this.wavesurfer?.getMediaElement();if(!t)return null;this.volume=this.options.volume??t.volume;const e=new window.AudioContext;this.gainNode=e.createGain(),this.setGainValue(),e.createMediaElementSource(t).connect(this.gainNode),this.gainNode.connect(e.destination),this.audioContext=e}invertNaturalVolume(t){return Math.pow((t-1e-4)/.9999,1/this.naturalVolumeExponent)}naturalVolume(t){return 1e-4+.9999*Math.pow(t,this.naturalVolumeExponent)}setGainValue(){this.gainNode&&(this.gainNode.gain.value=this.volume)}initFadeEffects(){if(!this.audioContext||!this.wavesurfer)return;const t=this.wavesurfer.on("timeupdate",(t=>{if(!this.audioContext||!this.gainNode)return;if(!this.wavesurfer?.isPlaying())return;if("suspended"===this.audioContext.state&&this.audioContext.resume(),!this.isFadingIn&&t>=this.options.fadeInStart&&t<=this.options.fadeInEnd)return this.isFadingIn=!0,this.gainNode.gain.setValueAtTime(0,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(this.volume,this.audioContext.currentTime+(this.options.fadeInEnd-t));if(!this.isFadingOut&&t>=this.options.fadeOutStart&&t<=this.options.fadeOutEnd)return this.isFadingOut=!0,this.gainNode.gain.setValueAtTime(this.volume,this.audioContext.currentTime),void this.gainNode.gain.linearRampToValueAtTime(0,this.audioContext.currentTime+(this.options.fadeOutEnd-t));let e=!1;this.isFadingIn&&(t<this.options.fadeInStart||t>this.options.fadeInEnd)&&(this.isFadingIn=!1,e=!0),this.isFadingOut&&(t<this.options.fadeOutStart||t>=this.options.fadeOutEnd)&&(this.isFadingOut=!1,e=!0),e&&(this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime),this.setGainValue())}));this.subscriptions.push(t)}getCurrentVolume(){return this.gainNode?this.gainNode.gain.value:this.volume}setStartTime(t,e=!1){if(e){const e=this.options.fadeInEnd-this.options.fadeInStart;this.options.fadeInEnd=t+e}this.options.fadeInStart=t,this.renderPolyline()}setEndTime(t,e=!1){if(e){const e=this.options.fadeOutEnd-this.options.fadeOutStart;this.options.fadeOutStart=t-e}this.options.fadeOutEnd=t,this.renderPolyline()}setVolume(t){this.volume=t,this.setGainValue(),this.renderPolyline(),this.emit("volume-change",t)}}const a=r})(),s.default})()));
|