1 | # Waveform Playlist
|
2 |
|
3 | Inspired by [Audacity](http://audacity.sourceforge.net/), this project is a multiple track playlist editor written in ES2015 using the [Web Audio API](http://webaudio.github.io/web-audio-api/).
|
4 |
|
5 | [![npm](https://img.shields.io/npm/dm/waveform-playlist.svg)](https://www.npmjs.com/package/waveform-playlist)
|
6 |
|
7 | Load tracks and set cues (track cue in, cue out), fades (track fade in, fade out) and track start/end times within the playlist.
|
8 | I've written up some demos on github for the different [audio fade types](https://github.com/naomiaro/Web-Audio-Fades) in the project.
|
9 |
|
10 | - [See examples in action](http://naomiaro.github.io/waveform-playlist)
|
11 | - [Try out the waveform editor!](http://naomiaro.github.io/waveform-playlist/web-audio-editor.html)
|
12 |
|
13 | ![Screenshot](img/stemtracks.png?raw=true "stem tracks mute solo volume control")
|
14 | (code for picture shown can be found in ghpages/\_examples/04stemtracks.html)
|
15 |
|
16 | ![Screenshot](img/annotations.png?raw=true "Aeneas annotations adjust alignment json export")
|
17 | (code for picture shown can be found in ghpages/\_examples/13annotations.html)
|
18 |
|
19 | ## Browser Support
|
20 |
|
21 | Waveform Playlist requires webaudio in the browser to function correctly: [Can I Use?](http://caniuse.com/#search=webaudio)
|
22 |
|
23 | ## Installation
|
24 |
|
25 | `npm install waveform-playlist --save`
|
26 |
|
27 | Hate npm? Check Unpkg: https://unpkg.com/browse/waveform-playlist/
|
28 |
|
29 | - If you want to download and run the already compiled website, navigate to folder `/dist` and run `python -m SimpleHTTPServer 8000`. The website will be available at `localhost:8000/waveform-playlist`.
|
30 |
|
31 | ## Basic Usage
|
32 |
|
33 | https://github.com/naomiaro/waveform-playlist/blob/main/examples/basic-html/
|
34 |
|
35 | https://github.com/naomiaro/waveform-playlist/tree/main/examples/basic-express/
|
36 |
|
37 | ```javascript
|
38 | import WaveformPlaylist from "waveform-playlist";
|
39 |
|
40 | var playlist = WaveformPlaylist({
|
41 | samplesPerPixel: 3000,
|
42 | mono: true,
|
43 | waveHeight: 70,
|
44 | container: document.getElementById("playlist"),
|
45 | state: "cursor",
|
46 | colors: {
|
47 | waveOutlineColor: "#E0EFF1",
|
48 | timeColor: "grey",
|
49 | fadeColor: "black",
|
50 | },
|
51 | controls: {
|
52 | show: false,
|
53 | width: 150,
|
54 | },
|
55 | zoomLevels: [500, 1000, 3000, 5000],
|
56 | });
|
57 |
|
58 | playlist
|
59 | .load([
|
60 | {
|
61 | src: "media/audio/Vocals30.mp3",
|
62 | name: "Vocals",
|
63 | gain: 0.5,
|
64 | },
|
65 | {
|
66 | src: "media/audio/BassDrums30.mp3",
|
67 | name: "Drums",
|
68 | start: 8.5,
|
69 | fadeIn: {
|
70 | duration: 0.5,
|
71 | },
|
72 | fadeOut: {
|
73 | shape: "logarithmic",
|
74 | duration: 0.5,
|
75 | },
|
76 | },
|
77 | {
|
78 | src: "media/audio/Guitar30.mp3",
|
79 | name: "Guitar",
|
80 | start: 23.5,
|
81 | fadeOut: {
|
82 | shape: "linear",
|
83 | duration: 0.5,
|
84 | },
|
85 | cuein: 15,
|
86 | },
|
87 | ])
|
88 | .then(function () {
|
89 | // can do stuff with the playlist.
|
90 | });
|
91 | ```
|
92 |
|
93 | ### Playlist Options
|
94 |
|
95 | ```javascript
|
96 | var options = {
|
97 | // webaudio api AudioContext
|
98 | ac: new (window.AudioContext || window.webkitAudioContext)(),
|
99 |
|
100 | // DOM container element REQUIRED
|
101 | container: document.getElementById("playlist"),
|
102 |
|
103 | // sample rate of the project. (used for correct peaks rendering)
|
104 | sampleRate: new (
|
105 | window.AudioContext || window.webkitAudioContext
|
106 | ).sampleRate(),
|
107 |
|
108 | // number of audio samples per waveform peak.
|
109 | // must be an entry in option: zoomLevels.
|
110 | samplesPerPixel: 4096,
|
111 |
|
112 | // whether to draw multiple channels or combine them.
|
113 | mono: true,
|
114 |
|
115 | // enables "exclusive solo" where solo switches between tracks
|
116 | exclSolo: false,
|
117 |
|
118 | // default fade curve type.
|
119 | fadeType: "logarithmic", // (logarithmic | linear | sCurve | exponential)
|
120 |
|
121 | // whether or not to include the time measure.
|
122 | timescale: false,
|
123 |
|
124 | // control panel on left side of waveform
|
125 | controls: {
|
126 | // whether or not to include the track controls
|
127 | show: false,
|
128 |
|
129 | // width of controls in pixels
|
130 | width: 150,
|
131 |
|
132 | // whether to render the widget or not in the controls panel.
|
133 | widgets: {
|
134 | // Mute & solo button widget
|
135 | muteOrSolo: true,
|
136 |
|
137 | // Volume slider
|
138 | volume: true,
|
139 |
|
140 | // Stereo pan slider
|
141 | stereoPan: true,
|
142 |
|
143 | // Collapse track button
|
144 | collapse: true,
|
145 |
|
146 | // Remove track button
|
147 | remove: true,
|
148 | },
|
149 | },
|
150 |
|
151 | colors: {
|
152 | // color of the wave background
|
153 | waveOutlineColor: "white",
|
154 |
|
155 | // color of the time ticks on the canvas
|
156 | timeColor: "grey",
|
157 |
|
158 | // color of the fade drawn on canvas
|
159 | fadeColor: "black",
|
160 | },
|
161 |
|
162 | // height in pixels of each canvas element a waveform is on.
|
163 | waveHeight: 128,
|
164 |
|
165 | // width in pixels of waveform bars.
|
166 | barWidth: 1,
|
167 |
|
168 | // spacing between of waveform bars.
|
169 | barGap: 0,
|
170 |
|
171 | // interaction state of the playlist
|
172 | // (cursor | select | fadein | fadeout | shift)
|
173 | state: "cursor",
|
174 |
|
175 | // (line | fill)
|
176 | seekStyle: "line",
|
177 |
|
178 | // Array of zoom levels in samples per pixel.
|
179 | // Smaller numbers have a greater zoom in.
|
180 | zoomLevels: [512, 1024, 2048, 4096],
|
181 |
|
182 | // Whether to automatically scroll the waveform while playing
|
183 | isAutomaticScroll: false,
|
184 |
|
185 | // configuration object for the annotations add on.
|
186 | annotationList: {
|
187 | // Array of annotations in [Aeneas](https://github.com/readbeyond/aeneas) JSON format
|
188 | annotations: [],
|
189 |
|
190 | // Whether the annotation texts will be in updateable contenteditable html elements
|
191 | editable: false,
|
192 |
|
193 | // User defined functions which can manipulate the loaded annotations
|
194 | controls: [
|
195 | {
|
196 | // class names for generated <i> tag separated by '.'
|
197 | class: "fa.fa-minus",
|
198 |
|
199 | // title attribute for the generated <i> tag
|
200 | title: "Reduce annotation end by 0.010s",
|
201 |
|
202 | // function which acts on the given annotation row
|
203 | // when the corresponding <i> is clicked.
|
204 | action: (annotation, i, annotations, opts) => {
|
205 | // @param Object annotation - current annotation
|
206 | // @param Number i - index of annotation
|
207 | // @param Array annotations - array of annotations in the playlist
|
208 | // @param Object opts - configuration options available
|
209 | // - opts.linkEndpoints
|
210 | },
|
211 | },
|
212 | ],
|
213 |
|
214 | // If false when clicking an annotation id segment
|
215 | // playback will stop after segment completion.
|
216 | isContinuousPlay: false,
|
217 |
|
218 | // If true annotation endpoints will remain linked when dragged
|
219 | // if they were the same value before dragging started.
|
220 | linkEndpoints: false,
|
221 | },
|
222 | };
|
223 | ```
|
224 |
|
225 | ### Track Options
|
226 |
|
227 | ```javascript
|
228 | {
|
229 | // a media path for XHR, a Blob, a File, or an AudioBuffer object.
|
230 | src: 'media/audio/BassDrums30.mp3',
|
231 |
|
232 | // name that will display in the playlist control panel.
|
233 | name: 'Drums',
|
234 |
|
235 | // volume level of the track between [0-1]
|
236 | gain: 1,
|
237 |
|
238 | // whether the track should initially be muted.
|
239 | muted: false,
|
240 |
|
241 | // whether the track should initially be soloed.
|
242 | soloed: false,
|
243 |
|
244 | // time in seconds relative to the playlist
|
245 | // ex (track will start after 8.5 seconds)
|
246 | // DEFAULT 0 - track starts at beginning of playlist
|
247 | start: 8.5,
|
248 |
|
249 | // track fade in details
|
250 | fadeIn: {
|
251 | // fade curve shape
|
252 | // (logarithmic | linear | sCurve | exponential)
|
253 | shape: 'logarithmic',
|
254 |
|
255 | // length of fade starting from the beginning of this track, in seconds.
|
256 | duration: 0.5,
|
257 | },
|
258 |
|
259 | // track fade out details
|
260 | fadeOut: {
|
261 | // fade curve shape
|
262 | // (logarithmic | linear | sCurve | exponential)
|
263 | shape: 'logarithmic',
|
264 |
|
265 | //length of fade which reaches the end of this track, in seconds.
|
266 | duration: 0.5,
|
267 | }
|
268 |
|
269 | // where the waveform for this track should begin from
|
270 | // ex (Waveform will begin 15 seconds into this track)
|
271 | // DEFAULT start at the beginning - 0 seconds
|
272 | cuein: 15,
|
273 |
|
274 | // where the waveform for this track should end
|
275 | // ex (Waveform will end at 30 second into this track)
|
276 | // DEFAULT duration of the track
|
277 | cueout: 30,
|
278 |
|
279 | // custom class for unique track styling
|
280 | customClass: 'vocals',
|
281 |
|
282 | // custom background-color for the canvas-drawn waveform
|
283 | waveOutlineColor: '#f3f3f3',
|
284 |
|
285 | // interaction states allowed on this track.
|
286 | // DEFAULT - all true
|
287 | states: {
|
288 | cursor: true,
|
289 | fadein: true,
|
290 | fadeout: true,
|
291 | select: true,
|
292 | shift: true,
|
293 | },
|
294 |
|
295 | // pre-selected section on track.
|
296 | // ONLY ONE selection is permitted in a list of tracks, will take most recently set if multiple passed.
|
297 | // This track is marked as 'active'
|
298 | selected: {
|
299 | // start time of selection in seconds, relative to the playlist
|
300 | start: 5,
|
301 |
|
302 | // end time of selection in seconds, relative to the playlist
|
303 | end: 15,
|
304 | },
|
305 |
|
306 | // value from -1 (full left pan) to 1 (full right pan)
|
307 | stereoPan: 0
|
308 | }
|
309 | ```
|
310 |
|
311 | ### Playlist Events
|
312 |
|
313 | Waveform Playlist uses an instance of [event-emitter](https://www.npmjs.com/package/event-emitter) to send & receive messages from the playlist.
|
314 |
|
315 | ```javascript
|
316 | import EventEmitter from "event-emitter";
|
317 | import WaveformPlaylist from "waveform-playlist";
|
318 |
|
319 | var playlist = WaveformPlaylist(
|
320 | {
|
321 | container: document.getElementById("playlist"),
|
322 | },
|
323 |
|
324 | // you can pass your own event emitter
|
325 | EventEmitter()
|
326 | );
|
327 |
|
328 | // retrieves the event emitter the playlist is using.
|
329 | var ee = playlist.getEventEmitter();
|
330 | ```
|
331 |
|
332 | An example of using the event emitter to control the playlist can be found in [/dist/js/examples/emitter.js](https://github.com/naomiaro/waveform-playlist/blob/main/dist/waveform-playlist/js/emitter.js)
|
333 |
|
334 | #### Events to Invoke
|
335 |
|
336 | | event | arguments | description |
|
337 | | --------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
338 | | `play` | `start:optional, end:optional` | Starts playout of the playlist. Takes optional Number parameters in seconds `start` and `end` to play just an audio segment. `start` can be passed without an `end` to play to the end of the track. |
|
339 | | `pause` | _none_ | Pauses playout of the playlist. |
|
340 | | `stop` | _none_ | Stops playout of the playlist. |
|
341 | | `rewind` | _none_ | Stops playout if playlist is playing, resets cursor to the beginning of the playlist. |
|
342 | | `fastforward` | _none_ | Stops playout if playlist is playing, resets cursor to the end of the playlist. |
|
343 | | `clear` | _none_ | Stops playout if playlist is playing, removes all tracks from the playlist. |
|
344 | | `record` | _none_ | Starts recording an audio track. Begins playout of other tracks in playlist if there are any. |
|
345 | | `zoomin` | _none_ | Changes zoom level to the next smallest entry (if one exists) from the array `zoomLevels`. |
|
346 | | `zoomout` | _none_ | Changes zoom level to the next largest entry (if one exists) from the array `zoomLevels`. |
|
347 | | `trim` | _none_ | Trims currently active track to the cursor selection. |
|
348 | | `statechange` | `cursor` / `select` / `fadein` / `fadeout` / `shift` | Changes interaction state to the state given. |
|
349 | | `fadetype` | `logarithmic` / `linear` / `sCurve` / `exponential` | Changes playlist default fade type. |
|
350 | | `newtrack` | `File` | Loads `File` object into the playlist. |
|
351 | | `volumechange` | `volume, track` | Set volume of `track` to `volume` (0-100) |
|
352 | | `mastervolumechange` | `volume` | Set a new master volume `volume` (0-100) |
|
353 | | `select` | `start, end, track:optional` | Seek to the start time or start/end selection optionally with active track `track`. |
|
354 | | `startaudiorendering` | `wav` / `buffer` | Request for a downloadable file or web Audio buffer that represent the current work |
|
355 | | `automaticscroll` | `true`/`false` | Change property `isAutomaticScroll`. |
|
356 | | `continuousplay` | `true`/`false` | Change property `isContinuousPlay`. |
|
357 | | `linkendpoints` | `true`/`false` | Change property `linkEndpoints`. |
|
358 | | `annotationsrequest` | _none_ | Requests to download the annotations to a json file. |
|
359 | | `stereopan` | `panvalue, track` | Set pan value of `track` to `panvalue` (-1-1) |
|
360 |
|
361 | #### Events to Listen to
|
362 |
|
363 | | event | arguments | description |
|
364 | | ------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
365 | | `select` | `start, end, track` | Cursor selection has occurred from `start` to `end` with active Track `track`. |
|
366 | | `timeupdate` | `playbackPosition` | Sends current position of playout `playbackPosition` in seconds. |
|
367 | | `scroll` | `scrollLeft` | Sends current position of scroll `scrollLeft` in seconds. |
|
368 | | `statechange` | `state` | Sends current interaction state `state`. |
|
369 | | `shift` | `deltaTime, track` | Sends `deltaTime` in seconds change for Track `track` |
|
370 | | `mute` | `track` | Mute button has been pressed for `track` |
|
371 | | `solo` | `track` | Solo button has been pressed for `track` |
|
372 | | `removeTrack` | `track` | Remove button has been pressed for `track` |
|
373 | | `changeTrackView` | `track, opts` | Collapse button has been pressed for `track` |
|
374 | | `volumechange` | `volume, track` | Volume of `track` has changed to `volume` (0-100) |
|
375 | | `mastervolumechange` | `volume` | Master volume of the playlist has changed to `volume` (0-100) |
|
376 | | `audiorequeststatechange` | `state, src` | Loading audio `src` (`string` or `File`) is now in state [`state`](https://github.com/naomiaro/waveform-playlist/wiki/Track-Loading-States) (Number) |
|
377 | | `loadprogress` | `percent, src` | Loading audio `src` has loaded percent `percent` (0-100) |
|
378 | | `audiosourcesloaded` | _none_ | Audio decoding has finished for all tracks |
|
379 | | `audiosourcesrendered` | _none_ | Tracks are rendered to the playlist |
|
380 | | `audiosourceserror` | `err` | Error thrown while loading tracks |
|
381 | | `finished` | _none_ | Event fired when cursor ( while playing ) reaches the end (maximum duration) |
|
382 | | `audiorenderingfinished` | `type, data` | Return the result of the rendering in the desired format. `type` can be `buffer` or `wav` and can be used to dertermine the `data` type. When `type` is `wav`, data is a `blob` object that represent the wav file. |
|
383 | | `stereopan` | `panvalue, track` | Pan value of `track` has been changed to `panvalue` |
|
384 |
|
385 | ## Tests
|
386 |
|
387 | `npm test`
|
388 |
|
389 | ## Development without example changes
|
390 |
|
391 | `npm install && npm start`
|
392 |
|
393 | This will install dependencies and start the webpack server.
|
394 |
|
395 | ## Development with example changes
|
396 |
|
397 | `gem install jekyll`
|
398 |
|
399 | Jekyll is needed if changes to the example pages will be done.
|
400 |
|
401 | `npm install && npm run dev`
|
402 |
|
403 | This will build and watch the jekyll site and startup the webpack dev server.
|
404 |
|
405 | ## Credits
|
406 |
|
407 | Originally created for the [Airtime](https://www.sourcefabric.org/software/airtime/) project at [Sourcefabric](https://www.sourcefabric.org/)
|
408 |
|
409 | The annotation plugin has been sponsored by a fond Italian TED volunteer transcriber, hoping to make easier and more fun the transcription process of TEDx talks.
|
410 |
|
411 | ## License
|
412 |
|
413 | [MIT License](http://doge.mit-license.org)
|