Skip to content

Commit

Permalink
💥 [Earwurm] Replace fadeMs with transitions (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
beefchimi authored Jan 9, 2024
1 parent d9ecb1b commit 5f7e2b1
Show file tree
Hide file tree
Showing 15 changed files with 257 additions and 152 deletions.
5 changes: 5 additions & 0 deletions .changeset/cheese-bread-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'earwurm': minor
---

Remove all `static` members in favour of exported `tokens` object.
5 changes: 5 additions & 0 deletions .changeset/lucky-mugs-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'earwurm': minor
---

Replace `fadeMs` option with `transitions` boolean.
18 changes: 16 additions & 2 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
# Earwurm migration guide

## 0.7.0

- Replaced the `fadeMs?: number` option for `Earwurm`, `Stack`, and `Sound` with a simpler `transitions?: boolean` option.
- Defaults to `false`.
- If opted-into, it will provide an opinionated `200ms` “fade”.
- To fix: Replace all instances of `fadeMs: someNumber` with `transitions: true`.
- Removed some `static` members from `Earwurm` and `Stack`.
- Now exposing equivalent values on the exported `tokens` object.
- To fix, simply replace any instances of:
- `Earwurm.suspendAfterMs` with `tokens.suspendAfterMs`.
- `Earwurm.maxStackSize` or `Stack.maxStackSize` with `tokens.maxStackSize`.

## 0.6.0

For more details on the released changes, please see [🐛 Various bug fixes](https://github.com/beefchimi/earwurm/pull/50).

- Rename all instances of `statechange` to `state`.
- Renamed all instances of `statechange` to `state`.
- Example: `manager.on('statechange', () => {})`
- Replace any instances of `LibraryKeys` with the equivalent `StackIds[]`.
- To fix: Simply find all instances of `statechange` and rename it to `state`.
- Removed the exported `LibraryKeys` type.
- Simply find-and-replace any instances of `LibraryKeys` with the equivalent `StackIds[]`.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ import type {ManagerConfig} from 'earwurm';

// Optionally configure some global settings.
const customConfig: ManagerConfig = {
fadeMs: 200,
transitions: true,
request: {},
};

Expand Down
15 changes: 7 additions & 8 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,12 @@ manager.activeEvents;

**Static members:**

```ts
// Retrieve the maximum `number` of sounds allowed to
// be contained within a `Stack` at once.
Earwurm.maxStackSize;
There are no static members. However, there are some relevant static values that can be retrieved from the exported `tokens` object:

```ts
// Retrieve the total time (in milliseconds) that needs
// to pass before the auto-suspension kicks in.
Earwurm.suspendAfterMs;
tokens.suspendAfterMs;
```

## Stack API
Expand Down Expand Up @@ -257,11 +255,12 @@ soundStack.activeEvents;

**Static members:**

There are no static members. However, there are some relevant static values that can be retrieved from the exported `tokens` object:

```ts
// Retrieve the maximum `number` of sounds allowed to
// be contained within a `Stack` at once. This is
// identical to what can also be read from `Earwurm`.
Stack.maxStackSize;
// be contained within a `Stack` at once.
tokens.maxStackSize;
```

## Sound API
Expand Down
2 changes: 1 addition & 1 deletion docs/examples-future.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function handleQueueChange(keys: SoundId[]) {
stack?.on('queuechange', (keys) => handleQueueChange(keys));

async function handleQueuedPlay() {
if (!stack || stack.keys.length >= Stack.maxStackSize) return;
if (!stack || stack.keys.length >= tokens.maxStackSize) return;

const sound = await stack.prepare();
return sound;
Expand Down
80 changes: 39 additions & 41 deletions src/Earwurm.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {EmittenCommon} from 'emitten';

import {getErrorMessage, linearRamp, unlockAudioContext} from './helpers';
import {arrayShallowEquals, clamp, msToSec, secToMs} from './utilities';
import {arrayShallowEquals, clamp} from './utilities';
import {tokens} from './tokens';

import type {
Expand All @@ -17,24 +17,16 @@ import type {
import {Stack} from './Stack';

export class Earwurm extends EmittenCommon<ManagerEventMap> {
static readonly maxStackSize = tokens.maxStackSize;
static readonly suspendAfterMs = tokens.suspendAfterMs;

static readonly errorMessage = {
close: 'Failed to close the Earwurm AudioContext.',
resume: 'Failed to resume the Earwurm AudioContext.',
};

private _volume = 1;
private _vol = 1;
private _mute = false;
private _trans = false;
private _keys: StackId[] = [];
private _state: ManagerState = 'suspended';

readonly #context = new AudioContext();
readonly #gainNode = this.#context.createGain();

readonly #fadeSec: number = 0;
readonly #request: ManagerConfig['request'];

#library: Stack[] = [];
#suspendId: TimeoutId = 0;
#queuedResume = false;
Expand All @@ -46,39 +38,54 @@ export class Earwurm extends EmittenCommon<ManagerEventMap> {
constructor(config?: ManagerConfig) {
super();

this._volume = config?.volume ?? this._volume;
this.#fadeSec = config?.fadeMs ? msToSec(config.fadeMs) : this.#fadeSec;
this._vol = config?.volume ?? this._vol;
this._trans = Boolean(config?.transitions);
this.#request = config?.request ?? undefined;

this.#gainNode.connect(this.#context.destination);
this.#gainNode.gain.setValueAtTime(this._volume, this.#context.currentTime);
this.#gainNode.gain.setValueAtTime(this._vol, this.#context.currentTime);

if (this._unlocked) this.#autoSuspend();

this.#context.addEventListener('statechange', this.#handleStateChange);
}

private get transDuration() {
return this._trans ? tokens.transitionSec : 0;
}

get transitions() {
return this._trans;
}

set transitions(value: boolean) {
this._trans = value;

this.#library.forEach((stack) => {
stack.transitions = value;
});
}

get volume() {
return this._volume;
return this._vol;
}

set volume(value: number) {
const oldVolume = this._volume;
const oldVolume = this._vol;
const newVolume = clamp(0, value, 1);

this._volume = newVolume;
if (oldVolume === newVolume) return;

if (oldVolume !== newVolume) {
this.emit('volume', newVolume);
}
this._vol = newVolume;
this.emit('volume', newVolume);

if (this._mute) return;

const {currentTime} = this.#context;
linearRamp(
this.#gainNode.gain,
{from: oldVolume, to: newVolume},
{from: currentTime, to: currentTime + this.#fadeSec},
{from: currentTime, to: currentTime + this.transDuration},
);
}

Expand All @@ -87,20 +94,19 @@ export class Earwurm extends EmittenCommon<ManagerEventMap> {
}

set mute(value: boolean) {
if (this._mute !== value) {
this.emit('mute', value);
}
if (this._mute === value) return;

this._mute = value;
this.emit('mute', value);

const fromValue = value ? this._volume : 0;
const toValue = value ? 0 : this._volume;
const fromValue = value ? this._vol : 0;
const toValue = value ? 0 : this._vol;

const {currentTime} = this.#context;
linearRamp(
this.#gainNode.gain,
{from: fromValue, to: toValue},
{from: currentTime, to: currentTime + this.#fadeSec},
{from: currentTime, to: currentTime + this.transDuration},
);
}

Expand Down Expand Up @@ -145,7 +151,7 @@ export class Earwurm extends EmittenCommon<ManagerEventMap> {
newKeys.push(id);

const newStack = new Stack(id, path, this.#context, this.#gainNode, {
fadeMs: secToMs(this.#fadeSec),
transitions: this._trans,
request: this.#request,
});

Expand Down Expand Up @@ -205,10 +211,7 @@ export class Earwurm extends EmittenCommon<ManagerEventMap> {
);
})
.catch((error) => {
this.emit('error', [
Earwurm.errorMessage.close,
getErrorMessage(error),
]);
this.emit('error', [tokens.error.close, getErrorMessage(error)]);
});

this.empty();
Expand All @@ -227,7 +230,7 @@ export class Earwurm extends EmittenCommon<ManagerEventMap> {

if (this.#suspendId) clearTimeout(this.#suspendId);

this.#suspendId = setTimeout(this.#handleSuspend, Earwurm.suspendAfterMs);
this.#suspendId = setTimeout(this.#handleSuspend, tokens.suspendAfterMs);
}

#autoResume() {
Expand All @@ -238,10 +241,7 @@ export class Earwurm extends EmittenCommon<ManagerEventMap> {

if (this._state === 'suspended' || this._state === 'interrupted') {
this.#context.resume().catch((error) => {
this.emit('error', [
Earwurm.errorMessage.resume,
getErrorMessage(error),
]);
this.emit('error', [tokens.error.resume, getErrorMessage(error)]);
});
}

Expand All @@ -265,9 +265,7 @@ export class Earwurm extends EmittenCommon<ManagerEventMap> {
this.#library = library;
this._keys = newKeys;

if (!identicalKeys) {
this.emit('library', newKeys, oldKeys);
}
if (!identicalKeys) this.emit('library', newKeys, oldKeys);
}

#setState(value: ManagerState) {
Expand Down
50 changes: 30 additions & 20 deletions src/Sound.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {EmittenCommon} from 'emitten';

import {linearRamp} from './helpers';
import {clamp, msToSec, progressPercentage} from './utilities';
import {clamp, progressPercentage} from './utilities';
import {tokens} from './tokens';
import type {
SoundId,
Expand All @@ -12,14 +12,14 @@ import type {
} from './types';

export class Sound extends EmittenCommon<SoundEventMap> {
private _volume = 1;
private _vol = 1;
private _mute = false;
private _trans = false;
private _speed = 1;
private _state: SoundState = 'created';

readonly #source: AudioBufferSourceNode;
readonly #gainNode: GainNode;
readonly #fadeSec: number = 0;
readonly #progress = {
elapsed: 0,
remaining: 0,
Expand All @@ -41,15 +41,15 @@ export class Sound extends EmittenCommon<SoundEventMap> {
) {
super();

this._volume = config?.volume ?? this._volume;
this.#fadeSec = config?.fadeMs ? msToSec(config.fadeMs) : this.#fadeSec;
this._vol = config?.volume ?? this._vol;
this._trans = Boolean(config?.transitions);

this.#gainNode = this.context.createGain();
this.#source = this.context.createBufferSource();
this.#source.buffer = buffer;

this.#source.connect(this.#gainNode).connect(this.destination);
this.#gainNode.gain.setValueAtTime(this._volume, this.context.currentTime);
this.#gainNode.gain.setValueAtTime(this._vol, this.context.currentTime);
this.#progress.remaining = this.#source.buffer.duration;

// The `ended` event is fired either when the sound has played its full duration,
Expand All @@ -61,27 +61,38 @@ export class Sound extends EmittenCommon<SoundEventMap> {
return this.activeEvents.some((event) => event === 'progress');
}

private get transDuration() {
return this._trans ? tokens.transitionSec : 0;
}

get transitions() {
return this._trans;
}

set transitions(value: boolean) {
this._trans = value;
}

get volume() {
return this._volume;
return this._vol;
}

set volume(value: number) {
const oldVolume = this._volume;
const oldVolume = this._vol;
const newVolume = clamp(0, value, 1);

this._volume = newVolume;
if (oldVolume === newVolume) return;

if (oldVolume !== newVolume) {
this.emit('volume', newVolume);
}
this._vol = newVolume;
this.emit('volume', newVolume);

if (this._mute) return;

const {currentTime} = this.context;
linearRamp(
this.#gainNode.gain,
{from: oldVolume, to: newVolume},
{from: currentTime, to: currentTime + this.#fadeSec},
{from: currentTime, to: currentTime + this.transDuration},
);
}

Expand All @@ -90,20 +101,19 @@ export class Sound extends EmittenCommon<SoundEventMap> {
}

set mute(value: boolean) {
if (this._mute !== value) {
this.emit('mute', value);
}
if (this._mute === value) return;

this._mute = value;
this.emit('mute', value);

const fromValue = value ? this._volume : 0;
const toValue = value ? 0 : this._volume;
const fromValue = value ? this._vol : 0;
const toValue = value ? 0 : this._vol;

const {currentTime} = this.context;
linearRamp(
this.#gainNode.gain,
{from: fromValue, to: toValue},
{from: currentTime, to: currentTime + this.#fadeSec},
{from: currentTime, to: currentTime + this.transDuration},
);
}

Expand Down Expand Up @@ -139,7 +149,7 @@ export class Sound extends EmittenCommon<SoundEventMap> {
linearRamp(
this.#source.playbackRate,
{from: oldSpeed, to: newSpeed},
{from: currentTime, to: currentTime + this.#fadeSec},
{from: currentTime, to: currentTime + this.transDuration},
);
*/

Expand Down
Loading

0 comments on commit 5f7e2b1

Please sign in to comment.