app.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /**
  2. * Create a WaveSurfer instance.
  3. */
  4. var wavesurfer;
  5. /**
  6. * Init & load.
  7. */
  8. document.addEventListener('DOMContentLoaded', function () {
  9. // Init wavesurfer
  10. wavesurfer = WaveSurfer.create({
  11. container: '#waveform',
  12. height: 100,
  13. pixelRatio: 1,
  14. scrollParent: true,
  15. normalize: true,
  16. minimap: true,
  17. backend: 'MediaElement',
  18. plugins: [
  19. WaveSurfer.regions.create(),
  20. WaveSurfer.minimap.create({
  21. height: 30,
  22. waveColor: '#ddd',
  23. progressColor: '#999',
  24. cursorColor: '#999'
  25. }),
  26. WaveSurfer.timeline.create({
  27. container: "#wave-timeline"
  28. })
  29. ]
  30. });
  31. wavesurfer.util.ajax({
  32. responseType: 'json',
  33. url: 'rashomon.json'
  34. }).on('success', function (data) {
  35. wavesurfer.load(
  36. 'http://www.archive.org/download/mshortworks_001_1202_librivox/msw001_03_rashomon_akutagawa_mt_64kb.mp3',
  37. data
  38. );
  39. });
  40. /* Regions */
  41. wavesurfer.on('ready', function () {
  42. wavesurfer.enableDragSelection({
  43. color: randomColor(0.1)
  44. });
  45. if (localStorage.regions) {
  46. loadRegions(JSON.parse(localStorage.regions));
  47. } else {
  48. // loadRegions(
  49. // extractRegions(
  50. // wavesurfer.backend.getPeaks(512),
  51. // wavesurfer.getDuration()
  52. // )
  53. // );
  54. wavesurfer.util.ajax({
  55. responseType: 'json',
  56. url: 'annotations.json'
  57. }).on('success', function (data) {
  58. loadRegions(data);
  59. saveRegions();
  60. });
  61. }
  62. });
  63. wavesurfer.on('region-click', function (region, e) {
  64. e.stopPropagation();
  65. // Play on click, loop on shift click
  66. e.shiftKey ? region.playLoop() : region.play();
  67. });
  68. wavesurfer.on('region-click', editAnnotation);
  69. wavesurfer.on('region-updated', saveRegions);
  70. wavesurfer.on('region-removed', saveRegions);
  71. wavesurfer.on('region-in', showNote);
  72. wavesurfer.on('region-play', function (region) {
  73. region.once('out', function () {
  74. wavesurfer.play(region.start);
  75. wavesurfer.pause();
  76. });
  77. });
  78. /* Toggle play/pause buttons. */
  79. var playButton = document.querySelector('#play');
  80. var pauseButton = document.querySelector('#pause');
  81. wavesurfer.on('play', function () {
  82. playButton.style.display = 'none';
  83. pauseButton.style.display = '';
  84. });
  85. wavesurfer.on('pause', function () {
  86. playButton.style.display = '';
  87. pauseButton.style.display = 'none';
  88. });
  89. });
  90. /**
  91. * Save annotations to localStorage.
  92. */
  93. function saveRegions() {
  94. localStorage.regions = JSON.stringify(
  95. Object.keys(wavesurfer.regions.list).map(function (id) {
  96. var region = wavesurfer.regions.list[id];
  97. return {
  98. start: region.start,
  99. end: region.end,
  100. attributes: region.attributes,
  101. data: region.data
  102. };
  103. })
  104. );
  105. }
  106. /**
  107. * Load regions from localStorage.
  108. */
  109. function loadRegions(regions) {
  110. regions.forEach(function (region) {
  111. region.color = randomColor(0.1);
  112. wavesurfer.addRegion(region);
  113. });
  114. }
  115. /**
  116. * Extract regions separated by silence.
  117. */
  118. function extractRegions(peaks, duration) {
  119. // Silence params
  120. var minValue = 0.0015;
  121. var minSeconds = 0.25;
  122. var length = peaks.length;
  123. var coef = duration / length;
  124. var minLen = minSeconds / coef;
  125. // Gather silence indeces
  126. var silences = [];
  127. Array.prototype.forEach.call(peaks, function (val, index) {
  128. if (Math.abs(val) <= minValue) {
  129. silences.push(index);
  130. }
  131. });
  132. // Cluster silence values
  133. var clusters = [];
  134. silences.forEach(function (val, index) {
  135. if (clusters.length && val == silences[index - 1] + 1) {
  136. clusters[clusters.length - 1].push(val);
  137. } else {
  138. clusters.push([ val ]);
  139. }
  140. });
  141. // Filter silence clusters by minimum length
  142. var fClusters = clusters.filter(function (cluster) {
  143. return cluster.length >= minLen;
  144. });
  145. // Create regions on the edges of silences
  146. var regions = fClusters.map(function (cluster, index) {
  147. var next = fClusters[index + 1];
  148. return {
  149. start: cluster[cluster.length - 1],
  150. end: (next ? next[0] : length - 1)
  151. };
  152. });
  153. // Add an initial region if the audio doesn't start with silence
  154. var firstCluster = fClusters[0];
  155. if (firstCluster && firstCluster[0] != 0) {
  156. regions.unshift({
  157. start: 0,
  158. end: firstCluster[firstCluster.length - 1]
  159. });
  160. }
  161. // Filter regions by minimum length
  162. var fRegions = regions.filter(function (reg) {
  163. return reg.end - reg.start >= minLen;
  164. });
  165. // Return time-based regions
  166. return fRegions.map(function (reg) {
  167. return {
  168. start: Math.round(reg.start * coef * 10) / 10,
  169. end: Math.round(reg.end * coef * 10) / 10
  170. };
  171. });
  172. }
  173. /**
  174. * Random RGBA color.
  175. */
  176. function randomColor(alpha) {
  177. return 'rgba(' + [
  178. ~~(Math.random() * 255),
  179. ~~(Math.random() * 255),
  180. ~~(Math.random() * 255),
  181. alpha || 1
  182. ] + ')';
  183. }
  184. /**
  185. * Edit annotation for a region.
  186. */
  187. function editAnnotation (region) {
  188. var form = document.forms.edit;
  189. form.style.opacity = 1;
  190. form.elements.start.value = Math.round(region.start * 10) / 10,
  191. form.elements.end.value = Math.round(region.end * 10) / 10;
  192. form.elements.note.value = region.data.note || '';
  193. form.onsubmit = function (e) {
  194. e.preventDefault();
  195. region.update({
  196. start: form.elements.start.value,
  197. end: form.elements.end.value,
  198. data: {
  199. note: form.elements.note.value
  200. }
  201. });
  202. form.style.opacity = 0;
  203. };
  204. form.onreset = function () {
  205. form.style.opacity = 0;
  206. form.dataset.region = null;
  207. };
  208. form.dataset.region = region.id;
  209. }
  210. /**
  211. * Display annotation.
  212. */
  213. function showNote (region) {
  214. if (!showNote.el) {
  215. showNote.el = document.querySelector('#subtitle');
  216. }
  217. showNote.el.textContent = region.data.note || '–';
  218. }
  219. /**
  220. * Bind controls.
  221. */
  222. GLOBAL_ACTIONS['delete-region'] = function () {
  223. var form = document.forms.edit;
  224. var regionId = form.dataset.region;
  225. if (regionId) {
  226. wavesurfer.regions.list[regionId].remove();
  227. form.reset();
  228. }
  229. };
  230. GLOBAL_ACTIONS['export'] = function () {
  231. window.open('data:application/json;charset=utf-8,' +
  232. encodeURIComponent(localStorage.regions));
  233. };