Psycokwet / bassaudio-updated

Other
10 stars 2 forks source link

Bassaudio-updated

A all in one bass library wrapper, maintained and mostly tested. This package come with its bass libraries files dependencies, and use the right one if your are on linux 32/64 or Windows 32/64 or macOS. However, you must check the licensing on un4seen. For a lighter version of this package, without the bass files, checkout bassaudio-updated-light

Tested on

macos-latest windows-latest ubuntu-latest

With

npm version npm version npm version npm version

Tests results :

github build status Coverage Status

Documentation :

Examples partials examples features structs callbacks Official documentation

Others :

install size npm downloads npm downloads MIT License

Disclaimer

This package has not been fully tested. Some features may not be fully functionning, if you encounter such a feature, please email me with your traces. I'm still working on it.

Also, I'm not in anyway linked to the bass library. I just like their lib and want it to be accessible to the world of node.

Announcement

The necessaries dll a part from the eventuals encoder ones are included in this library. For MacOs, Windows 32/64bits, and linux 32/64 bits. If you encounter any issues while using it, please let me know.

As the dll are already included, you need to check for the un4seen licence prior to using this package. You may have a free licence depending on the usage you are making, and as mentionned down there, its a very great library, so check it out ! There is also some documentation, that I did not include here, that you can get directly with the original dynamic library files

If you want a lighter version without superfluous binaries library included, please, check out bassaudio-updated-light

Do you need more features?

There is two way to use functions that I did not include in this wrapper. Either you send me a mail asking me to officially add it, which I will, either you "patch it" while you use the wrapper as explained here

Un4Seen Bass Audio Library Wrapper

Bass Audio library is the best audio library to play, edit, convert, stream etc. this wrapper wraps most of the "audio playback" features using ffi-napi, ref-napi, ref-struct-di, ref-array-di. It uses os to determines which dynamic library are needed.

ffi-napi enables to call c library methods, properties, callbacks etc.

Compatible with?

Included binaries library files for Windows 64/32bits, Linux 64/32bits, and macOs. Not fully tested yet, so report any encountered issues please.

Note on node 14

There is ongoing issues with node 14.9.0 and later that may cause segfault when loading a library file, that are outside my skills. I strongly discourage you from using node 14.9.0 and later until this problem is stabilized. If you want to find more info about this issue, here is a first lead : https://github.com/node-ffi-napi/node-ffi-napi/issues/99

Note on documentation

Bass documentation is available here : http://www.un4seen.com/doc/ You can also see this version : http://bass.radio42.com/help/

You can find direct link from wrapped content, to un4seen documentation here

There is also some documentation directly in the README files accompanying the library files on the un4seen website.

Let me now if there is other useful documentation that I can share here. :)

Original library files, bass community, licensing, other apis and more is available here : http://www.un4seen.com.

You can see more code examples from the wrapper original creator, Serkanp, in its git repositories linked here. The example I have build here are fully adapted with my version of the wrapper.

Partials examples

Wrapper parameters

// There is a diversity of parameter accepted by this wrapper, here is a full list of them :

var basslib = getBass({
  silent: true, //there is a few console log that may appear to indicate that everything is going smoothly, if you don't want to see it, you can set it to silent
  generatedFfiFunDeclaration: {
    bass: {
      // addon id
      ffiFunDeclaration: {
        BASS_ORIGINAL_FUN_NAME: ["bool", []],
      },
    },
    // Order of added library might be important if a lib file is depending of another one.
    webm: {
      // addon id
      ffiFunDeclaration: {
        BASS_ORIGINAL_FUN_NAME: [
          // Original name of the bass function
          "int", // return type of the desired function
          ["string", "float", "double", "long", "pointer", "bool"], // Arguments types
        ],
      },
      path: "path/to/lib/file.dylib/dll/so", // A path must be given to the added addon if it's not one already covered by the wrapper. If it is an addon covered, the path would be ignored
    },
  },
});

Arguments types : You must choose the most relevants types for each arguments. You can see a list of correspondances here.

Addon id : Identification of the addon, two choices :

basic capture and play microphone

var bass = require("bassaudio-updated");
var basslib = new bass();

