Manipulating a Buffer of raw PCM data in NodeJS










1















I'm working on a personal project that involves retrieving audio from YouTube, manipulating the audio, and streaming the result to the browser. So far I have the first and last steps down, but the middle is proving a challenge.



Thanks to the youtube-audio-stream package, getting the audio was easy. I wanted to manipulate the raw audio samples, so I followed their README example and piped the stream into a Decoder from the lame package.



I threw together a couple stream transforms... one to merge incoming chunks together until a size threshold was met, and the other one to actually do something with those chunks. At the end of the pipeline, I added a wav writer (which adds a WAV header so the browser isn't confused about the raw data coming in).



This actually results in normal audio output if my audio transform just passes along the chunks without any modification. So I know that the pipeline itself isn't broken. But for some reason, performing the following operation results in garbled noise:



chunk.reverse();


(This isn't the ultimate goal -- that involves FFT -- but I figured reversing audio chunks was a good operation to start with.)



I expected this to transform the stream into reversed fragments of sound, but instead it distorted it beyond recognition. I know that Node.js Buffers are Uint8Arrays, so I'm wondering if each sample is stored as 4 separate 8-bit integers. But I tried doing something like this:



const arr = Float32Array.from(chunk);
this.push(new Buffer(arr.reverse()));


and it's still garbled. I also tried writing a loop that used Buffer.readFloatLE and Buffer.writeFloatLE, but that didn't behave as expected either. What am I missing here? How can I retrieve and set audio sample data in a Node.js Buffer?



Edit: Adding example code (I'm running this locally as a microservice using micro):



index.js



const stream = require('youtube-audio-stream');
const wav = require('wav');
const decoder = require('lame').Decoder;
const Chunker, AudioThing = require('./transforms');

module.exports = (req, res) =>
const url = 'https://www.youtube.com/watch?v=-L7IdUqaZxo';
res.setHeader('Content-Type', 'audio/wav');
return stream(url)
.pipe(decoder())
.pipe(new Chunker(2 ** 16))
.pipe(new AudioThing())
.pipe(new wav.Writer());



transforms.js



const Transform = require('stream');

class Chunker extends Transform
constructor(threshold)
super();
this.size = 0;
this.chunks = ;
this.threshold = threshold;


_transform(chunk, encoding, done)
this.size += chunk.length;
this.chunks.push(chunk);
if (this.size >= this.threshold)
this.push(Buffer.concat(this.chunks, this.size));
this.chunks = ;
this.size = 0;

done();



class AudioThing extends Transform
_transform(chunk, encoding, done)
this.push(chunk.reverse());
done();



module.exports = Chunker, AudioThing ;


Edit 2: Solved! For future reference, here are the utility functions I wrote to decode/encode the audio data:



function decodeBuffer (buffer) 
return Array.from(
length: buffer.length / 2 ,
(v, i) => buffer.readInt16LE(i * 2) / (2 ** 15)
);


function encodeArray (array)
const buf = Buffer.alloc(array.length * 2);
for (let i = 0; i < array.length; i++)
buf.writeInt16LE(array[i] * (2 ** 15), i * 2);

return buf;










share|improve this question
























  • An MCVE would go a long way here. Just a short listing with just the necessary code and maybe even a "suitable" example URL ( though presumably any YouTube URL should suffice ). If you provide the minimal code to reproduce, then it's a lot easier for others to debug the process.

    – Neil Lunn
    Nov 14 '18 at 3:25











  • @NeilLunn Thanks for the suggestion... not sure how easily I could get a live example up, but I've edited my question to include the bulk of my code thus far.

    – Matt Diamond
    Nov 14 '18 at 3:33












  • Does not need to be "live", but simply enough code to "reproduce the problem". Also "snippets" are meant for code that does in fact run live in the browser. The button next to that one on the editor does normal code block indentation

    – Neil Lunn
    Nov 14 '18 at 3:39











  • Ah sorry, thanks for fixing it

    – Matt Diamond
    Nov 14 '18 at 3:40















1















I'm working on a personal project that involves retrieving audio from YouTube, manipulating the audio, and streaming the result to the browser. So far I have the first and last steps down, but the middle is proving a challenge.



Thanks to the youtube-audio-stream package, getting the audio was easy. I wanted to manipulate the raw audio samples, so I followed their README example and piped the stream into a Decoder from the lame package.



I threw together a couple stream transforms... one to merge incoming chunks together until a size threshold was met, and the other one to actually do something with those chunks. At the end of the pipeline, I added a wav writer (which adds a WAV header so the browser isn't confused about the raw data coming in).



This actually results in normal audio output if my audio transform just passes along the chunks without any modification. So I know that the pipeline itself isn't broken. But for some reason, performing the following operation results in garbled noise:



chunk.reverse();


(This isn't the ultimate goal -- that involves FFT -- but I figured reversing audio chunks was a good operation to start with.)



I expected this to transform the stream into reversed fragments of sound, but instead it distorted it beyond recognition. I know that Node.js Buffers are Uint8Arrays, so I'm wondering if each sample is stored as 4 separate 8-bit integers. But I tried doing something like this:



const arr = Float32Array.from(chunk);
this.push(new Buffer(arr.reverse()));


and it's still garbled. I also tried writing a loop that used Buffer.readFloatLE and Buffer.writeFloatLE, but that didn't behave as expected either. What am I missing here? How can I retrieve and set audio sample data in a Node.js Buffer?



Edit: Adding example code (I'm running this locally as a microservice using micro):



index.js



const stream = require('youtube-audio-stream');
const wav = require('wav');
const decoder = require('lame').Decoder;
const Chunker, AudioThing = require('./transforms');

module.exports = (req, res) =>
const url = 'https://www.youtube.com/watch?v=-L7IdUqaZxo';
res.setHeader('Content-Type', 'audio/wav');
return stream(url)
.pipe(decoder())
.pipe(new Chunker(2 ** 16))
.pipe(new AudioThing())
.pipe(new wav.Writer());



transforms.js



const Transform = require('stream');

class Chunker extends Transform
constructor(threshold)
super();
this.size = 0;
this.chunks = ;
this.threshold = threshold;


_transform(chunk, encoding, done)
this.size += chunk.length;
this.chunks.push(chunk);
if (this.size >= this.threshold)
this.push(Buffer.concat(this.chunks, this.size));
this.chunks = ;
this.size = 0;

done();



class AudioThing extends Transform
_transform(chunk, encoding, done)
this.push(chunk.reverse());
done();



module.exports = Chunker, AudioThing ;


Edit 2: Solved! For future reference, here are the utility functions I wrote to decode/encode the audio data:



function decodeBuffer (buffer) 
return Array.from(
length: buffer.length / 2 ,
(v, i) => buffer.readInt16LE(i * 2) / (2 ** 15)
);


function encodeArray (array)
const buf = Buffer.alloc(array.length * 2);
for (let i = 0; i < array.length; i++)
buf.writeInt16LE(array[i] * (2 ** 15), i * 2);

return buf;










share|improve this question
























  • An MCVE would go a long way here. Just a short listing with just the necessary code and maybe even a "suitable" example URL ( though presumably any YouTube URL should suffice ). If you provide the minimal code to reproduce, then it's a lot easier for others to debug the process.

    – Neil Lunn
    Nov 14 '18 at 3:25











  • @NeilLunn Thanks for the suggestion... not sure how easily I could get a live example up, but I've edited my question to include the bulk of my code thus far.

    – Matt Diamond
    Nov 14 '18 at 3:33












  • Does not need to be "live", but simply enough code to "reproduce the problem". Also "snippets" are meant for code that does in fact run live in the browser. The button next to that one on the editor does normal code block indentation

    – Neil Lunn
    Nov 14 '18 at 3:39











  • Ah sorry, thanks for fixing it

    – Matt Diamond
    Nov 14 '18 at 3:40













1












1








1








I'm working on a personal project that involves retrieving audio from YouTube, manipulating the audio, and streaming the result to the browser. So far I have the first and last steps down, but the middle is proving a challenge.



Thanks to the youtube-audio-stream package, getting the audio was easy. I wanted to manipulate the raw audio samples, so I followed their README example and piped the stream into a Decoder from the lame package.



I threw together a couple stream transforms... one to merge incoming chunks together until a size threshold was met, and the other one to actually do something with those chunks. At the end of the pipeline, I added a wav writer (which adds a WAV header so the browser isn't confused about the raw data coming in).



This actually results in normal audio output if my audio transform just passes along the chunks without any modification. So I know that the pipeline itself isn't broken. But for some reason, performing the following operation results in garbled noise:



chunk.reverse();


(This isn't the ultimate goal -- that involves FFT -- but I figured reversing audio chunks was a good operation to start with.)



I expected this to transform the stream into reversed fragments of sound, but instead it distorted it beyond recognition. I know that Node.js Buffers are Uint8Arrays, so I'm wondering if each sample is stored as 4 separate 8-bit integers. But I tried doing something like this:



const arr = Float32Array.from(chunk);
this.push(new Buffer(arr.reverse()));


and it's still garbled. I also tried writing a loop that used Buffer.readFloatLE and Buffer.writeFloatLE, but that didn't behave as expected either. What am I missing here? How can I retrieve and set audio sample data in a Node.js Buffer?



Edit: Adding example code (I'm running this locally as a microservice using micro):



index.js



const stream = require('youtube-audio-stream');
const wav = require('wav');
const decoder = require('lame').Decoder;
const Chunker, AudioThing = require('./transforms');

module.exports = (req, res) =>
const url = 'https://www.youtube.com/watch?v=-L7IdUqaZxo';
res.setHeader('Content-Type', 'audio/wav');
return stream(url)
.pipe(decoder())
.pipe(new Chunker(2 ** 16))
.pipe(new AudioThing())
.pipe(new wav.Writer());



transforms.js



const Transform = require('stream');

class Chunker extends Transform
constructor(threshold)
super();
this.size = 0;
this.chunks = ;
this.threshold = threshold;


_transform(chunk, encoding, done)
this.size += chunk.length;
this.chunks.push(chunk);
if (this.size >= this.threshold)
this.push(Buffer.concat(this.chunks, this.size));
this.chunks = ;
this.size = 0;

done();



class AudioThing extends Transform
_transform(chunk, encoding, done)
this.push(chunk.reverse());
done();



module.exports = Chunker, AudioThing ;


Edit 2: Solved! For future reference, here are the utility functions I wrote to decode/encode the audio data:



function decodeBuffer (buffer) 
return Array.from(
length: buffer.length / 2 ,
(v, i) => buffer.readInt16LE(i * 2) / (2 ** 15)
);


function encodeArray (array)
const buf = Buffer.alloc(array.length * 2);
for (let i = 0; i < array.length; i++)
buf.writeInt16LE(array[i] * (2 ** 15), i * 2);

return buf;










share|improve this question
















I'm working on a personal project that involves retrieving audio from YouTube, manipulating the audio, and streaming the result to the browser. So far I have the first and last steps down, but the middle is proving a challenge.



Thanks to the youtube-audio-stream package, getting the audio was easy. I wanted to manipulate the raw audio samples, so I followed their README example and piped the stream into a Decoder from the lame package.



I threw together a couple stream transforms... one to merge incoming chunks together until a size threshold was met, and the other one to actually do something with those chunks. At the end of the pipeline, I added a wav writer (which adds a WAV header so the browser isn't confused about the raw data coming in).



This actually results in normal audio output if my audio transform just passes along the chunks without any modification. So I know that the pipeline itself isn't broken. But for some reason, performing the following operation results in garbled noise:



chunk.reverse();


(This isn't the ultimate goal -- that involves FFT -- but I figured reversing audio chunks was a good operation to start with.)



I expected this to transform the stream into reversed fragments of sound, but instead it distorted it beyond recognition. I know that Node.js Buffers are Uint8Arrays, so I'm wondering if each sample is stored as 4 separate 8-bit integers. But I tried doing something like this:



const arr = Float32Array.from(chunk);
this.push(new Buffer(arr.reverse()));


and it's still garbled. I also tried writing a loop that used Buffer.readFloatLE and Buffer.writeFloatLE, but that didn't behave as expected either. What am I missing here? How can I retrieve and set audio sample data in a Node.js Buffer?



Edit: Adding example code (I'm running this locally as a microservice using micro):



index.js



const stream = require('youtube-audio-stream');
const wav = require('wav');
const decoder = require('lame').Decoder;
const Chunker, AudioThing = require('./transforms');

module.exports = (req, res) =>
const url = 'https://www.youtube.com/watch?v=-L7IdUqaZxo';
res.setHeader('Content-Type', 'audio/wav');
return stream(url)
.pipe(decoder())
.pipe(new Chunker(2 ** 16))
.pipe(new AudioThing())
.pipe(new wav.Writer());



transforms.js



const Transform = require('stream');

class Chunker extends Transform
constructor(threshold)
super();
this.size = 0;
this.chunks = ;
this.threshold = threshold;


_transform(chunk, encoding, done)
this.size += chunk.length;
this.chunks.push(chunk);
if (this.size >= this.threshold)
this.push(Buffer.concat(this.chunks, this.size));
this.chunks = ;
this.size = 0;

done();



class AudioThing extends Transform
_transform(chunk, encoding, done)
this.push(chunk.reverse());
done();



module.exports = Chunker, AudioThing ;


Edit 2: Solved! For future reference, here are the utility functions I wrote to decode/encode the audio data:



function decodeBuffer (buffer) 
return Array.from(
length: buffer.length / 2 ,
(v, i) => buffer.readInt16LE(i * 2) / (2 ** 15)
);


function encodeArray (array)
const buf = Buffer.alloc(array.length * 2);
for (let i = 0; i < array.length; i++)
buf.writeInt16LE(array[i] * (2 ** 15), i * 2);

return buf;







node.js audio






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 14 '18 at 4:46







Matt Diamond

















asked Nov 14 '18 at 3:20









Matt DiamondMatt Diamond

10.2k32233




10.2k32233












  • An MCVE would go a long way here. Just a short listing with just the necessary code and maybe even a "suitable" example URL ( though presumably any YouTube URL should suffice ). If you provide the minimal code to reproduce, then it's a lot easier for others to debug the process.

    – Neil Lunn
    Nov 14 '18 at 3:25











  • @NeilLunn Thanks for the suggestion... not sure how easily I could get a live example up, but I've edited my question to include the bulk of my code thus far.

    – Matt Diamond
    Nov 14 '18 at 3:33












  • Does not need to be "live", but simply enough code to "reproduce the problem". Also "snippets" are meant for code that does in fact run live in the browser. The button next to that one on the editor does normal code block indentation

    – Neil Lunn
    Nov 14 '18 at 3:39











  • Ah sorry, thanks for fixing it

    – Matt Diamond
    Nov 14 '18 at 3:40

















  • An MCVE would go a long way here. Just a short listing with just the necessary code and maybe even a "suitable" example URL ( though presumably any YouTube URL should suffice ). If you provide the minimal code to reproduce, then it's a lot easier for others to debug the process.

    – Neil Lunn
    Nov 14 '18 at 3:25











  • @NeilLunn Thanks for the suggestion... not sure how easily I could get a live example up, but I've edited my question to include the bulk of my code thus far.

    – Matt Diamond
    Nov 14 '18 at 3:33












  • Does not need to be "live", but simply enough code to "reproduce the problem". Also "snippets" are meant for code that does in fact run live in the browser. The button next to that one on the editor does normal code block indentation

    – Neil Lunn
    Nov 14 '18 at 3:39











  • Ah sorry, thanks for fixing it

    – Matt Diamond
    Nov 14 '18 at 3:40
















An MCVE would go a long way here. Just a short listing with just the necessary code and maybe even a "suitable" example URL ( though presumably any YouTube URL should suffice ). If you provide the minimal code to reproduce, then it's a lot easier for others to debug the process.

– Neil Lunn
Nov 14 '18 at 3:25





An MCVE would go a long way here. Just a short listing with just the necessary code and maybe even a "suitable" example URL ( though presumably any YouTube URL should suffice ). If you provide the minimal code to reproduce, then it's a lot easier for others to debug the process.

– Neil Lunn
Nov 14 '18 at 3:25













@NeilLunn Thanks for the suggestion... not sure how easily I could get a live example up, but I've edited my question to include the bulk of my code thus far.

– Matt Diamond
Nov 14 '18 at 3:33






@NeilLunn Thanks for the suggestion... not sure how easily I could get a live example up, but I've edited my question to include the bulk of my code thus far.

– Matt Diamond
Nov 14 '18 at 3:33














Does not need to be "live", but simply enough code to "reproduce the problem". Also "snippets" are meant for code that does in fact run live in the browser. The button next to that one on the editor does normal code block indentation

– Neil Lunn
Nov 14 '18 at 3:39





Does not need to be "live", but simply enough code to "reproduce the problem". Also "snippets" are meant for code that does in fact run live in the browser. The button next to that one on the editor does normal code block indentation

– Neil Lunn
Nov 14 '18 at 3:39













Ah sorry, thanks for fixing it

– Matt Diamond
Nov 14 '18 at 3:40





Ah sorry, thanks for fixing it

– Matt Diamond
Nov 14 '18 at 3:40












1 Answer
1






active

oldest

votes


















1














You can't simply reverse the byte array. As you suspected, samples are going to span more than one byte.



It seems plausible that you have the sample format wrong. It's probably not 32-bit float, but is probably signed 16-bit integers. This isn't documented well, but if you dig into the source code for node-lame, you find this:



if (ret == MPG123_NEW_FORMAT) 
var format = binding.mpg123_getformat(mh);
debug('new format: %j', format);
self.emit('format', format);
return read();



It looks like the underlying MPG123 can return PCM in several formats:



 if (ret == MPG123_OK) {
Local<Object> o = Nan::New<Object>();
Nan::Set(o, Nan::New<String>("raw_encoding").ToLocalChecked(), Nan::New<Number>(encoding));
Nan::Set(o, Nan::New<String>("sampleRate").ToLocalChecked(), Nan::New<Number>(rate));
Nan::Set(o, Nan::New<String>("channels").ToLocalChecked(), Nan::New<Number>(channels));
Nan::Set(o, Nan::New<String>("signed").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_SIGNED));
Nan::Set(o, Nan::New<String>("float").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_FLOAT));
Nan::Set(o, Nan::New<String>("ulaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ULAW_8));
Nan::Set(o, Nan::New<String>("alaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ALAW_8));
if (encoding & MPG123_ENC_8)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(8));
else if (encoding & MPG123_ENC_16)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(16));
else if (encoding & MPG123_ENC_24)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(24));
else if (encoding & MPG123_ENC_32 || encoding & MPG123_ENC_FLOAT_32)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(32));
else if (encoding & MPG123_ENC_FLOAT_64)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(64));
rtn = o;


I would try your looping technique again to reverse the samples while keeping the bytes in each sample in-tact, but try this with different sample sizes. Start with 16-bit signed, little-endian.






share|improve this answer























  • Holy crap, I think that actually worked. My secondary question is that of performance... is there a faster way to convert this besides calling buffer.readInt16LE in a loop? Can I use a TypedArray?

    – Matt Diamond
    Nov 14 '18 at 4:28












  • @MattDiamond First, make sure you've allocated the target buffer ahead of time. Next, try profiling buffer.copy(). You don't actually need to read those byte values, you just need to copy them to a new buffer in a different order. So, use .copy() in lengths of 2. Just be sure to profile it, since this is a tight loop with a lot of iterations.

    – Brad
    Nov 14 '18 at 4:32











  • Okay cool. I think this counts as solved, even if I have some more fine-tuning to do... thanks!

    – Matt Diamond
    Nov 14 '18 at 4:40










Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53292716%2fmanipulating-a-buffer-of-raw-pcm-data-in-nodejs%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









1














You can't simply reverse the byte array. As you suspected, samples are going to span more than one byte.



It seems plausible that you have the sample format wrong. It's probably not 32-bit float, but is probably signed 16-bit integers. This isn't documented well, but if you dig into the source code for node-lame, you find this:



if (ret == MPG123_NEW_FORMAT) 
var format = binding.mpg123_getformat(mh);
debug('new format: %j', format);
self.emit('format', format);
return read();



It looks like the underlying MPG123 can return PCM in several formats:



 if (ret == MPG123_OK) {
Local<Object> o = Nan::New<Object>();
Nan::Set(o, Nan::New<String>("raw_encoding").ToLocalChecked(), Nan::New<Number>(encoding));
Nan::Set(o, Nan::New<String>("sampleRate").ToLocalChecked(), Nan::New<Number>(rate));
Nan::Set(o, Nan::New<String>("channels").ToLocalChecked(), Nan::New<Number>(channels));
Nan::Set(o, Nan::New<String>("signed").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_SIGNED));
Nan::Set(o, Nan::New<String>("float").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_FLOAT));
Nan::Set(o, Nan::New<String>("ulaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ULAW_8));
Nan::Set(o, Nan::New<String>("alaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ALAW_8));
if (encoding & MPG123_ENC_8)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(8));
else if (encoding & MPG123_ENC_16)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(16));
else if (encoding & MPG123_ENC_24)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(24));
else if (encoding & MPG123_ENC_32 || encoding & MPG123_ENC_FLOAT_32)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(32));
else if (encoding & MPG123_ENC_FLOAT_64)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(64));
rtn = o;


I would try your looping technique again to reverse the samples while keeping the bytes in each sample in-tact, but try this with different sample sizes. Start with 16-bit signed, little-endian.






share|improve this answer























  • Holy crap, I think that actually worked. My secondary question is that of performance... is there a faster way to convert this besides calling buffer.readInt16LE in a loop? Can I use a TypedArray?

    – Matt Diamond
    Nov 14 '18 at 4:28












  • @MattDiamond First, make sure you've allocated the target buffer ahead of time. Next, try profiling buffer.copy(). You don't actually need to read those byte values, you just need to copy them to a new buffer in a different order. So, use .copy() in lengths of 2. Just be sure to profile it, since this is a tight loop with a lot of iterations.

    – Brad
    Nov 14 '18 at 4:32











  • Okay cool. I think this counts as solved, even if I have some more fine-tuning to do... thanks!

    – Matt Diamond
    Nov 14 '18 at 4:40















1














You can't simply reverse the byte array. As you suspected, samples are going to span more than one byte.



It seems plausible that you have the sample format wrong. It's probably not 32-bit float, but is probably signed 16-bit integers. This isn't documented well, but if you dig into the source code for node-lame, you find this:



if (ret == MPG123_NEW_FORMAT) 
var format = binding.mpg123_getformat(mh);
debug('new format: %j', format);
self.emit('format', format);
return read();



It looks like the underlying MPG123 can return PCM in several formats:



 if (ret == MPG123_OK) {
Local<Object> o = Nan::New<Object>();
Nan::Set(o, Nan::New<String>("raw_encoding").ToLocalChecked(), Nan::New<Number>(encoding));
Nan::Set(o, Nan::New<String>("sampleRate").ToLocalChecked(), Nan::New<Number>(rate));
Nan::Set(o, Nan::New<String>("channels").ToLocalChecked(), Nan::New<Number>(channels));
Nan::Set(o, Nan::New<String>("signed").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_SIGNED));
Nan::Set(o, Nan::New<String>("float").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_FLOAT));
Nan::Set(o, Nan::New<String>("ulaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ULAW_8));
Nan::Set(o, Nan::New<String>("alaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ALAW_8));
if (encoding & MPG123_ENC_8)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(8));
else if (encoding & MPG123_ENC_16)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(16));
else if (encoding & MPG123_ENC_24)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(24));
else if (encoding & MPG123_ENC_32 || encoding & MPG123_ENC_FLOAT_32)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(32));
else if (encoding & MPG123_ENC_FLOAT_64)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(64));
rtn = o;


I would try your looping technique again to reverse the samples while keeping the bytes in each sample in-tact, but try this with different sample sizes. Start with 16-bit signed, little-endian.






share|improve this answer























  • Holy crap, I think that actually worked. My secondary question is that of performance... is there a faster way to convert this besides calling buffer.readInt16LE in a loop? Can I use a TypedArray?

    – Matt Diamond
    Nov 14 '18 at 4:28












  • @MattDiamond First, make sure you've allocated the target buffer ahead of time. Next, try profiling buffer.copy(). You don't actually need to read those byte values, you just need to copy them to a new buffer in a different order. So, use .copy() in lengths of 2. Just be sure to profile it, since this is a tight loop with a lot of iterations.

    – Brad
    Nov 14 '18 at 4:32











  • Okay cool. I think this counts as solved, even if I have some more fine-tuning to do... thanks!

    – Matt Diamond
    Nov 14 '18 at 4:40













1












1








1







You can't simply reverse the byte array. As you suspected, samples are going to span more than one byte.



It seems plausible that you have the sample format wrong. It's probably not 32-bit float, but is probably signed 16-bit integers. This isn't documented well, but if you dig into the source code for node-lame, you find this:



if (ret == MPG123_NEW_FORMAT) 
var format = binding.mpg123_getformat(mh);
debug('new format: %j', format);
self.emit('format', format);
return read();



It looks like the underlying MPG123 can return PCM in several formats:



 if (ret == MPG123_OK) {
Local<Object> o = Nan::New<Object>();
Nan::Set(o, Nan::New<String>("raw_encoding").ToLocalChecked(), Nan::New<Number>(encoding));
Nan::Set(o, Nan::New<String>("sampleRate").ToLocalChecked(), Nan::New<Number>(rate));
Nan::Set(o, Nan::New<String>("channels").ToLocalChecked(), Nan::New<Number>(channels));
Nan::Set(o, Nan::New<String>("signed").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_SIGNED));
Nan::Set(o, Nan::New<String>("float").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_FLOAT));
Nan::Set(o, Nan::New<String>("ulaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ULAW_8));
Nan::Set(o, Nan::New<String>("alaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ALAW_8));
if (encoding & MPG123_ENC_8)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(8));
else if (encoding & MPG123_ENC_16)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(16));
else if (encoding & MPG123_ENC_24)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(24));
else if (encoding & MPG123_ENC_32 || encoding & MPG123_ENC_FLOAT_32)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(32));
else if (encoding & MPG123_ENC_FLOAT_64)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(64));
rtn = o;


I would try your looping technique again to reverse the samples while keeping the bytes in each sample in-tact, but try this with different sample sizes. Start with 16-bit signed, little-endian.






share|improve this answer













You can't simply reverse the byte array. As you suspected, samples are going to span more than one byte.



It seems plausible that you have the sample format wrong. It's probably not 32-bit float, but is probably signed 16-bit integers. This isn't documented well, but if you dig into the source code for node-lame, you find this:



if (ret == MPG123_NEW_FORMAT) 
var format = binding.mpg123_getformat(mh);
debug('new format: %j', format);
self.emit('format', format);
return read();



It looks like the underlying MPG123 can return PCM in several formats:



 if (ret == MPG123_OK) {
Local<Object> o = Nan::New<Object>();
Nan::Set(o, Nan::New<String>("raw_encoding").ToLocalChecked(), Nan::New<Number>(encoding));
Nan::Set(o, Nan::New<String>("sampleRate").ToLocalChecked(), Nan::New<Number>(rate));
Nan::Set(o, Nan::New<String>("channels").ToLocalChecked(), Nan::New<Number>(channels));
Nan::Set(o, Nan::New<String>("signed").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_SIGNED));
Nan::Set(o, Nan::New<String>("float").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_FLOAT));
Nan::Set(o, Nan::New<String>("ulaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ULAW_8));
Nan::Set(o, Nan::New<String>("alaw").ToLocalChecked(), Nan::New<Boolean>(encoding & MPG123_ENC_ALAW_8));
if (encoding & MPG123_ENC_8)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(8));
else if (encoding & MPG123_ENC_16)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(16));
else if (encoding & MPG123_ENC_24)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(24));
else if (encoding & MPG123_ENC_32 || encoding & MPG123_ENC_FLOAT_32)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(32));
else if (encoding & MPG123_ENC_FLOAT_64)
Nan::Set(o, Nan::New<String>("bitDepth").ToLocalChecked(), Nan::New<Integer>(64));
rtn = o;


I would try your looping technique again to reverse the samples while keeping the bytes in each sample in-tact, but try this with different sample sizes. Start with 16-bit signed, little-endian.







share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 14 '18 at 4:01









BradBrad

115k27230393




115k27230393












  • Holy crap, I think that actually worked. My secondary question is that of performance... is there a faster way to convert this besides calling buffer.readInt16LE in a loop? Can I use a TypedArray?

    – Matt Diamond
    Nov 14 '18 at 4:28












  • @MattDiamond First, make sure you've allocated the target buffer ahead of time. Next, try profiling buffer.copy(). You don't actually need to read those byte values, you just need to copy them to a new buffer in a different order. So, use .copy() in lengths of 2. Just be sure to profile it, since this is a tight loop with a lot of iterations.

    – Brad
    Nov 14 '18 at 4:32











  • Okay cool. I think this counts as solved, even if I have some more fine-tuning to do... thanks!

    – Matt Diamond
    Nov 14 '18 at 4:40

















  • Holy crap, I think that actually worked. My secondary question is that of performance... is there a faster way to convert this besides calling buffer.readInt16LE in a loop? Can I use a TypedArray?

    – Matt Diamond
    Nov 14 '18 at 4:28












  • @MattDiamond First, make sure you've allocated the target buffer ahead of time. Next, try profiling buffer.copy(). You don't actually need to read those byte values, you just need to copy them to a new buffer in a different order. So, use .copy() in lengths of 2. Just be sure to profile it, since this is a tight loop with a lot of iterations.

    – Brad
    Nov 14 '18 at 4:32











  • Okay cool. I think this counts as solved, even if I have some more fine-tuning to do... thanks!

    – Matt Diamond
    Nov 14 '18 at 4:40
















Holy crap, I think that actually worked. My secondary question is that of performance... is there a faster way to convert this besides calling buffer.readInt16LE in a loop? Can I use a TypedArray?

– Matt Diamond
Nov 14 '18 at 4:28






Holy crap, I think that actually worked. My secondary question is that of performance... is there a faster way to convert this besides calling buffer.readInt16LE in a loop? Can I use a TypedArray?

– Matt Diamond
Nov 14 '18 at 4:28














@MattDiamond First, make sure you've allocated the target buffer ahead of time. Next, try profiling buffer.copy(). You don't actually need to read those byte values, you just need to copy them to a new buffer in a different order. So, use .copy() in lengths of 2. Just be sure to profile it, since this is a tight loop with a lot of iterations.

– Brad
Nov 14 '18 at 4:32





@MattDiamond First, make sure you've allocated the target buffer ahead of time. Next, try profiling buffer.copy(). You don't actually need to read those byte values, you just need to copy them to a new buffer in a different order. So, use .copy() in lengths of 2. Just be sure to profile it, since this is a tight loop with a lot of iterations.

– Brad
Nov 14 '18 at 4:32













Okay cool. I think this counts as solved, even if I have some more fine-tuning to do... thanks!

– Matt Diamond
Nov 14 '18 at 4:40





Okay cool. I think this counts as solved, even if I have some more fine-tuning to do... thanks!

– Matt Diamond
Nov 14 '18 at 4:40

















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53292716%2fmanipulating-a-buffer-of-raw-pcm-data-in-nodejs%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







這個網誌中的熱門文章

Barbados

How to read a connectionString WITH PROVIDER in .NET Core?

Node.js Script on GitHub Pages or Amazon S3