Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,87 @@
# smplr

## 0.17.0

#### New audio player (`src/smplr/`)

Complete rewrite of the audio playback layer with full unit test coverage. All nine instruments
are migrated to the new player. The public `start` / `stop` / `disconnect` / `output` / `load`
interface is unchanged.

#### Per-note `onStart` / `onEnded` callbacks

Callbacks can now be set globally on instrument options and/or per-note on the event object.
Both levels are composed when set together:

```ts
const piano = new SplendidGrandPiano(context, {
onStart: (event) => console.log("started", event.note),
onEnded: (event) => console.log("ended", event.note),
});

// or per note
piano.start({
note: "C4",
onStart: (e) => console.log("note on", e.note),
onEnded: (e) => console.log("note off", e.note),
});
```

#### Load progress

`onLoadProgress` callback and `loadProgress` getter are now available on every instrument.
The total sample count is known before loading starts, enabling determinate progress bars:

```ts
const piano = new SplendidGrandPiano(context, {
onLoadProgress: ({ loaded, total }) => {
console.log(`Loading… ${loaded} / ${total}`);
},
});

await piano.load;
console.log(piano.loadProgress); // { loaded: N, total: N }
```

#### Sampler accepts pre-decoded `AudioBuffer` values

```ts
const sampler = new Sampler(context, {
buffers: {
C4: myAudioBuffer, // AudioBuffer
D4: "https://…/D4.mp3", // URL string still works
},
});
```

#### Shared `Scheduler` and `SampleLoader`

Multiple instruments can share a `Scheduler` for coordinated timing and a `SampleLoader` for
buffer cache reuse:

```ts
import { Scheduler, SampleLoader } from "smplr";

const scheduler = new Scheduler(context);
const loader = new SampleLoader(context);

const piano = new SplendidGrandPiano(context, { scheduler, loader });
const bass = new Smolken(context, { scheduler, loader });
```

#### Advanced region features (SFZ-based instruments)

- **MIDI CC range matching** (`ccRange`) — gates regions on sustain-pedal or other CC values
- **Velocity curve** (`ampVelCurve`) — per-region amplitude scaling
- **Exclusive groups / off-by** — voice stealing between groups
- **Round-robin sequencing** (`seqPosition` / `seqLength`) — cycle through sample variations
- **Trigger modes** (`trigger: "first" | "legato"`) — region-level note trigger filtering

#### Breaking changes

- `Soundfont2Sampler`: the public `player` property (a `RegionPlayer` instance) is removed.
Use `output`, `start`, and `stop` instead.

## 0.16.x

#### Safari bug fixes
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,23 @@ const piano = await new SplendidGrandPiano(context).load;

⚠️ In versions lower than 0.8.0 a `loaded()` function was exposed instead.

#### Load progress

Track how many samples have loaded via the `onLoadProgress` option or the `loadProgress` getter:

```js
const piano = new SplendidGrandPiano(context, {
onLoadProgress: ({ loaded, total }) => {
console.log(`${loaded} / ${total} samples loaded`);
},
});

// Or poll at any time:
console.log(piano.loadProgress); // { loaded: 12, total: 48 }
```

`total` is known before loading starts, so you can display a determinate progress bar.

#### Shared configuration options

All instruments share some configuration options that are passed as second argument of the constructor. As it name implies, all fields are optional:
Expand All @@ -124,6 +141,7 @@ All instruments share some configuration options that are passed as second argum
- `disableScheduler`: disable internal scheduler. `false` by default.
- `scheduleLookaheadMs`: the lookahead of the scheduler. If the start time of the note is less than current time plus this lookahead time, the note will be started. 200ms by default.
- `scheduleIntervalMs`: the interval of the scheduler. 50ms by default.
- `onLoadProgress`: a function called after each sample buffer is decoded. Receives `{ loaded, total }` where `total` is the full count known before loading starts.
- `onStart`: a function that is called when starting a note. It receives the note started as parameter. Bear in mind that the time this function is called is not precise, and it's determined by lookahead.
- `onEnded`: a function that is called when the note ends. It receives the started note as parameter.

Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "smplr",
"version": "0.16.4",
"version": "0.17.0",
"homepage": "https://github.com/danigb/smplr#readme",
"description": "A Sampled collection of instruments",
"main": "dist/index.js",
Expand Down Expand Up @@ -49,7 +49,11 @@
"node": ">=14.0.0"
},
"jest": {
"preset": "ts-jest"
"preset": "ts-jest",
"testPathIgnorePatterns": [
"/node_modules/",
"/site/"
]
},
"packageManager": "npm@10.2.3"
}
3 changes: 2 additions & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"dev": "next dev",
"devs": "next-dev-https --https --port 3001",
"deploy": "npm run deploy:build && npm run deploy:github",
"deploy:github": "npm run deploy:build && gh-pages -d out/ -t true",
"deploy:github": "npm run deploy:build && gh-pages -d out/ -t",
"deploy:build": "DEPLOY=true next build",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
Expand Down
Loading