var init = basslib.BASS_Init(
  -1,
  44100,
  basslib.BASS_Initflags.BASS_DEVICE_STEREO
);
if (init === false) {
  console.log("error at BASS_Init: " + basslib.BASS_ErrorGetCode());
  process.exit();
} else {
  console.log("Bass initialized");
}

basslib.EnableMixer(true);
var sampleRate = 44100;
var stereoChannel = 2;
var mixer = basslib.BASS_Mixer_StreamCreate(
  sampleRate,
  stereoChannel,
  basslib.BASSFlags.BASS_SAMPLE_FLOAT
);

if (mixer === 0) {
  console.log("error at Mixer_StreamCreate: " + basslib.BASS_ErrorGetCode());
  //   process.exit();
} else {
  console.log("Mixer channel: " + mixer);
}

var init = basslib.BASS_RecordInit(-1); //initialise default microphone
if (!init) {
  console.log("error at BASS_RecordInit: " + basslib.BASS_ErrorGetCode());
  process.exit();
}
var micChan = basslib.BASS_RecordStart(44100, 2, 0, null); // create a recording channel with 10ms period
if (!micChan) {
  console.log("error at BASS_RecordStart: " + basslib.BASS_ErrorGetCode());
  process.exit();
}

var isAdded = basslib.BASS_Mixer_StreamAddChannel(
  mixer,
  micChan,
  basslib.BASSFlags.BASS_STREAM_AUTOFREE | basslib.BASSFlags.BASS_MIXER_LIMIT
);
if (!isAdded) {
  console.log(
    "error adding microphone into mixer: " + basslib.BASS_ErrorGetCode()
  );
  process.exit();
}
// BASS_ChannelPlay
var success = basslib.BASS_ChannelPlay(mixer, true);
if (!success) {
  console.log("error at ChannelPlay: " + basslib.BASS_ErrorGetCode());
  process.exit();
}

basic load and play file

var bass = require("bassaudio-updated");
var basslib = new bass();

//get all sound cards
var cards = basslib.getDevices();
console.log("total found sound cards:" + cards.length);
//lets print first sound card's info, find out more inside source code..
//first item in array '[0]' is "no sound", then use the item [1]
//you will see that card isInitialized will be false, because we did not init it yet..
var card = cards[1];
console.log(
  card.name +
    " is enabled:" +
    card.enabled +
    " ,IsDefault:" +
    card.IsDefault +
    " , IsInitialized:" +
    card.IsInitialized +
    " ,typeSpeakers:" +
    card.typeSpeakers
);

// [device],[freq],[flags], -1 is default sound card
var result = basslib.BASS_Init(
  -1,
  44100,
  basslib.BASS_Initflags.BASS_DEVICE_STEREO
);
if (!result) {
  console.log("error init sound card:" + basslib.BASS_ErrorGetCode());
}

console.log("first card is init?:" + basslib.getDevice(1).IsInitialized);

// isMemoryFile,filename,offset,length,flags, returns pointer of file
var chan = basslib.BASS_StreamCreateFile(0, "c:\\mp3\\test.mp3", 0, 0, 0);
if (basslib.BASS_ErrorGetCode() != basslib.BASS_ErrorCode.BASS_OK) {
  console.log("error opening file:" + basslib.BASS_ErrorGetCode());
}

//lets play
//channel, restart, returns  (also there are stop, pause commands)
var success = basslib.BASS_ChannelPlay(chan, -1);
if (!success) {
  console.log("error playing file:" + basslib.BASS_ErrorGetCode());
}

Get Artist

// get artist or other meta data thanks to the Tags add-on
// This example return the artist name or "No artist" if the artist is not set. But you can doo much more with tags.

basslib.EnableTags(true);

var tagsEnabled = basslib.TagsEnabled();
if (tagsEnabled) {
  console.log("Tags enabled");
} else {
  console.log("Tags disabled");
  process.exit();
}

var artist = basslib.TAGS_Read(
  chan,
  basslib.BASS_TAGS_FORMAT_CONDITION.IF_X_THEN_A_IF_NOT_THEN_B(
    basslib.BASS_TAGS_FORMAT_STRINGS.SONG_ARTIST,
    basslib.BASS_TAGS_FORMAT_STRINGS.SONG_ARTIST,
    "No artist"
  )
);

Get Duration

