├── .gitignore ├── tests ├── sound.au ├── sound.aif ├── sound.m4a ├── sound.mp3 ├── sound.wav ├── Sous La Pluie.mp3 ├── test07.js ├── test01.js ├── test06.js ├── test08.js ├── test09.js ├── test04.js ├── test05.js ├── test12.js ├── test10.js ├── test14.js ├── test15.js ├── test11.js ├── test02.js ├── test03.js ├── test00.js ├── test13.js ├── test-sinewave.js ├── test-squarewave.js ├── test-sawtoothwave.js ├── test-sinewaves.js ├── test-sawtoothwaves.js ├── test-squarewaves.js ├── test-noise-stereo.js ├── test-noise-stereo-polyphonic.js ├── test-multiwaves-stereo.js └── test-multiwaves-stereo-polyphonic.js ├── imgs ├── resumen.png ├── downloads.png ├── downloads_OLD.png └── node-waf-output.png ├── AUTHORS ├── wscript ├── Changelog ├── err_01.js ├── LICENSE ├── README.md └── src └── sound.cc /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | build/ 3 | .lock-wscript 4 | -------------------------------------------------------------------------------- /tests/sound.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/tests/sound.au -------------------------------------------------------------------------------- /imgs/resumen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/imgs/resumen.png -------------------------------------------------------------------------------- /tests/sound.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/tests/sound.aif -------------------------------------------------------------------------------- /tests/sound.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/tests/sound.m4a -------------------------------------------------------------------------------- /tests/sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/tests/sound.mp3 -------------------------------------------------------------------------------- /tests/sound.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/tests/sound.wav -------------------------------------------------------------------------------- /imgs/downloads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/imgs/downloads.png -------------------------------------------------------------------------------- /imgs/downloads_OLD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/imgs/downloads_OLD.png -------------------------------------------------------------------------------- /imgs/node-waf-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/imgs/node-waf-output.png -------------------------------------------------------------------------------- /tests/Sous La Pluie.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xk/node-sound/HEAD/tests/Sous La Pluie.mp3 -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution. 2 | 3 | Jorge Chamorro Bieling 4 | -------------------------------------------------------------------------------- /tests/test07.js: -------------------------------------------------------------------------------- 1 | // test loop() to see if it makes any glitches when playing in a loop. 2 | 3 | var buffer= new Buffer(256*256); // enough 4 | var len= buffer.length; 5 | while (len--) buffer[len]= len%256; // saw; 6 | 7 | 8 | require('sound').create(buffer).loop(1e6).volume(1).play(); // loop 9 | 10 | setTimeout(Date.now, 1e9); // forever 11 | 12 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | VERSION = '0.0.1' 2 | 3 | def set_options(opt): 4 | opt.tool_options("compiler_cxx") 5 | 6 | def configure(conf): 7 | conf.check_tool("compiler_cxx") 8 | conf.check_tool("node_addon") 9 | 10 | def build(bld): 11 | obj = bld.new_task_gen("cxx", "shlib", "node_addon") 12 | obj.cxxflags = ["-g0", "-D_FILE_OFFSET_BITS=64", 13 | "-D_LARGEFILE_SOURCE", "-Wall"] 14 | obj.target = "sound" 15 | obj.source = "src/sound.cc" -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | 2012-02-23 2 | Poner la cosas en su sitio: crear /src y /tests 3 | 4 | 5 | 2011-12-20 6 | Ahora funciona con cualquier node >= 0.3.6 (comprobado desde 0.3.6 hasta 0.6.6) 7 | 8 | 9 | 2011-11-03 10 | Añadir tests que muestran como sintetizar una onda: 11 | 12 | test-sinewave.js 13 | test-sinewaves.js 14 | 15 | 16 | 2011-05-16 17 | Ya funciona la versión *síncrona* de bufferify(path) -> buffer. 18 | Falta añadirle un callback, y hacerla correr en un pthread. 19 | 20 | Ver test12.js DEMO DE BUFFERIFY 21 | -------------------------------------------------------------------------------- /tests/test01.js: -------------------------------------------------------------------------------- 1 | // tests proper destruction/absence of leaks when there's unreferenced sound (and buffer) objects that are still play()ing. 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | 12 | (function loop () { 13 | process.nextTick(loop); 14 | Sound.create(new Buffer(8192)).play(); 15 | process.stdout.write('.'); 16 | })(); 17 | 18 | -------------------------------------------------------------------------------- /tests/test06.js: -------------------------------------------------------------------------------- 1 | // test loop() to see if it makes any glitches when playing in a loop. 2 | 3 | // the buffer contains 1/3rd silence + 1/3rd noise + 1/3rd silence 4 | 5 | var buffer= new Buffer(44100*4); // 1 seconds. 6 | var len= buffer.length; 7 | while (len--) { 8 | if (len < (buffer.length/3) || len >= (2*buffer.length/3)) { 9 | buffer[len]= 0; // silence 10 | } 11 | else { 12 | buffer[len]= Math.floor(Math.random()*256); // noise 13 | } 14 | } 15 | 16 | 17 | var S= require('sound'); 18 | S.create(buffer).loop(1e6).volume(1).play(); // loop 19 | 20 | setTimeout(Date, 1e9); // forever 21 | 22 | -------------------------------------------------------------------------------- /tests/test08.js: -------------------------------------------------------------------------------- 1 | // tests loop() glitches with short sounds. 2 | 3 | var buffer= new Buffer(4096); // 4096/(44100*4) == 0,02321995465 s. 4 | var len= buffer.length; 5 | var i= 0; 6 | var flipflop= 1; 7 | var onda= 0; 8 | while (len--) { 9 | 10 | process.stdout.write(onda+ ' '); 11 | 12 | buffer[len--]= onda; 13 | buffer[len--]= onda; 14 | buffer[len--]= onda; 15 | buffer[len]= onda; 16 | 17 | if (flipflop) onda+= 2; 18 | else onda-= 2; 19 | if (onda > 255) { 20 | onda= 255; 21 | flipflop= !flipflop; 22 | } 23 | else if (onda < 0) { 24 | onda= 0; 25 | flipflop= !flipflop; 26 | } 27 | 28 | } 29 | 30 | 31 | require('sound').create(buffer).loop(1e6).volume(1).play(); // loop 32 | setTimeout(Date.now, 1e9); // forever 33 | 34 | -------------------------------------------------------------------------------- /tests/test09.js: -------------------------------------------------------------------------------- 1 | // tests loop() glitches with TOO short sounds. FAIL 2 | 3 | var buffer= new Buffer(2048); // 2048/(44100*4) == 0,01160997732 s. 4 | var len= buffer.length; 5 | var flipflop= 1; 6 | var onda= 0; 7 | var inc= 4; 8 | do { 9 | 10 | process.stdout.write(onda+ ' '); 11 | 12 | buffer[--len]= onda; 13 | buffer[--len]= onda; 14 | buffer[--len]= onda; 15 | buffer[--len]= onda; 16 | 17 | onda+= flipflop ? inc : -inc; 18 | 19 | if (onda > 255) { 20 | onda= 255; 21 | flipflop= !flipflop; 22 | } 23 | else if (onda < 0) { 24 | onda= 0; 25 | flipflop= !flipflop; 26 | } 27 | 28 | } while (len); 29 | 30 | 31 | require('sound').create(buffer).loop(1e6).volume(1).play(); // loop 32 | 33 | setTimeout(Date.now, 1e9); // forever 34 | 35 | -------------------------------------------------------------------------------- /tests/test04.js: -------------------------------------------------------------------------------- 1 | // test .play() and .pause(); 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | 12 | var buf= new Buffer(256*256); 13 | var i= buf.length; 14 | while (i--) buf[i]= i%256; 15 | //while (i--) buf[i]= Math.floor(Math.random()*256); 16 | 17 | var snd= Sound.create(buf); 18 | snd.loop(1e9); 19 | 20 | var flipflop= 0; 21 | function loop () { 22 | if (++flipflop % 2) { 23 | process.stdout.write('PLAY '); 24 | snd.play(); 25 | } 26 | else { 27 | process.stdout.write('PAUSE '); 28 | snd.pause(); 29 | } 30 | 31 | setTimeout(loop, 444); 32 | } 33 | 34 | loop(); 35 | 36 | -------------------------------------------------------------------------------- /tests/test05.js: -------------------------------------------------------------------------------- 1 | // tests volume(). 2 | 3 | var buffer= new Buffer(256*256); 4 | var len= buffer.length; 5 | //while (len--) buffer[len]= Math.floor(Math.random()*256); // noise 6 | while (len--) buffer[len]= len%256; // saw 7 | 8 | var Sound; 9 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 10 | while (paths.length) { 11 | var p= paths.pop(); 12 | try { Sound= require(p) } catch (e) { continue } 13 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 14 | break; 15 | } 16 | 17 | var sound= Sound.create(buffer).volume(0).loop(1e9).play(); 18 | 19 | var i= 0; 20 | var ii= 0.25; 21 | (function loop () { 22 | setTimeout(loop, 7); 23 | i+= ii; 24 | if (i<0) { 25 | ii= 0.25; 26 | i= 0; 27 | } 28 | else if (i>1) { 29 | ii= -0.01; 30 | i= 1; 31 | } 32 | sound.volume(i); 33 | })(); 34 | 35 | -------------------------------------------------------------------------------- /tests/test12.js: -------------------------------------------------------------------------------- 1 | // Test bufferifySync() 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | var sounds= ['sound.wav', 'sound.m4a', 'sound.aif', 'sound.mp3', 'sound.au', 'Sous La Pluie.mp3']; 12 | 13 | var i= sounds.length; 14 | (function next () { 15 | if (i--) { 16 | var path= __dirname+ '/'+ sounds[i]; 17 | process.stdout.write('\n******************************* '+ path+ " ->"); 18 | console.log(" -> buffer.length -> "+ Sound.create(Sound.bufferifySync(path)).play(next).data.length); 19 | } 20 | else { 21 | clearTimeout(quitTimer); 22 | } 23 | })(); 24 | 25 | var quitTimer= setTimeout(Date.now, 1e9); 26 | 27 | -------------------------------------------------------------------------------- /tests/test10.js: -------------------------------------------------------------------------------- 1 | // exercise callbacks; 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | 12 | setTimeout(function nop(){}, 1e9); // don't quit 13 | 14 | var howMany= 3e3; 15 | 16 | function cb () { 17 | process.stdout.write('\n['+ this.id+ '].callback'); 18 | } 19 | 20 | 21 | var buffer= new Buffer(2048); 22 | var i= buffer.length; 23 | while (i--) buffer[i]= Math.floor(256*Math.random()); // noise 24 | 25 | 26 | var i= howMany; 27 | function next () { 28 | var snd= Sound.create(buffer).play(cb); 29 | process.stdout.write('\n['+ snd.id+ '].play(cb)'); 30 | if (--i) setTimeout(next, Math.floor(10*Math.random())); 31 | }; 32 | 33 | next(); -------------------------------------------------------------------------------- /err_01.js: -------------------------------------------------------------------------------- 1 | // exercise callbacks; 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | 12 | var quit= setTimeout(Date.now, 1e9); // don't quit 13 | 14 | var howMany= 9999; 15 | 16 | 17 | function cb () { 18 | process.stdout.write('\ncb['+ this.id+ ']'); 19 | } 20 | 21 | 22 | var buffer= new Buffer(44100); 23 | var i= buffer.length; 24 | while (i--) buffer[i]= Math.floor(256*Math.random()); 25 | 26 | var i= howMany; 27 | function next () { 28 | var snd= Sound.create(buffer); 29 | process.stdout.write('\n['+ snd.id+ '].play(cb)'); 30 | snd.play(cb); 31 | snd= null; 32 | if (--i) setTimeout(next, Math.floor(1000*Math.random())); 33 | }; 34 | 35 | next(); -------------------------------------------------------------------------------- /tests/test14.js: -------------------------------------------------------------------------------- 1 | // test for async bufferify()s IN SERIES in a loop to detect leaks and other problems. 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | var sounds= ['sound.wav', 'sound.aif', 'sound.au', 'sound.mp3', 'sound.m4a']; 12 | 13 | 14 | var ctr= 0; 15 | var i= sounds.length; 16 | function next () { 17 | if (--i<0) i= sounds.length-1; 18 | var path= sounds[i]; 19 | Sound.bufferify(path, cb.bind(path)); 20 | } 21 | 22 | 23 | function cb (err, buffer) { 24 | var path= ''+ this; 25 | process.stdout.write('\n['+ (ctr++)+ '] *** -> '); 26 | 27 | if (err) { 28 | process.stdout.write('ERROR -> '+ path); 29 | } 30 | else { 31 | process.stdout.write('OK -> '+ path+ ' -> buffer.length:'+ buffer.length); 32 | } 33 | 34 | next(); 35 | } 36 | 37 | next(); 38 | 39 | setTimeout(Date.now, 1e9); 40 | -------------------------------------------------------------------------------- /tests/test15.js: -------------------------------------------------------------------------------- 1 | // test for multiple async bufferify()s IN PARALLEL in a loop to detect leaks and other problems. 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | var sounds= ['sound.wav', 'sound.aif', 'sound.au', 'sound.m4a', 'sound.mp3']; 12 | 13 | var ctr= 0; 14 | var all= sounds.length; 15 | function go () { 16 | process.stdout.write('\n'); 17 | while (sounds.length) { 18 | var path= sounds.pop(); 19 | Sound.bufferify(path, cb.bind(path)); 20 | } 21 | } 22 | 23 | 24 | function cb (err, buffer) { 25 | var path= ''+ this; 26 | var ctrStr= '\n['+ (ctr++)+ '] *** '; 27 | 28 | if (err) { 29 | process.stdout.write(ctrStr+ 'ERROR -> '+ path); 30 | } 31 | else { 32 | process.stdout.write(ctrStr+ 'OK -> '+ path+ ' -> buffer.length:'+ buffer.length); 33 | } 34 | 35 | if (sounds.push(path) === all) go(); 36 | } 37 | 38 | go(); 39 | 40 | setTimeout(Date.now, 1e9); 41 | -------------------------------------------------------------------------------- /tests/test11.js: -------------------------------------------------------------------------------- 1 | // exercise recursive .play() from the callback; It's a bad idea, looping via the cb() produces glitches, should really use .loop() instead ! 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | 12 | var quit= setTimeout(function nop(){}, 1e9); // don't quit 13 | 14 | var howMany= 999; 15 | 16 | function cb () { 17 | if (this.again === 10) { 18 | process.stdout.write('\n['+ this.id+ '].callback: NO_MORE'); 19 | } 20 | else { 21 | this.again= (this.again || 0) + 1; 22 | this.play(cb); 23 | process.stdout.write('\n['+ this.id+ '].callback: PLAY_IT_AGAIN'); // Sam. 24 | } 25 | } 26 | 27 | 28 | var buffer= new Buffer(8192); 29 | var i= buffer.length; 30 | while (i--) buffer[i]= i%256; 31 | 32 | 33 | var i= howMany; 34 | function next () { 35 | var snd= Sound.create(buffer); 36 | process.stdout.write('\n['+ snd.id+ '].play(cb)'); 37 | snd.play(cb); 38 | if (--i) setTimeout(next, 3333); 39 | }; 40 | 41 | next(); -------------------------------------------------------------------------------- /tests/test02.js: -------------------------------------------------------------------------------- 1 | // tests proper destruction/absence of leaks when there's unreferenced sound objects that are still play()ing. 2 | 3 | function noise (v,i,o) { 4 | o[i]= Math.floor(256*Math.random()); 5 | } 6 | 7 | function saw (v,i,o) { 8 | o[i]= i%256; 9 | } 10 | 11 | var buffers= []; 12 | 13 | var i= 50; 14 | while (i--) { 15 | var buf= new Buffer(4096+ 4*Math.floor(1024*Math.random())); 16 | [].forEach.call(buf, i%2 ? noise : saw); 17 | buffers.push(buf); 18 | } 19 | 20 | 21 | var Sound; 22 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 23 | while (paths.length) { 24 | var p= paths.pop(); 25 | try { Sound= require(p) } catch (e) { continue } 26 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 27 | break; 28 | } 29 | 30 | 31 | (function loop () { 32 | process.nextTick(loop); 33 | Sound.create(buffers[Math.floor(buffers.length*Math.random())]).loop(2).play(); 34 | Sound.create(buffers[Math.floor(buffers.length*Math.random())]).loop(2).play(); 35 | Sound.create(buffers[Math.floor(buffers.length*Math.random())]).loop(2).play(); 36 | Sound.create(buffers[Math.floor(buffers.length*Math.random())]).loop(2).play(); 37 | process.stdout.write('.'); 38 | })(); 39 | 40 | -------------------------------------------------------------------------------- /tests/test03.js: -------------------------------------------------------------------------------- 1 | // tests multiple sounds playing at once. 2 | 3 | // =================================================================== 4 | // = OJO DA SEGMENTATION FAULT DE VEZ EN CUANDO NO SE SABE POR QUE = 5 | // =================================================================== 6 | 7 | 8 | var Sound; 9 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 10 | while (paths.length) { 11 | var p= paths.pop(); 12 | try { Sound= require(p) } catch (e) { continue } 13 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 14 | break; 15 | } 16 | var snds= []; 17 | var bufs= []; 18 | 19 | (function () { 20 | var i= 222; 21 | while (i--) { 22 | var buf= new Buffer(8+4*Math.floor(4000*Math.random())); 23 | //console.log(buf.length); 24 | var len= buf.length; 25 | 26 | if (Math.random() < 0.5) while (len--) buf[len]= len%256; 27 | else while (len--) buf[len]= Math.floor(256*Math.random()); 28 | 29 | snds.push(Sound.create(buf)); 30 | process.stdout.write('+'); 31 | } 32 | })(); 33 | 34 | 35 | 36 | 37 | (function loop () { 38 | process.stdout.write('.'); 39 | snds[Math.floor(snds.length*Math.random())].loop(3).play(); 40 | process.nextTick(loop); 41 | })(); 42 | 43 | -------------------------------------------------------------------------------- /tests/test00.js: -------------------------------------------------------------------------------- 1 | // latency tests. not too good, it's about 30 ms. 2 | 3 | 4 | var Sound; 5 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 6 | while (paths.length) { 7 | var p= paths.pop(); 8 | try { Sound= require(p) } catch (e) { continue } 9 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 10 | break; 11 | } 12 | 13 | var tinyBuffer= new Buffer(1024); // 532/(44100*4) -> 0,003015873016 s -> ~3 ms 14 | var len= tinyBuffer.length; 15 | while (len--) tinyBuffer[len]= Math.floor(256*Math.random()); 16 | //while (len--) tinyBuffer[len]= Math.floor(len/4)%256; 17 | 18 | 19 | var i= 99; // Create this many identical sounds. 20 | var sounds= []; 21 | while (i--) sounds[i]= Sound.create(tinyBuffer); 22 | 23 | var sndIndex= 0; 24 | function demo (ms) { 25 | // .play() them sequentially at exactly ms intervals, during no more than a second. 26 | var t= Date.now(); 27 | var quit= t+ 1e3; 28 | var now; 29 | do { 30 | 31 | t+= ms; 32 | var playNext= sounds[sndIndex]; 33 | while ((now= Date.now()) < t) ; 34 | playNext.play(); 35 | 36 | sndIndex= ++sndIndex % sounds.length; 37 | } while (now < quit); 38 | 39 | setTimeout(next, 999); 40 | } 41 | 42 | 43 | var i= 10; 44 | function next () { 45 | console.log(i+ 'ms'); 46 | demo(i); 47 | i= (i<100) ? i+5 : 10; 48 | } 49 | 50 | next(); 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Node-sound's license follows: 2 | 3 | ==== 4 | 5 | Copyright Jorge Chamorro Bieling, Proyectos Equis Ka, S.L. and other 6 | Node-sound contributors (see AUTHORS). 7 | 8 | All rights reserved. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to 12 | deal in the Software without restriction, including without limitation the 13 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | sell copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 26 | IN THE SOFTWARE. 27 | 28 | ==== 29 | 30 | This license applies to all parts of Node-sound that are not externally 31 | maintained libraries. -------------------------------------------------------------------------------- /tests/test13.js: -------------------------------------------------------------------------------- 1 | // bufferifies in a loop to detect leaks. 2 | 3 | var Sound; 4 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 5 | while (paths.length) { 6 | var p= paths.pop(); 7 | try { Sound= require(p) } catch (e) { continue } 8 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 9 | break; 10 | } 11 | var sounds= ['sound.wav', 12 | 'Sous La Pluie.mp3', 13 | 'sound.m4a', 14 | 'Sous La Pluie.mp3', 15 | 'sound.aif', 16 | 'Sous La Pluie.mp3', 17 | 'sound.mp3', 18 | 'Sous La Pluie.mp3', 19 | 'sound.au', 20 | 'Sous La Pluie.mp3', 21 | 'Sous La Pluie.mp3', 22 | 'Sous La Pluie.mp3', 23 | 'Sous La Pluie.mp3', 24 | 'Sous La Pluie.mp3', 25 | 'Sous La Pluie.mp3', 26 | 'Sous La Pluie.mp3', 27 | 'Sous La Pluie.mp3', 28 | 'Sous La Pluie.mp3' 29 | ]; 30 | 31 | var kk; 32 | var max= 1e3; 33 | var i= sounds.length; 34 | (function next () { 35 | if (max) { 36 | max--; 37 | if (i--) { 38 | process.stdout.write('\n******************************* '+ sounds[i]); 39 | var len= Sound.bufferifySync(sounds[i]).length; 40 | process.stdout.write('\n******************************* '+ sounds[i]+ " -> "+ (len/(1024*1024)).toFixed(1)+ 'MB'); 41 | } 42 | else i= sounds.length; 43 | } 44 | else { 45 | try {gc()} catch (e) {} 46 | kk= new Buffer(8192); 47 | process.stdout.write('.'); 48 | } 49 | process.nextTick(next); 50 | })(); 51 | 52 | -------------------------------------------------------------------------------- /tests/test-sinewave.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Cómo sintetizar una onda sinusoidal 3 | 4 | 5 | function createSineWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 6 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 7 | 8 | function floatToSignedInteger (value) { 9 | //converts from float value in the range -1..1 to a 16bits signed integer 10 | 11 | function lonibble (integer) { 12 | return integer & 0x00ff; 13 | } 14 | 15 | function hinibble (integer) { 16 | return (integer & 0xff00) >>> 8; 17 | } 18 | 19 | if (value > 1) value= 1; 20 | else if (value < -1) value= -1; 21 | value= Math.floor(32767*value); 22 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 23 | } 24 | 25 | var kChannels= 2; 26 | var kBytesPerSample= 2; 27 | var kSamplesPerSecond= 44100; 28 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 29 | 30 | var i= 0; 31 | var step= kChannels* kBytesPerSample; 32 | do { 33 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 34 | var sample= floatToSignedInteger(Math.sin(α)); 35 | //process.stdout.write([i/step, α, sample.v, sample.hi, sample.lo] + "\r\n"); 36 | buffer[i]= buffer[i+2]= sample.lo; 37 | buffer[i+1]= buffer[i+3]= sample.hi; 38 | i+= step; 39 | } while (i < buffer.length); 40 | 41 | return buffer; 42 | } 43 | 44 | 45 | var DONE= 0; 46 | var Sound; 47 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 48 | while (paths.length) { 49 | var p= paths.pop(); 50 | try { Sound= require(p) } catch (e) { continue } 51 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 52 | break; 53 | } 54 | var buffer= createSineWave(1e3/*f in Hz*/, 1/*duration in seconds*/); 55 | var sound= Sound.create(buffer); 56 | sound.loop(3).play(cb); 57 | function cb () { 58 | DONE= 1; 59 | } 60 | 61 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 62 | -------------------------------------------------------------------------------- /tests/test-squarewave.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Cómo sintetizar una onda cuadrada 3 | 4 | 5 | function createSquareWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 6 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 7 | 8 | function floatToSignedInteger (value) { 9 | //converts from float value in the range -1..1 to a 16bits signed integer 10 | 11 | function lonibble (integer) { 12 | return integer & 0x00ff; 13 | } 14 | 15 | function hinibble (integer) { 16 | return (integer & 0xff00) >>> 8; 17 | } 18 | 19 | if (value > 1) value= 1; 20 | else if (value < -1) value= -1; 21 | value= Math.floor(32767*value); 22 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 23 | } 24 | 25 | var kChannels= 2; 26 | var kBytesPerSample= 2; 27 | var kSamplesPerSecond= 44100; 28 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 29 | 30 | var i= 0; 31 | var step= kChannels* kBytesPerSample; 32 | do { 33 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 34 | var sample= floatToSignedInteger((α > Math.PI) ? 1 : -1); 35 | //process.stdout.write([i/step, α, sample.v, sample.hi, sample.lo] + "\r\n"); 36 | buffer[i]= buffer[i+2]= sample.lo; 37 | buffer[i+1]= buffer[i+3]= sample.hi; 38 | i+= step; 39 | } while (i < buffer.length); 40 | 41 | return buffer; 42 | } 43 | 44 | 45 | var DONE= 0; 46 | var Sound; 47 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 48 | while (paths.length) { 49 | var p= paths.pop(); 50 | try { Sound= require(p) } catch (e) { continue } 51 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 52 | break; 53 | } 54 | var buffer= createSquareWave(1e3/*f in Hz*/, 1/*duration in seconds*/); 55 | var sound= Sound.create(buffer); 56 | sound.loop(3).play(cb); 57 | function cb () { 58 | DONE= 1; 59 | } 60 | 61 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 62 | -------------------------------------------------------------------------------- /tests/test-sawtoothwave.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Cómo sintetizar una onda diente de sierra 3 | 4 | 5 | function createSquareWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 6 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 7 | 8 | function floatToSignedInteger (value) { 9 | //converts from float value in the range -1..1 to a 16bits signed integer 10 | 11 | function lonibble (integer) { 12 | return integer & 0x00ff; 13 | } 14 | 15 | function hinibble (integer) { 16 | return (integer & 0xff00) >>> 8; 17 | } 18 | 19 | if (value > 1) value= 1; 20 | else if (value < -1) value= -1; 21 | value= Math.floor(32767*value); 22 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 23 | } 24 | 25 | var kChannels= 2; 26 | var kBytesPerSample= 2; 27 | var kSamplesPerSecond= 44100; 28 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 29 | 30 | var i= 0; 31 | var step= kChannels* kBytesPerSample; 32 | do { 33 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 34 | var sample= floatToSignedInteger(( α/ Math.PI)- 1); 35 | //process.stdout.write([i/step, α, sample.v, sample.hi, sample.lo] + "\r\n"); 36 | buffer[i]= buffer[i+2]= sample.lo; 37 | buffer[i+1]= buffer[i+3]= sample.hi; 38 | i+= step; 39 | } while (i < buffer.length); 40 | 41 | return buffer; 42 | } 43 | 44 | 45 | var DONE= 0; 46 | var Sound; 47 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 48 | while (paths.length) { 49 | var p= paths.pop(); 50 | try { Sound= require(p) } catch (e) { continue } 51 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 52 | break; 53 | } 54 | var buffer= createSquareWave(1e3/*f in Hz*/, 1/*duration in seconds*/); 55 | var sound= Sound.create(buffer); 56 | sound.loop(3).play(cb); 57 | function cb () { 58 | DONE= 1; 59 | } 60 | 61 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 62 | -------------------------------------------------------------------------------- /tests/test-sinewaves.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Hace una musiquilla sintetizando unas cuantas sinusoidales randommente 3 | 4 | 5 | function createSineWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 6 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 7 | 8 | function floatToSignedInteger (value) { 9 | //converts from float value in the range -1..1 to a 16bits signed integer 10 | 11 | function lonibble (integer) { 12 | return integer & 0x00ff; 13 | } 14 | 15 | function hinibble (integer) { 16 | return (integer & 0xff00) >>> 8; 17 | } 18 | 19 | if (value > 1) value= 1; 20 | else if (value < -1) value= -1; 21 | value= Math.floor(32767*value); 22 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 23 | } 24 | 25 | var kChannels= 2; 26 | var kBytesPerSample= 2; 27 | var kSamplesPerSecond= 44100; 28 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 29 | 30 | var i= 0; 31 | var step= kChannels* kBytesPerSample; 32 | do { 33 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 34 | var sample= floatToSignedInteger(Math.sin(α)); 35 | //process.stdout.write([i/step, α, sample.v, sample.hi, sample.lo] + "\r\n"); 36 | buffer[i]= buffer[i+2]= sample.lo; 37 | buffer[i+1]= buffer[i+3]= sample.hi; 38 | i+= step; 39 | } while (i < buffer.length); 40 | 41 | return buffer; 42 | } 43 | 44 | 45 | var DONE= 0; 46 | var Sound; 47 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 48 | while (paths.length) { 49 | var p= paths.pop(); 50 | try { Sound= require(p) } catch (e) { continue } 51 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 52 | break; 53 | } 54 | 55 | var musiquilla= []; 56 | var i= 20; 57 | while (i--) { 58 | var f= 222+ (3e3* Math.random()); 59 | var t= 0.05+ (Math.random()/20); 60 | var buffer= createSineWave(f/*f in Hz*/, t/*duration in seconds*/); 61 | musiquilla.push(Sound.create(buffer)); 62 | } 63 | 64 | (function cb () { 65 | if (!musiquilla.length) return (DONE= 1); 66 | musiquilla.pop().play(cb); 67 | })(); 68 | 69 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 70 | -------------------------------------------------------------------------------- /tests/test-sawtoothwaves.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Hace una musiquilla sintetizando unas cuantas randommente 3 | 4 | 5 | function createSawtoothWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 6 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 7 | 8 | function floatToSignedInteger (value) { 9 | //converts from float value in the range -1..1 to a 16bits signed integer 10 | 11 | function lonibble (integer) { 12 | return integer & 0x00ff; 13 | } 14 | 15 | function hinibble (integer) { 16 | return (integer & 0xff00) >>> 8; 17 | } 18 | 19 | if (value > 1) value= 1; 20 | else if (value < -1) value= -1; 21 | value= Math.floor(32767*value); 22 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 23 | } 24 | 25 | var kChannels= 2; 26 | var kBytesPerSample= 2; 27 | var kSamplesPerSecond= 44100; 28 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 29 | 30 | var i= 0; 31 | var step= kChannels* kBytesPerSample; 32 | do { 33 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 34 | var sample= floatToSignedInteger(( α/ Math.PI)- 1); 35 | //process.stdout.write([i/step, α, sample.v, sample.hi, sample.lo] + "\r\n"); 36 | buffer[i]= buffer[i+2]= sample.lo; 37 | buffer[i+1]= buffer[i+3]= sample.hi; 38 | i+= step; 39 | } while (i < buffer.length); 40 | 41 | return buffer; 42 | } 43 | 44 | 45 | var DONE= 0; 46 | var Sound; 47 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 48 | while (paths.length) { 49 | var p= paths.pop(); 50 | try { Sound= require(p) } catch (e) { continue } 51 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 52 | break; 53 | } 54 | 55 | var musiquilla= []; 56 | var i= 20; 57 | while (i--) { 58 | var f= 222+ (3e3* Math.random()); 59 | var t= 0.05+ (Math.random()/20); 60 | var buffer= createSawtoothWave(f/*f in Hz*/, t/*duration in seconds*/); 61 | musiquilla.push(Sound.create(buffer)); 62 | } 63 | 64 | (function cb () { 65 | if (!musiquilla.length) return (DONE= 1); 66 | musiquilla.pop().play(cb); 67 | })(); 68 | 69 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 70 | -------------------------------------------------------------------------------- /tests/test-squarewaves.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Hace una musiquilla sintetizando unas cuantas randommente 3 | 4 | 5 | function createSquareWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 6 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 7 | 8 | function floatToSignedInteger (value) { 9 | //converts from float value in the range -1..1 to a 16bits signed integer 10 | 11 | function lonibble (integer) { 12 | return integer & 0x00ff; 13 | } 14 | 15 | function hinibble (integer) { 16 | return (integer & 0xff00) >>> 8; 17 | } 18 | 19 | if (value > 1) value= 1; 20 | else if (value < -1) value= -1; 21 | value= Math.floor(32767*value); 22 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 23 | } 24 | 25 | var kChannels= 2; 26 | var kBytesPerSample= 2; 27 | var kSamplesPerSecond= 44100; 28 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 29 | 30 | var i= 0; 31 | var step= kChannels* kBytesPerSample; 32 | do { 33 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 34 | var sample= floatToSignedInteger((α > Math.PI) ? 1 : -1); 35 | //process.stdout.write([i/step, α, sample.v, sample.hi, sample.lo] + "\r\n"); 36 | buffer[i]= buffer[i+2]= sample.lo; 37 | buffer[i+1]= buffer[i+3]= sample.hi; 38 | i+= step; 39 | } while (i < buffer.length); 40 | 41 | return buffer; 42 | } 43 | 44 | 45 | var DONE= 0; 46 | var Sound; 47 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 48 | while (paths.length) { 49 | var p= paths.pop(); 50 | try { Sound= require(p) } catch (e) { continue } 51 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 52 | break; 53 | } 54 | 55 | var musiquilla= []; 56 | var i= 20; 57 | while (i--) { 58 | var f= 222+ (3e3* Math.random()); 59 | var t= 0.05+ (Math.random()/20); 60 | var buffer= createSquareWave(f/*f in Hz*/, t/*duration in seconds*/); 61 | musiquilla.push(Sound.create(buffer)); 62 | } 63 | 64 | (function cb () { 65 | if (!musiquilla.length) return (DONE= 1); 66 | musiquilla.pop().play(cb); 67 | })(); 68 | 69 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 70 | -------------------------------------------------------------------------------- /tests/test-noise-stereo.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Cómo sintetizar una onda 3 | 4 | 5 | var createWave= (function () { 6 | 7 | function floatToSignedInteger (value) { 8 | //converts from float value in the range -1..1 to a 16bits signed integer 9 | 10 | function lonibble (integer) { 11 | return integer & 0x00ff; 12 | } 13 | 14 | function hinibble (integer) { 15 | return (integer & 0xff00) >>> 8; 16 | } 17 | 18 | if (value > 1) value= 1; 19 | else if (value < -1) value= -1; 20 | value= Math.floor(32767*value); 21 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 22 | } 23 | 24 | function rnd (n) { 25 | return Math.floor(n* Math.random()); 26 | } 27 | 28 | function sawtooth_w (α) { 29 | return ( α/ Math.PI)- 1; 30 | } 31 | 32 | function sine_w (α) { 33 | return Math.sin( α); 34 | } 35 | 36 | function square_w (α) { 37 | return ( α > Math.PI) ? -1 : 1; 38 | } 39 | 40 | function noise_w (α) { 41 | return 1- (2* Math.random()); 42 | } 43 | 44 | function silence_w (α) { 45 | return 0; 46 | } 47 | 48 | function createWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 49 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 50 | 51 | var kChannels= 2; 52 | var kBytesPerSample= 2; 53 | var kSamplesPerSecond= 44100; 54 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 55 | 56 | var waves= [noise_w, silence_w]; 57 | var left= waves[rnd(waves.length)]; 58 | var right= waves[rnd(waves.length)]; 59 | 60 | var i= 0; 61 | var step= kChannels* kBytesPerSample; 62 | do { 63 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 64 | 65 | var sample= floatToSignedInteger(left(α)); 66 | buffer[i]= sample.lo; 67 | buffer[i+1]= sample.hi; 68 | 69 | var sample= floatToSignedInteger(right(α)); 70 | buffer[i+2]= sample.lo; 71 | buffer[i+3]= sample.hi; 72 | 73 | i+= step; 74 | } while (i < buffer.length); 75 | 76 | return buffer; 77 | } 78 | 79 | return createWave; 80 | })(); 81 | 82 | 83 | 84 | var DONE= 0; 85 | var Sound; 86 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 87 | while (paths.length) { 88 | var p= paths.pop(); 89 | try { Sound= require(p) } catch (e) { continue } 90 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 91 | break; 92 | } 93 | 94 | var musiquilla= []; 95 | var i= 200; 96 | while (i--) { 97 | var f= 222+ (3e3* Math.random()); 98 | var t= 0.05+ (Math.random()/20); 99 | var buffer= createWave(f/*f in Hz*/, t/*duration in seconds*/); 100 | musiquilla.push(Sound.create(buffer)); 101 | } 102 | 103 | 104 | function go () { 105 | if (!musiquilla.length) return (DONE= 1); 106 | musiquilla.pop().play(go); 107 | } 108 | go(); 109 | 110 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 111 | -------------------------------------------------------------------------------- /tests/test-noise-stereo-polyphonic.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Cómo sintetizar una onda 3 | 4 | 5 | var createWave= (function () { 6 | 7 | function floatToSignedInteger (value) { 8 | //converts from float value in the range -1..1 to a 16bits signed integer 9 | 10 | function lonibble (integer) { 11 | return integer & 0x00ff; 12 | } 13 | 14 | function hinibble (integer) { 15 | return (integer & 0xff00) >>> 8; 16 | } 17 | 18 | if (value > 1) value= 1; 19 | else if (value < -1) value= -1; 20 | value= Math.floor(32767*value); 21 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 22 | } 23 | 24 | function rnd (n) { 25 | return Math.floor(n* Math.random()); 26 | } 27 | 28 | function sawtooth_w (α) { 29 | return ( α/ Math.PI)- 1; 30 | } 31 | 32 | function sine_w (α) { 33 | return Math.sin( α); 34 | } 35 | 36 | function square_w (α) { 37 | return ( α > Math.PI) ? -1 : 1; 38 | } 39 | 40 | function noise_w (α) { 41 | return 1- (2* Math.random()); 42 | } 43 | 44 | function silence_w (α) { 45 | return 0; 46 | } 47 | 48 | function createWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 49 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 50 | 51 | var kChannels= 2; 52 | var kBytesPerSample= 2; 53 | var kSamplesPerSecond= 44100; 54 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 55 | 56 | var waves= [noise_w, silence_w]; 57 | var left= waves[rnd(waves.length)]; 58 | var right= waves[rnd(waves.length)]; 59 | 60 | var i= 0; 61 | var step= kChannels* kBytesPerSample; 62 | do { 63 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 64 | 65 | var sample= floatToSignedInteger(left(α)); 66 | buffer[i]= sample.lo; 67 | buffer[i+1]= sample.hi; 68 | 69 | var sample= floatToSignedInteger(right(α)); 70 | buffer[i+2]= sample.lo; 71 | buffer[i+3]= sample.hi; 72 | 73 | i+= step; 74 | } while (i < buffer.length); 75 | 76 | return buffer; 77 | } 78 | 79 | return createWave; 80 | })(); 81 | 82 | 83 | 84 | var DONE= 0; 85 | var Sound; 86 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 87 | while (paths.length) { 88 | var p= paths.pop(); 89 | try { Sound= require(p) } catch (e) { continue } 90 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 91 | break; 92 | } 93 | 94 | var musiquilla= []; 95 | var i= 200; 96 | while (i--) { 97 | var f= 222+ (3e3* Math.random()); 98 | var t= 0.05+ (Math.random()/20); 99 | var buffer= createWave(f/*f in Hz*/, t/*duration in seconds*/); 100 | musiquilla.push(Sound.create(buffer)); 101 | } 102 | 103 | function go () { 104 | if (!musiquilla.length) return (DONE= 1); 105 | musiquilla.pop().play(go); 106 | } 107 | go(); 108 | go(); 109 | 110 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 111 | -------------------------------------------------------------------------------- /tests/test-multiwaves-stereo.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Cómo sintetizar una onda 3 | 4 | 5 | var createWave= (function () { 6 | 7 | function floatToSignedInteger (value) { 8 | //converts from float value in the range -1..1 to a 16bits signed integer 9 | 10 | function lonibble (integer) { 11 | return integer & 0x00ff; 12 | } 13 | 14 | function hinibble (integer) { 15 | return (integer & 0xff00) >>> 8; 16 | } 17 | 18 | if (value > 1) value= 1; 19 | else if (value < -1) value= -1; 20 | value= Math.floor(32767*value); 21 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 22 | } 23 | 24 | function rnd (n) { 25 | return Math.floor(n* Math.random()); 26 | } 27 | 28 | function sawtooth_w (α) { 29 | return ( α/ Math.PI)- 1; 30 | } 31 | 32 | function sine_w (α) { 33 | return Math.sin( α); 34 | } 35 | 36 | function square_w (α) { 37 | return ( α > Math.PI) ? -1 : 1; 38 | } 39 | 40 | function noise_w (α) { 41 | return 1- (2* Math.random()); 42 | } 43 | 44 | function silence_w (α) { 45 | return 0; 46 | } 47 | 48 | function createWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 49 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 50 | 51 | var kChannels= 2; 52 | var kBytesPerSample= 2; 53 | var kSamplesPerSecond= 44100; 54 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 55 | 56 | var waves= [sine_w, square_w, sawtooth_w, silence_w]; 57 | var left= waves[rnd(waves.length)]; 58 | var right= waves[rnd(waves.length)]; 59 | 60 | var i= 0; 61 | var step= kChannels* kBytesPerSample; 62 | do { 63 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 64 | 65 | var sample= floatToSignedInteger(left(α)); 66 | buffer[i]= sample.lo; 67 | buffer[i+1]= sample.hi; 68 | 69 | var sample= floatToSignedInteger(right(α)); 70 | buffer[i+2]= sample.lo; 71 | buffer[i+3]= sample.hi; 72 | 73 | i+= step; 74 | } while (i < buffer.length); 75 | 76 | return buffer; 77 | } 78 | 79 | return createWave; 80 | })(); 81 | 82 | 83 | 84 | var DONE= 0; 85 | var Sound; 86 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 87 | while (paths.length) { 88 | var p= paths.pop(); 89 | try { Sound= require(p) } catch (e) { continue } 90 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 91 | break; 92 | } 93 | 94 | var musiquilla= []; 95 | var i= 50; 96 | while (i--) { 97 | var f= 222+ (3e3* Math.random()); 98 | var t= 0.05+ (Math.random()/20); 99 | var buffer= createWave(f/*f in Hz*/, t/*duration in seconds*/); 100 | musiquilla.push(Sound.create(buffer)); 101 | } 102 | 103 | 104 | function go () { 105 | if (!musiquilla.length) return (DONE= 1); 106 | musiquilla.pop().play(go); 107 | } 108 | go(); 109 | 110 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 111 | -------------------------------------------------------------------------------- /tests/test-multiwaves-stereo-polyphonic.js: -------------------------------------------------------------------------------- 1 | //2011-11-03 Jorge@jorgechamorro.com 2 | //Cómo sintetizar una onda 3 | 4 | 5 | var createWave= (function () { 6 | 7 | function floatToSignedInteger (value) { 8 | //converts from float value in the range -1..1 to a 16bits signed integer 9 | 10 | function lonibble (integer) { 11 | return integer & 0x00ff; 12 | } 13 | 14 | function hinibble (integer) { 15 | return (integer & 0xff00) >>> 8; 16 | } 17 | 18 | if (value > 1) value= 1; 19 | else if (value < -1) value= -1; 20 | value= Math.floor(32767*value); 21 | return {hi:hinibble(value), lo:lonibble(value), v:value}; 22 | } 23 | 24 | function rnd (n) { 25 | return Math.floor(n* Math.random()); 26 | } 27 | 28 | function sawtooth_w (α) { 29 | return ( α/ Math.PI)- 1; 30 | } 31 | 32 | function sine_w (α) { 33 | return Math.sin( α); 34 | } 35 | 36 | function square_w (α) { 37 | return ( α > Math.PI) ? -1 : 1; 38 | } 39 | 40 | function noise_w (α) { 41 | return 1- (2* Math.random()); 42 | } 43 | 44 | function silence_w (α) { 45 | return 0; 46 | } 47 | 48 | function createWave (f /*frequency in Hz*/, seconds /*duration in seconds*/) { 49 | //returns a buffer containing a sound of a sine wave of frequency f and duration seconds in PCM format 50 | 51 | var kChannels= 2; 52 | var kBytesPerSample= 2; 53 | var kSamplesPerSecond= 44100; 54 | var buffer= new Buffer(Math.floor(seconds*kSamplesPerSecond)* kChannels* kBytesPerSample); 55 | 56 | var waves= [sine_w, square_w, sawtooth_w, silence_w]; 57 | var left= waves[rnd(waves.length)]; 58 | var right= waves[rnd(waves.length)]; 59 | 60 | var i= 0; 61 | var step= kChannels* kBytesPerSample; 62 | do { 63 | var α= (f* 2* Math.PI* i/ kSamplesPerSecond/ step) % (2* Math.PI); 64 | 65 | var sample= floatToSignedInteger(left(α)); 66 | buffer[i]= sample.lo; 67 | buffer[i+1]= sample.hi; 68 | 69 | var sample= floatToSignedInteger(right(α)); 70 | buffer[i+2]= sample.lo; 71 | buffer[i+3]= sample.hi; 72 | 73 | i+= step; 74 | } while (i < buffer.length); 75 | 76 | return buffer; 77 | } 78 | 79 | return createWave; 80 | })(); 81 | 82 | 83 | 84 | var DONE= 0; 85 | var Sound; 86 | var paths= ['./build/default/sound', './build/release/sound', 'sound']; 87 | while (paths.length) { 88 | var p= paths.pop(); 89 | try { Sound= require(p) } catch (e) { continue } 90 | console.log("Módulo de sonido encontrado en: '"+ p+ "'"); 91 | break; 92 | } 93 | 94 | var musiquilla= []; 95 | var i= 200; 96 | while (i--) { 97 | var f= 222+ (3e3* Math.random()); 98 | var t= 0.05+ (Math.random()/20); 99 | var buffer= createWave(f/*f in Hz*/, t/*duration in seconds*/); 100 | musiquilla.push(Sound.create(buffer)); 101 | } 102 | 103 | function go () { 104 | if (!musiquilla.length) return (DONE= 1); 105 | musiquilla.pop().play(go); 106 | } 107 | go(); 108 | go(); 109 | 110 | (function cb () { if (!DONE) setTimeout(cb, 333); })(); 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #node-sound The best sound library for node.js 2 |

 

