├── .gitignore ├── .gitmodules ├── README.md ├── css └── style.css ├── experiments ├── application.css ├── experiment.js ├── images │ └── noise.png └── index.html ├── images ├── down.png ├── equal.png ├── github-banner.png ├── github.png ├── header.png ├── logo.png ├── mhd.png ├── nopicture.png ├── ofm.png ├── pause.png ├── play.png └── up.png ├── index.html ├── mhd.js ├── officialfm.js ├── scripts ├── jquery.js ├── madcat.sh ├── sink.min.js └── uglify ├── src ├── arraybuffer │ ├── ajaxstream.js │ ├── arraystream.js │ ├── bytestream.js │ ├── filestream.js │ └── substream.js ├── binaryajax.js ├── binarystring │ ├── ajaxstream.js │ ├── bytestream.js │ ├── filestream.js │ ├── stringstream.js │ └── substream.js ├── bit.js ├── frame.js ├── huffman.js ├── id3v22stream.js ├── id3v23stream.js ├── imdct_s.js ├── layer3.js ├── mad.js ├── mp3file.js ├── player.js ├── rq_table.js ├── stream.js └── synth.js └── tests └── node ├── aliasreduce.js ├── check.rb ├── dct.js ├── fastdsct.js ├── full_frame.js ├── graph.svg ├── huffdecode.js ├── imdct.js ├── one_second_beep.mp3 ├── one_second_of_silence.mp3 ├── out.raw ├── output.js ├── pcm.png ├── plot-pcm.gnuplot ├── plot-sample.gnuplot ├── reorder.js ├── sample.png ├── sample.svg ├── scalefac.js └── typed-array.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .*.swp 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "audiolib.js"] 2 | path = audiolib.js 3 | url = git://github.com/jussi-kalliokoski/audiolib.js.git 4 | [submodule "sink.js"] 5 | path = sink.js 6 | url = https://github.com/jussi-kalliokoski/sink.js.git 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Deprecated! Please use [mp3.js](https://github.com/audiocogs/mp3.js) and [aurora.js](https://github.com/audiocogs/aurora.js) instead. 2 | 3 | ## About 4 | 5 | jsmad is a pure javascript MP3 decoder, based on [libmad](http://www.underbit.com/products/mad/), with an ID3 decoder written from scratch. 6 | 7 | For example, jsmad allows Firefox 4.0+ to play MP3s without any Flash. Faster loading times. Fewer security holes. No 64-bit headaches on Linux. Fewer memory leaks. 8 | 9 | jsmad opens up a whole world of realtime audio applications implemented in javascript: 10 | dj-mixers, samplers, sequencers, all these applications benefit from using mp3s as audio source. 11 | 12 | jsmad is released under the GPLv2 license. 13 | 14 | ## Demo 15 | 16 | See a live demo here: http://jsmad.org/ in collaboration with [official.fm](http://official.fm/) and using the [musicmetric](http://musicmetric.com/) API 17 | 18 | It works out of the box under Firefox 4.0 and above. On Chrome 13.0+, you have to enable manually the Web Audio API in 'about:flags', then restart the browser and it should work fine! No Opera support at the moment. 19 | 20 | ## Authors 21 | 22 | * [@nddrylliog](http://twitter.com/nddrylliog) - lead developer 23 | * [@jensnockert](http://twitter.com/jensnockert) - helped porting & debugging the code at MusicHackDay Berlin 24 | * [@mgeorgi](http://twitter.com/mgeorgi) - helped debugging the code after MusicHackDay Berlin 25 | 26 | Special thanks to [@antoinem](http://twitter.com/antoinem) for the Demo design and particularly to [@_romac](http://twitter.com/_romac) for adding features & keeping the demo server alive! 27 | 28 | ## Porting notes 29 | 30 | Obviously, porting low-level C code to Javascript isn't an easy task. Some things had to be 31 | adapted pretty heavily. jsmad is not the result of an automatic translation - all 15K+ lines 32 | of code were translated by hand by @nddrylliog and @jensnockert during MusicHackDay Berlin. 33 | Then, @mgeorgi helped us a lot with the debugging process, and @antoinem did the design of the demo 34 | during MusicHackDay Barcelona. 35 | 36 | It performs well enough to decode and play MP3s in realtime on Firefox on modern computers, 37 | although if you do lots of things at once, Firefox might forget at all about scheduled tasks 38 | and let the soundcard underflow. There is a rescue mechanism for that in the demo, which works 39 | most of the time. 40 | 41 | jsmad will undoubtedly be an interesting benchmark for browser's javascript implementations. 42 | We would love to get feedback from the Mozilla, Google Chrome, and Opera team - shoot us a note! 43 | 44 | ## Accuracy 45 | 46 | The output from jsmad is NOT representative of the output quality of libmad itself. jsmad hasn't been 47 | tested for ISO/IEC 11172-4 computational accuracy requirements compliance. JavaScript number crunching 48 | has always been a bad idea, and we're aware of that - we've done it to push the limits of what is being 49 | done with JavaScript, much in the spirit of [pdf.js](https://github.com/andreasgal/pdf.js) 50 | 51 | ## License 52 | 53 | jsmad is available under the terms of the GNU General Public License, Version 2. Please note that 54 | under the GPL, there is absolutely no warranty of any kind, to the extent permitted by the law. 55 | 56 | What GPL means for a javascript library is not exactly clear to us. What's clear is that you have 57 | to release any fork/changes under the GPL as well, so that everyone can profit from it. However, 58 | using it on a commercial platform is probably alright. Remember: no guarantees, and we'd love to know 59 | about it! 60 | 61 | libmad has commercial license. As for jsmad, we're in a sort of grey legal area. If you're from 62 | Underbit and want to work this out with us please drop me a note at amos@official.fm 63 | 64 | ## Future 65 | 66 | What's next? A few things: 67 | 68 | - Strings are still used in the core decoding routines instead of Uint8Arrays - this should change 69 | - Optimizations, always 70 | - Better buffering strategy - player.js is still pretty naive and we stumble now and then onto buffer underflow 71 | - MPEG Layer I and II are not supported, only Layer III is - it should be pretty trivial but we had no interest for it in the first place. 72 | - MPEG 2.5 is not supported. 73 | - Free bitrate streams are not supported (this is different from VBR - VBR is supported) 74 | - Most of ID3v2.2 and ID3v2.3 are implemented, but some tags are mising. 75 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica; 3 | margin:0; 4 | padding:0; 5 | background:#fa5945 url(../images/header.png) repeat-x top left; 6 | } 7 | #container { 8 | width:960px; 9 | margin:0 auto; 10 | } 11 | 12 | h1 { 13 | background:transparent url(../images/logo.png) no-repeat top left; 14 | width: 157px; 15 | height: 180px; 16 | display: block; 17 | float:left; 18 | margin:20px 40px 20px 20px; 19 | } 20 | h1 span { display:none; } 21 | 22 | h2 { font-size: 30px; line-height: 38px; margin:0; } 23 | h3 { font-size: 22px; margin: 10px 0 20px; } 24 | 25 | h3 small { font-size: 14px; color:#FA5945 !important; font-weight:normal !important; margin-left:10px; } 26 | 27 | a, a:active { color:#490CCE; outline:0;} 28 | a:hover { color:#0A28AC; } 29 | 30 | div.github { 31 | -moz-transform: rotate(2deg); 32 | -moz-transform-origin: left top; 33 | background: url(../images/github.png) no-repeat 16px 10px rgba(255, 255, 255, 0.5); 34 | box-shadow: 0 0 27px rgba(0, 0, 0, 0.08); 35 | float: right; 36 | padding: 30px 30px 30px 91px; 37 | position: relative; 38 | top: -3px; 39 | } 40 | 41 | div.select { 42 | top: 66px; 43 | clear:right; 44 | position: relative; 45 | } 46 | div.select span { 47 | margin:0 10px; 48 | } 49 | form { display: inline-block;} 50 | 51 | label { color:#FFE400; font-size: 18px; line-height:22px; margin-bottom: 3px; text-shadow: rgba(0,0,0,0.3) 1px 1px 1px; display:block;} 52 | label.ofm { background:url(../images/ofm.png) no-repeat top left; padding-left:20px;} 53 | 54 | input { font-size:18px; } 55 | 56 | #content { 57 | clear:both; 58 | } 59 | 60 | #ID3 { 61 | background:#fff; 62 | padding:20px; 63 | min-height:400px; 64 | position:relative; 65 | } 66 | 67 | #ID3:before { 68 | content: ' '; 69 | height: 0; 70 | position: absolute; 71 | width: 0; 72 | border: 15px solid transparent; 73 | border-bottom-color: #fff; 74 | top: 0%; 75 | left: 90px; 76 | margin-top: -30px; 77 | } 78 | 79 | div.player { 80 | float:left; 81 | margin-right:20px; 82 | } 83 | 84 | div.picture { 85 | width:400px; 86 | height:400px; 87 | display: block; 88 | } 89 | div.picture img.picture { 90 | width:400px; 91 | height:400px; 92 | display: block; 93 | } 94 | 95 | a.button { 96 | width:100px; 97 | height:100px; 98 | display:block; 99 | position:relative; 100 | top:-250px; 101 | left:150px; 102 | } 103 | 104 | a.play { 105 | background: transparent url(../images/play.png) no-repeat top left; 106 | opacity:0.7; 107 | } 108 | a.play:hover {opacity:1;} 109 | 110 | a.pause { 111 | background: transparent url(../images/pause.png) no-repeat top left; 112 | opacity:0.7; 113 | } 114 | a.pause:hover {opacity:1;} 115 | 116 | div.timeline { 117 | width:360px; 118 | height:20px; 119 | border: 1px solid rgba(255,228,0,0.7); 120 | display:block; 121 | position:relative; 122 | top:-140px; 123 | left:20px; 124 | } 125 | div#progressbar { width: 0; height: 20px; background-color: #FA5945; } 126 | div#preloadbar { width: 0; height: 20px; background-color: rgba(255,228,0,0.5); } 127 | 128 | div.meta { background-color: rgba(0, 0, 0, 0.1); width:460px; padding:20px; font-size:16px; display:inline-block; } 129 | 130 | #shareTrack { 131 | text-align: right; 132 | margin: 20px 0 40px 0; 133 | } 134 | 135 | #playUrl { 136 | width: 320px; 137 | } 138 | 139 | #footer { 140 | clear:both; 141 | padding:0 20px 20px; 142 | color:#FFE400; 143 | font-size:15px; 144 | } 145 | #footer a { background:#fff; padding:2px 5px; -moz-border-radius:3px;font-size: 15px; 146 | padding: 3px 5px 1px; 147 | position: relative; 148 | top: -1px; 149 | font-weight:bold; 150 | text-decoration:none; 151 | color:#FA5945;} 152 | #footer a:hover { color:#000;} 153 | #footer a span {font-weight:normal;color:#000; font-style:italic;font-size:12px;} 154 | #footer a.mhd { background:transparent; top:0;} 155 | #footer img { position: relative;top: 4px; } 156 | -------------------------------------------------------------------------------- /experiments/application.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | -moz-user-select: -moz-none; 4 | } 5 | 6 | html { 7 | font: 18px/27px 'TeXGyreHerosBold'; 8 | color: white; 9 | } 10 | 11 | body { 12 | width: 35em; 13 | margin: 2em auto; 14 | background: url("images/noise.png") #3c3c46; 15 | -webkit-user-select: none; 16 | user-select: none; 17 | } 18 | 19 | span.even { 20 | color: #bc4; 21 | } 22 | 23 | h1, h2 { 24 | text-shadow: #1f2329 0 1px 0; 25 | } 26 | 27 | #outer { 28 | position: absolute; 29 | width: 36em; 30 | margin-left: -18em; 31 | left: 50%; 32 | top: 20%; 33 | margin-top: -125px; 34 | } 35 | 36 | #content { 37 | font: 100% 'TeXGyreHerosRegular'; 38 | } 39 | 40 | h2.slogan { 41 | width: 100%; 42 | left: 0; 43 | text-align: center; 44 | } 45 | 46 | h1.logo { 47 | font: 5em 'TeXGyreHerosBold'; 48 | color: white; 49 | margin-bottom: 0.6em; 50 | text-align: center; 51 | } 52 | 53 | h1.lovelogo { 54 | width: 100%; 55 | text-align: center; 56 | font: 3.4em 'TeXGyreHerosBold'; 57 | } 58 | 59 | h1 a, h1 a:visited{ 60 | color: white; 61 | text-decoration: none; 62 | } 63 | 64 | em { 65 | font-style: normal; 66 | color: #bc4; 67 | } 68 | 69 | a:visited { 70 | color: #444; 71 | } 72 | 73 | button:hover, input[type=submit]:hover, input[type=button]:hover, select:focus 74 | { 75 | background-color: rgb(106, 120, 145); 76 | } 77 | 78 | button:active, input[type=submit]:active, input[type=button]:active 79 | { 80 | background-color: rgb(37, 41, 47); 81 | background-image: -webkit-gradient( 82 | linear, 83 | left top, 84 | left bottom, 85 | color-stop(0.00, rgba(0, 0, 0, 0.3)), 86 | color-stop(0.50, rgba(0, 0, 0, 0.0)) 87 | ); 88 | background-image: -webkit-linear-gradient( 89 | top, 90 | rgba(0, 0, 0, 0.3) 0%, 91 | rgba(0, 0, 0, 0.0) 50% 92 | ); 93 | background-image: -moz-linear-gradient( 94 | top, 95 | rgba(0, 0, 0, 0.3) 0%, 96 | rgba(0, 0, 0, 0.0) 50% 97 | ); 98 | background-image: linear-gradient( 99 | top, 100 | rgba(0, 0, 0, 0.3) 0%, 101 | rgba(0, 0, 0, 0.0) 50% 102 | ); 103 | border-color: rgb(36, 40, 46); 104 | border-bottom-color: rgb(81, 83, 91); 105 | } 106 | 107 | textarea.code { 108 | display: block; 109 | margin-left: 10px; 110 | margin-right: 10px; 111 | font-size: 75%; 112 | min-height: 40px; 113 | max-width: 90%; 114 | min-width: 90%; 115 | } 116 | 117 | textarea.code:disabled { 118 | color: #fff; 119 | } 120 | 121 | .avatar, .avatar img { 122 | width: 80px; 123 | height: 80px; 124 | } 125 | 126 | .avatar { 127 | display: inline-block; 128 | border: 2px solid white; 129 | vertical-align: middle; 130 | border-radius: 0.15em; 131 | background: white; 132 | } 133 | 134 | .avatar img { 135 | border-radius: 0.15em; 136 | margin-bottom: 2px; 137 | } 138 | 139 | h2.slogan { 140 | font-size: 1.7em; 141 | bottom: 6%; 142 | } 143 | 144 | form { 145 | width: 100%; 146 | text-align: center; 147 | } 148 | 149 | ul.steps { 150 | font-size: 1.6em; 151 | width: 12em; 152 | margin-left: auto; 153 | margin-right: auto; 154 | } 155 | 156 | ul.steps li { 157 | padding: 0.3em; 158 | list-style-type: none; 159 | border-bottom: 1px solid white; 160 | color: #ddd; 161 | box-shadow: 0 0 35px #111111 inset; 162 | background: #222; 163 | } 164 | 165 | ul.steps li a { 166 | color: #ddd; 167 | text-decoration: none; 168 | } 169 | 170 | ul.steps li:first-child { 171 | border-radius: 8px 8px 0 0; 172 | } 173 | 174 | ul.steps li:last-child { 175 | border: none; 176 | border-radius: 0 0 8px 8px; 177 | } 178 | 179 | ul.steps li:hover { 180 | cursor: pointer; 181 | color: white; 182 | background: #444; 183 | } 184 | 185 | ul.steps span { 186 | display: inline-block; 187 | vertical-align: middle; 188 | width: 1.8em; 189 | height: 1.8em; 190 | font-size: 0.8em; 191 | text-align: center; 192 | padding: 0; 193 | color: white; 194 | text-shadow: -1px -1px 2px #444, 2px 2px 2px #222; 195 | } 196 | 197 | ul.steps span img { 198 | height: 1.8em; 199 | } 200 | 201 | #signup form { 202 | position: relative; 203 | margin: 10% 0; 204 | text-align: center; 205 | } 206 | 207 | #signup button { 208 | font-size: 2.2em; 209 | width: 9em; 210 | height: 2.7em; 211 | padding-right: 0.6em; 212 | padding-top: 0.3em; 213 | padding-bottom: 0; 214 | padding-left: 0; 215 | } 216 | 217 | #signup button img { 218 | vertical-align: middle; 219 | margin-bottom: 0.3em; 220 | margin-right: 0.3em; 221 | } 222 | 223 | h2.greeting, h2.status { 224 | margin-top: 0.1em; 225 | margin-bottom: 1.3em; 226 | } 227 | 228 | div.player, .dashboard { 229 | border: 2px solid #CCC; 230 | padding-top: 1em; 231 | padding-left: 1.2em; 232 | border-radius: 0.4em; 233 | background-color: #111; 234 | } 235 | 236 | div.signout form input[type=submit] { 237 | border: none; 238 | background: transparent; 239 | color: #A44; 240 | font-size: 100%; 241 | float: right; 242 | margin-bottom: 0.5em; 243 | margin-right: 0.5em; 244 | box-shadow: none; 245 | padding: 0; 246 | 247 | } 248 | 249 | div.signout form input[type=submit]:hover { 250 | text-decoration: underline; 251 | cursor: pointer; 252 | } 253 | 254 | div.clear { 255 | clear: both; 256 | } 257 | 258 | iframe.player { 259 | border: none; 260 | width: 220px; 261 | height: 350px; 262 | margin-left: auto; 263 | margin-right: auto; 264 | display: block; 265 | overflow: hidden; 266 | margin-bottom: 10px; 267 | scrolling: no; 268 | } 269 | -------------------------------------------------------------------------------- /experiments/experiment.js: -------------------------------------------------------------------------------- 1 | var domReady = function () 2 | { 3 | var fileChooser = document.forms['uploadData']['fileChooser']; 4 | fileChooser.onchange = function(ev) { 5 | readFile() 6 | } 7 | 8 | function readFile() { 9 | var file = fileChooser.files[0]; 10 | console.log("Reading file " + file.name); 11 | 12 | new Mad.FileStream(file, function(stream) { 13 | var mp3 = new Mad.MP3File(stream); 14 | console.log("File loaded"); 15 | var mpeg = mp3.getMpegStream(); 16 | 17 | var howmuch = 10000; 18 | var ss1 = Date.now(); 19 | console.log("Decoding first " + howmuch + " frames"); 20 | var frame = new Mad.Frame(); 21 | 22 | for(var i = 0; i < howmuch; i += 1) { 23 | frame = Mad.Frame.decode(frame, mpeg); 24 | } 25 | var ss2 = Date.now(); 26 | console.log("Done in " + (ss2 - ss1) + "ms"); 27 | }); 28 | } 29 | }; 30 | document.addEventListener("DOMContentLoaded", domReady, false); 31 | -------------------------------------------------------------------------------- /experiments/images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/experiments/images/noise.png -------------------------------------------------------------------------------- /experiments/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jsmad experiment 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/down.png -------------------------------------------------------------------------------- /images/equal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/equal.png -------------------------------------------------------------------------------- /images/github-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/github-banner.png -------------------------------------------------------------------------------- /images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/github.png -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/header.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/logo.png -------------------------------------------------------------------------------- /images/mhd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/mhd.png -------------------------------------------------------------------------------- /images/nopicture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/nopicture.png -------------------------------------------------------------------------------- /images/ofm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/ofm.png -------------------------------------------------------------------------------- /images/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/pause.png -------------------------------------------------------------------------------- /images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/play.png -------------------------------------------------------------------------------- /images/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/images/up.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jsmad - javascript mp3 decoder 6 | 7 | 8 | 9 |
10 | Fork me on GitHub 11 | 30 |
31 |
32 |
33 |

jsmad is a pure JavaScript MP3 decoder

34 |

Select a local file or enter an Official.fm track id :)

