npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rhythm.js

v1.1.8

Published

This library is a wrapper around the Web Audio API that (hopefully) makes interacting with it much more intuitive!

Readme

Trailer Video

Rhythm.JS Trailer

Click on the thumbnail image above this text to see Rhythm.JS's trailer on Youtube!

Live Demo

https://eliaxelang007.github.io/rhythm.js/examples/all.html

Description

rhythm.js is a relatively thin wrapper around the Web AudioAPI that (hopefully) makes interacting with it a lot more intuitive.

The whole library is built around the concepts of Audio Commands and Compiled Audio Commands. You can nest audio commands inside other audio commands to get more complex behavior. And, you can treat Compiled Audio Commands just like uncompiled ones!

It's like a declarative UI framework, but for audio.

Find Rhythm.js on npm and GitHub.

Documentation

Quickstart

  • Clone the repo: git clone https://github.com/eliaxelang007/rhythm.js.git
  • Install with npm: npm install rhythm.js
  • CDN using jsDelivr
    • <script src="https://cdn.jsdelivr.net/npm/[email protected]/rhythm.js"></script> (You'll have to prefix your imports with Rhythm.)
    • import { /* Imports */ } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/rhythm.esm.js";

The CDN may not update instantly once the latest version is released.

Commands

Here's a list of the currently available audio commands.

| AudioCommand | Description | | --- | --- | | Play(path: string) | The most basic audio command. Plays the audio file specified in path. | | Clip({ offset: Seconds, duration: Seconds }, child: AudioCommand) | Creates a clip of its child command starting at offset and playing from there for duration. | | Repeat({ duration: Seconds }, child: AudioCommand) | Repeats its child command until it fits into duration. | | Sequence(children: AudioCommand[]) | Plays each command in children one after the other. | | Gain({ gain_keyframes: GainKeyframe[] }, child: AudioCommand) | Plays its child command while letting gain_keyframes control how the volume will change over time. |

General Usage

First make a command,

const some_command = new Repeat(
    {
        duration: 20 // Repeat my child command for 20 seconds.
    },
    new Clip(
        {
            offset: 5, // Starting 5 seconds into my child command,
            duration: 10 // play 10 seconds.
        },
        new Play("example.mp3"), 
    )
);

compile and attach it,

const rhythm = new RhythmContext(/* new AudioContext() [You can optionally provide an AudioContext] */);
const track = await rhythm.compile_attached(some_command);

and then, finally, play it!

track.schedule_play();

schedule_play works just like AudioBufferSourceNode.start, but you can call it multiple times, and it doesn't have the third duration parameter.

// Starts the track 1 second from now with playback beginning at 3 seconds into the track.
track.schedule_play(rhythm.current_time + 1, 3); 

rhythm.current_time is an alias for the currentTime of the RhythmContext's inner AudioContext.

Each time you call play, it gives you a handle to the scheduled instance of the track. On the handle, schedule_stop works just like AudioScheduledSourceNode.stop.

const now = rhythm.current_time;
const scheduled = track.schedule_play(now);

scheduled.schedule_stop(now + 2); // Stops the track 2 seconds after it starts.

The handle also has start_time and end_time which stores when the command is scheduled for.

const start_at = rhythm.current_time + 1;
const offset = 3;

// Starts the track 1 second from now with playback beginning at 3 seconds into the track.
const scheduled = track.schedule_play(start_at, offset);

scheduled.start_time // Will be equal to [start_at]
scheduled.end_time // Will be equal to [start_at + (track.duration - offset)] 

Once a command is compiled, you have access to its duration.

track.duration

That's useful because you can treat compiled audio commands just like uncompiled audio commands which is one of the core tenets of rhythm.js.

You can see an example of this in the code snippet below.

const track = await rhythm.compile(new Play("example.mp3"));

const played_twice = await rhythm.compile_attached(
  new Repeat(
    {
        duration: track.duration * 2 // This makes the [track] play twice.
    },
    track
  )
);

played_twice.scheduled_play();

Loading audio tracks happens in the compilation stage, and since the Play inside track has already been compiled, await rhythm.compile_attached here runs almost instantly!

You can get a lot more complex with this tenet, try it out!

RhythmContext

Here's what the methods of RhythmContext do.

| Compilation Method | Description | | --- | --- | | compile(command: AudioCommand) | Compiles your AudioCommand into a form where you have to supply an AudioNode output_node as the first argument every time you call scheduled_play. | | attach(command: CompiledAudioCommand) | The destination node of the RhythmContext's inner AudioContext is always implicitly supplied as the first argument in the compiled command's scheduled_play, allowing you to call the method without passing an output_node in. | | compile_attached(command: AudioCommand) | Equivalent to this.attach(await this.compile(command)) |

Attached compiled commands can be detached using their detach methods, making their scheduled_play methods have to be called with an explicit output_node as their first parameter again.

onended Callbacks

If you want to run some code once a track has completed playing, do this.

const scheduled = track.schedule_play();

scheduled.add_on_stop_listener((event) => { /* Code to run when the track has finished playing. */ });

Advanced

If you'd like, you can read through the only 500 lines of code that make up this library. That way, you'll gain a fuller grasp of its inner workings.

I've tried to cover all the features comprehensively in this readme though, but still feel free to peek around!

Examples

We'll be using ./celery_in_a_carrot_field.ogg as our example track's filepath.

See an example live here!

Play

To play a track with rhythm.js, try this.

import { RhythmContext, Play } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/rhythm.esm.js";

async function play() {
    const command = new Play("./celery_in_a_carrot_field.ogg");

    const rhythm = new RhythmContext();
    const track = await rhythm.compile_attached(command);

    track.schedule_play();
}

play();

Clip

To create a clip of a certain section of a track, do this.

In this code snippet, Clip will create a track that starts 5 seconds into its child node, Play, and then plays its next 10 seconds.

import { RhythmContext, Play, Clip } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/rhythm.esm.js";

async function play() {
    const command = new Clip(
        {
            offset: 5,
            duration: 10
        },
        new Play("./celery_in_a_carrot_field.ogg")
    );

    const rhythm = new RhythmContext();
    const track = await rhythm.compile_attached(command);

    track.schedule_play();
}

play();

Repeat

To repeat a track, do this. In this code snippet, once the Repeat node is scheduled to play, it will keep repeating the Clip node inside it while it hasn't been 20 seconds yet, in effect making the Clip play twice.

import { RhythmContext, Play, Clip, Repeat } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/rhythm.esm.js";

async function play() {
    const command = new Repeat(
        {
            duration: 20
        },
        new Clip(
            {
                offset: 5,
                duration: 10
            },
            new Play("./celery_in_a_carrot_field.ogg")
        )
    );

    const rhythm = new RhythmContext();
    const track = await rhythm.compile_attached(command);

    track.schedule_play();
}

play();

Sequence

To play tracks one after the other, do this. Here, Sequence will play the Clip inside of it first, and when it ends will play the Repeat inside it next.

import { RhythmContext, Play, Clip, Repeat, Sequence } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/rhythm.esm.js";

async function play() {
    const song = new Play("./celery_in_a_carrot_field.ogg");

    const command = new Sequence(
        [
            new Clip(
                {
                    offset: 5,
                    duration: 10
                },
                song
            ),
            new Repeat(
                {
                    duration: 10
                },
                song
            )
        ]
    );

    const rhythm = new RhythmContext();
    const track = await rhythm.compile_attached(command);

    track.schedule_play();
}

play();

Gain

To change the volume of a track throughout its playback, try this. Gain here adds a 20 second exponential fade in and fade out to the track.

import { RhythmContext, Play, Gain } from "https://cdn.jsdelivr.net/npm/[email protected]/dist/rhythm.esm.js";

async function play() {
    const rhythm = new RhythmContext();
    const compiled_song = await rhythm.compile( // See [RhythmContext] section of the readme.
        new Play("./celery_in_a_carrot_field.ogg")
    ); 

    const fade_duration_seconds = 20;

    const faded_song = await rhythm.compile_attached(
        new Gain(
            {
                gain_keyframes: [
                    {
                        value: 0.01, // Can't exponentially fade in from a flat 0.
                        from_start: 0
                    },
                    {
                        transition: "exponential",
                        value: 1,
                        from_start: fade_duration_seconds
                    },
                    {
                        value: 1,
                        from_start: compiled_song.duration - fade_duration_seconds // I only compiled the song to be able to access its duration.
                    },
                    {
                        transition: "exponential",
                        value: 0.01, // Can't exponentially fade out to a flat 0.
                        from_start: compiled_song.duration
                    }
                ]
            },
            compiled_song, // You could very well put any other Audio Command here. It doesn't have to be compiled.
        )
    );

    faded_song.schedule_play();
}

play();

This is what the type of GainKeyframe look like.

type GainKeyframe = {
    transition: undefined | "exponential" | "linear"; // How the gain node should transition to its target [value] from the previous [GainKeyframe].
    value: number;                                    // The target value of the gain node.
    from_start: Seconds;                              // The time relative to the start of the track when the gain's value should be the target [value].
};

Credits

If you encounter any bugs or see any issues with this library, don't hesitate to file an issue/suggestion or pull request on GitHub!

The API is still constantly changing, so it's not production ready. (Yet!)

Sample music in tests from StarryAttic on Youtube.