//get the duration, bass returns the total bytes of the channel pointer, then we must convert it to seconds :)
var durationInBytes = basslib.BASS_ChannelGetLength(chan, 0);
var durationInSeconds = basslib.BASS_ChannelBytes2Seconds(
  chan,
  durationInBytes
);

Get Duration Example2

//if stream is active (playing), then get position and duration..
setInterval(function () {
  if (
    basslib.BASS_ChannelIsActive(chan) ==
    basslib.BASS_ChannelIsActiveAttribs.BASS_ACTIVE_PLAYING
  ) {
    var position = basslib.BASS_ChannelBytes2Seconds(
      chan,
      basslib.BASS_ChannelGetPosition(chan, 0)
    );
    var duration = basslib.BASS_ChannelBytes2Seconds(
      chan,
      basslib.BASS_ChannelGetLength(chan, 0)
    );
    console.log(position + " / " + duration);
  } else {
    console.log("stopped");
  }
}, 500);

Get Volume of channel

var ref = require("ref");
//set a float pointer
var volume = ref.alloc("float");
//get the volume
var result = basslib.BASS_ChannelGetAttribute(
  chan,
  basslib.BASS_ChannelAttributes.BASS_ATTRIB_VOL,
  volume
);
//now deref volume to get the value
console.log(ref.deref(volume));

Set Volume

//lets set to 0.3
basslib.BASS_ChannelSetAttribute(
  chan,
  basslib.BASS_ChannelAttributes.BASS_ATTRIB_VOL,
  0.3
);

Get current Position of playback

var positionInBytes = basslib.BASS_ChannelGetPosition(chan, 0);
//now convert it to seconds
var position = basslib.BASS_ChannelBytes2Seconds(chan, positionInBytes);

Set Position

//first get the byte position of desired seconds (ex:to last 10 seconds
var Last10SecondsBytePos = basslib.BASS_ChannelSeconds2Bytes(
  chan,
  durationInSeconds - 10
);
basslib.BASS_ChannelSetPosition(chan, Last10SecondsBytePos);

Is channel playing?

var result = basslib.BASS_ChannelIsActive(chan);
if (result == basslib.BASS_ChannelIsActiveAttribs.BASS_ACTIVE_PLAYING) {
  console.log("channel is playing");
}

sliding

//Lets slide volume to 0 in 3 seconds (3000 milliseconds)
basslib.BASS_ChannelSlideAttribute(
  chan,
  basslib.BASS_ChannelAttributes.BASS_ATTRIB_VOL,
  0,
  3000
);

callback

//lets make a callback when position reaches to 20. seconds.
var Pos20SecondsBytePos = basslib.BASS_ChannelSeconds2Bytes(chan, 20);
var proc20SecondsID = basslib.BASS_ChannelSetSync(
  chan,
  basslib.BASS_ChannelSyncTypes.BASS_SYNC_POS,
  Pos20SecondsBytePos,
  function (handle, channel, data, user) {
    if (handle == proc20SecondsID) {
      console.log("position reached to the 20 seconds..");
    }
  }
);

//lets get the event when the position reaches to end
var procTOENDID = basslib.BASS_ChannelSetSync(
  chan,
  basslib.BASS_ChannelSyncTypes.BASS_SYNC_END,
  0,
  function (handle, channel, data, user) {
    if (handle == procTOENDID) {
      console.log("playback finished..");
    }
  }
);

Vumeter

//lets get vumeter :)
var levels = basslib.BASS_ChannelGetLevel(chan);
//its a 64 bit value, lets get lo and hiwords
var rightlevel = basslib.toFloat64(levels)[0];
var leftlevel = basslib.toFloat64(levels)[1];

close the file

if (
  basslib.BASS_ChannelIsActive(chan) ==
  basslib.BASS_ChannelIsActiveAttribs.BASS_ACTIVE_PLAYING
) {
  //stop the channel
  basslib.BASS_ChannelStop(chan);
}

var result = basslib.BASS_StreamFree(chan);
if (!result) {
  console.log("stream free error:" + basslib.BASS_ErrorGetCode());
}

change sound card

