wavesurfer.js 7.0.0-alpha.47 → 7.0.0-alpha.49
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 +184 -171
- 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.js +1 -0
- package/dist/plugins/multitrack.min.js +1 -1
- package/dist/plugins/regions.d.ts +2 -2
- package/dist/plugins/regions.js +7 -4
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/spectrogram.d.ts +3 -0
- package/dist/plugins/spectrogram.js +3 -0
- package/dist/plugins/timeline.d.ts +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 +2 -2
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 0 0');
|
|
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,126 @@ 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
|
-
|
|
101
|
-
|
|
102
|
-
points
|
|
103
|
-
points.getItem(0).x =
|
|
104
|
-
points.getItem(
|
|
114
|
+
this.top = height - y * height;
|
|
115
|
+
const paddedTop = 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;
|
|
122
|
+
points.getItem(1).y = paddedTop;
|
|
123
|
+
points.getItem(2).x = x3 * width;
|
|
124
|
+
points.getItem(2).y = paddedTop;
|
|
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
|
-
line.setAttribute('y1',
|
|
109
|
-
line.setAttribute('y2',
|
|
130
|
+
line.setAttribute('y1', paddedTop.toString());
|
|
131
|
+
line.setAttribute('y2', paddedTop.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('redraw', () => {
|
|
176
|
+
const duration = this.wavesurfer?.getDuration();
|
|
177
|
+
if (!duration)
|
|
178
|
+
return;
|
|
179
|
+
this.options.fadeInStart = this.options.fadeInStart || 0;
|
|
180
|
+
this.options.fadeOutEnd = this.options.fadeOutEnd || duration;
|
|
181
|
+
this.options.fadeInEnd = this.options.fadeInEnd || this.options.fadeInStart;
|
|
182
|
+
this.options.fadeOutStart = this.options.fadeOutStart || this.options.fadeOutEnd;
|
|
183
|
+
this.renderPolyline();
|
|
184
|
+
}));
|
|
117
185
|
}
|
|
118
186
|
initSvg() {
|
|
119
187
|
if (!this.wavesurfer)
|
|
120
188
|
return;
|
|
121
189
|
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;
|
|
190
|
+
this.polyline = new Polyline(this.options, wrapper);
|
|
191
|
+
this.subscriptions.push(this.polyline.on('line-move', (relativeY) => {
|
|
192
|
+
this.setVolume(this.naturalVolume(relativeY));
|
|
193
|
+
}), this.polyline.on('point-move', (index, relativeX) => {
|
|
194
|
+
const duration = this.wavesurfer?.getDuration() || 0;
|
|
195
|
+
const newTime = relativeX * duration;
|
|
196
|
+
// Fade-in end point
|
|
189
197
|
if (index === 1) {
|
|
198
|
+
if (newTime < this.options.fadeInStart || newTime > this.options.fadeOutStart)
|
|
199
|
+
return;
|
|
190
200
|
this.options.fadeInEnd = newTime;
|
|
191
201
|
this.emit('fade-in-change', newTime);
|
|
192
202
|
}
|
|
193
203
|
else if (index === 2) {
|
|
204
|
+
// Fade-out start point
|
|
205
|
+
if (newTime > this.options.fadeOutEnd || newTime < this.options.fadeInEnd)
|
|
206
|
+
return;
|
|
194
207
|
this.options.fadeOutStart = newTime;
|
|
195
208
|
this.emit('fade-out-change', newTime);
|
|
196
209
|
}
|
|
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
|
-
}
|
|
210
|
+
this.renderPolyline();
|
|
211
|
+
}));
|
|
213
212
|
}
|
|
214
|
-
|
|
215
|
-
this.
|
|
216
|
-
|
|
213
|
+
renderPolyline() {
|
|
214
|
+
if (!this.polyline || !this.wavesurfer)
|
|
215
|
+
return;
|
|
216
|
+
const duration = this.wavesurfer.getDuration();
|
|
217
|
+
if (!duration)
|
|
218
|
+
return;
|
|
219
|
+
this.polyline.update({
|
|
220
|
+
x1: this.options.fadeInStart / duration,
|
|
221
|
+
x2: this.options.fadeInEnd / duration,
|
|
222
|
+
x3: this.options.fadeOutStart / duration,
|
|
223
|
+
x4: this.options.fadeOutEnd / duration,
|
|
224
|
+
y: this.invertNaturalVolume(this.volume),
|
|
225
|
+
});
|
|
217
226
|
}
|
|
218
227
|
initWebAudio() {
|
|
219
228
|
const audio = this.wavesurfer?.getMediaElement();
|
|
@@ -249,9 +258,6 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
249
258
|
this.gainNode.gain.value = this.volume;
|
|
250
259
|
}
|
|
251
260
|
}
|
|
252
|
-
onVolumeDrag(volume) {
|
|
253
|
-
this.setVolume(this.naturalVolume(volume));
|
|
254
|
-
}
|
|
255
261
|
initFadeEffects() {
|
|
256
262
|
if (!this.audioContext || !this.wavesurfer)
|
|
257
263
|
return;
|
|
@@ -305,22 +311,29 @@ class EnvelopePlugin extends BasePlugin {
|
|
|
305
311
|
getCurrentVolume() {
|
|
306
312
|
return this.gainNode ? this.gainNode.gain.value : this.volume;
|
|
307
313
|
}
|
|
308
|
-
/**
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Set the fade-in start time.
|
|
316
|
+
* @param time The time (in seconds) to set the fade-in start time to
|
|
317
|
+
* @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
|
|
318
|
+
*/
|
|
319
|
+
setStartTime(time, moveFadeInEnd = false) {
|
|
320
|
+
if (moveFadeInEnd) {
|
|
321
|
+
const rampLength = this.options.fadeInEnd - this.options.fadeInStart;
|
|
313
322
|
this.options.fadeInEnd = time + rampLength;
|
|
314
323
|
}
|
|
324
|
+
this.options.fadeInStart = time;
|
|
315
325
|
this.renderPolyline();
|
|
316
326
|
}
|
|
317
|
-
/** Set the fade-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
327
|
+
/** Set the fade-in end time.
|
|
328
|
+
* @param time The time (in seconds) to set the fade-in end time to
|
|
329
|
+
* @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
|
|
330
|
+
*/
|
|
331
|
+
setEndTime(time, moveFadeOutStart = false) {
|
|
332
|
+
if (moveFadeOutStart) {
|
|
333
|
+
const rampLength = this.options.fadeOutEnd - this.options.fadeOutStart;
|
|
322
334
|
this.options.fadeOutStart = time - rampLength;
|
|
323
335
|
}
|
|
336
|
+
this.options.fadeOutEnd = time;
|
|
324
337
|
this.renderPolyline();
|
|
325
338
|
}
|
|
326
339
|
/** Set the volume of the audio */
|