3 | Node-sound es un módulo nativo para node.js que sirve para crear/sintetizar y reproducir sonidos en tu Mac, en JavaScript, a partir de un buffer, o a partir de cualquier fichero de música (.aiff, .mp3, .m4a, .aac, .mov, ...) usando las librerías de sonido incorporadas en el Mac OSX. 4 | 5 | ##Instalar 6 | 7 | **NOTA: funciona con cualquier node >= v0.3.6. Sólo funciona con Mac OSX** 8 | 9 | **Works with all nodes >= v0.3.6 . Only for Mac OSX** 10 | 11 | (Puedes encontrar cualquier versión de node, desde la primera hasta la última, en: http://nodejs.org/dist/ ) 12 | 13 | Que un módulo sea nativo significa fundamentalmente dos cosas: que está escrito en C y que para poder usarlo hay que compilarlo primero. 14 | 15 | Es muy fácil de hacer, suponiendo que ya tengas node.js instalado (y compilado y funcionando). 16 | 17 | Lo primero es descargar el .zip o el .tar.gz dando al botón *DOWNLOADS* que hay ahí arriba a la derecha en esta misma página: 18 | 19 | 20 | 21 | Después descomprimes ese fichero y creará una carpeta *xk-node-sound-xxxx*. Puedes borrar el -xxxx y dejarla en *xk-node-sound* a secas. 22 | 23 | Por último, para compilar el módulo teclea: 24 | 25 | cd xk-node-sound 26 | node-waf configure uninstall distclean configure build install 27 | 28 | En mi Mac eso produce algo así, donde lo más importante es la última línea: *'build' finished successfully*: 29 | 30 | 31 | 32 | Si todo ha ido bien, el módulo compilado se encontrará, dependiendo de la versión de node que tengas instalada, en *xk-node-sound/build/default/sound.node* (versiones más antiguas de node), o en *xk-node-sound/build/release/sound.node* (versiones más modernas de node) 33 | 34 | Si algo no va bien, puedes abrir un ticket haciendo click en "issues" arriba, en esta misma página. Describe el problema lo mejor posible y yo recibiré un email automáticamente, y trataré de resolverlo lo antes posible. 35 | 36 |

 