//first check if this sound card is initialized
var soundCardIndex = 1;
var info = basslib.getDevice(soundCardIndex);
if (!info.enabled) {
  console.log("this device is not enabled");
}
if (!info.IsInitialized) {
  var result = basslib.BASS_Init(
    soundCardIndex,
    44100,
    basslib.BASS_Initflags.BASS_DEVICE_STEREO
  );
  if (result != basslib.BASS_ErrorCode.BASS_OK) {
    console.log("error init sound card:" + basslib.BASS_ErrorGetCode());
  }
}
var success = basslib.BASS_ChannelSetDevice(chan, soundCardIndex);
if (!success) {
  console.log("error init sound card:" + basslib.BASS_ErrorGetCode());
}

Info of a channel

var info = basslib.BASS_ChannelGetInfo(chan);
console.log("info.ctype:" + info.ctype);
console.log(
  "is channel an mp3 stream?:" +
    (info.ctype == basslib.BASS_CHANNELINFOtypes.BASS_CTYPE_STREAM_MP3)
);
//other infos are: freq,chans,flags,ctype,origres,plugin,sample,filename

Info of a device

var info = basslib.getInfo();
console.log("speaker count:" + info.speakers);
console.log("minimum buffer:" + info.minbuf);
console.log("latency:" + info.latency);

Free the memory from bass

basslib.BASS_Free();

MIXER FEATURES

Enable Mixer

//before using mixer, first enable it. and put the required component to root folder.
basslib.EnableMixer(true);

Mixer is Enabled?

//before using mixer, first enable it. and put the required component to root folder.
console.log(basslib.MixerEnabled());

Create mixer stream

basslib.EnableMixer(true);
var mixer = basslib.BASS_Mixer_StreamCreate(
  44100,
  2,
  basslib.BASSFlags.BASS_SAMPLE_FLOAT
);
var chan1 = basslib.BASS_StreamCreateFile(
  0,
  "d:\\mp3\\tes1.mp3",
  0,
  0,
  basslib.BASSFlags.BASS_STREAM_DECODE | basslib.BASSFlags.BASS_SAMPLE_FLOAT
);
var chan2 = basslib.BASS_StreamCreateFile(
  0,
  "d:\\mp3\\test2.mp3",
  0,
  0,
  basslib.BASSFlags.BASS_STREAM_DECODE | basslib.BASSFlags.BASS_SAMPLE_FLOAT
);
var ok1 = basslib.BASS_Mixer_StreamAddChannel(
  mixer,
  chan1,
  basslib.BASSFlags.BASS_SAMPLE_DEFAULT
);
var ok2 = basslib.BASS_Mixer_StreamAddChannel(
  mixer,
  chan2,
  basslib.BASSFlags.BASS_SAMPLE_DEFAULT
);
basslib.BASS_ChannelPlay(mixer, 0);

Get current Position of mixer playback

var positionInBytes = basslib.BASS_Mixer_ChannelGetPosition(chan, 0);
//now convert it to seconds
var position = basslib.BASS_ChannelBytes2Seconds(chan, positionInBytes);

ENCODER FEATURES

In order to be used to its full potential, you need to use an encoder like lame to stream audio to a distant server. Alternatively, bass encode mp3 addon does work the same, and is now availaible with this wrapper. I recommend using it directly. Instead of :

var _encoder = basslib.BASS_Encode_Start(
  mixer,
  `${LAME_PATH}lame -r -m s -s ${SAMPLE_RATE} -b ${BIT_RATE} -`,
  basslib.BASS_Encode_Startflags.BASS_ENCODE_NOHEAD
);

You can now use

var _encoder = basslib.BASS_Encode_MP3_Start(
  mixer,
  ` -r -m s -s ${SAMPLE_RATE} -b ${BIT_RATE} -`,
  basslib.BASS_Encode_Startflags.BASS_ENCODE_NOHEAD
);

Without having to find lame executables by yourself. If you still do prefer to use lame directly, you may find what you want here

you can directly encode and send output to shoutcast and icecast servers

use mixer as a trick, because if the channel freed or added new channel, the encoder stops itself.

add channels to mixer every time, and add mixer channel to encoder, so the encoder never stops.

Init encoder

