distopik / node-audio-asio

incubation -- Node interface to Steinberg ASIO
MIT License
27 stars 6 forks source link

How To Access Asio Driver Within My Nodejs App? #2

Open titus-shoats opened 7 years ago

titus-shoats commented 7 years ago

I'm building a Music Sequencer using nodejs, using the Web Audio API, but im running into latency issues. Would node-audio-asio help me out with this? For example how would I get access to the the users asio driver? Usually when working in a DAW it pops up as a green notification in the taskbar. Can you give me any advice with this?

`const nodeAsio = require('node-audio-asio'), samplesPerBlock = 256, bitsPerSample = 24, sampleRate = 44100, channelBufferLen = samplesPerBlock * bitsPerSample / 8, buf = new Buffer(channelBufferLen), initial = [buf, buf]

const asioErr = nodeAsio.initAsio({ driver: 'ASIO4ALL v2', sampleRate, bitsPerSample, samplesPerBlock, endianess: 'little', inputChannels: [0], // first channel outputChannels: [0, 1] // first two channels }); nodeAsio.start(initial, function(bufs) { // bufs[0] contains the recorded samples // TODO: what we return here are will be // propagated to the output return [buf, buf] })

//Music sequencer code...., but how to access ASIO Driver from here??

nodeAsio.stop() nodeAsio.deInit()`

baadc0de commented 7 years ago

Hi Titus.

This project is not actively maintained anymore, but it should serve as a good starting point if you are interested in low-latency audio access and can fix the rough edges of the C++ code here and there.

To answer your question about using the library, the ASIO driver is going to call a function that you must provide for each buffer switch, usually about 150-200 times a second. It depends on the block size and sample rate that you set up in the options of initAsio.

The only parameter that your function gets is an array of nodejs Buffers, one for each enabled recording channel (in case you want to use the user's analog audio input, for example a microphone). If there are no channels enabled for recording, it should be an empty array.

What your function is expected to return is an array of Buffers, one for each enabled output channel. For example let's say you enabled the first two channels for output, usually those get routed by the ASIO driver to the default left and right outputs that the user has, like headphones or monitors. So you should return something like [leftData, rightData] where each of those data buffers contains samples for the ASIO driver to send to the output of the sound card.

Hope you will get it to work, I'm very interested to hear more about where you are planning to use it.

titus-shoats commented 7 years ago

Thanks so much for the advice. I'm building a music sequencer via nodejs/desktop and my plans are to be able give the user the option to choose their ASIO driver like below in the pic***

asio4all_audio

Lets say in my app I have the following buffer using the Web Audio API

var bufferLoader = new BufferLoader(
                    context,
                    [
                        'Clap.wav',
                        'Kick_02.wav'
                    ],
                    play
            );
 bufferLoader.load();

function play(){
   var source1 = context.createBufferSource();
    source1.buffer = bufferList[0];
    source1.connect(context.destination);
   source.start(0);
}

I guess my question is it possible to give the user the option to choose their ASIO Driver by the given constant initializtion below

const asioErr = nodeAsio.initAsio({
driver: 'ASIO4ALL v2',
sampleRate,
bitsPerSample,
samplesPerBlock,
endianess: 'little',
inputChannels: [0], // first channel
outputChannels: [0, 1] // first two channels
});

and then send my actual buffers to through the sound card for low latency?? Thanks

baadc0de commented 7 years ago

Hi. Thanks for making it more clear. I'm not well versed with the Web Audio API, so my comment may not apply directly. Usually if you want to work with ASIO, be it in C++ or whatever language/framework, the ASIO thread "takes over" the audio tasks and calls a user supplied function. It's the same in node-audio-asio, the ASIO driver takes over and calls you when it wants buffers not the other way around.

What you could do is make a queue of all buffers that should be output and just return the first one from the queue each time the callback is called. Then fill up the queue in whatever way you are used to right now, just make sure you are doing it fast enough so that the ASIO thread does not run out of buffers or you will have buffer underruns which sound horrible and may damage speakers / equipment.

Hope this helps!

Andersama commented 6 years ago

Not exactly familiar with node gyp*, but I think he's just asking if you gave access to the function getDriverNames from asiodrivers.h

So far as I understand node-gyp you've got this bit*

void init(Local<Object> exports){
    NODE_SET_METHOD(exports, "init", AsioInit);
    NODE_SET_METHOD(exports, "stop", AsioStop);
    NODE_SET_METHOD(exports, "deInit", AsioDeInit);
    NODE_SET_METHOD(exports, "start", AsioStart);
}
NODE_MODULE(nodeAudioAsio, init)

which seems to tie in some of asio w/ some intermediate functions...

So tldr he's probably looking for something like this:

//Pretty sure this needs to be included somewhere up at the top of Source.cpp*
/*
long getDriverNames(char **names, long maxDrivers){
  if(!asioDrivers)
    asioDrivers = new AsioDrivers();
  if(asioDrivers)
    return asioDrivers->getDriverNames(names,maxDrivers);
  return false;
}
*/

//I'm not sure how you'd go about writing the function prototype here because all of your examples
//are just void functions which aren't meant to anything...maybe that's a limitation of node gyp (I don't know I'm not familiar with it yet)...but presumably here we're returning that list
//of driver names from getDriverNames*
//just following the example of nodeAsio.start({},function(bufs){ ... } it looks like you just printf'd to console in c++ and that ended up in the bufs[0]?
void AsioList(const FunctionCallbackInfo<Value>& args){
    //printf( ... getDriverNames() ... )
}

void init(Local<Object> exports){
        NODE_SET_METHOD(exports, "list", AsioList);
    NODE_SET_METHOD(exports, "init", AsioInit);
    NODE_SET_METHOD(exports, "stop", AsioStop);
    NODE_SET_METHOD(exports, "deInit", AsioDeInit);
    NODE_SET_METHOD(exports, "start", AsioStart);
}
NODE_MODULE(nodeAudioAsio, init);

I'll dig more into node gyp and maybe I'll fork the repo. I was hoping to make a mixer of sorts with electron and I think this'd be the thing to use.

baadc0de commented 6 years ago

Hi Alex,

yes indeed, there should be a way to iterate over the names of the devices. Unfortunately, I am without a Windows/ASIO workstation right now, feel free to fork or submit a PR.