37 | ##Manual de instrucciones: 38 | 39 | ### Require('sound') 40 | 41 | Lo primero es cargar el módulo y asignarle un nombre (por ejemplo *Sound*) en la aplicación: 42 | 43 | var Sound= require('sound'); 44 | 45 | Si node no es capaz de encontralo, tienes dos opciones. 46 | 47 | La mejor opción: translada el fichero *sound.node* a la carpeta *node_modules* (si no existe, simplemente créala), o bien, especifica el path completo hasta la carpeta en la que se encuentra *sound.node* : 48 | 49 | require('/absolute/path/to/sound.node's/folder/sound'); // ugh ! 50 | 51 | Por ejemplo, los tests usan: 52 | 53 | require('./build/default/sound'); 54 | 55 | El módulo (que una vez `require()`d se llama *Sound*) tiene 4 métodos: 56 | 57 | 58 |

 

59 | ###Sound.create(buffer) 60 | 61 | Crea un sonido a partir de un *buffer*. 62 | 63 | var buffer= new Buffer(8192); // Crear un buffer de 8kB 64 | 65 | var i= buffer.length; 66 | while (i--) buffer[i]= i%256; // Rellenar el buffer con algo que "suene" 67 | 68 | var sonido1= Sound.create(buffer); // Crear el sonido. 69 | 70 | sonido1.loop(5).volume(0.5).play(); // Y hacerlo sonar 5 veces seguidas con el volumen al 50% 71 | 72 | 73 |

 

