@seamless-medley/medley v0.5.3
node-medley
node-medley is a Node.js native module built on top of JUCE framework. It provides audio playback to both audio output device and Node.js stream
Features
- Cross-platform
- Seamless playback with automatic track transitions
- Smooth track transition, with customizable transition points
- Track metadata reading, including cover art and lyrics
- ReplayGain support
- Audio level normalization (in conjunction with ReplayGain)
- Built-in audio limiter
- Built-in Karaoke effect (vocal removal)
- Audio level measurement
- Play directly to audio device
- Consumption of PCM data via Node.js streams
Supported platforms
linux/amd64linux/arm64win32/x64macOS/x64macOS/arm64
Table of contents
Installation
Linux Requirements
To install on Linux system, you'll need to install the following dependencies:
asound- Required for audio output on Linux systems
freetype- Required by JUCE framework (but not actively used)
opusandopusfile- For decoding .opus audio files
Debian/Ubuntu
sudo apt-get install libasound2 libfreetype6 libopus0 libopusfile0RHEL/Fedora/Rocky Linux
sudo dnf install alsa-lib freetype opus opusfileInstall the npm package
With npm:
npm i @seamless-medley/medleyWith pnpm:
pnpm add @seamless-medley/medleyGetting started
// Import the main classes
import { Medley, Queue } from '@seamless-medley/medley';
// Create a queue instance and pass it to the Medley class during instantiation
const queue = new Queue();
const medley = new Medley(queue);
// Add tracks to the `queue` and start playing
queue.add('/path/to/file');
queue.add('/path/to/file2');
medley.play();This will start playback on the default audio device.
Supported File Formats
Currently, the supported file formats include: wav, aiff, mp3, ogg, flac and opus. More formats may be added in the future.
Guide
- Getting available audio devices
- Selecting audio device
- Null Audio device
- Getting PCM data
- Dynamic queue
- Check if a track is loadable
- Getting metadata
- Getting cover art and lyrics
- Reading audio level information
- Normalizing tracks audio level
- Custom transition point
Getting available audio devices
Simply use the getAvailableDevices method.
Selecting audio device
Utilize data returned from the getAvailableDevices method with the setAudioDevice method.
Example:
// Use the default device of the first type
const allDevices = medley.getAvailableDevices();
medley.setAudioDevice({
type: allDevices[0].type,
device: allDevices[0].defaultDevice
});Null Audio device
node-medley includes a special audio device called Null Device which does not output sound to a physical audio device.
This is useful in environments without installed audio devices or when only PCM audio data consumption is needed, see requestAudioStream method.
Example:
medley.setAudioDevice({ type: 'Null', device: 'Null Device' });Getting PCM data
// Request a stream of signed 16-bit, little endian audio at 48000 sample rate
const result = await medley.requestAudioStream({
format: 'Int16LE',
sampleRate: 48000
});
// Pipe to another stream
result.stream.pipe(/* destination */);
// Or intercept data with the `data` event
result.stream.on('data', (buffer) => {
// Do something with `buffer`
});
// When finished, don't forget the delete the stream
medley.deleteAudioStream(result.id);Dynamic queue
Sometimes, preloading all tracks can lead to monotony, so the queue can dynamically update using the enqueueNext event
Example:
medley.on('enqueueNext', (done) => {
const newTrack = getNewFreshTrack(); // Logic for fetching new track
queue.add(newTrack);
done(true);
});Check if a track is loadable
Use isTrackLoadable static method.
Getting metadata
Metadata can be retrieved in two ways:
- From a file path
- Use getMetadata static method
- From the deck
- Use getDeckMetadata method
Getting cover art and lyrics
Utilize the getCoverAndLyrics method.
Reading audio level information
Real-time audio level are accessible via the level property.
Example:
// Read audio levels at a rate of 30 times per second
setInterval(() => {
const audioLevel = medley.level;
// Use the value returned
}, 1000 / 30);Normalizing tracks audio level
ReplayGain analyzes and adjusts perceived loudness.
node-medley supports reading ReplayGain Track-gain metadata embeded in audio files.
To embed it, use one of these scanners.
Usually, ReplayGain attenuates the played back audio, a make-up gain should be applied to boost the audio level back to the normalized level, you can change this make-up gain by changing the replayGainBoost property.
The make-up gain will not cause clipping, because there is an audio limiter preventing that from happening in the audio pipline.
Custom transition point
node-medley automatically analyzes track to find audio positions in which it should start/stop playing and also the positions/durations the transition between track should occur.
but, sometimes this may not be as intended, you can customize that by giving node-medley some hints.
The hints can come from the metadata embed in the track itself using user-defined tag, here are the supported tags:
CUE-INorCUE_IN- Start position of the track, in secondsThis correspond to the TrackInfo
cueInPositionproperty.CUE-OUTorCUE_OUT- Stop position of the track, in secondsThis correspond to the TrackInfo
cueOutPositionproperty.
Alternatively, you can provide that values when adding a track into the queue.
See also:
API
- Methods
- Properties
- Events
- Static methods
- Metadata
Medley class
This is the main class, the constructor accepts an instance of the Queue class.
new Medley(queue, options)NOTE: JavaScript
Arraycannot be used as a queue.
Options?
logging(boolean?) - Enable logging, See log eventskipDeviceScanning(boolean?) - Skip scanning for audio devices
Methods
play(shouldFade = true)
Start playing, if the playing was previously paused it will be resumed.
The shouldFade parameter is used only when resuming.
stop(shouldFade = true)
Stop playing.
togglePause(shouldFade = true)
Toggle play/pause.
fadeOut()
Forcefully transit to the next track with a fade-out effect.
seek(time, deckIndex?)
time(number) - Time in seconds.deckIndex(number?) - Deck index, possible values are:0,1,2
seekFractional(fraction, deckIndex?)
fraction(number) - Fraction of the track's length.0- Seek to the beginning.0.5Seek to the middle of the track.
deckIndexoptional deck index, possible values are:0,1,2
getDeckPositions(deckIndex)
deckIndex(number) - Deck index, possible values are:0,1,2
Returns an object with:
current(number?) - Current playing positionduration(number?) - Total durationfirst(number?) - First audible positionlast(number?) - Last audible positionleading(number?) - Fade-in positiontrailing(number?) - Fade-out positioncuePoint(number?)transitionStart(number?)transitionEnd(number?)
getDeckMetadata(deckIndex)
deckIndex(number) - Deck index, possible values are:0,1,2
Returns Metadata for the specified deckIndex
getAvailableDevices()
Returns array of object describing audio devices.
type(string) - Device typeisCurrent(boolean) -trueif this device type is currently selecteddevices(string[]) - List of devices of this typedefaultDevice(string) - Default device name of this typecurrentDevice(string | undefined) - Currently selected device name of this type,undefinedif none
getAudioDevice()
Get the audio device currently being selected, returns undefined if none.
If available, returns an object with:
type(string) - Device typedevice(string) - Device name
setAudioDevice(descriptor)
Set audio device used for playback.
The descriptor is an object containing:
type(string?) - Device type, if omitted, the currently selected device type is useddevice(string?) - Device name, if omitted, the default
If both fields are omitted, this method does nothing.
Returns false if the specified device cannot be used.
Returns true if some device is selected.
Use getAudioDevice() to get the actual selected device.
requestAudioStream(options?)
Request a PCM audio data stream
options? is an object with:
sampleRate(number) - Sample rate for the PCM data. Defaults to the default device's sample rate if omitted.format- Audio sample format, possible values are:Int16LE- 16 bit signed integer, little endianInt16BE- 16 bit signed integer, big endianFloatLE- 32 bit floating point, little endianFloatBE- 32 bit floating point, big endian
bufferSize(number) - Maximun frames the internal buffer can hold, increase this value helps reduce stuttering in some situations- Default value is 250ms (
deviceSampleRate* 0.25)
- Default value is 250ms (
buffering(number):- Number of frames to buffer before returning the buffered frames back to Node.js stream
- Reducing this value will cause the stream to pump faster
- Default value is 10ms (
deviceSampleRate* 0.01)
gain(number) - Output gain, a floating point number ranging from 0 to 1fx(object) - Effects parameter:karaoke: Parameters for the karaoke effect, see setFx(type: 'karaoke', params)
Returns a Promise of object with:
id(number) - The request id, use this value to update or delete the requested streamchannels(number) - Number of audio channels, This is usuaully2originalSampleRate(number) - Original sample rate in audio pipelinesampleRate(number) - Sample rate as requestedbitPerSample(number) - Bit per sample, depending on theformat16- forInt16LEofInt16BE32- forFloatLEofFloatBE
stream(Readable) - Readable stream, for consuming PCM dataupdate((options) => boolean) - Update this audio stream, theoptionsis the same as updateAudioStream(id, options)getLatency() => number - Get the audio latencygetFx- See getFxCalling this method from this object only effects the corresponding stream, but does not effect the main output.
setFx- See setFxCalling this method from this object only effects the corresponding stream, but does not effect the main output.
updateAudioStream(id, options)
Update the requested audio stream specified by id returned from requestAudioStream method.
options is an object with:
gain(number) - Output gain, a floating point number ranging from 0 to 1buffering- See requestAudioStreamfx(object) - Effects parameter:karaoke: Parameters for the karaoke effect, see setFx(type: 'karaoke', params)
Returns true if succeeded.
deleteAudioStream(id)
Delete the requested audio stream specified by id returned from requestAudioStream method.
getFx(type: 'karaoke')
Get all parameter values for the karaoke effect.
Returns an object with:
enabled(boolean)mix(number) - Dry/Wet for the karaoke effectlowpassCutoff(number) - Cut off frequency for the low pass filterlowpassQ(number) - Quality factor for the low pass filterhighpassCutoff(number) - Cut off frequency for the high pass filterhighpassQ(number) - Quality factor for the high pass filter
setFx(type: 'karaoke', params)
Set karaoke effect parameters.
The params is an object with:
enabled(boolean?)dontTransit(boolean?) - Do not apply dry/wet mix transition while enabling/disabling the effect. Must be used with theenabledproperty.mix(number?) - Dry/Wet for the karaoke effectlowpassCutoff(number?) - Cut off frequency for the low pass filterlowpassQ(number?) - Quality factor for the low pass filterhighpassCutoff(number?) - Cut off frequency for the high pass filterhighpassQ(number?) - Quality factor for the high pass filter
Returns true if succeeded.
Properties
playing
Type: boolean
Read only
Returns true if playing, but not affected by the paused property.
paused
Type: boolean
Read only
Returns true if playing but has been paused.
volume
Type: number
Audio volume in linear scale.
0 = Silent
1 = 0dBFS
fadingCurve
Type: number
Minimum: 0
Maximum: 100
S-Curve value used for fading in/out.
maximumFadeOutDuration
Type: number
Maximum duration in seconds for the fade-out transition between tracks.
minimumLeadingToFade
Type: number
Duration in seconds at the beginning of a track to be considered as having a long intro.
A track with a long intro will cause a fading-in to occur during transition.
replayGainBoost
Type: number
Default: 9.0
Gain (in dB) to boost for tracks with ReplayGain metadata embeded, default to 9.0dB.
If a track has no ReplayGain metadata, this value is ignored.
level
Read only
Returns an object with:
left- Left channel levelright- Right channel level
With each channel having:
magnitude(number) - Audio levelpeak(number) - Holding peak
reduction
Read only
Returns audio reduction level in dB
Audio reduction occur during the internal audio processing
Events
Deck events
Parameters:
deckIndex(number) - Deck index where the event occurtrackPlay- An object describing detail of the play session for the Deck.uuid(string) - A unique string identifying thetrackPlayitselftrack- Track, see TrackInfoduration(number) - Track duration
loaded
Emits when a track has been loaded into a Deck.
unloaded
Emits when a track has been unloaded from a Deck.
started
Emits when a Deck starts playing.
finished
Emits when a Deck finishes playing.
mainDeckChanged
Emits when a Deck becomes the main playing Deck.
enqueueNext(done)
Emits when the playing queue is exhausted and needs to be filled.
See Dynamic quque
Parameter:
done- Call this function in the event handler withtruevalue to informnode-medleythat at least a track has been added to the queue and should be loaded.
audioDeviceChanged
Emits when the audio device changes, use getAudioDevice method to get the current audio device.
log
Emits when a log message is pushed from the native module.
Logging must be enabled when constructing the Medley instance, see Medley class options
Parameter:
level(number) - Log levelname value trace -1 debug 0 info 1 warn 2 error 3 fatal 4 name(string) - The logger's namemsg(string) - Log message
Static methods
getInfo
Returns an object containing information about node-medley
runtime:file- Node native module file nameruntime- Runtime namenapi-node-addon-apiversion
version-node-medleyversion numberjuce- Detail for the JUCE framework library being linked intonode-medleyversioncpuintel- Intel CPUarm- ARM CPUarm64- ARM64 CPUaarch64- ARM64 CPUsse- SIMD supports on x84_64 CPUneon- SIMD supports on ARM CPUvdsp- vDSP supports on macOS
isTrackLoadable(track)
Returns true if the track can be loaded and played.
getMetadata(path)
Returns Metadata for path
getAudioProperties(path)
Returns AudioProperties for path
Please note that this function may scan the whole file in order to get a good result.
getCoverAndLyrics(path)
Returns an object with:
cover(Buffer) - Cover art datacoverMimeType(string) - Cover art MIME typelyrics(string) - Raw lyrics data
Queue class
The queue class provides a list of tracks to the Medley class.
Constructor
new Queue(tracks?)
Create a new instance of the Queue class, an optional tracks is an array of tracks to initially fill the queue.
The Queue class is straightforward, For more control over your tracks list, manage it manually and provide a track when the Medley object requires one, see enqueueNext event
Methods
add(track)
Add a track to the queue, see TrackInfo
add(tracks)
Add a list of tracks to the queue, see TrackInfo
insert(index, track)
insert(index, tracks)
Insert track(s) at position specified by the index parameter.
delete(index, count = 1)
Delete tracks(s) specified by count starting from index.
swap(index1, index2)
Swap tracks.
move(currentIndex, newIndex)
Move a track from currentIndex to newIndex.
get(index)
Get the track at index
set(index, track)
Set the track at index
toArray()
Returns a new shallow copy of all tracks.
Properties
length property
Total number of tracks in the queue.
TrackInfo
A TrackInfo can be either a string to file path, or an object with:
path(string) - File pathcueInPosition(number?) - Start position of the trackcueOutPosition(number?) - Stop position of the trackdisableNextLeadIn(boolean?)- Disables the lead-in of the next track, useful for transitioning from jingles/sweepers.
- The lead-in is the position where it is considered as the start singing point, usually presented in a track which has smooth/long beginning.
Metadata
title(string?)artist(string?)album(string?)isrc(string?) - International Standard Recording CodealbumArtist(string?)originalArtist(string?)trackGain(number?) - ReplayGain value in dB (decibels),0means noReplayGainvalue for this trackbpm(number?) - Beats Per Minutecomments(string, string) - List of key/value pairs for all user-defined comments
AudioProperties
bitrate(number?) - in KbpssampleRate(number?) - in Hzduration(number?) - in seconds
Demo musics
Demo music from Bensound.com
10 months ago
10 months ago
12 months ago
1 year ago
12 months ago
10 months ago
12 months ago
10 months ago
11 months ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago