Browse Source

added audioplayer contrib module

Bachir Soussi Chiadmi 6 years ago
parent
commit
0942e9a212
100 changed files with 13705 additions and 0 deletions
  1. 14 0
      libraries/wavesurfer/.babelrc
  2. 11 0
      libraries/wavesurfer/.editorconfig
  3. 20 0
      libraries/wavesurfer/.esdoc.json
  4. 279 0
      libraries/wavesurfer/.eslintrc.js
  5. 26 0
      libraries/wavesurfer/.github/ISSUE_TEMPLATE.md
  6. 21 0
      libraries/wavesurfer/.github/PULL_REQUEST_TEMPLATE.md
  7. 13 0
      libraries/wavesurfer/.gitignore
  8. 7 0
      libraries/wavesurfer/.jscsrc
  9. 4 0
      libraries/wavesurfer/.npmignore
  10. 17 0
      libraries/wavesurfer/.travis.yml
  11. 26 0
      libraries/wavesurfer/CHANGES.md
  12. 1 0
      libraries/wavesurfer/CNAME
  13. 29 0
      libraries/wavesurfer/LICENSE
  14. 193 0
      libraries/wavesurfer/README.md
  15. 14 0
      libraries/wavesurfer/bower.json
  16. 45 0
      libraries/wavesurfer/example/angular-material/index.html
  17. 35 0
      libraries/wavesurfer/example/angular-material/main.css
  18. 21 0
      libraries/wavesurfer/example/angular-material/main.js
  19. 30 0
      libraries/wavesurfer/example/angular-material/md-player-audio.partial.html
  20. 70 0
      libraries/wavesurfer/example/angular-material/md-player.partial.html
  21. 415 0
      libraries/wavesurfer/example/angular-material/wavesurfer.directive.js
  22. 63 0
      libraries/wavesurfer/example/angular/app.js
  23. 120 0
      libraries/wavesurfer/example/angular/index.html
  24. 0 0
      libraries/wavesurfer/example/annotation/annotations.json
  25. 8 0
      libraries/wavesurfer/example/annotation/app.css
  26. 264 0
      libraries/wavesurfer/example/annotation/app.js
  27. 260 0
      libraries/wavesurfer/example/annotation/index.html
  28. 0 0
      libraries/wavesurfer/example/annotation/rashomon.json
  29. 133 0
      libraries/wavesurfer/example/audio-element/index.html
  30. 26 0
      libraries/wavesurfer/example/audio-element/main.js
  31. 189 0
      libraries/wavesurfer/example/css/doc.css
  32. 140 0
      libraries/wavesurfer/example/css/ribbon.css
  33. 110 0
      libraries/wavesurfer/example/css/style.css
  34. 76 0
      libraries/wavesurfer/example/cursor/index.html
  35. 25 0
      libraries/wavesurfer/example/cursor/main.js
  36. 194 0
      libraries/wavesurfer/example/elan-wave-segment/index.html
  37. 109 0
      libraries/wavesurfer/example/elan/app.js
  38. 25 0
      libraries/wavesurfer/example/elan/css/elan.css
  39. 105 0
      libraries/wavesurfer/example/elan/index.html
  40. BIN
      libraries/wavesurfer/example/elan/transcripts/001z.mp3
  41. 2866 0
      libraries/wavesurfer/example/elan/transcripts/001z.xml
  42. 92 0
      libraries/wavesurfer/example/equalizer/index.html
  43. 128 0
      libraries/wavesurfer/example/equalizer/main.js
  44. 84 0
      libraries/wavesurfer/example/html-init/index.html
  45. 108 0
      libraries/wavesurfer/example/main.js
  46. 168 0
      libraries/wavesurfer/example/media-session/index.html
  47. BIN
      libraries/wavesurfer/example/media/demo.wav
  48. BIN
      libraries/wavesurfer/example/media/demo_video.mp4
  49. 36 0
      libraries/wavesurfer/example/microphone/app.js
  50. 198 0
      libraries/wavesurfer/example/microphone/index.html
  51. 40 0
      libraries/wavesurfer/example/mute/app.js
  52. 75 0
      libraries/wavesurfer/example/mute/index.html
  53. 166 0
      libraries/wavesurfer/example/panner/index.html
  54. 68 0
      libraries/wavesurfer/example/panner/main.js
  55. BIN
      libraries/wavesurfer/example/panner/media.wav
  56. 67 0
      libraries/wavesurfer/example/playlist/app.js
  57. 112 0
      libraries/wavesurfer/example/playlist/index.html
  58. 113 0
      libraries/wavesurfer/example/plugin-system/app.js
  59. 188 0
      libraries/wavesurfer/example/plugin-system/index.html
  60. 42 0
      libraries/wavesurfer/example/regions/app.js
  61. 120 0
      libraries/wavesurfer/example/regions/index.html
  62. BIN
      libraries/wavesurfer/example/screenshot.png
  63. 52 0
      libraries/wavesurfer/example/spectrogram/app.js
  64. 138 0
      libraries/wavesurfer/example/spectrogram/index.html
  65. 55 0
      libraries/wavesurfer/example/split-channels/app.js
  66. 108 0
      libraries/wavesurfer/example/split-channels/index.html
  67. BIN
      libraries/wavesurfer/example/split-channels/stereo.mp3
  68. 157 0
      libraries/wavesurfer/example/split-wave-point-plot/index.html
  69. 55 0
      libraries/wavesurfer/example/timeline/app.js
  70. 136 0
      libraries/wavesurfer/example/timeline/index.html
  71. 72 0
      libraries/wavesurfer/example/trivia.js
  72. 145 0
      libraries/wavesurfer/example/video-element/index.html
  73. 28 0
      libraries/wavesurfer/example/video-element/main.js
  74. 122 0
      libraries/wavesurfer/example/zoom/index.html
  75. 54 0
      libraries/wavesurfer/example/zoom/main.js
  76. 65 0
      libraries/wavesurfer/karma.conf.js
  77. 55 0
      libraries/wavesurfer/package.json
  78. 108 0
      libraries/wavesurfer/spec/peakcache.spec.js
  79. 118 0
      libraries/wavesurfer/spec/plugin-api.spec.js
  80. BIN
      libraries/wavesurfer/spec/support/demo.wav
  81. 29 0
      libraries/wavesurfer/spec/util.spec.js
  82. 113 0
      libraries/wavesurfer/spec/wavesurfer.spec.js
  83. 357 0
      libraries/wavesurfer/src/drawer.js
  84. 529 0
      libraries/wavesurfer/src/drawer.multicanvas.js
  85. 221 0
      libraries/wavesurfer/src/html-init.js
  86. 312 0
      libraries/wavesurfer/src/mediaelement.js
  87. 116 0
      libraries/wavesurfer/src/peakcache.js
  88. 137 0
      libraries/wavesurfer/src/plugin/cursor.js
  89. 297 0
      libraries/wavesurfer/src/plugin/elan.js
  90. 350 0
      libraries/wavesurfer/src/plugin/microphone.js
  91. 326 0
      libraries/wavesurfer/src/plugin/minimap.js
  92. 622 0
      libraries/wavesurfer/src/plugin/regions.js
  93. 562 0
      libraries/wavesurfer/src/plugin/spectrogram.js
  94. 358 0
      libraries/wavesurfer/src/plugin/timeline.js
  95. 37 0
      libraries/wavesurfer/src/util/ajax.js
  96. 16 0
      libraries/wavesurfer/src/util/extend.js
  97. 13 0
      libraries/wavesurfer/src/util/frame.js
  98. 8 0
      libraries/wavesurfer/src/util/get-id.js
  99. 10 0
      libraries/wavesurfer/src/util/index.js
  100. 15 0
      libraries/wavesurfer/src/util/max.js

+ 14 - 0
libraries/wavesurfer/.babelrc

@@ -0,0 +1,14 @@
+{
+    "presets": [
+        ["es2015", "stage-0"]
+    ],
+    "plugins": [
+        "transform-class-properties",
+        "add-module-exports"
+    ],
+    "env": {
+        "test": {
+            "plugins": [ "istanbul" ]
+        }
+    }
+}

+ 11 - 0
libraries/wavesurfer/.editorconfig

@@ -0,0 +1,11 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 20 - 0
libraries/wavesurfer/.esdoc.json

@@ -0,0 +1,20 @@
+{
+  "source": "./src",
+  "destination": "./doc",
+  "title": "wavesurfer.js documentation",
+  "styles": ["example/css/doc.css"],
+  "lint": true,
+  "builtinExternal": true,
+  "experimentalProposal": {
+    "classProperties": true,
+    "objectRestSpread": true,
+    "exportExtensions": true
+  },
+  "test": {
+    "type": "mocha",
+    "source": "spec"
+  },
+  "manual": {
+    "changelog": ["CHANGES.md"]
+  }
+}

+ 279 - 0
libraries/wavesurfer/.eslintrc.js

@@ -0,0 +1,279 @@
+module.exports = {
+    "env": {
+        "browser": true,
+        "commonjs": true,
+        "es6": true
+    },
+    "extends": "eslint:recommended",
+    "parser": "babel-eslint",
+    "parserOptions": {
+        "ecmaVersion": 7,
+        "sourceType": "module",
+        "ecmaFeatures": {
+            "classes": true
+        },
+    },
+    "rules": {
+        "accessor-pairs": "error",
+        "array-bracket-spacing": "error",
+        "array-callback-return": "error",
+        "arrow-body-style": "error",
+        "arrow-parens": "off",
+        "arrow-spacing": [
+            "error",
+            {
+                "after": true,
+                "before": true
+            }
+        ],
+        "block-scoped-var": "off",
+        "block-spacing": [
+            "error",
+            "always"
+        ],
+        "brace-style": [
+            "error",
+            "1tbs",
+            {
+                "allowSingleLine": true
+            }
+        ],
+        "callback-return": "off",
+        "camelcase": "error",
+        "class-methods-use-this": "off",
+        "comma-dangle": "error",
+        "comma-spacing": [
+            "error",
+            {
+                "after": true,
+                "before": false
+            }
+        ],
+        "comma-style": [
+            "error",
+            "last"
+        ],
+        "complexity": "error",
+        "computed-property-spacing": [
+            "error",
+            "never"
+        ],
+        "consistent-return": "off",
+        "consistent-this": "off",
+        "curly": "error",
+        "default-case": "off",
+        "dot-location": "off",
+        "dot-notation": "error",
+        "eol-last": "error",
+        "eqeqeq": "off",
+        "func-call-spacing": "error",
+        "func-name-matching": "error",
+        "func-names": [
+            "error",
+            "never"
+        ],
+        "func-style": "off",
+        "generator-star-spacing": "error",
+        "global-require": "error",
+        "guard-for-in": "off",
+        "handle-callback-err": "error",
+        "id-blacklist": "error",
+        "id-length": "off",
+        "id-match": "error",
+        "indent": [2, 4, { "SwitchCase": 1 }],
+        "init-declarations": "off",
+        "jsx-quotes": "error",
+        "key-spacing": "off",
+        "keyword-spacing": [
+            "error",
+            {
+                "after": true,
+                "before": true
+            }
+        ],
+        "line-comment-position": "off",
+        "linebreak-style": [
+            "error",
+            "unix"
+        ],
+        "lines-around-comment": "off",
+        "lines-around-directive": "error",
+        "max-depth": "error",
+        "max-len": "off",
+        "max-lines": "off",
+        "max-nested-callbacks": "error",
+        "max-params": "off",
+        "max-statements": "off",
+        "max-statements-per-line": "off",
+        "multiline-ternary": "off",
+        "new-cap": "error",
+        "new-parens": "off",
+        "newline-after-var": "off",
+        "newline-before-return": "off",
+        "newline-per-chained-call": "off",
+        "no-redeclare": "error",
+        "no-unused-vars": "off",
+        "no-console": "off",
+        "no-alert": "error",
+        "no-array-constructor": "error",
+        "no-bitwise": "off",
+        "no-caller": "error",
+        "no-catch-shadow": "error",
+        "no-confusing-arrow": [
+            "error",
+            {
+                "allowParens": true
+            }
+        ],
+        "no-continue": "error",
+        "no-div-regex": "error",
+        "no-duplicate-imports": "error",
+        "no-else-return": "error",
+        "no-empty-function": "off",
+        "no-eq-null": "off",
+        "no-eval": "error",
+        "no-extend-native": "error",
+        "no-extra-bind": "error",
+        "no-extra-label": "error",
+        "no-extra-parens": "off",
+        "no-floating-decimal": "error",
+        "no-implicit-coercion": "off",
+        "no-implicit-globals": "error",
+        "no-implied-eval": "error",
+        "no-inline-comments": "off",
+        "no-inner-declarations": [
+            "error",
+            "functions"
+        ],
+        "no-invalid-this": "off",
+        "no-iterator": "error",
+        "no-label-var": "error",
+        "no-labels": "error",
+        "no-lone-blocks": "error",
+        "no-lonely-if": "error",
+        "no-loop-func": "error",
+        "no-magic-numbers": "off",
+        "no-mixed-operators": "off",
+        "no-mixed-requires": "error",
+        "no-multi-spaces": "error",
+        "no-multi-str": "error",
+        "no-multiple-empty-lines": "off",
+        "no-native-reassign": "error",
+        "no-negated-condition": "off",
+        "no-negated-in-lhs": "error",
+        "no-nested-ternary": "error",
+        "no-new": "error",
+        "no-new-func": "error",
+        "no-new-object": "error",
+        "no-new-require": "error",
+        "no-new-wrappers": "error",
+        "no-octal-escape": "error",
+        "no-param-reassign": "off",
+        "no-path-concat": "error",
+        "no-plusplus": "off",
+        "no-process-env": "error",
+        "no-process-exit": "error",
+        "no-proto": "error",
+        "no-prototype-builtins": "error",
+        "no-restricted-globals": "error",
+        "no-restricted-imports": "error",
+        "no-restricted-modules": "error",
+        "no-restricted-properties": "error",
+        "no-restricted-syntax": "error",
+        "no-return-assign": "error",
+        "no-script-url": "error",
+        "no-self-compare": "error",
+        "no-sequences": "error",
+        "no-shadow": "off",
+        "no-shadow-restricted-names": "error",
+        "no-spaced-func": "error",
+        "no-sync": "error",
+        "no-tabs": "off",
+        "no-template-curly-in-string": "error",
+        "no-ternary": "off",
+        "no-throw-literal": "off",
+        "no-trailing-spaces": "error",
+        "no-undef-init": "error",
+        "no-undefined": "off",
+        "no-underscore-dangle": "off",
+        "no-unmodified-loop-condition": "error",
+        "no-unneeded-ternary": "error",
+        "no-use-before-define": "error",
+        "no-useless-call": "error",
+        "no-useless-computed-key": "error",
+        "no-useless-concat": "error",
+        "no-useless-constructor": "off",
+        "no-useless-escape": "error",
+        "no-useless-rename": "error",
+        "no-useless-return": "error",
+        "no-var": "error",
+        "no-void": "error",
+        "no-warning-comments": "off",
+        "no-whitespace-before-property": "error",
+        "no-with": "error",
+        "object-curly-newline": "off",
+        "object-curly-spacing": "off",
+        "object-property-newline": "off",
+        "object-shorthand": [
+            "error",
+            "methods",
+            {
+                avoidQuotes: true
+            }
+        ],
+        "one-var": [
+            "error",
+            {
+                var: "never",
+                let: "never",
+                const: "never"
+            }
+        ],
+        "one-var-declaration-per-line": "error",
+        "operator-assignment": "off",
+        "operator-linebreak": "off",
+        "padded-blocks": "off",
+        "prefer-arrow-callback": "error",
+        "prefer-const": "error",
+        "prefer-numeric-literals": "error",
+        "prefer-reflect": "off",
+        "prefer-rest-params": "error",
+        "prefer-spread": "error",
+        "prefer-template": "off",
+        "quote-props": "off",
+        "quotes": [
+            "error",
+            "single"
+        ],
+        "radix": "off",
+        "require-jsdoc": "error",
+        "rest-spread-spacing": "error",
+        "semi": "error",
+        "semi-spacing": "error",
+        "sort-imports": "off",
+        "sort-keys": "off",
+        "sort-vars": "off",
+        "space-before-blocks": "off",
+        "space-before-function-paren": "off",
+        "space-in-parens": [
+            "error",
+            "never"
+        ],
+        "space-infix-ops": "off",
+        "space-unary-ops": "off",
+        "spaced-comment": "off",
+        "strict": "error",
+        "symbol-description": "error",
+        "template-curly-spacing": "error",
+        "unicode-bom": [
+            "error",
+            "never"
+        ],
+        "valid-jsdoc": "off",
+        "vars-on-top": "off",
+        "wrap-iife": "error",
+        "wrap-regex": "error",
+        "yield-star-spacing": "error",
+        "yoda": "off"
+    }
+};

+ 26 - 0
libraries/wavesurfer/.github/ISSUE_TEMPLATE.md

@@ -0,0 +1,26 @@
+# Hey, thank you for testing and contributing to wavesurfer.js!
+
+## Please make sure you can check all of these points below before opening an issue:
+
+(You don't have to post this section)
+
+- [ ] I have checked [the FAQ](https://wavesurfer-js.org/faq/) and it doesn't solve my problem.
+- [ ] I have checked [the documentation](https://wavesurfer-js.org/docs/) and it doesn't solve my problem
+- [ ] I have searched for [already open issues](https://github.com/katspaugh/wavesurfer.js/issues) which desribe my problem.
+- [ ] The issue I'm having is related to and caused by wavesurfer.js, not by other software (which maybe packages and uses wavesurfer incorrectly) – In that case you should open the issue on the respective project pages.
+
+## Please make sure you provide the following information (if applicable):
+
+### Wavesurfer.js version(s):
+
+
+### Browser version(s):
+
+
+### Code needed to reproduce the issue:
+
+(Please reduce your code as much as possible and only post the minimum code needed to reproduce the issue. [A Code pen](http://codepen.io/) is an excellent way to share such code)
+
+
+### Use behaviour needed to reproduce the issue:
+

+ 21 - 0
libraries/wavesurfer/.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,21 @@
+# Hey, thank you for contributing to wavesurfer.js!
+
+To review/merge open PRs it is very helpful to know as much as possible about the changes which are being introduced. Reviewing PRs is very time consuming, please be patient, it can take some time to do properly.
+
+**Title:** Please make sure the name of your PR is as descriptive as possible (Describe the feature that is introduced or the bug that is being fixed).
+
+## Please make sure you provide the information below:
+
+### Short description of changes:
+
+
+### Breaking in the external API:
+
+
+### Breaking changes in the internal API:
+
+
+### Todos/Notes:
+
+
+### Related Issues and other PRs:

+ 13 - 0
libraries/wavesurfer/.gitignore

@@ -0,0 +1,13 @@
+dist/wavesurfer.dev.js
+dist/plugin/*.js
+!/dist/plugin/*.min.js
+dist/**/*.map
+/node_modules
+/bower_components
+/npm-debug.log
+/.grunt
+/coverage
+/_SpecRunner.html
+.DS_Store
+.idea
+.project

+ 7 - 0
libraries/wavesurfer/.jscsrc

@@ -0,0 +1,7 @@
+{
+    "disallowTrailingWhitespace": true,
+    "disallowMixedSpacesAndTabs": true,
+    "requirePaddingNewLinesAfterUseStrict": true,
+    "requireSpaceAfterComma": true,
+    "requireSpaceBetweenArguments": true
+}

+ 4 - 0
libraries/wavesurfer/.npmignore

@@ -0,0 +1,4 @@
+# exclude everything except dist and src
+**/*
+!dist/**
+!src/**

+ 17 - 0
libraries/wavesurfer/.travis.yml

@@ -0,0 +1,17 @@
+sudo: required
+dist: trusty
+language: node_js
+node_js:
+  - "6.9.4"
+before_install:
+  - export CHROME_BIN=chromium-browser
+  - export DISPLAY=:99.0
+  - sh -e /etc/init.d/xvfb start
+install:
+  - npm prune
+  - npm install
+  - npm update
+script:
+  - npm run test
+  - npm run build
+  - npm run doc

+ 26 - 0
libraries/wavesurfer/CHANGES.md

@@ -0,0 +1,26 @@
+wavesurfer.js changelog
+=======================
+
+1.2.4 (11.11.2016)
+------------------
+- Fix a problem of Web Audio not playing in Safari on initial load (#749)
+
+1.2.3 (09.11.2016)
+------------------
+
+- Add a 'waveform-ready' event, triggered when waveform is drawn with MediaElement backend (#736)
+- Add a 'preload' parameter to load function to choose the preload HTML5 audio attribute value if MediaElement backend is choosen (#854)
+
+1.2.2 (31.10.2016)
+------------------
+
+- Determistic way to mute and unmute a track (#841)
+- Replace jasmine with karma / jasmine test suite (#849)
+- Regions plugin: fix a bug when clicking on scroll-bar in Firefox (#851)
+
+1.2.1 (01.10.2016)
+------------------
+
+- Added changelog (#824)
+- Correct AMD module name for plugins (#831)
+- Fix to remove small gaps between regions (#834)

+ 1 - 0
libraries/wavesurfer/CNAME

@@ -0,0 +1 @@
+wavesurfer-js.org

+ 29 - 0
libraries/wavesurfer/LICENSE

@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2012-2017, katspaugh and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 193 - 0
libraries/wavesurfer/README.md

@@ -0,0 +1,193 @@
+# [wavesurfer.js](https://wavesurfer-js.org)
+
+# Read below how to update to version 2!
+
+[![npm version](https://img.shields.io/npm/v/wavesurfer.js.svg?style=flat)](https://www.npmjs.com/package/wavesurfer.js)
+![npm](https://img.shields.io/npm/dm/wavesurfer.js.svg) [![Join the chat at https://gitter.im/katspaugh/wavesurfer.js](https://badges.gitter.im/katspaugh/wavesurfer.js.svg)](https://gitter.im/katspaugh/wavesurfer.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+Interactive navigable audio visualization using Web Audio and Canvas.
+
+[![Screenshot](https://raw.githubusercontent.com/katspaugh/wavesurfer.js/gh-pages/example/screenshot.png "Screenshot")](https://wavesurfer-js.org)
+
+See a [tutorial](https://wavesurfer-js.org/docs) and [examples](https://wavesurfer-js.org/examples) on [wavesurfer-js.org](https://wavesurfer-js.org).
+
+## Browser support
+wavesurfer.js works only in [modern browsers supporting Web Audio](http://caniuse.com/audio-api).
+
+It will fallback to Audio Element in other browsers (without graphics). You can also try [wavesurfer.swf](https://github.com/laurentvd/wavesurfer.swf) which is a Flash-based fallback.
+
+## FAQ
+### Can the audio start playing before the waveform is drawn?
+Yes, if you use the `backend: 'MediaElement'` option. See here: https://wavesurfer-js.org/example/audio-element/. The audio will start playing as you press play. A thin line will be displayed until the whole audio file is downloaded and decoded to draw the waveform.
+
+### Can drawing be done as file loads?
+No. Web Audio needs the whole file to decode it in the browser. You can however load pre-decoded waveform data to draw the waveform immediately. See here: https://wavesurfer-js.org/example/audio-element/ (the "Pre-recoded Peaks" section).
+
+## API in examples
+
+Choose a container:
+```html
+<div id="waveform"></div>
+```
+Create an instance, passing the container selector and [options](https://wavesurfer-js.org/docs/options.html):
+
+```javascript
+var wavesurfer = WaveSurfer.create({
+    container: '#waveform',
+    waveColor: 'violet',
+    progressColor: 'purple'
+});
+```
+
+Subscribe to some [events](https://wavesurfer-js.org/docs/events.html):
+
+```javascript
+wavesurfer.on('ready', function () {
+    wavesurfer.play();
+});
+```
+
+Load an audio file from a URL:
+
+```javascript
+wavesurfer.load('example/media/demo.wav');
+```
+
+## Documentation
+
+See the documentation on all available [methods](https://wavesurfer-js.org/docs/methods.html), [options](https://wavesurfer-js.org/docs/options.html) and [events](https://wavesurfer-js.org/docs/events.html) on the [homepage](https://wavesurfer-js.org/docs/).
+
+**Note on version 2**: The wavesurfer.js core library and the plugins were refactored to be modular so it can be used with a module bundler. (You can still use wavesurfer without, e.g. with `<script>` tags) The code was also updated to ES6/ES7 syntax and is transpiled with babel and webpack. Read below how to update your code.
+
+## Upgrading to version 2
+
+The API has mostly stayed the same but there are some changes to consider:
+
+1. **MultiCanvas renderer is now the default:** It provides all functionality of the Canvas renderer. – Most likely you can simply remove the renderer option – The Canvas renderer has been removed. (The `renderer` option still exists but wavesurfer expects it to be a renderer object, not merely a string.)
+
+2. **Constructor functions instead of object constructors**
+
+```javascript
+// Old:
+var wavesurfer = Object.create(WaveSurfer);
+Wavesurfer.init(options);
+
+// New:
+var wavesurfer = WaveSurfer.create(options);
+// ... or
+var wavesurfer = new WaveSurfer(options);
+wavesurfer.init();
+```
+
+3. **New plugin API:** Previously all plugins had their own initialisation API. The new API replaces all these different ways to do the same thing with one plugin API built into the core library. Plugins are now added as a property of the wavesurfer configuration object during creation. You don't need to initialise the plugins yourself anymore. Below is an example of initialising wavesurfer with plugins (Note the different ways to import the library at the top):
+
+```javascript
+// EITHER - accessing modules with <script> tags
+var WaveSurfer = window.WaveSurfer;
+var TimelinePlugin = window.WaveSurfer.timeline;
+var MinimapPlugin = window.WaveSurfer.minimap;
+
+// OR - importing as es6 module
+import WaveSurfer from 'wavesurfer.js';
+import TimelinePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.timeline.min.js';
+import MinimapPlugin from 'wavesurfer.js/dist/plugin/wavesurfer.minimap.min.js';
+
+// OR - importing as require.js/commonjs modules
+var WaveSurfer = require('wavesurfer.js');
+var TimelinePlugin = require('wavesurfer.js/dist/plugin/wavesurfer.timeline.min.js');
+var MinimapPlugin = require('wavesurfer.js/dist/plugin/wavesurfer.minimap.min.js');
+
+// ... initialising waveform with plugins
+var wavesurfer = WaveSurfer.create({
+    container: '#waveform',
+    waveColor: 'violet',
+    plugins: [
+        TimelinePlugin.create({
+            container: '#wave-timeline'
+        }),
+        MinimapPlugin.create()
+    ]
+});
+```
+
+**Note:** Read more about the plugin API in the documentation.
+
+## Using with a module bundler
+
+Wavesurfer can be used with a module system like this:
+```javascript
+// import
+import WaveSurfer from 'wavesurfer.js';
+
+// commonjs/requirejs
+var WaveSurfer = require('wavesurfer.js');
+
+// amd
+define(['WaveSurfer'], function(WaveSurfer) {
+  // ... code
+});
+
+```
+
+## Related projects
+
+For a list of  projects using wavesurfer.js, check out
+[the projects page](https://wavesurfer-js.org/projects/).
+
+## Development
+
+[![npm version](https://img.shields.io/npm/v/wavesurfer.js.svg?style=flat)](https://www.npmjs.com/package/wavesurfer.js)
+[![npm](https://img.shields.io/npm/dm/wavesurfer.js.svg)]()
+[![Build Status](https://travis-ci.org/katspaugh/wavesurfer.js.svg?branch=master)](https://travis-ci.org/katspaugh/wavesurfer.js)
+
+Install development dependencies:
+
+```
+npm install
+```
+Development tasks automatically rebuild certain parts of the library when files are changed (`start` – wavesurfer, `start:plugins` – plugins). Start a dev task and go to `localhost:8080/example/` to test the current build.
+
+Start development server for core library:
+
+```
+npm run start
+```
+
+Start development server for plugins:
+
+```
+npm run start:plugins
+```
+
+Build all the files. (generated files are placed in the `dist` directory.)
+
+```
+npm run build
+```
+
+Running tests only:
+
+```
+npm run test
+```
+
+Build documentation with esdoc (generated files are placed in the `doc` directory.)
+```
+npm run doc
+```
+
+## Editing documentation
+The homepage and documentation files are maintained in the [`gh-pages` branch](https://github.com/katspaugh/wavesurfer.js/tree/gh-pages). Contributions to the documentation are especially welcome.
+
+## Credits
+
+Initial idea by [Alex Khokhulin](https://github.com/xoxulin). Many
+thanks to
+[the awesome contributors](https://github.com/katspaugh/wavesurfer.js/contributors)!
+
+## License
+
+[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
+
+This work is licensed under a
+[BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause).

+ 14 - 0
libraries/wavesurfer/bower.json

@@ -0,0 +1,14 @@
+{
+  "name": "wavesurfer.js",
+  "version": "2.0.0-beta01",
+  "homepage": "http://wavesurfer-js.org",
+  "authors": [
+    "katspaugh <katspaugh@gmail.com>"
+  ],
+  "description": "Navigable waveform built on Web Audio and Canvas",
+  "main": "dist/wavesurfer.js",
+  "keywords": [
+    "waveform", "audio", "music", "player"
+  ],
+  "license": "BSD-3-Clause"
+}

+ 45 - 0
libraries/wavesurfer/example/angular-material/index.html

@@ -0,0 +1,45 @@
+<html ng-app="mdWavesurferApp">
+<head>
+    <title>wavesurfer.js | Angular Material</title>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link href='http://fonts.googleapis.com/css?family=Roboto:400,500' rel='stylesheet' type='text/css'>
+    <link href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css'
+          rel='stylesheet' type='text/css'>
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.9.1/styles/github.min.css" rel="stylesheet">
+    <link href="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.0-rc3/angular-material.min.css"
+          rel="stylesheet" type="text/css"/>
+
+    <link rel="stylesheet" href="main.css"/>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular-animate.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular-aria.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-material/1.0.0-rc3/angular-material.min.js"></script>
+    <script type="text/javascript" src="../../dist/wavesurfer.js"></script>
+    <script type="text/javascript" src="wavesurfer.directive.js"></script>
+    <script type="text/javascript" src="main.js"></script>
+
+</head>
+<body ng-controller="MainController">
+  <md-wavesurfer-audio player-wave-color="gray" player-progress-color="black"
+                       player-backend="MediaElement">
+    <!-- This is analogous to HTML <source> element -->
+    <md-wavesurfer-source src="../media/demo.wav" title="czskamaarù – Trou"></md-wavesurfer-source>
+    <md-wavesurfer-source src="../panner/media.wav" title="日本人の話し"></md-wavesurfer-source>
+    <md-wavesurfer-source src="../elan/transcripts/001z.mp3"
+                          title="Рассказы о сновидениях"></md-wavesurfer-source>
+    <!-- <md-wavesurfer-source -->
+    <!--    src="http://download.wavetlan.com/SVV/Media/HTTP/MP3/Nero_SmartTrax/NeroSmartTrax_test1_MPEG2_Stereo_CBR_48kbps_22050Hz.mp3" -->
+    <!--    title="Remote: Nero SmartTrax- Test 1"></md-wavesurfer-source> -->
+    <!-- <md-wavesurfer-source -->
+    <!--    src="http://download.wavetlan.com/SVV/Media/HTTP/MP3/Nero_SmartTrax/NeroSmartTrax_test2_MPEG1_Mono_CBR_64kbps_44100Hz.mp3" -->
+    <!--    title="Remote: Nero SmartTrax- Test 2"></md-wavesurfer-source> -->
+
+    <!-- Long list, using previously loaded files, need to test container.-->
+    <md-wavesurfer-source ng-repeat="item in longList" src="{{item.url}}"
+                          title="{{item.title}}"></md-wavesurfer-source>
+
+
+  </md-wavesurfer-audio>
+</body>
+</html>

+ 35 - 0
libraries/wavesurfer/example/angular-material/main.css

@@ -0,0 +1,35 @@
+.text-center {
+    text-align: center !important;
+}
+
+.text-left {
+    text-align: left !important;;
+}
+
+.text-right {
+    text-align: right !important;;
+}
+
+.text-justify {
+    text-align: justify !important;;
+}
+
+md-icon[md-font-icon] {
+    font-size: 24px;
+}
+
+md-toolbar.md-toolbar-sm .md-toolbar-tools {
+    max-height: 32px;
+    padding: 6px;
+    font-size: 16px;
+}
+
+md-toolbar.md-toolbar-sm {
+    min-height: 24px;
+}
+.md-player-controls .md-button {
+    min-width: 44px;
+}
+
+
+

+ 21 - 0
libraries/wavesurfer/example/angular-material/main.js

@@ -0,0 +1,21 @@
+/**
+ * Created by intelWorx on 19/11/2015.
+ */
+(function () {
+  'use strict';
+  angular.module('mdWavesurferApp', ['mdWavesurfer'])
+    .config(function ($mdIconProvider) {
+      //$mdIconProvider.fontSet('zmdi', 'fontawesome');
+    })
+    .controller('MainController', ['$scope',
+      function ($scope) {
+        $scope.urls = ["../media/demo.wav", "../panner/media.wav", "../elan/transcripts/001z.mp3"];
+        $scope.longList = [];
+        for (var i = 0; i < 100; i++) {
+          $scope.longList.push({
+            title: 'Long List test: ' + i,
+            url: $scope.urls[Math.floor(3 * Math.random())]
+          });
+        }
+      }])
+})();

+ 30 - 0
libraries/wavesurfer/example/angular-material/md-player-audio.partial.html

@@ -0,0 +1,30 @@
+<md-card layout="column">
+    <div ng-transclude=""></div>
+    <md-toolbar class="md-toolbar-sm">
+        <div class="text-center" class="md-toolbar-tools">
+            <h2 style="font-size: 24px;">Audio Player</h2>
+        </div>
+    </md-toolbar>
+    <md-list layout="column">
+        <md-virtual-repeat-container style="height: 250px">
+            <md-list-item md-virtual-repeat="track in audio.tracks" md-start-index="audio.selectedIndex"
+                          ng-click="audio.setTrack($index, true)">
+                <p>
+                    {{$index+1}}. {{track.title}}
+                </p>
+
+                <div class="text-right md-secondary">
+                    {{track.duration | mdWavesurferTimeFormat}}
+                </div>
+                <md-divider ng-if="$index < audio.tracks.length-1"></md-divider>
+            </md-list-item>
+        </md-virtual-repeat-container>
+    </md-list>
+    <md-wavesurfer-player
+            url="{{audio.currentTrack.src}}#{{audio.selectedIndex}}"
+            title="{{audio.selectedIndex+1}}. {{audio.currentTrack.title}}"
+            extra-buttons="audio.extraButtons"
+            properties="audio.playerProperties"
+    >
+    </md-wavesurfer-player>
+</md-card>

+ 70 - 0
libraries/wavesurfer/example/angular-material/md-player.partial.html

@@ -0,0 +1,70 @@
+<div layout="column" class="audioPlayerWrapper">
+    <md-toolbar class="md-toolbar-sm {{control.themeClass}} {{control.toolbarClass}} " style="color: #333333; background:
+    none;">
+        <div class="md-toolbar-tools" layout="row">
+            <div flex="initial" layout-align="center start">
+                {{control.currentTime | mdWavesurferTimeFormat}}
+            </div>
+            <div flex="grow" class="text-center">
+                {{control.title}}
+            </div>
+            <div flex="initial" layout-align="center end" class="text-right">
+                {{control.surfer.getDuration() | mdWavesurferTimeFormat}}
+            </div>
+        </div>
+    </md-toolbar>
+    <md-divider></md-divider>
+    <md-content>
+        <div class="waveSurferWave"></div>
+    </md-content>
+    <md-divider md-inset></md-divider>
+    <md-toolbar class="{{control.themeClass}} {{control.toolbarClass}} md-player-controls">
+        <div layout="row" class="md-toolbar-tools">
+            <div flex="initial" layout-align="center start" ng-show="control.extraButtons.length">
+                <md-button ng-click="btn.action()"
+                           ng-repeat="btn in control.extraButtons" class="{{control.themeClass}} {{btn.class}}">
+                    <md-tooltip>
+                        {{btn.title}}
+                    </md-tooltip>
+                    <md-icon md-font-icon="{{btn.icon}}"></md-icon>
+                </md-button>
+            </div>
+
+            <div flex layout="row" layout-align="center center">
+                <md-button ng-click="control.surfer.skipBackward()" type="button"
+                           ng-disabled="!control.surfer.isPlaying()">
+                    <md-tooltip>
+                        Rewind
+                    </md-tooltip>
+                    <md-icon md-font-icon="zmdi zmdi-fast-rewind"></md-icon>
+                </md-button>
+
+                <md-button ng-disabled="!control.isReady" type="button"
+                           ng-click="control.surfer.playPause()">
+                    <md-tooltip>
+                        {{control.surfer.isPlaying() ? 'Pause' : 'Play'}}
+                    </md-tooltip>
+                    <md-icon ng-show="control.surfer.isPlaying()" md-font-icon="zmdi zmdi-pause"></md-icon>
+                    <md-icon ng-show="!control.surfer.isPlaying()" md-font-icon="zmdi zmdi-play"></md-icon>
+                </md-button>
+
+                <md-button type="button" ng-click="control.surfer.skipForward()"
+                           ng-disabled="!control.surfer.isPlaying()">
+                    <md-tooltip>
+                        Skip forward
+                    </md-tooltip>
+                    <md-icon md-font-icon="zmdi zmdi-fast-forward"></md-icon>
+                </md-button>
+            </div>
+            <div flex="initial" layout-align="center end" >
+                <md-button type="button" ng-click="control.toggleMute()" ng-disabled="!control.surfer.isPlaying()">
+                    <md-tooltip>
+                        Toggle mute
+                    </md-tooltip>
+                    <md-icon md-font-icon="zmdi zmdi-volume-off" ng-show="control.isMute"></md-icon>
+                    <md-icon md-font-icon="zmdi zmdi-volume-up" ng-show="!control.isMute"></md-icon>
+                </md-button>
+            </div>
+        </div>
+    </md-toolbar>
+</div>

+ 415 - 0
libraries/wavesurfer/example/angular-material/wavesurfer.directive.js

@@ -0,0 +1,415 @@
+/**
+ * Created by intelWorx on 19/11/2015.
+ */
+(function () {
+  'use strict';
+
+  /**
+   * Main module, your application should depend on this
+   * @module {mdWavesurfer}
+   */
+  var app = angular.module('mdWavesurfer', ['ngMaterial']);
+
+  /**
+   * @ngdoc service
+   * @name $mdWavesurferUtils
+   *
+   * @description
+   *
+   * Utility service for this directive, exposes method:
+   *  - getLength(url), which returns a promise for the length of the audio specified by URL
+   *
+   * ```js
+   * app.directive('myFancyDirective', function(mdWavesurferUtils) {
+   *   return {
+   *     restrict: 'e',
+   *     link: function(scope, el, attrs) {
+   *       mdWavesurferUtils(attrs.url)
+   *       .then(function(l){
+   *        scope.length = l;
+   *       }, function(){
+   *          someErrorhandler()
+   *       })
+   *       ;
+   *     }
+   *   };
+   * });
+   * ```
+   */
+  app.factory('mdWavesurferUtils', ['$q', '$document', '$timeout',
+    function ($q, $document, $timeout) {
+      return {
+        getLength: function (object) {
+          var deferred = $q.defer();
+          var estimateLength = function (url) {
+            var audio = $document[0].createElement('audio');
+            audio.src = url;
+            audio.addEventListener('loadeddata', function listener() {
+              deferred.resolve(this.duration);
+              audio.removeEventListener('loadeddata', listener);
+              audio.src = 'data:audio/mpeg,0';//destroy loading.
+            });
+
+            audio.addEventListener('error', function (e) {
+              deferred.resolve(e.target.error);
+            });
+          };
+
+          if (typeof object === 'string') {
+            //this is a URL
+            estimateLength(object);
+          } else {
+            $timeout(function () {
+              deferred.reject(new DOMError("NotSupportedError", "Specified argument is not supported"));
+            });
+          }
+
+          return deferred.promise;
+        }
+      };
+    }
+  ]);
+
+  /**
+   * @ngdoc filter
+   * @name mdWavesurferTimeFormat
+   *
+   * Simple filter to convert value in seconds to MM:SS format
+   *
+   * @param Number duration in seconds
+   */
+  app.filter('mdWavesurferTimeFormat', function () {
+    return function (input) {
+      if (!input) {
+        return "00:00";
+      }
+
+      var minutes = Math.floor(input / 60);
+      var seconds = Math.ceil(input) % 60;
+
+      return (minutes < 10 ? '0' : '')
+        + minutes
+        + ":"
+        + (seconds < 10 ? '0' : '') + seconds;
+    };
+  });
+
+
+  app.controller('mdWavesurferAudioController', ['$attrs', '$element',
+    function (attributes, $element) {
+      var audio = this;
+
+      audio.tracks = [];
+      audio.selectedIndex = audio.selectedIndex || 0;
+      audio.currentTrack = null;
+
+      //adds to an audio track
+      audio.addTrack = function (trackScope) {
+        if (audio.tracks.indexOf(trackScope) < 0) {
+          audio.tracks.push(trackScope);
+        }
+
+        if (!audio.currentTrack) {
+          audio.currentTrack = audio.tracks[audio.selectedIndex];
+        }
+      };
+
+      //remove audio track
+      audio.removeTrack = function (trackScope) {
+        var idx = audio.tracks.indexOf(trackScope);
+        if (idx >= 0) {
+          audio.tracks.splice(idx, 1);
+        }
+      };
+
+      audio.playerProperties = {}
+      var nKey;
+      for (var attr in attributes) {
+        if (attr.match(/^player/)) {
+          nKey = attr.replace(/^player([A-Z])/, function (m, $1) {
+            return $1.toLowerCase();
+          });
+          audio.playerProperties[nKey] = attributes[attr];
+        }
+      }
+
+      var getPlayer = function(){
+        return $element.find('md-wavesurfer-player').controller('mdWavesurferPlayer');
+      };
+      var setAutoPlay = function (forcePlay) {
+        var controller = getPlayer();
+        if (controller && (forcePlay || controller.surfer.isPlaying())) {
+          controller.autoPlay = true;
+        }
+      };
+      audio.setTrack = function (idx, forcePlay) {
+        if (audio.tracks.length > idx) {
+          if (audio.selectedIndex === idx) {
+            var ctrl = getPlayer();
+            ctrl.surfer.playPause();
+          } else {
+            setAutoPlay(forcePlay);
+            audio.currentTrack = audio.tracks[idx];
+            audio.selectedIndex = idx;
+          }
+        }
+      };
+
+      audio.extraButtons = [{
+        icon: 'zmdi zmdi-skip-previous',
+        title: 'Previous',
+        action: function ($event) {
+          if (audio.selectedIndex > 0) {
+            audio.setTrack(audio.selectedIndex - 1);
+          }
+        },
+        class: ''
+      }, {
+        icon: 'zmdi zmdi-skip-next',
+        title: 'Next',
+        action: function ($event) {
+          if (audio.selectedIndex < audio.tracks.length - 1) {
+            audio.setTrack(audio.selectedIndex + 1);
+          }
+        },
+        class: ''
+      }];
+
+
+    }
+  ]);
+
+  /**
+   * @ngdoc directive
+   * @name md-wavesurfer-audio
+   *
+   * Directive for playing a set of audio files. This directive is analogous to `<audio>` HTML tag.
+   * The audio files, should be specified using the  `md-wavesurfer-source`
+   *
+   * WaveSurfer properties can be passed in using the prefix : player-* for attributes, e.g. `player-wave-color` is
+   * equivalent to WaveSurfer's waveColor option.
+   *
+   * Must be used as an element.
+   *
+   * @usage
+   * ```html
+   * <md-wavesurfer-audio player-wave-color="gray" player-progress-color="black" player-backend="MediaElement">
+   *   <md-wavesurfer-source src="source1" title="Title-1"></md-wavesurfer-source>
+   *   <md-wavesurfer-source src="source2" title="Title-2"></md-wavesurfer-source>
+   *   <md-wavesurfer-source src="source3" title="Title-3"></md-wavesurfer-source>
+   *   ...
+   *   <md-wavesurfer-source src="sourceN" title="Рассказы о сновидениях"></md-wavesurfer-source>
+   * </md-wavesurfer-audio>
+   * ```
+   *
+   * @param string player-* specifies WaveSurfer properties.
+   *
+   */
+  app.directive('mdWavesurferAudio', [
+    function () {
+      return {
+
+        restrict: 'E',
+        templateUrl: 'md-player-audio.partial.html',
+        transclude: true,
+        controller: 'mdWavesurferAudioController',
+        controllerAs: 'audio'
+      };
+    }
+  ]);
+
+  /**
+   * @ngdoc directive
+   *
+   * @name md-wavesurfer-source
+   *
+   * This directive is used within the `md-wavesurfer-audio` directive to specify an audio file source, it is
+   * synonymous to `<source>` tag in HTML
+   *
+   * The directive cannot be used as standalone.
+   *
+   * @usage
+   *
+   * ```html
+   *   <md-wavesurfer-source src="source3" title="Title-3" album-art="Album-Art-Url" duration=""></md-wavesurfer-source>
+   * ```
+   * @param String src the URL to the audio file, this is required.
+   * @param String title track title
+   * @param String album-art the album art URL
+   * @param Number duration the length of the audio file in seconds, will be auto-detected if not specified.
+   *
+   */
+  app.directive('mdWavesurferSource', ['mdWavesurferUtils',
+    function (mdWavesurferUtils) {
+      return {
+        restrict: 'E',
+        require: '^mdWavesurferAudio',
+        scope: {
+          src: '@',
+          albumArt: '@',
+          title: '@',
+          duration: '='
+        },
+        link: function (scope, element, attrs, audio) {
+          audio.addTrack(scope);
+
+          if (!scope.duration) {
+            mdWavesurferUtils.getLength(scope.src).then(function (dur) {
+              scope.duration = dur;
+            }, function (e) {
+              scope.duration = 0;
+              console.log('Failed to get audio length, reason: ', e.message);
+            });
+          }
+
+          element.on('$destroy', function () {
+            audio.removeTrack(audio);
+          });
+        }
+      };
+    }
+  ]);
+
+  app.controller('mdWavesurferPlayerController', ['$element', '$scope', '$attrs', '$interval', '$mdTheming',
+    function ($element, $scope, attributes, $interval, $mdTheme) {
+      var control = this, timeInterval;
+
+      control.themeClass = "md-" + $mdTheme.defaultTheme() + "-theme";
+      control.isReady = false;
+      control.surfer = null;
+
+      control.toggleMute = function () {
+        if (control.surfer) {
+          control.surfer.toggleMute();
+          control.isMute = !control.isMute;
+        }
+      };
+
+      var initWaveSurfer = function () {
+        control.isReady = false;
+        control.currentTime = 0;
+        if (!control.surfer) {
+          var options = {
+            container: $element[0].querySelector('.waveSurferWave')
+          }, defaults = {
+            scrollParent: true,
+            waveColor: 'violet',
+            progressColor: 'purple'
+          };
+
+          options = angular.extend(defaults, attributes, (control.properties || {}), options);
+          control.surfer = WaveSurfer.create(options);
+
+          control.surfer.on('ready', function () {
+            control.isReady = true;
+            if (control.autoPlay) {
+              control.surfer.play();
+            }
+            $scope.$apply();
+          });
+
+          control.surfer.on('pause', function () {
+            stopInterval();
+          });
+
+          control.surfer.on('finish', function () {
+            stopInterval();
+          });
+
+          control.surfer.on('play', function () {
+            startInterval();
+          });
+
+        }
+
+        control.title = control.title || control.src.split('/').pop();
+        control.surfer.load(control.src);
+      };
+
+      var startInterval = function () {
+        timeInterval = $interval(function () {
+          control.currentTime = control.isReady ? control.surfer.getCurrentTime() : 0;
+        }, 1000);
+      }, stopInterval = function () {
+        $interval.cancel(timeInterval);
+      };
+
+
+      initWaveSurfer();
+
+      $scope.$watch('control.src', function (src1, src2) {
+        if (src1 != src2) {
+          initWaveSurfer();
+        }
+      });
+
+      $element.on('$destroy', function () {
+        if (control.surfer) {
+          control.surfer.destroy();
+        }
+        stopInterval();
+      });
+
+      $scope.$watch(function () {
+        var div = $element[0].querySelector('.audioPlayerWrapper');
+        return div ? div.offsetWidth : 0;
+      }, function (width) {
+        if (width < 1) {
+          //hidden
+          control.surfer.pause();
+        }
+      });
+    }
+  ]);
+
+  /**
+   * @ngdoc directive
+   *
+   * @name md-wavesurfer-player
+   *
+   * @usage
+   * This directive can be used as a stand-alone directive to display Audio WaveSurfer with a few controls, by default
+   * this will only display play/pause, fast-forward, rewind and mute toggle buttons, however, you can add extra
+   * buttons using the `extra-buttons` parameters.
+   *
+   * ```html
+   *  <md-wavesurfer-player url="trackUrl" title="Track Title"
+   *         extra-buttons="extraButtons" properties="properties">
+   *  </md-wavesurfer-player>
+   * ```
+   *
+   * @param {string} url the URL of the audio file
+   * @param {string} title title of the audio track
+   * @param {object} properties an object specifying init options for WaveSurfer
+   * @param {boolean} auto-play specifies if the player should start as soon as it's loaded.
+   * @param {object[]} extra-buttons a list of extra buttons to add to the control panel
+   *    each button should be an object with the following properties:
+   *    {
+   *      title: "button title"
+   *      action: "call back to call when button is clicked, executed in parent scope",
+   *      icon: "md-font-icon parameter for the button"
+   *      class: "extra classes to add to the button."
+   *    }
+   *
+   * Every other attribute passed to this directive is assumed to a WaveSurver init parameter.
+   */
+  app.directive('mdWavesurferPlayer', function () {
+    return {
+      restrict: 'E',
+      templateUrl: 'md-player.partial.html',
+      scope: {
+        src: '@url',
+        title: '@',
+        extraButtons: '=',
+        toolbarClass: '@',
+        autoPlay: '=',
+        properties: '='
+      },
+      controller: 'mdWavesurferPlayerController',
+      controllerAs: 'control',
+      bindToController: true
+    };
+  });
+  ;
+})();
+

+ 63 - 0
libraries/wavesurfer/example/angular/app.js

@@ -0,0 +1,63 @@
+var app = angular.module('ngWavesurfer', []);
+
+app.directive('ngWavesurfer', function () {
+    return {
+        restrict: 'E',
+
+        link: function ($scope, $element, $attrs) {
+            $element.css('display', 'block');
+
+            var options = angular.extend({ container: $element[0] }, $attrs);
+            var wavesurfer = WaveSurfer.create(options);
+
+            if ($attrs.url) {
+                wavesurfer.load($attrs.url, $attrs.data || null);
+            }
+
+            $scope.$emit('wavesurferInit', wavesurfer);
+        }
+    };
+});
+
+app.controller('PlaylistController', function ($scope) {
+    var activeUrl = null;
+
+    $scope.paused = true;
+
+    $scope.$on('wavesurferInit', function (e, wavesurfer) {
+        $scope.wavesurfer = wavesurfer;
+
+        $scope.wavesurfer.on('play', function () {
+            $scope.paused = false;
+        });
+
+        $scope.wavesurfer.on('pause', function () {
+            $scope.paused = true;
+        });
+
+        $scope.wavesurfer.on('finish', function () {
+            $scope.paused = true;
+            $scope.wavesurfer.seekTo(0);
+            $scope.$apply();
+        });
+    });
+
+    $scope.play = function (url) {
+        if (!$scope.wavesurfer) {
+            return;
+        }
+
+        activeUrl = url;
+
+        $scope.wavesurfer.once('ready', function () {
+            $scope.wavesurfer.play();
+            $scope.$apply();
+        });
+
+        $scope.wavesurfer.load(activeUrl);
+    };
+
+    $scope.isPlaying = function (url) {
+        return url == activeUrl;
+    };
+});

+ 120 - 0
libraries/wavesurfer/example/angular/index.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Angular</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- AngularJS -->
+        <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+    </head>
+
+    <body ng-app="ngWavesurfer" ng-controller="PlaylistController">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">wavesurfer.js Angular Demo</h1>
+            </div>
+
+            <div id="demo">
+                <div class="row" style="margin: 30px 0">
+                    <div class="col-sm-10">
+                        <ng-wavesurfer url="../media/demo.wav" wave-color="#337ab7" progress-color="#23527c" height="64">
+                        </ng-wavesurfer>
+                    </div>
+
+                    <div class="col-sm-2">
+                        <button class="btn btn-success btn-block" ng-click="wavesurfer.playPause()">
+                            <span id="play" ng-show="paused">
+                                <i class="glyphicon glyphicon-play"></i>
+                                Play
+                            </span>
+
+                            <span id="pause" ng-show="!paused">
+                                <i class="glyphicon glyphicon-pause"></i>
+                                Pause
+                            </span>
+                        </button>
+                    </div>
+                </div>
+
+                <div class="list-group" id="playlist">
+                    <a href=""
+                       ng-class="{ 'list-group-item': true, active: isPlaying('../media/demo.wav') }"
+                       ng-click="play('../media/demo.wav')">
+                        <i class="glyphicon glyphicon-play"></i>
+                        czskamaarù – Trou
+                        <span class="badge">0:21</span>
+                    </a>
+
+                    <a href=""
+                       ng-class="{ 'list-group-item': true, active: isPlaying('../panner/media.wav') }"
+                       ng-click="play('../panner/media.wav')">
+                        <i class="glyphicon glyphicon-play"></i>
+                        日本人の話し
+                        <span class="badge">1:04</span>
+                    </a>
+
+                    <a href=""
+                       ng-class="{ 'list-group-item': true, active: isPlaying('../elan/transcripts/001z.mp3') }"
+                       ng-click="play('../elan/transcripts/001z.mp3')">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Рассказы о сновидениях
+                        <span class="badge badge-info">1:26</span>
+                    </a>
+
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <p>
+                        Audio sources:<br />
+                        <a rel="nofollow" href="http://www.jamendo.com/en/track/661578/trou"><b>Trou</b> <span class="muted">by</span>&nbsp;<b>czskamaarù</b></a>,
+                        <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

File diff suppressed because it is too large
+ 0 - 0
libraries/wavesurfer/example/annotation/annotations.json


+ 8 - 0
libraries/wavesurfer/example/annotation/app.css

@@ -0,0 +1,8 @@
+region.wavesurfer-region:before {
+    content: attr(data-region-label);
+}
+
+region.wavesurfer-region[data-region-highlight] {
+  border: 1px solid rgb(86, 180, 239);
+  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.05) inset, 0px 0px 8px rgba(82, 168, 236, 0.6);
+}

+ 264 - 0
libraries/wavesurfer/example/annotation/app.js

@@ -0,0 +1,264 @@
+/**
+ * Create a WaveSurfer instance.
+ */
+var wavesurfer;
+
+/**
+ * Init & load.
+ */
+document.addEventListener('DOMContentLoaded', function () {
+    // Init wavesurfer
+    wavesurfer = WaveSurfer.create({
+        container: '#waveform',
+        height: 100,
+        pixelRatio: 1,
+        scrollParent: true,
+        normalize: true,
+        minimap: true,
+        backend: 'MediaElement',
+        plugins: [
+            WaveSurfer.regions.create(),
+            WaveSurfer.minimap.create({
+                height: 30,
+                waveColor: '#ddd',
+                progressColor: '#999',
+                cursorColor: '#999'
+            }),
+            WaveSurfer.timeline.create({
+                container: "#wave-timeline"
+            })
+        ]
+    });
+
+    wavesurfer.util.ajax({
+        responseType: 'json',
+        url: 'rashomon.json'
+    }).on('success', function (data) {
+        wavesurfer.load(
+            'http://www.archive.org/download/mshortworks_001_1202_librivox/msw001_03_rashomon_akutagawa_mt_64kb.mp3',
+            data
+        );
+    });
+
+    /* Regions */
+
+    wavesurfer.on('ready', function () {
+        wavesurfer.enableDragSelection({
+            color: randomColor(0.1)
+        });
+
+        if (localStorage.regions) {
+            loadRegions(JSON.parse(localStorage.regions));
+        } else {
+            // loadRegions(
+            //     extractRegions(
+            //         wavesurfer.backend.getPeaks(512),
+            //         wavesurfer.getDuration()
+            //     )
+            // );
+            wavesurfer.util.ajax({
+                responseType: 'json',
+                url: 'annotations.json'
+            }).on('success', function (data) {
+                loadRegions(data);
+                saveRegions();
+            });
+        }
+    });
+    wavesurfer.on('region-click', function (region, e) {
+        e.stopPropagation();
+        // Play on click, loop on shift click
+        e.shiftKey ? region.playLoop() : region.play();
+    });
+    wavesurfer.on('region-click', editAnnotation);
+    wavesurfer.on('region-updated', saveRegions);
+    wavesurfer.on('region-removed', saveRegions);
+    wavesurfer.on('region-in', showNote);
+
+    wavesurfer.on('region-play', function (region) {
+        region.once('out', function () {
+            wavesurfer.play(region.start);
+            wavesurfer.pause();
+        });
+    });
+
+
+    /* Toggle play/pause buttons. */
+    var playButton = document.querySelector('#play');
+    var pauseButton = document.querySelector('#pause');
+    wavesurfer.on('play', function () {
+        playButton.style.display = 'none';
+        pauseButton.style.display = '';
+    });
+    wavesurfer.on('pause', function () {
+        playButton.style.display = '';
+        pauseButton.style.display = 'none';
+    });
+});
+
+
+/**
+ * Save annotations to localStorage.
+ */
+function saveRegions() {
+    localStorage.regions = JSON.stringify(
+        Object.keys(wavesurfer.regions.list).map(function (id) {
+            var region = wavesurfer.regions.list[id];
+            return {
+                start: region.start,
+                end: region.end,
+                attributes: region.attributes,
+                data: region.data
+            };
+        })
+    );
+}
+
+
+/**
+ * Load regions from localStorage.
+ */
+function loadRegions(regions) {
+    regions.forEach(function (region) {
+        region.color = randomColor(0.1);
+        wavesurfer.addRegion(region);
+    });
+}
+
+
+/**
+ * Extract regions separated by silence.
+ */
+function extractRegions(peaks, duration) {
+    // Silence params
+    var minValue = 0.0015;
+    var minSeconds = 0.25;
+
+    var length = peaks.length;
+    var coef = duration / length;
+    var minLen = minSeconds / coef;
+
+    // Gather silence indeces
+    var silences = [];
+    Array.prototype.forEach.call(peaks, function (val, index) {
+        if (Math.abs(val) <= minValue) {
+            silences.push(index);
+        }
+    });
+
+    // Cluster silence values
+    var clusters = [];
+    silences.forEach(function (val, index) {
+        if (clusters.length && val == silences[index - 1] + 1) {
+            clusters[clusters.length - 1].push(val);
+        } else {
+            clusters.push([ val ]);
+        }
+    });
+
+    // Filter silence clusters by minimum length
+    var fClusters = clusters.filter(function (cluster) {
+        return cluster.length >= minLen;
+    });
+
+    // Create regions on the edges of silences
+    var regions = fClusters.map(function (cluster, index) {
+        var next = fClusters[index + 1];
+        return {
+            start: cluster[cluster.length - 1],
+            end: (next ? next[0] : length - 1)
+        };
+    });
+
+    // Add an initial region if the audio doesn't start with silence
+    var firstCluster = fClusters[0];
+    if (firstCluster && firstCluster[0] != 0) {
+        regions.unshift({
+            start: 0,
+            end: firstCluster[firstCluster.length - 1]
+        });
+    }
+
+    // Filter regions by minimum length
+    var fRegions = regions.filter(function (reg) {
+        return reg.end - reg.start >= minLen;
+    });
+
+    // Return time-based regions
+    return fRegions.map(function (reg) {
+        return {
+            start: Math.round(reg.start * coef * 10) / 10,
+            end: Math.round(reg.end * coef * 10) / 10
+        };
+    });
+}
+
+
+/**
+ * Random RGBA color.
+ */
+function randomColor(alpha) {
+    return 'rgba(' + [
+        ~~(Math.random() * 255),
+        ~~(Math.random() * 255),
+        ~~(Math.random() * 255),
+        alpha || 1
+    ] + ')';
+
+}
+
+
+/**
+ * Edit annotation for a region.
+ */
+function editAnnotation (region) {
+    var form = document.forms.edit;
+    form.style.opacity = 1;
+    form.elements.start.value = Math.round(region.start * 10) / 10,
+    form.elements.end.value = Math.round(region.end * 10) / 10;
+    form.elements.note.value = region.data.note || '';
+    form.onsubmit = function (e) {
+        e.preventDefault();
+        region.update({
+            start: form.elements.start.value,
+            end: form.elements.end.value,
+            data: {
+                note: form.elements.note.value
+            }
+        });
+        form.style.opacity = 0;
+    };
+    form.onreset = function () {
+        form.style.opacity = 0;
+        form.dataset.region = null;
+    };
+    form.dataset.region = region.id;
+}
+
+
+/**
+ * Display annotation.
+ */
+function showNote (region) {
+    if (!showNote.el) {
+        showNote.el = document.querySelector('#subtitle');
+    }
+    showNote.el.textContent = region.data.note || '–';
+}
+
+/**
+ * Bind controls.
+ */
+GLOBAL_ACTIONS['delete-region'] = function () {
+    var form = document.forms.edit;
+    var regionId = form.dataset.region;
+    if (regionId) {
+        wavesurfer.regions.list[regionId].remove();
+        form.reset();
+    }
+};
+
+GLOBAL_ACTIONS['export'] = function () {
+    window.open('data:application/json;charset=utf-8,' +
+        encodeURIComponent(localStorage.regions));
+};

+ 260 - 0
libraries/wavesurfer/example/annotation/index.html

@@ -0,0 +1,260 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Annotation tool</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="stylesheet" href="app.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- plugins -->
+        <script src="../../dist/plugin/wavesurfer.timeline.js"></script>
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+        <script src="../../dist/plugin/wavesurfer.minimap.js"></script>
+
+        <!-- App -->
+        <script src="../trivia.js"></script>
+        <script src="app.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">wavesurfer.js Annotations Tool</h1>
+            </div>
+
+            <div id="demo">
+                <p id="subtitle" class="text-center text-info">&nbsp;</p>
+
+                <div id="wave-timeline"></div>
+
+                <div id="waveform">
+                    <!-- Here be waveform -->
+                </div>
+
+                <div class="row" style="margin: 30px 0">
+                    <div class="col-sm-8">
+                        <p>
+                            Click on a region to enter an annotation.<br />
+                            Shift-click plays a region in a loop.
+                        </p>
+                    </div>
+
+                    <div class="col-sm-2">
+                        <button class="btn btn-primary btn-block" data-action="play">
+                            <span id="play">
+                                <i class="glyphicon glyphicon-play"></i>
+                                Play
+                            </span>
+
+                            <span id="pause" style="display: none">
+                                <i class="glyphicon glyphicon-pause"></i>
+                                Pause
+                            </span>
+                        </button>
+                    </div>
+
+                    <div class="col-sm-2">
+                        <button class="btn btn-info btn-block" data-action="export" title="Export annotations to JSON">
+                            <i class="glyphicon glyphicon-file"></i>
+                            Export
+                        </button>
+                    </div>
+                </div>
+            </div>
+
+            <form role="form" name="edit" style="opacity: 0; transition: opacity 300ms linear; margin: 30px 0;">
+                <div class="form-group">
+                    <label for="start">Start</label>
+                    <input class="form-control" id="start" name="start" />
+                </div>
+
+                <div class="form-group">
+                    <label for="end">End</label>
+                    <input class="form-control" id="end" name="end" />
+                </div>
+
+                <div class="form-group">
+                    <label for="note">Note</label>
+                    <textarea id="note" class="form-control" rows="3" name="note"></textarea>
+                </div>
+
+                <button type="submit" class="btn btn-success btn-block">Save</button>
+                <center><i>or</i></center>
+                <button type="button" class="btn btn-danger btn-block" data-action="delete-region">Delete</button>
+            </form>
+
+            <div class="row">
+                <h4>Region events:</h4>
+
+                <ul>
+                    <li><code>region-in</code> – When playback enters a region. Callback will receive the <code>Region</code> object.</li>
+                    <li><code>region-out</code>– When playback leaves a region. Callback will receive the <code>Region</code> object.</li>
+                    <li><code>region-mouseenter</code> - When the mouse moves over a region.  Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
+                    <li><code>region-mouseleave</code> - When the mouse leaves a region.  Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
+                    <li><code>region-click</code> - When the mouse clicks on a region.  Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
+                    <li><code>region-dblclick</code> - When the mouse double-clicks on a region.  Callback will receive the <code>Region</code> object, and a <code>MouseEvent</code> object.</li>
+                    <li><code>region-created</code> – When a region is created. Callback will receive the <code>Region</code> object.</li>
+                    <li><code>region-updated</code> – When a region is updated. Callback will receive the <code>Region</code> object.</li>
+                    <li><code>region-update-end</code> – When dragging or resizing is finished. Callback will receive the <code>Region</code> object.</li>
+                    <li><code>region-removed</code> – When a region is removed. Callback will receive the <code>Region</code> object.</li>
+                </ul>
+
+                <h4>Regions Plugin</h4>
+
+                <p>Regions are visual overlays on waveform that can be used to play and
+                    loop portions of audio. Regions can be dragged and resized.</p>
+
+                <p>Visual customization is possible via CSS (using the selectors
+                    <code>.wavesurfer-region</code> and <code>.wavesurfer-handle</code>).</p>
+
+                <p>To enable the plugin, add the script <code>plugin/wavesurfer.regions.js</code> to
+                    your page.</p>
+
+                <p>After doing that, use <code>wavesurfer.addRegion()</code> to create Region objects.</p>
+
+                <h5>Exposed Methods</h5>
+
+                <ul>
+                    <li><code>addRegion(options)</code> – Creates a region on the waveform. Returns a <code>Region</code> object.  See <a href="#region-options">Region Options</a>, <a href="#region-methods">Region Methods</a> and <a href="#region-events">Region Events</a> below.
+
+                        <ul>
+                            <li><strong>Note:</strong> You cannot add regions until the audio has finished loading, otherwise the <code>start:</code> and <code>end:</code> properties of the new region will be set to <code>0</code>, or an unexpected value.</li>
+                    </ul></li>
+                    <li><code>clearRegions()</code> – Removes all regions.</li>
+                    <li><code>enableDragSelection(options)</code> – Lets you create regions by selecting.
+                        areas of the waveform with mouse. <code>options</code> are Region objects' params (see <a href="#region-options">below</a>).</li>
+		    <li><code>disableDragSelection()</code> - Disables ability to create regions.</li>
+                </ul>
+
+                <h5>Region Options</h5>
+
+                <table><thead>
+                        <tr>
+                            <th>option</th>
+                            <th>type</th>
+                            <th>default</th>
+                            <th>description</th>
+                        </tr>
+                    </thead><tbody>
+                        <tr>
+                            <td><code>id</code></td>
+                            <td>string</td>
+                            <td>random</td>
+                            <td>The id of the region.</td>
+                        </tr>
+                        <tr>
+                            <td><code>start</code></td>
+                            <td>float</td>
+                            <td><code>0</code></td>
+                            <td>The start position of the region (in seconds).</td>
+                        </tr>
+                        <tr>
+                            <td><code>end</code></td>
+                            <td>float</td>
+                            <td><code>0</code></td>
+                            <td>The end position of the region (in seconds).</td>
+                        </tr>
+                        <tr>
+                            <td><code>loop</code></td>
+                            <td>boolean</td>
+                            <td><code>false</code></td>
+                            <td>Whether to loop the region when played back.</td>
+                        </tr>
+                        <tr>
+                            <td><code>drag</code></td>
+                            <td>boolean</td>
+                            <td><code>true</code></td>
+                            <td>Allow/dissallow dragging the region.</td>
+                        </tr>
+                        <tr>
+                            <td><code>resize</code></td>
+                            <td>boolean</td>
+                            <td><code>true</code></td>
+                            <td>Allow/dissallow resizing the region.</td>
+                        </tr>
+                        <tr>
+                            <td><code>color</code></td>
+                            <td>string</td>
+                            <td><code>"rgba(0, 0, 0, 0.1)"</code></td>
+                            <td>HTML color code.</td>
+                        </tr>
+                </tbody></table>
+
+                <h5>Region Methods</h5>
+
+                <ul>
+                    <li><code>remove()</code> - Remove the region object.</li>
+                    <li><code>update(options)</code> - Modify the settings of the region.</li>
+                    <li><code>play()</code> - Play the audio region from the start to end position.</li>
+                </ul>
+
+                <h5>Region Events</h5>
+
+                <p>General events:</p>
+
+                <ul>
+                    <li><code>in</code> - When playback enters the region.</li>
+                    <li><code>out</code> - When playback leaves the region.</li>
+                    <li><code>remove</code> - Happens just before the region is removed.</li>
+                    <li><code>update</code> - When the region's options are updated.</li>
+                </ul>
+
+                <p>Mouse events:</p>
+
+                <ul>
+                    <li><code>click</code> - When the mouse clicks on the region.  Callback will receive a <code>MouseEvent</code>.</li>
+                    <li><code>dblclick</code> - When the mouse double-clicks on the region.  Callback will receive a <code>MouseEvent</code>.</li>
+                    <li><code>over</code> - When mouse moves over the region.  Callback will receive a <code>MouseEvent</code>.</li>
+                    <li><code>leave</code> - When mouse leaves the region.  Callback will receive a <code>MouseEvent</code>.</li>
+                </ul>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-8">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-4">
+                    <p>
+                        The sound file is from <a href="https://librivox.org/librivox-multilingual-short-works-collection-001-by-various/">librivox.org</a>.
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

File diff suppressed because it is too large
+ 0 - 0
libraries/wavesurfer/example/annotation/rashomon.json


+ 133 - 0
libraries/wavesurfer/example/audio-element/index.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Media Element Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+
+        <!-- Demo -->
+        <script src="main.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">Media Element Fallback Example</h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <!-- Here be the waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+            <div class="row marketing">
+                <h3>How to Enable the Fallback</h3>
+
+                <hr />
+
+                <p>
+                    <strong>wavesurfer.js</strong> will automatically
+                    fallback to HTML5 Media if Web Audio is not
+                    supported. However, you can choose to use audio
+                    element manually. Simply set the <code>backend</code>
+                    option to <code>"MediaElement"</code>.
+                </p>
+
+                <p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    container: document.querySelector('#wave'),
+    backend: 'MediaElement'
+});
+</code></pre>
+                </p>
+
+                <h3>Pre-rendered Peaks</h3>
+
+                <p>
+                    If you have pre-rendered peaks (on your server),
+                    you can pass them into the <code>load</code>
+                    function. This is optional–if you don't provide
+                    any peaks,
+                    <strong>waserver.js</strong> will first draw a
+                    thin line instead of a waveform, then attempt to
+                    download the audio file via Ajax and decode it
+                    with Web Audio if available.
+                </p>
+
+                <p>
+<pre><code>wavesurfer.load('example/media/demo.mp3');</code></pre>
+                </p>
+
+
+                <p>
+                    Press this button to see the same demo with pre-decoded peaks:
+                    <button class="btn btn-warning" data-action="peaks">
+                        Load with pre-rendered peaks
+                    </button>
+                </p>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <div class="pull-right">
+                        <noindex>
+                            The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
+                        </noindex>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

File diff suppressed because it is too large
+ 26 - 0
libraries/wavesurfer/example/audio-element/main.js


+ 189 - 0
libraries/wavesurfer/example/css/doc.css

@@ -0,0 +1,189 @@
+body {
+  line-height: 1.6;
+}
+.content {
+  max-width: 65em;
+  padding: 15px 5%;
+}
+.content > div {
+  margin-bottom: 4em;
+}
+.self-detail {
+  margin-bottom: 2em;
+}
+
+/* detail */
+.content .detail > div {
+  margin-left: 0;
+}
+.content .detail h4 + :not(pre) {
+  margin-left: 0;
+}
+.content .detail {
+  margin-top: 2em;
+}
+.content .detail + .detail {
+  border-top: 1px solid #f5f5f5;
+}
+.content .detail + .detail h3 {
+  padding-top: 3em;
+}
+.content .detail > h3 {
+  color: #4d4e53;
+  margin-top: 0;
+}
+
+/* table styling */
+table.params tr:hover td,
+table.summary tr:hover td {
+  background: #F5F5F5;
+}
+table.params td,
+table.summary td {
+  border: 2px solid #fff;
+  background: #FAFAFA;
+  padding: 0.25em;
+  vertical-align: baseline;
+}
+table.summary thead {
+  background: transparent;
+}
+
+/* Property tables should follow some sort of grid */
+div[data-ice="properties"] td:nth-child(1) {
+  width: 10% !important;
+}
+div[data-ice="properties"] td:nth-child(2),
+div[data-ice="properties"] td:nth-child(3) {
+  width: 20% !important;
+}
+div[data-ice="properties"] td:nth-child(4) {
+  width: 50% !important;
+}
+
+/* headings */
+h1,
+.github-markdown h1 a {
+  font-weight: normal;
+  border-bottom: 0;
+  margin-bottom: 1.5em;
+  font-size: 2.5rem;
+}
+h2,
+.github-markdown h2 {
+  font-size: 1.5rem;
+  border-bottom: 0;
+  border-top: 4px solid #f5f5f5;
+  padding-top: 1em;
+  margin-top: 1em;
+  margin-bottom: 1em;
+}
+h3 {
+  font-size: 1.25em;
+  background: transparent;
+  padding: 0;
+}
+
+h3 [data-ice="name"] {
+  font-weight: 600;
+}
+h4,
+table.summary thead {
+  font-weight: 600;
+  text-transform: uppercase;
+  font-size: 0.75rem;
+  letter-spacing: 0.075em;
+  color: #555;
+}
+
+/* misc & type */
+.access  {
+  font-style: italic;
+  opacity: 0.5;
+}
+.abstract {
+  font-style: italic;
+  color: #009800;
+}
+*[data-ice="appendix"] li {
+  margin: 0;
+}
+p {
+  line-height: inherit;
+  margin-bottom: 1em;
+}
+
+/* links */
+a,
+.inherited-summary thead a {
+  color: #528DD9;
+  border-bottom: 1px solid #DDEBFE;
+}
+a:hover,
+a:focus,
+a:active,
+.inherited-summary thead a:hover,
+.inherited-summary thead a:focus,
+.inherited-summary thead a:active {
+  color: #4271ae;
+  border-bottom-color: #4271ae;
+}
+
+/* sidebar  */
+.navigation {
+  border: 0;
+}
+.navigation li a {
+  border: 0;
+}
+.navigation li a:hover,
+.navigation li a:focus,
+.navigation li a:active {
+  color: #333;
+}
+.navigation *[data-ice="kind"] {
+  line-height: 1.25em;
+}
+
+/* active state for sections */
+.inner-link-active {
+  background: transparent;
+}
+.inner-link-active::before {
+  content: '';
+  display: block;
+  height: 0.5em;
+  width: 0.5em;
+  background: #009800;
+  position: absolute;
+  left: -1.5em;
+  top: 0.5em;
+}
+/* position for other than the first detail container */
+.content .detail + .detail .inner-link-active::before  {
+  top: 3.5em;
+}
+
+/* search */
+.search-box.active .search-input-edge {
+  display: none;
+}
+.search-box {
+  font-size: 0.85rem;
+}
+.search-result {
+  height: auto;
+  max-height: 90vh;
+  white-space: normal;
+  box-shadow: -2px 5px 10px rgba(0,0,0,.05);
+  border: 1px solid #ddd;
+  border-top: 0;
+}
+.search-result li a {
+  border: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.search-result li.search-separator {
+  background: #4d4e53;
+}

+ 140 - 0
libraries/wavesurfer/example/css/ribbon.css

@@ -0,0 +1,140 @@
+/*!
+ * "Fork me on GitHub" CSS ribbon v0.1.1 | MIT License
+ * https://github.com/simonwhitaker/github-fork-ribbon-css
+*/
+
+/* Left will inherit from right (so we don't need to duplicate code) */
+.github-fork-ribbon {
+  /* The right and left classes determine the side we attach our banner to */
+  position: absolute;
+
+  /* Add a bit of padding to give some substance outside the "stitching" */
+  padding: 2px 0;
+
+  /* Set the base colour */
+  background-color: #a00;
+
+  /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */
+  background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15)));
+  background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+  background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
+
+  /* Add a drop shadow */
+  -webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
+  -moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
+  box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
+
+  /* Set the font */
+  font: 700 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+  z-index: 9999;
+  pointer-events: auto;
+}
+
+.github-fork-ribbon a,
+.github-fork-ribbon a:hover {
+  /* Set the text properties */
+  color: #fff;
+  text-decoration: none;
+  text-shadow: 0 -1px rgba(0, 0, 0, 0.5);
+  text-align: center;
+
+  /* Set the geometry. If you fiddle with these you'll also need
+     to tweak the top and right values in .github-fork-ribbon. */
+  width: 200px;
+  line-height: 20px;
+
+  /* Set the layout properties */
+  display: inline-block;
+  padding: 2px 0;
+
+  /* Add "stitching" effect */
+  border-width: 1px 0;
+  border-style: dotted;
+  border-color: #fff;
+  border-color: rgba(255, 255, 255, 0.7);
+}
+
+.github-fork-ribbon-wrapper {
+  width: 150px;
+  height: 150px;
+  position: absolute;
+  overflow: hidden;
+  top: 0;
+  z-index: 9999;
+  pointer-events: none;
+}
+
+.github-fork-ribbon-wrapper.fixed {
+  position: fixed;
+}
+
+.github-fork-ribbon-wrapper.left {
+  left: 0;
+}
+
+.github-fork-ribbon-wrapper.right {
+  right: 0;
+}
+
+.github-fork-ribbon-wrapper.left-bottom {
+  position: fixed;
+  top: inherit;
+  bottom: 0;
+  left: 0;
+}
+
+.github-fork-ribbon-wrapper.right-bottom {
+  position: fixed;
+  top: inherit;
+  bottom: 0;
+  right: 0;
+}
+
+.github-fork-ribbon-wrapper.right .github-fork-ribbon {
+  top: 42px;
+  right: -43px;
+
+  -webkit-transform: rotate(45deg);
+  -moz-transform: rotate(45deg);
+  -ms-transform: rotate(45deg);
+  -o-transform: rotate(45deg);
+  transform: rotate(45deg);
+}
+
+.github-fork-ribbon-wrapper.left .github-fork-ribbon {
+  top: 42px;
+  left: -43px;
+
+  -webkit-transform: rotate(-45deg);
+  -moz-transform: rotate(-45deg);
+  -ms-transform: rotate(-45deg);
+  -o-transform: rotate(-45deg);
+  transform: rotate(-45deg);
+}
+
+
+.github-fork-ribbon-wrapper.left-bottom .github-fork-ribbon {
+  top: 80px;
+  left: -43px;
+
+  -webkit-transform: rotate(45deg);
+  -moz-transform: rotate(45deg);
+  -ms-transform: rotate(45deg);
+  -o-transform: rotate(45deg);
+  transform: rotate(45deg);
+}
+
+.github-fork-ribbon-wrapper.right-bottom .github-fork-ribbon {
+  top: 80px;
+  right: -43px;
+
+  -webkit-transform: rotate(-45deg);
+  -moz-transform: rotate(-45deg);
+  -ms-transform: rotate(-45deg);
+  -o-transform: rotate(-45deg);
+  transform: rotate(-45deg);
+}

+ 110 - 0
libraries/wavesurfer/example/css/style.css

@@ -0,0 +1,110 @@
+/* Space out content a bit */
+body {
+    padding-top: 20px;
+    padding-bottom: 20px;
+}
+
+/* Everything but the jumbotron gets side spacing for mobile first views */
+.header,
+.marketing,
+.footer {
+    padding-left: 15px;
+    padding-right: 15px;
+}
+
+/* Custom page header */
+.header {
+    border-bottom: 1px solid #e5e5e5;
+}
+
+/* Make the masthead heading the same height as the navigation */
+.header h3 {
+    margin-top: 0;
+    margin-bottom: 0;
+    line-height: 40px;
+    padding-bottom: 19px;
+}
+
+/* Custom page footer */
+.footer {
+    padding-top: 19px;
+    color: #777;
+    border-top: 1px solid #e5e5e5;
+}
+
+/* Customize container */
+@media (min-width: 1024px) {
+    .container {
+        max-width: 900px;
+    }
+}
+
+.container-narrow > hr {
+    margin: 30px 0;
+}
+
+/* Supporting marketing content */
+.marketing {
+    margin: 40px 0;
+}
+
+.marketing p + h4 {
+    margin-top: 28px;
+}
+
+/* Responsive: Portrait tablets and up */
+@media screen and (min-width: 768px) {
+    /* Remove the padding we set earlier */
+    .header,
+    .marketing,
+    .footer {
+        padding-left: 0;
+        padding-right: 0;
+    }
+    /* Space out the masthead */
+    .header {
+        margin-bottom: 30px;
+    }
+}
+
+.controls {
+    padding: 30px 0 15px;
+    text-align: center;
+}
+
+.controls .btn {
+    margin-bottom: 15px;
+}
+
+@media screen and (min-width: 990px) {
+    .controls .mark-controls {
+        display: inline;
+    }
+}
+
+.lead {
+    text-align: center;
+    padding: 20px;
+}
+
+#waveform {
+    position: relative;
+}
+
+#progress-bar {
+    position: absolute;
+    z-index: 10;
+    top: 50%;
+    margin-top: -10px;
+    left: 5%;
+    width: 90%;
+}
+
+#drop {
+    border: 3px dashed #ddd;
+    padding: 30px;
+}
+
+#drop.wavesurfer-dragover {
+    border-color: #333;
+}

File diff suppressed because it is too large
+ 76 - 0
libraries/wavesurfer/example/cursor/index.html


+ 25 - 0
libraries/wavesurfer/example/cursor/main.js

@@ -0,0 +1,25 @@
+'use strict';
+
+// Create an instance
+var wavesurfer = {};
+
+// Init & load audio file
+document.addEventListener('DOMContentLoaded', function () {
+
+    wavesurfer = WaveSurfer.create({
+        container: document.querySelector('#waveform'),
+        plugins: [
+            WaveSurfer.cursor.create()
+        ]
+    });
+
+
+    // Load audio from URL
+    wavesurfer.load('../media/demo.wav');
+
+
+    // Play button
+    var button = document.querySelector('[data-action="play"]');
+
+    button.addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
+});

+ 194 - 0
libraries/wavesurfer/example/elan-wave-segment/index.html

@@ -0,0 +1,194 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | ELAN Wave Segment player</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="stylesheet" href="css/elan.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.min.js"></script>
+
+        <!-- regions plugin -->
+        <script src="../../dist/plugin/wavesurfer.regions.min.js"></script>
+
+        <!-- ELAN format renderer -->
+        <script src="../../dist/plugin/wavesurfer.elan.min.js"></script>
+
+        <!-- ELAN wave segment renderer -->
+        <script src="../../plugin/wavesurfer.elan-wave-segment.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+        <script src="../trivia.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <noindex>
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="?fill">Fill</a></li>
+                    <li><a href="?scroll">Scroll</a></li>
+                </ul>
+                </noindex>
+
+                <h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + <a rel="nofollow" href="http://spokencorpora.ru/showelan.py">ELAN</a> + Wave Segment</noindex></h1>
+            </div>
+            <div><p>The Elan Wave Segment Plugin uses the table and the time values created by the
+                <a href="../elan/index.html">ELAN plugin</a> to insert a wave form column for each row.
+            </p></div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <div class="progress progress-striped active" id="progress-bar">
+                        <div class="progress-bar progress-bar-info"></div>
+                    </div>
+
+                    <!-- Here be waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+            <div id="annotations" class="table-responsive">
+                <!-- Here be transcript -->
+            </div>
+
+            <h2>How to Enable Elan Wave Segment</h2>
+
+            <h3>Javascript Dependencies</h3>
+            <ul>
+                <li>Wavesufer <code></code></li>
+                <li>Region Plugin <code></code></li></li>
+                <li>ElAN Plugin <code></code></li></li>
+                <li>ELAN Wave Segment</li>
+                <pre><code>&lt;script src="[path_to]/wavesurfer.min.js"&gt;&lt;/script&gt;
+&lt;script src="[path_to]/plugin/wavesurfer.region.min.js"&gt;&lt;/script&gt;
+&lt;script src="[path_to]/plugin/wavesurfer.elan.min.js"&gt;&lt;/script&gt;
+&lt;script src="[path_to]/plugin/wavesurfer.elan-wave-segment.min.js"&gt;&lt;/script&gt; </code></pre>
+
+            </ul>
+
+            <h2>Javascript Initialization</h2>
+            <pre><code>// Create the wave surfer instance
+var wavesurfer = Object.create(WaveSurfer);
+
+// Create elan instance
+var elan = Object.create(WaveSurfer.ELAN);
+
+// Create Elan Wave Segment instance
+var elanWaveSegment = Object.create(WaveSurfer.ELANWaveSegment);
+
+document.addEventListener('DOMContentLoaded', function () {
+    var options = {
+        container     : '#waveform',
+    };
+
+    //################## set up some listeners ####################
+
+    //set up listener for when elan is done
+    elan.on('ready', function (data) {
+        wavesurfer.load('../elan/transcripts/001z.mp3');
+    });
+
+    //set up listener for playing when clicked on
+    elan.on('select', function (start, end) {
+        wavesurfer.backend.play(start, end);
+    });
+    //############################## initialize wavesurfer and related plugins###############
+
+    // Init wavesurfer
+    wavesurfer.init(options);
+
+    //init elan
+    elan.init({
+        url: '../elan/transcripts/001z.xml',
+        container: '#annotations',
+        tiers: {
+            Text: true,
+            Comments: true
+        }
+    });
+
+    //int elanWaveSegment when wavesurfer is done loading the sound file
+    wavesurfer.on('ready', function() {
+        options.plotTimeEnd = wavesurfer.backend.getDuration();
+        options.wavesurfer = wavesurfer;
+        options.ELAN = elan;
+        elanWaveSegment.init(options);
+    });
+
+    //update waveSegments when time advances
+    var onProgress = function (time) {
+        elanWaveSegment.onProgress(time);
+        //code for scrolling Elan goes here
+    };
+    wavesurfer.on('audioprocess', onProgress);
+}); </code></pre>
+            <h2>Options</h2>
+            <ul>
+                <li><code>ELAN:</code>                       required - The ELAN instance used to parse the elan data</li>
+                <li><code>wafesurver:</code>                 required - The wavesurfer instance used to draw the original waveform</li>
+                <li><code>waveSegmentWidth:</code>           optional - The width of each wave segment (defaults to 200)</li>
+                <li><code>waveSegmentPeaksPerSegment:</code> optional - The number of peaks that should be drawn (defaults to 400)</li>
+                <li><code>waveSegmentHeight:</code>          optional - The height of each wave segment (defaults to 30)</li>
+                <li><code>waveSegmentRenderer:</code>        optional - The renderer (drawer) to be used for the wave segments</li>
+                <li><code>waveSegmentNormalizeTo:</code>     optional - What to normalize each wave segment to [whole, segment,none]</li>
+                <li><code>waveSegmentBorderWidth:</code>     optional - The width of the border of the container element</li>
+                <li><code>waveSegmentBarHeight:</code>       optional - the height of the peaks/bars (defaults to 1)</li>
+            </ul>
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-8">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-4">
+                    <p>
+                        The ELAN program and format were developed by <a href="http://tla.mpi.nl/tools/tla-tools/elan/">Max Planck Institute</a>.
+                    </p>
+
+                    <p>
+                        The sample ELAN file and audio are from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 109 - 0
libraries/wavesurfer/example/elan/app.js

@@ -0,0 +1,109 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load
+document.addEventListener('DOMContentLoaded', function () {
+    var options = {
+        container     : '#waveform',
+        waveColor     : 'violet',
+        progressColor : 'purple',
+        loaderColor   : 'purple',
+        cursorColor   : 'navy',
+        selectionColor: '#d0e9c6',
+        loopSelection : false,
+        plugins: [
+            WaveSurfer.elan.create({
+                url: 'transcripts/001z.xml',
+                container: '#annotations',
+                tiers: {
+                    Text: true,
+                    Comments: true
+                }
+            }),
+            WaveSurfer.regions.create()
+        ]
+    };
+
+    if (location.search.match('scroll')) {
+        options.minPxPerSec = 100;
+        options.scrollParent = true;
+    }
+
+    if (location.search.match('normalize')) {
+        options.normalize = true;
+    }
+
+    // Init wavesurfer
+    wavesurfer = WaveSurfer.create(options);
+
+    /* Progress bar */
+    (function () {
+        var progressDiv = document.querySelector('#progress-bar');
+        var progressBar = progressDiv.querySelector('.progress-bar');
+
+        var showProgress = function (percent) {
+            progressDiv.style.display = 'block';
+            progressBar.style.width = percent + '%';
+        };
+
+        var hideProgress = function () {
+            progressDiv.style.display = 'none';
+        };
+
+        wavesurfer.on('loading', showProgress);
+        wavesurfer.on('ready', hideProgress);
+        wavesurfer.on('destroy', hideProgress);
+        wavesurfer.on('error', hideProgress);
+    }());
+
+    wavesurfer.elan.on('ready', function (data) {
+        wavesurfer.load('transcripts/' + data.media.url);
+    });
+
+    wavesurfer.elan.on('select', function (start, end) {
+        wavesurfer.backend.play(start, end);
+    });
+
+    wavesurfer.elan.on('ready', function () {
+        var classList = wavesurfer.elan.container.querySelector('table').classList;
+        [ 'table', 'table-striped', 'table-hover' ].forEach(function (cl) {
+            classList.add(cl);
+        });
+    });
+
+    var prevAnnotation, prevRow, region;
+    var onProgress = function (time) {
+        var annotation = wavesurfer.elan.getRenderedAnnotation(time);
+
+        if (prevAnnotation != annotation) {
+            prevAnnotation = annotation;
+
+            region && region.remove();
+            region = null;
+
+            if (annotation) {
+                // Highlight annotation table row
+                var row = wavesurfer.elan.getAnnotationNode(annotation);
+                prevRow && prevRow.classList.remove('success');
+                prevRow = row;
+                row.classList.add('success');
+                var before = row.previousSibling;
+                if (before) {
+                    wavesurfer.elan.container.scrollTop = before.offsetTop;
+                }
+
+                // Region
+                region = wavesurfer.addRegion({
+                    start: annotation.start,
+                    end: annotation.end,
+                    resize: false,
+                    color: 'rgba(223, 240, 216, 0.7)'
+                });
+            }
+        }
+    };
+
+    wavesurfer.on('audioprocess', onProgress);
+});

+ 25 - 0
libraries/wavesurfer/example/elan/css/elan.css

@@ -0,0 +1,25 @@
+#annotations {
+    max-height: 300px;
+    overflow: auto;
+}
+
+.wavesurfer-annotations tr.wavesurfer-active td {
+    background-color: yellow;
+}
+
+.wavesurfer-time {
+    width: 100px;
+    color: #555;
+}
+
+.wavesurfer-tier-Text {
+    width: 500px;
+}
+
+td.wavesurfer-tier-Comments {
+    color: #999;
+}
+
+.wavesurfer-handle {
+    background-color: #c9e2b3;
+}

+ 105 - 0
libraries/wavesurfer/example/elan/index.html

@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | ELAN player</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="stylesheet" href="css/elan.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- regions plugin -->
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+
+        <!-- ELAN format renderer -->
+        <script src="../../dist/plugin/wavesurfer.elan.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+        <script src="../trivia.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <noindex>
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="?fill">Fill</a></li>
+                    <li><a href="?scroll">Scroll</a></li>
+                </ul>
+                </noindex>
+
+                <h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + <a rel="nofollow" href="http://spokencorpora.ru/showelan.py">ELAN</a></noindex></h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <div class="progress progress-striped active" id="progress-bar">
+                        <div class="progress-bar progress-bar-info"></div>
+                    </div>
+
+                    <!-- Here be waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+            <div id="annotations" class="table-responsive">
+                <!-- Here be transcript -->
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-8">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-4">
+                    <p>
+                        The ELAN program and format were developed by <a href="http://tla.mpi.nl/tools/tla-tools/elan/">Max Planck Institute</a>.
+                    </p>
+
+                    <p>
+                        The sample ELAN file and audio are from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

BIN
libraries/wavesurfer/example/elan/transcripts/001z.mp3


+ 2866 - 0
libraries/wavesurfer/example/elan/transcripts/001z.xml

@@ -0,0 +1,2866 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ANNOTATION_DOCUMENT AUTHOR="Mikhail Buryakov" DATE="2012-11-20T07:49:32+04:00" FORMAT="2.6" VERSION="2.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.mpi.nl/tools/elan/EAFv2.6.xsd">
+  <HEADER MEDIA_FILE="" TIME_UNITS="milliseconds">
+    <MEDIA_DESCRIPTOR MEDIA_URL="001z.mp3" MIME_TYPE="audio/mpeg"/>
+  </HEADER>
+
+  <TIME_ORDER>
+    <TIME_SLOT TIME_SLOT_ID="TS0" TIME_VALUE="0"/>
+    <TIME_SLOT TIME_SLOT_ID="TS1" TIME_VALUE="230"/>
+    <TIME_SLOT TIME_SLOT_ID="TS2" TIME_VALUE="1840"/>
+    <TIME_SLOT TIME_SLOT_ID="TS3" TIME_VALUE="2970"/>
+    <TIME_SLOT TIME_SLOT_ID="TS4" TIME_VALUE="4560"/>
+    <TIME_SLOT TIME_SLOT_ID="TS5" TIME_VALUE="7190"/>
+    <TIME_SLOT TIME_SLOT_ID="TS6" TIME_VALUE="10410"/>
+    <TIME_SLOT TIME_SLOT_ID="TS7" TIME_VALUE="11050"/>
+    <TIME_SLOT TIME_SLOT_ID="TS8" TIME_VALUE="12210"/>
+    <TIME_SLOT TIME_SLOT_ID="TS9" TIME_VALUE="13180"/>
+    <TIME_SLOT TIME_SLOT_ID="TS10" TIME_VALUE="16590"/>
+    <TIME_SLOT TIME_SLOT_ID="TS11" TIME_VALUE="17890"/>
+    <TIME_SLOT TIME_SLOT_ID="TS12" TIME_VALUE="19290"/>
+    <TIME_SLOT TIME_SLOT_ID="TS13" TIME_VALUE="20610"/>
+    <TIME_SLOT TIME_SLOT_ID="TS14" TIME_VALUE="21500"/>
+    <TIME_SLOT TIME_SLOT_ID="TS15" TIME_VALUE="22110"/>
+    <TIME_SLOT TIME_SLOT_ID="TS16" TIME_VALUE="22820"/>
+    <TIME_SLOT TIME_SLOT_ID="TS17" TIME_VALUE="25840"/>
+    <TIME_SLOT TIME_SLOT_ID="TS18" TIME_VALUE="28040"/>
+    <TIME_SLOT TIME_SLOT_ID="TS19" TIME_VALUE="28630"/>
+    <TIME_SLOT TIME_SLOT_ID="TS20" TIME_VALUE="31670"/>
+    <TIME_SLOT TIME_SLOT_ID="TS21" TIME_VALUE="35960"/>
+    <TIME_SLOT TIME_SLOT_ID="TS22" TIME_VALUE="38630"/>
+    <TIME_SLOT TIME_SLOT_ID="TS23" TIME_VALUE="39520"/>
+    <TIME_SLOT TIME_SLOT_ID="TS24" TIME_VALUE="44640"/>
+    <TIME_SLOT TIME_SLOT_ID="TS25" TIME_VALUE="45990"/>
+    <TIME_SLOT TIME_SLOT_ID="TS26" TIME_VALUE="48200"/>
+    <TIME_SLOT TIME_SLOT_ID="TS27" TIME_VALUE="50520"/>
+    <TIME_SLOT TIME_SLOT_ID="TS28" TIME_VALUE="52550"/>
+    <TIME_SLOT TIME_SLOT_ID="TS29" TIME_VALUE="53700"/>
+    <TIME_SLOT TIME_SLOT_ID="TS30" TIME_VALUE="54350"/>
+    <TIME_SLOT TIME_SLOT_ID="TS31" TIME_VALUE="58130"/>
+    <TIME_SLOT TIME_SLOT_ID="TS32" TIME_VALUE="59780"/>
+    <TIME_SLOT TIME_SLOT_ID="TS33" TIME_VALUE="62830"/>
+    <TIME_SLOT TIME_SLOT_ID="TS34" TIME_VALUE="64030"/>
+    <TIME_SLOT TIME_SLOT_ID="TS35" TIME_VALUE="65489"/>
+    <TIME_SLOT TIME_SLOT_ID="TS36" TIME_VALUE="70420"/>
+    <TIME_SLOT TIME_SLOT_ID="TS37" TIME_VALUE="74470"/>
+    <TIME_SLOT TIME_SLOT_ID="TS38" TIME_VALUE="76840"/>
+    <TIME_SLOT TIME_SLOT_ID="TS39" TIME_VALUE="78230"/>
+    <TIME_SLOT TIME_SLOT_ID="TS40" TIME_VALUE="79790"/>
+    <TIME_SLOT TIME_SLOT_ID="TS41" TIME_VALUE="81310"/>
+    <TIME_SLOT TIME_SLOT_ID="TS42" TIME_VALUE="83110"/>
+    <TIME_SLOT TIME_SLOT_ID="TS43" TIME_VALUE="83700"/>
+    <TIME_SLOT TIME_SLOT_ID="TS44" TIME_VALUE="84510"/>
+    <TIME_SLOT TIME_SLOT_ID="TS45" TIME_VALUE="85570"/>
+    <TIME_SLOT TIME_SLOT_ID="TS46" TIME_VALUE="87400"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP0" TIME_VALUE="816"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP1" TIME_VALUE="897"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP2" TIME_VALUE="1835"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP3" TIME_VALUE="2299"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP4" TIME_VALUE="2967"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP5" TIME_VALUE="3492"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP6" TIME_VALUE="4563"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP7" TIME_VALUE="5759"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP8" TIME_VALUE="7194"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP9" TIME_VALUE="7628"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP10" TIME_VALUE="7628"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP11" TIME_VALUE="7716"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP12" TIME_VALUE="7716"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP13" TIME_VALUE="8174"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP14" TIME_VALUE="8723"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP15" TIME_VALUE="8891"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP16" TIME_VALUE="9143"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP17" TIME_VALUE="9513"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP18" TIME_VALUE="10410"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP19" TIME_VALUE="10604"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP20" TIME_VALUE="11053"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP21" TIME_VALUE="11277"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP22" TIME_VALUE="13180"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP23" TIME_VALUE="14506"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP24" TIME_VALUE="15121"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP25" TIME_VALUE="15769"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP26" TIME_VALUE="16593"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP27" TIME_VALUE="17160"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP28" TIME_VALUE="17889"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP29" TIME_VALUE="18080"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP30" TIME_VALUE="19293"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP31" TIME_VALUE="19436"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP32" TIME_VALUE="19436"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP33" TIME_VALUE="19508"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP34" TIME_VALUE="19508"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP35" TIME_VALUE="19901"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP36" TIME_VALUE="20608"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP37" TIME_VALUE="20987"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP38" TIME_VALUE="22108"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP39" TIME_VALUE="22485"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP40" TIME_VALUE="22818"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP41" TIME_VALUE="22906"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP42" TIME_VALUE="22906"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP43" TIME_VALUE="22957"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP44" TIME_VALUE="22957"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP45" TIME_VALUE="23232"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP46" TIME_VALUE="24282"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP47" TIME_VALUE="24742"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP48" TIME_VALUE="25839"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP49" TIME_VALUE="26186"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP50" TIME_VALUE="26948"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP51" TIME_VALUE="27322"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP52" TIME_VALUE="28625"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP53" TIME_VALUE="29692"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP54" TIME_VALUE="30517"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP55" TIME_VALUE="30765"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP56" TIME_VALUE="31665"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP57" TIME_VALUE="33295"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP58" TIME_VALUE="35961"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP59" TIME_VALUE="36577"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP60" TIME_VALUE="36577"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP61" TIME_VALUE="37100"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP62" TIME_VALUE="37100"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP63" TIME_VALUE="37413"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP64" TIME_VALUE="39522"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP65" TIME_VALUE="40416"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP66" TIME_VALUE="41421"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP67" TIME_VALUE="42180"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP68" TIME_VALUE="42918"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP69" TIME_VALUE="43165"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP70" TIME_VALUE="44637"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP71" TIME_VALUE="45528"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP72" TIME_VALUE="45989"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP73" TIME_VALUE="46397"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP74" TIME_VALUE="47129"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP75" TIME_VALUE="47436"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP76" TIME_VALUE="48198"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP77" TIME_VALUE="49643"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP78" TIME_VALUE="50517"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP79" TIME_VALUE="50726"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP80" TIME_VALUE="50726"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP81" TIME_VALUE="50787"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP82" TIME_VALUE="50787"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP83" TIME_VALUE="51386"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP84" TIME_VALUE="51386"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP85" TIME_VALUE="51629"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP86" TIME_VALUE="52546"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP87" TIME_VALUE="53031"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP88" TIME_VALUE="54348"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP89" TIME_VALUE="54967"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP90" TIME_VALUE="56172"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP91" TIME_VALUE="56306"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP92" TIME_VALUE="58128"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP93" TIME_VALUE="58890"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP94" TIME_VALUE="59776"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP95" TIME_VALUE="60187"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP96" TIME_VALUE="60959"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP97" TIME_VALUE="61176"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP98" TIME_VALUE="61419"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP99" TIME_VALUE="61566"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP100" TIME_VALUE="61624"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP101" TIME_VALUE="61771"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP102" TIME_VALUE="61988"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP103" TIME_VALUE="62225"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP104" TIME_VALUE="62831"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP105" TIME_VALUE="63103"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP106" TIME_VALUE="64030"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP107" TIME_VALUE="64723"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP108" TIME_VALUE="65486"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP109" TIME_VALUE="66495"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP110" TIME_VALUE="67039"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP111" TIME_VALUE="67401"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP112" TIME_VALUE="70418"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP113" TIME_VALUE="70571"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP114" TIME_VALUE="71237"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP115" TIME_VALUE="71626"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP116" TIME_VALUE="72038"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP117" TIME_VALUE="72389"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP118" TIME_VALUE="72921"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP119" TIME_VALUE="73143"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP120" TIME_VALUE="74467"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP121" TIME_VALUE="74605"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP122" TIME_VALUE="76844"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP123" TIME_VALUE="77375"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP124" TIME_VALUE="78232"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP125" TIME_VALUE="78676"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP126" TIME_VALUE="80698"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP127" TIME_VALUE="80916"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP128" TIME_VALUE="81310"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP129" TIME_VALUE="81857"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP130" TIME_VALUE="83113"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP131" TIME_VALUE="83235"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP132" TIME_VALUE="85573"/>
+    <TIME_SLOT TIME_SLOT_ID="TSP133" TIME_VALUE="86819"/>
+  </TIME_ORDER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="EDU" TIER_ID="EDU">
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU1" TIME_SLOT_REF1="TS0" TIME_SLOT_REF2="TS1">
+        <ANNOTATION_VALUE>1</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU2" TIME_SLOT_REF1="TS1" TIME_SLOT_REF2="TS2">
+        <ANNOTATION_VALUE>2</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU3" TIME_SLOT_REF1="TS2" TIME_SLOT_REF2="TS3">
+        <ANNOTATION_VALUE>3</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU4" TIME_SLOT_REF1="TS3" TIME_SLOT_REF2="TS4">
+        <ANNOTATION_VALUE>4</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU5" TIME_SLOT_REF1="TS4" TIME_SLOT_REF2="TS5">
+        <ANNOTATION_VALUE>5</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU6" TIME_SLOT_REF1="TS5" TIME_SLOT_REF2="TS6">
+        <ANNOTATION_VALUE>6</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU7" TIME_SLOT_REF1="TS6" TIME_SLOT_REF2="TS7">
+        <ANNOTATION_VALUE>7</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU8" TIME_SLOT_REF1="TS7" TIME_SLOT_REF2="TS8">
+        <ANNOTATION_VALUE>8</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU9" TIME_SLOT_REF1="TS8" TIME_SLOT_REF2="TS9">
+        <ANNOTATION_VALUE>9</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU10" TIME_SLOT_REF1="TS9" TIME_SLOT_REF2="TS10">
+        <ANNOTATION_VALUE>10</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU11" TIME_SLOT_REF1="TS10" TIME_SLOT_REF2="TS11">
+        <ANNOTATION_VALUE>11</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU12" TIME_SLOT_REF1="TS11" TIME_SLOT_REF2="TS12">
+        <ANNOTATION_VALUE>12</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU13" TIME_SLOT_REF1="TS12" TIME_SLOT_REF2="TS13">
+        <ANNOTATION_VALUE>13</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU14" TIME_SLOT_REF1="TS13" TIME_SLOT_REF2="TS14">
+        <ANNOTATION_VALUE>14</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU15" TIME_SLOT_REF1="TS14" TIME_SLOT_REF2="TS15">
+        <ANNOTATION_VALUE>15</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU16" TIME_SLOT_REF1="TS15" TIME_SLOT_REF2="TS16">
+        <ANNOTATION_VALUE>16</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU17" TIME_SLOT_REF1="TS16" TIME_SLOT_REF2="TS17">
+        <ANNOTATION_VALUE>17</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU18" TIME_SLOT_REF1="TS17" TIME_SLOT_REF2="TS18">
+        <ANNOTATION_VALUE>18</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU19" TIME_SLOT_REF1="TS18" TIME_SLOT_REF2="TS19">
+        <ANNOTATION_VALUE>19</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU20" TIME_SLOT_REF1="TS19" TIME_SLOT_REF2="TS20">
+        <ANNOTATION_VALUE>20</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU21" TIME_SLOT_REF1="TS20" TIME_SLOT_REF2="TS21">
+        <ANNOTATION_VALUE>21</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU22" TIME_SLOT_REF1="TS21" TIME_SLOT_REF2="TS22">
+        <ANNOTATION_VALUE>22</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU23" TIME_SLOT_REF1="TS22" TIME_SLOT_REF2="TS23">
+        <ANNOTATION_VALUE>23</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU24" TIME_SLOT_REF1="TS23" TIME_SLOT_REF2="TS24">
+        <ANNOTATION_VALUE>24</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU25" TIME_SLOT_REF1="TS24" TIME_SLOT_REF2="TS25">
+        <ANNOTATION_VALUE>25</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU26" TIME_SLOT_REF1="TS25" TIME_SLOT_REF2="TS26">
+        <ANNOTATION_VALUE>26</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU27" TIME_SLOT_REF1="TS26" TIME_SLOT_REF2="TS27">
+        <ANNOTATION_VALUE>27</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU28" TIME_SLOT_REF1="TS27" TIME_SLOT_REF2="TS28">
+        <ANNOTATION_VALUE>28</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU29" TIME_SLOT_REF1="TS28" TIME_SLOT_REF2="TS29">
+        <ANNOTATION_VALUE>29</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU30" TIME_SLOT_REF1="TS29" TIME_SLOT_REF2="TS30">
+        <ANNOTATION_VALUE>30</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU31" TIME_SLOT_REF1="TS30" TIME_SLOT_REF2="TS31">
+        <ANNOTATION_VALUE>31</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU32" TIME_SLOT_REF1="TS31" TIME_SLOT_REF2="TS32">
+        <ANNOTATION_VALUE>32</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU33" TIME_SLOT_REF1="TS32" TIME_SLOT_REF2="TS33">
+        <ANNOTATION_VALUE>33</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU34" TIME_SLOT_REF1="TS33" TIME_SLOT_REF2="TS34">
+        <ANNOTATION_VALUE>34</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU35" TIME_SLOT_REF1="TS34" TIME_SLOT_REF2="TS35">
+        <ANNOTATION_VALUE>35</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU36" TIME_SLOT_REF1="TS35" TIME_SLOT_REF2="TS36">
+        <ANNOTATION_VALUE>36</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU37" TIME_SLOT_REF1="TS36" TIME_SLOT_REF2="TS37">
+        <ANNOTATION_VALUE>37</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU38" TIME_SLOT_REF1="TS37" TIME_SLOT_REF2="TS38">
+        <ANNOTATION_VALUE>38</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU39" TIME_SLOT_REF1="TS38" TIME_SLOT_REF2="TS39">
+        <ANNOTATION_VALUE>39</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU40" TIME_SLOT_REF1="TS39" TIME_SLOT_REF2="TS40">
+        <ANNOTATION_VALUE>40</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU41" TIME_SLOT_REF1="TS40" TIME_SLOT_REF2="TS41">
+        <ANNOTATION_VALUE>41</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU42" TIME_SLOT_REF1="TS41" TIME_SLOT_REF2="TS42">
+        <ANNOTATION_VALUE>42</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU43" TIME_SLOT_REF1="TS42" TIME_SLOT_REF2="TS43">
+        <ANNOTATION_VALUE>43</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU44" TIME_SLOT_REF1="TS43" TIME_SLOT_REF2="TS44">
+        <ANNOTATION_VALUE>44</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU45" TIME_SLOT_REF1="TS44" TIME_SLOT_REF2="TS45">
+        <ANNOTATION_VALUE>45</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="EDU46" TIME_SLOT_REF1="TS45" TIME_SLOT_REF2="TS46">
+        <ANNOTATION_VALUE>46</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="Pause" PARENT_REF="EDU" TIER_ID="Pauses">
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE0" TIME_SLOT_REF1="TSP0" TIME_SLOT_REF2="TSP1">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE1" TIME_SLOT_REF1="TSP2" TIME_SLOT_REF2="TSP3">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE2" TIME_SLOT_REF1="TSP4" TIME_SLOT_REF2="TSP5">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE3" TIME_SLOT_REF1="TSP6" TIME_SLOT_REF2="TSP7">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE4" TIME_SLOT_REF1="TSP8" TIME_SLOT_REF2="TSP9">
+        <ANNOTATION_VALUE>A+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE5" TIME_SLOT_REF1="TSP10" TIME_SLOT_REF2="TSP11">
+        <ANNOTATION_VALUE>+{sm}+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE6" TIME_SLOT_REF1="TSP12" TIME_SLOT_REF2="TSP13">
+        <ANNOTATION_VALUE>+A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE7" TIME_SLOT_REF1="TSP14" TIME_SLOT_REF2="TSP15">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE8" TIME_SLOT_REF1="TSP16" TIME_SLOT_REF2="TSP17">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE9" TIME_SLOT_REF1="TSP18" TIME_SLOT_REF2="TSP19">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE10" TIME_SLOT_REF1="TSP20" TIME_SLOT_REF2="TSP21">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE11" TIME_SLOT_REF1="TSP22" TIME_SLOT_REF2="TSP23">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE12" TIME_SLOT_REF1="TSP24" TIME_SLOT_REF2="TSP25">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE13" TIME_SLOT_REF1="TSP26" TIME_SLOT_REF2="TSP27">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE14" TIME_SLOT_REF1="TSP28" TIME_SLOT_REF2="TSP29">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE15" TIME_SLOT_REF1="TSP30" TIME_SLOT_REF2="TSP31">
+        <ANNOTATION_VALUE>A+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE16" TIME_SLOT_REF1="TSP32" TIME_SLOT_REF2="TSP33">
+        <ANNOTATION_VALUE>+{sm}+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE17" TIME_SLOT_REF1="TSP34" TIME_SLOT_REF2="TSP35">
+        <ANNOTATION_VALUE>+A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE18" TIME_SLOT_REF1="TSP36" TIME_SLOT_REF2="TSP37">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE19" TIME_SLOT_REF1="TSP38" TIME_SLOT_REF2="TSP39">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE20" TIME_SLOT_REF1="TSP40" TIME_SLOT_REF2="TSP41">
+        <ANNOTATION_VALUE>A+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE21" TIME_SLOT_REF1="TSP42" TIME_SLOT_REF2="TSP43">
+        <ANNOTATION_VALUE>+{sm}+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE22" TIME_SLOT_REF1="TSP44" TIME_SLOT_REF2="TSP45">
+        <ANNOTATION_VALUE>+A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE23" TIME_SLOT_REF1="TSP46" TIME_SLOT_REF2="TSP47">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE24" TIME_SLOT_REF1="TSP48" TIME_SLOT_REF2="TSP49">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE25" TIME_SLOT_REF1="TSP50" TIME_SLOT_REF2="TSP51">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE26" TIME_SLOT_REF1="TSP52" TIME_SLOT_REF2="TSP53">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE27" TIME_SLOT_REF1="TSP54" TIME_SLOT_REF2="TSP55">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE28" TIME_SLOT_REF1="TSP56" TIME_SLOT_REF2="TSP57">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE29" TIME_SLOT_REF1="TSP58" TIME_SLOT_REF2="TSP59">
+        <ANNOTATION_VALUE>A+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE30" TIME_SLOT_REF1="TSP60" TIME_SLOT_REF2="TSP61">
+        <ANNOTATION_VALUE>+{sm}+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE31" TIME_SLOT_REF1="TSP62" TIME_SLOT_REF2="TSP63">
+        <ANNOTATION_VALUE>+A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE32" TIME_SLOT_REF1="TSP64" TIME_SLOT_REF2="TSP65">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE33" TIME_SLOT_REF1="TSP66" TIME_SLOT_REF2="TSP67">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE34" TIME_SLOT_REF1="TSP68" TIME_SLOT_REF2="TSP69">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE35" TIME_SLOT_REF1="TSP70" TIME_SLOT_REF2="TSP71">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE36" TIME_SLOT_REF1="TSP72" TIME_SLOT_REF2="TSP73">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE37" TIME_SLOT_REF1="TSP74" TIME_SLOT_REF2="TSP75">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE38" TIME_SLOT_REF1="TSP76" TIME_SLOT_REF2="TSP77">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE39" TIME_SLOT_REF1="TSP78" TIME_SLOT_REF2="TSP79">
+        <ANNOTATION_VALUE>A+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE40" TIME_SLOT_REF1="TSP80" TIME_SLOT_REF2="TSP81">
+        <ANNOTATION_VALUE>+{sm}+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE41" TIME_SLOT_REF1="TSP82" TIME_SLOT_REF2="TSP83">
+        <ANNOTATION_VALUE>+A+</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE42" TIME_SLOT_REF1="TSP84" TIME_SLOT_REF2="TSP85">
+        <ANNOTATION_VALUE>+{lg}</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE43" TIME_SLOT_REF1="TSP86" TIME_SLOT_REF2="TSP87">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE44" TIME_SLOT_REF1="TSP88" TIME_SLOT_REF2="TSP89">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE45" TIME_SLOT_REF1="TSP90" TIME_SLOT_REF2="TSP91">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE46" TIME_SLOT_REF1="TSP92" TIME_SLOT_REF2="TSP93">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE47" TIME_SLOT_REF1="TSP94" TIME_SLOT_REF2="TSP95">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE48" TIME_SLOT_REF1="TSP96" TIME_SLOT_REF2="TSP97">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE49" TIME_SLOT_REF1="TSP98" TIME_SLOT_REF2="TSP99">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE50" TIME_SLOT_REF1="TSP100" TIME_SLOT_REF2="TSP101">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE51" TIME_SLOT_REF1="TSP102" TIME_SLOT_REF2="TSP103">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE52" TIME_SLOT_REF1="TSP104" TIME_SLOT_REF2="TSP105">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE53" TIME_SLOT_REF1="TSP106" TIME_SLOT_REF2="TSP107">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE54" TIME_SLOT_REF1="TSP108" TIME_SLOT_REF2="TSP109">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE55" TIME_SLOT_REF1="TSP110" TIME_SLOT_REF2="TSP111">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE56" TIME_SLOT_REF1="TSP112" TIME_SLOT_REF2="TSP113">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE57" TIME_SLOT_REF1="TSP114" TIME_SLOT_REF2="TSP115">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE58" TIME_SLOT_REF1="TSP116" TIME_SLOT_REF2="TSP117">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE59" TIME_SLOT_REF1="TSP118" TIME_SLOT_REF2="TSP119">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE60" TIME_SLOT_REF1="TSP120" TIME_SLOT_REF2="TSP121">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE61" TIME_SLOT_REF1="TSP122" TIME_SLOT_REF2="TSP123">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE62" TIME_SLOT_REF1="TSP124" TIME_SLOT_REF2="TSP125">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE63" TIME_SLOT_REF1="TSP126" TIME_SLOT_REF2="TSP127">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE64" TIME_SLOT_REF1="TSP128" TIME_SLOT_REF2="TSP129">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE65" TIME_SLOT_REF1="TSP130" TIME_SLOT_REF2="TSP131">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <ALIGNABLE_ANNOTATION ANNOTATION_ID="PAUSE66" TIME_SLOT_REF1="TSP132" TIME_SLOT_REF2="TSP133">
+        <ANNOTATION_VALUE>A</ANNOTATION_VALUE>
+      </ALIGNABLE_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="EDUProp" PARENT_REF="EDU" TIER_ID="Text">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT1" ANNOTATION_REF="EDU1">
+        <ANNOTATION_VALUE>Пото= ==</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT2" ANNOTATION_REF="EDU2">
+        <ANNOTATION_VALUE>А потом ∙∙ про зайчика сон.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT3" ANNOTATION_REF="EDU3">
+        <ANNOTATION_VALUE>∙∙ А зайчик вот,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT4" ANNOTATION_REF="EDU4">
+        <ANNOTATION_VALUE>∙∙∙ он был в лесу.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT5" ANNOTATION_REF="EDU5">
+        <ANNOTATION_VALUE>∙∙∙∙ А я была у р-речки.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT6" ANNOTATION_REF="EDU6">
+        <ANNOTATION_VALUE>∙∙ {ЧМОКАНЬЕ} ∙∙ А потом ∙∙ ког= ‖ ∙∙ вот я пошла,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT7" ANNOTATION_REF="EDU7">
+        <ANNOTATION_VALUE>∙∙ к ‖ домой,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT8" ANNOTATION_REF="EDU8">
+        <ANNOTATION_VALUE>∙∙ а зайчик за мной,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT9" ANNOTATION_REF="EDU9">
+        <ANNOTATION_VALUE>бежал-бежал.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT10" ANNOTATION_REF="EDU10">
+        <ANNOTATION_VALUE>∙∙∙∙ А потом ∙∙∙ он прибежал,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT11" ANNOTATION_REF="EDU11">
+        <ANNOTATION_VALUE>∙∙∙ ко мне опять,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT12" ANNOTATION_REF="EDU12">
+        <ANNOTATION_VALUE>∙∙ когда я ушла уже,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT13" ANNOTATION_REF="EDU13">
+        <ANNOTATION_VALUE>∙∙ {ЧМОКАНЬЕ} ∙∙ я вышла,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT14" ANNOTATION_REF="EDU14">
+        <ANNOTATION_VALUE>∙∙ к себе,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT15" ANNOTATION_REF="EDU15">
+        <ANNOTATION_VALUE>во двор.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT16" ANNOTATION_REF="EDU16">
+        <ANNOTATION_VALUE>∙∙ И онь= ==</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT17" ANNOTATION_REF="EDU17">
+        <ANNOTATION_VALUE>∙∙ {ЧМОКАНЬЕ} ∙∙ А зайчик как-то ∙∙ сюда прискакал,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT18" ANNOTATION_REF="EDU18">
+        <ANNOTATION_VALUE>∙∙ а потом он ∙∙ н-не знал,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT19" ANNOTATION_REF="EDU19">
+        <ANNOTATION_VALUE>где я.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT20" ANNOTATION_REF="EDU20">
+        <ANNOTATION_VALUE>∙∙∙∙ А я была ∙∙ у себя дома.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT21" ANNOTATION_REF="EDU21">
+        <ANNOTATION_VALUE>∙∙∙∙ А потом этот зайчик прискакал ко мне домой.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT22" ANNOTATION_REF="EDU22">
+        <ANNOTATION_VALUE>∙∙∙ {ЧМОКАНЬЕ} ∙∙ Он скакал-скакал,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT23" ANNOTATION_REF="EDU23">
+        <ANNOTATION_VALUE>и прискакал.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT24" ANNOTATION_REF="EDU24">
+        <ANNOTATION_VALUE>∙∙∙ А потом он ск= ‖ ∙∙∙ этот зайчик ∙∙ встретил й-ёжика.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT25" ANNOTATION_REF="EDU25">
+        <ANNOTATION_VALUE>∙∙∙ У меня!</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT26" ANNOTATION_REF="EDU26">
+        <ANNOTATION_VALUE>∙∙ Который был ∙∙ у меня дома,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT27" ANNOTATION_REF="EDU27">
+        <ANNOTATION_VALUE>∙∙∙∙ и они были —</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT28" ANNOTATION_REF="EDU28">
+        <ANNOTATION_VALUE>∙∙ {ЧМОКАНЬЕ} ∙∙∙ {СМЕХ}  этот зайчик,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT29" ANNOTATION_REF="EDU29">
+        <ANNOTATION_VALUE>∙∙ и ёжик,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT30" ANNOTATION_REF="EDU30">
+        <ANNOTATION_VALUE>— у меня,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT31" ANNOTATION_REF="EDU31">
+        <ANNOTATION_VALUE>∙∙∙ и они прям бегали ∙∙ тут прям-м по моему полу.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT32" ANNOTATION_REF="EDU32">
+        <ANNOTATION_VALUE>∙∙∙ И по коврику,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT33" ANNOTATION_REF="EDU33">
+        <ANNOTATION_VALUE>∙∙ а я им сделала ∙∙ пала’= ‖ ∙∙ к= ‖ ∙∙ по= ‖ ∙∙ постельку,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT34" ANNOTATION_REF="EDU34">
+        <ANNOTATION_VALUE>∙∙ и они спали там.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT35" ANNOTATION_REF="EDU35">
+        <ANNOTATION_VALUE>∙∙∙ На коврике.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT36" ANNOTATION_REF="EDU36">
+        <ANNOTATION_VALUE>∙∙∙∙ А потом ∙∙ как-то неожиданно зайчик захотел морковку кушать.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT37" ANNOTATION_REF="EDU37">
+        <ANNOTATION_VALUE>∙∙ А морковка ∙∙ стояла ‖ ∙∙ у меня там ∙∙ лежала в тарелочке.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT38" ANNOTATION_REF="EDU38">
+        <ANNOTATION_VALUE>∙∙ Он взял эту морковку и скуш-шал.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT39" ANNOTATION_REF="EDU39">
+        <ANNOTATION_VALUE>∙∙∙ С капусткой.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT40" ANNOTATION_REF="EDU40">
+        <ANNOTATION_VALUE>∙∙ С капустой тоже,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT41" ANNOTATION_REF="EDU41">
+        <ANNOTATION_VALUE>капустку ∙∙ съел,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT42" ANNOTATION_REF="EDU42">
+        <ANNOTATION_VALUE>∙∙∙ а я потом встала,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT43" ANNOTATION_REF="EDU43">
+        <ANNOTATION_VALUE>∙∙ и увидела:</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT44" ANNOTATION_REF="EDU44">
+        <ANNOTATION_VALUE>а где морковь?,</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT45" ANNOTATION_REF="EDU45">
+        <ANNOTATION_VALUE>а где капуста?</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="TEXT46" ANNOTATION_REF="EDU46">
+        <ANNOTATION_VALUE>∙∙∙∙ Всё.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="EDUProp" PARENT_REF="EDU" TIER_ID="Punct">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT1" ANNOTATION_REF="EDU1">
+        <ANNOTATION_VALUE>FLST</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT2" ANNOTATION_REF="EDU2">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT3" ANNOTATION_REF="EDU3">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT4" ANNOTATION_REF="EDU4">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT5" ANNOTATION_REF="EDU5">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT6" ANNOTATION_REF="EDU6">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT7" ANNOTATION_REF="EDU7">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT8" ANNOTATION_REF="EDU8">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT9" ANNOTATION_REF="EDU9">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT10" ANNOTATION_REF="EDU10">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT11" ANNOTATION_REF="EDU11">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT12" ANNOTATION_REF="EDU12">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT13" ANNOTATION_REF="EDU13">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT14" ANNOTATION_REF="EDU14">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT15" ANNOTATION_REF="EDU15">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT16" ANNOTATION_REF="EDU16">
+        <ANNOTATION_VALUE>FLST</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT17" ANNOTATION_REF="EDU17">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT18" ANNOTATION_REF="EDU18">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT19" ANNOTATION_REF="EDU19">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT20" ANNOTATION_REF="EDU20">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT21" ANNOTATION_REF="EDU21">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT22" ANNOTATION_REF="EDU22">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT23" ANNOTATION_REF="EDU23">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT24" ANNOTATION_REF="EDU24">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT25" ANNOTATION_REF="EDU25">
+        <ANNOTATION_VALUE>EXCLAM</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT26" ANNOTATION_REF="EDU26">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT27" ANNOTATION_REF="EDU27">
+        <ANNOTATION_VALUE>SPLIT</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT28" ANNOTATION_REF="EDU28">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT29" ANNOTATION_REF="EDU29">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT30" ANNOTATION_REF="EDU30">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT31" ANNOTATION_REF="EDU31">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT32" ANNOTATION_REF="EDU32">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT33" ANNOTATION_REF="EDU33">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT34" ANNOTATION_REF="EDU34">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT35" ANNOTATION_REF="EDU35">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT36" ANNOTATION_REF="EDU36">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT37" ANNOTATION_REF="EDU37">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT38" ANNOTATION_REF="EDU38">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT39" ANNOTATION_REF="EDU39">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT40" ANNOTATION_REF="EDU40">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT41" ANNOTATION_REF="EDU41">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT42" ANNOTATION_REF="EDU42">
+        <ANNOTATION_VALUE>COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT43" ANNOTATION_REF="EDU43">
+        <ANNOTATION_VALUE>COLON</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT44" ANNOTATION_REF="EDU44">
+        <ANNOTATION_VALUE>QUEST+COMMA</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT45" ANNOTATION_REF="EDU45">
+        <ANNOTATION_VALUE>QUEST</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="PUNCT46" ANNOTATION_REF="EDU46">
+        <ANNOTATION_VALUE>PERIOD</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="EDUProp" PARENT_REF="EDU" TIER_ID="Comments">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT3" ANNOTATION_REF="EDU3">
+        <ANNOTATION_VALUE>В начальной паузе шумный вдох. Слово вот — скрипучим голосом.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT13" ANNOTATION_REF="EDU13">
+        <ANNOTATION_VALUE>В начальной паузе шумный вдох.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT17" ANNOTATION_REF="EDU17">
+        <ANNOTATION_VALUE>Начальный союз — скрипучим голосом.
Слово как-то произносит скандированно.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT21" ANNOTATION_REF="EDU21">
+        <ANNOTATION_VALUE>Падение на \мне почти на октаву.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT26" ANNOTATION_REF="EDU26">
+        <ANNOTATION_VALUE>Первый слог слова который произносит со смехом.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT28" ANNOTATION_REF="EDU28">
+        <ANNOTATION_VALUE>Смех частично накладывается на слово этот.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT31" ANNOTATION_REF="EDU31">
+        <ANNOTATION_VALUE>Слова тут прям-м произносит с улыбкой. Первые два слова и последнее слово — скрипучим голосом.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT35" ANNOTATION_REF="EDU35">
+        <ANNOTATION_VALUE>Тихо. С улыбкой.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT40" ANNOTATION_REF="EDU40">
+        <ANNOTATION_VALUE>Тихо.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="COMMENT41" ANNOTATION_REF="EDU41">
+        <ANNOTATION_VALUE>Очень тихо.</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="Word" PARENT_REF="EDU" TIER_ID="Words">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD10" ANNOTATION_REF="EDU1">
+        <ANNOTATION_VALUE>Пото=</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD20" ANNOTATION_REF="EDU2">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD21" ANNOTATION_REF="EDU2" PREVIOUS_ANNOTATION="WORD20">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD22" ANNOTATION_REF="EDU2" PREVIOUS_ANNOTATION="WORD21">
+        <ANNOTATION_VALUE>про</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD23" ANNOTATION_REF="EDU2" PREVIOUS_ANNOTATION="WORD22">
+        <ANNOTATION_VALUE>зайчика</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD24" ANNOTATION_REF="EDU2" PREVIOUS_ANNOTATION="WORD23">
+        <ANNOTATION_VALUE>сон</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD30" ANNOTATION_REF="EDU3">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD31" ANNOTATION_REF="EDU3" PREVIOUS_ANNOTATION="WORD30">
+        <ANNOTATION_VALUE>зайчик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD32" ANNOTATION_REF="EDU3" PREVIOUS_ANNOTATION="WORD31">
+        <ANNOTATION_VALUE>вот</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD40" ANNOTATION_REF="EDU4">
+        <ANNOTATION_VALUE>он</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD41" ANNOTATION_REF="EDU4" PREVIOUS_ANNOTATION="WORD40">
+        <ANNOTATION_VALUE>был</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD42" ANNOTATION_REF="EDU4" PREVIOUS_ANNOTATION="WORD41">
+        <ANNOTATION_VALUE>в</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD43" ANNOTATION_REF="EDU4" PREVIOUS_ANNOTATION="WORD42">
+        <ANNOTATION_VALUE>лесу</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD50" ANNOTATION_REF="EDU5">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD51" ANNOTATION_REF="EDU5" PREVIOUS_ANNOTATION="WORD50">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD52" ANNOTATION_REF="EDU5" PREVIOUS_ANNOTATION="WORD51">
+        <ANNOTATION_VALUE>была</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD53" ANNOTATION_REF="EDU5" PREVIOUS_ANNOTATION="WORD52">
+        <ANNOTATION_VALUE>у</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD54" ANNOTATION_REF="EDU5" PREVIOUS_ANNOTATION="WORD53">
+        <ANNOTATION_VALUE>речки</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD60" ANNOTATION_REF="EDU6">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD61" ANNOTATION_REF="EDU6" PREVIOUS_ANNOTATION="WORD60">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD62" ANNOTATION_REF="EDU6" PREVIOUS_ANNOTATION="WORD61">
+        <ANNOTATION_VALUE>ког=</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD63" ANNOTATION_REF="EDU6" PREVIOUS_ANNOTATION="WORD62">
+        <ANNOTATION_VALUE>вот</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD64" ANNOTATION_REF="EDU6" PREVIOUS_ANNOTATION="WORD63">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD65" ANNOTATION_REF="EDU6" PREVIOUS_ANNOTATION="WORD64">
+        <ANNOTATION_VALUE>пошла</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD70" ANNOTATION_REF="EDU7">
+        <ANNOTATION_VALUE>к</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD71" ANNOTATION_REF="EDU7" PREVIOUS_ANNOTATION="WORD70">
+        <ANNOTATION_VALUE>домой</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD80" ANNOTATION_REF="EDU8">
+        <ANNOTATION_VALUE>а</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD81" ANNOTATION_REF="EDU8" PREVIOUS_ANNOTATION="WORD80">
+        <ANNOTATION_VALUE>зайчик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD82" ANNOTATION_REF="EDU8" PREVIOUS_ANNOTATION="WORD81">
+        <ANNOTATION_VALUE>за</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD83" ANNOTATION_REF="EDU8" PREVIOUS_ANNOTATION="WORD82">
+        <ANNOTATION_VALUE>мной</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD90" ANNOTATION_REF="EDU9">
+        <ANNOTATION_VALUE>бежал-бежал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD100" ANNOTATION_REF="EDU10">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD101" ANNOTATION_REF="EDU10" PREVIOUS_ANNOTATION="WORD100">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD102" ANNOTATION_REF="EDU10" PREVIOUS_ANNOTATION="WORD101">
+        <ANNOTATION_VALUE>он</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD103" ANNOTATION_REF="EDU10" PREVIOUS_ANNOTATION="WORD102">
+        <ANNOTATION_VALUE>прибежал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD110" ANNOTATION_REF="EDU11">
+        <ANNOTATION_VALUE>ко</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD111" ANNOTATION_REF="EDU11" PREVIOUS_ANNOTATION="WORD110">
+        <ANNOTATION_VALUE>мне</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD112" ANNOTATION_REF="EDU11" PREVIOUS_ANNOTATION="WORD111">
+        <ANNOTATION_VALUE>опять</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD120" ANNOTATION_REF="EDU12">
+        <ANNOTATION_VALUE>когда</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD121" ANNOTATION_REF="EDU12" PREVIOUS_ANNOTATION="WORD120">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD122" ANNOTATION_REF="EDU12" PREVIOUS_ANNOTATION="WORD121">
+        <ANNOTATION_VALUE>ушла</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD123" ANNOTATION_REF="EDU12" PREVIOUS_ANNOTATION="WORD122">
+        <ANNOTATION_VALUE>уже</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD130" ANNOTATION_REF="EDU13">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD131" ANNOTATION_REF="EDU13" PREVIOUS_ANNOTATION="WORD130">
+        <ANNOTATION_VALUE>вышла</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD140" ANNOTATION_REF="EDU14">
+        <ANNOTATION_VALUE>к</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD141" ANNOTATION_REF="EDU14" PREVIOUS_ANNOTATION="WORD140">
+        <ANNOTATION_VALUE>себе</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD150" ANNOTATION_REF="EDU15">
+        <ANNOTATION_VALUE>во</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD151" ANNOTATION_REF="EDU15" PREVIOUS_ANNOTATION="WORD150">
+        <ANNOTATION_VALUE>двор</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD160" ANNOTATION_REF="EDU16">
+        <ANNOTATION_VALUE>И</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD161" ANNOTATION_REF="EDU16" PREVIOUS_ANNOTATION="WORD160">
+        <ANNOTATION_VALUE>онь=</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD170" ANNOTATION_REF="EDU17">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD171" ANNOTATION_REF="EDU17" PREVIOUS_ANNOTATION="WORD170">
+        <ANNOTATION_VALUE>зайчик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD172" ANNOTATION_REF="EDU17" PREVIOUS_ANNOTATION="WORD171">
+        <ANNOTATION_VALUE>как-то</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD173" ANNOTATION_REF="EDU17" PREVIOUS_ANNOTATION="WORD172">
+        <ANNOTATION_VALUE>сюда</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD174" ANNOTATION_REF="EDU17" PREVIOUS_ANNOTATION="WORD173">
+        <ANNOTATION_VALUE>прискакал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD180" ANNOTATION_REF="EDU18">
+        <ANNOTATION_VALUE>а</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD181" ANNOTATION_REF="EDU18" PREVIOUS_ANNOTATION="WORD180">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD182" ANNOTATION_REF="EDU18" PREVIOUS_ANNOTATION="WORD181">
+        <ANNOTATION_VALUE>он</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD183" ANNOTATION_REF="EDU18" PREVIOUS_ANNOTATION="WORD182">
+        <ANNOTATION_VALUE>не</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD184" ANNOTATION_REF="EDU18" PREVIOUS_ANNOTATION="WORD183">
+        <ANNOTATION_VALUE>знал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD190" ANNOTATION_REF="EDU19">
+        <ANNOTATION_VALUE>где</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD191" ANNOTATION_REF="EDU19" PREVIOUS_ANNOTATION="WORD190">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD200" ANNOTATION_REF="EDU20">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD201" ANNOTATION_REF="EDU20" PREVIOUS_ANNOTATION="WORD200">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD202" ANNOTATION_REF="EDU20" PREVIOUS_ANNOTATION="WORD201">
+        <ANNOTATION_VALUE>была</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD203" ANNOTATION_REF="EDU20" PREVIOUS_ANNOTATION="WORD202">
+        <ANNOTATION_VALUE>у</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD204" ANNOTATION_REF="EDU20" PREVIOUS_ANNOTATION="WORD203">
+        <ANNOTATION_VALUE>себя</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD205" ANNOTATION_REF="EDU20" PREVIOUS_ANNOTATION="WORD204">
+        <ANNOTATION_VALUE>дома</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD210" ANNOTATION_REF="EDU21">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD211" ANNOTATION_REF="EDU21" PREVIOUS_ANNOTATION="WORD210">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD212" ANNOTATION_REF="EDU21" PREVIOUS_ANNOTATION="WORD211">
+        <ANNOTATION_VALUE>этот</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD213" ANNOTATION_REF="EDU21" PREVIOUS_ANNOTATION="WORD212">
+        <ANNOTATION_VALUE>зайчик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD214" ANNOTATION_REF="EDU21" PREVIOUS_ANNOTATION="WORD213">
+        <ANNOTATION_VALUE>прискакал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD215" ANNOTATION_REF="EDU21" PREVIOUS_ANNOTATION="WORD214">
+        <ANNOTATION_VALUE>ко</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD216" ANNOTATION_REF="EDU21" PREVIOUS_ANNOTATION="WORD215">
+        <ANNOTATION_VALUE>мне</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD217" ANNOTATION_REF="EDU21" PREVIOUS_ANNOTATION="WORD216">
+        <ANNOTATION_VALUE>домой</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD220" ANNOTATION_REF="EDU22">
+        <ANNOTATION_VALUE>Он</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD221" ANNOTATION_REF="EDU22" PREVIOUS_ANNOTATION="WORD220">
+        <ANNOTATION_VALUE>скакал-скакал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD230" ANNOTATION_REF="EDU23">
+        <ANNOTATION_VALUE>и</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD231" ANNOTATION_REF="EDU23" PREVIOUS_ANNOTATION="WORD230">
+        <ANNOTATION_VALUE>прискакал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD240" ANNOTATION_REF="EDU24">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD241" ANNOTATION_REF="EDU24" PREVIOUS_ANNOTATION="WORD240">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD242" ANNOTATION_REF="EDU24" PREVIOUS_ANNOTATION="WORD241">
+        <ANNOTATION_VALUE>он</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD243" ANNOTATION_REF="EDU24" PREVIOUS_ANNOTATION="WORD242">
+        <ANNOTATION_VALUE>ск=</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD244" ANNOTATION_REF="EDU24" PREVIOUS_ANNOTATION="WORD243">
+        <ANNOTATION_VALUE>этот</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD245" ANNOTATION_REF="EDU24" PREVIOUS_ANNOTATION="WORD244">
+        <ANNOTATION_VALUE>зайчик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD246" ANNOTATION_REF="EDU24" PREVIOUS_ANNOTATION="WORD245">
+        <ANNOTATION_VALUE>встретил</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD247" ANNOTATION_REF="EDU24" PREVIOUS_ANNOTATION="WORD246">
+        <ANNOTATION_VALUE>ёжика</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD250" ANNOTATION_REF="EDU25">
+        <ANNOTATION_VALUE>У</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD251" ANNOTATION_REF="EDU25" PREVIOUS_ANNOTATION="WORD250">
+        <ANNOTATION_VALUE>меня</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD260" ANNOTATION_REF="EDU26">
+        <ANNOTATION_VALUE>Который</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD261" ANNOTATION_REF="EDU26" PREVIOUS_ANNOTATION="WORD260">
+        <ANNOTATION_VALUE>был</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD262" ANNOTATION_REF="EDU26" PREVIOUS_ANNOTATION="WORD261">
+        <ANNOTATION_VALUE>у</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD263" ANNOTATION_REF="EDU26" PREVIOUS_ANNOTATION="WORD262">
+        <ANNOTATION_VALUE>меня</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD264" ANNOTATION_REF="EDU26" PREVIOUS_ANNOTATION="WORD263">
+        <ANNOTATION_VALUE>дома</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD270" ANNOTATION_REF="EDU27">
+        <ANNOTATION_VALUE>и</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD271" ANNOTATION_REF="EDU27" PREVIOUS_ANNOTATION="WORD270">
+        <ANNOTATION_VALUE>они</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD272" ANNOTATION_REF="EDU27" PREVIOUS_ANNOTATION="WORD271">
+        <ANNOTATION_VALUE>были</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD280" ANNOTATION_REF="EDU28">
+        <ANNOTATION_VALUE>этот</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD281" ANNOTATION_REF="EDU28" PREVIOUS_ANNOTATION="WORD280">
+        <ANNOTATION_VALUE>зайчик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD290" ANNOTATION_REF="EDU29">
+        <ANNOTATION_VALUE>и</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD291" ANNOTATION_REF="EDU29" PREVIOUS_ANNOTATION="WORD290">
+        <ANNOTATION_VALUE>ёжик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD300" ANNOTATION_REF="EDU30">
+        <ANNOTATION_VALUE>у</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD301" ANNOTATION_REF="EDU30" PREVIOUS_ANNOTATION="WORD300">
+        <ANNOTATION_VALUE>меня</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD310" ANNOTATION_REF="EDU31">
+        <ANNOTATION_VALUE>и</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD311" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD310">
+        <ANNOTATION_VALUE>они</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD312" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD311">
+        <ANNOTATION_VALUE>прям</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD313" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD312">
+        <ANNOTATION_VALUE>бегали</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD314" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD313">
+        <ANNOTATION_VALUE>тут</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD315" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD314">
+        <ANNOTATION_VALUE>прям</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD316" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD315">
+        <ANNOTATION_VALUE>по</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD317" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD316">
+        <ANNOTATION_VALUE>моему</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD318" ANNOTATION_REF="EDU31" PREVIOUS_ANNOTATION="WORD317">
+        <ANNOTATION_VALUE>полу</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD320" ANNOTATION_REF="EDU32">
+        <ANNOTATION_VALUE>И</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD321" ANNOTATION_REF="EDU32" PREVIOUS_ANNOTATION="WORD320">
+        <ANNOTATION_VALUE>по</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD322" ANNOTATION_REF="EDU32" PREVIOUS_ANNOTATION="WORD321">
+        <ANNOTATION_VALUE>коврику</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD330" ANNOTATION_REF="EDU33">
+        <ANNOTATION_VALUE>а</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD331" ANNOTATION_REF="EDU33" PREVIOUS_ANNOTATION="WORD330">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD332" ANNOTATION_REF="EDU33" PREVIOUS_ANNOTATION="WORD331">
+        <ANNOTATION_VALUE>им</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD333" ANNOTATION_REF="EDU33" PREVIOUS_ANNOTATION="WORD332">
+        <ANNOTATION_VALUE>сделала</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD334" ANNOTATION_REF="EDU33" PREVIOUS_ANNOTATION="WORD333">
+        <ANNOTATION_VALUE>пала’=</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD335" ANNOTATION_REF="EDU33" PREVIOUS_ANNOTATION="WORD334">
+        <ANNOTATION_VALUE>к=</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD336" ANNOTATION_REF="EDU33" PREVIOUS_ANNOTATION="WORD335">
+        <ANNOTATION_VALUE>по=</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD337" ANNOTATION_REF="EDU33" PREVIOUS_ANNOTATION="WORD336">
+        <ANNOTATION_VALUE>постельку</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD340" ANNOTATION_REF="EDU34">
+        <ANNOTATION_VALUE>и</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD341" ANNOTATION_REF="EDU34" PREVIOUS_ANNOTATION="WORD340">
+        <ANNOTATION_VALUE>они</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD342" ANNOTATION_REF="EDU34" PREVIOUS_ANNOTATION="WORD341">
+        <ANNOTATION_VALUE>спали</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD343" ANNOTATION_REF="EDU34" PREVIOUS_ANNOTATION="WORD342">
+        <ANNOTATION_VALUE>там</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD350" ANNOTATION_REF="EDU35">
+        <ANNOTATION_VALUE>На</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD351" ANNOTATION_REF="EDU35" PREVIOUS_ANNOTATION="WORD350">
+        <ANNOTATION_VALUE>коврике</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD360" ANNOTATION_REF="EDU36">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD361" ANNOTATION_REF="EDU36" PREVIOUS_ANNOTATION="WORD360">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD362" ANNOTATION_REF="EDU36" PREVIOUS_ANNOTATION="WORD361">
+        <ANNOTATION_VALUE>как-то</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD363" ANNOTATION_REF="EDU36" PREVIOUS_ANNOTATION="WORD362">
+        <ANNOTATION_VALUE>неожиданно</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD364" ANNOTATION_REF="EDU36" PREVIOUS_ANNOTATION="WORD363">
+        <ANNOTATION_VALUE>зайчик</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD365" ANNOTATION_REF="EDU36" PREVIOUS_ANNOTATION="WORD364">
+        <ANNOTATION_VALUE>захотел</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD366" ANNOTATION_REF="EDU36" PREVIOUS_ANNOTATION="WORD365">
+        <ANNOTATION_VALUE>морковку</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD367" ANNOTATION_REF="EDU36" PREVIOUS_ANNOTATION="WORD366">
+        <ANNOTATION_VALUE>кушать</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD370" ANNOTATION_REF="EDU37">
+        <ANNOTATION_VALUE>А</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD371" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD370">
+        <ANNOTATION_VALUE>морковка</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD372" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD371">
+        <ANNOTATION_VALUE>стояла</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD373" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD372">
+        <ANNOTATION_VALUE>у</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD374" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD373">
+        <ANNOTATION_VALUE>меня</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD375" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD374">
+        <ANNOTATION_VALUE>там</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD376" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD375">
+        <ANNOTATION_VALUE>лежала</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD377" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD376">
+        <ANNOTATION_VALUE>в</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD378" ANNOTATION_REF="EDU37" PREVIOUS_ANNOTATION="WORD377">
+        <ANNOTATION_VALUE>тарелочке</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD380" ANNOTATION_REF="EDU38">
+        <ANNOTATION_VALUE>Он</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD381" ANNOTATION_REF="EDU38" PREVIOUS_ANNOTATION="WORD380">
+        <ANNOTATION_VALUE>взял</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD382" ANNOTATION_REF="EDU38" PREVIOUS_ANNOTATION="WORD381">
+        <ANNOTATION_VALUE>эту</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD383" ANNOTATION_REF="EDU38" PREVIOUS_ANNOTATION="WORD382">
+        <ANNOTATION_VALUE>морковку</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD384" ANNOTATION_REF="EDU38" PREVIOUS_ANNOTATION="WORD383">
+        <ANNOTATION_VALUE>и</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD385" ANNOTATION_REF="EDU38" PREVIOUS_ANNOTATION="WORD384">
+        <ANNOTATION_VALUE>скушал</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD390" ANNOTATION_REF="EDU39">
+        <ANNOTATION_VALUE>С</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD391" ANNOTATION_REF="EDU39" PREVIOUS_ANNOTATION="WORD390">
+        <ANNOTATION_VALUE>капусткой</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD400" ANNOTATION_REF="EDU40">
+        <ANNOTATION_VALUE>С</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD401" ANNOTATION_REF="EDU40" PREVIOUS_ANNOTATION="WORD400">
+        <ANNOTATION_VALUE>капустой</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD402" ANNOTATION_REF="EDU40" PREVIOUS_ANNOTATION="WORD401">
+        <ANNOTATION_VALUE>тоже</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD410" ANNOTATION_REF="EDU41">
+        <ANNOTATION_VALUE>капустку</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD411" ANNOTATION_REF="EDU41" PREVIOUS_ANNOTATION="WORD410">
+        <ANNOTATION_VALUE>съел</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD420" ANNOTATION_REF="EDU42">
+        <ANNOTATION_VALUE>а</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD421" ANNOTATION_REF="EDU42" PREVIOUS_ANNOTATION="WORD420">
+        <ANNOTATION_VALUE>я</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD422" ANNOTATION_REF="EDU42" PREVIOUS_ANNOTATION="WORD421">
+        <ANNOTATION_VALUE>потом</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD423" ANNOTATION_REF="EDU42" PREVIOUS_ANNOTATION="WORD422">
+        <ANNOTATION_VALUE>встала</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD430" ANNOTATION_REF="EDU43">
+        <ANNOTATION_VALUE>и</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD431" ANNOTATION_REF="EDU43" PREVIOUS_ANNOTATION="WORD430">
+        <ANNOTATION_VALUE>увидела</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD440" ANNOTATION_REF="EDU44">
+        <ANNOTATION_VALUE>а</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD441" ANNOTATION_REF="EDU44" PREVIOUS_ANNOTATION="WORD440">
+        <ANNOTATION_VALUE>где</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD442" ANNOTATION_REF="EDU44" PREVIOUS_ANNOTATION="WORD441">
+        <ANNOTATION_VALUE>морковь</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD450" ANNOTATION_REF="EDU45">
+        <ANNOTATION_VALUE>а</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD451" ANNOTATION_REF="EDU45" PREVIOUS_ANNOTATION="WORD450">
+        <ANNOTATION_VALUE>где</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD452" ANNOTATION_REF="EDU45" PREVIOUS_ANNOTATION="WORD451">
+        <ANNOTATION_VALUE>капуста</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="WORD460" ANNOTATION_REF="EDU46">
+        <ANNOTATION_VALUE>Всё</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="WProp" PARENT_REF="Words" TIER_ID="Accents">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT21" ANNOTATION_REF="WORD21">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT23" ANNOTATION_REF="WORD23">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT31" ANNOTATION_REF="WORD31">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT43" ANNOTATION_REF="WORD43">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT51" ANNOTATION_REF="WORD51">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT54" ANNOTATION_REF="WORD54">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT61" ANNOTATION_REF="WORD61">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT65" ANNOTATION_REF="WORD65">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT71" ANNOTATION_REF="WORD71">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT81" ANNOTATION_REF="WORD81">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT83" ANNOTATION_REF="WORD83">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT90" ANNOTATION_REF="WORD90">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT101" ANNOTATION_REF="WORD101">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT103" ANNOTATION_REF="WORD103">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT112" ANNOTATION_REF="WORD112">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT122" ANNOTATION_REF="WORD122">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT131" ANNOTATION_REF="WORD131">
+        <ANNOTATION_VALUE>R-F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT141" ANNOTATION_REF="WORD141">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT151" ANNOTATION_REF="WORD151">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT171" ANNOTATION_REF="WORD171">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT174" ANNOTATION_REF="WORD174">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT181" ANNOTATION_REF="WORD181">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT184" ANNOTATION_REF="WORD184">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT191" ANNOTATION_REF="WORD191">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT201" ANNOTATION_REF="WORD201">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT202" ANNOTATION_REF="WORD202">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT205" ANNOTATION_REF="WORD205">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT211" ANNOTATION_REF="WORD211">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT221" ANNOTATION_REF="WORD221">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT231" ANNOTATION_REF="WORD231">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT241" ANNOTATION_REF="WORD241">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT245" ANNOTATION_REF="WORD245">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT246" ANNOTATION_REF="WORD246">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT247" ANNOTATION_REF="WORD247">
+        <ANNOTATION_VALUE>R-F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT251" ANNOTATION_REF="WORD251">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT264" ANNOTATION_REF="WORD264">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT271" ANNOTATION_REF="WORD271">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT281" ANNOTATION_REF="WORD281">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT291" ANNOTATION_REF="WORD291">
+        <ANNOTATION_VALUE>R-P</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT301" ANNOTATION_REF="WORD301">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT311" ANNOTATION_REF="WORD311">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT313" ANNOTATION_REF="WORD313">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT318" ANNOTATION_REF="WORD318">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT322" ANNOTATION_REF="WORD322">
+        <ANNOTATION_VALUE>P</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT331" ANNOTATION_REF="WORD331">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT337" ANNOTATION_REF="WORD337">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT342" ANNOTATION_REF="WORD342">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT351" ANNOTATION_REF="WORD351">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT361" ANNOTATION_REF="WORD361">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT363" ANNOTATION_REF="WORD363">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT366" ANNOTATION_REF="WORD366">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT371" ANNOTATION_REF="WORD371">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT372" ANNOTATION_REF="WORD372">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT374" ANNOTATION_REF="WORD374">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT376" ANNOTATION_REF="WORD376">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT378" ANNOTATION_REF="WORD378">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT381" ANNOTATION_REF="WORD381">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT385" ANNOTATION_REF="WORD385">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT391" ANNOTATION_REF="WORD391">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT402" ANNOTATION_REF="WORD402">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT411" ANNOTATION_REF="WORD411">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT421" ANNOTATION_REF="WORD421">
+        <ANNOTATION_VALUE>R-F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT423" ANNOTATION_REF="WORD423">
+        <ANNOTATION_VALUE>R</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT431" ANNOTATION_REF="WORD431">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT441" ANNOTATION_REF="WORD441">
+        <ANNOTATION_VALUE>R-F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT442" ANNOTATION_REF="WORD442">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT451" ANNOTATION_REF="WORD451">
+        <ANNOTATION_VALUE>R-F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT452" ANNOTATION_REF="WORD452">
+        <ANNOTATION_VALUE>F</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="ACCENT460" ANNOTATION_REF="WORD460">
+        <ANNOTATION_VALUE>P</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="WProp" PARENT_REF="Accents" TIER_ID="MainAccent">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT23" ANNOTATION_REF="WORD23">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT31" ANNOTATION_REF="WORD31">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT43" ANNOTATION_REF="WORD43">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT54" ANNOTATION_REF="WORD54">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT65" ANNOTATION_REF="WORD65">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT71" ANNOTATION_REF="WORD71">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT83" ANNOTATION_REF="WORD83">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT90" ANNOTATION_REF="WORD90">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT103" ANNOTATION_REF="WORD103">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT112" ANNOTATION_REF="WORD112">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT122" ANNOTATION_REF="WORD122">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT131" ANNOTATION_REF="WORD131">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT141" ANNOTATION_REF="WORD141">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT151" ANNOTATION_REF="WORD151">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT174" ANNOTATION_REF="WORD174">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT184" ANNOTATION_REF="WORD184">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT191" ANNOTATION_REF="WORD191">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT205" ANNOTATION_REF="WORD205">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT221" ANNOTATION_REF="WORD221">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT231" ANNOTATION_REF="WORD231">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT247" ANNOTATION_REF="WORD247">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT251" ANNOTATION_REF="WORD251">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT264" ANNOTATION_REF="WORD264">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT281" ANNOTATION_REF="WORD281">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT291" ANNOTATION_REF="WORD291">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT301" ANNOTATION_REF="WORD301">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT318" ANNOTATION_REF="WORD318">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT322" ANNOTATION_REF="WORD322">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT337" ANNOTATION_REF="WORD337">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT342" ANNOTATION_REF="WORD342">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT351" ANNOTATION_REF="WORD351">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT366" ANNOTATION_REF="WORD366">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT378" ANNOTATION_REF="WORD378">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT385" ANNOTATION_REF="WORD385">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT391" ANNOTATION_REF="WORD391">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT402" ANNOTATION_REF="WORD402">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT411" ANNOTATION_REF="WORD411">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT423" ANNOTATION_REF="WORD423">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT431" ANNOTATION_REF="WORD431">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT441" ANNOTATION_REF="WORD441">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT442" ANNOTATION_REF="WORD442">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT451" ANNOTATION_REF="WORD451">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT452" ANNOTATION_REF="WORD452">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="MAINACCENT460" ANNOTATION_REF="WORD460">
+        <ANNOTATION_VALUE>Main</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="WProp" PARENT_REF="Words" TIER_ID="Lengthening">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="LENGTHENING54" ANNOTATION_REF="WORD54">
+        <ANNOTATION_VALUE>Len</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="LENGTHENING183" ANNOTATION_REF="WORD183">
+        <ANNOTATION_VALUE>Len</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="LENGTHENING315" ANNOTATION_REF="WORD315">
+        <ANNOTATION_VALUE>Len</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="LENGTHENING385" ANNOTATION_REF="WORD385">
+        <ANNOTATION_VALUE>Len</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="WProp" PARENT_REF="Words" TIER_ID="Reduction">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION32" ANNOTATION_REF="WORD32">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION120" ANNOTATION_REF="WORD120">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION171" ANNOTATION_REF="WORD171">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION212" ANNOTATION_REF="WORD212">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION280" ANNOTATION_REF="WORD280">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION367" ANNOTATION_REF="WORD367">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION378" ANNOTATION_REF="WORD378">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION422" ANNOTATION_REF="WORD422">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="REDUCTION423" ANNOTATION_REF="WORD423">
+        <ANNOTATION_VALUE>Red</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="WProp" PARENT_REF="Words" TIER_ID="Stop">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="STOP172" ANNOTATION_REF="WORD172">
+        <ANNOTATION_VALUE>Asp-E</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="STOP202" ANNOTATION_REF="WORD202">
+        <ANNOTATION_VALUE>Lab-E</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="STOP243" ANNOTATION_REF="WORD243">
+        <ANNOTATION_VALUE>Asp-E</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="STOP334" ANNOTATION_REF="WORD334">
+        <ANNOTATION_VALUE>Gl-E</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="STOP336" ANNOTATION_REF="WORD336">
+        <ANNOTATION_VALUE>Asp-E</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="WProp" PARENT_REF="Words" TIER_ID="Emph">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="EMPH215" ANNOTATION_REF="WORD215">
+        <ANNOTATION_VALUE>Emph</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="EMPH216" ANNOTATION_REF="WORD216">
+        <ANNOTATION_VALUE>Emph</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="EMPH247" ANNOTATION_REF="WORD247">
+        <ANNOTATION_VALUE>Emph</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <TIER DEFAULT_LOCALE="ru" LINGUISTIC_TYPE_REF="WProp" PARENT_REF="Words" TIER_ID="Corr_Inside">
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="CORRINSIDE62" ANNOTATION_REF="WORD62">
+        <ANNOTATION_VALUE>YES</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="CORRINSIDE70" ANNOTATION_REF="WORD70">
+        <ANNOTATION_VALUE>YES</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="CORRINSIDE243" ANNOTATION_REF="WORD243">
+        <ANNOTATION_VALUE>YES</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="CORRINSIDE334" ANNOTATION_REF="WORD334">
+        <ANNOTATION_VALUE>YES</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="CORRINSIDE335" ANNOTATION_REF="WORD335">
+        <ANNOTATION_VALUE>YES</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="CORRINSIDE336" ANNOTATION_REF="WORD336">
+        <ANNOTATION_VALUE>YES</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+    <ANNOTATION>
+      <REF_ANNOTATION ANNOTATION_ID="CORRINSIDE372" ANNOTATION_REF="WORD372">
+        <ANNOTATION_VALUE>YES</ANNOTATION_VALUE>
+      </REF_ANNOTATION>
+    </ANNOTATION>
+  </TIER>
+  <LINGUISTIC_TYPE GRAPHIC_REFERENCES="true" LINGUISTIC_TYPE_ID="EDU" TIME_ALIGNABLE="true"/>
+  <LINGUISTIC_TYPE CONSTRAINTS="Symbolic_Subdivision" GRAPHIC_REFERENCES="false" LINGUISTIC_TYPE_ID="Word" TIME_ALIGNABLE="false"/>
+  <LINGUISTIC_TYPE CONSTRAINTS="Symbolic_Association" GRAPHIC_REFERENCES="false" LINGUISTIC_TYPE_ID="EDUProp" TIME_ALIGNABLE="false"/>
+  <LINGUISTIC_TYPE CONSTRAINTS="Symbolic_Association" GRAPHIC_REFERENCES="false" LINGUISTIC_TYPE_ID="WProp" TIME_ALIGNABLE="false"/>
+  <LINGUISTIC_TYPE CONSTRAINTS="Included_In" GRAPHIC_REFERENCES="true" LINGUISTIC_TYPE_ID="Pause" TIME_ALIGNABLE="true"/>
+</ANNOTATION_DOCUMENT>

+ 92 - 0
libraries/wavesurfer/example/equalizer/index.html

@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Equalizer Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- Demo -->
+        <script src="main.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">Equalizer Example</h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <div class="progress progress-striped active" id="progress-bar">
+                        <div class="progress-bar progress-bar-info"></div>
+                    </div>
+
+                    <!-- Here be the waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+
+                    <div id="equalizer">
+                        <!-- Here be equalizer sliders -->
+                    </div>
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <div class="pull-right">
+                        <noindex>
+                        Demo music track is <a href="http://www.jamendo.com/en/track/661578/trou" rel="nofollow"><b>Trou</b> <span class="muted">by</span>&nbsp;<b>czskamaarù</b></a>. Thanks!
+                        </noindex>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 128 - 0
libraries/wavesurfer/example/equalizer/main.js

@@ -0,0 +1,128 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load audio file
+document.addEventListener('DOMContentLoaded', function () {
+    // Init
+    wavesurfer = WaveSurfer.create({
+        container: document.querySelector('#waveform'),
+        waveColor: '#A8DBA8',
+        progressColor: '#3B8686'
+    });
+
+    // Load audio from URL
+    wavesurfer.load('../media/demo.wav');
+
+    // Equalizer
+    wavesurfer.on('ready', function () {
+        var EQ = [
+            {
+                f: 32,
+                type: 'lowshelf'
+            }, {
+                f: 64,
+                type: 'peaking'
+            }, {
+                f: 125,
+                type: 'peaking'
+            }, {
+                f: 250,
+                type: 'peaking'
+            }, {
+                f: 500,
+                type: 'peaking'
+            }, {
+                f: 1000,
+                type: 'peaking'
+            }, {
+                f: 2000,
+                type: 'peaking'
+            }, {
+                f: 4000,
+                type: 'peaking'
+            }, {
+                f: 8000,
+                type: 'peaking'
+            }, {
+                f: 16000,
+                type: 'highshelf'
+            }
+        ];
+
+        // Create filters
+        var filters = EQ.map(function (band) {
+            var filter = wavesurfer.backend.ac.createBiquadFilter();
+            filter.type = band.type;
+            filter.gain.value = 0;
+            filter.Q.value = 1;
+            filter.frequency.value = band.f;
+            return filter;
+        });
+
+        // Connect filters to wavesurfer
+        wavesurfer.backend.setFilters(filters);
+
+        // Bind filters to vertical range sliders
+        var container = document.querySelector('#equalizer');
+        filters.forEach(function (filter) {
+            var input = document.createElement('input');
+            wavesurfer.util.extend(input, {
+                type: 'range',
+                min: -40,
+                max: 40,
+                value: 0,
+                title: filter.frequency.value
+            });
+            input.style.display = 'inline-block';
+            input.setAttribute('orient', 'vertical');
+            wavesurfer.util.style(input, {
+                'webkitAppearance': 'slider-vertical',
+                width: '50px',
+                height: '150px'
+            });
+            container.appendChild(input);
+
+            var onChange = function (e) {
+                filter.gain.value = ~~e.target.value;
+            };
+
+            input.addEventListener('input', onChange);
+            input.addEventListener('change', onChange);
+        });
+
+        // For debugging
+        wavesurfer.filters = filters;
+    });
+
+    // Log errors
+    wavesurfer.on('error', function (msg) {
+        console.log(msg);
+    });
+
+    // Bind play/pause button
+    document.querySelector(
+        '[data-action="play"]'
+    ).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
+
+    // Progress bar
+    (function () {
+        var progressDiv = document.querySelector('#progress-bar');
+        var progressBar = progressDiv.querySelector('.progress-bar');
+
+        var showProgress = function (percent) {
+            progressDiv.style.display = 'block';
+            progressBar.style.width = percent + '%';
+        };
+
+        var hideProgress = function () {
+            progressDiv.style.display = 'none';
+        };
+
+        wavesurfer.on('loading', showProgress);
+        wavesurfer.on('ready', hideProgress);
+        wavesurfer.on('destroy', hideProgress);
+        wavesurfer.on('error', hideProgress);
+    }());
+});

File diff suppressed because it is too large
+ 84 - 0
libraries/wavesurfer/example/html-init/index.html


+ 108 - 0
libraries/wavesurfer/example/main.js

@@ -0,0 +1,108 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load audio file
+document.addEventListener('DOMContentLoaded', function () {
+    var options = {
+        container     : document.querySelector('#waveform'),
+        waveColor     : 'violet',
+        progressColor : 'purple',
+        cursorColor   : 'navy'
+    };
+
+    if (location.search.match('scroll')) {
+        options.minPxPerSec = 100;
+        options.scrollParent = true;
+    }
+
+    // Init
+    wavesurfer = WaveSurfer.create(options);
+    // Load audio from URL
+    wavesurfer.load('example/media/demo.wav');
+
+    // Regions
+    if (wavesurfer.enableDragSelection) {
+        wavesurfer.enableDragSelection({
+            color: 'rgba(0, 255, 0, 0.1)'
+        });
+    }
+});
+
+// Play at once when ready
+// Won't work on iOS until you touch the page
+wavesurfer.on('ready', function () {
+    //wavesurfer.play();
+});
+
+// Report errors
+wavesurfer.on('error', function (err) {
+    console.error(err);
+});
+
+// Do something when the clip is over
+wavesurfer.on('finish', function () {
+    console.log('Finished playing');
+});
+
+
+/* Progress bar */
+document.addEventListener('DOMContentLoaded', function () {
+    var progressDiv = document.querySelector('#progress-bar');
+    var progressBar = progressDiv.querySelector('.progress-bar');
+
+    var showProgress = function (percent) {
+        progressDiv.style.display = 'block';
+        progressBar.style.width = percent + '%';
+    };
+
+    var hideProgress = function () {
+        progressDiv.style.display = 'none';
+    };
+
+    wavesurfer.on('loading', showProgress);
+    wavesurfer.on('ready', hideProgress);
+    wavesurfer.on('destroy', hideProgress);
+    wavesurfer.on('error', hideProgress);
+});
+
+
+// Drag'n'drop
+document.addEventListener('DOMContentLoaded', function () {
+    var toggleActive = function (e, toggle) {
+        e.stopPropagation();
+        e.preventDefault();
+        toggle ? e.target.classList.add('wavesurfer-dragover') :
+            e.target.classList.remove('wavesurfer-dragover');
+    };
+
+    var handlers = {
+        // Drop event
+        drop: function (e) {
+            toggleActive(e, false);
+
+            // Load the file into wavesurfer
+            if (e.dataTransfer.files.length) {
+                wavesurfer.loadBlob(e.dataTransfer.files[0]);
+            } else {
+                wavesurfer.fireEvent('error', 'Not a file');
+            }
+        },
+
+        // Drag-over event
+        dragover: function (e) {
+            toggleActive(e, true);
+        },
+
+        // Drag-leave event
+        dragleave: function (e) {
+            toggleActive(e, false);
+        }
+    };
+
+    var dropTarget = document.querySelector('#drop');
+    Object.keys(handlers).forEach(function (event) {
+        dropTarget.addEventListener(event, handlers[event]);
+    });
+});

+ 168 - 0
libraries/wavesurfer/example/media-session/index.html

@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Media Sesssion plugin</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.min.js"></script>
+
+        <!-- media session plugin -->
+        <script src="../../dist/plugin/wavesurfer.mediasession.min.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Media Session API</noindex></h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform"></div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+
+                <audio src='../media/demo.wav' controls />
+            </div>
+
+            <div class="row marketing">
+                <div class="col-lg-4">
+                    <h4>wavesurfer.js Media Session Plugin</h4>
+
+                    <p itemprop="about">The <a href='https://wicg.github.io/mediasession/' target='blank'>Media Session API</a> plugin for <strong>wavesurfer.js</strong> allows you to customize media notifications
+                    by providing metadata for the media your web app is playing. It also allows you to handle media related events such as
+                    seeking or playback which may come from notifications or media keys.</p>
+
+                    <p><strong>Note</strong>: this plugin only works in Chrome 57 and newer (Firefox is <a href='https://bugzilla.mozilla.org/show_bug.cgi?id=1112032' target='blank'>working on it</a>).</p>
+
+                    <h4>Installation</h4>
+
+                    <p>
+                      <ol>
+                        <li>add the MediaSession plugin script tag</li>
+                        <li>create a <code>WaveSurfer</code> instance and supply an object for the <code>metadata</code> property</li>
+                        <li>create a <code>MediaSession</code> instance</li>
+                        <li>control playback from the notification screen on a mobile device</li>
+                      </ol>
+                    </p>
+                    <p>
+                        <a class="btn btn-large btn-success" href="../../plugin/wavesurfer.mediasession.js" itemprop="downloadUrl">Download <strong>the plugin</strong> (1.2 KB)</a>
+                    </p>
+                </div>
+
+                <div class="col-lg-8">
+                    <h4>Quick Start</h4>
+
+                    <noindex><p>
+<pre><code>var wavesurfer = Object.create(WaveSurfer);
+
+wavesurfer.init({
+  container     : '#waveform',
+  waveColor     : 'black',
+  backend       : 'MediaElement'
+});
+
+var msPlugin = Object.create(WaveSurfer.MediaSession);
+
+msPlugin.init({
+    wavesurfer: wavesurfer,
+    metadata: {
+        title: 'Wavesurfer.js Example',
+        artist: 'The Wavesurfer.js Project',
+        album: 'Media Session Example',
+        artwork: [
+          {src: 'https://dummyimage.com/96x96',   sizes: '96x96',   type: 'image/png'},
+          {src: 'https://dummyimage.com/128x128', sizes: '128x128', type: 'image/png'},
+          {src: 'https://dummyimage.com/192x192', sizes: '192x192', type: 'image/png'},
+          {src: 'https://dummyimage.com/256x256', sizes: '256x256', type: 'image/png'},
+          {src: 'https://dummyimage.com/384x384', sizes: '384x384', type: 'image/png'},
+          {src: 'https://dummyimage.com/512x512', sizes: '512x512', type: 'image/png'},
+        ]
+    }
+});
+
+// load audio from existing media element
+var mediaElt = document.querySelector('audio');
+wavesurfer.load(mediaElt);
+</code></pre>
+                    </p></noindex>
+
+                    <br />
+
+                    <h4>Options</h4>
+
+                    <table class="table table-striped table-bordered">
+                      <thead>
+                        <tr>
+                          <th>Name</th>
+                          <th>Required</th>
+                          <th>Default</th>
+                          <th>Description</th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        <tr>
+                          <td><code>wavesurfer</code></td>
+                          <td>yes</td>
+                          <td></td>
+                          <td>A WaveSurfer instance.</td>
+                        </tr>
+                        <tr>
+                          <td><code>metadata</code></td>
+                          <td>yes</td>
+                          <td></td>
+                          <td>A <a href='https://wicg.github.io/mediasession/#mediametadata' target='blank'>MediaMetadata</a> object: a representation of the metadata associated with a MediaSession that can be used by
+                          user agents to provide customized user interface.</td>
+                        </tr>
+                      </tbody>
+                    </table>
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-12">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

BIN
libraries/wavesurfer/example/media/demo.wav


BIN
libraries/wavesurfer/example/media/demo_video.mp4


+ 36 - 0
libraries/wavesurfer/example/microphone/app.js

@@ -0,0 +1,36 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load
+document.addEventListener('DOMContentLoaded', function () {
+    var micBtn = document.querySelector('#micBtn');
+
+    // Init wavesurfer
+    wavesurfer = WaveSurfer.create({
+        container     : '#waveform',
+        waveColor     : 'black',
+        interact      : false,
+        cursorWidth   : 0,
+        plugins: [
+            WaveSurfer.microphone.create()
+        ]
+    });
+
+    wavesurfer.microphone.on('deviceReady', function() {
+        console.info('Device ready!');
+    });
+    wavesurfer.microphone.on('deviceError', function(code) {
+        console.warn('Device error: ' + code);
+    });
+
+    // start/stop mic on button click
+    micBtn.onclick = function() {
+        if (wavesurfer.microphone.active) {
+            wavesurfer.microphone.stop();
+        } else {
+            wavesurfer.microphone.start();
+        }
+    };
+});

+ 198 - 0
libraries/wavesurfer/example/microphone/index.html

@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Microphone plugin</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- microphone renderer -->
+        <script src="../../dist/plugin/wavesurfer.microphone.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Microphone</noindex></h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform"></div>
+
+                <div class="controls">
+                    <button id="micBtn" class="btn btn-primary" data-action="start">
+                        Microphone:
+                        <i class="glyphicon glyphicon-play"></i>
+                        Start
+                        /
+                        <i class="glyphicon glyphicon-stop"></i>
+                        Stop
+                    </button>
+                </div>
+            </div>
+
+            <div class="row marketing">
+                <div class="col-lg-4">
+                    <h4>wavesurfer.js Microphone Plugin</h4>
+
+                    <p itemprop="about">Visualizes audio input from a microphone in <strong>wavesurfer.js</strong> instances.</p>
+
+                    <h4>Installation</h4>
+
+                    <p>
+                      <ol>
+                        <li>add the Microphone plugin to the plugins property of the wavesurfer options</li>
+                        <li>create a new instance of wavesurfer by using the create function</li>
+                        <li>control the Microphone using the <code>start</code>, <code>stopDevice</code>, <code>play</code>, <code>pause</code>, <code>stop</code> and <code>togglePlay</code> methods</li>
+                      </ol>
+                    </p>
+                    <p>
+                        <a class="btn btn-large btn-success" href="../../dist/plugin/wavesurfer.microphone.min.js" itemprop="downloadUrl" download>Download the plugin</a>
+                    </p>
+                </div>
+
+                <div class="col-lg-8">
+                    <h4>Quick Start</h4>
+
+                    <noindex><p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+  container     : '#waveform',
+  waveColor     : 'black',
+  interact      : false,
+  cursorWidth   : 0,
+  plugins: [
+    WaveSurfer.microphone.create()
+  ]
+});
+
+wavesurfer.microphone.on('deviceReady', function(stream) {
+    console.log('Device ready!', stream);
+});
+wavesurfer.microphone.on('deviceError', function(code) {
+    console.warn('Device error: ' + code);
+});
+
+// start the microphone
+wavesurfer.microphone.start();
+
+// pause rendering
+//wavesurfer.microphone.pause();
+
+// resume rendering
+//wavesurfer.microphone.play();
+
+// stop visualization and disconnect microphone
+//wavesurfer.microphone.stopDevice();
+
+// same as stopDevice() but also clears the wavesurfer canvas
+//wavesurfer.microphone.stop();
+
+// destroy the plugin
+//wavesurfer.microphone.destroy();
+</code></pre>
+                    </p></noindex>
+
+                    <br />
+
+                    <h4>Options</h4>
+
+                    <table class="table table-striped table-bordered">
+                      <thead>
+                        <tr>
+                          <th>Name</th>
+                          <th>Required</th>
+                          <th>Default</th>
+                          <th>Description</th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        <tr>
+                          <td><code>wavesurfer</code></td>
+                          <td>yes</td>
+                          <td></td>
+                          <td>A WaveSurfer instance.</td>
+                        </tr>
+                        <tr>
+                          <td><code>bufferSize</code></td>
+                          <td>no</td>
+                          <td>4096</td>
+                          <td>The buffer size in units of sample-frames. If specified, the <code>bufferSize</code> must be one of the following values: 256, 512, 1024, 2048, 4096, 8192, 16384.</td>
+                        </tr>
+                        <tr>
+                          <td><code>numberOfInputChannels</code></td>
+                          <td>no</td>
+                          <td>1</td>
+                          <td>Integer specifying the number of channels for this node's input. Values of up to 32 are supported.</td>
+                        </tr>
+                        <tr>
+                          <td><code>numberOfOutputChannels</code></td>
+                          <td>no</td>
+                          <td>1</td>
+                          <td>Integer specifying the number of channels for this node's output. Values of up to 32 are supported.</td>
+                        </tr>
+                      </tbody>
+                    </table>
+
+                    <h4>Events</h4>
+
+                    <table class="table table-striped table-bordered">
+                      <thead>
+                        <tr>
+                          <th>Name</th>
+                          <th>Description</th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        <tr>
+                          <td><code>deviceReady</code></td>
+                          <td>Invoked when the device is ready to use. Callback will receive a <code>LocalMediaStream</code> object that contains the microphone stream.</td>
+                        </tr>
+                        <tr>
+                          <td><code>deviceError</code></td>
+                          <td>Invoked when the user doesn't allow the browser to access the microphone. Callback will receive a (string) <a href='https://developer.mozilla.org/en-US/docs/NavigatorUserMedia.getUserMedia#errorCallback'>error code</a>.</td>
+                        </tr>
+                      </tbody>
+                    </table>
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-12">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 40 - 0
libraries/wavesurfer/example/mute/app.js

@@ -0,0 +1,40 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load
+document.addEventListener('DOMContentLoaded', function () {
+    var playButton = document.querySelector('#playBtn'),
+        toggleMuteButton = document.querySelector('#toggleMuteBtn'),
+        setMuteOnButton = document.querySelector('#setMuteOnBtn'),
+        setMuteOffButton = document.querySelector('#setMuteOffBtn');
+
+    // Init wavesurfer
+    wavesurfer = WaveSurfer.create({
+        container     : '#waveform',
+        waveColor     : 'black',
+        interact      : false,
+        cursorWidth   : 0
+    });
+
+    wavesurfer.load('../media/demo.wav');
+
+    wavesurfer.on('ready', function() {
+        playButton.onclick = function() {
+            wavesurfer.playPause();
+        };
+
+        toggleMuteButton.onclick = function() {
+            wavesurfer.toggleMute();
+        }
+
+        setMuteOnButton.onclick = function() {
+            wavesurfer.setMute(true);
+        }
+
+        setMuteOffButton.onclick = function() {
+            wavesurfer.setMute(false);
+        }
+    });
+});

+ 75 - 0
libraries/wavesurfer/example/mute/index.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Mute Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+    </head>
+
+    <body>
+        <div class="container">
+            <div class="header">
+                <h1>
+                    <a href="http://wavesurfer-js.org">wavesurfer.j</a> (Muting)
+                </h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform"></div>
+
+                <div class="controls">
+                    <button id="playBtn" class="btn btn-primary">Play / Pause</button>
+                    <button id="toggleMuteBtn" class="btn btn-primary">Toggle Mute</button>
+                    <button id="setMuteOnBtn" class="btn btn-primary">
+                        Mute <i class="glyphicon glyphicon-volume-off"></i>
+                    </button>
+
+                    <button id="setMuteOffBtn" class="btn btn-primary">
+                        Unmute <i class="glyphicon glyphicon-volume-up"></i>
+                    </button>
+                </div>
+            </div>
+
+
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-12">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 166 - 0
libraries/wavesurfer/example/panner/index.html

@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Panner Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- Demo -->
+        <script src="main.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">Panner Filter Example</h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <div class="progress progress-striped active" id="progress-bar">
+                        <div class="progress-bar progress-bar-info"></div>
+                    </div>
+
+                    <!-- Here be the waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+
+                    <hr />
+
+                    <div class="row">
+                        <div class="col-sm-4">
+                            &larr; left
+                        </div>
+                        <div class="col-sm-4">
+                            <!-- Panner -->
+                            <input data-action="pan" type="range" min="-45" max="45" value="0" style="width: 100%" />
+                        </div>
+                        <div class="col-sm-4">
+                            right &rarr;
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="row marketing">
+                <h3>How to Create a Panner Interface</h3>
+
+                <p>
+                    This is an example of how to add an arbitrary Web
+                    Audio node into a wavesurfer.js graph. Panner node
+                    is one such node.
+                </p>
+
+                <hr />
+
+                <div class="col-lg-6">
+                    <h4>1. Initialize wavesurfer.js</h4>
+
+                    <p>Create a <code>WaveSurfer</code> instance and load an audio clip.</p>
+                    <noindex><p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    container: '#demo' // this is the only required param
+});
+
+wavesurfer.load('media.wav');</code></pre>
+                    </p></noindex>
+
+                    <h4>2. Create a Panner Node</h4>
+
+                    <p>
+                        Create a panner node and add it to the Web
+                        Audio graph using the <code>setFilter</code> method.
+                    </p>
+                    <noindex><p>
+<pre><code>var panner = wavesurfer.backend.ac.createPanner();
+wavesurfer.backend.setFilter(panner);</code></pre>
+                    </p></noindex>
+                </div>
+
+                <div class="col-lg-6">
+                    <h4>3. Create a Range Slider</h4>
+                    <p>
+                        In your HTML, add a range input.
+                    </p>
+                    <noindex><p>
+<pre><code>&lt;input id="panner-input" type="range" min="-45" max="45" value="0" /&gt;</code></pre>
+                    </p></noindex>
+
+                    <h4>4. Bind the Range Slider</h4>
+                    <p>
+                        Listen to the range input's <code>input</code> event and set the panner's position
+                        according to the input's value.
+                        <small>Adapted from <a href="http://stackoverflow.com/a/14412601/352796">this SO answer</a>.</small>
+                    </p>
+
+                    <noindex><p>
+<pre><code>var slider = document.querySelector('#panner-input');
+slider.addEventListener('input', function (e) {
+    var xDeg = parseInt(e.target.value);
+    var x = Math.sin(xDeg * (Math.PI / 180));
+    wavesurfer.panner.setPosition(x, 0, 0);
+});</code></pre>
+                    </p></noindex>
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <div class="pull-right">
+                        <noindex>
+                            The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
+                        </noindex>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 68 - 0
libraries/wavesurfer/example/panner/main.js

@@ -0,0 +1,68 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load audio file
+document.addEventListener('DOMContentLoaded', function () {
+    // Init
+    wavesurfer = WaveSurfer.create({
+        container: document.querySelector('#waveform'),
+        minPxPerSec: 30,
+        scrollParent: true,
+        waveColor: '#A8DBA8',
+        progressColor: '#3B8686'
+    });
+
+    // Load audio from URL
+    wavesurfer.load('media.wav');
+
+    // Panner
+    (function () {
+        // Add panner
+        wavesurfer.panner = wavesurfer.backend.ac.createPanner();
+        wavesurfer.backend.setFilter(wavesurfer.panner);
+
+        // Bind panner slider
+        // @see http://stackoverflow.com/a/14412601/352796
+        var onChange = function () {
+            var xDeg = parseInt(slider.value);
+            var x = Math.sin(xDeg * (Math.PI / 180));
+            wavesurfer.panner.setPosition(x, 0, 0);
+        };
+        var slider = document.querySelector('[data-action="pan"]');
+        slider.addEventListener('input', onChange);
+        slider.addEventListener('change', onChange);
+        onChange();
+    }());
+
+    // Log errors
+    wavesurfer.on('error', function (msg) {
+        console.log(msg);
+    });
+
+    // Bind play/pause button
+    document.querySelector(
+        '[data-action="play"]'
+    ).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
+
+    // Progress bar
+    (function () {
+        var progressDiv = document.querySelector('#progress-bar');
+        var progressBar = progressDiv.querySelector('.progress-bar');
+
+        var showProgress = function (percent) {
+            progressDiv.style.display = 'block';
+            progressBar.style.width = percent + '%';
+        };
+
+        var hideProgress = function () {
+            progressDiv.style.display = 'none';
+        };
+
+        wavesurfer.on('loading', showProgress);
+        wavesurfer.on('ready', hideProgress);
+        wavesurfer.on('destroy', hideProgress);
+        wavesurfer.on('error', hideProgress);
+    }());
+});

BIN
libraries/wavesurfer/example/panner/media.wav


+ 67 - 0
libraries/wavesurfer/example/playlist/app.js

@@ -0,0 +1,67 @@
+// Create a WaveSurfer instance
+var wavesurfer;
+
+
+// Init on DOM ready
+document.addEventListener('DOMContentLoaded', function () {
+    wavesurfer = WaveSurfer.create({
+        container: '#waveform',
+        waveColor: '#428bca',
+        progressColor: '#31708f',
+        height: 120,
+        barWidth: 3
+    });
+});
+
+
+// Bind controls
+document.addEventListener('DOMContentLoaded', function () {
+    var playPause = document.querySelector('#playPause');
+    playPause.addEventListener('click', function () {
+        wavesurfer.playPause();
+    });
+
+    // Toggle play/pause text
+    wavesurfer.on('play', function () {
+        document.querySelector('#play').style.display = 'none';
+        document.querySelector('#pause').style.display = '';
+    });
+    wavesurfer.on('pause', function () {
+        document.querySelector('#play').style.display = '';
+        document.querySelector('#pause').style.display = 'none';
+    });
+
+
+    // The playlist links
+    var links = document.querySelectorAll('#playlist a');
+    var currentTrack = 0;
+
+    // Load a track by index and highlight the corresponding link
+    var setCurrentSong = function (index) {
+        links[currentTrack].classList.remove('active');
+        currentTrack = index;
+        links[currentTrack].classList.add('active');
+        wavesurfer.load(links[currentTrack].href);
+    };
+
+    // Load the track on click
+    Array.prototype.forEach.call(links, function (link, index) {
+        link.addEventListener('click', function (e) {
+            e.preventDefault();
+            setCurrentSong(index);
+        });
+    });
+
+    // Play on audio load
+    wavesurfer.on('ready', function () {
+        wavesurfer.play();
+    });
+
+    // Go to the next track on finish
+    wavesurfer.on('finish', function () {
+        setCurrentSong((currentTrack + 1) % links.length);
+    });
+
+    // Load the first track
+    setCurrentSong(currentTrack);
+});

+ 112 - 0
libraries/wavesurfer/example/playlist/index.html

@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Playlist</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.min.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+    </head>
+
+    <body>
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">wavesurfer.js Playlist Demo</h1>
+            </div>
+
+            <div id="demo">
+                <div class="row" style="margin: 30px 0">
+                    <div class="col-sm-10">
+                        <div id="waveform">
+                            <!-- Here be waveform -->
+                        </div>
+                    </div>
+
+                    <div class="col-sm-2">
+                        <button class="btn btn-success btn-block" id="playPause">
+                            <span id="play">
+                                <i class="glyphicon glyphicon-play"></i>
+                                Play
+                            </span>
+
+                            <span id="pause" style="display: none">
+                                <i class="glyphicon glyphicon-pause"></i>
+                                Pause
+                            </span>
+                        </button>
+                    </div>
+                </div>
+
+                <div class="list-group" id="playlist">
+                    <a href="../media/demo.wav" class="list-group-item">
+                        <i class="glyphicon glyphicon-play"></i>
+                        czskamaarù – Trou
+                        <span class="badge">0:21</span>
+                    </a>
+
+                    <a href="../panner/media.wav" class="list-group-item">
+                        <i class="glyphicon glyphicon-play"></i>
+                        日本人の話し
+                        <span class="badge">1:04</span>
+                    </a>
+
+                    <a href="../elan/transcripts/001z.mp3" class="list-group-item">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Рассказы о сновидениях
+                        <span class="badge badge-info">1:26</span>
+                    </a>
+
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <p>
+                        Audio sources:<br />
+                        <a rel="nofollow" href="http://www.jamendo.com/en/track/661578/trou"><b>Trou</b> <span class="muted">by</span>&nbsp;<b>czskamaarù</b></a>,
+                        <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 113 - 0
libraries/wavesurfer/example/plugin-system/app.js

@@ -0,0 +1,113 @@
+'use strict';
+
+var wavesurfer;
+
+// Init & load
+document.addEventListener('DOMContentLoaded', function () {
+    var pluginOptions = {
+        minimap: {
+            waveColor     : '#777',
+            progressColor : '#222',
+            height: 30
+        },
+        timeline: {
+            container: '#wave-timeline'
+        },
+        spectrogram: {
+            container: '#wave-spectrogram'
+        },
+        regions: {
+            regions: [
+                {
+                    start: 1,
+                    end: 3,
+                    color: 'hsla(400, 100%, 30%, 0.5)'
+                },
+                {
+                    start: 4,
+                    end: 5.4
+                },
+                {
+                    start: 6.22,
+                    end: 7.1
+                }
+            ]
+        },
+        elan: {
+            url: '../elan/transcripts/001z.xml',
+            container: '#annotations',
+            tiers: {
+                Text: true,
+                Comments: true
+            }
+        }
+    };
+    var options = {
+        container     : '#waveform',
+        waveColor     : 'violet',
+        progressColor : 'purple',
+        loaderColor   : 'purple',
+        cursorColor   : 'navy',
+        plugins: [
+            WaveSurfer.minimap.create(pluginOptions.minimap)
+        ]
+    };
+
+    if (location.search.match('scroll')) {
+        options.minPxPerSec = 100;
+        options.scrollParent = true;
+    }
+
+    if (location.search.match('normalize')) {
+        options.normalize = true;
+    }
+
+    // Init wavesurfer
+    wavesurfer = WaveSurfer.create(options);
+
+    [].forEach.call(document.querySelectorAll('[data-activate-plugin]'), function (el) {
+        var activePlugins = wavesurfer.initialisedPluginList;
+        Object.keys(activePlugins).forEach(function(name) {
+            if (el.dataset.activatePlugin === name) {
+                el.checked = true;
+            }
+        });
+    });
+
+    [].forEach.call(document.querySelectorAll('[data-activate-plugin]'), function (el) {
+        el.addEventListener('change', function (e) {
+            var pluginName = e.currentTarget.dataset.activatePlugin;
+            var activate = e.target.checked;
+            var options = pluginOptions[pluginName] || {};
+            var plugin = WaveSurfer[pluginName].create(options);
+
+            if (activate) {
+                wavesurfer.addPlugin(plugin).initPlugin(pluginName);
+            } else {
+                wavesurfer.destroyPlugin(pluginName);
+            }
+        });
+    });
+
+    /* Progress bar */
+    (function () {
+        var progressDiv = document.querySelector('#progress-bar');
+        var progressBar = progressDiv.querySelector('.progress-bar');
+
+        var showProgress = function (percent) {
+            progressDiv.style.display = 'block';
+            progressBar.style.width = percent + '%';
+        };
+
+        var hideProgress = function () {
+            progressDiv.style.display = 'none';
+        };
+
+        wavesurfer.on('loading', showProgress);
+        wavesurfer.on('ready', hideProgress);
+        wavesurfer.on('destroy', hideProgress);
+        wavesurfer.on('error', hideProgress);
+    }());
+
+    wavesurfer.load('../media/demo.wav');
+});

+ 188 - 0
libraries/wavesurfer/example/plugin-system/index.html

@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Timeline plugin</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+
+        <link rel="screenshot" itemprop="screenshot" href="//katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- timeline format renderer -->
+        <script src="../../dist/plugin/wavesurfer.timeline.js"></script>
+		<script src="../../dist/plugin/wavesurfer.minimap.js"></script>
+		<script src="../../dist/plugin/wavesurfer.cursor.js"></script>
+		<script src="../../dist/plugin/wavesurfer.elan.js"></script>
+		<script src="../../dist/plugin/wavesurfer.microphone.js"></script>
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+        <script src="../../dist/plugin/wavesurfer.spectrogram.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+        <script src="../trivia.js"></script>
+
+        <!-- Styling information for the elan plugin -->
+        <style type="text/css">
+        #annotations {
+            max-height: 300px;
+            overflow: auto;
+        }
+
+        .wavesurfer-annotations tr.wavesurfer-active td {
+            background-color: yellow;
+        }
+
+        .wavesurfer-time {
+            width: 100px;
+            color: #555;
+        }
+
+        .wavesurfer-tier-Text {
+            width: 500px;
+        }
+
+        td.wavesurfer-tier-Comments {
+            color: #999;
+        }
+
+        .wavesurfer-handle {
+            background-color: #c9e2b3;
+        }
+        </style>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <noindex>
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="?fill">Fill</a></li>
+                    <li><a href="?scroll">Scroll</a></li>
+                </ul>
+                </noindex>
+
+                <h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> Plugin system</noindex></h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <div class="progress progress-striped active" id="progress-bar">
+                        <div class="progress-bar progress-bar-info"></div>
+                    </div>
+
+                    <!-- Here be waveform -->
+                </div>
+                <div id="wave-timeline"></div>
+				<div id="wave-spectrogram"></div>
+                <div id="annotations"></div>
+
+                <div class="controls">
+
+                </div>
+            </div>
+
+            <div class="row">
+                <div class="col-lg-4">
+					<div class="form-group marketing">
+                        <h4>Disable and enable plugins on the fly:</h4>
+                        <hr />
+						<div class="checkbox">
+  							<label><input type="checkbox" value="" data-activate-plugin="minimap">Minimap</label>
+						</div>
+						<div class="checkbox">
+  							<label><input type="checkbox" value="" data-activate-plugin="timeline">Timeline</label>
+						</div>
+						<div class="checkbox">
+  							<label><input type="checkbox" value="" data-activate-plugin="cursor">Cursor</label>
+						</div>
+						<div class="checkbox">
+  							<label><input type="checkbox" value="" data-activate-plugin="spectrogram">Spectrogram</label>
+						</div>
+						<div class="checkbox">
+  							<label><input type="checkbox" value="" data-activate-plugin="regions">Regions</label>
+						</div>
+						<div class="checkbox">
+  							<label><input type="checkbox" value="" data-activate-plugin="elan">Elan</label>
+						</div>
+					</div>
+                </div>
+                <div class="col-lg-8">
+                    <div class="marketing">
+                        <h4>Initialising wavesurfer with plugins</h4>
+                        <hr />
+                        <p>The <code>plugins</code> option is an array of plugin definitions. Calling a plugin with the parameter <code>deferInit: true</code> will stop it from automatically initialising – you can do that at a later time with <code>wavesurfer.initPlugin('mypluginname')</code>.</p>
+                        <noindex><p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+        container: '#waveform',
+    waveColor: 'violet',
+    // ... other wavesurfer options
+    plugins: [
+        WaveSurfer.timeline.create{
+            container: '#wave-timeline',
+            // ... other timeline options
+        })
+    ]
+});
+
+wavesurfer.load('example/media/demo.mp3');</code></pre>
+                        </p></noindex>
+                    </div>
+                    <div class="marketing">
+                        <h4>Dynamically adding and initialising a plugin</h4>
+                        <hr />
+                        <noindex><p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    container: '#waveform',
+    waveColor: 'violet',
+    // ... other wavesurfer options
+});
+
+// adding and initialising a plugin after initialisation
+wavesurfer.addPlugin(WaveSurfer.timeline.create{
+    container: '#wave-timeline',
+    // ... other timeline options
+})).initPlugin('timeline')
+
+wavesurfer.load('example/media/demo.mp3');</code></pre>
+                        </p></noindex>
+                    </div>
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/80x15.png" /></a>
+                </div>
+
+                <div class="col-sm-12">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://github.com/katspaugh/wavesurfer.js" property="cc:attributionName" rel="cc:attributionURL">katspaugh</a> is licensed under a <a rel="license" href="https://creativecommons.org/licenses/by/3.0/deed.en_US">Creative Commons Attribution 3.0 Unported License</a>.
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 42 - 0
libraries/wavesurfer/example/regions/app.js

@@ -0,0 +1,42 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load audio file
+document.addEventListener('DOMContentLoaded', function () {
+    // Init
+    wavesurfer = WaveSurfer.create({
+        container: document.querySelector('#waveform'),
+        waveColor: '#A8DBA8',
+        progressColor: '#3B8686',
+        backend: 'MediaElement',
+        plugins: [
+            WaveSurfer.regions.create({
+                regions: [
+                    {
+                        start: 1,
+                        end: 3,
+                        color: 'hsla(400, 100%, 30%, 0.5)'
+                    }, {
+                        start: 5,
+                        end: 7,
+                        color: 'hsla(200, 50%, 70%, 0.4)'
+                    }
+                ],
+                dragSelection: {
+                    slop: 5
+                }
+            })
+        ]
+    });
+
+    // Load audio from URL
+    wavesurfer.load('../media/demo.wav');
+
+    // this is already being done in /examples/trivia.js
+    // document.querySelector(
+    //     '[data-action="play"]'
+    // ).addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
+
+});

+ 120 - 0
libraries/wavesurfer/example/regions/index.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Regions</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- plugins -->
+        <script src="../../dist/plugin/wavesurfer.timeline.js"></script>
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+        <script src="../../dist/plugin/wavesurfer.minimap.js"></script>
+
+        <!-- App -->
+        <script src="../trivia.js"></script>
+        <script src="app.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">wavesurfer.js Regions</h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <!-- Here be waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+            <div class="row marketing">
+                <p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    container: document.querySelector('#waveform'),
+    waveColor: '#A8DBA8',
+    progressColor: '#3B8686',
+    backend: 'MediaElement',
+    plugins: [
+        WaveSurfer.regions.create{
+            regions: [
+                {
+                    start: 1,
+                    end: 3,
+                    color: 'hsla(400, 100%, 30%, 0.5)'
+                }, {
+                    start: 5,
+                    end: 7,
+                    color: 'hsla(200, 50%, 70%, 0.4)'
+                }
+            ],
+            dragSelection: {
+                slop: 5
+            }
+        })
+    ]
+});
+</code></pre>
+                </p>
+
+            </div>
+
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-8">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-4">
+                    <p>
+                        The sound file is from <a href="https://librivox.org/librivox-multilingual-short-works-collection-001-by-various/">librivox.org</a>.
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

BIN
libraries/wavesurfer/example/screenshot.png


+ 52 - 0
libraries/wavesurfer/example/spectrogram/app.js

@@ -0,0 +1,52 @@
+'use strict';
+
+var wavesurfer;
+
+// Init & load
+document.addEventListener('DOMContentLoaded', function () {
+    // Create an instance
+    wavesurfer = WaveSurfer.create({
+        container     : '#waveform',
+        waveColor     : 'violet',
+        progressColor : 'purple',
+        loaderColor   : 'purple',
+        cursorColor   : 'navy',
+        plugins: [
+            WaveSurfer.spectrogram.create({
+                container: '#wave-spectrogram'
+            })
+        ]
+    });
+
+    if (location.search.match('scroll')) {
+        options.minPxPerSec = 100;
+        options.scrollParent = true;
+    }
+
+    if (location.search.match('normalize')) {
+        options.normalize = true;
+    }
+
+    /* Progress bar */
+    (function () {
+        var progressDiv = document.querySelector('#progress-bar');
+        var progressBar = progressDiv.querySelector('.progress-bar');
+
+        var showProgress = function (percent) {
+            progressDiv.style.display = 'block';
+            progressBar.style.width = percent + '%';
+        };
+
+        var hideProgress = function () {
+            progressDiv.style.display = 'none';
+        };
+
+        wavesurfer.on('loading', showProgress);
+        wavesurfer.on('ready', hideProgress);
+        wavesurfer.on('destroy', hideProgress);
+        wavesurfer.on('error', hideProgress);
+    }());
+
+
+    wavesurfer.load('../media/demo.wav');
+});

+ 138 - 0
libraries/wavesurfer/example/spectrogram/index.html

@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Spectrogram plugin</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- spectrogram format renderer -->
+        <script src="../../dist/plugin/wavesurfer.spectrogram.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+        <script src="../trivia.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <noindex>
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="?fill">Fill</a></li>
+                    <li><a href="?scroll">Scroll</a></li>
+                </ul>
+                </noindex>
+
+                <h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Spectrogram</noindex></h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <div class="progress progress-striped active" id="progress-bar">
+                        <div class="progress-bar progress-bar-info"></div>
+                    </div>
+
+                    <!-- Here be waveform -->
+                </div>
+                <div id="wave-spectrogram"></div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+            <div class="row marketing">
+                <div class="col-lg-4">
+                    <h4>wavesurfer.js Spectrogram Plugin</h4>
+
+                    <p itemprop="about">Adds a simple spectrogram to your <strong>wavesurfer.js</strong> instances.</p>
+
+                    <h4>Installation</h4>
+
+                    <p>
+                      <ol>
+                        <li>add the Spectrogram plugin script tag</li>
+                        <li>create a <code>WaveSurfer</code> instance</li>
+                        <li>add a container HTML element for the spectrogram</li>
+                        <li>create a Spectrogram instance in the <code>WaveSurfer</code>'s <code>"ready"</code> event callback</li>
+                      </ol>
+                    </p>
+                    <p>
+                        <a class="btn btn-large btn-success" href="../../plugin/wavesurfer.spectrogram.js" itemprop="downloadUrl" download>Download <strong>the plugin</strong> (5 KB)</a>
+                    </p>
+                </div>
+
+                <div class="col-lg-8">
+                    <h4>Quick Start</h4>
+
+                    <noindex><p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    // your options here
+    plugins: [
+        WaveSurfer.spectrogram.create{
+            wavesurfer: wavesurfer,
+            container: "#wave-spectrogram"
+        })
+    ]
+});
+
+wavesurfer.load('example/media/demo.wav');</code></pre>
+                    </p></noindex>
+
+                    <br />
+
+                    <h4>Options</h4>
+
+                    <ul class="markdown-body">
+                      <li><code>wavesurfer</code> - <em>required</em> - a WaveSurfer instance.</li>
+                      <li><code>container</code> - <em>required</em> - the element in which to place the spectrogram, or a CSS selector to find it.</li>
+                      <li><code>fftSamples</code> - number of FFT samples (<code>512</code> by default). Number of spectral lines and height of the spectrogram will be a half of this parameter.</li>
+                      <li><code>frequenciesDataUrl</code> - URL to load spectral data from.</li>
+                    </ul>
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-12">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 55 - 0
libraries/wavesurfer/example/split-channels/app.js

@@ -0,0 +1,55 @@
+// Create an instance
+var wavesurfer;
+
+window.onload = function () {
+    wavesurfer = WaveSurfer.create({
+        container: document.querySelector('#waveform'),
+        splitChannels: true
+    });
+
+    // Load audio from URL
+    wavesurfer.load('stereo.mp3');
+
+    // Play/pause on button press
+    document.querySelector('[data-action="play"]').addEventListener(
+        'click', wavesurfer.playPause.bind(wavesurfer)
+    );
+
+
+    // Drag'n'drop
+    var toggleActive = function (e, toggle) {
+        e.stopPropagation();
+        e.preventDefault();
+        toggle ? e.target.classList.add('wavesurfer-dragover') :
+            e.target.classList.remove('wavesurfer-dragover');
+    };
+
+    var handlers = {
+        // Drop event
+        drop: function (e) {
+            toggleActive(e, false);
+
+            // Load the file into wavesurfer
+            if (e.dataTransfer.files.length) {
+                wavesurfer.loadBlob(e.dataTransfer.files[0]);
+            } else {
+                wavesurfer.fireEvent('error', 'Not a file');
+            }
+        },
+
+        // Drag-over event
+        dragover: function (e) {
+            toggleActive(e, true);
+        },
+
+        // Drag-leave event
+        dragleave: function (e) {
+            toggleActive(e, false);
+        }
+    };
+
+    var dropTarget = document.querySelector('#drop');
+    Object.keys(handlers).forEach(function (event) {
+        dropTarget.addEventListener(event, handlers[event]);
+    });
+};

+ 108 - 0
libraries/wavesurfer/example/split-channels/index.html

@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Split Channel Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+
+        <!-- Demo -->
+        <script src="app.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">Split Channel Waveforms</h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <!-- Here be the waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+            <p class="lead pull-center" id="drop">
+                Drag'n'drop your
+                <i class="glyphicon glyphicon-music"></i>-file
+                here!
+            </p>
+
+            <div class="row marketing">
+                <h3>How to Enable Split Channels</h3>
+
+                <p>
+                    Set the <code>splitChannels</code> option to <code>true</code>.
+                </p>
+
+                <p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    container: document.querySelector('#wave'),
+    splitChannels: true
+});
+</code></pre>
+                </p>
+
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <noindex>
+                        Demo music track is <a href="https://www.jamendo.com/en/track/205154/transistor" rel="nofollow"><b>Transistor</b> <span class="muted">by</span>&nbsp;<b>Transistor</b></a> (CC BY-NC-ND 3.0). Thanks!
+                    </noindex>
+
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

BIN
libraries/wavesurfer/example/split-channels/stereo.mp3


+ 157 - 0
libraries/wavesurfer/example/split-wave-point-plot/index.html

@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Split Wave Point Plot Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.min.js"></script>
+
+        <script src="../../plugin/wavesurfer.regions.js"></script>
+
+        <!-- Demo -->
+        <script src="app.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">Split Wave / Point Plot </h1>
+            </div>
+            <div>
+                <p>
+                The Split Wave Point Plot drawer splits the graphic in two, with the upper half being a plot of
+                points defined by time and a range of values. The following example shows the calculated pitch
+                at each point in time.
+                </p>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <!-- Here be the waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+
+            <div class="row marketing">
+                <h3>How to Enable Split Wave/Point Plot</h3>
+
+                <p>
+                    Set the <code>renderer</code> option to <code>SplitWavePointPlot</code> and the <code>plotFileUrl</code> to the file containing the time aligned data.
+                </p>
+
+                <p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    container: document.querySelector('#wave'),
+    renderer: 'SplitWavePointPlot',
+    plotFileUrl: 'data.txt'
+});
+</code></pre>
+                </p>
+
+                <h3>Providing Point Data</h3>
+                <p>The data to be graphed can be provided by either providing a file defined in the <code>plotFileUrl</code> or by passing in an array of data in the <code>plotArray</code> option. The time data does not need to be continous and can have gaps.</p>
+                <strong>Note: if the timing of your data does not span the duration of the sound file you should set <code>plotTimeEnd</code> to the total duration of the sound file or the points may not be aligned correctly</strong></li>
+                <h4>Data File Format</h4>
+                <p>If providing data by loading a file, each line of the file must contain two elements: the time and the value of the point separated by a delimiter (defaults to tab).  E.g.</p>
+<pre><code>0.01 123
+0.02 121
+0.03 127
+0.22 120
+0.23 119</code></pre>
+
+                <h4>Data Array Format</h4>
+                <p>If providing data by a javascript array via the <code>plotArray</code> option the array should have the following form:</p>
+<pre><code>[
+{time: 0.02, value: 121},
+{time: 0.03, value: 127},
+{time: 0.22, value: 120},
+{time: 0.03, value: 119}
+]</code></pre>
+
+                <h3>Options</h3>
+                <p>The following additional options can be set when initializing wavesurfer to control the waveform</p>
+                <ul>
+                    <li><code>plotArray:</code>               array of objects with time and plot (required unless plotFileUrl is set)</li>
+                    <li><code>plotFileUrl:</code>              url of the file that contains the plot information (required unless plotArray is set)</li>
+                    <li><code>plotNormalizeTo:</code>          [whole/segment/none/values] - what value to normalize the plot to (defaults to "whole")</li>
+                    <li><code>plotMin:</code>                  the minimum value to normalize points to.  Any value below this will be ignored (defaults to 0)</li>
+                    <li><code>plotMax:</code>                  the maximum value to normalize points to.  Any value above this will be ignored (defaults to 1)</li>
+                    <li><code>plotTimeStart:</code>            the time included in the plot file which corresponds with the start of the displayed wave (defaults to 0)</li>
+                    <li><code>plotTimeEnd:</code>              the time included in the plot file which corresponds with the end of the displayed wave (defaults maximum plot time)</li>
+                    <li><code>plotColor:</code>                the color of the plot (defaults to #f63)</li>
+                    <li><code>plotProgressColor:</code>       the color of the progress plot (defaults to '#F00')</li>
+                    <li><code>plotFileDelimiter:</code>        the delimiter which separates the time from the value in the plot file (defaults to tab characater = "\t")</li>
+                    <li><code>plotPointHeight:</code>          the canvas height of each plot point (defaults to 2)</li>
+                    <li><code>plotPointWidth:</code>           the canvas width of each plot point (defaults to 2)</li>
+                    <li><code>plotSeparator:</code>            boolean indicating a separator should be included between the wave and point plot</li>
+                    <li><code>plotRangeDisplay:</code>         boolean indicating if the min and max range should be displayed (defaults to false)</li>
+                    <li><code>plotRangePrecision:</code>       integer determining the precision of the displayed plot range</li>
+                    <li><code>plotRangeUnits:</code>           units appended to the range</li>
+                    <li><code>plotRangeFontSize:</code>        the font for displaying the range - defaults to 20</li>
+                    <li><code>plotRangeFontType:</code>        the font type for displaying range - defaults to Ariel</li>
+                    <li><code>plotRangeIgnoreOutliers:</code>  boolean indicating if values outside of range should be ignored or plotted at min/max</li>
+                </ul>
+            </div>
+
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <noindex>
+                        Demo is selection of from The Hobbit by J.R.R. Tolkien
+                    </noindex>
+
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            /*
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+            */
+        </script>
+    </body>
+</html>

+ 55 - 0
libraries/wavesurfer/example/timeline/app.js

@@ -0,0 +1,55 @@
+'use strict';
+
+var wavesurfer;
+
+// Init & load
+document.addEventListener('DOMContentLoaded', function () {
+    var options = {
+        container     : '#waveform',
+        waveColor     : 'violet',
+        progressColor : 'purple',
+        loaderColor   : 'purple',
+        cursorColor   : 'navy',
+        plugins: [
+            WaveSurfer.timeline.create({
+                container: '#wave-timeline'
+            })
+        ]
+    };
+
+    if (location.search.match('scroll')) {
+        options.minPxPerSec = 100;
+        options.scrollParent = true;
+    }
+
+    if (location.search.match('normalize')) {
+        options.normalize = true;
+    }
+
+    // Init wavesurfer
+    wavesurfer = WaveSurfer.create(options);
+
+    /* Progress bar */
+    (function () {
+        var progressDiv = document.querySelector('#progress-bar');
+        var progressBar = progressDiv.querySelector('.progress-bar');
+
+        var showProgress = function (percent) {
+            progressDiv.style.display = 'block';
+            progressBar.style.width = percent + '%';
+        };
+
+        var hideProgress = function () {
+            progressDiv.style.display = 'none';
+        };
+
+        wavesurfer.on('loading', showProgress);
+        wavesurfer.on('ready', hideProgress);
+        wavesurfer.on('destroy', hideProgress);
+        wavesurfer.on('error', hideProgress);
+    }());
+
+
+
+    wavesurfer.load('../media/demo.wav');
+});

+ 136 - 0
libraries/wavesurfer/example/timeline/index.html

@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Timeline plugin</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+
+        <link rel="screenshot" itemprop="screenshot" href="//katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <!-- timeline format renderer -->
+        <script src="../../dist/plugin/wavesurfer.timeline.js"></script>
+
+        <!-- App -->
+        <script src="app.js"></script>
+        <script src="../trivia.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <noindex>
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="?fill">Fill</a></li>
+                    <li><a href="?scroll">Scroll</a></li>
+                </ul>
+                </noindex>
+
+                <h1 itemprop="name"><a href="http://wavesurfer-js.org">wavesurfer.js</a><noindex> + Timeline</noindex></h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <div class="progress progress-striped active" id="progress-bar">
+                        <div class="progress-bar progress-bar-info"></div>
+                    </div>
+
+                    <!-- Here be waveform -->
+                </div>
+                <div id="wave-timeline"></div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+            </div>
+
+            <div class="row marketing">
+                <div class="col-lg-4">
+                    <h4>wavesurfer.js Timeline Plugin</h4>
+
+                    <p itemprop="about">Adds a simple timeline to your <strong>wavesurfer.js</strong> instances.</p>
+
+                    <p>
+                        <a class="btn btn-large btn-success" href="../../plugin/wavesurfer.timeline.js" itemprop="downloadUrl" download>Download the plugin</a>
+                    </p>
+                </div>
+
+                <div class="col-lg-8">
+                    <h4>Quick Start</h4>
+
+                    <noindex><p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+  // your options here
+  plugins: [
+    WaveSurfer.timeline.create{
+        container: "#wave-timeline"
+    })
+  ]
+});
+
+wavesurfer.load('example/media/demo.mp3');</code></pre>
+                    </p></noindex>
+
+                    <br />
+
+                    <h4>Options</h4>
+
+                    <ul class="markdown-body">
+                      <li><code>container</code> - <em>required</em> - the element in which to place the timeline, or a CSS selector to find it</li>
+                      <li><code>height</code> - the height (in pixels) of the timeline. The default is 20.</li>
+                      <li><code>notchPercentHeight</code> - the height (in percent) of the minor notch lines in the timeline. The default is 90.</li>
+                      <li><code>primaryColor</code> - the color of the modulo-ten notch lines (e.g. 10sec, 20sec). The default is '#000'.</li>
+                      <li><code>secondaryColor</code> - the color of the non-modulo-ten notch lines. The default is '#c0c0c0'.</li>
+                      <li><code>primaryFontColor</code> - the color of the non-modulo-ten time labels (e.g. 10sec, 20sec). The default is '#000'.</li>
+                      <li><code>primaryFontColor</code> - the color of the non-modulo-ten time labels (e.g. 5sec, 15sec). The default is '#c0c0c0'.</li>
+                      <li><code>timeInterval</code> - number of intervals that records consists of. Usually it is equal to the duration in minutes. (Integer or function which receives pxPerSec value and reurns value)</li>
+                      <li><code>primaryLabelInterval</code> - number of primary time labels. (Integer or function which receives pxPerSec value and reurns value)</li>
+                      <li><code>secondaryLabelInterval</code> - number of secondary time labels (Time labels between primary labels, integer or function which receives pxPerSec value and reurns value).</li>
+                      <li><code>formatTimeCallback</code> - custom time format callback. (Function which receives number of seconds and returns formatted string)</li>
+                    </ul>
+                </div>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-12">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 72 - 0
libraries/wavesurfer/example/trivia.js

@@ -0,0 +1,72 @@
+var GLOBAL_ACTIONS = {
+    'play': function () {
+        wavesurfer.playPause();
+    },
+
+    'back': function () {
+        wavesurfer.skipBackward();
+    },
+
+    'forth': function () {
+        wavesurfer.skipForward();
+    },
+
+    'toggle-mute': function () {
+        wavesurfer.toggleMute();
+    }
+};
+
+
+// Bind actions to buttons and keypresses
+document.addEventListener('DOMContentLoaded', function () {
+    document.addEventListener('keydown', function (e) {
+        var map = {
+            32: 'play',       // space
+            37: 'back',       // left
+            39: 'forth'       // right
+        };
+        var action = map[e.keyCode];
+        if (action in GLOBAL_ACTIONS) {
+            if (document == e.target || document.body == e.target) {
+                e.preventDefault();
+            }
+            GLOBAL_ACTIONS[action](e);
+        }
+    });
+
+    [].forEach.call(document.querySelectorAll('[data-action]'), function (el) {
+        el.addEventListener('click', function (e) {
+            var action = e.currentTarget.dataset.action;
+            if (action in GLOBAL_ACTIONS) {
+                e.preventDefault();
+                GLOBAL_ACTIONS[action](e);
+            }
+        });
+    });
+});
+
+
+// Misc
+document.addEventListener('DOMContentLoaded', function () {
+    // Web Audio not supported
+    if (!window.AudioContext && !window.webkitAudioContext) {
+        var demo = document.querySelector('#demo');
+        if (demo) {
+            demo.innerHTML = '<img src="/example/screenshot.png" />';
+        }
+    }
+
+
+    // Navbar links
+    var ul = document.querySelector('.nav-pills');
+    var pills = ul.querySelectorAll('li');
+    var active = pills[0];
+    if (location.search) {
+        var first = location.search.split('&')[0];
+        var link = ul.querySelector('a[href="' + first + '"]');
+        if (link) {
+            active =  link.parentNode;
+        }
+    }
+    active && active.classList.add('active');
+});

+ 145 - 0
libraries/wavesurfer/example/video-element/index.html

@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Media Element Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+
+        <!-- Demo -->
+        <script src="main.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">Existing Media Element Example</h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <!-- Here be the waveform -->
+                </div>
+
+                <div class="controls">
+                    <button class="btn btn-primary" data-action="play">
+                        <i class="glyphicon glyphicon-play"></i>
+                        Play
+                        /
+                        <i class="glyphicon glyphicon-pause"></i>
+                        Pause
+                    </button>
+                </div>
+
+                <video src="../media/demo_video.mp4" type="video/mpeg"></video>
+            </div>
+
+            <div class="row marketing">
+                <h3>How to Enable the Fallback</h3>
+
+                <hr />
+
+                <p>
+                    You can choose to use an existing
+                    html5 audio or video element manually. Simply set the <code>backend</code>
+                    option to <code>"MediaElement"</code> and pass the media element as the first
+                    argument to wavesurfer.load(). Include an array of peaks as the second
+                    argument, the Web Audio API will not be used to render the peaks.
+                </p>
+
+                <p>
+                <pre><code>var wavesurfer = WaveSurfer.create({
+  container: document.querySelector('#waveform'),
+  waveColor: '#A8DBA8',
+  progressColor: '#3B8686',
+  backend: 'MediaElement'
+});
+
+// Load audio from existing media element
+var mediaElt = document.querySelector('video');
+
+wavesurfer.load(mediaElt);</code></pre>
+                </p>
+
+                <h3>Pre-rendered Peaks</h3>
+
+                <p>
+                    If you have pre-rendered peaks (on your server),
+                    you can pass them into the <code>load</code>
+                    function. This is optional–if you don't provide
+                    any peaks,
+                    <strong>waserver.js</strong> will first draw a
+                    thin line instead of a waveform, then attempt to
+                    download the audio file via Ajax and decode it
+                    with Web Audio if available.
+                </p>
+
+                <p>
+                <pre><code>
+// init with an array of peak data.
+wavesurfer.load(mediaElt, [0.0218, 0.0183, 0.0165, 0.0198, 0.2137]);
+                </code></pre>
+                </p>
+
+
+                <p>
+                    Press this button to see the same demo with pre-decoded peaks:
+                    <button class="btn btn-warning" data-action="peaks">
+                        Load with pre-rendered peaks
+                    </button>
+                </p>
+
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <div class="pull-right">
+                        <noindex>
+                            The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
+                        </noindex>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

File diff suppressed because it is too large
+ 28 - 0
libraries/wavesurfer/example/video-element/main.js


+ 122 - 0
libraries/wavesurfer/example/zoom/index.html

@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>wavesurfer.js | Zoom Example</title>
+
+        <link href="data:image/gif;" rel="icon" type="image/x-icon" />
+
+        <!-- Bootstrap -->
+        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+
+        <link rel="stylesheet" href="../css/style.css" />
+        <link rel="stylesheet" href="../css/ribbon.css" />
+        <link rel="screenshot" itemprop="screenshot" href="https://katspaugh.github.io/wavesurfer.js/example/screenshot.png" />
+
+        <!-- wavesurfer.js -->
+        <script src="../../dist/wavesurfer.js"></script>
+        <script src="../../dist/plugin/wavesurfer.timeline.js"></script>
+        <script src="../../dist/plugin/wavesurfer.regions.js"></script>
+
+        <!-- Demo -->
+        <script src="main.js"></script>
+    </head>
+
+    <body itemscope itemtype="http://schema.org/WebApplication">
+        <div class="container">
+            <div class="header">
+                <ul class="nav nav-pills pull-right">
+                    <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
+                </ul>
+
+                <h1 itemprop="name">Zooming Feature Example</h1>
+            </div>
+
+            <div id="demo">
+                <div id="waveform">
+                    <!-- Here be the waveform -->
+                </div>
+
+                <div id="timeline"></div>
+
+                <div class="controls">
+                    <div class="row">
+                        <div class="col-sm-7">
+                            <button class="btn btn-primary" data-action="play">
+                                <i class="glyphicon glyphicon-play"></i>
+                                Play
+                                /
+                                <i class="glyphicon glyphicon-pause"></i>
+                                Pause
+                            </button>
+                        </div>
+
+                        <div class="col-sm-1">
+                            <i class="glyphicon glyphicon-zoom-in"></i>
+                        </div>
+
+                        <div class="col-sm-3">
+                          <input data-action="zoom" type="range" min="1" max="200" value="0" style="width: 100%" />
+                        </div>
+
+                        <div class="col-sm-1">
+                            <i class="glyphicon glyphicon-zoom-out"></i>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="row marketing">
+                <h3>How to Zoom</h3>
+
+                <hr />
+
+                <p>
+<pre><code>var wavesurfer = WaveSurfer.create({
+    container: document.querySelector('#wave'),
+    backend: 'MediaElement'
+});
+
+document.querySelector('#slider').oninput = function () {
+    wavesurfer.zoom(Number(this.value));
+};
+</code></pre>
+                </p>
+            </div>
+
+            <div class="footer row">
+                <div class="col-sm-12">
+                    <a rel="license" href="https://opensource.org/licenses/BSD-3-Clause"><img alt="BSD-3-Clause License" style="border-width:0" src="https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" /></a>
+                </div>
+
+                <div class="col-sm-7">
+                    <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">wavesurfer.js</span> by <a href="https://github.com/katspaugh/wavesurfer.js">katspaugh</a> is licensed under a&nbsp;<a style="white-space: nowrap" rel="license" href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause License</a>.
+                </div>
+
+                <div class="col-sm-5">
+                    <div class="pull-right">
+                        <noindex>
+                            The audio file is from <a rel="nofollow" href="http://spokencorpora.ru/">spokencorpora.ru</a>, used with permission.
+                        </noindex>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="github-fork-ribbon-wrapper right">
+            <div class="github-fork-ribbon">
+                <a itemprop="isBasedOnUrl" href="https://github.com/katspaugh/wavesurfer.js">Fork me on GitHub</a>
+            </div>
+        </div>
+
+        <script>
+            (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+            })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+            ga('create', 'UA-50026819-1', 'wavesurfer.fm');
+            ga('send', 'pageview');
+        </script>
+    </body>
+</html>

+ 54 - 0
libraries/wavesurfer/example/zoom/main.js

@@ -0,0 +1,54 @@
+'use strict';
+
+// Create an instance
+var wavesurfer;
+
+// Init & load audio file
+document.addEventListener('DOMContentLoaded', function () {
+    // Init
+    wavesurfer = WaveSurfer.create({
+        container: document.querySelector('#waveform'),
+        waveColor: '#A8DBA8',
+        progressColor: '#3B8686',
+        backend: 'MediaElement',
+        plugins: [
+            WaveSurfer.regions.create({
+                regions: [
+                    {
+                        start: 0,
+                        end: 5,
+                        color: 'hsla(400, 100%, 30%, 0.1)'
+                    }, {
+                        start: 10,
+                        end: 100,
+                        color: 'hsla(200, 50%, 70%, 0.1)'
+                    }
+                ]
+            }),
+            WaveSurfer.timeline.create({
+                container: '#timeline'
+            })
+        ]
+    });
+
+
+    // Load audio from URL
+    wavesurfer.load('../media/demo.wav');
+
+
+    // Zoom slider
+    var slider = document.querySelector('[data-action="zoom"]');
+
+    slider.value = wavesurfer.params.minPxPerSec;
+    slider.min = wavesurfer.params.minPxPerSec;
+
+    slider.addEventListener('input', function () {
+        wavesurfer.zoom(Number(this.value));
+    });
+
+
+    // Play button
+    var button = document.querySelector('[data-action="play"]');
+
+    button.addEventListener('click', wavesurfer.playPause.bind(wavesurfer));
+});

+ 65 - 0
libraries/wavesurfer/karma.conf.js

@@ -0,0 +1,65 @@
+require('babel-register');
+var webpackConfig = require('./webpack.config.babel.js').default({ test: true });
+
+module.exports = function (config) {
+    var configuration = {
+        basePath: '',
+        frameworks: [
+            'jasmine',
+            'jasmine-matchers'
+        ],
+        hostname: 'localhost',
+        port: 9876,
+        singleRun: true,
+        autoWatch: false,
+        files: [
+            {
+                pattern: 'spec/support/demo.wav',
+                included: false,
+                watched: false,
+                served: true
+            },
+
+            // specs
+            'spec/plugin-api.spec.js',
+            'spec/util.spec.js',
+            'spec/wavesurfer.spec.js',
+            'spec/peakcache.spec.js'
+        ],
+        preprocessors: {
+            'spec/plugin-api.spec.js': ['webpack'],
+            'spec/util.spec.js': ['webpack'],
+            'spec/wavesurfer.spec.js': ['webpack'],
+            'spec/peakcache.spec.js': ['webpack']
+        },
+        webpackMiddleware: {
+            stats: 'errors-only'
+        },
+        plugins: [
+            'karma-webpack',
+            'karma-jasmine',
+            'karma-jasmine-matchers',
+            'karma-chrome-launcher'
+        ],
+        browsers: [
+            'Chrome'
+        ],
+        captureConsole: true,
+        colors: true,
+        reporters: ['progress'],
+        webpack: webpackConfig,
+
+        customLaunchers: {
+            Chrome_travis_ci: {
+                base: 'Chrome',
+                flags: ['--no-sandbox']
+            }
+        }
+    };
+
+    if (process.env.TRAVIS) {
+        configuration.browsers = ['Chrome_travis_ci'];
+    }
+
+    config.set(configuration);
+};

+ 55 - 0
libraries/wavesurfer/package.json

@@ -0,0 +1,55 @@
+{
+  "name": "wavesurfer.js",
+  "version": "2.0.0-beta02",
+  "description": "Interactive navigable audio visualization using Web Audio and Canvas",
+  "main": "dist/wavesurfer.min.js",
+  "directories": {
+    "example": "example"
+  },
+  "scripts": {
+    "start": "webpack --env.plugins && webpack --env.htmlinit && webpack-dev-server",
+    "start:htmlinit": "webpack && webpack --env.plugins && webpack-dev-server --env.htmlinit",
+    "start:plugins": "webpack && webpack --env.htmlinit && webpack-dev-server --env.plugins",
+    "build": "npm run build:normal & npm run build:minified",
+    "build:normal": "webpack & webpack --env.plugins & webpack --env.htmlinit",
+    "build:minified": "webpack --env.minify & webpack --env.plugins --env.minify & webpack --env.htmlinit --env.minify",
+    "doc": "esdoc",
+    "test": "BABEL_ENV=test karma start karma.conf.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/katspaugh/wavesurfer.js.git"
+  },
+  "author": "",
+  "license": "BSD-3-Clause",
+  "bugs": {
+    "url": "https://github.com/katspaugh/wavesurfer.js/issues"
+  },
+  "devDependencies": {
+    "babel-core": "^6.18.2",
+    "babel-eslint": "^7.1.1",
+    "babel-loader": "^6.2.7",
+    "babel-plugin-add-module-exports": "^0.2.1",
+    "babel-plugin-istanbul": "^2.0.3",
+    "babel-plugin-transform-class-properties": "^6.22.0",
+    "babel-preset-es2015": "^6.18.0",
+    "babel-preset-es2015-native-modules": "^6.9.4",
+    "babel-preset-stage-0": "^6.22.0",
+    "babel-register": "^6.18.0",
+    "debounce": "^1.0.0",
+    "esdoc": "^0.5.2",
+    "eslint": "^3.9.1",
+    "eslint-loader": "^1.6.1",
+    "jasmine-core": "^2.5.2",
+    "karma": "^1.3.0",
+    "karma-chrome-launcher": "2.0.0",
+    "karma-coverage": "^1.1.1",
+    "karma-jasmine": "1.0.2",
+    "karma-jasmine-matchers": "3.0.1",
+    "load-script": "^1.0.0",
+    "karma-webpack": "^2.0.2",
+    "webpack": "2",
+    "webpack-dev-server": "2"
+  },
+  "homepage": "https://github.com/katspaugh/wavesurfer.js"
+}

+ 108 - 0
libraries/wavesurfer/spec/peakcache.spec.js

@@ -0,0 +1,108 @@
+import PeakCache from '../src/peakcache';
+
+describe('PeakCache:', function() {
+    var peakcache;
+    var test_length = 200;
+    var test_length2 = 300;
+    var test_start = 50;
+    var test_end = 100;
+    var test_start2 = 100;
+    var test_end2 = 120;
+    var test_start3 = 120;
+    var test_end3 = 150;
+
+    var window_size = 20;
+
+    function __createPeakCache() {
+        peakcache = new PeakCache();
+    }
+
+    beforeEach(function (done) {
+        __createPeakCache();
+        done();
+    });
+
+    /** @test {PeakCache#addRangeToPeakCache} */
+    it('empty cache returns full range', function() {
+        var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        expect(newranges.length).toEqual(1);
+        expect(newranges[0][0]).toEqual(test_start);
+        expect(newranges[0][1]).toEqual(test_end);
+    });
+
+    /** @test {PeakCache#addRangeToPeakCache} */
+    it('different length clears cache', function() {
+        peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        var newranges = peakcache.addRangeToPeakCache(test_length2, test_start, test_end);
+        expect(newranges.length).toEqual(1);
+        expect(newranges[0][0]).toEqual(test_start);
+        expect(newranges[0][1]).toEqual(test_end);
+    });
+
+    /** @test {PeakCache#addRangeToPeakCache} */
+    it('consecutive calls return no ranges', function() {
+        peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        expect(newranges.length).toEqual(0);
+    });
+
+    /** @test {PeakCache#addRangeToPeakCache} */
+    it('sliding window returns window sized range', function() {
+        var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        expect(newranges.length).toEqual(1);
+        expect(newranges[0][0]).toEqual(test_start);
+        expect(newranges[0][1]).toEqual(test_end);
+        var newranges = peakcache.addRangeToPeakCache(test_length, test_start + window_size, test_end + window_size);
+        expect(newranges.length).toEqual(1);
+        expect(newranges[0][0]).toEqual(test_end);
+        expect(newranges[0][1]).toEqual(test_end + window_size);
+        var newranges = peakcache.addRangeToPeakCache(test_length, test_start + window_size * 2, test_end + window_size * 2);
+        expect(newranges.length).toEqual(1);
+        expect(newranges[0][0]).toEqual(test_end + window_size);
+        expect(newranges[0][1]).toEqual(test_end + window_size * 2);
+    });
+
+    /** @test {PeakCache#addRangeToPeakCache} */
+    /** @test {PeakCache#getCacheRanges} */
+    it('disjoint set creates two ranges', function() {
+        peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        peakcache.addRangeToPeakCache(test_length, test_start3, test_end3);
+        var ranges = peakcache.getCacheRanges();
+        expect(ranges.length).toEqual(2);
+        expect(ranges[0][0]).toEqual(test_start);
+        expect(ranges[0][1]).toEqual(test_end);
+        expect(ranges[1][0]).toEqual(test_start3);
+        expect(ranges[1][1]).toEqual(test_end3);
+    });
+
+    /** @test {PeakCache#addRangeToPeakCache} */
+    /** @test {PeakCache#getCacheRanges} */
+    it('filling in disjoint sets coalesces', function() {
+        peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        peakcache.addRangeToPeakCache(test_length, test_start3, test_end3);
+        var newranges = peakcache.addRangeToPeakCache(test_length, test_start, test_end3);
+        expect(newranges.length).toEqual(1);
+        expect(newranges[0][0]).toEqual(test_end);
+        expect(newranges[0][1]).toEqual(test_start3);
+        var ranges = peakcache.getCacheRanges();
+        expect(ranges.length).toEqual(1);
+        expect(ranges[0][0]).toEqual(test_start);
+        expect(ranges[0][1]).toEqual(test_end3);
+    });
+
+    /** @test {PeakCache#addRangeToPeakCache} */
+    /** @test {PeakCache#getCacheRanges} */
+    it('filling in disjoint sets coalesces / edge cases', function() {
+        peakcache.addRangeToPeakCache(test_length, test_start, test_end);
+        peakcache.addRangeToPeakCache(test_length, test_start3, test_end3);
+        var newranges = peakcache.addRangeToPeakCache(test_length, test_start2, test_end2);
+        expect(newranges.length).toEqual(1);
+        expect(newranges[0][0]).toEqual(test_end);
+        expect(newranges[0][1]).toEqual(test_start3);
+        var ranges = peakcache.getCacheRanges();
+        expect(ranges.length).toEqual(1);
+        expect(ranges[0][0]).toEqual(test_start);
+        expect(ranges[0][1]).toEqual(test_end3);
+    });
+
+});

+ 118 - 0
libraries/wavesurfer/spec/plugin-api.spec.js

@@ -0,0 +1,118 @@
+import WaveSurfer from '../src/wavesurfer.js';
+
+
+/** @test {WaveSurfer} */
+describe('WaveSurfer/plugin API:', () => {
+    jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
+
+    let waveformDiv;
+    let dummyPlugin;
+    let wavesurfer;
+
+    // clean up after each test
+    afterEach(done => {
+        wavesurfer.destroy();
+        waveformDiv.parentNode.removeChild(waveformDiv);
+        done();
+    });
+
+    // utility function to generate a mock plugin object
+    function mockPlugin(name, deferInit = false) {
+        class MockPlugin {
+            constructor(params, ws) {
+                this.ws = ws;
+                // using the instance factory unfortunately makes it
+                // difficult to use the spyOn function, so we use this
+                // instead
+                this.isInitialised = false;
+            }
+            init() {
+                this.isInitialised = true;
+            }
+            destroy() { }
+        }
+        return {
+            name,
+            deferInit,
+            staticProps: {
+                [`${name}Static`]: 'static property value'
+            },
+            instance: MockPlugin
+        };
+    }
+
+    // utility function to generate wavesurfer instances for testing
+    function __createWaveform(options = {}) {
+        waveformDiv = document.createElement('div');
+        document.getElementsByTagName('body')[0].appendChild(waveformDiv);
+
+        wavesurfer =  WaveSurfer.create(Object.assign({
+            container: waveformDiv
+        }, options));
+        wavesurfer.load('/base/spec/support/demo.wav');
+    }
+
+    // plugin methods
+    /** @test {WaveSurfer#addPlugin} */
+    it('addPlugin adds staticProps and correctly builds and instantiates plugin class', () => {
+        dummyPlugin = mockPlugin('dummy');
+        __createWaveform();
+        wavesurfer.addPlugin(dummyPlugin);
+
+        expect(wavesurfer.dummyStatic).toEqual(dummyPlugin.staticProps.dummyStatic);
+        expect(wavesurfer.dummy.ws).toEqual(wavesurfer);
+        expect(typeof Object.getPrototypeOf(wavesurfer.dummy).on).toEqual('function');
+    });
+
+    /** @test {WaveSurfer#initPlugin} */
+    it('initPlugin calls init function of the plugin and adds its name to the initialisedPluginList', () => {
+        dummyPlugin = mockPlugin('dummy');
+        __createWaveform();
+        wavesurfer.addPlugin(dummyPlugin);
+        spyOn(wavesurfer.dummy, 'init');
+        wavesurfer.initPlugin('dummy');
+
+        expect(wavesurfer.dummy.init).toHaveBeenCalled();
+        expect(wavesurfer.initialisedPluginList.dummy).toBeTrue();
+    });
+
+    /** @test {WaveSurfer#destroyPlugin} */
+    it('destroyPlugin calls plugin destroy function and removes the plugin name from the initialisedPluginList', () => {
+        dummyPlugin = mockPlugin('dummy');
+        __createWaveform();
+        wavesurfer.addPlugin(dummyPlugin);
+        wavesurfer.initPlugin('dummy');
+        spyOn(wavesurfer.dummy, 'destroy');
+        wavesurfer.destroyPlugin('dummy');
+
+        expect(wavesurfer.dummy.destroy).toHaveBeenCalled();
+        expect(wavesurfer.initialisedPluginList.dummy).toBeUndefined();
+    });
+
+    // auto-adding and initialising of plugins (registerPlugins)
+    /** @test {WaveSurfer#registerPlugins} */
+    it('registerPlugin adds a plugin but does not call plugin init function if the plugin property deferInit is truethy', () => {
+        dummyPlugin = mockPlugin('dummy', true);
+        __createWaveform({
+            plugins: [
+                dummyPlugin
+            ]
+        });
+        expect(wavesurfer.dummyStatic).toEqual(dummyPlugin.staticProps.dummyStatic);
+        expect(wavesurfer.dummy.ws).toEqual(wavesurfer);
+        expect(wavesurfer.dummy.isInitialised).toBeFalse();
+    });
+
+    /** @test {WaveSurfer#registerPlugins} */
+    it('registerPlugin adds a plugin ands calls plugin init function if the plugin property deferInit is falsey', () => {
+        dummyPlugin = mockPlugin('dummy');
+        __createWaveform({
+            plugins: [
+                dummyPlugin
+            ]
+        });
+        expect(wavesurfer.dummyStatic).toEqual(dummyPlugin.staticProps.dummyStatic);
+        expect(wavesurfer.dummy.ws).toEqual(wavesurfer);
+        expect(wavesurfer.dummy.isInitialised).toBeTrue();
+    });
+});

BIN
libraries/wavesurfer/spec/support/demo.wav


+ 29 - 0
libraries/wavesurfer/spec/util.spec.js

@@ -0,0 +1,29 @@
+import WaveSurfer from '../src/wavesurfer.js';
+
+/** @test {util} */
+describe('util:', function() {
+    /** @test {getId} */
+    it('getId returns a random string', function() {
+        expect(WaveSurfer.util.getId()).toStartWith('wavesurfer_');
+    });
+
+    /** @test {min} */
+    it('min returns the smallest number in the provided array', function() {
+        expect(WaveSurfer.util.min([0, 1, 1.1, 100, -1])).toEqual(-1);
+    });
+
+    /** @test {min} */
+    it('min returns +Infinity for an empty array', function() {
+        expect(WaveSurfer.util.min([])).toEqual(+Infinity);
+    });
+
+    /** @test {max} */
+    it('max returns the largest number in the provided array', function() {
+        expect(WaveSurfer.util.max([0, 1, 1.1, 100, -1])).toEqual(100);
+    });
+
+    /** @test {max} */
+    it('max returns -Infinity for an empty array', function() {
+        expect(WaveSurfer.util.max([])).toEqual(-Infinity);
+    });
+});

+ 113 - 0
libraries/wavesurfer/spec/wavesurfer.spec.js

@@ -0,0 +1,113 @@
+import WaveSurfer from '../src/wavesurfer.js';
+
+/** @test {WaveSurfer} */
+describe('WaveSurfer/playback:', function () {
+    var wavesurfer;
+
+    jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
+
+    /*
+     * Handle creating wavesurfer ui requirements
+     */
+    function __createWaveform() {
+        var waveformDiv = document.createElement('div');
+        waveformDiv.id = 'waveform';
+        document.getElementsByTagName('body')[0].appendChild(waveformDiv);
+
+        return WaveSurfer.create({
+            container: '#waveform',
+            waveColor: 'violet',
+            progressColor: 'purple'
+        });
+    }
+
+    beforeAll(function (done) {
+        wavesurfer = __createWaveform();
+        wavesurfer.load('/base/spec/support/demo.wav');
+
+        wavesurfer.on('ready', function () {
+            done();
+        });
+    });
+
+    beforeEach(function () {
+        wavesurfer.seekTo(0);
+    });
+
+    afterAll(function () {
+        wavesurfer.destroy();
+    });
+
+    /**
+     * @test {WaveSurfer#play}
+     * @test {WaveSurfer#isPlaying}
+     */
+    it('should play', function () {
+        wavesurfer.play();
+
+        expect(wavesurfer.isPlaying()).toBeTrue();
+    });
+
+    /**
+     * @test {WaveSurfer#play}
+     * @test {WaveSurfer#isPlaying}
+     * @test {WaveSurfer#pause}
+     */
+    it('should pause', function () {
+        wavesurfer.play();
+        expect(wavesurfer.isPlaying()).toBeTrue();
+
+        wavesurfer.pause();
+        expect(wavesurfer.isPlaying()).toBeFalse();
+    });
+
+    /**
+     * @test {WaveSurfer#playPause}
+     * @test {WaveSurfer#isPlaying}
+     */
+    it('should play or pause', function () {
+        wavesurfer.playPause();
+        expect(wavesurfer.isPlaying()).toBeTrue();
+
+        wavesurfer.playPause();
+        expect(wavesurfer.isPlaying()).toBeFalse();
+    });
+
+    /** @test {WaveSurfer#getDuration}  */
+    it('should get duration', function () {
+        var duration = parseInt(wavesurfer.getDuration(), 10);
+        expect(duration).toBeNumber();
+    });
+
+    /** @test {WaveSurfer#toggleMute}  */
+    it('should toggle mute', function () {
+        wavesurfer.toggleMute();
+        expect(wavesurfer.isMuted).toBeTrue();
+
+        wavesurfer.toggleMute();
+        expect(wavesurfer.isMuted).toBeFalse();
+    });
+
+    /** @test {WaveSurfer#setMute}  */
+    it('should set mute', function () {
+        wavesurfer.setMute(true);
+        expect(wavesurfer.isMuted).toBeTrue();
+
+        wavesurfer.setMute(false);
+        expect(wavesurfer.isMuted).toBeFalse();
+    });
+
+    /** @test {WaveSurfer#zoom}  */
+    it('should set zoom parameters', function () {
+        wavesurfer.zoom(20);
+        expect(wavesurfer.params.minPxPerSec).toEqual(20);
+        expect(wavesurfer.params.scrollParent).toBe(true);
+    });
+
+    /** @test {WaveSurfer#zoom}  */
+    it('should set unzoom parameters', function () {
+        wavesurfer.zoom(false);
+        expect(wavesurfer.params.minPxPerSec).toEqual(wavesurfer.defaultParams.minPxPerSec);
+        expect(wavesurfer.params.scrollParent).toBe(false);
+    });
+});

+ 357 - 0
libraries/wavesurfer/src/drawer.js

@@ -0,0 +1,357 @@
+import * as util from './util';
+/**
+ * Parent class for renderers
+ *
+ * @extends {Observer}
+ */
+export default class Drawer extends util.Observer {
+    /**
+     * @param {HTMLElement} container The container node of the wavesurfer instance
+     * @param {WavesurferParams} params The wavesurfer initialisation options
+     */
+    constructor(container, params) {
+        super();
+        /** @private */
+        this.container = container;
+        /**
+         * @type {WavesurferParams}
+         * @private
+         */
+        this.params = params;
+        /**
+         * The width of the renderer
+         * @type {number}
+         */
+        this.width = 0;
+        /**
+         * The height of the renderer
+         * @type {number}
+         */
+        this.height = params.height * this.params.pixelRatio;
+        /** @private */
+        this.lastPos = 0;
+        /**
+         * The `<wave>` element which is added to the container
+         * @type {HTMLElement}
+         */
+        this.wrapper = null;
+    }
+
+    /**
+     * Alias of `util.style`
+     *
+     * @param {HTMLElement} el The element that the styles will be applied to
+     * @param {Object} styles The map of propName: attribute, both are used as-is
+     * @return {HTMLElement} el
+     */
+    style(el, styles) {
+        return util.style(el, styles);
+    }
+
+    /**
+     * Create the wrapper `<wave>` element, style it and set up the events for
+     * interaction
+     */
+    createWrapper() {
+        this.wrapper = this.container.appendChild(
+            document.createElement('wave')
+        );
+
+        this.style(this.wrapper, {
+            display: 'block',
+            position: 'relative',
+            userSelect: 'none',
+            webkitUserSelect: 'none',
+            height: this.params.height + 'px'
+        });
+
+        if (this.params.fillParent || this.params.scrollParent) {
+            this.style(this.wrapper, {
+                width: '100%',
+                overflowX: this.params.hideScrollbar ? 'hidden' : 'auto',
+                overflowY: 'hidden'
+            });
+        }
+
+        this.setupWrapperEvents();
+    }
+
+    /**
+     * Handle click event
+     *
+     * @param {Event} e Click event
+     * @param {?boolean} noPrevent Set to true to not call `e.preventDefault()`
+     * @return {number} Playback position from 0 to 1
+     */
+    handleEvent(e, noPrevent) {
+        !noPrevent && e.preventDefault();
+
+        const clientX = e.targetTouches ? e.targetTouches[0].clientX : e.clientX;
+        const bbox = this.wrapper.getBoundingClientRect();
+
+        const nominalWidth = this.width;
+        const parentWidth = this.getWidth();
+
+        let progress;
+
+        if (!this.params.fillParent && nominalWidth < parentWidth) {
+            progress = ((clientX - bbox.left) * this.params.pixelRatio / nominalWidth) || 0;
+
+            if (progress > 1) {
+                progress = 1;
+            }
+        } else {
+            progress = ((clientX - bbox.left + this.wrapper.scrollLeft) / this.wrapper.scrollWidth) || 0;
+        }
+
+        return progress;
+    }
+
+    /**
+     * @private
+     */
+    setupWrapperEvents() {
+        this.wrapper.addEventListener('click', e => {
+            const scrollbarHeight = this.wrapper.offsetHeight - this.wrapper.clientHeight;
+            if (scrollbarHeight != 0) {
+                // scrollbar is visible.  Check if click was on it
+                const bbox = this.wrapper.getBoundingClientRect();
+                if (e.clientY >= bbox.bottom - scrollbarHeight) {
+                    // ignore mousedown as it was on the scrollbar
+                    return;
+                }
+            }
+
+            if (this.params.interact) {
+                this.fireEvent('click', e, this.handleEvent(e));
+            }
+        });
+
+        this.wrapper.addEventListener('scroll', e => this.fireEvent('scroll', e));
+    }
+
+    /**
+     * Draw peaks on the canvas
+     *
+     * @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
+     * rendering
+     * @param {number} length The width of the area that should be drawn
+     * @param {number} start The x-offset of the beginning of the area that
+     * should be rendered
+     * @param {number} end The x-offset of the end of the area that should be
+     * rendered
+     */
+    drawPeaks(peaks, length, start, end) {
+        if (!this.setWidth(length)) {
+            this.clearWave();
+        }
+
+        this.params.barWidth ?
+            this.drawBars(peaks, 0, start, end) :
+            this.drawWave(peaks, 0, start, end);
+    }
+
+    /**
+     * Scroll to the beginning
+     */
+    resetScroll() {
+        if (this.wrapper !== null) {
+            this.wrapper.scrollLeft = 0;
+        }
+    }
+
+    /**
+     * Recenter the viewport at a certain percent of the waveform
+     *
+     * @param {number} percent Value from 0 to 1 on the waveform
+     */
+    recenter(percent) {
+        const position = this.wrapper.scrollWidth * percent;
+        this.recenterOnPosition(position, true);
+    }
+
+    /**
+     * Recenter the viewport on a position, either scroll there immediately or
+     * in steps of 5 pixels
+     *
+     * @param {number} position X-offset in pixels
+     * @param {boolean} immediate Set to true to immediately scroll somewhere
+     */
+    recenterOnPosition(position, immediate) {
+        const scrollLeft = this.wrapper.scrollLeft;
+        const half = ~~(this.wrapper.clientWidth / 2);
+        const maxScroll = this.wrapper.scrollWidth - this.wrapper.clientWidth;
+        let target = position - half;
+        let offset = target - scrollLeft;
+
+        if (maxScroll == 0) {
+            // no need to continue if scrollbar is not there
+            return;
+        }
+
+        // if the cursor is currently visible...
+        if (!immediate && -half <= offset && offset < half) {
+            // we'll limit the "re-center" rate.
+            const rate = 5;
+            offset = Math.max(-rate, Math.min(rate, offset));
+            target = scrollLeft + offset;
+        }
+
+        // limit target to valid range (0 to maxScroll)
+        target = Math.max(0, Math.min(maxScroll, target));
+        // no use attempting to scroll if we're not moving
+        if (target != scrollLeft) {
+            this.wrapper.scrollLeft = target;
+        }
+    }
+
+    /**
+     * Get the current scroll position in pixels
+     *
+     * @return {number}
+     */
+    getScrollX() {
+        return Math.round(this.wrapper.scrollLeft * this.params.pixelRatio);
+    }
+
+    /**
+     * Get the width of the container
+     *
+     * @return {number}
+     */
+    getWidth() {
+        return Math.round(this.container.clientWidth * this.params.pixelRatio);
+    }
+
+    /**
+     * Set the width of the container
+     *
+     * @param {number} width
+     */
+    setWidth(width) {
+        if (this.width == width) {
+            return false;
+        }
+
+        this.width = width;
+
+        if (this.params.fillParent || this.params.scrollParent) {
+            this.style(this.wrapper, {
+                width: ''
+            });
+        } else {
+            this.style(this.wrapper, {
+                width: ~~(this.width / this.params.pixelRatio) + 'px'
+            });
+        }
+
+        this.updateSize();
+        return true;
+    }
+
+    /**
+     * Set the height of the container
+     *
+     * @param {number} height
+     */
+    setHeight(height) {
+        if (height == this.height) {
+            return false;
+        }
+        this.height = height;
+
+        this.style(this.wrapper, {
+            height: ~~(this.height / this.params.pixelRatio) + 'px'
+        });
+
+        this.updateSize();
+        return true;
+    }
+
+    /**
+     * Called by wavesurfer when progress should be renderered
+     *
+     * @param {number} progress From 0 to 1
+     */
+    progress(progress) {
+        const minPxDelta = 1 / this.params.pixelRatio;
+        const pos = Math.round(progress * this.width) * minPxDelta;
+
+        if (pos < this.lastPos || pos - this.lastPos >= minPxDelta) {
+            this.lastPos = pos;
+
+            if (this.params.scrollParent && this.params.autoCenter) {
+                const newPos = ~~(this.wrapper.scrollWidth * progress);
+                this.recenterOnPosition(newPos);
+            }
+
+            this.updateProgress(pos);
+        }
+    }
+
+    /**
+     * This is called when wavesurfer is destroyed
+     */
+    destroy() {
+        this.unAll();
+        if (this.wrapper) {
+            if (this.wrapper.parentNode == this.container) {
+                this.container.removeChild(this.wrapper);
+            }
+            this.wrapper = null;
+        }
+    }
+
+    /* Renderer-specific methods */
+    /**
+     * Called when the size of the container changes so the renderer can adjust
+     *
+     * @abstract
+     */
+    updateSize() {}
+
+    /**
+     * Draw a waveform with bars
+     *
+     * @abstract
+     * @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
+     * rendering
+     * @param {number} channelIndex The index of the current channel. Normally
+     * should be 0
+     * @param {number} start The x-offset of the beginning of the area that
+     * should be rendered
+     * @param {number} end The x-offset of the end of the area that should be
+     * rendered
+     */
+    drawBars(peaks, channelIndex, start, end) {}
+
+    /**
+     * Draw a waveform
+     *
+     * @abstract
+     * @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
+     * rendering
+     * @param {number} channelIndex The index of the current channel. Normally
+     * should be 0
+     * @param {number} start The x-offset of the beginning of the area that
+     * should be rendered
+     * @param {number} end The x-offset of the end of the area that should be
+     * rendered
+     */
+    drawWave(peaks, channelIndex, start, end) {}
+
+    /**
+     * Clear the waveform
+     *
+     * @abstract
+     */
+    clearWave() {}
+
+    /**
+     * Render the new progress
+     *
+     * @abstract
+     * @param {number} position X-Offset of progress position in pixels
+     */
+    updateProgress(position) {}
+}

+ 529 - 0
libraries/wavesurfer/src/drawer.multicanvas.js

@@ -0,0 +1,529 @@
+import Drawer from './drawer';
+import * as util from './util';
+
+/**
+ * @typedef {Object} CanvasEntry
+ * @private
+ * @property {HTMLElement} wave The wave node
+ * @property {CanvasRenderingContext2D} waveCtx The canvas rendering context
+ * @property {?HTMLElement} progress The progress wave node
+ * @property {?CanvasRenderingContext2D} progressCtx The progress wave canvas
+ * rendering context
+ * @property {?number} start Start of the area the canvas should render, between 0 and 1
+ * @property {?number} end End of the area the canvas should render, between 0 and 1
+ */
+
+/**
+ * MultiCanvas renderer for wavesurfer. Is currently the default and sole built
+ * in renderer.
+ */
+export default class MultiCanvas extends Drawer {
+    /**
+     * @param {HTMLElement} container The container node of the wavesurfer instance
+     * @param {WavesurferParams} params The wavesurfer initialisation options
+     */
+    constructor(container, params) {
+        super(container, params);
+        /**
+         * @type {number}
+         * @private
+         */
+        this.maxCanvasWidth = params.maxCanvasWidth;
+        /**
+         * @private
+         * @type {number}
+         */
+        this.maxCanvasElementWidth = Math.round(params.maxCanvasWidth / params.pixelRatio);
+
+        /**
+         * Whether or not the progress wave is renderered. If the `waveColor`
+         * and `progressColor` are the same colour it is not.
+         * @type {boolean}
+         */
+        this.hasProgressCanvas = params.waveColor != params.progressColor;
+        /**
+         * @private
+         * @type {number}
+         */
+        this.halfPixel = 0.5 / params.pixelRatio;
+        /**
+         * @private
+         * @type {Array}
+         */
+        this.canvases = [];
+        /** @private */
+        this.progressWave = null;
+    }
+
+    /**
+     * Initialise the drawer
+     */
+    init() {
+        this.createWrapper();
+        this.createElements();
+    }
+
+    /**
+     * Create the canvas elements and style them
+     *
+     * @private
+     */
+    createElements() {
+        this.progressWave = this.wrapper.appendChild(
+            this.style(document.createElement('wave'), {
+                position: 'absolute',
+                zIndex: 3,
+                left: 0,
+                top: 0,
+                bottom: 0,
+                overflow: 'hidden',
+                width: '0',
+                display: 'none',
+                boxSizing: 'border-box',
+                borderRightStyle: 'solid',
+                borderRightWidth: this.params.cursorWidth + 'px',
+                borderRightColor: this.params.cursorColor,
+                pointerEvents: 'none'
+            })
+        );
+
+        this.addCanvas();
+    }
+
+    /**
+     * Adjust to the updated size by adding or removing canvases
+     */
+    updateSize() {
+        const totalWidth = Math.round(this.width / this.params.pixelRatio);
+        const requiredCanvases = Math.ceil(totalWidth / this.maxCanvasElementWidth);
+
+        while (this.canvases.length < requiredCanvases) {
+            this.addCanvas();
+        }
+
+        while (this.canvases.length > requiredCanvases) {
+            this.removeCanvas();
+        }
+
+        this.canvases.forEach((entry, i) => {
+            // Add some overlap to prevent vertical white stripes, keep the width even for simplicity.
+            let canvasWidth = this.maxCanvasWidth + 2 * Math.ceil(this.params.pixelRatio / 2);
+
+            if (i == this.canvases.length - 1) {
+                canvasWidth = this.width - (this.maxCanvasWidth * (this.canvases.length - 1));
+            }
+
+            this.updateDimensions(entry, canvasWidth, this.height);
+            this.clearWaveForEntry(entry);
+        });
+    }
+
+    /**
+     * Add a canvas to the canvas list
+     *
+     * @private
+     */
+    addCanvas() {
+        const entry = {};
+        const leftOffset = this.maxCanvasElementWidth * this.canvases.length;
+
+        entry.wave = this.wrapper.appendChild(
+            this.style(document.createElement('canvas'), {
+                position: 'absolute',
+                zIndex: 2,
+                left: leftOffset + 'px',
+                top: 0,
+                bottom: 0,
+                height: '100%',
+                pointerEvents: 'none'
+            })
+        );
+        entry.waveCtx = entry.wave.getContext('2d');
+
+        if (this.hasProgressCanvas) {
+            entry.progress = this.progressWave.appendChild(
+                this.style(document.createElement('canvas'), {
+                    position: 'absolute',
+                    left: leftOffset + 'px',
+                    top: 0,
+                    bottom: 0,
+                    height: '100%'
+                })
+            );
+            entry.progressCtx = entry.progress.getContext('2d');
+        }
+
+        this.canvases.push(entry);
+    }
+
+    /**
+     * Pop one canvas from the list
+     *
+     * @private
+     */
+    removeCanvas() {
+        const lastEntry = this.canvases.pop();
+        lastEntry.wave.parentElement.removeChild(lastEntry.wave);
+        if (this.hasProgressCanvas) {
+            lastEntry.progress.parentElement.removeChild(lastEntry.progress);
+        }
+    }
+
+    /**
+     * Update the dimensions of a canvas element
+     *
+     * @private
+     * @param {CanvasEntry} entry
+     * @param {number} width The new width of the element
+     * @param {number} height The new height of the element
+     */
+    updateDimensions(entry, width, height) {
+        const elementWidth = Math.round(width / this.params.pixelRatio);
+        const totalWidth = Math.round(this.width / this.params.pixelRatio);
+
+        // Where the canvas starts and ends in the waveform, represented as a decimal between 0 and 1.
+        entry.start = (entry.waveCtx.canvas.offsetLeft / totalWidth) || 0;
+        entry.end = entry.start + elementWidth / totalWidth;
+
+        entry.waveCtx.canvas.width = width;
+        entry.waveCtx.canvas.height = height;
+        this.style(entry.waveCtx.canvas, { width: elementWidth + 'px'});
+
+        this.style(this.progressWave, { display: 'block'});
+
+        if (this.hasProgressCanvas) {
+            entry.progressCtx.canvas.width = width;
+            entry.progressCtx.canvas.height = height;
+            this.style(entry.progressCtx.canvas, { width: elementWidth + 'px'});
+        }
+    }
+
+    /**
+     * Clear the whole waveform
+     */
+    clearWave() {
+        this.canvases.forEach(entry => this.clearWaveForEntry(entry));
+    }
+
+    /**
+     * Clear one canvas
+     *
+     * @private
+     * @param {CanvasEntry} entry
+     */
+    clearWaveForEntry(entry) {
+        entry.waveCtx.clearRect(0, 0, entry.waveCtx.canvas.width, entry.waveCtx.canvas.height);
+        if (this.hasProgressCanvas) {
+            entry.progressCtx.clearRect(0, 0, entry.progressCtx.canvas.width, entry.progressCtx.canvas.height);
+        }
+    }
+
+    /**
+     * Draw a waveform with bars
+     *
+     * @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
+     * rendering
+     * @param {number} channelIndex The index of the current channel. Normally
+     * should be 0. Must be an integer.
+     * @param {number} start The x-offset of the beginning of the area that
+     * should be rendered
+     * @param {number} end The x-offset of the end of the area that should be
+     * rendered
+     */
+    drawBars(peaks, channelIndex, start, end) {
+        return this.prepareDraw(peaks, channelIndex, start, end, ({
+            absmax,
+            hasMinVals,
+            height,
+            offsetY,
+            halfH
+        }) => {
+            // if drawBars was called within ws.empty we don't pass a start and
+            // don't want anything to happen
+            if (start === undefined) {
+                return;
+            }
+            // Skip every other value if there are negatives.
+            const peakIndexScale = hasMinVals ? 2 : 1;
+            const length = peaks.length / peakIndexScale;
+            const bar = this.params.barWidth * this.params.pixelRatio;
+            const gap = Math.max(this.params.pixelRatio, ~~(bar / 2));
+            const step = bar + gap;
+
+            const scale = length / this.width;
+            const first = start;
+            const last = end;
+            let i;
+
+            for (i = first; i < last; i += step) {
+                const peak = peaks[Math.floor(i * scale * peakIndexScale)] || 0;
+                const h = Math.round(peak / absmax * halfH);
+                this.fillRect(i + this.halfPixel, halfH - h + offsetY, bar + this.halfPixel, h * 2);
+            }
+        });
+    }
+
+    /**
+     * Draw a waveform
+     *
+     * @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
+     * rendering
+     * @param {number} channelIndex The index of the current channel. Normally
+     * should be 0
+     * @param {number?} start The x-offset of the beginning of the area that
+     * should be rendered (If this isn't set only a flat line is rendered)
+     * @param {number?} end The x-offset of the end of the area that should be
+     * rendered
+     */
+    drawWave(peaks, channelIndex, start, end) {
+        return this.prepareDraw(peaks, channelIndex, start, end, ({
+            absmax,
+            hasMinVals,
+            height,
+            offsetY,
+            halfH
+        }) => {
+            if (!hasMinVals) {
+                const reflectedPeaks = [];
+                const len = peaks.length;
+                let i;
+                for (i = 0; i < len; i++) {
+                    reflectedPeaks[2 * i] = peaks[i];
+                    reflectedPeaks[2 * i + 1] = -peaks[i];
+                }
+                peaks = reflectedPeaks;
+            }
+
+            // if drawWave was called within ws.empty we don't pass a start and
+            // end and simply want a flat line
+            if (start !== undefined) {
+                this.drawLine(peaks, absmax, halfH, offsetY, start, end);
+            }
+
+            // Always draw a median line
+            this.fillRect(0, halfH + offsetY - this.halfPixel, this.width, this.halfPixel);
+        });
+    }
+
+    /**
+     * Tell the canvas entries to render their portion of the waveform
+     *
+     * @private
+     * @param {number[]} peaks Peak data
+     * @param {number} absmax Maximum peak value (absolute)
+     * @param {number} halfH Half the height of the waveform
+     * @param {number} offsetY Offset to the top
+     * @param {number} start The x-offset of the beginning of the area that
+     * should be rendered
+     * @param {number} end The x-offset of the end of the area that
+     * should be rendered
+     */
+    drawLine(peaks, absmax, halfH, offsetY, start, end) {
+        this.canvases.forEach(entry => {
+            this.setFillStyles(entry);
+            this.drawLineToContext(entry, entry.waveCtx, peaks, absmax, halfH, offsetY, start, end);
+            this.drawLineToContext(entry, entry.progressCtx, peaks, absmax, halfH, offsetY, start, end);
+        });
+    }
+
+    /**
+     * Render the actual waveform line on a canvas
+     *
+     * @private
+     * @param {CanvasEntry} entry
+     * @param {Canvas2DContextAttributes} ctx Essentially `entry.[wave|progress]Ctx`
+     * @param {number[]} peaks
+     * @param {number} absmax Maximum peak value (absolute)
+     * @param {number} halfH Half the height of the waveform
+     * @param {number} offsetY Offset to the top
+     * @param {number} start The x-offset of the beginning of the area that
+     * should be rendered
+     * @param {number} end The x-offset of the end of the area that
+     * should be rendered
+     */
+    drawLineToContext(entry, ctx, peaks, absmax, halfH, offsetY, start, end) {
+        if (!ctx) { return; }
+
+        const length = peaks.length / 2;
+        const scale = (this.params.fillParent && this.width != length)
+          ? this.width / length
+          : 1;
+
+        const first = Math.round(length * entry.start);
+        // Use one more peak value to make sure we join peaks at ends -- unless,
+        // of course, this is the last canvas.
+        const last = Math.round(length * entry.end) + 1;
+        if (first > end || last < start) { return; }
+        const canvasStart = Math.min(first, start);
+        const canvasEnd = Math.max(last, end);
+        let i;
+        let j;
+
+        ctx.beginPath();
+        ctx.moveTo((canvasStart - first) * scale + this.halfPixel, halfH + offsetY);
+
+        for (i = canvasStart; i < canvasEnd; i++) {
+            const peak = peaks[2 * i] || 0;
+            const h = Math.round(peak / absmax * halfH);
+            ctx.lineTo((i - first) * scale + this.halfPixel, halfH - h + offsetY);
+        }
+
+        // Draw the bottom edge going backwards, to make a single
+        // closed hull to fill.
+        for (j = canvasEnd - 1; j >= canvasStart; j--) {
+            const peak = peaks[2 * j + 1] || 0;
+            const h = Math.round(peak / absmax * halfH);
+            ctx.lineTo((j - first) * scale + this.halfPixel, halfH - h + offsetY);
+        }
+
+        ctx.closePath();
+        ctx.fill();
+    }
+
+    /**
+     * Draw a rectangle on the waveform
+     *
+     * @param {number} x
+     * @param {number} y
+     * @param {number} width
+     * @param {number} height
+     */
+    fillRect(x, y, width, height) {
+        const startCanvas = Math.floor(x / this.maxCanvasWidth);
+        const endCanvas = Math.min(
+          Math.ceil((x + width) / this.maxCanvasWidth) + 1,
+          this.canvases.length
+        );
+        let i;
+        for (i = startCanvas; i < endCanvas; i++) {
+            const entry = this.canvases[i];
+            const leftOffset = i * this.maxCanvasWidth;
+
+            const intersection = {
+                x1: Math.max(x, i * this.maxCanvasWidth),
+                y1: y,
+                x2: Math.min(x + width, i * this.maxCanvasWidth + entry.waveCtx.canvas.width),
+                y2: y + height
+            };
+
+            if (intersection.x1 < intersection.x2) {
+                this.setFillStyles(entry);
+
+                this.fillRectToContext(entry.waveCtx,
+                        intersection.x1 - leftOffset,
+                        intersection.y1,
+                        intersection.x2 - intersection.x1,
+                        intersection.y2 - intersection.y1);
+
+                this.fillRectToContext(entry.progressCtx,
+                        intersection.x1 - leftOffset,
+                        intersection.y1,
+                        intersection.x2 - intersection.x1,
+                        intersection.y2 - intersection.y1);
+            }
+        }
+    }
+
+    /**
+     * Performs preparation tasks and calculations which are shared by drawBars and drawWave
+     *
+     * @private
+     * @param {number[]|number[][]} peaks Can also be an array of arrays for split channel
+     * rendering
+     * @param {number} channelIndex The index of the current channel. Normally
+     * should be 0
+     * @param {number?} start The x-offset of the beginning of the area that
+     * should be rendered (If this isn't set only a flat line is rendered)
+     * @param {number?} end The x-offset of the end of the area that should be
+     * rendered
+     * @param {function} fn The render function to call
+     */
+    prepareDraw(peaks, channelIndex, start, end, fn) {
+        return util.frame(() => {
+            // Split channels and call this function with the channelIndex set
+            if (peaks[0] instanceof Array) {
+                const channels = peaks;
+                if (this.params.splitChannels) {
+                    this.setHeight(channels.length * this.params.height * this.params.pixelRatio);
+                    channels.forEach((channelPeaks, i) => this.prepareDraw(channelPeaks, i, start, end, fn));
+                    return;
+                }
+                peaks = channels[0];
+            }
+
+            // calculate maximum modulation value, either from the barHeight
+            // parameter or if normalize=true from the largest value in the peak
+            // set
+            let absmax = 1 / this.params.barHeight;
+            if (this.params.normalize) {
+                const max = util.max(peaks);
+                const min = util.min(peaks);
+                absmax = -min > max ? -min : max;
+            }
+
+            // Bar wave draws the bottom only as a reflection of the top,
+            // so we don't need negative values
+            const hasMinVals = [].some.call(peaks, val => val < 0);
+            const height = this.params.height * this.params.pixelRatio;
+            const offsetY = height * channelIndex || 0;
+            const halfH = height / 2;
+
+            return fn({
+                absmax: absmax,
+                hasMinVals: hasMinVals,
+                height: height,
+                offsetY: offsetY,
+                halfH: halfH
+            });
+        })();
+    }
+
+    /**
+     * Draw the actual rectangle on a canvas
+     *
+     * @private
+     * @param {Canvas2DContextAttributes} ctx
+     * @param {number} x
+     * @param {number} y
+     * @param {number} width
+     * @param {number} height
+     */
+    fillRectToContext(ctx, x, y, width, height) {
+        if (!ctx) { return; }
+        ctx.fillRect(x, y, width, height);
+    }
+
+    /**
+     * Set the fill styles for a certain entry (wave and progress)
+     *
+     * @private
+     * @param {CanvasEntry} entry
+     */
+    setFillStyles(entry) {
+        entry.waveCtx.fillStyle = this.params.waveColor;
+        if (this.hasProgressCanvas) {
+            entry.progressCtx.fillStyle = this.params.progressColor;
+        }
+    }
+
+    /**
+     * Return image data of the waveform
+     *
+     * @param {string} type='image/png' An optional value of a format type.
+     * @param {number} quality=0.92 An optional value between 0 and 1.
+     * @return {string|string[]} images A data URL or an array of data URLs
+     */
+    getImage(type, quality) {
+        const images = this.canvases.map(entry => entry.wave.toDataURL(type, quality));
+        return images.length > 1 ? images : images[0];
+    }
+
+    /**
+     * Render the new progress
+     *
+     * @param {number} position X-Offset of progress position in pixels
+     */
+    updateProgress(position) {
+        this.style(this.progressWave, { width: position + 'px' });
+    }
+}

+ 221 - 0
libraries/wavesurfer/src/html-init.js

@@ -0,0 +1,221 @@
+import loadScript from 'load-script';
+
+/**
+ * @typedef {Object} InitParams
+ * @property {WavesurferParams} [defaults={backend: 'MediaElement,
+ * mediaControls: true}] The default wavesurfer initialisation parameters
+ * @property {string|NodeList} containers='wavesurfer' Selector or NodeList of
+ * elements to attach instances to
+ * @property {string}
+ * pluginCdnTemplate='//localhost:8080/dist/plugin/wavesurfer.[name].js' URL
+ * template for the dynamic loading of plugins
+ * @property {function} loadPlugin If set overwrites the default ajax function,
+ * can be used to inject plugins differently.
+ */
+/**
+ * The HTML initialisation API is not part of the main library bundle file and
+ * must be additionally included.
+ *
+ * The API attaches wavesurfer instances to all `<wavesurfer>` (can be
+ * customised), parsing their `data-` attributes to construct an options object
+ * for initialisation. Among other things it can dynamically load plugin code.
+ *
+ * The automatic initialisation can be prevented by setting the
+ * `window.WS_StopAutoInit` flag to true. The `html-init[.min].js` file exports
+ * the `Init` class, which can be called manually.
+ *
+ * Site-wide defaults can be added by setting `window.WS_InitOptions`.
+ *
+ * @example
+ * <!-- with minimap and timeline plugin -->
+ * <wavesurfer
+ *   data-url="../media/demo.wav"
+ *   data-plugins="minimap,timeline"
+ *   data-minimap-height="30"
+ *   data-minimap-wave-color="#ddd"
+ *   data-minimap-progress-color="#999"
+ *   data-timeline-font-size="13px"
+ *   data-timeline-container="#timeline"
+ * >
+ * </wavesurfer>
+ * <div id="timeline"></div>
+ *
+ * <!-- with regions plugin -->
+ * <wavesurfer
+ *   data-url="../media/demo.wav"
+ *   data-plugins="regions"
+ *   data-regions-regions='[{"start": 1,"end": 3,"color": "hsla(400, 100%, 30%, 0.5)"}]'
+ * >
+ * </wavesurfer>
+ */
+class Init {
+    /**
+     * Instantiate Init class and initialise elements
+     *
+     * This is done automatically if `window` is defined and
+     * `window.WS_StopAutoInit` is not set to true
+     *
+     * @param {WaveSurfer} WaveSurfer The WaveSurfer library object
+     * @param {InitParams} params initialisation options
+     */
+    constructor(WaveSurfer, params = {}) {
+        if (!WaveSurfer) {
+            throw new Error('WaveSurfer is not available!');
+        }
+
+        /**
+         * cache WaveSurfer
+         * @private
+         */
+        this.WaveSurfer = WaveSurfer;
+
+        /**
+         * build parameters, cache them in _params so minified builds are smaller
+         * @private
+         */
+        const _params = this.params = WaveSurfer.util.extend({}, {
+            // wavesurfer parameter defaults so by default the audio player is
+            // usable with native media element controls
+            defaults: {
+                backend: 'MediaElement',
+                mediaControls: true
+            },
+            // containers to instantiate on, can be selector string or NodeList
+            containers: 'wavesurfer',
+            // @TODO insert plugin CDN URIs
+            pluginCdnTemplate: '//localhost:8080/dist/plugin/wavesurfer.[name].js',
+            // loadPlugin function can be overriden to inject plugin definition
+            // objects, this default function uses load-script to load a plugin
+            // and pass it to a callback
+            loadPlugin(name, cb) {
+                const src = _params.pluginCdnTemplate.replace('[name]', name);
+                loadScript(src, { async: false }, (err, plugin) => {
+                    if (err) {
+                        return console.error(`WaveSurfer plugin ${name} not found at ${src}`);
+                    }
+                    cb(window.WaveSurfer[name]);
+                });
+            }
+        }, params);
+        /**
+         * The nodes that should have instances attached to them
+         * @type {NodeList}
+         */
+        this.containers = typeof _params.containers == 'string'
+            ? document.querySelectorAll(_params.containers)
+            : _params.containers;
+        /** @private */
+        this.pluginCache = {};
+        /**
+         * An array of wavesurfer instances
+         * @type {Object[]}
+         */
+        this.instances = [];
+
+        this.initAllEls();
+    }
+
+    /**
+     * Initialise all container elements
+     */
+    initAllEls() {
+        // iterate over all the container elements
+        Array.prototype.forEach.call(this.containers, el => {
+            // load the plugins as an array of plugin names
+            const plugins = el.dataset.plugins
+                ? el.dataset.plugins.split(',')
+                : [];
+
+            // no plugins to be loaded, just render
+            if (!plugins.length) {
+                return this.initEl(el);
+            }
+            // … or: iterate over all the plugins
+            plugins.forEach((name, i) => {
+                // plugin is not cached already, load it
+                if (!this.pluginCache[name]) {
+                    this.params.loadPlugin(name, lib => {
+                        this.pluginCache[name] = lib;
+                        // plugins were all loaded, render the element
+                        if (i + 1 === plugins.length) {
+                            this.initEl(el, plugins);
+                        }
+                    });
+                } else if (i === plugins.length) {
+                    // plugin was cached and this plugin was the last
+                    this.initEl(el, plugins);
+                }
+            });
+        });
+    }
+
+    /**
+     * Initialise a single container element and add to `this.instances`
+     *
+     * @param  {HTMLElement} el The container to instantiate wavesurfer to
+     * @param  {PluginDefinition[]} plugins An Array of plugin names to initialise with
+     * @return {Object} Wavesurfer instance
+     */
+    initEl(el, plugins = []) {
+        const jsonRegex = /^[[|{]/;
+        // initialise plugins with the correct options
+        const initialisedPlugins = plugins.map(plugin => {
+            const options = {};
+            // the regex to find this plugin attributes
+            const attrNameRegex = new RegExp('^' + plugin);
+            let attrName;
+            // iterate over all the data attributes and find ones for this
+            // plugin
+            for (attrName in el.dataset) {
+                const regexResult = attrNameRegex.exec(attrName);
+                if (regexResult) {
+                    const attr = el.dataset[attrName];
+                    // if the string begins with a [ or a { parse it as JSON
+                    const prop = jsonRegex.test(attr) ? JSON.parse(attr) : attr;
+                    // this removes the plugin prefix and changes the first letter
+                    // of the resulting string to lower case to follow the naming
+                    // convention of ws params
+                    const unprefixedOptionName = attrName.slice(plugin.length, plugin.length + 1).toLowerCase()
+                        + attrName.slice(plugin.length + 1);
+                    options[unprefixedOptionName] = prop;
+                }
+            }
+            return this.pluginCache[plugin].create(options);
+        });
+        // build parameter object for this container
+        const params = this.WaveSurfer.util.extend(
+            { container: el },
+            this.params.defaults,
+            el.dataset,
+            { plugins: initialisedPlugins }
+        );
+
+        // @TODO make nicer
+        el.style.display = 'block';
+
+        // initialise wavesurfer, load audio (with peaks if provided)
+        const instance = this.WaveSurfer.create(params);
+        const peaks = params.peaks ? JSON.parse(params.peaks) : undefined;
+        instance.load(params.url, peaks);
+
+        // push this instance into the instances cache
+        this.instances.push(instance);
+        return instance;
+    }
+}
+
+// if window object exists and window.WS_StopAutoInit is not true
+if (typeof window === 'object' && !window.WS_StopAutoInit) {
+    // call init when document is ready, apply any custom default settings
+    // in window.WS_InitOptions
+    if (document.readyState === 'complete') {
+        window.WaveSurferInit = new Init(window.WaveSurfer, window.WS_InitOptions);
+    } else {
+        window.addEventListener('load', () => {
+            window.WaveSurferInit = new Init(window.WaveSurfer, window.WS_InitOptions);
+        });
+    }
+}
+
+// export init for manual usage
+export default Init;

+ 312 - 0
libraries/wavesurfer/src/mediaelement.js

@@ -0,0 +1,312 @@
+import WebAudio from './webaudio';
+import * as util from './util';
+
+/**
+ * MediaElement backend
+ */
+export default class MediaElement extends WebAudio {
+    /**
+     * Construct the backend
+     *
+     * @param {WavesurferParams} params
+     */
+    constructor(params) {
+        super(params);
+        /** @private */
+        this.params = params;
+
+        // Dummy media to catch errors
+        /** @private */
+        this.media = {
+            currentTime: 0,
+            duration: 0,
+            paused: true,
+            playbackRate: 1,
+            play() {},
+            pause() {}
+        };
+
+        /** @private */
+        this.mediaType = params.mediaType.toLowerCase();
+        /** @private */
+        this.elementPosition = params.elementPosition;
+        /** @private */
+        this.peaks = null;
+        /** @private */
+        this.playbackRate = 1;
+        /** @private */
+        this.buffer = null;
+        /** @private */
+        this.onPlayEnd = null;
+    }
+
+    /**
+     * Initialise the backend, called in `wavesurfer.createBackend()`
+     */
+    init() {
+        this.setPlaybackRate(this.params.audioRate);
+        this.createTimer();
+    }
+
+    /**
+     * Create a timer to provide a more precise `audioprocess` event.
+     *
+     * @private
+     */
+    createTimer() {
+        const onAudioProcess = () => {
+            if (this.isPaused()) { return; }
+            this.fireEvent('audioprocess', this.getCurrentTime());
+
+            // Call again in the next frame
+            const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
+            requestAnimationFrame(onAudioProcess);
+        };
+
+        this.on('play', onAudioProcess);
+    }
+
+    /**
+     *  Create media element with url as its source,
+     *  and append to container element.
+     *
+     *  @param {string} url Path to media file
+     *  @param {HTMLElement} container HTML element
+     *  @param {Array} peaks Array of peak data
+     *  @param {string} preload HTML 5 preload attribute value
+     */
+    load(url, container, peaks, preload) {
+        const media = document.createElement(this.mediaType);
+        media.controls = this.params.mediaControls;
+        media.autoplay = this.params.autoplay || false;
+        media.preload = preload == null ? 'auto' : preload;
+        media.src = url;
+        media.style.width = '100%';
+
+        const prevMedia = container.querySelector(this.mediaType);
+        if (prevMedia) {
+            container.removeChild(prevMedia);
+        }
+        container.appendChild(media);
+
+        this._load(media, peaks);
+    }
+
+    /**
+     *  Load existing media element.
+     *
+     *  @param {MediaElement} elt HTML5 Audio or Video element
+     *  @param {Array} peaks Array of peak data
+     */
+    loadElt(elt, peaks) {
+        elt.controls = this.params.mediaControls;
+        elt.autoplay = this.params.autoplay || false;
+
+        this._load(elt, peaks);
+    }
+
+    /**
+     *  Private method called by both load (from url)
+     *  and loadElt (existing media element).
+     *
+     *  @param  {MediaElement}  media     HTML5 Audio or Video element
+     *  @param  {Array}         peaks   array of peak data
+     *  @private
+     */
+    _load(media, peaks) {
+        // load must be called manually on iOS, otherwise peaks won't draw
+        // until a user interaction triggers load --> 'ready' event
+        if (typeof media.load == 'function') {
+            media.load();
+        }
+
+        media.addEventListener('error', () => {
+            this.fireEvent('error', 'Error loading media element');
+        });
+
+        media.addEventListener('canplay', () => {
+            this.fireEvent('canplay');
+        });
+
+        media.addEventListener('ended', () => {
+            this.fireEvent('finish');
+        });
+
+        // Listen to and relay play and pause events to enable
+        // playback control from the external media element
+        media.addEventListener('play', () => {
+            this.fireEvent('play');
+        });
+
+        media.addEventListener('pause', () => {
+            this.fireEvent('pause');
+        });
+
+        this.media = media;
+        this.peaks = peaks;
+        this.onPlayEnd = null;
+        this.buffer = null;
+        this.setPlaybackRate(this.playbackRate);
+    }
+
+    /**
+     * Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
+     *
+     * @return {boolean}
+     */
+    isPaused() {
+        return !this.media || this.media.paused;
+    }
+
+    /**
+     * Used by `wavesurfer.getDuration()`
+     *
+     * @return {number}
+     */
+    getDuration() {
+        let duration = (this.buffer || this.media).duration;
+        if (duration >= Infinity) { // streaming audio
+            duration = this.media.seekable.end(0);
+        }
+        return duration;
+    }
+
+    /**
+    * Returns the current time in seconds relative to the audioclip's
+    * duration.
+    *
+    * @return {number}
+    */
+    getCurrentTime() {
+        return this.media && this.media.currentTime;
+    }
+
+    /**
+     * Get the position from 0 to 1
+     *
+     * @return {number}
+     */
+    getPlayedPercents() {
+        return (this.getCurrentTime() / this.getDuration()) || 0;
+    }
+
+    /**
+     * Get the audio source playback rate.
+     *
+     * @return {number}
+     */
+    getPlaybackRate() {
+        return this.playbackRate || this.media.playbackRate;
+    }
+
+    /**
+     * Set the audio source playback rate.
+     *
+     * @param {number} value
+     */
+    setPlaybackRate(value) {
+        this.playbackRate = value || 1;
+        this.media.playbackRate = this.playbackRate;
+    }
+
+    /**
+     * Used by `wavesurfer.seekTo()`
+     *
+     * @param {number} start Position to start at in seconds
+     */
+    seekTo(start) {
+        if (start != null) {
+            this.media.currentTime = start;
+        }
+        this.clearPlayEnd();
+    }
+
+    /**
+     * Plays the loaded audio region.
+     *
+     * @param {Number} start Start offset in seconds, relative to the beginning
+     * of a clip.
+     * @param {Number} end When to stop relative to the beginning of a clip.
+     * @emits MediaElement#play
+     */
+    play(start, end) {
+        this.seekTo(start);
+        this.media.play();
+        end && this.setPlayEnd(end);
+    }
+
+    /**
+     * Pauses the loaded audio.
+     *
+     * @emits MediaElement#pause
+     */
+    pause() {
+        this.media && this.media.pause();
+        this.clearPlayEnd();
+    }
+
+    /** @private */
+    setPlayEnd(end) {
+        this._onPlayEnd = time => {
+            if (time >= end) {
+                this.pause();
+                this.seekTo(end);
+            }
+        };
+        this.on('audioprocess', this._onPlayEnd);
+    }
+
+    /** @private */
+    clearPlayEnd() {
+        if (this._onPlayEnd) {
+            this.un('audioprocess', this._onPlayEnd);
+            this._onPlayEnd = null;
+        }
+    }
+
+    /**
+     * Compute the max and min value of the waveform when broken into
+     * <length> subranges.
+     *
+     * @param {number} length How many subranges to break the waveform into.
+     * @param {number} first First sample in the required range.
+     * @param {number} last Last sample in the required range.
+     * @return {number[]|number[][]} Array of 2*<length> peaks or array of
+     * arrays of peaks consisting of (max, min) values for each subrange.
+     */
+    getPeaks(length, first, last) {
+        if (this.buffer) {
+            return super.getPeaks(length, first, last);
+        }
+        return this.peaks || [];
+    }
+
+    /**
+     * Get the current volume
+     *
+     * @return {number} value A floating point value between 0 and 1.
+     */
+    getVolume() {
+        return this.media.volume;
+    }
+
+    /**
+     * Set the audio volume
+     *
+     * @param {number} value A floating point value between 0 and 1.
+     */
+    setVolume(value) {
+        this.media.volume = value;
+    }
+
+    /**
+     * This is called when wavesurfer is destroyed
+     *
+     */
+    destroy() {
+        this.pause();
+        this.unAll();
+        this.media && this.media.parentNode && this.media.parentNode.removeChild(this.media);
+        this.media = null;
+    }
+}

+ 116 - 0
libraries/wavesurfer/src/peakcache.js

@@ -0,0 +1,116 @@
+/**
+ * Caches the decoded peaks data to improve rendering speed for lage audio
+ *
+ * Is used if the option parameter `partialRender` is set to `true`
+ */
+export default class PeakCache {
+    /**
+     * Instantiate cache
+     */
+    constructor() {
+        this.clearPeakCache();
+    }
+
+    /**
+     * Empty the cache
+     */
+    clearPeakCache() {
+        /**
+         * Flat array with entries that are always in pairs to mark the
+         * beginning and end of each subrange.  This is a convenience so we can
+         * iterate over the pairs for easy set difference operations.
+         * @private
+         */
+        this.peakCacheRanges = [];
+        /**
+         * Length of the entire cachable region, used for resetting the cache
+         * when this changes (zoom events, for instance).
+         * @private
+         */
+        this.peakCacheLength = -1;
+    }
+
+    /**
+     * Add a range of peaks to the cache
+     *
+     * @param {number} length The length of the range
+     * @param {number} start The x offset of the start of the range
+     * @param {number} end The x offset of the end of the range
+     * @return {number[][]}
+     */
+    addRangeToPeakCache(length, start, end) {
+        if (length != this.peakCacheLength) {
+            this.clearPeakCache();
+            this.peakCacheLength = length;
+        }
+
+        // Return ranges that weren't in the cache before the call.
+        let uncachedRanges = [];
+        let i = 0;
+        // Skip ranges before the current start.
+        while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] < start) {
+            i++;
+        }
+        // If |i| is even, |start| falls after an existing range.  Otherwise,
+        // |start| falls between an existing range, and the uncached region
+        // starts when we encounter the next node in |peakCacheRanges| or
+        // |end|, whichever comes first.
+        if (i % 2 == 0) {
+            uncachedRanges.push(start);
+        }
+        while (i < this.peakCacheRanges.length && this.peakCacheRanges[i] <= end) {
+            uncachedRanges.push(this.peakCacheRanges[i]);
+            i++;
+        }
+        // If |i| is even, |end| is after all existing ranges.
+        if (i % 2 == 0) {
+            uncachedRanges.push(end);
+        }
+
+        // Filter out the 0-length ranges.
+        uncachedRanges = uncachedRanges.filter((item, pos, arr) => {
+            if (pos == 0) {
+                return item != arr[pos + 1];
+            } else if (pos == arr.length - 1) {
+                return item != arr[pos - 1];
+            }
+            return item != arr[pos - 1] && item != arr[pos + 1];
+        });
+
+        // Merge the two ranges together, uncachedRanges will either contain
+        // wholly new points, or duplicates of points in peakCacheRanges.  If
+        // duplicates are detected, remove both and extend the range.
+        this.peakCacheRanges = this.peakCacheRanges.concat(uncachedRanges);
+        this.peakCacheRanges = this.peakCacheRanges.sort((a, b) => a - b).filter((item, pos, arr) => {
+            if (pos == 0) {
+                return item != arr[pos + 1];
+            } else if (pos == arr.length - 1) {
+                return item != arr[pos - 1];
+            }
+            return item != arr[pos - 1] && item != arr[pos + 1];
+        });
+
+        // Push the uncached ranges into an array of arrays for ease of
+        // iteration in the functions that call this.
+        const uncachedRangePairs = [];
+        for (i = 0; i < uncachedRanges.length; i += 2) {
+            uncachedRangePairs.push([uncachedRanges[i], uncachedRanges[i+1]]);
+        }
+
+        return uncachedRangePairs;
+    }
+
+    /**
+     * For testing
+     *
+     * @return {number[][]}
+     */
+    getCacheRanges() {
+        const peakCacheRangePairs = [];
+        let i;
+        for (i = 0; i < this.peakCacheRanges.length; i += 2) {
+            peakCacheRangePairs.push([this.peakCacheRanges[i], this.peakCacheRanges[i+1]]);
+        }
+        return peakCacheRangePairs;
+    }
+}

+ 137 - 0
libraries/wavesurfer/src/plugin/cursor.js

@@ -0,0 +1,137 @@
+/**
+ * @typedef {Object} CursorPluginParams
+ * @property {?boolean} deferInit Set to true to stop auto init in `addPlugin()`
+ */
+
+/**
+ * Displays a thin line at the position of the cursor on the waveform.
+ *
+ * @implements {PluginClass}
+ * @extends {Observer}
+ * @example
+ * // es6
+ * import CursorPlugin from 'wavesurfer.cursor.js';
+ *
+ * // commonjs
+ * var CursorPlugin = require('wavesurfer.cursor.js');
+ *
+ * // if you are using <script> tags
+ * var CursorPlugin = window.WaveSurfer.cursor;
+ *
+ * // ... initialising wavesurfer with the plugin
+ * var wavesurfer = WaveSurfer.create({
+ *   // wavesurfer options ...
+ *   plugins: [
+ *     CursorPlugin.create({
+ *       // plugin options ...
+ *     })
+ *   ]
+ * });
+ */
+export default class CursorPlugin {
+    /**
+     * Cursor plugin definition factory
+     *
+     * This function must be used to create a plugin definition which can be
+     * used by wavesurfer to correctly instantiate the plugin.
+     *
+     * @param  {CursorPluginParams} params parameters use to initialise the
+     * plugin
+     * @return {PluginDefinition} an object representing the plugin
+     */
+    static create(params) {
+        return {
+            name: 'cursor',
+            deferInit: params && params.deferInit ? params.deferInit : false,
+            params: params,
+            staticProps: {
+                enableCursor() {
+                    console.warn('Deprecated enableCursor!');
+                    this.initPlugins('cursor');
+                }
+            },
+            instance: CursorPlugin
+        };
+    }
+
+    constructor(params, ws) {
+        this.wavesurfer = ws;
+        this.style = ws.util.style;
+        this._onDrawerCreated = () => {
+            this.drawer = this.wavesurfer.drawer;
+            this.wrapper = this.wavesurfer.drawer.wrapper;
+
+            this._onMousemove = e => this.updateCursorPosition(this.drawer.handleEvent(e));
+            this.wrapper.addEventListener('mousemove', this._onMousemove);
+
+            this._onMouseenter = () => this.showCursor();
+            this.wrapper.addEventListener('mouseenter', this._onMouseenter);
+
+            this._onMouseleave = () => this.hideCursor();
+            this.wrapper.addEventListener('mouseleave', this._onMouseleave);
+
+            this.cursor = this.wrapper.appendChild(
+                this.style(document.createElement('wave'), {
+                    position: 'absolute',
+                    zIndex: 3,
+                    left: 0,
+                    top: 0,
+                    bottom: 0,
+                    width: '0',
+                    display: 'block',
+                    borderRightStyle: 'solid',
+                    borderRightWidth: 1 + 'px',
+                    borderRightColor: 'black',
+                    opacity: '.25',
+                    pointerEvents: 'none'
+                })
+            );
+        };
+    }
+
+    init() {
+        // drawer already existed, just call initialisation code
+        if (this.wavesurfer.drawer) {
+            this._onDrawerCreated();
+        }
+
+        // the drawer was initialised, call the initialisation code
+        this.wavesurfer.on('drawer-created', this._onDrawerCreated);
+    }
+
+    destroy() {
+        this.wavesurfer.un('drawer-created', this._onDrawerCreated);
+
+        // if cursor was appended, remove it
+        if (this.cursor) {
+            this.cursor.parentNode.removeChild(this.cursor);
+        }
+
+        // if the drawer existed (the cached version referenced in the init code),
+        // remove the event listeners attached to it
+        if (this.drawer) {
+            this.wrapper.removeEventListener('mousemove', this._onMousemove);
+            this.wrapper.removeEventListener('mouseenter', this._onMouseenter);
+            this.wrapper.removeEventListener('mouseleave', this._onMouseleave);
+        }
+    }
+
+    updateCursorPosition(progress) {
+        const pos = Math.round(this.drawer.width * progress) / this.drawer.params.pixelRatio - 1;
+        this.style(this.cursor, {
+            left: `${pos}px`
+        });
+    }
+
+    showCursor() {
+        this.style(this.cursor, {
+            display: 'block'
+        });
+    }
+
+    hideCursor() {
+        this.style(this.cursor, {
+            display: 'none'
+        });
+    }
+}

+ 297 - 0
libraries/wavesurfer/src/plugin/elan.js

@@ -0,0 +1,297 @@
+/**
+ * @typedef {Object} ElanPluginParams
+ * @property {string|HTMLElement} container CSS selector or HTML element where
+ * the ELAN information should be renderer.
+ * @property {string} url The location of ELAN XML data
+ * @property {?boolean} deferInit Set to true to manually call
+ * @property {?Object} tiers If set only shows the data tiers with the `TIER_ID`
+ * in this map.
+ */
+
+/**
+ * Downloads and renders ELAN audio transcription documents alongside the
+ * waveform.
+ *
+ * @implements {PluginClass}
+ * @extends {Observer}
+ * @example
+ * // es6
+ * import ElanPlugin from 'wavesurfer.elan.js';
+ *
+ * // commonjs
+ * var ElanPlugin = require('wavesurfer.elan.js');
+ *
+ * // if you are using <script> tags
+ * var ElanPlugin = window.WaveSurfer.elan;
+ *
+ * // ... initialising wavesurfer with the plugin
+ * var wavesurfer = WaveSurfer.create({
+ *   // wavesurfer options ...
+ *   plugins: [
+ *     ElanPlugin.create({
+ *       // plugin options ...
+ *     })
+ *   ]
+ * });
+ */
+export default class ElanPlugin {
+    /**
+     * Elan plugin definition factory
+     *
+     * This function must be used to create a plugin definition which can be
+     * used by wavesurfer to correctly instantiate the plugin.
+     *
+     * @param  {ElanPluginParams} params parameters use to initialise the plugin
+     * @return {PluginDefinition} an object representing the plugin
+     */
+    static create(params) {
+        return {
+            name: 'elan',
+            deferInit: params && params.deferInit ? params.deferInit : false,
+            params: params,
+            instance: ElanPlugin
+        };
+    }
+
+    Types = {
+        ALIGNABLE_ANNOTATION: 'ALIGNABLE_ANNOTATION',
+        REF_ANNOTATION: 'REF_ANNOTATION'
+    }
+
+    constructor(params, ws) {
+        this.data = null;
+        this.params = params;
+        this.container = 'string' == typeof params.container ?
+            document.querySelector(params.container) : params.container;
+
+        if (!this.container) {
+            throw Error('No container for ELAN');
+        }
+    }
+
+    init() {
+        this.bindClick();
+
+        if (this.params.url) {
+            this.load(this.params.url);
+        }
+    }
+
+    destroy() {
+        this.container.removeEventListener('click', this._onClick);
+        this.container.removeChild(this.table);
+    }
+
+    load(url) {
+        this.loadXML(url, xml => {
+            this.data = this.parseElan(xml);
+            this.render();
+            this.fireEvent('ready', this.data);
+        });
+    }
+
+    loadXML(url, callback) {
+        const xhr = new XMLHttpRequest();
+        xhr.open('GET', url, true);
+        xhr.responseType = 'document';
+        xhr.send();
+        xhr.addEventListener('load', e => {
+            callback && callback(e.target.responseXML);
+        });
+    }
+
+    parseElan(xml) {
+        const _forEach = Array.prototype.forEach;
+        const _map = Array.prototype.map;
+
+        const data = {
+            media: {},
+            timeOrder: {},
+            tiers: [],
+            annotations: {},
+            alignableAnnotations: []
+        };
+
+        const header = xml.querySelector('HEADER');
+        const inMilliseconds = header.getAttribute('TIME_UNITS') == 'milliseconds';
+        const media = header.querySelector('MEDIA_DESCRIPTOR');
+        data.media.url = media.getAttribute('MEDIA_URL');
+        data.media.type = media.getAttribute('MIME_TYPE');
+
+        const timeSlots = xml.querySelectorAll('TIME_ORDER TIME_SLOT');
+        const timeOrder = {};
+        _forEach.call(timeSlots, slot => {
+            let value = parseFloat(slot.getAttribute('TIME_VALUE'));
+            // If in milliseconds, convert to seconds with rounding
+            if (inMilliseconds) {
+                value = Math.round(value * 1e2) / 1e5;
+            }
+            timeOrder[slot.getAttribute('TIME_SLOT_ID')] = value;
+        });
+
+        data.tiers = _map.call(xml.querySelectorAll('TIER'), tier => ({
+            id: tier.getAttribute('TIER_ID'),
+            linguisticTypeRef: tier.getAttribute('LINGUISTIC_TYPE_REF'),
+            defaultLocale: tier.getAttribute('DEFAULT_LOCALE'),
+            annotations: _map.call(
+                tier.querySelectorAll('REF_ANNOTATION, ALIGNABLE_ANNOTATION'), node => {
+                    const annot = {
+                        type: node.nodeName,
+                        id: node.getAttribute('ANNOTATION_ID'),
+                        ref: node.getAttribute('ANNOTATION_REF'),
+                        value: node.querySelector('ANNOTATION_VALUE')
+                            .textContent.trim()
+                    };
+
+                    if (this.Types.ALIGNABLE_ANNOTATION == annot.type) {
+                        // Add start & end to alignable annotation
+                        annot.start = timeOrder[node.getAttribute('TIME_SLOT_REF1')];
+                        annot.end = timeOrder[node.getAttribute('TIME_SLOT_REF2')];
+                        // Add to the list of alignable annotations
+                        data.alignableAnnotations.push(annot);
+                    }
+
+                    // Additionally, put into the flat map of all annotations
+                    data.annotations[annot.id] = annot;
+
+                    return annot;
+                }
+            )
+        }));
+
+        // Create JavaScript references between annotations
+        data.tiers.forEach(tier => {
+            tier.annotations.forEach(annot => {
+                if (null != annot.ref) {
+                    annot.reference = data.annotations[annot.ref];
+                }
+            });
+        });
+
+        // Sort alignable annotations by start & end
+        data.alignableAnnotations.sort((a, b) => {
+            let d = a.start - b.start;
+            if (d == 0) {
+                d = b.end - a.end;
+            }
+            return d;
+        });
+
+        data.length = data.alignableAnnotations.length;
+
+        return data;
+    }
+
+    render() {
+        // apply tiers filter
+        let tiers = this.data.tiers;
+        if (this.params.tiers) {
+            tiers = tiers.filter(tier => tier.id in this.params.tiers);
+        }
+
+        // denormalize references to alignable annotations
+        const backRefs = {};
+        let indeces = {};
+        tiers.forEach((tier, index) => {
+            tier.annotations.forEach(annot => {
+                if (annot.reference && annot.reference.type == this.Types.ALIGNABLE_ANNOTATION) {
+                    if (!(annot.reference.id in backRefs)) {
+                        backRefs[annot.ref] = {};
+                    }
+                    backRefs[annot.ref][index] = annot;
+                    indeces[index] = true;
+                }
+            });
+        });
+        indeces = Object.keys(indeces).sort();
+
+        this.renderedAlignable = this.data.alignableAnnotations.filter(alignable => backRefs[alignable.id]);
+
+        // table
+        const table = this.table = document.createElement('table');
+        table.className = 'wavesurfer-annotations';
+
+        // head
+        const thead = document.createElement('thead');
+        const headRow = document.createElement('tr');
+        thead.appendChild(headRow);
+        table.appendChild(thead);
+        const th = document.createElement('th');
+        th.textContent = 'Time';
+        th.className = 'wavesurfer-time';
+        headRow.appendChild(th);
+        indeces.forEach(index => {
+            const tier = tiers[index];
+            const th = document.createElement('th');
+            th.className = 'wavesurfer-tier-' + tier.id;
+            th.textContent = tier.id;
+            th.style.width = this.params.tiers[tier.id];
+            headRow.appendChild(th);
+        });
+
+        // body
+        const tbody = document.createElement('tbody');
+        table.appendChild(tbody);
+        this.renderedAlignable.forEach(alignable => {
+            const row = document.createElement('tr');
+            row.id = 'wavesurfer-alignable-' + alignable.id;
+            tbody.appendChild(row);
+
+            const td = document.createElement('td');
+            td.className = 'wavesurfer-time';
+            td.textContent = alignable.start.toFixed(1) + '–' +
+                alignable.end.toFixed(1);
+            row.appendChild(td);
+
+            const backRef = backRefs[alignable.id];
+            indeces.forEach(index => {
+                const tier = tiers[index];
+                const td = document.createElement('td');
+                const annotation = backRef[index];
+                if (annotation) {
+                    td.id = 'wavesurfer-annotation-' + annotation.id;
+                    td.dataset.ref = alignable.id;
+                    td.dataset.start = alignable.start;
+                    td.dataset.end = alignable.end;
+                    td.textContent = annotation.value;
+                }
+                td.className = 'wavesurfer-tier-' + tier.id;
+                row.appendChild(td);
+            });
+        });
+
+        this.container.innerHTML = '';
+        this.container.appendChild(table);
+    }
+
+    bindClick() {
+        this._onClick = e => {
+            const ref = e.target.dataset.ref;
+            if (null != ref) {
+                const annot = this.data.annotations[ref];
+                if (annot) {
+                    this.fireEvent('select', annot.start, annot.end);
+                }
+            }
+        };
+        this.container.addEventListener('click', this._onClick);
+    }
+
+    getRenderedAnnotation(time) {
+        let result;
+        this.renderedAlignable.some(annotation => {
+            if (annotation.start <= time && annotation.end >= time) {
+                result = annotation;
+                return true;
+            }
+            return false;
+        });
+        return result;
+    }
+
+    getAnnotationNode(annotation) {
+        return document.getElementById(
+            'wavesurfer-alignable-' + annotation.id
+        );
+    }
+}

+ 350 - 0
libraries/wavesurfer/src/plugin/microphone.js

@@ -0,0 +1,350 @@
+/**
+ * @typedef {Object} MicrophonePluginParams
+ * @property {MediaStreamConstraints} constraints The constraints parameter is a
+ * MediaStreamConstaints object with two members: video and audio, describing
+ * the media types requested. Either or both must be specified.
+ * @property {number} bufferSize=4096 The buffer size in units of sample-frames.
+ * If specified, the bufferSize must be one of the following values: `256`,
+ * `512`, `1024`, `2048`, `4096`, `8192`, `16384`
+ * @property {number} numberOfInputChannels=1 Integer specifying the number of
+ * channels for this node's input. Values of up to 32 are supported.
+ * @property {?boolean} deferInit Set to true to manually call
+ * `initPlugin('microphone')`
+ */
+
+/**
+ * Visualise microphone input in a wavesurfer instance.
+ *
+ * @implements {PluginClass}
+ * @extends {Observer}
+ * @example
+ * // es6
+ * import MicrophonePlugin from 'wavesurfer.microphone.js';
+ *
+ * // commonjs
+ * var MicrophonePlugin = require('wavesurfer.microphone.js');
+ *
+ * // if you are using <script> tags
+ * var MicrophonePlugin = window.WaveSurfer.microphone;
+ *
+ * // ... initialising wavesurfer with the plugin
+ * var wavesurfer = WaveSurfer.create({
+ *   // wavesurfer options ...
+ *   plugins: [
+ *     MicrophonePlugin.create({
+ *       // plugin options ...
+ *     })
+ *   ]
+ * });
+ */
+export default class MicrophonePlugin {
+    /**
+     * Microphone plugin definition factory
+     *
+     * This function must be used to create a plugin definition which can be
+     * used by wavesurfer to correctly instantiate the plugin.
+     *
+     * @param  {MicrophonePluginParams} params parameters use to initialise the plugin
+     * @return {PluginDefinition} an object representing the plugin
+     */
+    static create(params) {
+        return {
+            name: 'microphone',
+            deferInit: params && params.deferInit ? params.deferInit : false,
+            params: params,
+            instance: MicrophonePlugin
+        };
+    }
+
+    constructor(params, ws) {
+        this.params = params;
+        this.wavesurfer = ws;
+
+        this.active = false;
+        this.paused = false;
+        this.reloadBufferFunction = e => this.reloadBuffer(e);
+
+        // cross-browser getUserMedia
+        const promisifiedOldGUM = (constraints, successCallback, errorCallback) => {
+            // get ahold of getUserMedia, if present
+            const getUserMedia = (navigator.getUserMedia ||
+                navigator.webkitGetUserMedia ||
+                navigator.mozGetUserMedia ||
+                navigator.msGetUserMedia
+            );
+            // Some browsers just don't implement it - return a rejected
+            // promise with an error to keep a consistent interface
+            if (!getUserMedia) {
+                return Promise.reject(
+                    new Error('getUserMedia is not implemented in this browser')
+                );
+            }
+            // otherwise, wrap the call to the old navigator.getUserMedia with
+            // a Promise
+            return new Promise((successCallback, errorCallback) => {
+                getUserMedia.call(navigator, constraints, successCallback, errorCallback);
+            });
+        };
+        // Older browsers might not implement mediaDevices at all, so we set an
+        // empty object first
+        if (navigator.mediaDevices === undefined) {
+            navigator.mediaDevices = {};
+        }
+        // Some browsers partially implement mediaDevices. We can't just assign
+        // an object with getUserMedia as it would overwrite existing
+        // properties. Here, we will just add the getUserMedia property if it's
+        // missing.
+        if (navigator.mediaDevices.getUserMedia === undefined) {
+            navigator.mediaDevices.getUserMedia = promisifiedOldGUM;
+        }
+        this.constraints = this.params.constraints || {
+            video: false,
+            audio: true
+        };
+        this.bufferSize = this.params.bufferSize || 4096;
+        this.numberOfInputChannels = this.params.numberOfInputChannels || 1;
+        this.numberOfOutputChannels = this.params.numberOfOutputChannels || 1;
+
+        this._onBackendCreated = () => {
+            // wavesurfer's AudioContext where we'll route the mic signal to
+            this.micContext = this.wavesurfer.backend.getAudioContext();
+        };
+    }
+
+    init() {
+        this.wavesurfer.on('backend-created', this._onBackendCreated);
+        if (this.wavesurfer.backend) {
+            this._onBackendCreated();
+        }
+    }
+
+    /**
+     * Destroy the microphone plugin.
+     */
+    destroy() {
+        // make sure the buffer is not redrawn during
+        // cleanup and demolition of this plugin.
+        this.paused = true;
+
+        this.wavesurfer.un('backend-created', this._onBackendCreated);
+        this.stop();
+    }
+
+    /**
+    * Allow user to select audio input device, eg. microphone, and
+    * start the visualization.
+    */
+    start() {
+        navigator.mediaDevices.getUserMedia(this.constraints)
+            .then((data) => this.gotStream(data))
+            .catch((data) => this.deviceError(data));
+    }
+
+    /**
+    * Pause/resume visualization.
+    */
+    togglePlay() {
+        if (!this.active) {
+            // start it first
+            this.start();
+        } else {
+            // toggle paused
+            this.paused = !this.paused;
+
+            if (this.paused) {
+                this.pause();
+            } else {
+                this.play();
+            }
+        }
+    }
+
+    /**
+    * Play visualization.
+    */
+    play() {
+        this.paused = false;
+
+        this.connect();
+    }
+
+    /**
+    * Pause visualization.
+    */
+    pause() {
+        this.paused = true;
+
+        // disconnect sources so they can be used elsewhere
+        // (eg. during audio playback)
+        this.disconnect();
+    }
+
+    /**
+    * Stop the device stream and remove any remaining waveform drawing from
+    * the wavesurfer canvas.
+    */
+    stop() {
+        if (this.active) {
+            // stop visualization and device
+            this.stopDevice();
+
+            // empty last frame
+            this.wavesurfer.empty();
+        }
+    }
+
+    /**
+    * Stop the device and the visualization.
+    */
+    stopDevice() {
+        this.active = false;
+
+        // stop visualization
+        this.disconnect();
+
+        // stop stream from device
+        if (this.stream) {
+            const result = this.detectBrowser();
+            // MediaStream.stop is deprecated since:
+            // - Firefox 44 (https://www.fxsitecompat.com/en-US/docs/2015/mediastream-stop-has-been-deprecated/)
+            // - Chrome 45 (https://developers.google.com/web/updates/2015/07/mediastream-deprecations)
+            if ((result.browser === 'chrome' && result.version >= 45) ||
+                (result.browser === 'firefox' && result.version >= 44) ||
+                (result.browser === 'edge')) {
+                if (this.stream.getTracks) { // note that this should not be a call
+                    this.stream.getTracks().forEach(stream => stream.stop());
+                    return;
+                }
+            }
+
+            this.stream.stop();
+        }
+    }
+
+    /**
+    * Connect the media sources that feed the visualization.
+    */
+    connect() {
+        if (this.stream !== undefined) {
+            // Create an AudioNode from the stream.
+            this.mediaStreamSource = this.micContext.createMediaStreamSource(this.stream);
+
+            this.levelChecker = this.micContext.createScriptProcessor(
+                this.bufferSize,
+                this.numberOfInputChannels,
+                this.numberOfOutputChannels
+            );
+            this.mediaStreamSource.connect(this.levelChecker);
+
+            this.levelChecker.connect(this.micContext.destination);
+            this.levelChecker.onaudioprocess = this.reloadBufferFunction;
+        }
+    }
+
+    /**
+    * Disconnect the media sources that feed the visualization.
+    */
+    disconnect() {
+        if (this.mediaStreamSource !== undefined) {
+            this.mediaStreamSource.disconnect();
+        }
+
+        if (this.levelChecker !== undefined) {
+            this.levelChecker.disconnect();
+            this.levelChecker.onaudioprocess = undefined;
+        }
+    }
+
+    /**
+    * Redraw the waveform.
+    */
+    reloadBuffer(event) {
+        if (!this.paused) {
+            this.wavesurfer.empty();
+            this.wavesurfer.loadDecodedBuffer(event.inputBuffer);
+        }
+    }
+
+    /**
+    * Audio input device is ready.
+    *
+    * @param {LocalMediaStream} stream The microphone's media stream.
+    */
+    gotStream(stream) {
+        this.stream = stream;
+        this.active = true;
+
+        // start visualization
+        this.play();
+
+        // notify listeners
+        this.fireEvent('deviceReady', stream);
+    }
+
+    /**
+    * Device error callback.
+    */
+    deviceError(code) {
+        // notify listeners
+        this.fireEvent('deviceError', code);
+    }
+
+    /**
+    * Extract browser version out of the provided user agent string.
+    * @param {!string} uastring userAgent string.
+    * @param {!string} expr Regular expression used as match criteria.
+    * @param {!number} pos position in the version string to be returned.
+    * @return {!number} browser version.
+    */
+    extractVersion(uastring, expr, pos) {
+        const match = uastring.match(expr);
+        return match && match.length >= pos && parseInt(match[pos], 10);
+    }
+
+    /**
+    * Browser detector.
+    * @return {object} result containing browser, version and minVersion
+    *     properties.
+    */
+    detectBrowser() {
+        // Returned result object.
+        const result = {};
+        result.browser = null;
+        result.version = null;
+        result.minVersion = null;
+
+        // Non supported browser.
+        if (typeof window === 'undefined' || !window.navigator) {
+            result.browser = 'Not a supported browser.';
+            return result;
+        }
+
+        // Firefox.
+        if (navigator.mozGetUserMedia) {
+            result.browser = 'firefox';
+            result.version = this.extractVersion(navigator.userAgent, /Firefox\/([0-9]+)\./, 1);
+            result.minVersion = 31;
+            return result;
+        }
+
+        // Chrome/Chromium/Webview.
+        if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) {
+            result.browser = 'chrome';
+            result.version = this.extractVersion(navigator.userAgent, /Chrom(e|ium)\/([0-9]+)\./, 2);
+            result.minVersion = 38;
+            return result;
+        }
+
+        // Edge.
+        if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
+            result.browser = 'edge';
+            result.version = this.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2);
+            result.minVersion = 10547;
+            return result;
+        }
+
+        // Non supported browser default.
+        result.browser = 'Not a supported browser.';
+        return result;
+    }
+}

+ 326 - 0
libraries/wavesurfer/src/plugin/minimap.js

@@ -0,0 +1,326 @@
+/**
+ * @typedef {Object} MinimapPluginParams
+ * @desc Extends the `WavesurferParams` wavesurfer was initialised with
+ * @property {?string|HTMLElement} container CSS selector or HTML element where
+ * the ELAN information should be renderer. By default it is simply appended
+ * after the waveform.
+ * @property {?boolean} deferInit Set to true to manually call
+ * `initPlugin('minimap')`
+ */
+
+/**
+ * Renders a smaller version waveform as a minimap of the main waveform.
+ *
+ * @implements {PluginClass}
+ * @extends {Observer}
+ * @example
+ * // es6
+ * import MinimapPlugin from 'wavesurfer.minimap.js';
+ *
+ * // commonjs
+ * var MinimapPlugin = require('wavesurfer.minimap.js');
+ *
+ * // if you are using <script> tags
+ * var MinimapPlugin = window.WaveSurfer.minimap;
+ *
+ * // ... initialising wavesurfer with the plugin
+ * var wavesurfer = WaveSurfer.create({
+ *   // wavesurfer options ...
+ *   plugins: [
+ *     MinimapPlugin.create({
+ *       // plugin options ...
+ *     })
+ *   ]
+ * });
+ */
+export default class MinimapPlugin {
+    /**
+     * Minimap plugin definition factory
+     *
+     * This function must be used to create a plugin definition which can be
+     * used by wavesurfer to correctly instantiate the plugin.
+     *
+     * @param  {MinimapPluginParams} params parameters use to initialise the plugin
+     * @return {PluginDefinition} an object representing the plugin
+     */
+    static create(params) {
+        return {
+            name: 'minimap',
+            deferInit: params && params.deferInit ? params.deferInit : false,
+            params: params,
+            staticProps: {
+                initMinimap(customConfig) {
+                    console.warn('Deprecated initMinimap!');
+                    params = customConfig;
+                    this.initPlugins('minimap');
+                }
+            },
+            instance: MinimapPlugin
+        };
+    }
+
+    constructor(params, ws) {
+        this.params = ws.util.extend(
+            {}, ws.params, {
+                showRegions: false,
+                showOverview: false,
+                overviewBorderColor: 'green',
+                overviewBorderSize: 2,
+                // the container should be different
+                container: false,
+                height: Math.max(Math.round(ws.params.height / 4), 20)
+            }, params, {
+                scrollParent: false,
+                fillParent: true
+            }
+        );
+        // if container is a selector, get the element
+        if (typeof params.container === 'string') {
+            const el = document.querySelector(params.container);
+            if (!el) {
+                console.warn(`Wavesurfer minimap container ${params.container} was not found! The minimap will be automatically appended below the waveform.`);
+            }
+            this.params.container = el;
+        }
+        // if no container is specified add a new element and insert it
+        if (!params.container) {
+            this.params.container = ws.util.style(document.createElement('minimap'), {
+                display: 'block'
+            });
+        }
+        this.drawer = new (ws.Drawer)(this.params.container, this.params);
+        this.wavesurfer = ws;
+        this.util = ws.util;
+        /**
+         * Minimap needs to register to ready and waveform-ready events to
+         * work with MediaElement, the time when ready is called is different
+         * (peaks can not be got)
+         *
+         * @type {string}
+         * @see https://github.com/katspaugh/wavesurfer.js/issues/736
+         */
+        this.renderEvent = ws.params.backend === 'MediaElement' ? 'waveform-ready' : 'ready';
+        this.overviewRegion = null;
+
+        this.drawer.createWrapper();
+        this.createElements();
+        let isInitialised = false;
+
+        // ws ready event listener
+        this._onShouldRender = () => {
+            // only bind the events in the first run
+            if (!isInitialised) {
+                this.bindWavesurferEvents();
+                this.bindMinimapEvents();
+                isInitialised = true;
+            }
+            // if there is no such element, append it to the container (below
+            // the waveform)
+            if (!document.body.contains(this.params.container)) {
+                ws.container.insertBefore(this.params.container, null);
+            }
+
+            if (this.wavesurfer.regions && this.params.showRegions) {
+                this.regions();
+            }
+            this.render();
+        };
+
+        this._onAudioprocess = currentTime => {
+            this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
+        };
+
+        // ws seek event listener
+        this._onSeek = () => this.drawer.progress(ws.backend.getPlayedPercents());
+
+        // event listeners for the overview region
+        this._onScroll = e => {
+            if (!this.draggingOverview) {
+                this.moveOverviewRegion(e.target.scrollLeft / this.ratio);
+            }
+        };
+        this._onMouseover = e => {
+            if (this.draggingOverview) {
+                this.draggingOverview = false;
+            }
+        };
+        let prevWidth = 0;
+        this._onResize = ws.util.debounce(() => {
+            if (prevWidth != this.drawer.wrapper.clientWidth) {
+                prevWidth = this.drawer.wrapper.clientWidth;
+                this.render();
+                this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
+            }
+        });
+    }
+
+    init() {
+        if (this.wavesurfer.isReady) {
+            this._onShouldRender();
+        }
+        this.wavesurfer.on(this.renderEvent, this._onShouldRender);
+    }
+
+    destroy() {
+        window.removeEventListener('resize', this._onResize, true);
+        window.removeEventListener('orientationchange', this._onResize, true);
+        this.wavesurfer.drawer.wrapper.removeEventListener('mouseover', this._onMouseover);
+        this.wavesurfer.un(this.renderEvent, this._onShouldRender);
+        this.wavesurfer.un('seek', this._onSeek);
+        this.wavesurfer.un('scroll', this._onScroll);
+        this.wavesurfer.un('audioprocess', this._onAudioprocess);
+        this.drawer.destroy();
+        this.overviewRegion = null;
+        this.unAll();
+    }
+
+    regions() {
+        this.regions = {};
+
+        this.wavesurfer.on('region-created', region => {
+            this.regions[region.id] = region;
+            this.renderRegions();
+        });
+
+        this.wavesurfer.on('region-updated', region => {
+            this.regions[region.id] = region;
+            this.renderRegions();
+        });
+
+        this.wavesurfer.on('region-removed', region => {
+            delete this.regions[region.id];
+            this.renderRegions();
+        });
+    }
+
+    renderRegions() {
+        const regionElements = this.drawer.wrapper.querySelectorAll('region');
+        let i;
+        for (i = 0; i < regionElements.length; ++i) {
+            this.drawer.wrapper.removeChild(regionElements[i]);
+        }
+
+        Object.keys(this.regions).forEach(id => {
+            const region = this.regions[id];
+            const width = (this.drawer.width * ((region.end - region.start) / this.wavesurfer.getDuration()));
+            const left = (this.drawer.width * (region.start / this.wavesurfer.getDuration()));
+            const regionElement = this.util.style(document.createElement('region'), {
+                height: 'inherit',
+                backgroundColor: region.color,
+                width: width + 'px',
+                left: left + 'px',
+                display: 'block',
+                position: 'absolute'
+            });
+            regionElement.classList.add(id);
+            this.drawer.wrapper.appendChild(regionElement);
+        });
+    }
+
+    createElements() {
+        this.drawer.createElements();
+        if (this.params.showOverview) {
+            this.overviewRegion = this.util.style(document.createElement('overview'), {
+                height: (this.drawer.wrapper.offsetHeight - (this.params.overviewBorderSize * 2)) + 'px',
+                width: '0px',
+                display: 'block',
+                position: 'absolute',
+                cursor: 'move',
+                border: this.params.overviewBorderSize + 'px solid ' + this.params.overviewBorderColor,
+                zIndex: 2,
+                opacity: this.params.overviewOpacity
+            });
+            this.drawer.wrapper.appendChild(this.overviewRegion);
+        }
+    }
+
+    bindWavesurferEvents() {
+        window.addEventListener('resize', this._onResize, true);
+        window.addEventListener('orientationchange', this._onResize, true);
+        this.wavesurfer.on('audioprocess', this._onAudioprocess);
+        this.wavesurfer.on('seek', this._onSeek);
+        if (this.params.showOverview) {
+            this.wavesurfer.on('scroll', this._onScroll);
+            this.wavesurfer.drawer.wrapper.addEventListener('mouseover', this._onMouseover);
+        }
+    }
+
+    bindMinimapEvents() {
+        const positionMouseDown = {
+            clientX: 0,
+            clientY: 0
+        };
+        let relativePositionX = 0;
+        let seek = true;
+
+        // the following event listeners will be destroyed by using
+        // this.unAll() and nullifying the DOM node references after
+        // removing them
+        this.on('click', (e, position) => {
+            if (seek) {
+                this.progress(position);
+                this.wavesurfer.seekAndCenter(position);
+            } else {
+                seek = true;
+            }
+        });
+
+        if (this.params.showOverview) {
+            this.overviewRegion.addEventListener('mousedown', event => {
+                this.draggingOverview = true;
+                relativePositionX = event.layerX;
+                positionMouseDown.clientX = event.clientX;
+                positionMouseDown.clientY = event.clientY;
+            });
+
+            this.drawer.wrapper.addEventListener('mousemove', event => {
+                if (this.draggingOverview) {
+                    this.moveOverviewRegion(event.clientX - this.drawer.container.getBoundingClientRect().left - relativePositionX);
+                }
+            });
+
+            this.drawer.wrapper.addEventListener('mouseup', event => {
+                if (positionMouseDown.clientX - event.clientX === 0 && positionMouseDown.clientX - event.clientX === 0) {
+                    seek = true;
+                    this.draggingOverview = false;
+                } else if (this.draggingOverview) {
+                    seek = false;
+                    this.draggingOverview = false;
+                }
+            });
+        }
+    }
+
+    render() {
+        const len = this.drawer.getWidth();
+        const peaks = this.wavesurfer.backend.getPeaks(len, 0, len);
+        this.drawer.drawPeaks(peaks, len, 0, len);
+        this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
+
+        if (this.params.showOverview) {
+            //get proportional width of overview region considering the respective
+            //width of the drawers
+            this.ratio = this.wavesurfer.drawer.width / this.drawer.width;
+            this.waveShowedWidth = this.wavesurfer.drawer.width / this.ratio;
+            this.waveWidth = this.wavesurfer.drawer.width;
+            this.overviewWidth = (this.drawer.width / this.ratio);
+            this.overviewPosition = 0;
+            this.moveOverviewRegion(this.wavesurfer.drawer.wrapper.scrollLeft / this.ratio);
+            this.overviewRegion.style.width = (this.overviewWidth - (this.params.overviewBorderSize * 2)) + 'px';
+        }
+    }
+
+    moveOverviewRegion(pixels) {
+        if (pixels < 0) {
+            this.overviewPosition = 0;
+        } else if (pixels + this.overviewWidth < this.drawer.width) {
+            this.overviewPosition = pixels;
+        } else {
+            this.overviewPosition = (this.drawer.width - this.overviewWidth);
+        }
+        this.overviewRegion.style.left = this.overviewPosition + 'px';
+        if (this.draggingOverview) {
+            this.wavesurfer.drawer.wrapper.scrollLeft = this.overviewPosition * this.ratio;
+        }
+    }
+}

+ 622 - 0
libraries/wavesurfer/src/plugin/regions.js

@@ -0,0 +1,622 @@
+/**
+ * (Single) Region plugin class
+ *
+ * Must be turned into an observer before instantiating. This is done in
+ * RegionsPlugin (main plugin class)
+ *
+ * @extends {Observer}
+ */
+class Region {
+    constructor(params, ws) {
+        this.wavesurfer = ws;
+        this.wrapper = ws.drawer.wrapper;
+        this.style = ws.util.style;
+
+        this.id = params.id == null ? ws.util.getId() : params.id;
+        this.start = Number(params.start) || 0;
+        this.end = params.end == null ?
+            // small marker-like region
+            this.start + (4 / this.wrapper.scrollWidth) * this.wavesurfer.getDuration() :
+            Number(params.end);
+        this.resize = params.resize === undefined ? true : Boolean(params.resize);
+        this.drag = params.drag === undefined ? true : Boolean(params.drag);
+        this.loop = Boolean(params.loop);
+        this.color = params.color || 'rgba(0, 0, 0, 0.1)';
+        this.data = params.data || {};
+        this.attributes = params.attributes || {};
+
+        this.maxLength = params.maxLength;
+        this.minLength = params.minLength;
+
+        this.bindInOut();
+        this.render();
+        this.onZoom = this.updateRender.bind(this);
+        this.wavesurfer.on('zoom', this.onZoom);
+        this.wavesurfer.fireEvent('region-created', this);
+
+    }
+
+    /* Update region params. */
+    update(params) {
+        if (null != params.start) {
+            this.start = Number(params.start);
+        }
+        if (null != params.end) {
+            this.end = Number(params.end);
+        }
+        if (null != params.loop) {
+            this.loop = Boolean(params.loop);
+        }
+        if (null != params.color) {
+            this.color = params.color;
+        }
+        if (null != params.data) {
+            this.data = params.data;
+        }
+        if (null != params.resize) {
+            this.resize = Boolean(params.resize);
+        }
+        if (null != params.drag) {
+            this.drag = Boolean(params.drag);
+        }
+        if (null != params.maxLength) {
+            this.maxLength = Number(params.maxLength);
+        }
+        if (null != params.minLength) {
+            this.minLength = Number(params.minLength);
+        }
+        if (null != params.attributes) {
+            this.attributes = params.attributes;
+        }
+
+        this.updateRender();
+        this.fireEvent('update');
+        this.wavesurfer.fireEvent('region-updated', this);
+    }
+
+    /* Remove a single region. */
+    remove() {
+        if (this.element) {
+            this.wrapper.removeChild(this.element);
+            this.element = null;
+            this.fireEvent('remove');
+            this.wavesurfer.un('zoom', this.onZoom);
+            this.wavesurfer.fireEvent('region-removed', this);
+        }
+    }
+
+    /* Play the audio region. */
+    play() {
+        this.wavesurfer.play(this.start, this.end);
+        this.fireEvent('play');
+        this.wavesurfer.fireEvent('region-play', this);
+    }
+
+    /* Play the region in loop. */
+    playLoop() {
+        this.play();
+        this.once('out', () => this.playLoop());
+    }
+
+    /* Render a region as a DOM element. */
+    render() {
+        const regionEl = document.createElement('region');
+        regionEl.className = 'wavesurfer-region';
+        regionEl.title = this.formatTime(this.start, this.end);
+        regionEl.setAttribute('data-id', this.id);
+
+        for (const attrname in this.attributes) {
+            regionEl.setAttribute('data-region-' + attrname, this.attributes[attrname]);
+        }
+
+        const width = this.wrapper.scrollWidth;
+        this.style(regionEl, {
+            position: 'absolute',
+            zIndex: 2,
+            height: '100%',
+            top: '0px'
+        });
+
+        /* Resize handles */
+        if (this.resize) {
+            const handleLeft = regionEl.appendChild(document.createElement('handle'));
+            const handleRight = regionEl.appendChild(document.createElement('handle'));
+            handleLeft.className = 'wavesurfer-handle wavesurfer-handle-start';
+            handleRight.className = 'wavesurfer-handle wavesurfer-handle-end';
+            const css = {
+                cursor: 'col-resize',
+                position: 'absolute',
+                left: '0px',
+                top: '0px',
+                width: '1%',
+                maxWidth: '4px',
+                height: '100%'
+            };
+            this.style(handleLeft, css);
+            this.style(handleRight, css);
+            this.style(handleRight, {
+                left: '100%'
+            });
+        }
+
+        this.element = this.wrapper.appendChild(regionEl);
+        this.updateRender();
+        this.bindEvents(regionEl);
+    }
+
+    formatTime(start, end) {
+        return (start == end ? [start] : [start, end]).map(time => [
+            Math.floor((time % 3600) / 60), // minutes
+            ('00' + Math.floor(time % 60)).slice(-2) // seconds
+        ].join(':')).join('-');
+    }
+
+    getWidth() {
+        return this.wavesurfer.drawer.width / this.wavesurfer.params.pixelRatio;
+    }
+
+    /* Update element's position, width, color. */
+    updateRender() {
+        const dur = this.wavesurfer.getDuration();
+        const width = this.getWidth();
+
+        if (this.start < 0) {
+            this.start = 0;
+            this.end = this.end - this.start;
+        }
+        if (this.end > dur) {
+            this.end = dur;
+            this.start = dur - (this.end - this.start);
+        }
+
+        if (this.minLength != null) {
+            this.end = Math.max(this.start + this.minLength, this.end);
+        }
+
+        if (this.maxLength != null) {
+            this.end = Math.min(this.start + this.maxLength, this.end);
+        }
+
+        if (this.element != null) {
+            // Calculate the left and width values of the region such that
+            // no gaps appear between regions.
+            const left = Math.round(this.start / dur * width);
+            const regionWidth =
+                Math.round(this.end / dur * width) - left;
+
+            this.style(this.element, {
+                left: left + 'px',
+                width: regionWidth + 'px',
+                backgroundColor: this.color,
+                cursor: this.drag ? 'move' : 'default'
+            });
+
+            for (const attrname in this.attributes) {
+                this.element.setAttribute('data-region-' + attrname, this.attributes[attrname]);
+            }
+
+            this.element.title = this.formatTime(this.start, this.end);
+        }
+    }
+
+    /* Bind audio events. */
+    bindInOut() {
+        this.firedIn = false;
+        this.firedOut = false;
+
+        const onProcess = time => {
+            if (!this.firedOut && this.firedIn && (this.start >= Math.round(time * 100) / 100 || this.end <= Math.round(time * 100) / 100)) {
+                this.firedOut = true;
+                this.firedIn = false;
+                this.fireEvent('out');
+                this.wavesurfer.fireEvent('region-out', this);
+            }
+            if (!this.firedIn && this.start <= time && this.end > time) {
+                this.firedIn = true;
+                this.firedOut = false;
+                this.fireEvent('in');
+                this.wavesurfer.fireEvent('region-in', this);
+            }
+        };
+
+        this.wavesurfer.backend.on('audioprocess', onProcess);
+
+        this.on('remove', () => {
+            this.wavesurfer.backend.un('audioprocess', onProcess);
+        });
+
+        /* Loop playback. */
+        this.on('out', () => {
+            if (this.loop) {
+                this.wavesurfer.play(this.start);
+            }
+        });
+    }
+
+    /* Bind DOM events. */
+    bindEvents() {
+        this.element.addEventListener('mouseenter', e => {
+            this.fireEvent('mouseenter', e);
+            this.wavesurfer.fireEvent('region-mouseenter', this, e);
+        });
+
+        this.element.addEventListener('mouseleave', e => {
+            this.fireEvent('mouseleave', e);
+            this.wavesurfer.fireEvent('region-mouseleave', this, e);
+        });
+
+        this.element.addEventListener('click', e => {
+            e.preventDefault();
+            this.fireEvent('click', e);
+            this.wavesurfer.fireEvent('region-click', this, e);
+        });
+
+        this.element.addEventListener('dblclick', e => {
+            e.stopPropagation();
+            e.preventDefault();
+            this.fireEvent('dblclick', e);
+            this.wavesurfer.fireEvent('region-dblclick', this, e);
+        });
+
+        /* Drag or resize on mousemove. */
+        (this.drag || this.resize) && (() => {
+            const duration = this.wavesurfer.getDuration();
+            let startTime;
+            let touchId;
+            let drag;
+            let resize;
+
+            const onDown = e => {
+                if (e.touches && e.touches.length > 1) { return; }
+                touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
+
+                e.stopPropagation();
+                startTime = this.wavesurfer.drawer.handleEvent(e, true) * duration;
+
+                if (e.target.tagName.toLowerCase() == 'handle') {
+                    if (e.target.classList.contains('wavesurfer-handle-start')) {
+                        resize = 'start';
+                    } else {
+                        resize = 'end';
+                    }
+                } else {
+                    drag = true;
+                    resize = false;
+                }
+            };
+            const onUp = e => {
+                if (e.touches && e.touches.length > 1) { return; }
+
+                if (drag || resize) {
+                    drag = false;
+                    resize = false;
+
+                    this.fireEvent('update-end', e);
+                    this.wavesurfer.fireEvent('region-update-end', this, e);
+                }
+            };
+            const onMove = e => {
+                if (e.touches && e.touches.length > 1) { return; }
+                if (e.targetTouches && e.targetTouches[0].identifier != touchId) { return; }
+
+                if (drag || resize) {
+                    const time = this.wavesurfer.drawer.handleEvent(e) * duration;
+                    const delta = time - startTime;
+                    startTime = time;
+
+                    // Drag
+                    if (this.drag && drag) {
+                        this.onDrag(delta);
+                    }
+
+                    // Resize
+                    if (this.resize && resize) {
+                        this.onResize(delta, resize);
+                    }
+                }
+            };
+
+            this.element.addEventListener('mousedown', onDown);
+            this.element.addEventListener('touchstart', onDown);
+
+            this.wrapper.addEventListener('mousemove', onMove);
+            this.wrapper.addEventListener('touchmove', onMove);
+
+            document.body.addEventListener('mouseup', onUp);
+            document.body.addEventListener('touchend', onUp);
+
+            this.on('remove', () => {
+                document.body.removeEventListener('mouseup', onUp);
+                document.body.removeEventListener('touchend', onUp);
+                this.wrapper.removeEventListener('mousemove', onMove);
+                this.wrapper.removeEventListener('touchmove', onMove);
+            });
+
+            this.wavesurfer.on('destroy', () => {
+                document.body.removeEventListener('mouseup', onUp);
+                document.body.removeEventListener('touchend', onUp);
+            });
+        })();
+    }
+
+    onDrag(delta) {
+        const maxEnd = this.wavesurfer.getDuration();
+        if ((this.end + delta) > maxEnd || (this.start + delta) < 0) {
+            return;
+        }
+
+        this.update({
+            start: this.start + delta,
+            end: this.end + delta
+        });
+    }
+
+    onResize(delta, direction) {
+        if (direction == 'start') {
+            this.update({
+                start: Math.min(this.start + delta, this.end),
+                end: Math.max(this.start + delta, this.end)
+            });
+        } else {
+            this.update({
+                start: Math.min(this.end + delta, this.start),
+                end: Math.max(this.end + delta, this.start)
+            });
+        }
+    }
+}
+
+/**
+ * @typedef {Object} RegionsPluginParams
+ * @property {?boolean} dragSelection Enable creating regions by dragging wih
+ * the mouse
+ * @property {?RegionParams[]} regions Regions that should be added upon
+ * initialisation
+ * @property {number} slop=2 The sensitivity of the mouse dragging
+ * @property {?boolean} deferInit Set to true to manually call
+ * `initPlugin('regions')`
+ */
+
+/**
+ * @typedef {Object} RegionParams
+ * @desc The parameters used to describe a region.
+ * @example wavesurfer.addRegion(regionParams);
+ * @property {string} id=→random The id of the region
+ * @property {number} start=0 The start position of the region (in seconds).
+ * @property {number} end=0 The end position of the region (in seconds).
+ * @property {?boolean} loop Whether to loop the region when played back.
+ * @property {boolean} drag=true Allow/dissallow dragging the region.
+ * @property {boolean} resize=true Allow/dissallow resizing the region.
+ * @property {string} [color='rgba(0, 0, 0, 0.1)'] HTML color code.
+ */
+
+/**
+ * Regions are visual overlays on waveform that can be used to play and loop
+ * portions of audio. Regions can be dragged and resized.
+ *
+ * Visual customization is possible via CSS (using the selectors
+ * `.wavesurfer-region` and `.wavesurfer-handle`).
+ *
+ * @implements {PluginClass}
+ * @extends {Observer}
+ *
+ * @example
+ * // es6
+ * import RegionsPlugin from 'wavesurfer.regions.js';
+ *
+ * // commonjs
+ * var RegionsPlugin = require('wavesurfer.regions.js');
+ *
+ * // if you are using <script> tags
+ * var RegionsPlugin = window.WaveSurfer.regions;
+ *
+ * // ... initialising wavesurfer with the plugin
+ * var wavesurfer = WaveSurfer.create({
+ *   // wavesurfer options ...
+ *   plugins: [
+ *     RegionsPlugin.create({
+ *       // plugin options ...
+ *     })
+ *   ]
+ * });
+ */
+export default class RegionsPlugin {
+    /**
+     * Regions plugin definition factory
+     *
+     * This function must be used to create a plugin definition which can be
+     * used by wavesurfer to correctly instantiate the plugin.
+     *
+     * @param {RegionsPluginParams} params parameters use to initialise the plugin
+     * @return {PluginDefinition} an object representing the plugin
+     */
+    static create(params) {
+        return {
+            name: 'regions',
+            deferInit: params && params.deferInit ? params.deferInit : false,
+            params: params,
+            staticProps: {
+                initRegions() {
+                    console.warn('Deprecated initRegions! Use wavesurfer.initPlugins("regions") instead!');
+                    this.initPlugin('regions');
+                },
+
+                addRegion(options) {
+                    if (!this.initialisedPluginList.regions) {
+                        this.initPlugin('regions');
+                    }
+                    return this.regions.add(options);
+                },
+
+                clearRegions() {
+                    this.regions && this.regions.clear();
+                },
+
+                enableDragSelection(options) {
+                    if (!this.initialisedPluginList.regions) {
+                        this.initPlugin('regions');
+                    }
+                    this.regions.enableDragSelection(options);
+                },
+
+                disableDragSelection() {
+                    this.regions.disableDragSelection();
+                }
+            },
+            instance: RegionsPlugin
+        };
+    }
+
+    constructor(params, ws) {
+        this.params = params;
+        this.wavesurfer = ws;
+        this.util = ws.util;
+
+        // turn the plugin instance into an observer
+        const observerPrototypeKeys = Object.getOwnPropertyNames(this.util.Observer.prototype);
+        observerPrototypeKeys.forEach(key => {
+            Region.prototype[key] = this.util.Observer.prototype[key];
+        });
+        this.wavesurfer.Region = Region;
+
+        // Id-based hash of regions.
+        this.list = {};
+        this._onReady = () => {
+            this.wrapper = this.wavesurfer.drawer.wrapper;
+            if (this.params.regions) {
+                this.params.regions.forEach(region => {
+                    this.add(region);
+                });
+            }
+            if (this.params.dragSelection) {
+                this.enableDragSelection(this.params);
+            }
+        };
+    }
+
+    init() {
+        // Check if ws is ready
+        if (this.wavesurfer.isReady) {
+            this._onReady();
+        }
+        this.wavesurfer.on('ready', this._onReady);
+    }
+
+    destroy() {
+        this.wavesurfer.un('ready', this._onReady);
+        this.disableDragSelection();
+        this.clear();
+    }
+    /* Add a region. */
+    add(params) {
+        const region = new this.wavesurfer.Region(params, this.wavesurfer);
+
+        this.list[region.id] = region;
+
+        region.on('remove', () => {
+            delete this.list[region.id];
+        });
+
+        return region;
+    }
+
+    /* Remove all regions. */
+    clear() {
+        Object.keys(this.list).forEach(id => {
+            this.list[id].remove();
+        });
+    }
+
+    enableDragSelection(params) {
+        const slop = params.slop || 2;
+        let drag;
+        let start;
+        let region;
+        let touchId;
+        let pxMove = 0;
+
+        const eventDown = e => {
+            if (e.touches && e.touches.length > 1) { return; }
+            touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
+
+            drag = true;
+            start = this.wavesurfer.drawer.handleEvent(e, true);
+            region = null;
+        };
+        this.wrapper.addEventListener('mousedown', eventDown);
+        this.wrapper.addEventListener('touchstart', eventDown);
+        this.on('disable-drag-selection', () => {
+            this.wrapper.removeEventListener('touchstart', eventDown);
+            this.wrapper.removeEventListener('mousedown', eventDown);
+        });
+
+        const eventUp = e => {
+            if (e.touches && e.touches.length > 1) { return; }
+
+            drag = false;
+            pxMove = 0;
+
+            if (region) {
+                region.fireEvent('update-end', e);
+                this.wavesurfer.fireEvent('region-update-end', region, e);
+            }
+
+            region = null;
+        };
+        this.wrapper.addEventListener('mouseup', eventUp);
+        this.wrapper.addEventListener('touchend', eventUp);
+        this.on('disable-drag-selection', () => {
+            this.wrapper.removeEventListener('touchend', eventUp);
+            this.wrapper.removeEventListener('mouseup', eventUp);
+        });
+
+        const eventMove = e => {
+            if (!drag) { return; }
+            if (++pxMove <= slop) { return; }
+
+            if (e.touches && e.touches.length > 1) { return; }
+            if (e.targetTouches && e.targetTouches[0].identifier != touchId) { return; }
+
+            if (!region) {
+                region = this.add(params || {});
+            }
+
+            const duration = this.wavesurfer.getDuration();
+            const end = this.wavesurfer.drawer.handleEvent(e);
+            region.update({
+                start: Math.min(end * duration, start * duration),
+                end: Math.max(end * duration, start * duration)
+            });
+        };
+        this.wrapper.addEventListener('mousemove', eventMove);
+        this.wrapper.addEventListener('touchmove', eventMove);
+        this.on('disable-drag-selection', () => {
+            this.wrapper.removeEventListener('touchmove', eventMove);
+            this.wrapper.removeEventListener('mousemove', eventMove);
+        });
+    }
+
+    disableDragSelection() {
+        this.fireEvent('disable-drag-selection');
+    }
+
+    /* Get current region
+     *  The smallest region that contains the current time.
+     *  If several such regions exist, we take the first.
+     *  Return null if none exist. */
+    getCurrentRegion() {
+        const time = this.wavesurfer.getCurrentTime();
+        let min = null;
+        Object.keys(this.list).forEach(id => {
+            const cur = this.list[id];
+            if (cur.start <= time && cur.end >= time) {
+                if (!min || ((cur.end - cur.start) < (min.end - min.start))) {
+                    min = cur;
+                }
+            }
+        });
+
+        return min;
+    }
+
+}

+ 562 - 0
libraries/wavesurfer/src/plugin/spectrogram.js

@@ -0,0 +1,562 @@
+/**
+ * Calculate FFT - Based on https://github.com/corbanbrook/dsp.js
+ */
+/* eslint-disable complexity, no-redeclare, no-var, one-var */
+const FFT = function(bufferSize, sampleRate, windowFunc, alpha) {
+    this.bufferSize = bufferSize;
+    this.sampleRate = sampleRate;
+    this.bandwidth = 2 / bufferSize * sampleRate / 2;
+
+    this.sinTable = new Float32Array(bufferSize);
+    this.cosTable = new Float32Array(bufferSize);
+    this.windowValues = new Float32Array(bufferSize);
+    this.reverseTable = new Uint32Array(bufferSize);
+
+    this.peakBand = 0;
+    this.peak = 0;
+
+    switch (windowFunc) {
+        case 'bartlett' :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = 2 / (bufferSize - 1) * ((bufferSize - 1) / 2 - Math.abs(i - (bufferSize - 1) / 2));
+            }
+            break;
+        case 'bartlettHann' :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = 0.62 - 0.48 * Math.abs(i / (bufferSize - 1) - 0.5) - 0.38 * Math.cos(Math.PI * 2 * i / (bufferSize - 1));
+            }
+            break;
+        case 'blackman' :
+            alpha = alpha || 0.16;
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = (1 - alpha)/2 - 0.5 * Math.cos(Math.PI * 2 * i / (bufferSize - 1)) + alpha/2 * Math.cos(4 * Math.PI * i / (bufferSize - 1));
+            }
+            break;
+        case 'cosine' :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = Math.cos(Math.PI * i / (bufferSize - 1) - Math.PI / 2);
+            }
+            break;
+        case 'gauss' :
+            alpha = alpha || 0.25;
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = Math.pow(Math.E, -0.5 * Math.pow((i - (bufferSize - 1) / 2) / (alpha * (bufferSize - 1) / 2), 2));
+            }
+            break;
+        case 'hamming' :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = 0.54 - 0.46 * Math.cos(Math.PI * 2 * i / (bufferSize - 1));
+            }
+            break;
+        case 'hann' :
+        case undefined :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = 0.5 * (1 - Math.cos(Math.PI * 2 * i / (bufferSize - 1)));
+            }
+            break;
+        case 'lanczoz' :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = Math.sin(Math.PI * (2 * i / (bufferSize - 1) - 1)) / (Math.PI * (2 * i / (bufferSize - 1) - 1));
+            }
+            break;
+        case 'rectangular' :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = 1;
+            }
+            break;
+        case 'triangular' :
+            for (var i = 0; i<bufferSize; i++) {
+                this.windowValues[i] = 2 / bufferSize * (bufferSize / 2 - Math.abs(i - (bufferSize - 1) / 2));
+            }
+            break;
+        default:
+            throw Error('No such window function \'' + windowFunc + '\'');
+    }
+
+    var limit = 1;
+    var bit = bufferSize >> 1;
+
+    var i;
+
+    while (limit < bufferSize) {
+        for (i = 0; i < limit; i++) {
+            this.reverseTable[i + limit] = this.reverseTable[i] + bit;
+        }
+
+        limit = limit << 1;
+        bit = bit >> 1;
+    }
+
+    for (i = 0; i < bufferSize; i++) {
+        this.sinTable[i] = Math.sin(-Math.PI/i);
+        this.cosTable[i] = Math.cos(-Math.PI/i);
+    }
+
+
+    this.calculateSpectrum = function(buffer) {
+        // Locally scope variables for speed up
+        var bufferSize = this.bufferSize,
+            cosTable = this.cosTable,
+            sinTable = this.sinTable,
+            reverseTable = this.reverseTable,
+            real = new Float32Array(bufferSize),
+            imag = new Float32Array(bufferSize),
+            bSi = 2 / this.bufferSize,
+            sqrt = Math.sqrt,
+            rval,
+            ival,
+            mag,
+            spectrum = new Float32Array(bufferSize / 2);
+
+        var k = Math.floor(Math.log(bufferSize) / Math.LN2);
+
+        if (Math.pow(2, k) !== bufferSize) {
+            throw 'Invalid buffer size, must be a power of 2.';
+        }
+        if (bufferSize !== buffer.length) {
+            throw 'Supplied buffer is not the same size as defined FFT. FFT Size: ' + bufferSize + ' Buffer Size: ' + buffer.length;
+        }
+
+        var halfSize = 1,
+            phaseShiftStepReal,
+            phaseShiftStepImag,
+            currentPhaseShiftReal,
+            currentPhaseShiftImag,
+            off,
+            tr,
+            ti,
+            tmpReal;
+
+        for (var i = 0; i < bufferSize; i++) {
+            real[i] = buffer[reverseTable[i]] * this.windowValues[reverseTable[i]];
+            imag[i] = 0;
+        }
+
+        while (halfSize < bufferSize) {
+            phaseShiftStepReal = cosTable[halfSize];
+            phaseShiftStepImag = sinTable[halfSize];
+
+            currentPhaseShiftReal = 1;
+            currentPhaseShiftImag = 0;
+
+            for (var fftStep = 0; fftStep < halfSize; fftStep++) {
+                var i = fftStep;
+
+                while (i < bufferSize) {
+                    off = i + halfSize;
+                    tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]);
+                    ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]);
+
+                    real[off] = real[i] - tr;
+                    imag[off] = imag[i] - ti;
+                    real[i] += tr;
+                    imag[i] += ti;
+
+                    i += halfSize << 1;
+                }
+
+                tmpReal = currentPhaseShiftReal;
+                currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag);
+                currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal);
+            }
+
+            halfSize = halfSize << 1;
+        }
+
+        for (var i = 0, N = bufferSize / 2; i < N; i++) {
+            rval = real[i];
+            ival = imag[i];
+            mag = bSi * sqrt(rval * rval + ival * ival);
+
+            if (mag > this.peak) {
+                this.peakBand = i;
+                this.peak = mag;
+            }
+            spectrum[i] = mag;
+        }
+        return spectrum;
+    };
+};
+/* eslint-enable complexity, no-redeclare, no-var, one-var */
+
+/**
+ * @typedef {Object} SpectrogramPluginParams
+ * @property {string|HTMLElement} container Selector of element or element in
+ * which to render
+ * @property {number} fftSamples=512 number of samples to fetch to FFT. Must be
+ * a pwer of 2.
+ * @property {number} noverlap Size of the overlapping window. Must be <
+ * fftSamples. Auto deduced from canvas size by default.
+ * @property {string} windowFunc='hann' The window function to be used. One of
+ * these: `'bartlett'`, `'bartlettHann'`, `'blackman'`, `'cosine'`, `'gauss'`,
+ * `'hamming'`, `'hann'`, `'lanczoz'`, `'rectangular'`, `'triangular'`
+ * @property {?number} alpha Some window functions have this extra value.
+ * (Between 0 and 1)
+ * @property {number} pixelRatio=wavesurfer.params.pixelRatio to control the
+ * size of the spectrogram in relation with its canvas. 1 = Draw on the whole
+ * canvas. 2 = Draw on a quarter (1/2 the length and 1/2 the width)
+ * @property {?boolean} deferInit Set to true to manually call
+ * `initPlugin('spectrogram')`
+ */
+
+/**
+ * Render a spectrogram visualisation of the audio.
+ *
+ * @implements {PluginClass}
+ * @extends {Observer}
+ * @example
+ * // es6
+ * import SpectrogramPlugin from 'wavesurfer.spectrogram.js';
+ *
+ * // commonjs
+ * var SpectrogramPlugin = require('wavesurfer.spectrogram.js');
+ *
+ * // if you are using <script> tags
+ * var SpectrogramPlugin = window.WaveSurfer.spectrogram;
+ *
+ * // ... initialising wavesurfer with the plugin
+ * var wavesurfer = WaveSurfer.create({
+ *   // wavesurfer options ...
+ *   plugins: [
+ *     SpectrogramPlugin.create({
+ *       // plugin options ...
+ *     })
+ *   ]
+ * });
+ */
+export default class SpectrogramPlugin {
+    /**
+     * Spectrogram plugin definition factory
+     *
+     * This function must be used to create a plugin definition which can be
+     * used by wavesurfer to correctly instantiate the plugin.
+     *
+     * @param  {SpectrogramPluginParams} params parameters use to initialise the plugin
+     * @return {PluginDefinition} an object representing the plugin
+     */
+    static create(params) {
+        return {
+            name: 'spectrogram',
+            deferInit: params && params.deferInit ? params.deferInit : false,
+            params: params,
+            staticProps: {
+                FFT: FFT
+            },
+            instance: SpectrogramPlugin
+        };
+    }
+
+    constructor(params, ws) {
+        this.params = params;
+        this.wavesurfer = ws;
+        this.util = ws.util;
+
+        this.frequenciesDataUrl = params.frequenciesDataUrl;
+        this._onScroll = e => {
+            this.updateScroll(e);
+        };
+        this._onReady = () => {
+            const drawer = this.drawer = ws.drawer;
+
+            this.container = 'string' == typeof params.container ?
+                document.querySelector(params.container) : params.container;
+
+            if (!this.container) {
+                throw Error('No container for WaveSurfer spectrogram');
+            }
+
+            this.width = drawer.width;
+            this.pixelRatio = this.params.pixelRatio || ws.params.pixelRatio;
+            this.fftSamples = this.params.fftSamples || ws.params.fftSamples || 512;
+            this.height = this.fftSamples / 2;
+            this.noverlap = params.noverlap;
+            this.windowFunc = params.windowFunc;
+            this.alpha = params.alpha;
+
+            this.createWrapper();
+            this.createCanvas();
+            this.render();
+
+            drawer.wrapper.addEventListener('scroll', this._onScroll);
+            ws.on('redraw', () => this.render());
+        };
+    }
+
+    init() {
+        // Check if ws is ready
+        if (this.wavesurfer.isReady) {
+            this._onReady();
+        }
+
+        this.wavesurfer.on('ready', this._onReady);
+    }
+
+    destroy() {
+        this.unAll();
+        this.wavesurfer.un('ready', this._onReady);
+        this.drawer.wrapper.removeEventListener('scroll', this._onScroll);
+        this.wavesurfer = null;
+        this.util = null;
+        this.params = null;
+        if (this.wrapper) {
+            this.wrapper.parentNode.removeChild(this.wrapper);
+            this.wrapper = null;
+        }
+    }
+
+    createWrapper() {
+        const prevSpectrogram = this.container.querySelector('spectrogram');
+        if (prevSpectrogram) {
+            this.container.removeChild(prevSpectrogram);
+        }
+        const wsParams = this.wavesurfer.params;
+        this.wrapper = document.createElement('spectrogram');
+        // if labels are active
+        if (this.params.labels) {
+            const labelsEl = this.labelsEl = document.createElement('canvas');
+            labelsEl.classList.add('spec-labels');
+            this.drawer.style(labelsEl, {
+                left: 0,
+                position: 'absolute',
+                zIndex: 9,
+                height: `${this.height / this.pixelRatio}px`,
+                width: `${55 / this.pixelRatio}px`
+            });
+            this.wrapper.appendChild(labelsEl);
+            // can be customized in next version
+            this.loadLabels('rgba(68,68,68,0.5)', '12px', '10px', '', '#fff', '#f7f7f7', 'center', '#specLabels');
+        }
+
+        this.drawer.style(this.wrapper, {
+            display: 'block',
+            position: 'relative',
+            userSelect: 'none',
+            webkitUserSelect: 'none',
+            height: this.height + 'px'
+        });
+
+        if (wsParams.fillParent || wsParams.scrollParent) {
+            this.drawer.style(this.wrapper, {
+                width: '100%',
+                overflowX: 'hidden',
+                overflowY: 'hidden'
+            });
+        }
+        this.container.appendChild(this.wrapper);
+
+        this.wrapper.addEventListener('click', e => {
+            e.preventDefault();
+            const relX = 'offsetX' in e ? e.offsetX : e.layerX;
+            this.fireEvent('click', (relX / this.scrollWidth) || 0);
+        });
+    }
+
+    createCanvas() {
+        const canvas = this.canvas = this.wrapper.appendChild(
+            document.createElement('canvas')
+        );
+
+        this.spectrCc = canvas.getContext('2d');
+
+        this.util.style(canvas, {
+            position: 'absolute',
+            zIndex: 4
+        });
+    }
+
+    render() {
+        this.updateCanvasStyle();
+
+        if (this.frequenciesDataUrl) {
+            this.loadFrequenciesData(this.frequenciesDataUrl);
+        } else {
+            this.getFrequencies(this.drawSpectrogram);
+        }
+    }
+
+    updateCanvasStyle() {
+        const width = Math.round(this.width / this.pixelRatio) + 'px';
+        this.canvas.width = this.width;
+        this.canvas.height = this.height;
+        this.canvas.style.width = width;
+    }
+
+    drawSpectrogram(frequenciesData, my) {
+        const spectrCc = my.spectrCc;
+        const length = my.wavesurfer.backend.getDuration();
+        const height = my.height;
+        const pixels = my.resample(frequenciesData);
+        const heightFactor = my.buffer ? 2 / my.buffer.numberOfChannels : 1;
+        let i;
+        let j;
+
+        for (i = 0; i < pixels.length; i++) {
+            for (j = 0; j < pixels[i].length; j++) {
+                const colorValue = 255 - pixels[i][j];
+                my.spectrCc.fillStyle = 'rgb(' + colorValue + ', ' + colorValue + ', ' + colorValue + ')';
+                my.spectrCc.fillRect(i, height - j * heightFactor, 1, heightFactor);
+            }
+        }
+    }
+
+    getFrequencies(callback) {
+        const fftSamples = this.fftSamples;
+        const buffer = this.buffer = this.wavesurfer.backend.buffer;
+        const channelOne = buffer.getChannelData(0);
+        const bufferLength = buffer.length;
+        const sampleRate = buffer.sampleRate;
+        const frequencies = [];
+
+        if (!buffer) {
+            this.fireEvent('error', 'Web Audio buffer is not available');
+            return;
+        }
+
+        let noverlap = this.noverlap;
+        if (!noverlap) {
+            const uniqueSamplesPerPx = buffer.length / this.canvas.width;
+            noverlap = Math.max(0, Math.round(fftSamples - uniqueSamplesPerPx));
+        }
+
+        const fft = new FFT(fftSamples, sampleRate, this.windowFunc, this.alpha);
+        const maxSlicesCount = Math.floor(bufferLength / (fftSamples - noverlap));
+        let currentOffset = 0;
+
+        while (currentOffset + fftSamples < channelOne.length) {
+            const segment = channelOne.slice(currentOffset, currentOffset + fftSamples);
+            const spectrum = fft.calculateSpectrum(segment);
+            const array = new Uint8Array(fftSamples / 2);
+            let j;
+            for (j = 0; j < fftSamples / 2; j++) {
+                array[j] = Math.max(-255, Math.log10(spectrum[j]) * 45);
+            }
+            frequencies.push(array);
+            currentOffset += (fftSamples - noverlap);
+        }
+        callback(frequencies, this);
+    }
+
+    loadFrequenciesData(url) {
+        const ajax = this.util.ajax({ url: url });
+
+        ajax.on('success', data => this.drawSpectrogram(JSON.parse(data), this));
+        ajax.on('error', e => this.fireEvent('error', 'XHR error: ' + e.target.statusText));
+
+        return ajax;
+    }
+
+    freqType(freq) {
+        return (freq >= 1000 ? (freq / 1000).toFixed(1) : Math.round(freq));
+    }
+
+    unitType(freq) {
+        return (freq >= 1000 ? 'KHz' : 'Hz');
+    }
+
+    loadLabels(bgFill, fontSizeFreq, fontSizeUnit, fontType, textColorFreq, textColorUnit, textAlign, container) {
+        const frequenciesHeight = this.height;
+        bgFill = bgFill || 'rgba(68,68,68,0)';
+        fontSizeFreq = fontSizeFreq || '12px';
+        fontSizeUnit = fontSizeUnit || '10px';
+        fontType = fontType || 'Helvetica';
+        textColorFreq = textColorFreq || '#fff';
+        textColorUnit = textColorUnit || '#fff';
+        textAlign = textAlign || 'center';
+        container = container || '#specLabels';
+        const getMaxY = frequenciesHeight || 512;
+        const labelIndex = 5 * (getMaxY / 256);
+        const freqStart = 0;
+        const step = ((this.wavesurfer.backend.ac.sampleRate / 2) - freqStart) / labelIndex;
+
+        const ctx = this.labelsEl.getContext('2d');
+        this.labelsEl.height = this.height;
+        this.labelsEl.width = 55;
+
+        ctx.fillStyle = bgFill;
+        ctx.fillRect(0, 0, 55, getMaxY);
+        ctx.fill();
+        let i;
+
+        for (i = 0; i <= labelIndex; i++) {
+            ctx.textAlign = textAlign;
+            ctx.textBaseline = 'middle';
+
+            const freq = freqStart + (step * i);
+            const index = Math.round(freq / (this.sampleRate / 2) * this.fftSamples);
+            const label = this.freqType(freq);
+            const units = this.unitType(freq);
+            const x = 16;
+            const yLabelOffset = 2;
+
+            if (i == 0) {
+                ctx.fillStyle = textColorUnit;
+                ctx.font = fontSizeUnit + ' ' + fontType;
+                ctx.fillText(units, x + 24, getMaxY + i - 10);
+                ctx.fillStyle = textColorFreq;
+                ctx.font = fontSizeFreq + ' ' + fontType;
+                ctx.fillText(label, x, getMaxY + i - 10);
+            } else {
+                ctx.fillStyle = textColorUnit;
+                ctx.font = fontSizeUnit + ' ' + fontType;
+                ctx.fillText(units, x + 24, getMaxY - i * 50 + yLabelOffset);
+                ctx.fillStyle = textColorFreq;
+                ctx.font = fontSizeFreq + ' ' + fontType;
+                ctx.fillText(label, x, getMaxY - i * 50 + yLabelOffset);
+            }
+        }
+    }
+
+    updateScroll(e) {
+        if (this.wrapper) {
+            this.wrapper.scrollLeft = e.target.scrollLeft;
+        }
+    }
+
+    resample(oldMatrix) {
+        const columnsNumber = this.width;
+        const newMatrix = [];
+
+        const oldPiece = 1 / oldMatrix.length;
+        const newPiece = 1 / columnsNumber;
+        let i;
+
+        for (i = 0; i < columnsNumber; i++) {
+            const column = new Array(oldMatrix[0].length);
+            let j;
+
+            for (j = 0; j < oldMatrix.length; j++) {
+                const oldStart = j * oldPiece;
+                const oldEnd = oldStart + oldPiece;
+                const newStart = i * newPiece;
+                const newEnd = newStart + newPiece;
+
+                const overlap = (oldEnd <= newStart || newEnd <= oldStart) ?
+                    0 :
+                    Math.min(Math.max(oldEnd, newStart), Math.max(newEnd, oldStart)) -
+                    Math.max(Math.min(oldEnd, newStart), Math.min(newEnd, oldStart));
+                let k;
+                /* eslint-disable max-depth */
+                if (overlap > 0) {
+                    for (k = 0; k < oldMatrix[0].length; k++) {
+                        if (column[k] == null) {
+                            column[k] = 0;
+                        }
+                        column[k] += (overlap / newPiece) * oldMatrix[j][k];
+                    }
+                }
+                /* eslint-enable max-depth */
+            }
+
+            const intColumn = new Uint8Array(oldMatrix[0].length);
+            let m;
+
+            for (m = 0; m < oldMatrix[0].length; m++) {
+                intColumn[m] = column[m];
+            }
+
+            newMatrix.push(intColumn);
+        }
+
+        return newMatrix;
+    }
+}

+ 358 - 0
libraries/wavesurfer/src/plugin/timeline.js

@@ -0,0 +1,358 @@
+/**
+ * @typedef {Object} TimelinePluginParams
+ * @desc Extends the `WavesurferParams` wavesurfer was initialised with
+ * @property {!string|HTMLElement} container CSS selector or HTML element where
+ * the timeline should be drawn. This is the only required parameter.
+ * @property {number} notchPercentHeight=90 Height of notches in percent
+ * @property {string} primaryColor='#000' The colour of the main notches
+ * @property {string} secondaryColor='#c0c0c0' The colour of the secondary
+ * notches
+ * @property {string} primaryFontColor='#000' The colour of the labels next to
+ * the main notches
+ * @property {string} secondaryFontColor='#000' The colour of the labels next to
+ * the secondary notches
+ * @property {string} fontFamily='Arial'
+ * @property {number} fontSize=10 Font size of labels in pixels
+ * @property {function} formatTimeCallback=→00:00
+ * @property {?boolean} deferInit Set to true to manually call
+ * `initPlugin('timeline')`
+ */
+
+/**
+ * Adds a timeline to the waveform.
+ *
+ * @implements {PluginClass}
+ * @extends {Observer}
+ * @example
+ * // es6
+ * import TimelinePlugin from 'wavesurfer.timeline.js';
+ *
+ * // commonjs
+ * var TimelinePlugin = require('wavesurfer.timeline.js');
+ *
+ * // if you are using <script> tags
+ * var TimelinePlugin = window.WaveSurfer.timeline;
+ *
+ * // ... initialising wavesurfer with the plugin
+ * var wavesurfer = WaveSurfer.create({
+ *   // wavesurfer options ...
+ *   plugins: [
+ *     TimelinePlugin.create({
+ *       // plugin options ...
+ *     })
+ *   ]
+ * });
+ */
+export default class TimelinePlugin {
+    /**
+     * Timeline plugin definition factory
+     *
+     * This function must be used to create a plugin definition which can be
+     * used by wavesurfer to correctly instantiate the plugin.
+     *
+     * @param  {TimelinePluginParams} params parameters use to initialise the plugin
+     * @return {PluginDefinition} an object representing the plugin
+     */
+    static create(params) {
+        return {
+            name: 'timeline',
+            deferInit: params && params.deferInit ? params.deferInit : false,
+            params: params,
+            instance: TimelinePlugin
+        };
+    }
+
+    constructor(params, ws) {
+        this.container = 'string' == typeof params.container
+            ? document.querySelector(params.container)
+            : params.container;
+
+        if (!this.container) {
+            throw new Error('No container for wavesurfer timeline');
+        }
+        this.wavesurfer = ws;
+        this.util = ws.util;
+        this.params = this.util.extend({}, {
+            height: 20,
+            notchPercentHeight: 90,
+            primaryColor: '#000',
+            secondaryColor: '#c0c0c0',
+            primaryFontColor: '#000',
+            secondaryFontColor: '#000',
+            fontFamily: 'Arial',
+            fontSize: 10,
+            formatTimeCallback(seconds) {
+                if (seconds / 60 > 1) {
+                    // calculate minutes and seconds from seconds count
+                    const minutes = parseInt(seconds / 60, 10);
+                    seconds = parseInt(seconds % 60, 10);
+                    // fill up seconds with zeroes
+                    seconds = (seconds < 10) ? '0' + seconds : seconds;
+                    return `${minutes}:${seconds}`;
+                }
+                return Math.round(seconds * 1000) / 1000;
+            },
+            timeInterval(pxPerSec) {
+                if (pxPerSec >= 25) {
+                    return 1;
+                } else if (pxPerSec * 5 >= 25) {
+                    return 5;
+                } else if (pxPerSec * 15 >= 25) {
+                    return 15;
+                }
+                return Math.ceil(0.5 / pxPerSec) * 60;
+            },
+            primaryLabelInterval(pxPerSec) {
+                if (pxPerSec >= 25) {
+                    return 10;
+                } else if (pxPerSec * 5 >= 25) {
+                    return 6;
+                } else if (pxPerSec * 15 >= 25) {
+                    return 4;
+                }
+                return 4;
+            },
+            secondaryLabelInterval(pxPerSec) {
+                if (pxPerSec >= 25) {
+                    return 5;
+                } else if (pxPerSec * 5 >= 25) {
+                    return 2;
+                } else if (pxPerSec * 15 >= 25) {
+                    return 2;
+                }
+                return 2;
+            }
+        }, params);
+
+        this.canvases = [];
+
+        this._onZoom = () => this.render();
+        this._onScroll = () => {
+            if (this.wrapper && this.drawer.wrapper) {
+                this.wrapper.scrollLeft = this.drawer.wrapper.scrollLeft;
+            }
+        };
+        this._onRedraw = () => this.render();
+        this._onReady = () => {
+            this.drawer = ws.drawer;
+            this.pixelRatio = ws.drawer.params.pixelRatio;
+            this.maxCanvasWidth = ws.drawer.maxCanvasWidth || ws.drawer.width;
+            this.maxCanvasElementWidth = ws.drawer.maxCanvasElementWidth || Math.round(this.maxCanvasWidth / this.pixelRatio);
+
+            this.createWrapper();
+            this.render();
+            ws.drawer.wrapper.addEventListener('scroll', this._onScroll);
+            ws.on('redraw', this._onRedraw);
+            ws.on('zoom', this._onZoom);
+        };
+    }
+
+    init() {
+        this.wavesurfer.on('ready', this._onReady);
+        // Check if ws is ready
+        if (this.wavesurfer.isReady) {
+            this._onReady();
+        }
+    }
+
+    destroy() {
+        this.unAll();
+        this.wavesurfer.un('redraw', this._onRedraw);
+        this.wavesurfer.un('zoom', this._onZoom);
+        this.wavesurfer.un('ready', this._onReady);
+        this.wavesurfer.drawer.wrapper.removeEventListener('scroll', this._onScroll);
+        if (this.wrapper && this.wrapper.parentNode) {
+            this.wrapper.parentNode.removeChild(this.wrapper);
+            this.wrapper = null;
+        }
+    }
+
+    createWrapper() {
+        const wsParams = this.wavesurfer.params;
+        this.wrapper = this.container.appendChild(
+            document.createElement('timeline')
+        );
+        this.util.style(this.wrapper, {
+            display: 'block',
+            position: 'relative',
+            userSelect: 'none',
+            webkitUserSelect: 'none',
+            height: `${this.params.height}px`
+        });
+
+        if (wsParams.fillParent || wsParams.scrollParent) {
+            this.util.style(this.wrapper, {
+                width: '100%',
+                overflowX: 'hidden',
+                overflowY: 'hidden'
+            });
+        }
+
+        this._onClick = e => {
+            e.preventDefault();
+            const relX = 'offsetX' in e ? e.offsetX : e.layerX;
+            this.fireEvent('click', (relX / this.wrapper.scrollWidth) || 0);
+        };
+        this.wrapper.addEventListener('click', this._onClick);
+    }
+
+    removeOldCanvases() {
+        while (this.canvases.length > 0) {
+            const canvas = this.canvases.pop();
+            canvas.parentElement.removeChild(canvas);
+        }
+    }
+
+    createCanvases() {
+        this.removeOldCanvases();
+
+        const totalWidth = Math.round(this.drawer.wrapper.scrollWidth);
+        const requiredCanvases = Math.ceil(totalWidth / this.maxCanvasElementWidth);
+        let i;
+
+        for (i = 0; i < requiredCanvases; i++) {
+            const canvas = this.wrapper.appendChild(document.createElement('canvas'));
+            this.canvases.push(canvas);
+            this.util.style(canvas, {
+                position: 'absolute',
+                zIndex: 4
+            });
+        }
+    }
+
+    render() {
+        this.createCanvases();
+        this.updateCanvasStyle();
+        this.drawTimeCanvases();
+    }
+
+    updateCanvasStyle() {
+        const requiredCanvases = this.canvases.length;
+        let i;
+        for (i = 0; i < requiredCanvases; i++) {
+            const canvas = this.canvases[i];
+            let canvasWidth = this.maxCanvasElementWidth;
+
+            if (i === requiredCanvases - 1) {
+                canvasWidth = this.drawer.wrapper.scrollWidth - (this.maxCanvasElementWidth * (requiredCanvases - 1));
+            }
+
+            canvas.width = canvasWidth * this.pixelRatio;
+            canvas.height = this.params.height * this.pixelRatio;
+            this.util.style(canvas, {
+                width: `${canvasWidth}px`,
+                height: `${this.params.height}px`,
+                left: `${i * this.maxCanvasElementWidth}px`
+            });
+        }
+    }
+
+    drawTimeCanvases() {
+        const backend = this.wavesurfer.backend;
+        const wsParams = this.wavesurfer.params;
+        const duration = this.wavesurfer.backend.getDuration();
+        const totalSeconds = parseInt(duration, 10) + 1;
+        const width = wsParams.fillParent && !wsParams.scrollParent
+            ? this.drawer.getWidth()
+            : this.drawer.wrapper.scrollWidth * wsParams.pixelRatio;
+        const pixelsPerSecond = width / duration;
+
+        const formatTime = this.params.formatTimeCallback;
+        // if parameter is function, call the function with
+        // pixelsPerSecond, otherwise simply take the value as-is
+        const intervalFnOrVal = option => (typeof option === 'function' ? option(pixelsPerSecond) : option);
+        const timeInterval = intervalFnOrVal(this.params.timeInterval);
+        const primaryLabelInterval = intervalFnOrVal(this.params.primaryLabelInterval);
+        const secondaryLabelInterval = intervalFnOrVal(this.params.secondaryLabelInterval);
+
+        let curPixel = 0;
+        let curSeconds = 0;
+
+        if (duration <= 0) {
+            return;
+        }
+
+        const height1 = this.params.height - 4;
+        const height2 = (this.params.height * (this.params.notchPercentHeight / 100)) - 4;
+        const fontSize = this.params.fontSize * wsParams.pixelRatio;
+        let i;
+
+        for (i = 0; i < totalSeconds / timeInterval; i++) {
+            if (i % primaryLabelInterval == 0) {
+                this.setFillStyles(this.params.primaryColor);
+                this.fillRect(curPixel, 0, 1, height1);
+                this.setFonts(`${fontSize}px ${this.params.fontFamily}`);
+                this.setFillStyles(this.params.primaryFontColor);
+                this.fillText(formatTime(curSeconds), curPixel + 5, height1);
+            } else if (i % secondaryLabelInterval == 0) {
+                this.setFillStyles(this.params.secondaryColor);
+                this.fillRect(curPixel, 0, 1, height1);
+                this.setFonts(`${fontSize}px ${this.params.fontFamily}`);
+                this.setFillStyles(this.params.secondaryFontColor);
+                this.fillText(formatTime(curSeconds), curPixel + 5, height1);
+            } else {
+                this.setFillStyles(this.params.secondaryColor);
+                this.fillRect(curPixel, 0, 1, height2);
+            }
+
+            curSeconds += timeInterval;
+            curPixel += pixelsPerSecond * timeInterval;
+        }
+    }
+
+    setFillStyles(fillStyle) {
+        this.canvases.forEach(canvas => {
+            canvas.getContext('2d').fillStyle = fillStyle;
+        });
+    }
+
+    setFonts(font) {
+        this.canvases.forEach(canvas => {
+            canvas.getContext('2d').font = font;
+        });
+    }
+
+    fillRect(x, y, width, height) {
+        this.canvases.forEach((canvas, i) => {
+            const leftOffset = i * this.maxCanvasWidth;
+
+            const intersection = {
+                x1: Math.max(x, i * this.maxCanvasWidth),
+                y1: y,
+                x2: Math.min(x + width, i * this.maxCanvasWidth + canvas.width),
+                y2: y + height
+            };
+
+            if (intersection.x1 < intersection.x2) {
+                canvas.getContext('2d').fillRect(
+                    intersection.x1 - leftOffset,
+                    intersection.y1,
+                    intersection.x2 - intersection.x1,
+                    intersection.y2 - intersection.y1
+                );
+            }
+        });
+    }
+
+    fillText(text, x, y) {
+        let textWidth;
+        let xOffset = 0;
+        let i;
+
+        for (i in this.canvases) {
+            const context = this.canvases[i].getContext('2d');
+            const canvasWidth = context.canvas.width;
+
+            if (xOffset > x + textWidth) {
+                break;
+            }
+
+            if (xOffset + canvasWidth > x) {
+                textWidth = context.measureText(text).width;
+                context.fillText(text, x - xOffset, y);
+            }
+
+            xOffset += canvasWidth;
+        }
+    }
+}

+ 37 - 0
libraries/wavesurfer/src/util/ajax.js

@@ -0,0 +1,37 @@
+import Observer from './observer';
+
+/**
+ * Perform an ajax request
+ *
+ * @param {Options} options Description
+ *
+ * @returns {Object} Observer instance
+ */
+export default function ajax (options) {
+    const instance = new Observer();
+    const xhr = new XMLHttpRequest();
+    let fired100 = false;
+    xhr.open(options.method || 'GET', options.url, true);
+    xhr.responseType = options.responseType || 'json';
+    xhr.addEventListener('progress', e => {
+        instance.fireEvent('progress', e);
+        if (e.lengthComputable && e.loaded == e.total) {
+            fired100 = true;
+        }
+    });
+    xhr.addEventListener('load', e => {
+        if (!fired100) {
+            instance.fireEvent('progress', e);
+        }
+        instance.fireEvent('load', e);
+        if (200 == xhr.status || 206 == xhr.status) {
+            instance.fireEvent('success', xhr.response, e);
+        } else {
+            instance.fireEvent('error', e);
+        }
+    });
+    xhr.addEventListener('error', e => instance.fireEvent('error', e));
+    xhr.send();
+    instance.xhr = xhr;
+    return instance;
+}

+ 16 - 0
libraries/wavesurfer/src/util/extend.js

@@ -0,0 +1,16 @@
+ /**
+  * Extend an object shallowly with others
+  *
+  * @param {Object} dest The target object
+  * @param {Object[]} sources The objects to use for extending
+  *
+  * @return {Object} Merged object
+  */
+export default function extend (dest, ...sources) {
+    sources.forEach(source => {
+        Object.keys(source).forEach(key => {
+            dest[key] = source[key];
+        });
+    });
+    return dest;
+}

+ 13 - 0
libraries/wavesurfer/src/util/frame.js

@@ -0,0 +1,13 @@
+import reqAnimationFrame from './request-animation-frame';
+
+/**
+ * Create a function which will be called at the next requestAnimationFrame
+ * cycle
+ *
+ * @param {function} func The function to call
+ *
+ * @return {func} The function wrapped within a requestAnimationFrame
+ */
+export default function frame (func) {
+    return (...args) => reqAnimationFrame(() => func(...args));
+}

+ 8 - 0
libraries/wavesurfer/src/util/get-id.js

@@ -0,0 +1,8 @@
+/**
+ * Get a random prefixed ID
+ *
+ * @returns {String} Random ID
+ */
+export default function getId () {
+    return 'wavesurfer_' + Math.random().toString(32).substring(2);
+}

+ 10 - 0
libraries/wavesurfer/src/util/index.js

@@ -0,0 +1,10 @@
+export { default as ajax } from './ajax';
+export { default as getId } from './get-id';
+export { default as max } from './max';
+export { default as min } from './min';
+export { default as Observer } from './observer';
+export { default as extend } from './extend';
+export { default as style } from './style';
+export { default as requestAnimationFrame } from './request-animation-frame';
+export { default as frame } from './frame';
+export { default as debounce } from 'debounce';

+ 15 - 0
libraries/wavesurfer/src/util/max.js

@@ -0,0 +1,15 @@
+/**
+ * Get the largest value
+ *
+ * @param   {Array} values Array of numbers
+ * @returns {Number} Largest number found
+ */
+export default function max (values) {
+    let largest = -Infinity;
+    Object.keys(values).forEach(i => {
+        if (values[i] > largest) {
+            largest = values[i];
+        }
+    });
+    return largest;
+}

Some files were not shown because too many files changed in this diff