Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

audio-vs1053b

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

audio-vs1053b - npm Package Compare versions

Comparing version 0.1.1 to 0.1.2

examples/audio.js

427

index.js

@@ -6,4 +6,5 @@ // VS1053b module

var util = require('util');
var async = require('async');
var hw = process.binding('hw')
var Writable = require('stream').Writable;
var Readable = require('stream').Readable;

@@ -28,2 +29,3 @@ // VS10xx SCI Registers

var MODE_SM_RESET = 0x04;

@@ -34,2 +36,10 @@ var GPIO_DIR_ADDR = 0xC017

var inputReg = 0x05,
outputReg = 0x07;
var _audioCallbacks = {};
var _fillBuff = new Buffer(15000);
_fillBuff.fill(0);
function use (hardware, next) {

@@ -48,8 +58,19 @@ return new Audio(hardware, next);

// Set our register select pins
this.MP3_XCS = hardware.digital[1].output(true); //Control Chip Select Pin (for accessing SPI Control/Status registers)
this.MP3_DCS = hardware.digital[2].output(true); //Data Chip Select / BSYNC Pin
this.MP3_DREQ = hardware.digital[3].input() //Data Request Pin: Player asks for more data
this.MP3_XCS = hardware.digital[0].output(true); //Control Chip Select Pin (for accessing SPI Control/Status registers)
this.MP3_DCS = hardware.digital[1].output(true); //Data Chip Select / BSYNC Pin
this.MP3_DREQ = hardware.digital[2].input() //Data Request Pin: Player asks for more data
this.input = "";
this.output = "";
// Waits for the audio completion event which signifies that a buffer has finished streaming
process.on('audio_playback_complete', this._handlePlaybackComplete.bind(this));
// Waits for the audio data event which is recorded data being output from the C shim
process.on('audio_recording_data', this._handleRecordedData.bind(this));
// Waits for final flushed buffer of a recording
process.on('audio_recording_complete', this._handleRecordedData.bind(this));
// Initialize the module
this.initialize(callback);

@@ -95,2 +116,39 @@ }

