Skip to content

d-buckner/d-piano

 
 

Repository files navigation

d-piano

High quality multisampled web audio piano with progressive loading and caching

About

This is a fork of @tonejs/piano by Yotam Mann with performance optimizations, sample caching, progressive loading, and more active maintaince. Built on high-quality samples from Salamander Grand Piano.

Features

  • High-quality samples - Up to 16 velocity levels across 88 keys (Yamaha C5)
  • Complete instrument - Includes pedal sounds and string harmonics
  • Progressive loading - Loads 1 velocity level immediately for fast startup, then upgrades to full quality in the background and seemlessly improves audio quality
  • Cache-aware - Probes the Cache Storage API at startup; if target-velocity samples are already cached, starts at full quality immediately rather than the single-velocity warm-up pass
  • Buffer caching - Audio buffers are shared across multiple piano instances and across progressive upgrade steps without no re-fetching

Install

Install the npm package:

npm install --save d-piano

d-piano requires Tone.js as a peer dependency:

npm install --save tone

Usage

Import

import { Piano } from 'd-piano'

Create and load samples

// Create the piano — progressive loading is automatic
const piano = new Piano({
	velocities: 8  // target quality; default is 8
})

// Connect to speaker output
piano.toDestination()

// Resolves as soon as the first pass (1 velocity) is ready to play.
// Upgrading to full quality happens automatically in the background.
piano.load().then(() => {
	console.log('Piano ready — playing at 1 velocity, upgrading in background')
})

How progressive loading works

load() resolves after a fast first pass (1 velocity layer) so you can start playing immediately. The upgrade to the target velocity count happens in the background, expanding the velocity layers in-place without interrupting notes that are already playing.

If the target samples are already in the Cache Storage API, the piano starts at full quality immediately instead of going through the single-velocity warm-up pass first. This pairs well with preloadSamples and any service worker caching strategy in your own app.

Cold load (nothing cached):
  load() resolves → playing at 1 velocity
  [idle] → upgrades to 8 velocities in-place, no interruption

Warm load (samples cached):
  load() resolves → playing at 8 velocities immediately

Preloading samples

Use preloadSamples to fetch samples ahead of time without importing the full Tone.js piano. Combined with a service worker caching strategy in your app, this means Piano will detect the cached samples on the next load and start at full quality immediately.

import { preloadSamples } from 'd-piano'

await preloadSamples(8, {
	baseUrl: '/assets/samples/piano/',
	minNote: 21,
	maxNote: 108,
})

API Reference

Piano Options

interface PianoOptions {
	velocities: number;    // Target velocity levels (default: 8, max: 16). Progressive
	                       // loading starts at 1 and upgrades to this in the background.
	minNote: number;       // Lowest MIDI note to load (default: 21)
	maxNote: number;       // Highest MIDI note to load (default: 108)
	release: boolean;      // Include release sounds (default: false)
	pedal: boolean;        // Include pedal sounds (default: true)
	url: string;           // Sample directory URL
	maxPolyphony: number;  // Max simultaneous notes (default: 32)
	volume: {              // Component volume levels in dB (default: 0)
		pedal: number;
		strings: number;
		keybed: number;
		harmonics: number;
	}
}

Methods

.keyDown({ note: string, time?: Time, velocity?: number })

Press a note down on the piano.

// Play a 'C4' immediately
piano.keyDown({ note: 'C4' })

// Play a 'C4' 1 second from now with velocity 0.8
piano.keyDown({ note: 'C4', time: '+1', velocity: 0.8 })

.keyUp({ note: string, time?: Time })

Release a note at the given time.

// Release the pressed 'C4' immediately
piano.keyUp({ note: 'C4' })

.pedalDown({ time?: Time })

Press and hold the sustain pedal. Notes played while the pedal is down will be sustained until the pedal is released.

.pedalUp({ time?: Time })

Release the sustain pedal and dampen any sustained notes.

.load(): Promise<void>

Start loading samples. Resolves as soon as the first velocity pass is ready to play. The upgrade to full velocity resolution continues in the background and requires no further interaction.

.loaded: boolean

true once the first velocity pass has finished loading and the piano is ready to play.

.dispose()

Clean up the piano instance and free resources.

Development

Building

npm run build

Testing

npm test

Linting

npm run lint

Credits

This project builds upon the great work of:

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

About

High quality multisampled web audio piano with progressive loading

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages

  • TypeScript 83.5%
  • HTML 12.0%
  • JavaScript 4.5%