74 | ###Sound.bufferifySync(path) 75 | 76 | Lee un fichero de sonido, preferiblemente :-) y lo transforma en un *buffer*. Admite casi cualquier formato de sonido: .wav, .mp3, .aif, .m4a, etc. 77 | 78 | var buffer = Sound.bufferifySync('unPingüinoEnMiAscensor.mp3'); 79 | var sonido2= Sound.create(buffer); 80 | 81 | //o simplemente: 82 | 83 | var sonido2= Sound.create( Sound.bufferifySync(path) ); 84 | 85 | //Y luego le damos a play: 86 | 87 | sonido2.play(); 88 | 89 |

 

90 | ###Sound.bufferify(path, callback) 91 | 92 | Es la versión asíncrona de `bufferifySync()`, hace lo mismo pero (en una thread en paralelo) sin bloquear, y cuando ha acabado llama a *callback* y le pasa el *buffer* si no ha habido ningún *error* : 93 | 94 | Sound.bufferify('/path/to/a/sound.file', cb) 95 | function cb (error, buffer) { 96 | if (!error) { 97 | var sonido2= Sound.create(buffer); 98 | } 99 | } 100 | 101 |

 

102 | ###Sound.stream(path) 103 | 104 | Aún no va (2011-05-19). Mejor lo dejamos para otro momento. 105 | 106 | var sonido3= Sound.stream(path) 107 | 108 |

 