35 |
36 |
37 |
38 |
39 | 52 |
53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /mhd.js: -------------------------------------------------------------------------------- 1 | 2 | ( function() 3 | { 4 | 5 | var DEBUG = window.MHD_DEBUG || false; 6 | 7 | window.MHD = function( o ) 8 | { 9 | this.options = o; 10 | this.ofm = new OfficialFM( o.ofmAPIKey ); 11 | this.player = null; 12 | this.ofmTrack = null; 13 | 14 | // var globalPlayer = null; 15 | // var ofm = new OfficialFM('Q5Bd7987TmfsNVOHP9Zt'); 16 | // var ofmTrack = null; 17 | }; 18 | 19 | MHD.MP3_URL = 'http://mp3.jsmad.org'; 20 | 21 | MHD.prototype = { 22 | 23 | bindEvents : ( function() 24 | { 25 | var eventsBinded = false; 26 | 27 | return function() 28 | { 29 | if(eventsBinded) return; 30 | 31 | var self = this; 32 | 33 | this.el = {}; 34 | 35 | for(key in this.options.el) 36 | { 37 | this.el[key] = document.querySelector(this.options.el[key]); 38 | } 39 | 40 | this.el.fileChooser.onchange = function( ev ) 41 | { 42 | self.readFile( ev ); 43 | }; 44 | 45 | this.el.trackId.onkeypress = function(ev) 46 | { 47 | // enter pressed ? 48 | if(ev.keyCode == 13) { 49 | 50 | self.playOfm(); 51 | return false; 52 | } 53 | }; 54 | }; 55 | 56 | } )(), 57 | 58 | onPageLoad : function() 59 | { 60 | this.bindEvents(); 61 | this.checkLocation(); 62 | //this.playOfm(); 63 | }, 64 | 65 | onPlayPause : function() 66 | { 67 | DEBUG && console.log("play/paused pressed."); 68 | }, 69 | 70 | onSeek : function(percentage) 71 | { 72 | DEBUG && console.log("seek " + percentage + "%"); 73 | }, 74 | 75 | onProgress: function( current, total, preload ) 76 | { 77 | DEBUG && console.log("current = " + current + ", total = " + total); 78 | 79 | this.el.preloadBar.style.width = (preload * 360) + 'px'; 80 | this.el.progressBar.style.width = (current / total * 360) + 'px'; 81 | }, 82 | 83 | checkLocation: function() 84 | { 85 | this.location = window.location; 86 | 87 | var match = /^\/play\/(\d+)\/?/.exec( this.location.pathname ); 88 | match && ( this.el.trackId.value = match[ 1 ] ); 89 | }, 90 | 91 | playOfm : function() 92 | { 93 | var trackId = this.el.trackId.value, 94 | url = MHD.MP3_URL + "/mp3s/" + Math.floor( trackId / 1000 ) + "/" + trackId + ".mp3", 95 | self = this; 96 | 97 | this.el.playUrl.value = this.location.protocol + '//' + this.location.host + '/play/' + trackId; 98 | 99 | this.ofm.track( trackId, function( track ) 100 | { 101 | self.ofmTrack = track; 102 | Mad.Player.fromURL( url, function( player ) 103 | { 104 | setTimeout(function () { self.usePlayer( player ) }, 1000); 105 | } ); 106 | } ); 107 | }, 108 | 109 | readFile : function() 110 | { 111 | // uploadData is a form element 112 | // fileChooser is input element of type 'file' 113 | var file = document.forms['uploadData']['fileChooser'].files[0], 114 | self = this; 115 | 116 | if (!file) return; 117 | 118 | Mad.Player.fromFile(file, function( player ) 119 | { 120 | self.usePlayer( player ); 121 | }); 122 | }, 123 | 124 | // TODO: Refactor this method. 125 | usePlayer : function(player) 126 | { 127 | var self = this; 128 | 129 | if(this.player) player.destroy(); 130 | 131 | this.player = player; 132 | 133 | // Workaround for MP3s without ID3, all is going to be undefined 134 | if (player.id3 || true) { 135 | 136 | var id3 = player.id3 ? player.id3.toHash() : {}, 137 | id3element = self.el.id3, 138 | id3string = "
", 139 | pictures = id3['Attached picture'], 140 | artist = this.ofmTrack ? this.ofmTrack.artist_string : id3['Lead artist/Lead performer/Soloist/Performing group']; 141 | 142 | if(this.ofmTrack) { 143 | 144 | id3string += ""; 145 | 146 | } else if (pictures) { 147 | 148 | var mime = pictures[0].mime, 149 | enc = btoa(pictures[0].value); 150 | 151 | id3string += ""; 152 | 153 | } else { 154 | 155 | id3string += ""; 156 | } 157 | 158 | id3string += ""; 159 | id3string += "
"; 160 | 161 | id3string += "
"; 162 | id3string += "
"; 163 | 164 | id3string += "

" + (this.ofmTrack ? this.ofmTrack.title : id3['Title/Songname/Content description']) + "

"; 165 | id3string += "

" + artist + "

"; 166 | id3string += "
"; 167 | 168 | if(id3['Album/Movie/Show title']) { 169 | 170 | id3string += "

Album: " + id3['Album/Movie/Show title'] + "

"; 171 | } 172 | 173 | if(id3['Track number/Position in set']) { 174 | 175 | id3string += "

Track: " + id3['Track number/Position in set'] + "

"; 176 | } 177 | 178 | if(id3['Year']) { 179 | 180 | id3string += "

Year: " + id3['Year'] + "

"; 181 | } 182 | 183 | id3string += "
"; 184 | id3string += "
"; 185 | 186 | this.ofmTrack = null; 187 | 188 | id3element.innerHTML = id3string; 189 | 190 | // TODO: Make these elements configurable (will be part of the future refactor). 191 | this.el.progressBar = document.getElementById( 'progressbar' ); 192 | this.el.preloadBar = document.getElementById( 'preloadbar' ); 193 | this.el.playPause = document.getElementById( 'playpause' ); 194 | 195 | this.el.playPause.onclick = function( ev ) 196 | { 197 | player.setPlaying(!player.playing); 198 | 199 | return false; 200 | }; 201 | 202 | // musicbrainz + musicmetric queries 203 | /* Disabled for now, as there is no backend to handle this request. 204 | $.ajax({ 205 | type: "GET", 206 | url: "http://jsmad.org/musicbrainz/" + escape(artist), 207 | dataType: "xml", 208 | success: function(xml) { 209 | var doc = $(xml); 210 | console.log("real artist name = " + doc.find('artist').children('name').text()); 211 | var artist_id = doc.find('artist').attr('id'); 212 | console.log("musicbrainz artist id = " + artist_id); 213 | 214 | var icons = { 215 | facebook: 'http://facebook.com/favicon.ico', 216 | lastfm: 'http://last.fm/favicon.ico', 217 | myspace: 'http://myspace.com/favicon.ico', 218 | twitter: 'http://twitter.com/favicon.ico', 219 | youtube: 'http://www.youtube.com/favicon.ico', 220 | }; 221 | 222 | $.ajax({ 223 | type: "GET", 224 | url: "http://jsmad.org/musicmetric/musicbrainz:" + artist_id, 225 | dataType: "json", 226 | success: function(json) { 227 | console.log("success? " + json.success); 228 | var up_img = ''; 229 | var down_img = ''; 230 | var equal_img = ''; 231 | 232 | for(var platform in json.response.fans) { 233 | if(!json.response.fans.hasOwnProperty(platform)) continue; 234 | if(platform == "total") continue; 235 | 236 | var fans = json.response.fans[platform]; 237 | var previousFans = parseInt(fans.previous); 238 | var currentFans = parseInt(fans.current); 239 | var totalFans = parseInt(fans.total); 240 | console.log("previous/current fans? " + previousFans + "/" + currentFans); 241 | $('#meta_info').append('

' + (Math.abs(currentFans - previousFans) < 5 ? equal_img : (currentFans > previousFans ? up_img : down_img)) + ' ' + platform + ': ' + (totalFans > 1000000 ? ((Math.floor(totalFans / 100000) / 10.0) + "M") : (totalFans > 1000 ? ((Math.floor(totalFans / 100) / 10.0) + "K") : totalFans)) + ' fans' + '

'); 242 | } 243 | $('#artist_span').append(' ' + json.response.fans.total.total + ' fans'); 244 | } 245 | }); 246 | } 247 | }); 248 | */ 249 | } 250 | 251 | this.player.onProgress = function( current, total, preload ) 252 | { 253 | self.onProgress(current, total, preload); 254 | } 255 | 256 | this.player.onPlay = function() 257 | { 258 | self.el.playPause.className = 'button pause'; 259 | }; 260 | 261 | this.player.onPause = function() 262 | { 263 | self.el.playPause.className = 'button play'; 264 | }; 265 | 266 | this.player.createDevice(); 267 | } 268 | 269 | }; 270 | 271 | var mhd = new MHD( { 272 | ofmAPIKey : 'Q5Bd7987TmfsNVOHP9Zt', 273 | el : { 274 | // playPause : '#playpause', 275 | // preloadBar : '#preloadbar', 276 | // progressBar: '#progressbar', 277 | fileChooser: '#fileChooser', 278 | trackId : '#ofm', 279 | id3 : '#ID3', 280 | playUrl : '#playUrl' 281 | } 282 | } ); 283 | 284 | window.onload = function() 285 | { 286 | mhd.onPageLoad(); 287 | }; 288 | 289 | } )(); 290 | -------------------------------------------------------------------------------- /officialfm.js: -------------------------------------------------------------------------------- 1 | if(typeof jQuery == 'undefined') { 2 | alert('jQuery must be loaded for officialfm-javascript to work.'); 3 | } 4 | 5 | function OfficialFM(api_key, preload_player) { 6 | this.api_key = api_key; 7 | 8 | if(preload_player) OfficialFM.is_player_loaded(); 9 | } 10 | 11 | OfficialFM.VERSION = '0.0.1'; 12 | OfficialFM.API_URL = 'http://api.official.fm/'; 13 | 14 | OfficialFM.LOADING_TRIES_INTERVAL = 250; /* in ms */ 15 | OfficialFM.MAX_LOADING_TRIES = 30000 / OfficialFM.LOADING_TRIES_INTERVAL; /* try loading for 30 seconds */ 16 | 17 | OfficialFM.player_loading = false; 18 | OfficialFM.loading_tries = 0; 19 | 20 | OfficialFM.is_player_loaded = function () { 21 | if (typeof OfficialFM.Player == "undefined") { 22 | if (!OfficialFM.player_loading) { 23 | OfficialFM.player_loading = true; 24 | $.getScript("https://github.com/officialfm/officialfm-javascript/raw/master/player_api.js"); 25 | } 26 | return false; 27 | } 28 | return true; 29 | } 30 | 31 | OfficialFM.with_player = function (callback) { 32 | if(!callback) return; 33 | 34 | if(OfficialFM.is_player_loaded()) { 35 | callback(); 36 | } else { 37 | if(OfficialFM.loading_tries < OfficialFM.MAX_LOADING_TRIES) { 38 | OfficialFM.loading_tries++; 39 | setTimeout(function () { 40 | OfficialFM.with_player(callback); 41 | }, OfficialFM.LOADING_TRIES_INTERVAL); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * Internal function used to query the server easily 48 | */ 49 | OfficialFM.prototype.call_api = function(sub_url, callback, other_params) { 50 | var default_params = { 51 | key: this.api_key, 52 | format: 'json' 53 | } 54 | var params = default_params; 55 | for (attrname in other_params) { 56 | if(attrname == 'limit') { 57 | params['api_max_responses'] = other_params[attrname]; 58 | } else if(attrname == 'embed') { 59 | params['api_embed_codes'] = other_params[attrname]; 60 | } else { 61 | params[attrname] = other_params[attrname]; 62 | } 63 | } 64 | 65 | var url = OfficialFM.API_URL + sub_url; 66 | $.ajax({ 67 | url: url, 68 | data: params, 69 | dataType: 'jsonp', 70 | success: callback 71 | }); 72 | } 73 | 74 | /** 75 | * Create an Official.fm player 76 | * 77 | * Valid options: 78 | * - container_id : string : id of the container. ex : 'player_container' 79 | * - type : string : content type : track/playlist (todo : user, dynamic tracklist..) 80 | * - id : content id : track/playlist's id 81 | * - aspect : optional. string : aspect of player : [large], standard, artwork, small, mini. ex : 'artwork' 82 | * - skin : optional. number : skin id (null = default skin). ex : 123 83 | * - width : optional. string or number : width of player in pixel - or percentage when used with %. (valid only for artwork & small players). ex : '300' or '60%' 84 | * - onReady : listener with no param. Called when player is ready to play. 85 | * - onPlay : listener with 1 param track_id. Called when a track starts playing 86 | * - onPause : listener with 1 param track_id. Called when a track is being paused 87 | * - onProgress : listener with 1 param object {played_seconds, played_percent, total}. Called twice per second minimum. 88 | * - onChangeTrack : listener with 1 param track_id. Called when player switch to another track 89 | * - onChangeTracklist : listener with no param. Called when player switch to another tracklist 90 | * - onTracklistEnd : listener with no param. Called when tracklist's end is reached. 91 | * 92 | * Subsequent calls to player() with the same div_id will replace 93 | * previous instances of the player hosted in the given div 94 | * 95 | */ 96 | OfficialFM.prototype.player = function (options, callback) { 97 | if(!options.hasOwnProperty('container_id')) { 98 | options.container_id = 'player_container'; 99 | } 100 | 101 | if(!options.hasOwnProperty('type')) { 102 | options.type = 'track'; 103 | } 104 | 105 | if(!options.hasOwnProperty('aspect')) { 106 | options.aspect = 'small'; 107 | } 108 | 109 | OfficialFM.with_player(function() { 110 | var _player = OfficialFM.Player.create(options); 111 | /* Hack to add play_track to a player */ 112 | _player.play_track = function(track_id) { 113 | this.play(track_id, OfficialFM.Player.build_feed('track', track_id)); 114 | } 115 | callback(_player); 116 | }); 117 | } 118 | 119 | /** 120 | * Create an Official.fm track player 121 | * 122 | * @param div_id The ID of the div the player will be put in 123 | * @param track_id content id : The ID of the track that should be played 124 | * @param options : 125 | * - callback: function taking the player object as an argument 126 | * - all other options valid in OfficialFM.player() 127 | * 128 | * Subsequent calls to track_player() with the same div_id will replace 129 | * previous instances of the player hosted in the given div 130 | * 131 | * Example: 132 | * ofm.track_player('player_div', 226556) 133 | */ 134 | OfficialFM.prototype.track_player = function (div_id, track_id, options) { 135 | if(!options) options = {}; 136 | options.container_id = div_id; 137 | options.type = 'track'; 138 | options.id = track_id; 139 | if(!options.hasOwnProperty('callback')) options.callback = function() {}; 140 | 141 | this.player(options, options.callback); 142 | } 143 | 144 | /** 145 | * Create an Official.fm playlist player 146 | * 147 | * @param div_id The ID of the div the player will be put in 148 | * @param track_id content id : The ID of the track that should be played 149 | * @param options : 150 | * - callback: function taking the player object as an argument 151 | * - all other options valid in OfficialFM.player() 152 | * 153 | * Subsequent calls to playlist_player() with the same div_id will replace 154 | * previous instances of the player hosted in the given div 155 | */ 156 | OfficialFM.prototype.playlist_player = function (div_id, playlist_id, options) { 157 | if(!options) options = {}; 158 | options.container_id = div_id; 159 | options.type = 'playlist'; 160 | options.id = playlist_id; 161 | if(!options.hasOwnProperty('callback')) options.callback = function() {}; 162 | 163 | this.player(options, options.callback); 164 | } 165 | 166 | /* ==================== users functions ===================== */ 167 | 168 | /** 169 | * Search for users 170 | * 171 | * @param string search_param: a search parameter (eg + name of the user) 172 | * @param int limit (50) limit per page 173 | * @User list 174 | */ 175 | OfficialFM.prototype.users = function (search_term, callback, options) { 176 | this.call_api('search/users/' + encodeURI(search_term), callback, options); 177 | } 178 | 179 | OfficialFM.prototype.each_user = function (search_term, callback, options) { 180 | this.users(search_term, function(users) { $.each(users, callback) }, options); 181 | } 182 | 183 | /** 184 | * Retrieve information about a specific user 185 | * 186 | * @param string user_id: id or login 187 | * @a User object 188 | */ 189 | OfficialFM.prototype.user = function (user_id, callback, options) { 190 | this.call_api('user/' + user_id, function(data) { callback(data[0]); }, {}); 191 | } 192 | 193 | /** 194 | * Retrieve a list of the tracks of this user 195 | * 196 | * @param string user_id: id or login 197 | * @param integer limit (50) limit per page 198 | * @param bool embed (false) should embed codes be included in the response 199 | * @array Track list 200 | */ 201 | OfficialFM.prototype.user_tracks = function (user_id, callback, options) { 202 | this.call_api('user/' + user_id + '/tracks', callback, options); 203 | } 204 | 205 | OfficialFM.prototype.each_user_track = function (search_term, callback, options) { 206 | this.user_tracks(search_term, function(user_tracks) { $.each(user_tracks, callback) }, options); 207 | } 208 | 209 | /** 210 | * Retrieve a list of the playlists of this user 211 | * 212 | * @param string user_id: id or login 213 | * @param integer limit (50) limit per page 214 | * @param bool embed (false) should embed codes be included in the response 215 | * @array Playlist list 216 | */ 217 | OfficialFM.prototype.user_playlists = function (user_id, callback, options) { 218 | this.call_api('user/' + user_id + '/playlists', callback, options); 219 | } 220 | 221 | OfficialFM.prototype.each_user_playlists = function (search_term, callback, options) { 222 | this.user_playlists(search_term, function(user_playlists) { $.each(user_playlists, callback) }, options); 223 | } 224 | 225 | /** 226 | * Retrieve a list of the subscribers of this user 227 | * 228 | * @param string user_id: id or login 229 | * @param integer limit (50) limit per page 230 | * @array User list 231 | */ 232 | OfficialFM.prototype.user_subscribers = function (user_id, callback, options) { 233 | this.call_api('user/' + user_id + '/subscribers', callback, options); 234 | } 235 | 236 | OfficialFM.prototype.each_user_subscribers = function (search_term, callback, options) { 237 | this.user_subscribers(search_term, function(user_subscribers) { $.each(user_subscribers, callback) }, options); 238 | } 239 | 240 | /** 241 | * Retrieve a list of the subscriptions of this user 242 | * 243 | * @param string user_id: id or login 244 | * @param integer limit (50) limit per page 245 | * @array User list 246 | */ 247 | OfficialFM.prototype.user_subscriptions = function (user_id, callback, options) { 248 | this.call_api('user/' + user_id + '/subscriptions', callback, options); 249 | } 250 | 251 | OfficialFM.prototype.each_user_subscriptions = function (search_term, callback, options) { 252 | this.user_subscriptions(search_term, function(user_subscriptions) { $.each(user_subscriptions, callback) }, options); 253 | } 254 | 255 | /** 256 | * Retrieve a list of the contacts of this user 257 | * 258 | * @param string user_id: id or login 259 | * @param integer limit (50) limit per page 260 | * @param bool embed (false) should embed codes be included in the response 261 | * @array User list 262 | */ 263 | OfficialFM.prototype.user_contacts = function (user_id, callback, options) { 264 | this.call_api('user/' + user_id + '/contacts', callback, options); 265 | } 266 | 267 | OfficialFM.prototype.each_user_contacts = function (search_term, callback, options) { 268 | this.user_contacts(search_term, function(user_contacts) { $.each(user_contacts, callback) }, options); 269 | } 270 | 271 | /* ==================== tracks functions ===================== */ 272 | 273 | /** 274 | * Search for tracks 275 | * 276 | * @param string search_param: a search parameter (eg + name of the track) 277 | * @param integer limit (50) limit per page (optional) 278 | * @array Track list 279 | */ 280 | OfficialFM.prototype.tracks = function (search_term, callback, options) { 281 | this.call_api('search/tracks/' + encodeURI(search_term), callback, options); 282 | } 283 | 284 | OfficialFM.prototype.each_track = function (search_term, callback, options) { 285 | this.tracks(search_term, function(tracks) { $.each(tracks, callback) }, options); 286 | } 287 | 288 | /** 289 | * Retrieve information about a specific track 290 | * 291 | * Note: http://official + fm/developers/simple_api#track_show 292 | * says that api_max_responses is a valid parameter + Why escapes me + 293 | * 294 | * @param string track_id: id 295 | * @param bool embed (false) should embed codes be included in the response 296 | * @array Track 297 | */ 298 | OfficialFM.prototype.track = function (track_id, callback, options) { 299 | this.call_api('track/' + track_id, function(data) { callback(data[0]); }, options); 300 | } 301 | 302 | /** 303 | * Retrieve users that have voted for this track 304 | * 305 | * @param string track_id: id 306 | * @param integer limit (50) limit per page 307 | * @array User list 308 | */ 309 | OfficialFM.prototype.track_votes = function (track_id, callback, options) { 310 | this.call_api('track/' + track_id + '/votes', callback, options); 311 | } 312 | 313 | OfficialFM.prototype.each_track_vote = function (search_term, callback, options) { 314 | this.track_votes(search_term, function(track_votes) { $.each(track_votes, callback) }, options); 315 | } 316 | 317 | /** 318 | * Retrieve 200 tracks of selected chart 319 | * 320 | * @param string charting: 'today', 'week', 'month', 'year' or 'all_time' 321 | * @param string genre: Genre string ('Electronic', 'Rock', 'Jazz', ...) (optional) 322 | * @param string country: ISO country id (CH, FR, UK) (optional) 323 | * @param bool embed (false) should embed codes be included in the response (optional) 324 | * @param integer limit (200) limit per page (optional) 325 | * @array Track list 326 | */ 327 | OfficialFM.prototype.charts = function (charting, callback, options) { 328 | this.call_api('tracks/charts', callback, $.extend({}, options, { charting: charting })); 329 | } 330 | 331 | OfficialFM.prototype.each_chart = function (search_term, callback, options) { 332 | this.charts(search_term, function(charts) { $.each(charts, callback) }, options); 333 | } 334 | 335 | /** 336 | * Retrieve 200 latest tracks 337 | * 338 | * @param string genre: Genre string (Electronic, Rock, Jazz, + + + ) (optional) 339 | * @param string country: ISO country id (CH, FR, UK) (optional) 340 | * @param bool embed (false) should embed codes be included in the response (optional) 341 | * @param integer limit (200) limit per page (optional) 342 | * @array Track list 343 | */ 344 | OfficialFM.prototype.latest = function (callback, options) { 345 | this.call_api('tracks/latest', callback, options); 346 | } 347 | 348 | OfficialFM.prototype.each_latest = function (callback, options) { 349 | this.latest(function(latests) { $.each(latests, callback) }, options); 350 | } 351 | 352 | /* ==================== playlists functions ===================== */ 353 | 354 | /** 355 | * Search for playlists 356 | * 357 | * @param string search_param: a search parameter (eg + name of the playlist) 358 | * @param integer limit (50) limit per page (optional) 359 | * @array Playlist list 360 | */ 361 | OfficialFM.prototype.playlists = function (search_param, callback, options) { 362 | this.call_api('search/playlists/' + encodeURI(search_param), function(result) { 363 | callback($.map(result, OfficialFM.improve_playlist)); 364 | }, options); 365 | } 366 | 367 | OfficialFM.prototype.each_playlist = function (search_term, callback, options) { 368 | this.playlists(search_term, function(playlists) { $.each(playlists, callback) }, options); 369 | } 370 | 371 | /** 372 | * Retrieve information about a specific playlist 373 | * 374 | * @param string playlist_id: id 375 | * @param bool embed (false) should embed codes be included in the response 376 | * @array Playlist 377 | */ 378 | OfficialFM.prototype.playlist = function (playlist_id, callback, options) { 379 | this.call_api('playlist/' + playlist_id, function(result) { 380 | callback(OfficialFM.improve_playlist(result[0])); 381 | }, options); 382 | } 383 | 384 | /** 385 | * Retrieve users that have voted for this playlist 386 | * 387 | * @param string playlist_id: id 388 | * @param integer limit (50) limit per page 389 | * @array User list 390 | */ 391 | OfficialFM.prototype.playlist_votes = function (playlist_id, callback, options) { 392 | this.call_api('playlist/' + playlist_id + '/votes', callback, options); 393 | } 394 | 395 | OfficialFM.prototype.each_playlist_vote = function (search_term, callback, options) { 396 | this.playlist_votes(search_term, function(playlist_votes) { $.each(playlist_votes, callback) }, options); 397 | } 398 | 399 | /* Hack to improve playlist id lists (see issue #4 in sandbox-api) */ 400 | OfficialFM.improve_playlist = function (playlist) { 401 | playlist.running_time = playlist['length']; 402 | /* TODO: actually improve the item list ha*/ 403 | return playlist; 404 | } 405 | 406 | -------------------------------------------------------------------------------- /scripts/madcat.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Concatenates all the JSMad sources into a single .js file 4 | 5 | cat src/mad.js src/rq_table.js src/imdct_s.js src/huffman.js src/bit.js src/stream.js src/layer3.js src/frame.js src/synth.js src/arraybuffer/bytestream.js src/arraybuffer/filestream.js src/arraybuffer/substream.js src/arraybuffer/arraystream.js src/arraybuffer/ajaxstream.js src/binarystring/bytestream.js src/binarystring/filestream.js src/binarystring/substream.js src/binarystring/stringstream.js src/binarystring/ajaxstream.js src/id3v22stream.js src/id3v23stream.js src/mp3file.js src/player.js 6 | -------------------------------------------------------------------------------- /scripts/sink.min.js: -------------------------------------------------------------------------------- 1 | (function(a){function b(a,c,d,e){var f=b.sinks,g;for(g in f)if(f.hasOwnProperty(g)&&f[g].enabled)try{return new f[g](a,c,d,e)}catch(h){}throw b.Error(2)}function c(){var a;for(a in c.prototype)c.prototype.hasOwnProperty(a)&&(this[a]=c.prototype[a]);this._listeners={}}function d(a){if(!d.hasOwnProperty(a))throw d(1);if(!(this instanceof d))return new d(a);var b;for(b in d[a])d[a].hasOwnProperty(b)&&(this[b]=d[a][b]);this.code=a}function e(a){this.boundTo=a,this.buffers=[],a.activeRecordings.push(this)}function f(){}function g(a,c,d,e){d=d||c.prototype,c.prototype=new b.SinkClass,c.prototype.type=a,c.enabled=!e;for(e in d)d.hasOwnProperty(e)&&(c.prototype[e]=d[e]);g[a]=c}c.prototype={_listeners:null,emit:function(a,b){if(this._listeners[a])for(var c=0;c=e&&b.splice(f--,1)}},writeBuffersSync:function(a){var b=this.syncBuffers,c=a.length,d=0,e=0;for(;d0||j===g){try{i=new Float32Array(j===g?a.preBufferSize*a.channelCount:a.forceBufferSize?h2e3&&(a._audio=e=new Audio,e.mozSetup(a.channelCount,a.sampleRate),c=0,a.emit("error",[b.Error(17)]))},1e3)),this._timers.push(b.doInterval(l,a.interval)),a._bufferFill=l,a._audio=e},{bufferSize:24576,preBufferSize:24576,forceBufferSize:!1,interval:20,kill:function(){while(this._timers.length)this._timers[0](),this._timers.splice(0,1);this.emit("kill")},getPlaybackTime:function(){return this._audio.mozCurrentSampleOffset()/this.channelCount}});var h=[];g("webkit",function(a,c,d,e){function k(a){var b=a.outputBuffer,c=b.numberOfChannels,d,e,g=b.length,h=b.size,i=new Array(c),j=new Float32Array(g*c),k;for(d=0;d=a.length-.5?a[0]:a[Math.round(b)]}),a("linear")}(),b.resample=function(a,c,d,e,f){var g=arguments.length,h=g===2?c:g===3?c/d:e/c*f/d,i=a.length,j=Math.ceil(i/h),k=new Float32Array(j),l,m;for(l=0,m=0;l 127) 53 | return iByte - 256; 54 | else 55 | return iByte; 56 | } 57 | 58 | this.getShortAt = function(iOffset, bBigEndian) { 59 | var iShort = bBigEndian ? 60 | (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1) 61 | : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset) 62 | if (iShort < 0) iShort += 65536; 63 | return iShort; 64 | } 65 | this.getSShortAt = function(iOffset, bBigEndian) { 66 | var iUShort = this.getShortAt(iOffset, bBigEndian); 67 | if (iUShort > 32767) 68 | return iUShort - 65536; 69 | else 70 | return iUShort; 71 | } 72 | this.getLongAt = function(iOffset, bBigEndian) { 73 | var iByte1 = this.getByteAt(iOffset), 74 | iByte2 = this.getByteAt(iOffset + 1), 75 | iByte3 = this.getByteAt(iOffset + 2), 76 | iByte4 = this.getByteAt(iOffset + 3); 77 | 78 | var iLong = bBigEndian ? 79 | (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4 80 | : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1; 81 | if (iLong < 0) iLong += 4294967296; 82 | return iLong; 83 | } 84 | this.getSLongAt = function(iOffset, bBigEndian) { 85 | var iULong = this.getLongAt(iOffset, bBigEndian); 86 | if (iULong > 2147483647) 87 | return iULong - 4294967296; 88 | else 89 | return iULong; 90 | } 91 | 92 | this.getStringAt = function(iOffset, iLength) { 93 | var aStr = []; 94 | 95 | var aBytes = this.getBytesAt(iOffset, iLength); 96 | for (var j=0; j < iLength; j++) { 97 | aStr[j] = String.fromCharCode(aBytes[j]); 98 | } 99 | return aStr.join(""); 100 | } 101 | 102 | this.getCharAt = function(iOffset) { 103 | return String.fromCharCode(this.getByteAt(iOffset)); 104 | } 105 | this.toBase64 = function() { 106 | return window.btoa(data); 107 | } 108 | this.fromBase64 = function(strBase64) { 109 | data = window.atob(strBase64); 110 | } 111 | } 112 | 113 | 114 | var BinaryAjax = (function() { 115 | 116 | function createRequest() { 117 | var oHTTP = null; 118 | if (window.ActiveXObject) { 119 | oHTTP = new ActiveXObject("Microsoft.XMLHTTP"); 120 | } else if (window.XMLHttpRequest) { 121 | oHTTP = new XMLHttpRequest(); 122 | } 123 | return oHTTP; 124 | } 125 | 126 | function getHead(strURL, fncCallback, fncError) { 127 | var oHTTP = createRequest(); 128 | if (oHTTP) { 129 | if (fncCallback) { 130 | if (typeof(oHTTP.onload) != "undefined") { 131 | oHTTP.onload = function() { 132 | if (oHTTP.status === "200") { 133 | fncCallback(this); 134 | } else { 135 | if (fncError) fncError(); 136 | } 137 | oHTTP = null; 138 | }; 139 | } else { 140 | oHTTP.onreadchange = function() { 141 | if (oHTTP.readyState === 4) { 142 | if (oHTTP.status === "200") { 143 | fncCallback(this); 144 | } else { 145 | if (fncError) fncError(); 146 | } 147 | oHTTP = null; 148 | } 149 | }; 150 | } 151 | } 152 | oHTTP.open("HEAD", strURL, true); 153 | oHTTP.send(null); 154 | } else { 155 | if (fncError) fncError(); 156 | } 157 | } 158 | 159 | function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize) { 160 | var oHTTP = createRequest(); 161 | if (oHTTP) { 162 | 163 | var iDataOffset = 0; 164 | if (aRange && !bAcceptRanges) { 165 | iDataOffset = aRange[0]; 166 | } 167 | var iDataLen = 0; 168 | if (aRange) { 169 | iDataLen = aRange[1]-aRange[0]+1; 170 | } 171 | 172 | if (fncCallback) { 173 | if (typeof(oHTTP.onload) != "undefined") { 174 | oHTTP.onload = function() { 175 | if (oHTTP.status === "200" || oHTTP.status === "206" || oHTTP.status === "0") { 176 | oHTTP.binaryResponse = new BinaryFile(oHTTP.responseText, iDataOffset, iDataLen); 177 | oHTTP.fileSize = iFileSize || oHTTP.getResponseHeader("Content-Length"); 178 | fncCallback(oHTTP); 179 | } else { 180 | if (fncError) fncError(); 181 | } 182 | oHTTP = null; 183 | }; 184 | } else { 185 | oHTTP.onreadchange = function() { 186 | if (oHTTP.readyState === 4) { 187 | if (oHTTP.status === "200" || oHTTP.status === "206" || oHTTP.status === "0") { 188 | // IE6 craps if we try to extend the XHR object 189 | var oRes = { 190 | status : oHTTP.status, 191 | // IE needs responseBody, Chrome/Safari needs responseText 192 | binaryResponse : new BinaryFile( 193 | typeof oHTTP.responseBody === "unknown" ? oHTTP.responseBody : oHTTP.responseText, iDataOffset, iDataLen 194 | ), 195 | fileSize : iFileSize || oHTTP.getResponseHeader("Content-Length") 196 | }; 197 | fncCallback(oRes); 198 | } else { 199 | if (fncError) fncError(); 200 | } 201 | oHTTP = null; 202 | } 203 | }; 204 | } 205 | } 206 | oHTTP.open("GET", strURL, true); 207 | 208 | if (oHTTP.overrideMimeType) oHTTP.overrideMimeType('text/plain; charset=x-user-defined'); 209 | 210 | if (aRange && bAcceptRanges) { 211 | oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]); 212 | } 213 | 214 | oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT"); 215 | 216 | oHTTP.send(null); 217 | } else { 218 | if (fncError) fncError(); 219 | } 220 | } 221 | 222 | return function(strURL, fncCallback, fncError, aRange) { 223 | 224 | if (aRange) { 225 | getHead( 226 | strURL, 227 | function(oHTTP) { 228 | var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"),10); 229 | var strAcceptRanges = oHTTP.getResponseHeader("Accept-Ranges"); 230 | 231 | var iStart, iEnd; 232 | iStart = aRange[0]; 233 | if (aRange[0] < 0) 234 | iStart += iLength; 235 | iEnd = iStart + aRange[1] - 1; 236 | 237 | sendRequest(strURL, fncCallback, fncError, [iStart, iEnd], (strAcceptRanges === "bytes"), iLength); 238 | } 239 | ); 240 | 241 | } else { 242 | sendRequest(strURL, fncCallback, fncError); 243 | } 244 | } 245 | 246 | }()); 247 | 248 | /* 249 | document.write( 250 | "\r\n" 258 | ); 259 | */ 260 | 261 | document.write( 262 | "\r\n" 278 | ); 279 | -------------------------------------------------------------------------------- /src/binarystring/ajaxstream.js: -------------------------------------------------------------------------------- 1 | Mad.BinaryStrings.AjaxStream = function(url) { 2 | this.offset = 0; 3 | var request = window.XMLHttpRequest ? new XMLHttpRequest() : ActiveXObject("Microsoft.XMLHTTP"); 4 | 5 | // pseudo-binary XHR 6 | request.overrideMimeType('text/plain; charset=x-user-defined'); 7 | request.open('GET', url); 8 | 9 | this.request = request; 10 | this.amountRead = 0; 11 | this.inProgress = true; 12 | this.callbacks = []; 13 | 14 | var self = this; 15 | 16 | var iteration = 0; 17 | 18 | var ochange = function () { 19 | iteration += 1; 20 | if ((self.callbacks.length > 0 && iteration % 64 === 0) || iteration % 256 === 0) { 21 | self.updateBuffer(); 22 | 23 | var newCallbacks = []; 24 | 25 | for (var i = 0; i < self.callbacks.length; i++) { 26 | var callback = self.callbacks[i]; 27 | 28 | if (callback[0] < self.amountRead) { 29 | try { 30 | callback[1](); 31 | } catch (e) { 32 | console.log(e); 33 | } 34 | } else { 35 | newCallbacks.push(callback); 36 | } 37 | } 38 | 39 | self.callbacks = newCallbacks; 40 | } 41 | 42 | if (request.readyState === 4) { 43 | self.amountRead = self.contentLength; 44 | for (var i = 0; i < self.callbacks.length; i++) { 45 | var callback = self.callbacks[i]; 46 | callback[1](); 47 | } 48 | 49 | window.clearInterval(self.timer); 50 | 51 | self.inProgress = false; 52 | } 53 | } 54 | 55 | request.onreadchange = ochange; 56 | 57 | this.timer = window.setInterval(ochange, 250); 58 | 59 | request.send(null); 60 | } 61 | 62 | Mad.BinaryStrings.AjaxStream.prototype = new Mad.BinaryStrings.ByteStream(); 63 | 64 | Mad.BinaryStrings.AjaxStream.prototype.updateBuffer = function() { 65 | if (!this.finalAmount) { 66 | this.arrayBuffer = this.request.mozResponseArrayBuffer; 67 | if(this.arrayBuffer) { 68 | this.byteBuffer = new Uint8Array(this.arrayBuffer); 69 | this.amountRead = this.arrayBuffer.byteLength; 70 | } else { 71 | this.buffer = this.request.responseText 72 | this.amountRead = this.buffer.length; 73 | } 74 | 75 | this.contentLength = this.request.getResponseHeader('Content-Length'); 76 | if(!this.contentLength) { 77 | // if the server doesn't send a Content-Length Header, just use amountRead instead 78 | // it's less precise at first, but once everything is buffered it becomes accurate. 79 | this.contentLength = this.amountRead; 80 | } 81 | 82 | if (!this.inProgress) { 83 | this.finalAmount = true; 84 | } 85 | 86 | return true; 87 | } else { 88 | return false; 89 | } 90 | } 91 | 92 | Mad.BinaryStrings.AjaxStream.prototype.absoluteAvailable = function(n, updated) { 93 | if (n > this.amountRead) { 94 | if (updated) { 95 | throw new Error("buffer underflow with absoluteAvailable!"); 96 | } else if (this.updateBuffer()) { 97 | return this.absoluteAvailable(n, true); 98 | } else { 99 | return false; 100 | } 101 | } else { 102 | return true; 103 | } 104 | } 105 | 106 | Mad.BinaryStrings.AjaxStream.prototype.seek = function(n) { 107 | this.offset += n; 108 | } 109 | 110 | Mad.BinaryStrings.AjaxStream.prototype.read = function(n) { 111 | var result = this.peek(n); 112 | 113 | this.seek(n); 114 | 115 | return result; 116 | } 117 | 118 | Mad.BinaryStrings.AjaxStream.prototype.peek = function(n) { 119 | if (this.available(n)) { 120 | var offset = this.offset; 121 | 122 | var result = this.get(offset, n); 123 | 124 | return result; 125 | } else { 126 | throw new Error("buffer underflow with peek!"); 127 | } 128 | } 129 | 130 | Mad.BinaryStrings.AjaxStream.prototype.get = function(offset, length) { 131 | if (this.absoluteAvailable(offset + length)) { 132 | var tmpbuffer = ""; 133 | if(this.byteBuffer) { 134 | for(var i = offset; i < offset + length; i += 1) { 135 | tmpbuffer = tmpbuffer + String.fromCharCode(this.byteBuffer[i]); 136 | } 137 | } else { 138 | for(var i = offset; i < offset + length; i += 1) { 139 | tmpbuffer = tmpbuffer + String.fromCharCode(this.buffer.charCodeAt(i) & 0xff); 140 | } 141 | } 142 | return tmpbuffer; 143 | } else { 144 | throw new Error("buffer underflow with get!"); 145 | } 146 | } 147 | 148 | Mad.BinaryStrings.AjaxStream.prototype.getU8 = function(offset, bigEndian) { 149 | if(this.byteBuffer) { 150 | return this.byteBuffer[offset]; 151 | } 152 | 153 | return this.get(offset, 1).charCodeAt(0); 154 | } 155 | 156 | Mad.BinaryStrings.AjaxStream.prototype.requestAbsolute = function(n, callback) { 157 | if (n < this.amountRead) { 158 | callback(); 159 | } else { 160 | this.callbacks.push([n, callback]); 161 | } 162 | } 163 | 164 | Mad.BinaryStrings.AjaxStream.prototype.request = function(n, callback) { 165 | this.requestAbsolute(this.offset + n, callback); 166 | } 167 | -------------------------------------------------------------------------------- /src/binarystring/bytestream.js: -------------------------------------------------------------------------------- 1 | Mad.BinaryStrings.ByteStream = function(url) { } 2 | 3 | Mad.BinaryStrings.ByteStream.prototype.available = function(n) { 4 | return this.absoluteAvailable(this.offset + n); 5 | } 6 | 7 | Mad.BinaryStrings.ByteStream.prototype.strEquals = function (offset, string) { 8 | for (var i = 0; i < string.length; i++) { 9 | if(this.getU8(offset + i, false) != string.charCodeAt(i)) return false; 10 | } 11 | return true; 12 | } 13 | 14 | Mad.BinaryStrings.ByteStream.prototype.getU8 = function(offset, bigEndian) { 15 | var bytes = this.get(offset, 1); 16 | return bytes.charCodeAt(0); 17 | } 18 | 19 | Mad.BinaryStrings.ByteStream.prototype.getU16 = function(offset, bigEndian) { 20 | var bytes = this.get(offset, 2); 21 | 22 | if (!bigEndian) { 23 | bytes = bytes.reverse(); 24 | } 25 | 26 | return (bytes.charCodeAt(0) << 8) | bytes.charCodeAt(1); 27 | } 28 | 29 | Mad.BinaryStrings.ByteStream.prototype.getU24 = function(offset, bigEndian) { 30 | var bytes = this.get(offset, 3); 31 | 32 | if (!bigEndian) { 33 | bytes = bytes.reverse(); 34 | } 35 | 36 | return (bytes.charCodeAt(0) << 16) | (bytes.charCodeAt(1) << 8) | bytes.charCodeAt(2); 37 | } 38 | 39 | Mad.BinaryStrings.ByteStream.prototype.getU32 = function(offset, bigEndian) { 40 | var bytes = this.get(offset, 4); 41 | 42 | if (!bigEndian) { 43 | bytes = bytes.reverse(); 44 | } 45 | 46 | return (bytes.charCodeAt(0) << 24) | (bytes.charCodeAt(1) << 16) | (bytes.charCodeAt(2) << 8) | bytes.charCodeAt(3); 47 | } 48 | 49 | Mad.BinaryStrings.ByteStream.prototype.getI8 = function(offset, bigEndian) { 50 | return this.getU8(offset, bigEndian) - 128; // 2 ** 7 51 | } 52 | 53 | Mad.BinaryStrings.ByteStream.prototype.getI16 = function(offset, bigEndian) { 54 | return this.getU16(offset, bigEndian) - 65536; // 2 ** 15 55 | } 56 | 57 | Mad.BinaryStrings.ByteStream.prototype.getI32 = function(offset, bigEndian) { 58 | return this.getU32(offset, bigEndian) - 2147483648; // 2 ** 31 59 | } 60 | 61 | Mad.BinaryStrings.ByteStream.prototype.getSyncInteger = function(offset) { 62 | var bytes = this.get(offset, 4); 63 | 64 | return (bytes.charCodeAt(0) << 21) | (bytes.charCodeAt(1) << 14) | (bytes.charCodeAt(2) << 7) | bytes.charCodeAt(3); 65 | } 66 | 67 | Mad.BinaryStrings.ByteStream.prototype.peekU8 = function(bigEndian) { 68 | return this.getU8(this.offset, bigEndian); 69 | } 70 | 71 | Mad.BinaryStrings.ByteStream.prototype.peekU16 = function(bigEndian) { 72 | return this.getU16(this.offset, bigEndian); 73 | } 74 | 75 | Mad.BinaryStrings.ByteStream.prototype.peekU24 = function(bigEndian) { 76 | return this.getU24(this.offset, bigEndian); 77 | } 78 | 79 | Mad.BinaryStrings.ByteStream.prototype.peekU32 = function(bigEndian) { 80 | return this.getU32(this.offset, bigEndian); 81 | } 82 | 83 | Mad.BinaryStrings.ByteStream.prototype.peekI8 = function(bigEndian) { 84 | return this.getI8(this.offset, bigEndian); 85 | } 86 | 87 | Mad.BinaryStrings.ByteStream.prototype.peekI16 = function(bigEndian) { 88 | return this.getI16(this.offset, bigEndian); 89 | } 90 | 91 | Mad.BinaryStrings.ByteStream.prototype.peekI32 = function(bigEndian) { 92 | return this.getI32(this.offset, bigEndian); 93 | } 94 | 95 | Mad.BinaryStrings.ByteStream.prototype.peekSyncInteger = function() { 96 | return this.getSyncInteger(this.offset); 97 | } 98 | 99 | Mad.BinaryStrings.ByteStream.prototype.readU8 = function(bigEndian) { 100 | var result = this.peekU8(bigEndian); 101 | 102 | this.seek(1); 103 | 104 | return result; 105 | } 106 | 107 | Mad.BinaryStrings.ByteStream.prototype.readU16 = function(bigEndian) { 108 | var result = this.peekU16(bigEndian); 109 | 110 | this.seek(2); 111 | 112 | return result; 113 | } 114 | 115 | Mad.BinaryStrings.ByteStream.prototype.readU24 = function(bigEndian) { 116 | var result = this.peekU24(bigEndian); 117 | 118 | this.seek(3); 119 | 120 | return result; 121 | } 122 | 123 | Mad.BinaryStrings.ByteStream.prototype.readU32 = function(bigEndian) { 124 | var result = this.peekU32(bigEndian); 125 | 126 | this.seek(4); 127 | 128 | return result; 129 | } 130 | 131 | Mad.BinaryStrings.ByteStream.prototype.readI8 = function(bigEndian) { 132 | var result = this.peekI8(bigEndian); 133 | 134 | this.seek(1); 135 | 136 | return result; 137 | } 138 | 139 | Mad.BinaryStrings.ByteStream.prototype.readI16 = function(bigEndian) { 140 | var result = this.peekI16(bigEndian); 141 | 142 | this.seek(2); 143 | 144 | return result; 145 | } 146 | 147 | Mad.BinaryStrings.ByteStream.prototype.readI32 = function(bigEndian) { 148 | var result = this.peekI32(bigEndian); 149 | 150 | this.seek(4); 151 | 152 | return result; 153 | } 154 | 155 | Mad.BinaryStrings.ByteStream.prototype.readSyncInteger = function() { 156 | var result = this.getSyncInteger(this.offset); 157 | 158 | this.seek(4); 159 | 160 | return result; 161 | } 162 | -------------------------------------------------------------------------------- /src/binarystring/filestream.js: -------------------------------------------------------------------------------- 1 | Mad.BinaryStrings.FileStream = function(file, callback) { 2 | this.offset = 0; 3 | var self = this, reader = new FileReader(); 4 | 5 | reader.onload = function () { 6 | self['buffer'] = reader.result; 7 | self['amountRead'] = self['buffer'].length; 8 | self['contentLength'] = self['buffer'].length; 9 | 10 | self.length = self['amountRead']; 11 | 12 | callback(self); 13 | } 14 | 15 | reader.onerror = function () { 16 | console.log("Error!"); 17 | } 18 | 19 | reader.readAsBinaryString(file); 20 | } 21 | 22 | Mad.BinaryStrings.FileStream.prototype = new Mad.BinaryStrings.ByteStream(); 23 | 24 | Mad.BinaryStrings.FileStream.prototype.absoluteAvailable = function(n, updated) { 25 | return n < this['amountRead']; 26 | } 27 | 28 | Mad.BinaryStrings.FileStream.prototype.substream = function (offset, length) { 29 | return new Mad.BinaryStrings.SubStream(this, offset, length); 30 | } 31 | 32 | Mad.BinaryStrings.FileStream.prototype.seek = function(n) { 33 | this['offset'] += n; 34 | } 35 | 36 | Mad.BinaryStrings.FileStream.prototype.read = function(n) { 37 | var result = this.peek(n); 38 | 39 | this.seek(n); 40 | 41 | return result; 42 | } 43 | 44 | Mad.BinaryStrings.FileStream.prototype.peek = function(n) { 45 | if (this.available(n)) { 46 | var offset = this['offset']; 47 | 48 | var result = this.get(offset, n); 49 | 50 | return result; 51 | } else { 52 | throw 'TODO: THROW PEEK ERROR!'; 53 | } 54 | } 55 | 56 | Mad.BinaryStrings.FileStream.prototype.get = function(offset, length) { 57 | if (this.absoluteAvailable(offset + length)) { 58 | return this['buffer'].slice(offset, offset + length); 59 | } else { 60 | throw 'TODO: THROW GET ERROR!'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/binarystring/stringstream.js: -------------------------------------------------------------------------------- 1 | Mad.BinaryStrings.StringStream = function(string) { 2 | this.offset = 0; 3 | this.buffer = string; 4 | this.amountRead = string.length; 5 | this.length = string.length; 6 | } 7 | 8 | Mad.BinaryStrings.StringStream.prototype = new Mad.BinaryStrings.ByteStream(); 9 | 10 | Mad.BinaryStrings.StringStream.prototype.absoluteAvailable = function(n, updated) { 11 | return n < this['amountRead']; 12 | } 13 | 14 | Mad.BinaryStrings.StringStream.prototype.seek = function(n) { 15 | this['offset'] += n; 16 | } 17 | 18 | Mad.BinaryStrings.StringStream.prototype.read = function(n) { 19 | var result = this.peek(n); 20 | 21 | this.seek(n); 22 | 23 | return result; 24 | } 25 | 26 | Mad.BinaryStrings.StringStream.prototype.peek = function(n) { 27 | if (this.available(n)) { 28 | var offset = this['offset']; 29 | 30 | var result = this.get(offset, n); 31 | 32 | return result; 33 | } else { 34 | throw 'TODO: THROW PEEK ERROR!'; 35 | } 36 | } 37 | 38 | Mad.BinaryStrings.StringStream.prototype.get = function(offset, length) { 39 | if (this.absoluteAvailable(offset + length)) { 40 | return this['buffer'].slice(offset, offset + length); 41 | } else { 42 | throw 'TODO: THROW GET ERROR!'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/binarystring/substream.js: -------------------------------------------------------------------------------- 1 | 2 | Mad.BinaryStrings.SubStream = function(stream, offset, length) { 3 | this.offset = 0; 4 | this.start = offset; 5 | this.parentStream = stream; 6 | this.length = length; 7 | } 8 | 9 | Mad.BinaryStrings.SubStream.prototype = new Mad.BinaryStrings.ByteStream; 10 | 11 | Mad.BinaryStrings.SubStream.prototype.substream = function (offset, length) { 12 | return new Mad.BinaryStrings.SubStream(this.parentStream, this.start + offset, length); 13 | } 14 | 15 | 16 | Mad.BinaryStrings.SubStream.prototype.absoluteAvailable = function(n) { 17 | return this.parentStream.absoluteAvailable(this.start + n); 18 | } 19 | 20 | Mad.BinaryStrings.SubStream.prototype.seek = function(n) { 21 | this.offset += n; 22 | } 23 | 24 | Mad.BinaryStrings.SubStream.prototype.read = function(n) { 25 | var result = this.peek(n); 26 | 27 | this.seek(n); 28 | 29 | return result; 30 | } 31 | 32 | Mad.BinaryStrings.SubStream.prototype.peek = function(n) { 33 | return this.get(this.offset, n); 34 | } 35 | 36 | Mad.BinaryStrings.SubStream.prototype.get = function(offset, length) { 37 | return this.parentStream.get(this.start + offset, length); 38 | } 39 | 40 | Mad.BinaryStrings.SubStream.prototype.slice = function(start, end) { 41 | return this.parentStream.get(this.start + start, end - start); 42 | } 43 | 44 | Mad.BinaryStrings.SubStream.prototype.requestAbsolute = function(n, callback) { 45 | this.parentStream.requestAbsolute(this.start + n) 46 | } 47 | 48 | Mad.BinaryStrings.SubStream.prototype.request = function(n, callback) { 49 | this.parentStream.requestAbsolute(this.start + this.offset + n) 50 | } 51 | -------------------------------------------------------------------------------- /src/bit.js: -------------------------------------------------------------------------------- 1 | 2 | // Well duh. 3 | var CHAR_BIT = 8; 4 | 5 | /* 6 | * NAME: bit.init() 7 | * DESCRIPTION: initialize bit pointer struct 8 | */ 9 | Mad.Bit = function (stream, offset) { 10 | if (typeof(stream) === 'string') { 11 | this.stream = new Mad.BinaryStrings.StringStream(stream); 12 | } else if(stream instanceof Uint8Array) { 13 | this.stream = new Mad.ArrayBuffers.ArrayStream(stream); 14 | } else { 15 | this.stream = stream; 16 | } 17 | 18 | this.offset = offset; 19 | 20 | this.cache = 0; 21 | this.left = CHAR_BIT; 22 | } 23 | 24 | Mad.Bit.prototype.clone = function() { 25 | var c = new Mad.Bit(this.stream, this.offset); 26 | 27 | c.cache = this.cache; 28 | c.left = this.left; 29 | 30 | return c; 31 | } 32 | 33 | /* 34 | * NAME: bit.length() 35 | * DESCRIPTION: return number of bits between start and end points 36 | */ 37 | Mad.Bit.prototype.length = function(end) { 38 | return this.left + CHAR_BIT * (end.offset - (this.offset + 1)) + (CHAR_BIT - end.left); 39 | } 40 | 41 | /* 42 | * NAME: bit.nextbyte() 43 | * DESCRIPTION: return pointer to next unprocessed byte 44 | */ 45 | Mad.Bit.prototype.nextbyte = function() { 46 | return this.left === CHAR_BIT ? this.offset : this.offset + 1; 47 | } 48 | 49 | /* 50 | * NAME: bit.skip() 51 | * DESCRIPTION: advance bit pointer 52 | */ 53 | Mad.Bit.prototype.skip = function(len) { 54 | this.offset += (len / CHAR_BIT) >> 0; // javascript trick to get integer divison 55 | this.left -= len % CHAR_BIT; 56 | 57 | if (this.left > CHAR_BIT) { 58 | this.offset++; 59 | this.left += CHAR_BIT; 60 | } 61 | 62 | if (this.left < CHAR_BIT) { 63 | this.cache = this.stream.getU8(this.offset); 64 | } 65 | } 66 | 67 | /* 68 | * NAME: bit.read() 69 | * DESCRIPTION: read an arbitrary number of bits and return their UIMSBF value 70 | */ 71 | Mad.Bit.prototype.read = function(len) { 72 | if(len > 16) { 73 | return this.readBig(len); 74 | } 75 | 76 | var value = 0; 77 | 78 | if (this.left === CHAR_BIT) { 79 | this.cache = this.stream.getU8(this.offset); 80 | } 81 | 82 | if (len < this.left) { 83 | value = (this.cache & ((1 << this.left) - 1)) >> (this.left - len); 84 | this.left -= len; 85 | 86 | return value; 87 | } 88 | 89 | /* remaining bits in current byte */ 90 | value = this.cache & ((1 << this.left) - 1); 91 | len -= this.left; 92 | 93 | this.offset++; 94 | this.left = CHAR_BIT; 95 | 96 | /* more bytes */ 97 | while (len >= CHAR_BIT) { 98 | value = (value << CHAR_BIT) | this.stream.getU8(this.offset++); 99 | len -= CHAR_BIT; 100 | } 101 | 102 | if (len > 0) { 103 | this.cache = this.stream.getU8(this.offset); 104 | 105 | value = (value << len) | (this.cache >> (CHAR_BIT - len)); 106 | this.left -= len; 107 | } 108 | 109 | return value; 110 | } 111 | 112 | Mad.Bit.prototype.readBig = function(len) { 113 | var value = 0; 114 | 115 | if (this.left === CHAR_BIT) { 116 | this.cache = this.stream.getU8(this.offset); 117 | } 118 | 119 | if (len < this.left) { 120 | value = (this.cache & ((1 << this.left) - 1)) >> (this.left - len); 121 | this.left -= len; 122 | 123 | return value; 124 | } 125 | 126 | /* remaining bits in current byte */ 127 | value = this.cache & ((1 << this.left) - 1); 128 | len -= this.left; 129 | 130 | this.offset++; 131 | this.left = CHAR_BIT; 132 | 133 | /* more bytes */ 134 | while (len >= CHAR_BIT) { 135 | value = Mad.bitwiseOr(Mad.lshift(value, CHAR_BIT), this.stream.getU8(this.offset++)); 136 | len -= CHAR_BIT; 137 | } 138 | 139 | if (len > 0) { 140 | this.cache = this.stream.getU8(this.offset); 141 | 142 | value = Mad.bitwiseOr(Mad.lshift(value, len), (this.cache >> (CHAR_BIT - len))); 143 | this.left -= len; 144 | } 145 | 146 | return value; 147 | } 148 | 149 | -------------------------------------------------------------------------------- /src/frame.js: -------------------------------------------------------------------------------- 1 | 2 | var bitrate_table /* [5][15] */ = [ 3 | /* MPEG-1 */ 4 | [ 0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, /* Layer I */ 5 | 256000, 288000, 320000, 352000, 384000, 416000, 448000 ], 6 | [ 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, /* Layer II */ 7 | 128000, 160000, 192000, 224000, 256000, 320000, 384000 ], 8 | [ 0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, /* Layer III */ 9 | 112000, 128000, 160000, 192000, 224000, 256000, 320000 ], 10 | 11 | /* MPEG-2 LSF */ 12 | [ 0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, /* Layer I */ 13 | 128000, 144000, 160000, 176000, 192000, 224000, 256000 ], 14 | [ 0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, /* Layers */ 15 | 64000, 80000, 96000, 112000, 128000, 144000, 160000 ] /* II & III */ 16 | ]; 17 | 18 | var samplerate_table /* [3] */ = [ 44100, 48000, 32000 ]; 19 | 20 | var decoder_table = [ 21 | function() { console.log("Layer I decoding is not implemented!"); }, 22 | function() { console.log("Layer II decoding is not implemented!"); }, 23 | Mad.layer_III 24 | ]; 25 | 26 | Mad.Layer = { 27 | I: 1, 28 | II: 2, 29 | III: 3 30 | }; 31 | 32 | Mad.Mode = { 33 | SINGLE_CHANNEL : 0, 34 | DUAL_CHANNEL : 1, /* dual channel */ 35 | JOINT_STEREO : 2, /* joint (MS/intensity) stereo */ 36 | STEREO : 3 /* normal LR stereo */ 37 | }; 38 | 39 | Mad.Emphasis = { 40 | NONE : 0, /* no emphasis */ 41 | _50_15_US : 1, /* 50/15 microseconds emphasis */ 42 | CCITT_J_17 : 3, /* CCITT J.17 emphasis */ 43 | RESERVED : 2 /* unknown emphasis */ 44 | }; 45 | 46 | Mad.Header = function () { 47 | this.layer = 0; /* audio layer (1, 2, or 3) */ 48 | this.mode = 0; /* channel mode (see above) */ 49 | this.mode_extension = 0; /* additional mode info */ 50 | this.emphasis = 0; /* de-emphasis to use (see above) */ 51 | 52 | this.bitrate = 0; /* stream bitrate (bps) */ 53 | this.samplerate = 0; /* sampling frequency (Hz) */ 54 | 55 | this.crc_check = 0; /* frame CRC accumulator */ 56 | this.crc_target = 0; /* final target CRC checksum */ 57 | 58 | this.flags = 0; /* flags (see below) */ 59 | this.private_bits = 0; /* private bits (see below) */ 60 | 61 | //this.duration = mad_timer_zero; /* audio playing time of frame */ 62 | }; 63 | 64 | Mad.Header.prototype.nchannels = function () { 65 | return this.mode === 0 ? 1 : 2; 66 | } 67 | 68 | Mad.Header.prototype.nbsamples = function() { 69 | return (this.layer === Mad.Layer.I ? 12 : 70 | ((this.layer === Mad.Layer.III && (this.flags & Mad.Flag.LSF_EXT)) ? 18 : 36)); 71 | } 72 | 73 | /* libmad's decode_header */ 74 | Mad.Header.actually_decode = function(stream) { 75 | var header = new Mad.Header(); 76 | 77 | header.flags = 0; 78 | header.private_bits = 0; 79 | 80 | /* header() */ 81 | 82 | /* syncword */ 83 | stream.ptr.skip(11); 84 | 85 | /* MPEG 2.5 indicator (really part of syncword) */ 86 | if (stream.ptr.read(1) === 0) { 87 | header.flags |= Mad.Flag.MPEG_2_5_EXT; 88 | } 89 | 90 | /* ID */ 91 | if (stream.ptr.read(1) === 0) { 92 | header.flags |= Mad.Flag.LSF_EXT; 93 | } else if (header.flags & Mad.Flag.MPEG_2_5_EXT) { 94 | stream.error = Mad.Error.LOSTSYNC; 95 | return null; 96 | } 97 | 98 | /* layer */ 99 | header.layer = 4 - stream.ptr.read(2); 100 | 101 | if (header.layer === 4) { 102 | stream.error = Mad.Error.BADLAYER; 103 | return header; 104 | } 105 | 106 | /* protection_bit */ 107 | if (stream.ptr.read(1) === 0) { 108 | header.flags |= Mad.Flag.PROTECTION; 109 | // TODO: crc 110 | //header.crc_check = mad_bit_crc(stream.ptr, 16, 0xffff); 111 | stream.ptr.skip(16); 112 | } 113 | 114 | /* bitrate_index */ 115 | var index = stream.ptr.read(4); 116 | if (index === 15) { 117 | stream.error = Mad.Error.BADBITRATE; 118 | return header; 119 | } 120 | 121 | if (header.flags & Mad.Flag.LSF_EXT) { 122 | header.bitrate = bitrate_table[3 + (header.layer >> 1)][index]; 123 | } else { 124 | header.bitrate = bitrate_table[header.layer - 1][index]; 125 | } 126 | 127 | /* sampling_frequency */ 128 | index = stream.ptr.read(2); 129 | 130 | if (index === 3) { 131 | stream.error = Mad.Error.BADSAMPLERATE; 132 | return header; 133 | } 134 | 135 | header.samplerate = samplerate_table[index]; 136 | 137 | if (header.flags & Mad.Flag.LSF_EXT) { 138 | header.samplerate /= 2; 139 | 140 | if (header.flags & Mad.Flag.MPEG_2_5_EXT) 141 | header.samplerate /= 2; 142 | } 143 | 144 | /* padding_bit */ 145 | if (stream.ptr.read(1)) 146 | header.flags |= Mad.Flag.PADDING; 147 | 148 | /* private_bit */ 149 | if (stream.ptr.read(1)) 150 | header.private_bits |= Mad.Private.HEADER; 151 | 152 | /* mode */ 153 | header.mode = 3 - stream.ptr.read(2); 154 | 155 | /* mode_extension */ 156 | header.mode_extension = stream.ptr.read(2); 157 | 158 | /* copyright */ 159 | if (stream.ptr.read(1)) 160 | header.flags |= Mad.Flag.COPYRIGHT; 161 | 162 | /* original/copy */ 163 | if (stream.ptr.read(1)) 164 | header.flags |= Mad.Flag.ORIGINAL; 165 | 166 | /* emphasis */ 167 | header.emphasis = stream.ptr.read(2); 168 | 169 | /* error_check() */ 170 | 171 | /* crc_check */ 172 | if (header.flags & Mad.Flag.PROTECTION) 173 | header.crc_target = stream.ptr.read(16); 174 | 175 | return header; 176 | } 177 | 178 | /* libmad's mad_header_decode */ 179 | Mad.Header.decode = function(stream) { 180 | var header = null; 181 | 182 | // those are actually pointers. javascript powa. 183 | var ptr = stream.next_frame; 184 | var end = stream.bufend; 185 | var pad_slot = 0; 186 | var N = 0; 187 | 188 | /* stream skip */ 189 | if (stream.skiplen) { 190 | if (!stream.sync) 191 | ptr = stream.this_frame; 192 | 193 | if (end - ptr < stream.skiplen) { 194 | stream.skiplen -= end - ptr; 195 | stream.next_frame = end; 196 | 197 | stream.error = Mad.Error.BUFLEN; 198 | return null; 199 | } 200 | 201 | ptr += stream.skiplen; 202 | stream.skiplen = 0; 203 | 204 | stream.sync = 1; 205 | } 206 | 207 | // emulating goto in JS, yay! this was a 'sync:' label 208 | var syncing = true; 209 | 210 | while(syncing) { 211 | syncing = false; 212 | 213 | /* synchronize */ 214 | try { 215 | if (stream.sync) { 216 | if (end - ptr < Mad.BUFFER_GUARD) { 217 | stream.next_frame = ptr; 218 | 219 | stream.error = Mad.Error.BUFLEN; 220 | return null; 221 | } else if (!(stream.getU8(ptr) === 0xff && (stream.getU8(ptr + 1) & 0xe0) === 0xe0)) { 222 | /* mark point where frame sync word was expected */ 223 | stream.this_frame = ptr; 224 | stream.next_frame = ptr + 1; 225 | 226 | stream.error = Mad.Error.LOSTSYNC; 227 | return null; 228 | } 229 | } else { 230 | stream.ptr = new Mad.Bit(stream.stream, ptr); 231 | 232 | if (stream.doSync() === -1) { 233 | if (end - stream.next_frame >= Mad.BUFFER_GUARD) 234 | stream.next_frame = end - Mad.BUFFER_GUARD; 235 | stream.error = Mad.Error.BUFLEN; 236 | return null; 237 | } 238 | 239 | ptr = stream.ptr.nextbyte(); 240 | } 241 | } catch (e) { 242 | console.log("Synchronization error: " + e); 243 | 244 | stream.error = Mad.Error.BUFLEN; 245 | 246 | return null; 247 | } 248 | 249 | /* begin processing */ 250 | stream.this_frame = ptr; 251 | stream.next_frame = ptr + 1; /* possibly bogus sync word */ 252 | 253 | stream.ptr = new Mad.Bit(stream.stream, stream.this_frame); 254 | 255 | header = Mad.Header.actually_decode(stream); 256 | if(header === null) return null; // well Duh^2 257 | 258 | //console.log("=============== Decoding layer " + header.layer + " audio mode " + 259 | // header.mode + " with " + header.bitrate + 260 | // " bps and a samplerate of " + header.samplerate); 261 | 262 | /* calculate frame duration */ 263 | //mad_timer_set(&header.duration, 0, 32 * MAD_NSBSAMPLES(header), header.samplerate); 264 | 265 | /* calculate free bit rate */ 266 | if (header.bitrate === 0) { 267 | console.log("Uh oh, a free bitrate stream. We're fucked."); 268 | stream.error = Mad.Error.BADDATAPTR; // best guess 269 | return null; 270 | 271 | // if ((stream.freerate === 0 || !stream.sync || 272 | // (header.layer === Mad.Layer.III && stream.freerate > 640000)) && 273 | // free_bitrate(stream, header) === -1) 274 | // return null; 275 | // 276 | // header.bitrate = stream.freerate; 277 | // header.flags |= Mad.Flag.FREEFORMAT; 278 | } 279 | 280 | /* calculate beginning of next frame */ 281 | pad_slot = (header.flags & Mad.Flag.PADDING) ? 1 : 0; 282 | 283 | if (header.layer === Mad.Layer.I) { 284 | N = (((12 * header.bitrate / header.samplerate) << 0) + pad_slot) * 4; 285 | } else { 286 | var slots_per_frame = (header.layer === Mad.Layer.III && 287 | (header.flags & Mad.Flag.LSF_EXT)) ? 72 : 144; 288 | //console.log("slots_per_frame = " + slots_per_frame + ", bitrate = " + header.bitrate + ", samplerate = " + header.samplerate); 289 | 290 | N = ((slots_per_frame * header.bitrate / header.samplerate) << 0) + pad_slot; 291 | } 292 | 293 | 294 | /* verify there is enough data left in buffer to decode this frame */ 295 | if (N + Mad.BUFFER_GUARD > end - stream.this_frame) { 296 | stream.next_frame = stream.this_frame; 297 | 298 | stream.error = Mad.Error.BUFLEN; 299 | return null; 300 | } 301 | 302 | stream.next_frame = stream.this_frame + N; 303 | 304 | // console.log("N = " + N + ", pad_slot = " + pad_slot + ", next_frame = " + stream.next_frame); 305 | 306 | if (!stream.sync) { 307 | /* check that a valid frame header follows this frame */ 308 | ptr = stream.next_frame; 309 | if (!(stream.getU8(ptr) === 0xff && (stream.getU8(ptr + 1) & 0xe0) === 0xe0)) { 310 | ptr = stream.next_frame = stream.this_frame + 1; 311 | 312 | // emulating 'goto sync' 313 | syncing = true; 314 | continue; 315 | } 316 | stream.sync = 1; 317 | } 318 | } // end of goto emulation (label 'sync') 319 | 320 | header.flags |= Mad.Flag.INCOMPLETE; 321 | return header; 322 | } 323 | 324 | Mad.Frame = function () { 325 | this.header = new Mad.Header(); /* MPEG audio header */ 326 | 327 | this.options = 0; /* decoding options (from stream) */ 328 | 329 | // sbsample[2][36][32] 330 | this.sbsample = []; /* synthesis subband filter samples */ 331 | for(var ch = 0; ch < 2; ch++) { 332 | this.sbsample[ch] = []; 333 | for(var grp = 0; grp < 36; grp++) { 334 | // this.sbsample[ch][grp] = new Float64Array(new ArrayBuffer(8 * 32)); 335 | this.sbsample[ch][grp] = []; 336 | for(var i = 0; i < 32; i++) { 337 | this.sbsample[ch][grp][i] = 0; 338 | } 339 | } 340 | } 341 | 342 | // overlap[2][32][18] 343 | this.overlap = []; /* Layer III block overlap data */ 344 | for(var ch = 0; ch < 2; ch++) { 345 | this.overlap[ch] = []; 346 | for(var sb = 0; sb < 32; sb++) { 347 | // this.overlap[ch][sb] = new Float64Array(new ArrayBuffer(8 * 18)); 348 | this.overlap[ch][sb] = []; 349 | for(var i = 0; i < 18; i++) { 350 | this.overlap[ch][sb][i] = 0; 351 | } 352 | } 353 | } 354 | }; 355 | 356 | Mad.Frame.decode = function(frame, stream) { 357 | frame.options = stream.options; 358 | 359 | /* header() */ 360 | /* error_check() */ 361 | 362 | if (!(frame.header.flags & Mad.Flag.INCOMPLETE)) { 363 | frame.header = Mad.Header.decode(stream); 364 | if(frame.header === null) { 365 | // something went wrong 366 | throw 'Header decoding failed'; 367 | } 368 | } 369 | 370 | frame.header.flags &= ~Mad.Flag.INCOMPLETE; 371 | 372 | if (decoder_table[frame.header.layer - 1](stream, frame) === -1) { 373 | if (!Mad.recoverable(stream.error)) { 374 | stream.next_frame = stream.this_frame; 375 | } 376 | throw 'Decoder table error'; 377 | } 378 | 379 | return frame; 380 | }; 381 | 382 | Mad.sbsampleIndex = function (i, j, k) { 383 | return i * 36 * 32 + j * 32 + k; 384 | }; 385 | 386 | Mad.overlapIndex = function (i, j, k) { 387 | return i * 32 * 18 + j * 18 + k; 388 | }; 389 | 390 | Mad.Flag = { 391 | NPRIVATE_III : 0x0007, /* number of Layer III private bits */ 392 | INCOMPLETE : 0x0008, /* header but not data is decoded */ 393 | 394 | PROTECTION : 0x0010, /* frame has CRC protection */ 395 | COPYRIGHT : 0x0020, /* frame is copyright */ 396 | ORIGINAL : 0x0040, /* frame is original (else copy) */ 397 | PADDING : 0x0080, /* frame has additional slot */ 398 | 399 | I_STEREO : 0x0100, /* uses intensity joint stereo */ 400 | MS_STEREO : 0x0200, /* uses middle/side joint stereo */ 401 | FREEFORMAT : 0x0400, /* uses free format bitrate */ 402 | 403 | LSF_EXT : 0x1000, /* lower sampling freq. extension */ 404 | MC_EXT : 0x2000, /* multichannel audio extension */ 405 | MPEG_2_5_EXT : 0x4000 /* MPEG 2.5 (unofficial) extension */ 406 | }; 407 | 408 | Mad.Private = { 409 | HEADER : 0x0100, /* header private bit */ 410 | III : 0x001f /* Layer III private bits (up to 5) */ 411 | }; 412 | -------------------------------------------------------------------------------- /src/id3v22stream.js: -------------------------------------------------------------------------------- 1 | var trimString = function (string) { 2 | return string.replace(/\0$/, ""); 3 | }; 4 | 5 | var stringToEncoding = function(string, encoding) { 6 | switch (encoding) { 7 | case 0: 8 | return trimString(string); 9 | case 1: 10 | var ix = 2, offset1 = 1, offset2 = 0; 11 | 12 | if (string.slice(0, 2) === "\xFE\xFF") { 13 | offset1 = 0, offset2 = 1; 14 | } else { 15 | offset1 = 1, offset2 = 0; 16 | } 17 | 18 | var result = ""; 19 | 20 | for (var ix = 2; ix < string.length; ix += 2) { 21 | var byte1 = string[ix + offset1].charCodeAt(0); 22 | var byte2 = string[ix + offset2].charCodeAt(0); 23 | 24 | var word1 = (byte1 << 8) | byte2; 25 | 26 | if (byte1 < 0xD8 || byte1 >= 0xE0) { 27 | result += String.fromCharCode(word1); 28 | } else { 29 | ix += 2; 30 | 31 | var byte3 = string[ix + offset1].charCodeAt(0); 32 | var byte4 = string[ix + offset2].charCodeAt(0); 33 | 34 | var word2 = (byte3 << 8) | byte4; 35 | 36 | result += String.fromCharCode(word1, word2); 37 | } 38 | } 39 | 40 | return trimString(result); 41 | default: 42 | return string; 43 | } 44 | } 45 | 46 | var decodeIdentifierFrame = function(header, stream) { 47 | var data = stream.read(header['length']); 48 | 49 | var array = data.split("\0", 2); 50 | 51 | return { 52 | 'header': header, 53 | 'owner': array[0], 54 | 'identifier': array[1] 55 | }; 56 | } 57 | 58 | var decodeTextFrame = function(header, stream) { 59 | var encoding = stream.readU8(); 60 | var data = stream.read(header['length'] - 1); 61 | 62 | return { 63 | 'header': header, 64 | 'value': stringToEncoding(data, encoding) 65 | }; 66 | } 67 | 68 | var decodeCommentFrame = function(header, stream) { 69 | var encoding = stream.readU8(); 70 | var language = stream.read(3); 71 | 72 | var data = stream.read(header['length'] - 4); 73 | 74 | var array = data.split("\0", 2); 75 | 76 | return { 77 | 'header': header, 78 | 'language': stringToEncoding(language, 0), 79 | 'description': stringToEncoding(array[0], 0), 80 | 'value': stringToEncoding(array[1], encoding) 81 | }; 82 | } 83 | 84 | var extendWithValueFrame = function(tags, frame) { 85 | tags[frame['name']] = frame['value']; 86 | 87 | return tags; 88 | } 89 | 90 | 91 | var extendWithIdentifierFrame = function(tags, frame) { 92 | tags[frame['name']] = { 93 | 'owner': frame['value'], 94 | 'identifier': frame['identifier'] 95 | }; 96 | 97 | return tags; 98 | } 99 | 100 | var extendWithCommentFrame = function(tags, frame) { 101 | if (!tags[frame['name']]) { 102 | tags[frame['name']] = [] 103 | } 104 | 105 | tags[frame['name']][frame['description']] = { 106 | 'language': frame['language'], 107 | 'value': frame['value'] 108 | }; 109 | 110 | return tags; 111 | } 112 | 113 | Mad.ID3v22Stream = function(header, stream) { 114 | this.offset = 0; 115 | 116 | this.header = header; 117 | this.stream = stream; 118 | 119 | this.decoders = { 120 | 'UFI': decodeIdentifierFrame, 121 | 'TT1': decodeTextFrame, 122 | 'TT2': decodeTextFrame, 123 | 'TT3': decodeTextFrame, 124 | 'TP1': decodeTextFrame, 125 | 'TP2': decodeTextFrame, 126 | 'TP3': decodeTextFrame, 127 | 'TP4': decodeTextFrame, 128 | 'TCM': decodeTextFrame, 129 | 'TXT': decodeTextFrame, 130 | 'TLA': decodeTextFrame, 131 | 'TCO': decodeTextFrame, 132 | 'TAL': decodeTextFrame, 133 | 'TPA': decodeTextFrame, 134 | 'TRK': decodeTextFrame, 135 | 'TRC': decodeTextFrame, 136 | 'TYE': decodeTextFrame, 137 | 'TDA': decodeTextFrame, 138 | 'TIM': decodeTextFrame, 139 | 'TRD': decodeTextFrame, 140 | 'TMT': decodeTextFrame, 141 | 'TFT': decodeTextFrame, 142 | 'TBP': decodeTextFrame, 143 | 'TCR': decodeTextFrame, 144 | 'TPB': decodeTextFrame, 145 | 'TEN': decodeTextFrame, 146 | 'TSS': decodeTextFrame, 147 | 'TOF': decodeTextFrame, 148 | 'TLE': decodeTextFrame, 149 | 'TSI': decodeTextFrame, 150 | 'TDY': decodeTextFrame, 151 | 'TKE': decodeTextFrame, 152 | 'TOT': decodeTextFrame, 153 | 'TOA': decodeTextFrame, 154 | 'TOL': decodeTextFrame, 155 | 'TOR': decodeTextFrame, 156 | 157 | 'COM': decodeCommentFrame 158 | }; 159 | 160 | this.names = { 161 | /* Identification Frames */ 162 | 'TT1': 'Content group description', 163 | 'TT2': 'Title/Songname/Content description', 164 | 'TT3': 'Subtitle/Description refinement', 165 | 'TAL': 'Album/Movie/Show title', 166 | 'TOT': 'Original album/movie/show title', 167 | 'TRK': 'Track number/Position in set', 168 | 'TPA': 'Part of a set', 169 | 'TRC': 'ISRC', 170 | 171 | /* Involved Persons Frames */ 172 | 'TP1': 'Lead artist/Lead performer/Soloist/Performing group', 173 | 'TP2': 'Band/Orchestra/Accompaniment', 174 | 'TP3': 'Conductor', 175 | 'TP4': 'Interpreted, remixed, or otherwise modified by', 176 | 'TOA': 'Original artist/performer', 177 | 'TXT': 'Lyricist/Text writer', 178 | 'TOL': 'Original lyricist/text writer', 179 | 'TCO': 'Composer', 180 | 'TEN': 'Encoded by', 181 | 182 | /* Derived and Subjective Properties Frames */ 183 | 'TBP': 'BPM', 184 | 'TLE': 'Length', 185 | 'TKE': 'Initial key', 186 | 'TLA': 'Language', 187 | 'TMT': 'Media type', 188 | 189 | /* Rights and Licence Frames */ 190 | 'TCR': 'Copyright message', 191 | 'TPB': 'Publisher', 192 | 193 | /* Other Text Frames */ 194 | 'TOF': 'Original filename', 195 | 'TDY': 'Playlist delay', 196 | 'TSS': 'Software/Hardware and settings used for encoding', 197 | 'TFT': 'File type', 198 | 199 | /* Buffering */ 200 | 'BUF': 'Recommended buffer size', 201 | 202 | /* Attached Picture Frame */ 203 | 'PIC': 'Attached picture', 204 | 205 | /* Unique Identifier Frame */ 206 | 'UFI': 'Unique identifier', 207 | 208 | /* Music CD Identifier Frame */ 209 | 'MCI': 'Music CD identifier', 210 | 211 | /* Comment Frame */ 212 | 'COM': 'Comment', 213 | 214 | /* User Defined URL Link Frame */ 215 | 'WXX': 'User defined URL link', 216 | 217 | /* Deprecated ID3v2 frames */ 218 | 'TDA': 'Date', 219 | 'TIM': 'Time', 220 | 'TOR': 'Original release year', 221 | 'TRD': 'Recording dates', 222 | 'TSI': 'Size', 223 | 'TYE': 'Year' 224 | }; 225 | 226 | this.extenders = { 227 | 'UFI': extendWithIdentifierFrame, 228 | 'TT1': extendWithValueFrame, 229 | 'TT2': extendWithValueFrame, 230 | 'TT3': extendWithValueFrame, 231 | 'TP1': extendWithValueFrame, 232 | 'TP2': extendWithValueFrame, 233 | 'TP3': extendWithValueFrame, 234 | 'TP4': extendWithValueFrame, 235 | 'TCM': extendWithValueFrame, 236 | 'TXT': extendWithValueFrame, 237 | 'TLA': extendWithValueFrame, 238 | 'TCO': extendWithValueFrame, 239 | 'TAL': extendWithValueFrame, 240 | 'TPA': extendWithValueFrame, 241 | 'TRK': extendWithValueFrame, 242 | 'TRC': extendWithValueFrame, 243 | 'TYE': extendWithValueFrame, 244 | 'TDA': extendWithValueFrame, 245 | 'TIM': extendWithValueFrame, 246 | 'TRD': extendWithValueFrame, 247 | 'TMT': extendWithValueFrame, 248 | 'TFT': extendWithValueFrame, 249 | 'TBP': extendWithValueFrame, 250 | 'TCR': extendWithValueFrame, 251 | 'TPB': extendWithValueFrame, 252 | 'TEN': extendWithValueFrame, 253 | 'TSS': extendWithValueFrame, 254 | 'TOF': extendWithValueFrame, 255 | 'TLE': extendWithValueFrame, 256 | 'TSI': extendWithValueFrame, 257 | 'TDY': extendWithValueFrame, 258 | 'TKE': extendWithValueFrame, 259 | 'TOT': extendWithValueFrame, 260 | 'TOA': extendWithValueFrame, 261 | 'TOL': extendWithValueFrame, 262 | 'TOR': extendWithValueFrame, 263 | 264 | 'COM': extendWithCommentFrame 265 | }; 266 | } 267 | 268 | Mad.ID3v22Stream.prototype.readFrame = function() { 269 | if (this.offset >= this.header.length) { 270 | return null; 271 | } 272 | 273 | var identifier = this.stream.read(3); 274 | 275 | if (identifier.charCodeAt(0) === 0) { 276 | this.offset = this.header.length + 1; 277 | 278 | return null; 279 | } 280 | 281 | var length = this.stream.readU24(true); 282 | 283 | var header = { 284 | identifier: identifier, 285 | length: length 286 | }; 287 | 288 | if (this.decoders[identifier]) { 289 | var result = this.decoders[identifier](header, this.stream); 290 | } else { 291 | var result = { 292 | identifier: identifier, 293 | header: header 294 | }; 295 | 296 | this.stream.read(length); 297 | } 298 | 299 | result.name = this.names[identifier] ? this.names[identifier] : 'UNKNOWN'; 300 | 301 | this.offset += 10 + length; 302 | 303 | return result; 304 | } 305 | 306 | Mad.ID3v22Stream.prototype.read = function() { 307 | if (!this.array) { 308 | this.array = []; 309 | 310 | var frame = null; 311 | 312 | try { 313 | while (frame = this.readFrame()) { 314 | this.array.push(frame); 315 | } 316 | } catch (e) { 317 | throw(e); 318 | //console.log("ID3 Error: " + e); 319 | } 320 | } 321 | 322 | return this.array; 323 | } 324 | 325 | Mad.ID3v22Stream.prototype.toHash = function() { 326 | var frames = this.read(); 327 | 328 | var hash = {}; 329 | 330 | for (var i = 0; i < frames.length; i++) { 331 | var frame = frames[i]; 332 | 333 | var extender = this.extenders[frame['header']['identifier']]; 334 | 335 | if (extender) { 336 | hash = extender(hash, frames[i]); 337 | } 338 | } 339 | 340 | return hash; 341 | } 342 | -------------------------------------------------------------------------------- /src/id3v23stream.js: -------------------------------------------------------------------------------- 1 | var trimString = function (string) { 2 | return string.replace(/\0$/, ""); 3 | }; 4 | 5 | var stringToEncoding = function(string, encoding) { 6 | switch (encoding) { 7 | case 0: 8 | return trimString(string); 9 | case 1: 10 | var ix = 2, offset1 = 1, offset2 = 0; 11 | 12 | if (string.slice(0, 2) === "\xFE\xFF") { 13 | offset1 = 0, offset2 = 1; 14 | } else { 15 | offset1 = 1, offset2 = 0; 16 | } 17 | 18 | var result = ""; 19 | 20 | for (var ix = 2; ix < string.length; ix += 2) { 21 | var byte1 = string[ix + offset1].charCodeAt(0); 22 | var byte2 = string[ix + offset2].charCodeAt(0); 23 | 24 | var word1 = (byte1 << 8) | byte2; 25 | 26 | if (byte1 < 0xD8 || byte1 >= 0xE0) { 27 | result += String.fromCharCode(word1); 28 | } else { 29 | ix += 2; 30 | 31 | var byte3 = string[ix + offset1].charCodeAt(0); 32 | var byte4 = string[ix + offset2].charCodeAt(0); 33 | 34 | var word2 = (byte3 << 8) | byte4; 35 | 36 | result += String.fromCharCode(word1, word2); 37 | } 38 | } 39 | 40 | return trimString(result); 41 | default: 42 | return string; 43 | } 44 | } 45 | 46 | var decodeTerminatedString = function(header, stream) { 47 | 48 | // skip text encoding (nobody cares, always ISO-8559-15) 49 | stream.readAsString(1); 50 | 51 | var c; 52 | 53 | var mimetype = ""; 54 | while(true) { 55 | c = stream.readAsString(1); 56 | if(c === "\0") { 57 | break; 58 | } 59 | mimetype += c; 60 | } 61 | console.log("mimetype = " + mimetype); 62 | 63 | var filename = ""; 64 | while(true) { 65 | c = stream.readAsString(1); 66 | if(c === "\0") { 67 | break; 68 | } 69 | filename += c; 70 | } 71 | console.log("filename = " + filename); 72 | 73 | var contentDescription = ""; 74 | while(true) { 75 | c = stream.readAsString(1); 76 | if(c === "\0") { 77 | break; 78 | } 79 | contentDescription += c; 80 | } 81 | console.log("contentDescription = " + contentDescription); 82 | 83 | var objectName = stream.readAsString(4); 84 | 85 | // skip 4 bytes, then read binary length 86 | stream.readAsString(4); 87 | var length = stream.readU32(true) - 4; 88 | 89 | if(length > header.length) return null; 90 | var value = stream.readAsString(length); 91 | 92 | return { 93 | 'header': header, 94 | 'mimetype': mimetype, 95 | 'filename': filename, 96 | 'contentDescription': contentDescription, 97 | 'objectName': objectName, 98 | 'value': value 99 | }; 100 | } 101 | 102 | var decodeTextFrame = function(header, stream) { 103 | var encoding = stream.readU8(); 104 | var data = stream.readAsString(header['length'] - 1); 105 | 106 | return { 107 | 'header': header, 108 | 'value': stringToEncoding(data, encoding) 109 | }; 110 | } 111 | 112 | var decodeAttachedPictureFrame = function(header, stream) { 113 | var encoding = stream.readU8(); 114 | 115 | var data = stream.readAsString(header['length'] - 1); 116 | 117 | var array = data.split("\0"); 118 | 119 | return { 120 | 'header': header, 121 | 'mime': stringToEncoding(array[0], 0), 122 | 'type': array[1].charCodeAt(0), 123 | 'description': array[1].slice(1, array[1].length - 1), 124 | 'value': array.slice(2, array.length - 2).join("\0") 125 | }; 126 | } 127 | 128 | var decodeIdentifierFrame = function(header, stream) { 129 | var data = stream.readAsString(header['length']); 130 | 131 | var array = data.split("\0", 2); 132 | 133 | return { 134 | 'header': header, 135 | 'owner': array[0], 136 | 'identifier': array[1] 137 | }; 138 | } 139 | 140 | 141 | var decodeCommentFrame = function(header, stream) { 142 | var encoding = stream.readU8(); 143 | var language = stream.readAsString(3); 144 | 145 | var data = stream.readAsString(header['length'] - 4); 146 | 147 | var array = data.split("\0", 2); 148 | 149 | return { 150 | 'header': header, 151 | 'language': stringToEncoding(language, 0), 152 | 'description': stringToEncoding(array[0], 0), 153 | 'value': stringToEncoding(array[1], encoding) 154 | }; 155 | } 156 | 157 | var decodeBinaryFrame = function(header, stream) { 158 | var data = stream.readAsString(header['length']); 159 | 160 | return { 161 | 'header': header, 162 | 'value': data 163 | }; 164 | } 165 | 166 | var decodeUserDefinedLinkFrame = function(header, stream) { 167 | var encoding = stream.readU8(); 168 | var data = stream.readAsString(header['length'] - 1); 169 | 170 | var array = data.split("\0", 2); 171 | 172 | return { 173 | 'header': header, 174 | 'description': stringToEncoding(array[0], 0), 175 | 'value': stringToEncoding(array[1], encoding) 176 | }; 177 | } 178 | 179 | var extendWithValueFrame = function(tags, frame) { 180 | tags[frame['name']] = frame['value']; 181 | 182 | return tags; 183 | } 184 | 185 | var extendWithPictureFrame = function(tags, frame) { 186 | if (!tags[frame['name']]) { 187 | tags[frame['name']] = [] 188 | } 189 | 190 | tags[frame['name']].push({ 191 | 'mime': frame['mime'], 192 | 'type': frame['type'], 193 | 'description': frame['description'], 194 | 'value': frame['value'] 195 | }); 196 | 197 | return tags; 198 | } 199 | 200 | var extendWithIdentifierFrame = function(tags, frame) { 201 | tags[frame['name']] = { 202 | 'owner': frame['value'], 203 | 'identifier': frame['identifier'] 204 | }; 205 | 206 | return tags; 207 | } 208 | 209 | var extendWithCommentFrame = function(tags, frame) { 210 | if (!tags[frame['name']]) { 211 | tags[frame['name']] = [] 212 | } 213 | 214 | tags[frame['name']][frame['description']] = { 215 | 'language': frame['language'], 216 | 'value': frame['value'] 217 | }; 218 | 219 | return tags; 220 | } 221 | 222 | var extendWithUserDefinedLinkFrame = function(tags, frame) { 223 | if (!tags[frame['name']]) { 224 | tags[frame['name']] = {} 225 | } 226 | 227 | tags[frame['name']][frame['description']] = frame['value']; 228 | 229 | return tags; 230 | } 231 | 232 | var extendWithPrivateFrame = function(tags, frame) { 233 | if (!tags[frame['name']]) { 234 | tags[frame['name']] = [] 235 | } 236 | 237 | tags[frame['name']].push(frame['value']); 238 | 239 | return tags; 240 | } 241 | 242 | Mad.ID3v23Stream = function(header, stream) { 243 | this.offset = 0; 244 | 245 | this.header = header; 246 | this.stream = stream; 247 | 248 | this.decoders = { 249 | /* Identification Frames */ 250 | 'TIT1': decodeTextFrame, 251 | 'TIT2': decodeTextFrame, 252 | 'TIT3': decodeTextFrame, 253 | 'TALB': decodeTextFrame, 254 | 'TOAL': decodeTextFrame, 255 | 'TRCK': decodeTextFrame, 256 | 'TPOS': decodeTextFrame, 257 | 'TSST': decodeTextFrame, 258 | 'TSRC': decodeTextFrame, 259 | 260 | /* Involved Persons Frames */ 261 | 'TPE1': decodeTextFrame, 262 | 'TPE2': decodeTextFrame, 263 | 'TPE3': decodeTextFrame, 264 | 'TPE4': decodeTextFrame, 265 | 'TOPE': decodeTextFrame, 266 | 'TEXT': decodeTextFrame, 267 | 'TOLY': decodeTextFrame, 268 | 'TCOM': decodeTextFrame, 269 | 'TMCL': decodeTextFrame, 270 | 'TIPL': decodeTextFrame, 271 | 'TENC': decodeTextFrame, 272 | 273 | /* Derived and Subjective Properties Frames */ 274 | 'TBPM': decodeTextFrame, 275 | 'TLEN': decodeTextFrame, 276 | 'TKEY': decodeTextFrame, 277 | 'TLAN': decodeTextFrame, 278 | 'TCON': decodeTextFrame, 279 | 'TFLT': decodeTextFrame, 280 | 'TMED': decodeTextFrame, 281 | 'TMOO': decodeTextFrame, 282 | 283 | /* Rights and Licence Frames */ 284 | 'TCOP': decodeTextFrame, 285 | 'TPRO': decodeTextFrame, 286 | 'TPUB': decodeTextFrame, 287 | 'TOWN': decodeTextFrame, 288 | 'TRSN': decodeTextFrame, 289 | 'TRSO': decodeTextFrame, 290 | 291 | /* Other Text Frames */ 292 | 'TOFN': decodeTextFrame, 293 | 'TDLY': decodeTextFrame, 294 | 'TDEN': decodeTextFrame, 295 | 'TDOR': decodeTextFrame, 296 | 'TDRC': decodeTextFrame, 297 | 'TDRL': decodeTextFrame, 298 | 'TDTG': decodeTextFrame, 299 | 'TSSE': decodeTextFrame, 300 | 'TSOA': decodeTextFrame, 301 | 'TSOP': decodeTextFrame, 302 | 'TSOT': decodeTextFrame, 303 | 304 | /* Attached Picture Frame */ 305 | 'APIC': decodeAttachedPictureFrame, 306 | 307 | /* Unique Identifier Frame */ 308 | 'UFID': decodeIdentifierFrame, 309 | 310 | /* Music CD Identifier Frame */ 311 | 'MCDI': decodeBinaryFrame, 312 | 313 | /* Comment Frame */ 314 | 'COMM': decodeCommentFrame, 315 | 316 | /* User Defined URL Link Frame */ 317 | 'WXXX': decodeUserDefinedLinkFrame, 318 | 319 | /* Private Frame */ 320 | 'PRIV': decodeBinaryFrame, 321 | 322 | /* Deprecated ID3v2 Frames */ 323 | 'TDAT': decodeTextFrame, 324 | 'TIME': decodeTextFrame, 325 | 'TORY': decodeTextFrame, 326 | 'TRDA': decodeTextFrame, 327 | 'TSIZ': decodeTextFrame, 328 | 'TYER': decodeTextFrame, 329 | 330 | /* General encapsulated object */ 331 | 'GEOB': decodeTerminatedString 332 | }; 333 | 334 | this.names = { 335 | /* Identification Frames */ 336 | 'TIT1': 'Content group description', 337 | 'TIT2': 'Title/Songname/Content description', 338 | 'TIT3': 'Subtitle/Description refinement', 339 | 'TALB': 'Album/Movie/Show title', 340 | 'TOAL': 'Original album/movie/show title', 341 | 'TRCK': 'Track number/Position in set', 342 | 'TPOS': 'Part of a set', 343 | 'TSST': 'Set subtitle', 344 | 'TSRC': 'ISRC', 345 | 346 | /* Involved Persons Frames */ 347 | 'TPE1': 'Lead artist/Lead performer/Soloist/Performing group', 348 | 'TPE2': 'Band/Orchestra/Accompaniment', 349 | 'TPE3': 'Conductor', 350 | 'TPE4': 'Interpreted, remixed, or otherwise modified by', 351 | 'TOPE': 'Original artist/performer', 352 | 'TEXT': 'Lyricist/Text writer', 353 | 'TOLY': 'Original lyricist/text writer', 354 | 'TCOM': 'Composer', 355 | 'TMCL': 'Musician credits list', 356 | 'TIPL': 'Involved people list', 357 | 'TENC': 'Encoded by', 358 | 359 | /* Derived and Subjective Properties Frames */ 360 | 'TBPM': 'BPM', 361 | 'TLEN': 'Length', 362 | 'TKEY': 'Initial key', 363 | 'TLAN': 'Language', 364 | 'TCON': 'Content type', 365 | 'TFLT': 'File type', 366 | 'TMED': 'Media type', 367 | 'TMOO': 'Mood', 368 | 369 | /* Rights and Licence Frames */ 370 | 'TCOP': 'Copyright message', 371 | 'TPRO': 'Produced notice', 372 | 'TPUB': 'Publisher', 373 | 'TOWN': 'File owner/licensee', 374 | 'TRSN': 'Internet radio station name', 375 | 'TRSO': 'Internet radio station owner', 376 | 377 | /* Other Text Frames */ 378 | 'TOFN': 'Original filename', 379 | 'TDLY': 'Playlist delay', 380 | 'TDEN': 'Encoding time', 381 | 'TDOR': 'Original release time', 382 | 'TDRC': 'Recording time', 383 | 'TDRL': 'Release time', 384 | 'TDTG': 'Tagging time', 385 | 'TSSE': 'Software/Hardware and settings used for encoding', 386 | 'TSOA': 'Album sort order', 387 | 'TSOP': 'Performer sort order', 388 | 'TSOT': 'Title sort order', 389 | 390 | /* Attached Picture Frame */ 391 | 'APIC': 'Attached picture', 392 | 393 | /* Unique Identifier Frame */ 394 | 'UFID': 'Unique identifier', 395 | 396 | /* Music CD Identifier Frame */ 397 | 'MCDI': 'Music CD identifier', 398 | 399 | /* Comment Frame */ 400 | 'COMM': 'Comment', 401 | 402 | /* User Defined URL Link Frame */ 403 | 'WXXX': 'User defined URL link', 404 | 405 | /* Private Frame */ 406 | 'PRIV': 'Private', 407 | 408 | /* Deprecated ID3v2 frames */ 409 | 'TDAT': 'Date', 410 | 'TIME': 'Time', 411 | 'TORY': 'Original release year', 412 | 'TRDA': 'Recording dates', 413 | 'TSIZ': 'Size', 414 | 'TYER': 'Year' 415 | }; 416 | 417 | this.extenders = { 418 | /* Identification Frames */ 419 | 'TIT1': extendWithValueFrame, 420 | 'TIT2': extendWithValueFrame, 421 | 'TIT3': extendWithValueFrame, 422 | 'TALB': extendWithValueFrame, 423 | 'TOAL': extendWithValueFrame, 424 | 'TRCK': extendWithValueFrame, 425 | 'TPOS': extendWithValueFrame, 426 | 'TSST': extendWithValueFrame, 427 | 'TSRC': extendWithValueFrame, 428 | 429 | /* Involved Persons Frames */ 430 | 'TPE1': extendWithValueFrame, 431 | 'TPE2': extendWithValueFrame, 432 | 'TPE3': extendWithValueFrame, 433 | 'TPE4': extendWithValueFrame, 434 | 'TOPE': extendWithValueFrame, 435 | 'TEXT': extendWithValueFrame, 436 | 'TOLY': extendWithValueFrame, 437 | 'TCOM': extendWithValueFrame, 438 | 'TMCL': extendWithValueFrame, 439 | 'TIPL': extendWithValueFrame, 440 | 'TENC': extendWithValueFrame, 441 | 442 | /* Derived and Subjective Properties Frames */ 443 | 'TBPM': extendWithValueFrame, 444 | 'TLEN': extendWithValueFrame, 445 | 'TKEY': extendWithValueFrame, 446 | 'TLAN': extendWithValueFrame, 447 | 'TCON': extendWithValueFrame, 448 | 'TFLT': extendWithValueFrame, 449 | 'TMED': extendWithValueFrame, 450 | 'TMOO': extendWithValueFrame, 451 | 452 | /* Rights and Licence Frames */ 453 | 'TCOP': extendWithValueFrame, 454 | 'TPRO': extendWithValueFrame, 455 | 'TPUB': extendWithValueFrame, 456 | 'TOWN': extendWithValueFrame, 457 | 'TRSN': extendWithValueFrame, 458 | 'TRSO': extendWithValueFrame, 459 | 460 | /* Other Text Frames */ 461 | 'TOFN': extendWithValueFrame, 462 | 'TDLY': extendWithValueFrame, 463 | 'TDEN': extendWithValueFrame, 464 | 'TDOR': extendWithValueFrame, 465 | 'TDRC': extendWithValueFrame, 466 | 'TDRL': extendWithValueFrame, 467 | 'TDTG': extendWithValueFrame, 468 | 'TSSE': extendWithValueFrame, 469 | 'TSOA': extendWithValueFrame, 470 | 'TSOP': extendWithValueFrame, 471 | 'TSOT': extendWithValueFrame, 472 | 473 | /* Attached Picture Frame */ 474 | 'APIC': extendWithPictureFrame, 475 | 476 | /* Unique Identifier Frame */ 477 | 'UFID': extendWithIdentifierFrame, 478 | 479 | /* Music CD Identifier */ 480 | 'MCDI': extendWithValueFrame, 481 | 482 | /* Comment Frame */ 483 | 'COMM': extendWithCommentFrame, 484 | 485 | /* User Defined URL Link Frame */ 486 | 'WXXX': extendWithUserDefinedLinkFrame, 487 | 488 | /* Private Frame */ 489 | 'PRIV': extendWithPrivateFrame, 490 | 491 | /* Deprecated ID3v2 Frames */ 492 | 'TDAT': extendWithValueFrame, 493 | 'TIME': extendWithValueFrame, 494 | 'TORY': extendWithValueFrame, 495 | 'TRDA': extendWithValueFrame, 496 | 'TSIZ': extendWithValueFrame, 497 | 'TYER': extendWithValueFrame 498 | }; 499 | } 500 | 501 | Mad.ID3v23Stream.prototype.readFrame = function() { 502 | if (this.offset >= this.header.length) { 503 | return null; 504 | } 505 | 506 | var identifier = this.stream.readAsString(4); 507 | 508 | if (identifier.charCodeAt(0) === 0) { 509 | this.offset = this.header.length + 1; 510 | return null; 511 | } 512 | 513 | var length = this.stream.readU32(true); 514 | var flags = this.stream.readU16(true); 515 | 516 | var header = { 517 | 'identifier': identifier, 518 | 'length': length, 519 | 'flags': flags 520 | }; 521 | 522 | var result = null; 523 | 524 | if (this.decoders[identifier]) { 525 | result = this.decoders[identifier](header, this.stream); 526 | } else { 527 | result = { 528 | 'identifier': identifier, 529 | 'header': header 530 | }; 531 | 532 | this.stream.readAsString(Math.min(length, this.header.length - this.offset)); 533 | } 534 | 535 | if(result) { 536 | result['name'] = this.names[identifier] ? this.names[identifier] : 'UNKNOWN'; 537 | } 538 | 539 | this.offset += 10 + length; 540 | 541 | return result; 542 | } 543 | 544 | Mad.ID3v23Stream.prototype.read = function() { 545 | if (!this.array) { 546 | this.array = []; 547 | 548 | var frame = null; 549 | 550 | //try { 551 | while (frame = this.readFrame()) { 552 | this.array.push(frame); 553 | } 554 | //} catch (e) { 555 | //throw(e); 556 | //console.log("ID3 Error: " + e); 557 | //} 558 | } 559 | 560 | return this.array; 561 | } 562 | 563 | Mad.ID3v23Stream.prototype.toHash = function() { 564 | var frames = this.read(); 565 | 566 | var hash = {}; 567 | 568 | for (var i = 0; i < frames.length; i++) { 569 | var frame = frames[i]; 570 | 571 | var extender = this.extenders[frame['header']['identifier']]; 572 | 573 | if (extender) { 574 | hash = extender(hash, frames[i]); 575 | } 576 | } 577 | 578 | return hash; 579 | } 580 | -------------------------------------------------------------------------------- /src/imdct_s.js: -------------------------------------------------------------------------------- 1 | Mad.imdct_s = [ 2 | /* 0 */ [ 0.608761429, 3 | -0.923879533, 4 | -0.130526192, 5 | 0.991444861, 6 | -0.382683432, 7 | -0.793353340 ], 8 | 9 | /* 6 */ [ -0.793353340, 10 | 0.382683432, 11 | 0.991444861, 12 | 0.130526192, 13 | -0.923879533, 14 | -0.608761429 ], 15 | 16 | /* 1 */ [ 0.382683432, 17 | -0.923879533, 18 | 0.923879533, 19 | -0.382683432, 20 | -0.382683432, 21 | 0.923879533 ], 22 | 23 | /* 7 */ [ -0.923879533, 24 | -0.382683432, 25 | 0.382683432, 26 | 0.923879533, 27 | 0.923879533, 28 | 0.382683432 ], 29 | 30 | /* 2 */ [ 0.130526192, 31 | -0.382683432, 32 | 0.608761429, 33 | -0.793353340, 34 | 0.923879533, 35 | -0.991444861 ], 36 | 37 | /* 8 */ [ -0.991444861, 38 | -0.923879533, 39 | -0.793353340, 40 | -0.608761429, 41 | -0.382683432, 42 | -0.130526192 ] 43 | ] 44 | -------------------------------------------------------------------------------- /src/mad.js: -------------------------------------------------------------------------------- 1 | 2 | /* Namespaces */ 3 | Mad = {}; 4 | Mad.ArrayBuffers = {}; 5 | Mad.BinaryStrings = {}; 6 | 7 | Mad.recoverable = function (error) { 8 | return (error & 0xff00) != 0; 9 | }; 10 | 11 | // change this value for testing 12 | Mad.enforceBinaryString = false; 13 | 14 | if (!Mad.enforceBinaryString && typeof(ArrayBuffer) === 'function' && typeof(Uint8Array) === 'function') { 15 | console.log("Using ArrayBuffer"); 16 | Mad.Storage = { 17 | backing: 'arraybuffer', 18 | 19 | newBuffer: function (length) { 20 | return new Uint8Array(length); 21 | }, 22 | 23 | memcpy: function (dst, dstOffset, pSrc, srcOffset, length) { 24 | while (pSrc.parentStream) { 25 | srcOffset += pSrc.start; 26 | pSrc = pSrc.parentStream; 27 | } 28 | var src = pSrc.subarray ? pSrc : pSrc.buffer; 29 | var subarr = src.subarray(srcOffset, srcOffset + length); 30 | 31 | // oh my, memcpy actually exists in JavaScript? 32 | dst.set(subarr, dstOffset); 33 | return dst; 34 | } 35 | }; 36 | Mad.FileStream = function (file, callback) { 37 | return new Mad.ArrayBuffers.FileStream(file, callback); 38 | } 39 | Mad.AjaxStream = function (file, callback) { 40 | return new Mad.ArrayBuffers.AjaxStream(file, callback); 41 | } 42 | } else { 43 | console.log("Using BinaryString"); 44 | Mad.Storage = { 45 | backing: 'binarystring', 46 | 47 | newBuffer: function (length) { 48 | return Mad.mul("\0", length); 49 | }, 50 | 51 | memcpy: function (dst, dstOffset, src, srcOffset, length) { 52 | // this is a pretty weird memcpy actually - it constructs a new version of dst, because we have no other way to do it 53 | return dst.slice(0, dstOffset) + src.slice(srcOffset, srcOffset + length) + dst.slice(dstOffset + length); 54 | } 55 | }; 56 | Mad.FileStream = function (file, callback) { 57 | return new Mad.BinaryStrings.FileStream(file, callback); 58 | } 59 | Mad.AjaxStream = function (file, callback) { 60 | return new Mad.BinaryStrings.AjaxStream(file, callback); 61 | } 62 | } 63 | 64 | // credit: http://blog.stevenlevithan.com/archives/fast-string-multiply 65 | Mad.mul = function (str, num) { 66 | var i = Math.ceil(Math.log(num) / Math.LN2), res = str; 67 | do { 68 | res += res; 69 | } while (0 < --i); 70 | return res.slice(0, str.length * num); 71 | }; 72 | 73 | Mad.rshift = function (num, bits) { 74 | return Math.floor(num / Math.pow(2, bits)); 75 | }; 76 | 77 | Mad.lshiftU32 = function (num, bits) { 78 | return Mad.bitwiseAnd(Mad.lshift(num, bits), 4294967295 /* 2^32 - 1 */); 79 | }; 80 | 81 | Mad.lshift = function (num, bits) { 82 | return num * Math.pow(2, bits); 83 | }; 84 | 85 | Mad.bitwiseOr = function (a, b) { 86 | var w = 2147483648; // 2^31 87 | 88 | var aHI = (a / w) << 0; 89 | var aLO = a % w; 90 | var bHI = (b / w) << 0; 91 | var bLO = b % w; 92 | 93 | return ((aHI | bHI) * w + (aLO | bLO)); 94 | }; 95 | 96 | Mad.bitwiseAnd = function (a, b) { 97 | var w = 2147483648; // 2^31 98 | 99 | var aHI = (a / w) << 0; 100 | var aLO = a % w; 101 | var bHI = (b / w) << 0; 102 | var bLO = b % w; 103 | 104 | return ((aHI & bHI) * w + (aLO & bLO)); 105 | }; 106 | 107 | /* Simple JavaScript Inheritance 108 | * By John Resig http://ejohn.org/ 109 | * MIT Licensed. 110 | */ 111 | // Inspired by base2 and Prototype 112 | (function(){ 113 | var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 114 | // The base Class implementation (does nothing) 115 | this.Class = function(){}; 116 | 117 | // Create a new Class that inherits from this class 118 | Class.extend = function(prop) { 119 | var _super = this.prototype; 120 | 121 | // Instantiate a base class (but only create the instance, 122 | // don't run the init constructor) 123 | initializing = true; 124 | var prototype = new this(); 125 | initializing = false; 126 | 127 | // Copy the properties over onto the new prototype 128 | for (var name in prop) { 129 | // Check if we're overwriting an existing function 130 | prototype[name] = typeof prop[name] === "function" && 131 | typeof _super[name] === "function" && fnTest.test(prop[name]) ? 132 | (function(name, fn){ 133 | return function() { 134 | var tmp = this._super; 135 | 136 | // Add a new ._super() method that is the same method 137 | // but on the super-class 138 | this._super = _super[name]; 139 | 140 | // The method only need to be bound temporarily, so we 141 | // remove it when we're done executing 142 | var ret = fn.apply(this, arguments); 143 | this._super = tmp; 144 | 145 | return ret; 146 | }; 147 | })(name, prop[name]) : 148 | prop[name]; 149 | } 150 | 151 | // The dummy class constructor 152 | function Class() { 153 | // All construction is actually done in the init method 154 | if ( !initializing && this.init ) 155 | this.init.apply(this, arguments); 156 | } 157 | 158 | // Populate our constructed prototype object 159 | Class.prototype = prototype; 160 | 161 | // Enforce the constructor to be what we expect 162 | Class.prototype.constructor = Class; 163 | 164 | // And make this class extendable 165 | Class.extend = arguments.callee; 166 | 167 | return Class; 168 | }; 169 | })(); 170 | 171 | 172 | -------------------------------------------------------------------------------- /src/mp3file.js: -------------------------------------------------------------------------------- 1 | Mad.MP3File = function(stream) { 2 | this.stream = stream; 3 | } 4 | 5 | Mad.MP3File.prototype.getID3v2Header = function() { 6 | if (this.stream.strEquals(0, "ID3")) { 7 | var headerStream = this.stream.substream(0, 10); 8 | 9 | headerStream.seek(3); // 'ID3' 10 | 11 | var major = headerStream.readU8(); 12 | var minor = headerStream.readU8(); 13 | 14 | var flags = headerStream.readU8(); 15 | 16 | var length = headerStream.readSyncInteger(); 17 | 18 | return { version: '2.' + major + '.' + minor, major: major, minor: minor, flags: flags, length: length }; 19 | } else { 20 | return null; 21 | } 22 | } 23 | 24 | Mad.MP3File.prototype.getID3v2Stream = function() { 25 | var header = this.getID3v2Header(); 26 | 27 | if (header) { 28 | if (header.major > 2) { 29 | return new Mad.ID3v23Stream(header, this.stream.substream(10, header.length)); 30 | } else { 31 | return new Mad.ID3v22Stream(header, this.stream.substream(10, header.length)); 32 | } 33 | } else { 34 | return null; 35 | } 36 | } 37 | 38 | Mad.MP3File.prototype.getMpegStream = function() { 39 | var id3header = this.getID3v2Header(); 40 | 41 | if (id3header) { 42 | var offset = 10 + id3header.length; 43 | } else { 44 | var offset = 0; 45 | } 46 | 47 | var length = this.stream.length - offset; 48 | 49 | return new Mad.Stream(this.stream.substream(offset), length); 50 | } 51 | -------------------------------------------------------------------------------- /src/player.js: -------------------------------------------------------------------------------- 1 | 2 | Mad.Player = function (stream) { 3 | this.stream = stream; 4 | this.mp3 = new Mad.MP3File(stream); 5 | this.id3 = this.mp3.getID3v2Stream(); 6 | this.mpeg = this.mp3.getMpegStream(); 7 | 8 | this.totalLength = this.id3 && ~~this.id3.toHash()['Length']; 9 | 10 | // default onProgress handler 11 | this.onProgress = function (playtime, total, preloaded) { 12 | console.log("playtime = " + playtime + " / " + total + ", preloaded = " + preloaded); 13 | } 14 | }; 15 | 16 | // Create a device. 17 | Mad.Player.prototype.createDevice = function() { 18 | var synth = new Mad.Synth(); 19 | this.frame = new Mad.Frame(); 20 | this.frame = Mad.Frame.decode(this.frame, this.mpeg); 21 | if (this.frame === null) { 22 | if (this.mpeg.error === Mad.Error.BUFLEN) { 23 | console.log("End of file!"); 24 | } 25 | 26 | console.log("First error! code = " + this.mpeg.error + ", recoverable ? = " + Mad.recoverable(this.mpeg.error)); 27 | return; 28 | } 29 | 30 | this.channelCount = this.frame.header.nchannels(); 31 | this.sampleRate = this.frame.header.samplerate; 32 | 33 | console.log("this.playing " + this.channelCount + " channels, samplerate = " + this.sampleRate + " audio, mode " + this.frame.header.mode); 34 | 35 | this.offset = 0; 36 | this.absoluteFrameIndex = 0; 37 | synth.frame(this.frame); 38 | 39 | this.lastRebuffer = Date.now(); 40 | this.playing = false; 41 | this.progress(); 42 | 43 | var self = this; 44 | var MAX_FRAMES_IN_BUFFER = 40; 45 | 46 | this.refill = function (sampleBuffer) { 47 | //console.log("delta = " + (Date.now() - self.lastRebuffer) + ", asked for " + sampleBuffer.length); 48 | self.lastRebuffer = Date.now(); 49 | 50 | if(!self.playing) return; // empty sampleBuffer, no prob 51 | 52 | var index = 0; 53 | 54 | while (index < sampleBuffer.length) { 55 | for (var i = 0; i < self.channelCount; ++i) { 56 | sampleBuffer[index++] = synth.pcm.samples[i][self.offset]; 57 | } 58 | 59 | self.offset++; 60 | 61 | if (self.offset >= synth.pcm.samples[0].length) { 62 | self.offset = 0; 63 | 64 | self.frame = Mad.Frame.decode(self.frame, self.mpeg); 65 | if (self.frame === null) { 66 | if (self.stream.error === Mad.Error.BUFLEN) { 67 | console.log("End of file!"); 68 | } 69 | console.log("Error! code = " + self.mpeg.error); 70 | self.playing = false; 71 | self.onProgress(1.0, 1.0, 1.0); 72 | self.dev.kill(); 73 | } else { 74 | synth.frame(self.frame); 75 | self.absoluteFrameIndex++; 76 | } 77 | } 78 | } 79 | 80 | if (self.onPostProcessing) { 81 | self.onPostProcessing.apply(this, arguments); 82 | } 83 | 84 | }; 85 | 86 | this.reinitDevice(); 87 | }; 88 | 89 | Mad.Player.prototype.reinitDevice = function() { 90 | if(this.dev) this.dev.kill(); 91 | // The default value is the safest 92 | var bufferSize = null; 93 | var self = this; 94 | 95 | var temp = Sink.sinks.moz.prototype.interval; 96 | Sink.sinks.moz.prototype.interval = 100; 97 | 98 | this.dev = Sink(function(){ 99 | return self.refill.apply(this, arguments); 100 | }, this.channelCount, bufferSize, this.sampleRate); 101 | 102 | this.dev.on && this.dev.on('error', function (e) { 103 | console.log(e); 104 | }); 105 | 106 | Sink.sinks.moz.prototype.interval = temp; 107 | } 108 | 109 | Mad.Player.prototype.setPlaying = function(playing) { 110 | this.playing = playing; 111 | if(playing) { 112 | this.onPlay(); 113 | } else { 114 | this.onPause(); 115 | } 116 | } 117 | 118 | Mad.Player.prototype.destroy = function() { 119 | clearTimeout(this.progressTimeout); 120 | if(this.dev) { 121 | this.dev.kill(); 122 | } 123 | } 124 | 125 | Mad.Player.prototype.progress = function () { 126 | var delta = Date.now() - this.lastRebuffer; 127 | 128 | var playtime = ((this.absoluteFrameIndex * 1152 + this.offset) / this.sampleRate) + delta / 1000.0; 129 | //console.log("delta = " + delta + ", contentLength = " + this.stream.contentLength + ", this.offset = " + this.mpeg.this_frame); 130 | var total = this.totalLength ? this.totalLength : playtime * this.stream.contentLength / this.mpeg.this_frame; 131 | var preloaded = this.stream.amountRead / this.stream.contentLength; 132 | //console.log("amountRead = " + this.stream.amountRead + ", preloaded = " + preloaded); 133 | this.onProgress(playtime, total, preloaded); 134 | 135 | var that = this; 136 | var nextCall = function() { that.progress(); }; 137 | this.progressTimeout = setTimeout(nextCall, 250); 138 | } 139 | 140 | Mad.Player.fromFile = function (file, callback) { 141 | new Mad.FileStream(file, function (stream) { 142 | callback(new Mad.Player(stream)); 143 | }); 144 | }; 145 | 146 | Mad.Player.fromURL = function (url, callback) { 147 | var stream = new Mad.AjaxStream(url); 148 | stream.requestAbsolute(1 * 1024, function () { 149 | callback(new Mad.Player(stream)); 150 | }); 151 | }; 152 | -------------------------------------------------------------------------------- /src/stream.js: -------------------------------------------------------------------------------- 1 | 2 | Mad.Error = { 3 | NONE : 0x0000, /* no error */ 4 | 5 | BUFLEN : 0x0001, /* input buffer too small (or EOF) */ 6 | BUFPTR : 0x0002, /* invalid (null) buffer pointer */ 7 | 8 | NOMEM : 0x0031, /* not enough memory */ 9 | 10 | LOSTSYNC : 0x0101, /* lost synchronization */ 11 | BADLAYER : 0x0102, /* reserved header layer value */ 12 | BADBITRATE : 0x0103, /* forbidden bitrate value */ 13 | BADSAMPLERATE : 0x0104, /* reserved sample frequency value */ 14 | BADEMPHASIS : 0x0105, /* reserved emphasis value */ 15 | 16 | BADCRC : 0x0201, /* CRC check failed */ 17 | BADBITALLOC : 0x0211, /* forbidden bit allocation value */ 18 | BADSCALEFACTOR : 0x0221, /* bad scalefactor index */ 19 | BADMODE : 0x0222, /* bad bitrate/mode combination */ 20 | BADFRAMELEN : 0x0231, /* bad frame length */ 21 | BADBIGVALUES : 0x0232, /* bad big_values count */ 22 | BADBLOCKTYPE : 0x0233, /* reserved block_type */ 23 | BADSCFSI : 0x0234, /* bad scalefactor selection info */ 24 | BADDATAPTR : 0x0235, /* bad main_data_begin pointer */ 25 | BADPART3LEN : 0x0236, /* bad audio data length */ 26 | BADHUFFTABLE : 0x0237, /* bad Huffman table select */ 27 | BADHUFFDATA : 0x0238, /* Huffman data overrun */ 28 | BADSTEREO : 0x0239 /* incompatible block_type for JS */ 29 | }; 30 | 31 | Mad.BUFFER_GUARD = 8; 32 | Mad.BUFFER_MDLEN = (511 + 2048 + Mad.BUFFER_GUARD); 33 | 34 | Mad.Stream = function (stream) { 35 | this.stream = stream; /* actual buffer (js doesn't have pointers!) */ 36 | this.buffer = 0; /* input bitstream buffer */ 37 | this.bufend = stream.length; /* input bitstream buffer */ 38 | this.skiplen = 0; /* bytes to skip before next frame */ 39 | 40 | this.sync = 0; /* stream sync found */ 41 | this.freerate = 0; /* free bitrate (fixed) */ 42 | 43 | this.this_frame = 0; /* start of current frame */ 44 | this.next_frame = 0; /* start of next frame */ 45 | 46 | this.ptr = new Mad.Bit(this.stream, this.buffer); /* current processing bit pointer */ 47 | 48 | this.anc_ptr = /* MadBit */ null; /* ancillary bits pointer */ 49 | this.anc_bitlen = 0; /* number of ancillary bits */ 50 | 51 | this.main_data = /* string */ Mad.Storage.newBuffer(Mad.BUFFER_MDLEN); /* Layer III main_data() */ 52 | this.md_len = 0; /* bytes in main_data */ 53 | 54 | var options = 0; /* decoding options (see below) */ 55 | var error = Mad.Error.NONE; /* error code (see above) */ 56 | }; 57 | 58 | Mad.Stream.fromFile = function(file, callback) { 59 | var reader = new FileReader(); 60 | reader.onloadend = function (evt) { 61 | callback(new Mad.Stream(evt.target.result)); 62 | }; 63 | reader.readAsBinaryString(file); 64 | }; 65 | 66 | Mad.Stream.prototype.readShort = function(bBigEndian) { 67 | return this.stream.readU16(bBigEndian); 68 | }; 69 | 70 | Mad.Stream.prototype.readSShort = function(bBigEndian) { 71 | return this.stream.readI16(bBigEndian); 72 | }; 73 | 74 | Mad.Stream.prototype.getU8 = function(index) { 75 | return this.stream.getU8(index); 76 | }; 77 | 78 | 79 | Mad.Stream.prototype.readU8 = function() { 80 | return this.stream.readU8(index); 81 | }; 82 | 83 | Mad.Stream.prototype.readChars = function(length) { 84 | return this.stream.read(length); 85 | }; 86 | 87 | Mad.Stream.prototype.peekChars = function(length) { 88 | return this.stream.peek(length); 89 | } 90 | 91 | /* 92 | * NAME: stream->sync() 93 | * DESCRIPTION: locate the next stream sync word 94 | */ 95 | Mad.Stream.prototype.doSync = function() { 96 | var ptr = this.ptr.nextbyte(); 97 | var end = this.bufend; 98 | 99 | while (ptr < end - 1 && !(this.getU8(ptr) === 0xff && (this.getU8(ptr + 1) & 0xe0) === 0xe0)) { 100 | ++ptr; 101 | } 102 | 103 | if (end - ptr < Mad.BUFFER_GUARD) { 104 | return -1; 105 | } 106 | 107 | this.ptr = new Mad.Bit(this.stream, ptr); 108 | 109 | return 0; 110 | } 111 | 112 | 113 | -------------------------------------------------------------------------------- /tests/node/aliasreduce.js: -------------------------------------------------------------------------------- 1 | require('./typed-array.js'); 2 | require('../../mad.js'); 3 | require('../../layer3.js'); 4 | 5 | var xr = [ 6 | -0.00049903,0.00151888,-0.00044203,-0.00307714,0.00167362,0.00613469,-0.00406994,-0.01457764, 7 | 0.01214388,0.06229898,-0.14182327,0.92929485,0.13768128,0.06171946,-0.01344565,-0.01477975, 8 | 0.00646184,0.00701636,0.00416800,0.00344713,-0.00471758,-0.00237028,0.00517955,0.00416800, 9 | 0.00312281,0.00100904,-0.00067976,-0.00012207,0.00016437,0.00004844,-0.00006523,-0.00001923, 10 | 0.00003301,0.00001923,-0.00001923,-0.00001923,-0.00001211,-0.00001211,0.00000481,0.00000825, 11 | -0.00000191,-0.00000191,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000539, 12 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 13 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000381,0.00000000,0.00000000,0.00000000, 14 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 15 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 16 | 0.00000000,0.00000000,0.00000000,0.00000191,0.00000000,0.00000000,0.00000000,0.00000000, 17 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 18 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 19 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 20 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000381, 21 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 22 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 23 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 24 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 25 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 26 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 27 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 28 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 29 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000270,0.00000000, 30 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 31 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 32 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 33 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 34 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 35 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 36 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 37 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 38 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 39 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 40 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 41 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 42 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 43 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 44 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 45 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 46 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 47 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 48 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 49 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 50 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 51 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 52 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 53 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 54 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,-0.00000763,0.00000000, 55 | 0.00000000,0.00000763,-0.00000763,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 56 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 57 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000763,0.00000763,0.00000000, 58 | 0.00000000,-0.00000763,0.00000000,0.00000000,0.00000000,0.00000000,0.00000763,0.00000000, 59 | -0.00000763,0.00000000,0.00000000,0.00000000,0.00000000,-0.00000763,0.00000000,0.00000763, 60 | -0.00000763,-0.00000763,0.00000000,-0.00000763,-0.00000763,0.00000000,0.00000763,0.00000763, 61 | 0.00000000,-0.00000763,-0.00000763,-0.00000763,0.00000000,0.00000000,0.00000000,-0.00000763, 62 | -0.00000763,0.00000000,-0.00000763,0.00000000,-0.00000763,0.00000000,0.00000000,0.00000000, 63 | 0.00001923,0.00000763,0.00000000,0.00000763,0.00000000,0.00000763,0.00000000,0.00000000, 64 | -0.00000763,-0.00000763,0.00000000,0.00000763,0.00000763,-0.00000763,0.00000000,0.00000000, 65 | -0.00000763,0.00000763,0.00000763,0.00000000,-0.00000763,0.00000000,0.00001923,-0.00000763, 66 | 0.00000000,0.00000763,0.00000000,0.00000763,0.00000000,-0.00001923,-0.00000763,0.00000763, 67 | 0.00000763,-0.00000763,-0.00000763,-0.00000763,0.00000763,0.00000000,0.00000000,0.00000000, 68 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 69 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 70 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 71 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 72 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 73 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 74 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 75 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 76 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 77 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000 78 | ]; 79 | var xr2 = [ 80 | -0.00049903,0.00151888,-0.00044203,-0.00307714,0.00167362,0.00613469,-0.00406994,-0.01457764, 81 | 0.01214388,0.06229898,-0.14181857,0.92924551,0.13773644,0.06193267,-0.01365250,-0.01551367, 82 | 0.00732380,0.00816090,-0.00003586,-0.00000878,0.00015169,0.00011521,-0.00068073,-0.00147570, 83 | -0.01007217,0.00153378,-0.00067976,-0.00012207,0.00016437,0.00004844,-0.00006526,-0.00001932, 84 | 0.00003396,0.00001976,-0.00002267,-0.00002272,-0.00000050,-0.00000161,-0.00000146,0.00000211, 85 | -0.00000008,0.00000076,-0.00000069,-0.00000061,0.00000000,0.00000000,0.00000000,0.00000544, 86 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 87 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000373,0.00000000,0.00000000,0.00000000, 88 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 89 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 90 | 0.00000000,0.00000000,0.00000000,0.00000190,0.00000000,0.00000000,0.00000000,0.00000000, 91 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 92 | -0.00000003,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 93 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 94 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000381, 95 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 96 | 0.00000000,0.00000000,0.00000000,0.00000000,-0.00000006,0.00000000,0.00000000,0.00000000, 97 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 98 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 99 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 100 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 101 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 102 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 103 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000269,0.00000000, 104 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 105 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,-0.00000001,0.00000000,0.00000000, 106 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 107 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 108 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 109 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 110 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 111 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 112 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 113 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 114 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 115 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 116 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 117 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 118 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 119 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 120 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 121 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 122 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 123 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 124 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 125 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 126 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 127 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 128 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,-0.00000763,0.00000000, 129 | 0.00000000,0.00000724,-0.00000673,0.00000000,0.00000000,0.00000360,-0.00000239,0.00000000, 130 | 0.00000000,0.00000031,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 131 | 0.00000000,0.00000000,-0.00000139,0.00000000,0.00000000,0.00001046,0.00000262,0.00000000, 132 | 0.00000000,-0.00000750,0.00000000,0.00000000,0.00000000,0.00000000,0.00000763,0.00000000, 133 | -0.00000760,0.00000011,0.00000000,-0.00000072,-0.00000139,-0.00000725,-0.00000360,0.00000262, 134 | -0.00001047,-0.00000673,0.00000239,-0.00000750,-0.00000760,0.00000000,0.00000763,0.00000765, 135 | 0.00000000,-0.00000763,-0.00000760,-0.00000736,0.00000000,0.00000000,0.00000000,-0.00000964, 136 | -0.00000673,-0.00000393,-0.00000655,0.00000360,-0.00000486,0.00000000,0.00000000,0.00000000, 137 | 0.00001933,0.00000765,0.00000000,0.00000763,0.00000000,0.00000773,0.00000031,-0.00000072, 138 | -0.00000750,-0.00000725,-0.00000360,0.00001046,0.00000262,-0.00000673,0.00000239,0.00000139, 139 | -0.00000760,0.00000762,0.00000751,0.00000000,-0.00000763,0.00000000,0.00001922,-0.00000752, 140 | -0.00000031,0.00000687,-0.00000139,0.00000963,0.00000360,-0.00002041,0.00000335,0.00000672, 141 | 0.00000485,-0.00000750,-0.00000832,-0.00000763,0.00000773,-0.00000007,0.00000000,0.00000000, 142 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 143 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 144 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 145 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 146 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 147 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 148 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 149 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 150 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000, 151 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000 152 | ]; 153 | 154 | Mad.III_aliasreduce(xr, 576); 155 | 156 | for (var i = 0; i < 576; i++) { 157 | console.log(Math.abs(xr[i] - xr2[i])); 158 | } -------------------------------------------------------------------------------- /tests/node/check.rb: -------------------------------------------------------------------------------- 1 | # Dir.chdir("../../../libmad") do 2 | # system("make minimad && ./minimad < one_second_beep.mp3 > ../jsmad/experiments/node/libmad.txt") 3 | # end 4 | 5 | # system("node test.js > jsmad.txt") 6 | 7 | libmad = File.readlines(ARGV[0]) 8 | jsmad = File.readlines(ARGV[1]) 9 | 10 | diffs = [] 11 | 12 | libmad.zip(jsmad) do |a, b| 13 | a.split("\t").zip(b.split("\t")) do |x, y| 14 | diffs << (x.to_f - y.to_f).abs 15 | end 16 | end 17 | 18 | puts diffs.max 19 | -------------------------------------------------------------------------------- /tests/node/dct.js: -------------------------------------------------------------------------------- 1 | require('./typed-array.js'); 2 | require('../../mad.js'); 3 | require('../../synth.js'); 4 | 5 | var _in = [-24885929,47196,16460,331490,-41214,-9529,22626,8736,-7265,-6506,12615,-7664,11799,-5896,-5173,-4064,5503,-2814,8527,-8558,-2798,-11028,-11343,-4432,6220,2036,-1558,5120,0,0,0,0]; 6 | var lo = [-17429241,-16475780,-15459112,-14484372,-13536059,-12581038,-11687286,-10701390,-9665573,-8622052,-7557596,-6432024,-5223547,-3996414,-2651606,-1281770]; 7 | var hi = [-18405790,-19328854,-20162190,-20939601,-21600256,-22213828,-22816256,-23239559,-23583918,-23939478,-24126106,-24292333,-24437136,-24456072,-24497924,-24537443]; 8 | 9 | function fixToFloat(x) { 10 | return x / (1 << 28); 11 | } 12 | 13 | function floatArray(list) { 14 | var result = []; 15 | for (var i = 0; i < list.length; i++) { 16 | result[i] = fixToFloat(list[i]); 17 | } 18 | return result; 19 | } 20 | 21 | lo = floatArray(lo); 22 | hi = floatArray(hi); 23 | 24 | var _lo = []; 25 | var _hi = []; 26 | 27 | for (var i = 0; i < 16; i++) { 28 | _lo[i] = []; 29 | _hi[i] = []; 30 | } 31 | 32 | Mad.Synth.dct32(floatArray(_in), 0, _lo, _hi); 33 | 34 | for (var i = 0; i < 16; i++) { 35 | console.log(Math.abs(_lo[i][0] - lo[i])); 36 | } 37 | 38 | for (var i = 0; i < 16; i++) { 39 | console.log(Math.abs(_hi[i][0] - hi[i])); 40 | } -------------------------------------------------------------------------------- /tests/node/fastdsct.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var sys = require('sys'); 3 | 4 | require('./typed-array.js'); 5 | require('../../mad.js'); 6 | require('../../id3.js'); 7 | require('../../layer3.js'); 8 | var x = [ 9 | -0.00188436,-0.00002753,0.00420964,0.00203913,-0.00782420,-0.00490182,0.01520704,0.01104762,-0.03868370,-0.04013703,0.37942966,-0.46913085,-0.27882083,-0.02320458,0.01611372,0.00400412,-0.00274224,-0.00063538]; 10 | var X = [ 11 | -0.43594157,0.53499295,0.26976736,-0.95755410,0.85856501,0.13600542,-1.19115227,1.15186305,0.17104355,-1.59530227,1.05767249,0.86786658,-1.50597779,0.15820546,1.25546645,-0.87142866,-0.66177376,1.18015846]; 12 | 13 | var _X = []; 14 | 15 | sdctII(x, _X); 16 | 17 | for (var i = 0; i < 18; i++) { 18 | console.log(Math.abs(_X[i] - X[i])); 19 | } 20 | -------------------------------------------------------------------------------- /tests/node/huffdecode.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var sys = require('sys'); 3 | 4 | require('./typed-array.js'); 5 | require('../../mad.js'); 6 | require('../../id3.js'); 7 | require('../../layer3.js'); 8 | require('../../synth.js'); 9 | require("../../mad.js"); 10 | require("../../rq_table.js"); 11 | require("../../imdct_s.js"); 12 | require("../../huffman.js"); 13 | require("../../bit.js"); 14 | require("../../stream.js"); 15 | require("../../id3.js"); 16 | require("../../layer3.js"); 17 | require("../../frame.js"); 18 | require("../../synth.js"); 19 | 20 | var data = fs.readFileSync("one_second_of_silence.mp3", "binary"); 21 | // var data = fs.readFileSync("soul-2.mp3", "binary"); 22 | 23 | console.log("Reading a " + Math.round(data.length / 1024) + "KB file"); 24 | 25 | var stream = new Mad.Stream(data); 26 | 27 | ID3_skipHeader(stream); 28 | 29 | var STEPS_COUNT = 0; 30 | 31 | var frame = null; 32 | 33 | frame = Mad.Frame.decode(stream); // apparently first frame has nothing to decode 34 | frame = Mad.Frame.decode(stream); 35 | 36 | console.log("error code: " + stream.error); 37 | -------------------------------------------------------------------------------- /tests/node/imdct.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var sys = require('sys'); 3 | 4 | require('./typed-array.js'); 5 | require('../../mad.js'); 6 | require('../../imdct_s.js'); 7 | require('../../layer3.js'); 8 | 9 | function test_imdct_s() { 10 | var X = [ 11 | -0.04093509,0.08902737,-0.04673872,0.81290334,0.33560085,-0.10171456,0.00504069,0.01295291,-0.06297208,0.59918465,0.49750966,0.04453132,0.00895240,0.00263847,-0.08176661,0.16397671,0.64196996,0.02567524]; 12 | var z = [ 13 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.08577475,-0.25815145,-0.18377631,0.23950195,0.62323273,-0.65152391,-0.07279154,0.76031951,-0.55000097,-0.29281906,0.79850607,-0.38041764,-0.47771208,0.78320405,-0.18270260,-0.62917816,0.71326943,0.02766476,-0.66871921,0.65951302,-0.23426843,-0.17976049,0.27317924,-0.08803856,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000]; 14 | 15 | var _z = []; 16 | 17 | Mad.III_imdct_s(X, _z); 18 | 19 | for (var i = 0; i < 36; i++) { 20 | console.log(Math.abs(_z[i] - z[i])); 21 | } 22 | } 23 | 24 | function test_imdct_l() { 25 | var X, z; 26 | 27 | X = [ 28 | -0.00048066,-0.00054808,0.00167604,0.00182871,-0.00358925,-0.00410959,0.00777910,0.01007085,-0.02279030,-0.04289347,0.26965541,-0.63553121,-0.26141275,-0.04325058,0.02372279,0.01200023,-0.01000499,-0.00799890]; 29 | z = [ 30 | -0.01657986,0.10046391,-0.03129764,-0.19516312,0.21620646,0.12183119,-0.38838422,0.12602713,0.39296440,-0.42884540,-0.16424175,0.60964111,-0.23403555,-0.52196859,0.61897832,0.14117452,-0.76309917,0.37974112,0.46963350,-0.76341553,0.29892312,0.41404148,-0.67129315,0.27711135,0.32650343,-0.55648259,0.25239373,0.23127624,-0.42700411,0.20800562,0.14425503,-0.27805873,0.13054677,0.06626966,-0.10050556,0.02050464]; 31 | 32 | _z = []; 33 | 34 | Mad.III_imdct_l(X, _z, 0); 35 | 36 | for (var i = 0; i < 36; i++) { 37 | console.log(Math.abs(_z[i] - z[i])); 38 | } 39 | 40 | X = [ 41 | -0.02502749,-0.02467428,-0.02383710,-0.02223757,-0.01950704,-0.01533347,-0.00945976,-0.00192413,0.00689093,0.01623087,0.02511228,0.03241420,0.03710076,0.03837438,0.03574897,0.02914715,0.01910392,0.00665824]; 42 | z = [ 43 | -0.00000008,-0.00000039,-0.00000881,0.00000329,-0.00000494,0.00000096,0.00000473,0.00000410,0.00001113,-0.00001216,-0.00000535,-0.00000744,-0.00000184,0.00001191,-0.00001045,0.00003971,0.00000298,0.00000172,-0.00000943,0.00001619,-0.00017760,0.00081702,-0.00045995,0.00505592,-0.09994537,0.26467714,0.02750298,0.02110377,0.10963286,-0.01315806,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000]; 44 | 45 | _z = []; 46 | 47 | Mad.III_imdct_l(X, _z, 1); 48 | 49 | for (var i = 0; i < 36; i++) { 50 | console.log(Math.abs(_z[i] - z[i])); 51 | } 52 | 53 | X = [ 54 | -0.00000888,-0.00000255,0.00000000,0.00000000,0.00000537,0.00000539,0.00000007,0.00000539,0.00000539,0.00000000,0.00000541,0.00000539,0.00000056,0.00000408,-0.00000283,-0.00000513,-0.00000221,-0.00000463]; 55 | z = [ 56 | 0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000000,0.00000061,-0.00000124,0.00000068,-0.00000089,0.00000298,-0.00000468,0.00000791,-0.00001652,0.00000371,0.00001790,0.00000728,0.00000033,-0.00000029,0.00000181,0.00000694,-0.00000166,0.00002309,-0.00000006,0.00002645,0.00001713,-0.00000899,-0.00000824,0.00001314,0.00001685,-0.00000003,0.00000956,-0.00000053,0.00000154,0.00000024,-0.00000001]; 57 | 58 | 59 | Mad.III_imdct_l(X, _z, 3); 60 | 61 | for (var i = 0; i < 36; i++) { 62 | console.log(Math.abs(_z[i] - z[i])); 63 | } 64 | 65 | } 66 | 67 | test_imdct_s(); 68 | test_imdct_l(); -------------------------------------------------------------------------------- /tests/node/one_second_beep.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/tests/node/one_second_beep.mp3 -------------------------------------------------------------------------------- /tests/node/one_second_of_silence.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/tests/node/one_second_of_silence.mp3 -------------------------------------------------------------------------------- /tests/node/out.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/tests/node/out.raw -------------------------------------------------------------------------------- /tests/node/output.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var sys = require('sys'); 3 | 4 | require('./typed-array.js'); 5 | require('../../mad.js'); 6 | require('../../id3.js'); 7 | require('../../layer3.js'); 8 | require('../../synth.js'); 9 | require("../../mad.js"); 10 | require("../../rq_table.js"); 11 | require("../../imdct_s.js"); 12 | require("../../huffman.js"); 13 | require("../../bit.js"); 14 | require("../../stream.js"); 15 | require("../../id3.js"); 16 | require("../../layer3.js"); 17 | require("../../frame.js"); 18 | require("../../synth.js"); 19 | 20 | // var data = fs.readFileSync("one_second_of_silence.mp3", "binary"); 21 | // var data = fs.readFileSync("one_second_beep.mp3", "binary"); 22 | var data = fs.readFileSync("output.mp3", "binary"); 23 | // var data = fs.readFileSync("soul-2.mp3", "binary"); 24 | 25 | console.log("Reading a " + Math.round(data.length / 1024) + "KB file"); 26 | 27 | var stream = new Mad.Stream(data); 28 | 29 | ID3_skipHeader(stream); 30 | 31 | var STEPS_COUNT = 0; 32 | 33 | var frame = null; 34 | 35 | var allmin = 0; 36 | var allmax = 0; 37 | 38 | while (frame = Mad.Frame.decode(stream)) { 39 | var synth = new Mad.Synth(); 40 | synth.frame(frame); 41 | 42 | var samples = synth.pcm.samples[0]; 43 | 44 | var min = 0.0; 45 | var max = 0.0; 46 | var mean = 0.0; 47 | 48 | for (var i = 0; i < samples.length; i++) { 49 | var sample = samples[i]; 50 | mean += (sample / samples.length); 51 | if(min > sample) min = sample; 52 | if(max < sample) max = sample; 53 | } 54 | 55 | console.log("min = " + min + ", max = " + max + ", mean = " + mean); 56 | if(allmin > min) allmin = min; 57 | if(allmax < max) allmax = max; 58 | } 59 | 60 | console.log("allmin = " + allmin + ", allmax = " + allmax); 61 | 62 | console.log("error code: " + stream.error); 63 | -------------------------------------------------------------------------------- /tests/node/pcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/tests/node/pcm.png -------------------------------------------------------------------------------- /tests/node/plot-pcm.gnuplot: -------------------------------------------------------------------------------- 1 | set terminal png 2 | set output 'pcm.png' 3 | plot "pcm-js.txt" title 'jsmad' with lines, "pcm.txt" title 'libmad' with lines 4 | -------------------------------------------------------------------------------- /tests/node/plot-sample.gnuplot: -------------------------------------------------------------------------------- 1 | set terminal png 2 | set output 'sample.png' 3 | plot "sample-js.txt" title 'jsmad' with lines, "sample.txt" title 'libmad' with lines 4 | -------------------------------------------------------------------------------- /tests/node/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/audiocogs/jsmad/3edad8b2cae8b61d692f14094cdb52ce9792f7bb/tests/node/sample.png -------------------------------------------------------------------------------- /tests/node/sample.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | Produced by GNUPLOT 4.4 patchlevel 2 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -0.01 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -0.008 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -0.006 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -0.004 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -0.002 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 0 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 0.002 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 0.004 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 0.006 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 0.008 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 0.01 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -0.8 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -0.6 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -0.4 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -0.2 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 0 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 0.2 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 0.4 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 0.6 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 0.8 210 | 211 | 212 | 213 | 214 | 215 | 216 | jsmad 217 | 218 | 229 | 230 | 231 | 232 | 233 | 234 | libmad 235 | 236 | 247 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /tests/node/typed-array.js: -------------------------------------------------------------------------------- 1 | 2 | ArrayBuffer = function(size) { 3 | var array = new Array(size / 4); 4 | 5 | for (var i = 0; i < array.length; i++) { 6 | array[i] = 0; 7 | } 8 | 9 | return array; 10 | }; 11 | 12 | Int32Array = function(buffer) { 13 | return buffer; 14 | }; 15 | 16 | Float32Array = function(buffer) { 17 | return buffer; 18 | }; 19 | 20 | Float64Array = function(buffer) { 21 | return buffer; 22 | }; 23 | --------------------------------------------------------------------------------