basslib.EnableMixer(true);
var mixer = basslib.BASS_Mixer_StreamCreate(
  44100,
  2,
  basslib.BASSFlags.BASS_SAMPLE_FLOAT
);
var chan = basslib.BASS_StreamCreateFile(
  0,
  "d:\\mp3\\tes1.mp3",
  0,
  0,
  basslib.BASSFlags.BASS_STREAM_DECODE | basslib.BASSFlags.BASS_SAMPLE_FLOAT
);
var ok = basslib.BASS_Mixer_StreamAddChannel(
  mixer,
  chan,
  basslib.BASSFlags.BASS_SAMPLE_DEFAULT
);

basslib.EnableEncoder(true);

//lets try icecast
var enc_chan = basslib.BASS_Encode_Start(
  mixer,
  "lame -r -m s -s 22050 -b 56 -",
  basslib.BASS_Encode_Startflags.BASS_ENCODE_NOHEAD
);
var result = basslib.BASS_Encode_CastInit(
  enc_chan,
  "http://server-ip:8000/test",
  "password",
  basslib.BASS_Encode_CastInitcontentMIMEtypes.BASS_ENCODE_TYPE_MP3,
  "test stream",
  "http://your-server-ip",
  "pop",
  "this is my new icecast test",
  "header1\r\nheader2\r\n",
  44100,
  true //public
);

basslib.BASS_ChannelPlay(mixer, 0);

get notification from encoder server

var result=basslib.BASS_Encode_SetNotify(enc_chan,function(handle,status,user){
  if(status==basslib.EncoderNotifyStatus.BASS_ENCODE_NOTIFY_CAST){
  console.log('server connection is dead');
});

mono speaker output

//lets say if you have 5.1 speaker and want to use each output stereo or mono
//basically with 5.1 output you can use 6 different output channels.
//this example shows how to do it
var bass = require("bassaudio-updated");
var basslib = new bass();

//set init to speakers
var result = basslib.BASS_Init(
  -1,
  44100,
  basslib.BASS_Initflags.BASS_DEVICE_SPEAKERS
);
if (!result) {
  console.log("error init sound card:" + basslib.BASS_ErrorGetCode());
}

//to use mixer feature, you have to enable it
basslib.EnableMixer(true);

var cards = basslib.getDevices();
var speakerCount = basslib.getInfo().speakers;
console.log("we have", speakerCount, "speakers");
var path = require("path");
var f1 = path.join(__dirname, "1.mp3");

//create a mixer and tell it to output front right
var mixer = basslib.BASS_Mixer_StreamCreate(
  44100,
  1,
  basslib.BASS_SPEAKERtypes.BASS_SPEAKER_FRONTRIGHT
);
console.log("mixer:", mixer, " error code:", basslib.BASS_ErrorGetCode());

//set channel to decode
var chan1 = basslib.BASS_StreamCreateFile(
  0,
  f1,
  0,
  0,
  basslib.BASSFlags.BASS_STREAM_DECODE
);

//if your file is stereo, you have to downmix to mono, else you cannot get it mono output to only 1 speaker.
var ok1 = basslib.BASS_Mixer_StreamAddChannel(
  mixer,
  chan1,
  basslib.BASS_MIXERsourceflags.BASS_MIXER_DOWNMIX
);
var ok2 = basslib.BASS_ChannelPlay(mixer, 0);

console.log("chan1:", chan1, " error code:", basslib.BASS_ErrorGetCode());
console.log("ok1:", ok1, " error code:", basslib.BASS_ErrorGetCode());

setInterval(() => {}, 1000);

splitting channels

//if you want to add a mixer to another mixer, it is not possible/supported
//but there is another way to do it.
//you can split any channel or mixer into two, then you can use the new splitted channel on any other mixer.
//A "splitter" basically does the opposite of a mixer: it splits a single source into multiple streams rather then mixing multiple sources into a single stream. Like mixer sources, splitter sources must be decoding channels.

var splitChan = basslib.BASS_Split_StreamCreate(mixer, 0);
var splits = basslib.BASS_Split_StreamGetSplits(mixer);
var splitsource = basslib.BASS_Split_StreamGetSource(splitChan);
var avail1 = basslib.BASS_Split_StreamGetAvailable(splitChan);
var avail2 = basslib.BASS_Split_StreamGetAvailable(mixer);

UPDATE LOG

--------------2.X.X------------------

--------------1.X.X------------------

-----BEFORE FORK-UPDATE LOG-----