├── README.md ├── css └── style.css ├── docs ├── docco.css └── local_audio_visualizer.html ├── index.html └── js └── local_audio_visualizer.js /README.md: -------------------------------------------------------------------------------- 1 | # local_audio_visualizer 2 | 3 | An HTML5 experiment. 4 | 5 | Drag an audio file to the browser window. It will be played directly from your hard disk, and you'll have a neat spectrum visualization. 6 | 7 | Here is [the demo](http://cbrandolino.github.com/local-audio-visualizer) 8 | 9 | Here is the docco-generated [documentation, which makes for a decent tutorial](http://cbrandolino.github.com/local-audio-visualizer/docs/local_audio_visualizer) 10 | 11 | ## HTML5 features showcased 12 | 13 | - **Web Audio Api** (for playback and analysis) 14 | - **File access** (for local playback) 15 | - **Native drag and drop** (for, you know, drag and drop) 16 | - **Canvas and css transforms** (for the things you see) 17 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Droid Sans'; 3 | font-style: normal; 4 | font-weight: bold; 5 | src: local('Droid Sans'), url('http://playground.html5rocks.com/samples/html5_misc/Droid_Sans.ttf') format('truetype'); 6 | } 7 | 8 | #instructions { 9 | display: block; 10 | position: absolute; 11 | width: 100%; 12 | text-align: center; 13 | top: 50%; 14 | margin-top: -100px; 15 | font-family: 'Droid Sans'; 16 | color: #fff; 17 | } 18 | 19 | #bottom-bar { 20 | position: absolute; 21 | bottom: 20px; 22 | left: 10px; 23 | height: 160px; 24 | font-family: 'Droid Sans'; 25 | color: #fff; 26 | } 27 | 28 | #info { 29 | float: left; 30 | } 31 | 32 | #info a { 33 | color: #ddd; 34 | } 35 | 36 | #container { 37 | display: block; 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | width: 100%; 42 | height: 100%; 43 | background: radial-gradient( 44 | ellipse farthest-corner, 45 | #7d7d7d 0%, 46 | #0e0e0e 100%); 47 | } 48 | 49 | #canvas-container { 50 | width: 800px; 51 | height: 600px; 52 | margin: auto; 53 | position: relative; 54 | top: 50%; 55 | margin-top: -300px; 56 | } 57 | #canvas-copy { 58 | opacity: 0.05; 59 | -webkit-transform: scaleY(-1); 60 | margin-top: -6px; 61 | } 62 | -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Layout and Typography ----------------------------*/ 2 | body { 3 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 4 | font-size: 15px; 5 | line-height: 22px; 6 | color: #252519; 7 | margin: 0; padding: 0; 8 | } 9 | a { 10 | color: #261a3b; 11 | } 12 | a:visited { 13 | color: #261a3b; 14 | } 15 | p { 16 | margin: 0 0 15px 0; 17 | } 18 | h1, h2, h3, h4, h5, h6 { 19 | margin: 0px 0 15px 0; 20 | } 21 | h1 { 22 | margin-top: 40px; 23 | } 24 | hr { 25 | border: 0 none; 26 | border-top: 1px solid #e5e5ee; 27 | height: 1px; 28 | margin: 20px 0; 29 | } 30 | #container { 31 | position: relative; 32 | } 33 | #background { 34 | position: fixed; 35 | top: 0; left: 525px; right: 0; bottom: 0; 36 | background: #f5f5ff; 37 | border-left: 1px solid #e5e5ee; 38 | z-index: -1; 39 | } 40 | #jump_to, #jump_page { 41 | background: white; 42 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 43 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 44 | font: 10px Arial; 45 | text-transform: uppercase; 46 | cursor: pointer; 47 | text-align: right; 48 | } 49 | #jump_to, #jump_wrapper { 50 | position: fixed; 51 | right: 0; top: 0; 52 | padding: 5px 10px; 53 | } 54 | #jump_wrapper { 55 | padding: 0; 56 | display: none; 57 | } 58 | #jump_to:hover #jump_wrapper { 59 | display: block; 60 | } 61 | #jump_page { 62 | padding: 5px 0 3px; 63 | margin: 0 0 25px 25px; 64 | } 65 | #jump_page .source { 66 | display: block; 67 | padding: 5px 10px; 68 | text-decoration: none; 69 | border-top: 1px solid #eee; 70 | } 71 | #jump_page .source:hover { 72 | background: #f5f5ff; 73 | } 74 | #jump_page .source:first-child { 75 | } 76 | table td { 77 | border: 0; 78 | outline: 0; 79 | } 80 | td.docs, th.docs { 81 | max-width: 450px; 82 | min-width: 450px; 83 | min-height: 5px; 84 | padding: 10px 25px 1px 50px; 85 | overflow-x: hidden; 86 | vertical-align: top; 87 | text-align: left; 88 | } 89 | .docs pre { 90 | margin: 15px 0 15px; 91 | padding-left: 15px; 92 | } 93 | .docs p tt, .docs p code { 94 | background: #f8f8ff; 95 | border: 1px solid #dedede; 96 | font-size: 12px; 97 | padding: 0 0.2em; 98 | } 99 | .pilwrap { 100 | position: relative; 101 | } 102 | .pilcrow { 103 | font: 12px Arial; 104 | text-decoration: none; 105 | color: #454545; 106 | position: absolute; 107 | top: 3px; left: -20px; 108 | padding: 1px 2px; 109 | opacity: 0; 110 | -webkit-transition: opacity 0.2s linear; 111 | } 112 | td.docs:hover .pilcrow { 113 | opacity: 1; 114 | } 115 | td.code, th.code { 116 | padding: 14px 15px 16px 25px; 117 | width: 100%; 118 | vertical-align: top; 119 | background: #f5f5ff; 120 | border-left: 1px solid #e5e5ee; 121 | } 122 | pre, tt, code { 123 | font-size: 12px; line-height: 18px; 124 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 125 | margin: 0; padding: 0; 126 | } 127 | 128 | 129 | /*---------------------- Syntax Highlighting -----------------------------*/ 130 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 131 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 132 | body .hll { background-color: #ffffcc } 133 | body .c { color: #408080; font-style: italic } /* Comment */ 134 | body .err { border: 1px solid #FF0000 } /* Error */ 135 | body .k { color: #954121 } /* Keyword */ 136 | body .o { color: #666666 } /* Operator */ 137 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 138 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 139 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 140 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 141 | body .gd { color: #A00000 } /* Generic.Deleted */ 142 | body .ge { font-style: italic } /* Generic.Emph */ 143 | body .gr { color: #FF0000 } /* Generic.Error */ 144 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 145 | body .gi { color: #00A000 } /* Generic.Inserted */ 146 | body .go { color: #808080 } /* Generic.Output */ 147 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 148 | body .gs { font-weight: bold } /* Generic.Strong */ 149 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 150 | body .gt { color: #0040D0 } /* Generic.Traceback */ 151 | body .kc { color: #954121 } /* Keyword.Constant */ 152 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 153 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 154 | body .kp { color: #954121 } /* Keyword.Pseudo */ 155 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 156 | body .kt { color: #B00040 } /* Keyword.Type */ 157 | body .m { color: #666666 } /* Literal.Number */ 158 | body .s { color: #219161 } /* Literal.String */ 159 | body .na { color: #7D9029 } /* Name.Attribute */ 160 | body .nb { color: #954121 } /* Name.Builtin */ 161 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 162 | body .no { color: #880000 } /* Name.Constant */ 163 | body .nd { color: #AA22FF } /* Name.Decorator */ 164 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 165 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 166 | body .nf { color: #0000FF } /* Name.Function */ 167 | body .nl { color: #A0A000 } /* Name.Label */ 168 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 169 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 170 | body .nv { color: #19469D } /* Name.Variable */ 171 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 172 | body .w { color: #bbbbbb } /* Text.Whitespace */ 173 | body .mf { color: #666666 } /* Literal.Number.Float */ 174 | body .mh { color: #666666 } /* Literal.Number.Hex */ 175 | body .mi { color: #666666 } /* Literal.Number.Integer */ 176 | body .mo { color: #666666 } /* Literal.Number.Oct */ 177 | body .sb { color: #219161 } /* Literal.String.Backtick */ 178 | body .sc { color: #219161 } /* Literal.String.Char */ 179 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 180 | body .s2 { color: #219161 } /* Literal.String.Double */ 181 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 182 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 183 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 184 | body .sx { color: #954121 } /* Literal.String.Other */ 185 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 186 | body .s1 { color: #219161 } /* Literal.String.Single */ 187 | body .ss { color: #19469D } /* Literal.String.Symbol */ 188 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 189 | body .vc { color: #19469D } /* Name.Variable.Class */ 190 | body .vg { color: #19469D } /* Name.Variable.Global */ 191 | body .vi { color: #19469D } /* Name.Variable.Instance */ 192 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/local_audio_visualizer.html: -------------------------------------------------------------------------------- 1 |
local_audio_visualizer.js | |
---|---|
window.onload = function() {
2 | var element = document.getElementById('container')
3 | dropAndLoad(element, init, "ArrayBuffer")
4 | } | |
Reusable dropAndLoad function: it reads a local file dropped on a
5 | | function dropAndLoad(dropElement, callback, readFormat) {
7 | var readFormat = readFormat || "DataUrl"
8 |
9 | dropElement.addEventListener('dragover', function(e) {
10 | e.stopPropagation()
11 | e.preventDefault()
12 | e.dataTransfer.dropEffect = 'copy'
13 | }, false)
14 |
15 | dropElement.addEventListener('drop', function(e) {
16 | e.stopPropagation()
17 | e.preventDefault()
18 | loadFile(e.dataTransfer.files[0])
19 | }, false)
20 |
21 | function loadFile(files) {
22 | var file = files
23 | var reader = new FileReader()
24 | reader.onload = function(e) {
25 | callback(e.target.result)
26 | }
27 | reader['readAs'+readFormat](file)
28 | }
29 | } |
Once the file is loaded, we start getting our hands dirty. | function init(arrayBuffer) {
30 | document.getElementById('instructions').innerHTML = 'Loading ...' |
Create a new | window.audioCtx = new webkitAudioContext()
31 | window.analyser = audioCtx.createAnalyser() |
If a sound is still playing, stop it. | if (window.source)
32 | source.noteOff(0) |
Decode the data in our array into an audio buffer | audioCtx.decodeAudioData(arrayBuffer, function(buffer) { |
Use the audio buffer with as our audio source | window.source = audioCtx.createBufferSource()
33 | source.buffer = buffer |
Connect to the analyser ... | source.connect(analyser) |
and back to the destination, to play the sound after the analysis. | analyser.connect(audioCtx.destination) |
Start playing the buffer. | source.noteOn(0) |
Initialize a visualizer object | var viz = new simpleViz() |
Finally, initialize the visualizer. | new visualizer(viz['update'], analyser)
34 | document.getElementById('instructions').innerHTML = ''
35 | })
36 | } |
The visualizer object.
37 | Calls the | function visualizer(visualization, analyser) {
40 | var self = this
41 | this.visualization = visualization
42 | var last = Date.now()
43 | var loop = function() {
44 | var dt = Date.now() - last |
we get the current byteFreq data from our analyser | var byteFreq = new Uint8Array(analyser.frequencyBinCount)
45 | analyser.getByteFrequencyData(byteFreq)
46 | last = Date.now() |
We might want to use a delta time ( | self.visualization(byteFreq, dt)
47 | webkitRequestAnimationFrame(loop)
48 | }
49 | webkitRequestAnimationFrame(loop)
50 | } |
A simple visualization. Its update function illustrates how to use 51 | the byte frequency data from an audioContext analyser. | function simpleViz(canvas) {
52 | var self = this
53 | this.canvas = document.getElementById('canvas')
54 | this.ctx = this.canvas.getContext("2d")
55 | this.copyCtx = document.getElementById('canvas-copy').getContext("2d")
56 | this.ctx.fillStyle = '#fff'
57 | this.barWidth = 10
58 | this.barGap = 4 |
We get the total number of bars to display | this.bars = Math.floor(this.canvas.width / (this.barWidth + this.barGap)) |
This function is launched for each frame, together with the byte frequency data. | this.update = function(byteFreq) {
59 | self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height) |
We take an element from the byteFreq array for each of the bars. 60 | Let's pretend our byteFreq contains 20 elements, and we have five bars... | var step = Math.floor(byteFreq.length / self.bars) |
| for (var i = 0; i < self.bars; i ++) { |
Draw each bar | var barHeight = byteFreq[i*step]
62 | self.ctx.fillRect(
63 | i * (self.barWidth + self.barGap),
64 | self.canvas.height - barHeight,
65 | self.barWidth,
66 | barHeight)
67 | self.copyCtx.clearRect(0, 0, self.canvas.width, self.canvas.height)
68 | self.copyCtx.drawImage(self.canvas, 0, 0)
69 | }
70 | }
71 | }
72 |
73 | |
Uses the following HTML5 featrures: 28 | Web Audio API, 29 | drop events, 30 | file access, and 31 | canvas. 32 |
Only works on the most recent versions of Chrome (and possibly Safari). 33 |
Here's the documentation, and here's 34 | cbrandolino's github. 35 |