109 | ###Los métodos de los sonidos: 110 | 111 | `Sound.create(buffer)` devuelve un objeto sonido que tiene los siguentes métodos: 112 | 113 | .play() // evidente. 114 | .play(callback) // Igual, pero al acabar llama a callback 115 | .loop(veces) // repite el sonido en bucle *veces* veces 116 | .volume( 0..1 ) // 0 es silencio, 1 es a tope, cualquier cosa intermedia vale también. 117 | .pause() // pues eso. 118 | 119 | Cada vez que se llama a cualquiera de ellos, devuelve el objeto sonido otra vez, lo que permite encadenar las llamadas: 120 | 121 | En vez de: 122 | 123 | sonido.loop(5); 124 | sonido.volume(1); 125 | sonido.play(); 126 | 127 | Puedes hacerlo en una sola línea: 128 | 129 | sonido.loop(5).volume(1).play(); 130 | 131 | Además, cada objeto sonido tiene estos otros 2 atributos: 132 | 133 | .id // Un número de serie que se asigna secuencialmente. 134 | .data // Una referencia al buffer con el que se ha creado. 135 | 136 | En resumen: 137 | 138 | 139 | 140 | *** 141 | © Jorge Chamorro Bieling, 2011. Ver la Licencia -------------------------------------------------------------------------------- /src/sound.cc: -------------------------------------------------------------------------------- 1 | // 2 | // sound.cc 3 | // node_sound 4 | // 5 | // Created by Jorge@jorgechamorro.com on 2011-05-11. 6 | // Copyright 2011 Proyectos Equis Ka. All rights reserved. 7 | // 8 | 9 | /* 10 | TODO 11 | - Throw exceptions instead of just writing ERRORs to stderr. 12 | - provide a vumeter per sound 13 | - play callbacks (DONE) 14 | - bufferifySync(path) returns a buffer; 15 | - bufferify(path, cb) renders it in a background thread and calls cb(err, buffer) when done. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #if defined (__APPLE__) 28 | #include 29 | #include 30 | #endif 31 | 32 | 33 | typedef struct playerStruct { 34 | int paused; 35 | long int id; 36 | int playing; 37 | int destroying; 38 | unsigned long loop; 39 | ssize_t bufferLength; 40 | 41 | #if defined (__APPLE__) 42 | AudioQueueRef AQ; 43 | UInt32 AQBuffer1Length; 44 | UInt32 AQBuffer2Length; 45 | AudioQueueBufferRef AQBuffer1; 46 | AudioQueueBufferRef AQBuffer2; 47 | AudioStreamBasicDescription* format; 48 | ExtAudioFileRef inputAudioFile; 49 | #endif 50 | 51 | int hasCallback; 52 | int callbackIsPending; 53 | v8::Persistent pendingJSCallback; 54 | v8::Persistent JSObject; 55 | v8::Persistent JSCallback; 56 | }; 57 | 58 | #if defined (__APPLE__) 59 | static AudioStreamBasicDescription gFormato; 60 | typedef Boolean macBoolean; 61 | #endif 62 | 63 | static playerStruct* fondoSnd; 64 | 65 | using namespace node; 66 | using namespace v8; 67 | 68 | typedef struct bufferStruct { 69 | void* buffer; 70 | ssize_t used; 71 | ssize_t size; 72 | }; 73 | 74 | enum kTypes { 75 | kPlayCallbackQueueItemType, 76 | kRenderCallbackQueueItemType, 77 | kBufferListQueueItemType, 78 | kRenderJobsListQueueItemType 79 | }; 80 | 81 | typedef struct queueStruct { 82 | int type; 83 | void* item; 84 | queueStruct* next; 85 | queueStruct* last; 86 | }; 87 | static queueStruct* callbacksQueue= NULL; 88 | static queueStruct* renderJobsQueue= NULL; 89 | static ev_async eio_sound_async_notifier; 90 | pthread_mutex_t callbacksQueue_mutex = PTHREAD_MUTEX_INITIALIZER; 91 | pthread_mutex_t renderJobsQueue_mutex = PTHREAD_MUTEX_INITIALIZER; 92 | 93 | typedef struct renderJob { 94 | char* str; 95 | ssize_t strLen; 96 | ssize_t bytesRead; 97 | queueStruct* qHead; 98 | v8::Persistent JSCallback; 99 | }; 100 | static pthread_t theRenderThread; 101 | 102 | static v8::Persistent volume_symbol; 103 | static v8::Persistent loop_symbol; 104 | static v8::Persistent play_symbol; 105 | static v8::Persistent pause_symbol; 106 | static v8::Persistent volume_function; 107 | static v8::Persistent loop_function; 108 | static v8::Persistent play_function; 109 | static v8::Persistent pause_function; 110 | 111 | static v8::Persistent id_symbol; 112 | static v8::Persistent data_symbol; 113 | static v8::Persistent hiddenPlayerPtr_symbol; 114 | 115 | static long int createdCtr= 0; 116 | static long int destroyedCtr= 0; 117 | static long int wasPlayingCtr= 0; 118 | static long int playingNow= 0; 119 | 120 | 121 | 122 | 123 | 124 | // ================== 125 | // = newQueueItem() = 126 | // ================== 127 | 128 | queueStruct* newQueueItem (void* item, int type, queueStruct* qHead) { 129 | queueStruct* nuItem= (queueStruct*) calloc(1, sizeof(queueStruct)); 130 | nuItem->item= item; 131 | nuItem->type= type; 132 | if (qHead) { 133 | qHead->last->next= nuItem; 134 | qHead->last= nuItem; 135 | } 136 | else { 137 | nuItem->last= nuItem; 138 | } 139 | return nuItem; 140 | } 141 | 142 | 143 | 144 | 145 | 146 | // ====================== 147 | // = destroyQueueItem() = 148 | // ====================== 149 | 150 | queueStruct* destroyQueueItem (queueStruct* qitem) { 151 | queueStruct* next= qitem->next; 152 | if (next != NULL) next->last= qitem->last; 153 | free(qitem); 154 | return next; 155 | } 156 | 157 | 158 | 159 | 160 | 161 | 162 | // =============== 163 | // = newBuffer() = 164 | // =============== 165 | 166 | bufferStruct* newBuffer (ssize_t size) { 167 | bufferStruct* nuBuffer= (bufferStruct*) malloc(sizeof(bufferStruct)); 168 | nuBuffer->buffer= malloc(size); 169 | nuBuffer->used= 0; 170 | nuBuffer->size= size; 171 | return nuBuffer; 172 | } 173 | 174 | 175 | 176 | 177 | 178 | // =================== 179 | // = destroyBuffer() = 180 | // =================== 181 | 182 | void destroyBuffer (bufferStruct* buffer) { 183 | free(buffer->buffer); 184 | free(buffer); 185 | } 186 | 187 | 188 | 189 | 190 | 191 | 192 | // ============= 193 | // = tracker() = 194 | // ============= 195 | 196 | void tracker (int i) { 197 | playingNow+= i; 198 | 199 | #if defined (__APPLE__) 200 | if (playingNow > 0) { 201 | if (fondoSnd->playing) { 202 | fondoSnd->playing= 0; 203 | AudioQueuePause(fondoSnd->AQ); 204 | //fprintf(stderr, "\nfondoSnd->pause()"); 205 | } 206 | } 207 | else { 208 | if (!fondoSnd->playing) { 209 | fondoSnd->playing= 1; 210 | AudioQueueStart(fondoSnd->AQ, NULL); 211 | //fprintf(stderr, "\nfondoSnd->play()"); 212 | } 213 | } 214 | #else 215 | 216 | 217 | #endif 218 | } 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | // =============== 227 | // = newPlayer() = 228 | // =============== 229 | 230 | 231 | playerStruct* newPlayer () { 232 | playerStruct* player; 233 | 234 | player= (playerStruct*) calloc(1, sizeof(playerStruct)); 235 | V8::AdjustAmountOfExternalAllocatedMemory(sizeof(playerStruct)); 236 | 237 | player->id= createdCtr++; 238 | 239 | #if defined (__APPLE__) 240 | player->format= &gFormato; 241 | #else 242 | 243 | #endif 244 | 245 | player->loop= 0; 246 | player->paused= 0; 247 | player->playing= 0; 248 | player->destroying= 0; 249 | player->hasCallback= 0; 250 | player->callbackIsPending= 0; 251 | 252 | return player; 253 | } 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | // ==================== 262 | // = **** Volume **** = 263 | // ==================== 264 | 265 | v8::Handle Volume (const Arguments &args) { 266 | 267 | HandleScope scope; 268 | 269 | #if defined (__APPLE__) 270 | 271 | OSStatus err; 272 | double volumen; 273 | 274 | playerStruct* player; 275 | player= (playerStruct*) (External::Unwrap(args.This()->ToObject()->GetHiddenValue(hiddenPlayerPtr_symbol))); 276 | 277 | if (args.Length() && args[0]->IsNumber()) { 278 | volumen= args[0]->NumberValue(); 279 | 280 | if (volumen < 0) volumen= 0; 281 | else if (volumen > 1) volumen= 1; 282 | 283 | err= AudioQueueSetParameter (player->AQ, kAudioQueueParam_Volume, (AudioQueueParameterValue) volumen); 284 | } 285 | 286 | #else 287 | fprintf(stderr, "\nERROR *** Sound::volume() LINUX not implemented argghh"); 288 | fflush(stderr); 289 | 290 | #endif 291 | 292 | 293 | return scope.Close(args.This()); 294 | } 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | // ================== 305 | // = **** Loop **** = 306 | // ================== 307 | 308 | v8::Handle Loop (const Arguments &args) { 309 | 310 | HandleScope scope; 311 | 312 | #if defined (__APPLE__) 313 | 314 | double loop; 315 | 316 | playerStruct* player; 317 | player= (playerStruct*) (External::Unwrap(args.This()->ToObject()->GetHiddenValue(hiddenPlayerPtr_symbol))); 318 | 319 | if (args.Length() && args[0]->IsNumber()) { 320 | loop= args[0]->NumberValue(); 321 | 322 | if (loop < 0) loop= 0; 323 | else if (loop > 9007199254740992) loop= 9007199254740992; // 2^53 324 | 325 | player->loop= truncl(loop); 326 | } 327 | 328 | #else 329 | fprintf(stderr, "\nERROR *** Sound::loop() LINUX not implemented argghh"); 330 | fflush(stderr); 331 | 332 | #endif 333 | 334 | return scope.Close(args.This()); 335 | } 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | // ================== 347 | // = **** Play **** = 348 | // ================== 349 | 350 | v8::Handle Play (const Arguments &args) { 351 | 352 | HandleScope scope; 353 | 354 | #if defined (__APPLE__) 355 | 356 | OSStatus err; 357 | playerStruct* player; 358 | 359 | player= (playerStruct*) (External::Unwrap(args.This()->GetHiddenValue(hiddenPlayerPtr_symbol))); 360 | 361 | //fprintf(stderr, "\n*** Sound::play() [%ld]", player->id); 362 | 363 | if (player->hasCallback) { 364 | player->hasCallback= 0; 365 | player->JSCallback.Dispose(); 366 | } 367 | 368 | 369 | if (args.Length()) { 370 | if (args[0]->IsFunction()) { 371 | player->hasCallback= 1; 372 | player->JSCallback= v8::Persistent::New(args[0]->ToObject()); 373 | } 374 | else { 375 | return ThrowException(Exception::TypeError(String::New("Sound::Play(): The callback must be a function"))); 376 | } 377 | } 378 | 379 | 380 | if (player->paused) { 381 | player->paused= 0; 382 | player->playing= 1; 383 | tracker(+1); 384 | 385 | err= AudioQueueStart(player->AQ, NULL); 386 | if (err) { 387 | fprintf(stderr, " ERROR:AudioQueueStart:[%d]", err); 388 | } 389 | 390 | goto end; 391 | } 392 | 393 | if (player->playing) goto end; 394 | 395 | //fprintf(stderr, "\n[%d] PLAY", player->id); 396 | //fflush(stderr); 397 | 398 | player->playing= 1; 399 | tracker(+1); 400 | 401 | player->AQBuffer1->mAudioDataByteSize= player->AQBuffer1Length; 402 | err= AudioQueueEnqueueBuffer(player->AQ, player->AQBuffer1, 0, NULL); 403 | if (err) { 404 | fprintf(stderr, " ERROR:AudioQueueEnqueueBuffer:[%d]", err); 405 | } 406 | 407 | player->AQBuffer2->mAudioDataByteSize= player->AQBuffer2Length; 408 | err= AudioQueueEnqueueBuffer(player->AQ, player->AQBuffer2, 0, NULL); 409 | if (err) { 410 | fprintf(stderr, " ERROR:AudioQueueEnqueueBuffer:[%d]", err); 411 | } 412 | 413 | err= AudioQueueStart(player->AQ, NULL); 414 | if (err) { 415 | fprintf(stderr, " ERROR:AudioQueueStart:[%d]", err); 416 | } 417 | 418 | #else 419 | fprintf(stderr, "\nERROR *** Sound::play() LINUX not implemented argghh"); 420 | fflush(stderr); 421 | 422 | #endif 423 | 424 | 425 | end: 426 | 427 | //fflush(stderr); 428 | return scope.Close(args.This()); 429 | } 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | // ====================================== 438 | // = **** AudioQueueBufferCallback **** = 439 | // ====================================== 440 | 441 | #if defined (__APPLE__) 442 | 443 | void AQBufferCallback (void* priv, AudioQueueRef AQ, AudioQueueBufferRef AQBuffer) { 444 | 445 | OSStatus err; 446 | playerStruct* player= (playerStruct*) priv; 447 | 448 | //fprintf(stderr, "\n[%ld] AQBufferCallback() [%d]", player->id, 1+(AQBuffer == player->AQBuffer2)); 449 | 450 | 451 | if (player->destroying) { 452 | 453 | //fprintf(stderr, "\n[%ld] DESTROYING", player->id); 454 | 455 | goto end; 456 | }; 457 | 458 | if (!player->playing) { 459 | 460 | //fprintf(stderr, "\n[%ld] IGNORE", player->id); 461 | 462 | goto end; 463 | } 464 | 465 | if (player->loop && (AQBuffer == player->AQBuffer1)) { 466 | 467 | //fprintf(stderr, "\n[%ld] loop--", player->id); 468 | 469 | player->loop--; 470 | } 471 | 472 | if (player->loop) { 473 | 474 | //fprintf(stderr, "\n[%ld] RE_QUEUE[%d]", player->id, 1+(AQBuffer == player->AQBuffer2)); 475 | 476 | // Si estamos en loop simplemente hay que volver a meterlo en la cola. 477 | 478 | AQBuffer->mAudioDataByteSize= (AQBuffer == player->AQBuffer1) ? player->AQBuffer1Length : player->AQBuffer2Length; 479 | 480 | err= AudioQueueEnqueueBuffer(player->AQ, AQBuffer, 0, NULL); 481 | if (err) { 482 | fprintf(stderr, " ERROR:AQBufferCallback AudioQueueEnqueueBuffer:[%d] ", err); 483 | } 484 | 485 | } 486 | else if (AQBuffer == player->AQBuffer2) { 487 | 488 | //fprintf(stderr, "\n[%ld] CLEANUP", player->id); 489 | 490 | err= AudioQueueStop(player->AQ, true); 491 | if (err) { 492 | fprintf(stderr, " ERROR:AudioQueueStop:[%d]", err); 493 | } 494 | 495 | player->playing= 0; 496 | 497 | if (player->hasCallback) { 498 | player->callbackIsPending= 1; 499 | player->pendingJSCallback= player->JSCallback; 500 | player->hasCallback= 0; 501 | queueStruct* theItem= newQueueItem(player, kPlayCallbackQueueItemType, NULL); 502 | 503 | pthread_mutex_lock(&callbacksQueue_mutex); 504 | if (callbacksQueue == NULL) callbacksQueue= theItem; 505 | else callbacksQueue->last->next= theItem; 506 | callbacksQueue->last= theItem; 507 | pthread_mutex_unlock(&callbacksQueue_mutex); 508 | 509 | if (!ev_async_pending(&eio_sound_async_notifier)) ev_async_send(EV_DEFAULT_UC_ &eio_sound_async_notifier); 510 | } 511 | } 512 | else { 513 | 514 | //fprintf(stderr, "\n[%ld] STOP", player->id); 515 | 516 | tracker(-1); 517 | 518 | err= AudioQueueStop(player->AQ, false); 519 | if (err) { 520 | fprintf(stderr, " ERROR:AudioQueueStop:[%d]", err); 521 | } 522 | } 523 | 524 | end: 525 | 526 | //fflush(stderr); 527 | return; 528 | } 529 | 530 | #endif 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | // =================== 541 | // = **** Pause **** = 542 | // =================== 543 | 544 | v8::Handle Pause (const Arguments &args) { 545 | 546 | HandleScope scope; 547 | 548 | #if defined (__APPLE__) 549 | 550 | OSStatus err; 551 | playerStruct* player; 552 | player= (playerStruct*) (External::Unwrap(args.This()->ToObject()->GetHiddenValue(hiddenPlayerPtr_symbol))); 553 | 554 | //fprintf(stderr, "\n*** Sound::pause [%ld]", player->id); 555 | 556 | if (player->paused) { 557 | //fprintf(stderr, " IGNORED_WAS_PAUSED"); 558 | goto end; 559 | } 560 | 561 | if (!player->playing) { 562 | //fprintf(stderr, " IGNORED_WAS_NOT_PLAYING"); 563 | goto end; 564 | } 565 | 566 | player->paused= 1; 567 | tracker(-1); 568 | err= AudioQueuePause(player->AQ); 569 | if (err) { 570 | fprintf(stderr, " ERROR:AudioQueuePause:[%d]", err); 571 | } 572 | 573 | #else 574 | fprintf(stderr, "\nERROR *** Sound::pause() LINUX not implemented argghh"); 575 | fflush(stderr); 576 | 577 | 578 | #endif 579 | 580 | end: 581 | return scope.Close(args.This()); 582 | } 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | // ========================= 592 | // = **** DestroyerCB **** = 593 | // ========================= 594 | 595 | void destroyerCB (v8::Persistent object, void* parameter) { 596 | 597 | #if defined (__APPLE__) 598 | 599 | OSStatus err; 600 | playerStruct* player= (playerStruct*) parameter; 601 | 602 | //fprintf(stderr, "\n*** destroyerCB() [%ld]", player->id); 603 | 604 | if (player->playing || player->hasCallback || player->callbackIsPending) { 605 | // Habrá que esperar a que termine antes de cargárselo... 606 | 607 | //fprintf(stderr, "\n[%ld] NOT_DESTROYED_WAS_PLAYING_OR_HAS_PENDING_CB", player->id); 608 | 609 | object.MakeWeak(player, destroyerCB); 610 | wasPlayingCtr++; 611 | goto end; 612 | } 613 | 614 | 615 | player->destroying= 1; 616 | err= AudioQueueDispose(player->AQ, true); 617 | if (err) { 618 | 619 | //fprintf(stderr, "\n[%ld] DESTROYING: AudioQueueDispose ERROR:[%d]", player->id, err); 620 | 621 | player->destroying= 0; 622 | object.MakeWeak((void*) player, destroyerCB); 623 | goto end; 624 | } 625 | else { 626 | 627 | //fprintf(stderr, "\n[%ld] DESTROYED", player->id); 628 | 629 | V8::AdjustAmountOfExternalAllocatedMemory(-(2*player->bufferLength + sizeof(playerStruct))); 630 | free(player); 631 | object.Dispose(); 632 | destroyedCtr++; 633 | } 634 | 635 | 636 | #else 637 | fprintf(stderr, "\nERROR *** destroyerCB() LINUX not implemented argghh"); 638 | fflush(stderr); 639 | 640 | 641 | #endif 642 | 643 | end: 644 | 645 | return; 646 | } 647 | 648 | 649 | 650 | 651 | 652 | // ==================== 653 | // = **** Create **** = 654 | // ==================== 655 | 656 | v8::Handle Create (const Arguments &args) { 657 | 658 | //fprintf(stderr, "\nOK *** Sound::Create() BEGIN"); 659 | //fflush(stderr); 660 | 661 | HandleScope scope; 662 | 663 | if (args.Length() != 1) { 664 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer): bad number of arguments"))); 665 | } 666 | 667 | if (!Buffer::HasInstance(args[0])) { 668 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer): The argument must be a Buffer() instance"))); 669 | } 670 | 671 | //v8::Persistent buffer= v8::Persistent::New(args[0]->ToObject()); 672 | Local buffer= args[0]->ToObject(); 673 | char* bufferData= Buffer::Data(buffer); 674 | size_t bufferLength= Buffer::Length(buffer); 675 | 676 | if (!bufferLength) { 677 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer): buffer has length == 0"))); 678 | } 679 | 680 | if (bufferLength < 8) { 681 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer): buffer.length must be >= 8"))); 682 | } 683 | 684 | if (bufferLength % 4) { 685 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer): buffer.length must a multiple of 4"))); 686 | } 687 | 688 | #if defined (__APPLE__) 689 | 690 | OSStatus err; 691 | playerStruct* player= newPlayer(); 692 | 693 | player->AQBuffer1Length= bufferLength/2; 694 | (player->AQBuffer1Length % 4) && (player->AQBuffer1Length-= (player->AQBuffer1Length % 4)); 695 | player->AQBuffer2Length= bufferLength- player->AQBuffer1Length; 696 | 697 | //fprintf(stderr, "\nOK *** AQBufferSize -> [%ld, %ld]", player->AQBuffer1Length, player->AQBuffer2Length); 698 | //fflush(stderr); 699 | 700 | 701 | 702 | err= AudioQueueNewOutput( 703 | player->format, // const AudioStreamBasicDescription *inFormat 704 | AQBufferCallback, // AudioQueueOutputCallback inCallbackProc 705 | player, // void *inUserData 706 | NULL, // CFRunLoopRef inCallbackRunLoop 707 | kCFRunLoopDefaultMode, // CFStringRef inCallbackRunLoopMode 708 | 0, // UInt32 inFlags 709 | &player->AQ // AudioQueueRef *outAQ 710 | ); 711 | 712 | if (err) { 713 | free(player); 714 | V8::AdjustAmountOfExternalAllocatedMemory((int) -sizeof(playerStruct)); 715 | fprintf(stderr, "\nERROR *** Sound::create AudioQueueNewOutput:[%d]\n", err); 716 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer) AudioQueueNewOutput error"))); 717 | } 718 | 719 | /* 720 | TODO 721 | - Hay que hacer un método rewind() ?. 722 | */ 723 | 724 | err= AudioQueueAllocateBuffer ( 725 | player->AQ, // AudioQueueRef inAQ 726 | player->AQBuffer1Length, // UInt32 inBufferByteSize 727 | &player->AQBuffer1 // AudioQueueBufferRef *outBuffer 728 | ); 729 | 730 | if (err) { 731 | AudioQueueDispose (player->AQ, true); 732 | free(player); 733 | V8::AdjustAmountOfExternalAllocatedMemory((int) -sizeof(playerStruct)); 734 | fprintf(stderr, "\nERROR *** Sound::create AudioQueueAllocateBuffer:[%d]\n", err); 735 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer) AudioQueueAllocateBuffer error"))); 736 | } 737 | 738 | err= AudioQueueAllocateBuffer ( 739 | player->AQ, // AudioQueueRef inAQ 740 | player->AQBuffer2Length, // UInt32 inBufferByteSize 741 | &player->AQBuffer2 // AudioQueueBufferRef *outBuffer 742 | ); 743 | 744 | if (err) { 745 | AudioQueueDispose (player->AQ, true); 746 | free(player); 747 | V8::AdjustAmountOfExternalAllocatedMemory((int) -sizeof(playerStruct)); 748 | fprintf(stderr, "\nERROR *** Sound::create AudioQueueAllocateBuffer:[%d]\n", err); 749 | return ThrowException(Exception::TypeError(String::New("Sound::create(buffer) AudioQueueAllocateBuffer error"))); 750 | } 751 | 752 | V8::AdjustAmountOfExternalAllocatedMemory(bufferLength); 753 | memcpy(player->AQBuffer1->mAudioData, bufferData, player->AQBuffer1Length); 754 | memcpy(player->AQBuffer2->mAudioData, (bufferData+ player->AQBuffer1Length), player->AQBuffer2Length); 755 | 756 | //fprintf(stderr, "\nOK *** Sound::Create() END"); 757 | 758 | #else 759 | fprintf(stderr, "\nERROR *** Sound::create() LINUX not implemented argghh."); 760 | 761 | playerStruct* player= newPlayer(); 762 | 763 | #endif 764 | 765 | v8::Persistent JSObject= v8::Persistent::New(Object::New()); 766 | JSObject.MakeWeak(player, destroyerCB); 767 | player->JSObject= JSObject; 768 | 769 | 770 | JSObject->Set(id_symbol, Integer::New(player->id)); 771 | JSObject->Set(play_symbol, play_function); 772 | JSObject->Set(loop_symbol, loop_function); 773 | JSObject->Set(pause_symbol, pause_function); 774 | JSObject->Set(volume_symbol, volume_function); 775 | JSObject->Set(data_symbol, buffer); 776 | JSObject->SetHiddenValue(hiddenPlayerPtr_symbol, External::Wrap(player)); 777 | 778 | 779 | if ((createdCtr % 500) == 0) { 780 | fprintf(stderr, "\nGC *** Sound::create [Created:%ld, Destroyed:%ld, WerePlaying:%ld]\n", createdCtr, destroyedCtr, wasPlayingCtr); 781 | } 782 | 783 | //fprintf(stderr, "\nOK *** Create\n"); 784 | //fflush(stderr); 785 | 786 | return scope.Close(JSObject); 787 | } 788 | 789 | 790 | 791 | 792 | 793 | // ================= 794 | // = renderSound() = 795 | // ================= 796 | 797 | void renderSound (renderJob* job) { 798 | 799 | #if defined (__APPLE__) 800 | 801 | OSStatus err; 802 | CFURLRef pathURL; 803 | job->bytesRead= 0; 804 | CFDataRef strChars; 805 | CFStringRef pathStr; 806 | queueStruct* bufferQItem; 807 | job->qHead= bufferQItem= NULL; 808 | ExtAudioFileRef inputAudioFile; 809 | 810 | strChars= CFDataCreate(NULL, (UInt8*) job->str, job->strLen); 811 | pathStr= CFStringCreateFromExternalRepresentation(NULL, strChars, kCFStringEncodingUTF8); 812 | //pathURL= CFURLCreateWithString (NULL, pathStr, NULL); 813 | pathURL= CFURLCreateWithFileSystemPath(NULL, pathStr, kCFURLPOSIXPathStyle, false); 814 | 815 | err= ExtAudioFileOpenURL(pathURL, &inputAudioFile); 816 | if (err) { 817 | fprintf(stderr, "\nERROR ExtAudioFileOpenURL [%d]", err); 818 | goto end1; 819 | } 820 | 821 | UInt32 size; 822 | macBoolean writable; 823 | err= ExtAudioFileGetPropertyInfo(inputAudioFile, kExtAudioFileProperty_FileDataFormat, &size, &writable); 824 | if (err) { 825 | fprintf(stderr, "\nERROR ExtAudioFileGetPropertyInfo [%d]", err); 826 | goto end2; 827 | } 828 | 829 | AudioStreamBasicDescription* inputFormat; 830 | inputFormat= (AudioStreamBasicDescription*) malloc(size); 831 | err= ExtAudioFileGetProperty(inputAudioFile, kExtAudioFileProperty_FileDataFormat, &size, inputFormat); 832 | if (err) { 833 | fprintf(stderr, "\nERROR ExtAudioFileGetProperty [%d]", err); 834 | goto end3; 835 | } 836 | /* 837 | fprintf(stderr, "\nmSampleRate: %lf", inputFormat->mSampleRate); 838 | fprintf(stderr, "\nmFormatID: %d", inputFormat->mFormatID); 839 | fprintf(stderr, "\nmFormatFlags: %d", inputFormat->mFormatFlags); 840 | fprintf(stderr, "\nmBytesPerPacket: %d", inputFormat->mBytesPerPacket); 841 | fprintf(stderr, "\nmFramesPerPacket: %d", inputFormat->mFramesPerPacket); 842 | fprintf(stderr, "\nmBytesPerFrame: %d", inputFormat->mBytesPerFrame); 843 | fprintf(stderr, "\nmChannelsPerFrame: %d", inputFormat->mChannelsPerFrame); 844 | fprintf(stderr, "\nmBitsPerChannel: %d", inputFormat->mBitsPerChannel); 845 | fprintf(stderr, "\nmReserved: %d", inputFormat->mReserved); 846 | */ 847 | 848 | err= ExtAudioFileSetProperty(inputAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &gFormato); 849 | if (err) { 850 | fprintf(stderr, "\nERROR ExtAudioFileSetProperty [%d]", err); 851 | goto end3; 852 | } 853 | 854 | #define kBufferSize 4*1024*1024 855 | 856 | UInt32 frames; 857 | bufferStruct* buffer; 858 | AudioBufferList bufferList; 859 | 860 | do { 861 | buffer= newBuffer(kBufferSize); 862 | bufferQItem= newQueueItem(buffer, kBufferListQueueItemType, job->qHead); 863 | if (job->qHead == NULL) job->qHead= bufferQItem; 864 | 865 | frames= buffer->size/4; 866 | bufferList.mNumberBuffers= 1; 867 | bufferList.mBuffers[0].mNumberChannels= 2; 868 | bufferList.mBuffers[0].mDataByteSize= buffer->size; 869 | bufferList.mBuffers[0].mData= buffer->buffer; 870 | 871 | err= ExtAudioFileRead (inputAudioFile, &frames, &bufferList); 872 | if (err) { 873 | fprintf(stderr, "\nERROR ExtAudioFileRead [%d]", err); 874 | goto end3; 875 | } 876 | 877 | job->bytesRead+= (buffer->used= frames*4); 878 | 879 | //fprintf(stderr, "\nSe han convertido %d frames", frames); 880 | 881 | } while (/*frames*/ buffer->used == buffer->size); 882 | 883 | 884 | 885 | 886 | end3: 887 | free(inputFormat); 888 | 889 | end2: 890 | err= ExtAudioFileDispose(inputAudioFile); 891 | if (err) { 892 | fprintf(stderr, "\nERROR ExtAudioFileDispose [%d]", err); 893 | } 894 | 895 | end1: 896 | CFRelease(pathURL); 897 | CFRelease(pathStr); 898 | CFRelease(strChars); 899 | 900 | #else 901 | fprintf(stderr, "\nERROR *** renderSound() LINUX not implemented argghh"); 902 | 903 | #endif 904 | 905 | } 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | // =========================== 915 | // = renderJobToNodeBuffer() = 916 | // =========================== 917 | 918 | v8::Handle renderJobToNodeBuffer (renderJob* job) { 919 | 920 | ssize_t offset; 921 | char* nodeBufferData; 922 | bufferStruct* buffer; 923 | Buffer* nodeBuffer; 924 | queueStruct* bufferQItem; 925 | 926 | if (job->qHead != NULL) { 927 | 928 | if (job->bytesRead) { 929 | nodeBuffer= Buffer::New(job->bytesRead); 930 | nodeBufferData= Buffer::Data(nodeBuffer->handle_); 931 | } 932 | 933 | offset= 0; 934 | bufferQItem= job->qHead; 935 | while (bufferQItem) { 936 | buffer= (bufferStruct*) bufferQItem->item; 937 | if (job->bytesRead && buffer->used) { 938 | memcpy(nodeBufferData+ offset, buffer->buffer, buffer->used); 939 | offset+= buffer->used; 940 | } 941 | destroyBuffer(buffer); 942 | bufferQItem= destroyQueueItem(bufferQItem); 943 | } 944 | 945 | return nodeBuffer->handle_; 946 | } 947 | 948 | return v8::Object::New(); 949 | } 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | // ============================= 959 | // = **** BufferifySync() **** = 960 | // ============================= 961 | 962 | v8::Handle BufferifySync (const Arguments &args) { 963 | 964 | HandleScope scope; 965 | 966 | Local str; 967 | renderJob job; 968 | 969 | if ((args.Length() != 1) || !args[0]->IsString()) { 970 | return ThrowException(Exception::TypeError(String::New("Sound::bufferifySync(): bad arguments"))); 971 | } 972 | 973 | str= args[0]->ToString(); 974 | job.str= *String::Utf8Value(str); 975 | job.strLen= str->Utf8Length(); 976 | if (!job.strLen) { 977 | return Undefined(); 978 | } 979 | job.bytesRead= 0; 980 | job.qHead= NULL; 981 | 982 | renderSound(&job); 983 | return scope.Close(renderJobToNodeBuffer(&job)); 984 | } 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | // ================== 993 | // = renderThread() = 994 | // ================== 995 | 996 | void* renderThread (void* ptr) { 997 | 998 | int RUN; 999 | renderJob* job; 1000 | queueStruct* qitem; 1001 | 1002 | do { 1003 | job= (renderJob*) renderJobsQueue->item; 1004 | 1005 | renderSound(job); 1006 | qitem= newQueueItem(job, kRenderCallbackQueueItemType, NULL); 1007 | 1008 | pthread_mutex_lock(&callbacksQueue_mutex); 1009 | if (callbacksQueue == NULL) callbacksQueue= qitem; 1010 | else callbacksQueue->last->next= qitem; 1011 | callbacksQueue->last= qitem; 1012 | pthread_mutex_unlock(&callbacksQueue_mutex); 1013 | 1014 | if (!ev_async_pending(&eio_sound_async_notifier)) ev_async_send(EV_DEFAULT_UC_ &eio_sound_async_notifier); 1015 | 1016 | RUN= 0; 1017 | pthread_mutex_lock(&renderJobsQueue_mutex); 1018 | renderJobsQueue= destroyQueueItem(renderJobsQueue); 1019 | RUN= renderJobsQueue != NULL; 1020 | pthread_mutex_unlock(&renderJobsQueue_mutex); 1021 | 1022 | } while (RUN); 1023 | 1024 | return NULL; 1025 | } 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | // ========================= 1036 | // = **** Bufferify() **** = 1037 | // ========================= 1038 | 1039 | v8::Handle Bufferify (const Arguments &args) { 1040 | 1041 | HandleScope scope; 1042 | 1043 | int RUN; 1044 | renderJob* job; 1045 | Local str; 1046 | queueStruct* qitem; 1047 | 1048 | if ((args.Length() != 2) || (!(args[0]->IsString() && args[1]->IsFunction()))) { 1049 | return ThrowException(Exception::TypeError(String::New("Sound::bufferify(): bad arguments"))); 1050 | } 1051 | 1052 | str= args[0]->ToString(); 1053 | job= (renderJob*) malloc(sizeof(renderJob)); 1054 | job->strLen= str->Utf8Length(); 1055 | job->str= (char*) malloc(job->strLen); 1056 | strncpy(job->str, *String::Utf8Value(str), job->strLen); 1057 | job->bytesRead= 0; 1058 | job->qHead= NULL; 1059 | job->JSCallback= v8::Persistent::New(args[1]->ToObject()); 1060 | 1061 | // Grab the queue. 1062 | qitem= newQueueItem(job, kRenderJobsListQueueItemType, NULL); 1063 | pthread_mutex_lock(&renderJobsQueue_mutex); 1064 | if (renderJobsQueue == NULL) { 1065 | RUN= 1; 1066 | renderJobsQueue= qitem; 1067 | } 1068 | else { 1069 | RUN= 0; 1070 | renderJobsQueue->last->next= qitem; 1071 | } 1072 | renderJobsQueue->last= qitem; 1073 | pthread_mutex_unlock(&renderJobsQueue_mutex); 1074 | 1075 | if (RUN) pthread_create(&theRenderThread, NULL, renderThread, NULL); 1076 | 1077 | return Undefined(); 1078 | } 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | // =================================== 1091 | // = **** Callback into node.js **** = 1092 | // =================================== 1093 | 1094 | // this is the async libev event callback that runs in node.js main thread 1095 | static void Callback (EV_P_ ev_async *watcher, int revents) { 1096 | 1097 | //fprintf(stderr, "*** JSCallback"); 1098 | //fflush(stderr); 1099 | 1100 | HandleScope scope; 1101 | 1102 | assert(watcher == &eio_sound_async_notifier); 1103 | assert(revents == EV_ASYNC); 1104 | 1105 | renderJob* job; 1106 | queueStruct* qitem; 1107 | playerStruct* player; 1108 | Local argv[2]; 1109 | v8::Persistent cb; 1110 | Local mayBeBuffer; 1111 | 1112 | // Grab the queue. 1113 | pthread_mutex_lock(&callbacksQueue_mutex); 1114 | qitem= callbacksQueue; 1115 | callbacksQueue= NULL; 1116 | pthread_mutex_unlock(&callbacksQueue_mutex); 1117 | 1118 | /* 1119 | TODO 1120 | - ver qué hay que hacer exactamente cuando cb() throws. 1121 | */ 1122 | 1123 | //TryCatch try_catch; 1124 | 1125 | while (qitem != NULL) { 1126 | if (qitem->type == kPlayCallbackQueueItemType) { 1127 | 1128 | player= (playerStruct*) qitem->item; 1129 | 1130 | if (player->callbackIsPending) { 1131 | cb= player->pendingJSCallback; 1132 | v8::Persistent::Cast(cb)->Call(player->JSObject, 0, NULL); 1133 | player->callbackIsPending= 0; 1134 | cb.Dispose(); 1135 | //if (try_catch.HasCaught()) FatalException(try_catch); 1136 | } 1137 | } 1138 | else if (qitem->type == kRenderCallbackQueueItemType) { 1139 | job= (renderJob*) qitem->item; 1140 | cb= job->JSCallback; 1141 | mayBeBuffer= renderJobToNodeBuffer(job)->ToObject(); 1142 | if (Buffer::HasInstance(mayBeBuffer)) { 1143 | argv[0]= Integer::New(0); 1144 | argv[1]= Local::Cast(mayBeBuffer); 1145 | } 1146 | else { 1147 | argv[0]= Integer::New(1); 1148 | argv[1]= Local::New(Null()); 1149 | } 1150 | v8::Persistent::Cast(cb)->Call(Context::GetCurrent()->Global(), 2, argv); 1151 | cb.Dispose(); 1152 | free(job->str); 1153 | free(job); 1154 | } 1155 | 1156 | qitem= destroyQueueItem(qitem); 1157 | } 1158 | } 1159 | 1160 | 1161 | 1162 | 1163 | // ============ 1164 | // = Stream() = 1165 | // ============ 1166 | 1167 | v8::Handle Stream (const Arguments &args) { 1168 | 1169 | HandleScope scope; 1170 | 1171 | fprintf(stderr, "\nERROR *** Sound::Stream() Not yet."); 1172 | 1173 | return Undefined(); 1174 | } 1175 | 1176 | 1177 | 1178 | // ================================= 1179 | // = **** Initialization code **** = 1180 | // ================================= 1181 | 1182 | // Esto se llama una sola vez, al hacer require('sound'); 1183 | extern "C" { 1184 | void init (v8::Handle target) { 1185 | 1186 | HandleScope scope; 1187 | 1188 | volume_symbol= v8::Persistent::New(String::New("volume")); 1189 | play_symbol= v8::Persistent::New(String::New("play")); 1190 | loop_symbol= v8::Persistent::New(String::New("loop")); 1191 | pause_symbol= v8::Persistent::New(String::New("pause")); 1192 | 1193 | id_symbol= v8::Persistent::New(String::New("id")); 1194 | data_symbol= v8::Persistent::New(String::New("data")); 1195 | hiddenPlayerPtr_symbol= v8::Persistent::New(String::New("_hiddenPlayerPtr")); 1196 | 1197 | volume_function= v8::Persistent::New(FunctionTemplate::New(Volume)->GetFunction()); 1198 | loop_function= v8::Persistent::New(FunctionTemplate::New(Loop)->GetFunction()); 1199 | play_function= v8::Persistent::New(FunctionTemplate::New(Play)->GetFunction()); 1200 | pause_function= v8::Persistent::New(FunctionTemplate::New(Pause)->GetFunction()); 1201 | 1202 | target->Set(String::New("create"), v8::Persistent::New(FunctionTemplate::New(Create)->GetFunction())); 1203 | target->Set(String::New("stream"), v8::Persistent::New(FunctionTemplate::New(Stream)->GetFunction())); 1204 | target->Set(String::New("bufferify"), v8::Persistent::New(FunctionTemplate::New(Bufferify)->GetFunction())); 1205 | target->Set(String::New("bufferifySync"), v8::Persistent::New(FunctionTemplate::New(BufferifySync)->GetFunction())); 1206 | 1207 | // Start async events for callbacks. 1208 | ev_async_init(&eio_sound_async_notifier, Callback); 1209 | ev_async_start(EV_DEFAULT_UC_ &eio_sound_async_notifier); 1210 | ev_unref(EV_DEFAULT_UC); 1211 | 1212 | #if defined (__APPLE__) 1213 | gFormato.mSampleRate= 44100; 1214 | gFormato.mFormatID= kAudioFormatLinearPCM; 1215 | gFormato.mFormatFlags= kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 1216 | gFormato.mBytesPerPacket= 4; 1217 | gFormato.mFramesPerPacket= 1; 1218 | gFormato.mBytesPerFrame= 4; 1219 | gFormato.mChannelsPerFrame= 2; 1220 | gFormato.mBitsPerChannel= 16; 1221 | 1222 | /* 1223 | This is a dummy player to init the sound machinery and to keep it up and running when no other sounds are playing. 1224 | It does not need any buffers, it just plays silence. It's paused when not needed (when other sounds are playing) 1225 | and restarted when no other sounds are playing (tracker() keeps track of that). 1226 | */ 1227 | 1228 | OSStatus err; 1229 | fondoSnd= newPlayer(); 1230 | err= AudioQueueNewOutput( 1231 | fondoSnd->format, // const AudioStreamBasicDescription *inFormat 1232 | AQBufferCallback, // AudioQueueOutputCallback inCallbackProc 1233 | fondoSnd, // void *inUserData 1234 | NULL, // CFRunLoopRef inCallbackRunLoop 1235 | kCFRunLoopDefaultMode, // CFStringRef inCallbackRunLoopMode 1236 | 0, // UInt32 inFlags 1237 | &fondoSnd->AQ // AudioQueueRef *outAQ 1238 | ); 1239 | 1240 | err= AudioQueueStart(fondoSnd->AQ, NULL); 1241 | if (err) { 1242 | fprintf(stderr, "\nERROR *** Sound::Init AudioQueueStart:[%d]\n", err); 1243 | } 1244 | 1245 | #endif 1246 | 1247 | fprintf(stderr, "\nOK *** Sound::init"); 1248 | 1249 | #if defined (__APPLE__) 1250 | fprintf(stderr, " [MAC] "); 1251 | #else 1252 | fprintf(stderr, " [LINUX] "); 1253 | #endif 1254 | 1255 | fprintf(stderr, " © 2011 Jorge@jorgechamorro.com\n"); 1256 | fflush(stderr); 1257 | } 1258 | 1259 | NODE_MODULE(sound, init); 1260 | } 1261 | --------------------------------------------------------------------------------