Audio.prototype._handlePlaybackComplete = function(errBool, completedStream) {
var self = this;
if (self.lock) {
self.lock.release(function(err) {
if (err) {
self.emit('error', err);
return
}
else {
// Get the callback if one was saved
var callback = _audioCallbacks[completedStream];
// If it exists
if (callback) {
// Remove it from our datastructure
delete _audioCallbacks[completedStream];
var err;
// Generate an error message if there was an error
if (errBool) {
err = new Error("Error sending buffer over SPI");
}
// Call the callback
callback(err);
self.emit('end', err);
}
}
});
}
}
Audio.prototype._handleRecordedData = function(length) {
this.emit('data', _fillBuff.slice(0, length));
}
Audio.prototype._failConnect = function(err, callback) {

@@ -104,2 +162,62 @@ setImmediate(function() {

Audio.prototype.createPlayStream = function() {
var audio = this;
var playStream = new Writable;
// The Audio Module won't play chunks that are
// less than ~3425 bytes...
playStream.bufs = [];
playStream.bufferedLen = 0;
playStream._write = function (chunk, enc, next) {
var err;
this.bufs.push(chunk);
this.bufferedLen += chunk.length;
// Check if this chunk is too small to be played solo
if (this.bufferedLen >= 10000) {
var audioData = Buffer.concat(this.bufs);
this.bufs = []; this.bufferedLen = 0;
var ret = audio.queue(audioData);
if (ret < 0) {
err = new Error("Unable to queue the streamed buffer.");
}
}
next(err);
};
playStream.on('finish', function flush() {
var audioData = Buffer.concat(this.bufs);
this.bufs = []; this.bufferedLen = 0;
if (audioData.length) {
var ret = audio.queue(audioData);
if (ret < 0) {
err = new Error("Unable to queue the streamed buffer.");
}
}
});
return playStream;
}
// Creates a Readable record stream
Audio.prototype.createRecordStream = function(profile) {
var audio = this;
var recordStream = new Readable;
var pusher = recordStream.push.bind(recordStream);
audio.on('data', pusher);
process.once('audio_recording_complete', recordStream.push.bind(recordStream));
recordStream._write = function() {};
audio.startRecording(profile);
return recordStream;
}
Audio.prototype._softReset = function(callback) {

@@ -109,3 +227,3 @@ this._readSciRegister16(SCI_MODE, function(err, mode) {

else {
this._writeSciRegister16(SCI_MODE, mode | 2, function(err) {
this._writeSciRegister16(SCI_MODE, mode | MODE_SM_RESET, function(err) {
if (err) { return callback && callback(err); }

@@ -125,2 +243,4 @@ else {

else if ((MP3Status >> 4) & 0x000F != 4){
var err = new Error("Invalid version returned from module.");
return callback && callback(new Error("Invalid version returned from module."));

@@ -166,3 +286,2 @@ }

//SCI consists of instruction byte, address byte, and 16-bit data word.
// TODO: Error handling
this._SPItransferByte(0x03, function(err) {

@@ -257,3 +376,3 @@ this._SPItransferByte(addressbyte, function(err) {

else {
self.setInput('microphone', function(err) {
self.setInput('mic', function(err) {
if (err) { self._failConnect(err, callback); }

@@ -286,3 +405,3 @@ else {

Audio.prototype.setInput = function(input, callback) {
if (input != 'lineIn' && input != 'microphone') {
if (input != 'lineIn' && input != 'mic') {
return callback && callback(new Error("Invalid input requested..."));

@@ -293,8 +412,7 @@ }

var bit = (input == 'microphone' ? 1 : 0);
this._getChipGpio(function(err, gpio) {
if (err) { return callback && callback(err); }
else {
this._setChipGpio(gpio & (bit << 5), callback);
var newReg = (input === "lineIn" ? (gpio | (1 << inputReg)) : (gpio & ~(1 << inputReg)));
this._setChipGpio(newReg, callback);
}

@@ -311,9 +429,8 @@ }.bind(this));

this.output = output;
var bit = (output == 'lineOut' ? 1 : 0);
this._getChipGpio(function(err, gpio) {
if (err) { return callback && callback(err); }
else {
this._setChipGpio(gpio & (bit << 7), callback);
// Check if it's input or output and set the 7th bit of the gpio reg accordingly
var newReg = (output === 'lineOut' ? (gpio | (1 << outputReg)) : (gpio & ~(1 << outputReg)));
this._setChipGpio(newReg, callback);
}

@@ -324,33 +441,265 @@ }.bind(this));

function Track(length, id, callback) {
this._buflen = length;
this.id = id;
this.callback = callback;
}
util.inherits(Track, events.EventEmitter);
Audio.prototype.play = function(buff, callback) {
console.log('Loading mp3');
// Check if no buffer was passed in but a callback was
// (the user would like to resume playback)
var self = this;
console.log('chunking', buff.length, 'bytes.');
console.log('ret: ', hw.audio_play_buffer(this.MP3_DCS.pin, this.MP3_DREQ.pin, buff, buff.length));
if (!callback && typeof buff == "function") {
callback = buff;
buff = new Buffer(0);
}
// Check if there was no buffer or callback passed in
else if (!buff && !callback) {
buff = new Buffer(0);
}
self.spi.lock(function(err, lock) {
if (err) {
if (callback) {
callback(err);
}
// var len = buff.length;
// var chunks = [], clen = 64;
// var p = 0, i = 0;
// while (p < len) {
// chunks[i] = buff.slice(p, p + clen);
// i = i + 1;
// p = p + clen;
// }
return;
}
// console.log('done chunking:', chunks.length, 'chunks. Playing song...');
// Save the lock reference so we can free it later
self.lock = lock;
// Initialize SPI so it's set to the right settings
self.spi.initialize();
// Send this buffer off to our shim to have it start playing
var streamID = hw.audio_play_buffer(self.MP3_XCS.pin, self.MP3_DCS.pin, self.MP3_DREQ.pin, buff, buff.length);
// this.MP3_DCS.low();
// async.eachSeries(
// chunks,
// function playChunk(chunk, callback) {
// while(!this.MP3_DREQ.read()){};
// this.spi.transfer(chunk, callback);
// }.bind(this),
// function playComplete(err) {
// this.MP3_DCS.high();
// callback && callback(err);
// }.bind(this)
// );
var track = new Track(buff.length, streamID, callback);
self._handleTrack(track);
self.emit('play', track);
});
}
Audio.prototype._handleTrack = function(track) {
// If stream id is less than zero, an error occured
if (track.id < 0) {
var err;
if (track.id == -1) {
err = new Error("Attempt to move to an invalid state.");
}
else if (track.id == -2) {
err = new Error("Audio playback requires one GPIO Interrupt and none are available.");
}
else if (track.id == -3) {
err = new Error("Unable to allocate memory required for transfer...");
}
if (track.callback) {
track.callback(err);
}
this.emit('error', err, track);
return;
}
// No error occured
else {
// If a callback was provided
if (track.callback) {
// Add it to the callbacks dict
_audioCallbacks[track.id] = track.callback;
}
}
}
Audio.prototype.queue = function(buff, callback) {
var self = this;
if (!buff) {
if (callback) {
callback(new Error("Must pass valid buffer to queue."));
}
return;
}
// Initialize SPI to the correct settings
self.spi.initialize();
self.spi.lock(function(err, lock) {
if (err) {
if (callback) {
callback(err);
}
return;
}
else {
self.lock = lock;
var streamID = hw.audio_queue_buffer(self.MP3_XCS.pin, self.MP3_DCS.pin, self.MP3_DREQ.pin, buff, buff.length);
var track = new Track(buf_len, streamID, callback);
self._handleTrack(track);
}
});
}
Audio.prototype.pause = function(callback) {
var err;
var ret = hw.audio_pause_buffer();
if (ret < 0) {
err = new Error("A buffer is not being played.");
this.emit('error', err);
}
// If we have a lock on the spi bus
if (this.lock) {
// Release it
this.lock.release(function(err) {
// Call the callback if we have one
if (callback) {
callback(err);
}
return;
});
}
// If there is no lock, just call the callback
else {
if (callback) {
callback(err);
}
}
}
Audio.prototype.stop = function(callback) {
var err;
var ret = hw.audio_stop_buffer();
if (ret < 0) {
err = new Error("Not in a valid state to call stop.");
this.emit('error', err);
}
// If we have a lock on the spi bus
if (this.lock) {
// Release it
this.lock.release(function(err) {
// Call the callback if we have one
if (callback) {
callback(err);
}
return;
});
}
// If there is no lock, just call the callback
else {
if (callback) {
callback(err);
}
}
}
Audio.prototype.availableRecordingProfiles = function() {
return [
'voice',
'wideband-voice',
'wideband-stereo',
'hifi-voice',
'stereo-music'
];
}
Audio.prototype.startRecording = function(profile, callback) {
var self = this;
if (!callback && typeof profile == "function") {
callback = profile;
profile = "hifi-voice";
}
else if (!profile && !callback) {
profile = "hifi-voice";
}
if (self.availableRecordingProfiles().indexOf(profile) == -1) {
var err = new Error("Invalid profile name. See audio.availableRecordingProfiles()");
if (callback) {
callback(err);
}
self.emit('error', err);
return;
}
// Initialize SPI to the correct settings
self.spi.initialize();
var pluginDir = __dirname + "/plugins/" + profile + ".img";
var ret = hw.audio_start_recording(this.MP3_XCS.pin, this.MP3_DREQ.pin, pluginDir, _fillBuff);
if (ret < 0) {
var err;
if (ret == -1) {
err = new Error("Not able to allocate recording memory...");
}
else if (ret == -2) {
err = new Error("Invalid plugin file.");
}
else if (ret == -3) {
err = new Error("Module must be in an idle state to start recording.");
}
if (callback) {
callback(err);
}
this.emit('error', err);
return;
}
else {
if (callback) {
callback();
}
this.emit('startRecording');
}
}
Audio.prototype.stopRecording = function(callback) {
var self = this;
process.once('audio_recording_complete', function recStopped(length) {
// If a callback was provided, return it
if (callback) {
callback();
}
// Stop recording
self.emit('stopRecording');
});
var ret = hw.audio_stop_recording();
if (ret < 0) {
var err = new Error("Not in valid state to stop recording.");
if (callback) {
callback(err);
}
this.emit('error', err);
return;
}
}
exports.use = use;

9

package.json
{
"name": "audio-vs1053b",
"version": "0.1.1",
"version": "0.1.2",
"description": "",

@@ -11,9 +11,6 @@ "main": "index.js",

"author": "",
"license": "BSD-2-Clause",
"dependencies": {
"async": "*"
},
"devDependencies": {
"tinytap": "~0.0.2"
}
},
"license": "BSD-2-Clause"
}

@@ -7,2 +7,12 @@ #Audio Module

## Limitations
The current version of the Tessel runtime is too slow to play audio files smoothly. That means we wrote a custom C shim that handles most of the playback and recording of data. There are several consequences of the C shim:
* Any other modules that use SPI for communication will be blocked while the audio module is playing a buffer.
* You can only have one audio module attached to Tessel at a time.
* Updates to the Audio Module driver must be released in both firmware and this npm repo.
It sucks but we're expecting major runtime speed improvements to render the C shim uncessesary within the next couple months.
##Example

@@ -16,4 +26,10 @@ 1. Writing mic data to a file (w/out streams)

// Start recording data for a second into a file
audio.setInput('microphone', function(err) {
audio.setInput('mic', function(err) {
var chunks = [];
audio.on('data', function(data) {
chunks.push(data);
});
// Start the recording

@@ -24,5 +40,5 @@ audio.startRecording(function(err) {

// Stop recording
audio.stopRecording(function(err, oggBuffer) {
audio.stopRecording(function(err) {
// Write the buffer to a file
fs.writeFile("micdata", oggBuffer, function(err) {
fs.writeFile("micdata", Buffer.concat(chunks), function(err) {
console.log("Wrote to a file");

@@ -44,12 +60,9 @@ });

// Start recording data for a second into a file
audio.setInput('microphone', function(err) {
audio.setInput('line-in', function(err) {
// Open a stream to a file
var file = fs.createWriteStream('lineInData.ogg');
// Create a readable stream of incoming data
var soundData = audio.createReadStream();
var soundData = audio.createRecordStream();
// Pipe data to the file
soundData.pipe(file);
// Enable sound input
audio.startRecording();
});

@@ -59,3 +72,3 @@ });

```
3. Output audio on the headphone Jack
3. Output audio on the headphone Jack (w/o streams)
```.js

@@ -76,3 +89,17 @@ var tessel = require('tessel');

```
4. Output audio on the headphone Jack (with streams)
```.js
var tessel = require('tessel');
var fs = require('fs');
var audio = require('audio-vs1053b').use(tessel.port('a'), function(err) {
// Start recording data for a second into a file
audio.setOutput('headphone', function(err) {
// Open a file
fs.createReadStream('rayman.ogg').pipe(audio.createPlayStream());
});
});
```
##API

@@ -86,3 +113,3 @@

// Set the input to either 'lineIn' or 'microphone'. Defaults to 'lineIn'.
// Set the input to either 'lineIn' or 'mic'. Defaults to 'lineIn'.
audio.setInput( input, function(err) {...} );

@@ -93,10 +120,12 @@

// Start recording sound from the input.
audio.startRecording( function(err) {...} );
// Start recording sound from the input. (Receive data in the 'data' event) Callback called after recording initialized (not stopped.)
quality is an optional argument that can be 'voice', 'wideband-voice', 'wideband-stereo', 'hifi-voice', or 'stereo-music'. Default is 'hifi-voice'.
audio.startRecording([profile] function(err) {...} );
// Stop recording sound and return an OGG-encoded buffer
audio.stopRecording( function(err, oggBuff) {...} );
// Stop recording sound (note that may receive one more 'data' event before this completes when the buffer is flushed.)
audio.stopRecording( function(err) {...} );
// Play a buffer
audio.play( audioBuff, function(err) {...} );
// Play a buffer. If no buffer is passed in, the module
// will attempt to resume a buffer that was paused.
audio.play( [audioBuff], function(err) {...} );

@@ -110,7 +139,10 @@ // Pause the buffer

// Returns a stream that a buffer can be piped into to play audio
audio.createWriteableStream();
audio.createPlayStream();
// Returns a readable stream of mic data
audio.createReadableStream()
audio.createRecordStream()
// Returns an array of available profiles
audio.availableRecordingProfiles();
```

@@ -140,2 +172,5 @@

// Received recorded data
audio.on('data', function(audioBuff) {...} );
// Stopped recording on the input

@@ -147,8 +182,11 @@ audio.on('stopRecording', function() {...} );

// The buffer was paused
// Playback was paused
audio.on('pause', function() {...} );
// The buffer was stopped
// Playback was stopped
audio.on('stop', function() {...} );
// The buffer finished playing
audio.on('end', function(err) {...})
```
var tessel = require('tessel');
var fs = require('fs');
var audio = require('./').use(tessel.port('a'));
var song = fs.readFileSync('/app/playback/sample.mp3');
var audio = require('./').use(tessel.port['A']);
var stream = require('stream');
var filename = process.argv[2] || 'audio-recording.ogg';
console.log("Saving to filename:", filename);
var datas = [];
audio.on('ready', function() {
console.log("Ready to go!");
// audio.setVolume(2, 2, function(err) {
// if (err) return console.log('err setting volume', err);
var song = fs.readFileSync('/app/sample.mp3');
audio.on('data', function weRecorded(data) {
console.log('got this recording data!', data.length);;
datas.push(data);
})
audio.on('error', function(err) {
console.log("ERROR:", err);
})
audio.on('startRecording', function() {
console.log('started recording!');
});
audio.on('stopRecording', function() {
console.log('stopped recording!');
var rec = Buffer.concat(datas);
console.log('playing len', rec.length);
process.sendfile(filename, rec);
// fs.writeFileSync(filename, rec);
// fs.createReadStream(filename).pipe(audio.createPlayStream());
});
function testOutputs() {
audio.setInput('lineIn', function(err) {
console.log('line in is set', err);
audio.startRecording('voice', function(err) {
console.log('started recording');
setTimeout(audio.stopRecording.bind(audio), 12000);
});
});
}
function testInputs() {
audio.setOutput('lineOut', function(err) {
console.log('set to line in', err);
audio.play(song, function(err) {
console.log('finished playing song', err);
console.log('finished with line in.');
audio.setOutput('headphones', function(err) {
console.log('set to phones', err);
audio.play(song);
});
});
});
}
function testSwitchPlayRecord() {
console.log('playing song.');
audio.play(song, function(err) {
if (err) return console.log("err playing..", err);
else {
console.log('finished playing...');
audio.startRecording();
}
})
}
function testSwitchRecordPlay() {
console.log('recording song.');
audio.startRecording(function recStarted(err) {
if (err) return console.log("err starting recording..", err);
});
setTimeout(function() {
console.log('calling stop recording...');
audio.stopRecording(function recStopped(err) {
if (err) return console.log("err stopping recording..", err);
else {
audio.play(song);
}
})
}, 3000);
}
function testRecording() {
audio.setInput('mic', function() {
audio.startRecording('voice', function() {
setTimeout(function stopRecording() {
audio.stopRecording(function stopped() {
console.log("Stop recording callback called...");
})
}, 4000);
});
})
}
function testPlayback() {
var ret = audio.play(song, function(err) {
if (err) {
console.log("error playing song: ", err);
}
else {
console.log("Done playing the song");
}
});
}
function testQueue() {
audio.queue(song);
audio.queue(song);
audio.queue(song);
audio.queue(song);
}
function testPlayStop() {
audio.on('play', audio.stop.bind(audio, undefined));
audio.play(song);
}
function testPlayQueue() {
audio.play(song);
audio.queue(song);
audio.queue(song);
audio.queue(song);
audio.queue(song);
}
function testPlayStream() {
var file = fs.createReadStream('/app/playback/rayman.ogg');
file.pipe(audio.createPlayStream());
}
function testRecordStream() {
var rec = audio.createRecordStream("voice");
var file = fs.createWriteStream('rec.ogg');
rec.pipe(file);
setTimeout(function() {
audio.stopRecording(function() {
console.log("Stop recording callback after stream called...");
rec.unpipe(file);
});
// })
});
}, 4000);
}
audio.on('error', function(err) {
console.log("Failed to connect", err);
})
function testPlayStreamSmallChunks(chunkSize) {
var chunk = chunkSize;
var incr = Math.floor(song.length/chunk);
console.log('length', song.length, 'floor', Math.floor(song.length/chunk));
var rs = new stream.Readable;
setInterval(function(){}, 20000);
for (var i = 0; i < incr; i++) {
var pos = chunk * i;
rs.push(song.slice(pos, pos + chunk));
}
if (song.length%chunk) {
var pos = chunk * incr;
rs.push(song.slice(pos, pos + song.length%chunk));
}
rs.push(null);
rs.pipe(audio.createPlayStream())
}
audio.on('ready', function() {
console.log("Ready to go!");
audio.setVolume(20, 20, function(e) {
// testOutputs();
// testInputs();
// testPlayStreamSmallChunks(5000);
// testRecordStream();
// testPlayStream();
// testSwitchPlayRecord();
// testSwitchRecordPlay();
// testQueue();
// testPlayQueue();
testRecording();
// testPlayStop();
// testPlayback();
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc