diff --git a/.changeset/rich-bags-train.md b/.changeset/rich-bags-train.md new file mode 100644 index 0000000..8be5e2b --- /dev/null +++ b/.changeset/rich-bags-train.md @@ -0,0 +1,5 @@ +--- +"earwurm": patch +--- + +Earwurm state is now set to the `context.state` upon initialization. diff --git a/docs/api.md b/docs/api.md index b023b42..16f5b53 100644 --- a/docs/api.md +++ b/docs/api.md @@ -53,7 +53,7 @@ manager.resume(); // suspend and resume the `AudioContext` behind the scenes. manager.suspend(); -// Stop and destory all sounds within each `Stack`. +// Stop and destroy all sounds within each `Stack`. // Each `Stack` will remain available in the library // to continue interacting with. manager.stop(); @@ -139,7 +139,7 @@ manager.activeEvents; // Event called whenever the `mute` property changes. (event: 'mute', listener: (muted: boolean) => void) -// Event called whenever an error is occured on the `AudioContext`. +// Event called whenever an error occurs on the `AudioContext`. // This could be a result of: failed to resume, failed to close. (event: 'error', listener: (messages: CombinedErrorMessage) => void) ``` @@ -150,7 +150,7 @@ There are no static members or relevant tokens exposed at the `manager` level. ## Stack API -Capabilities of an indivudual `Earwurm > Stack`. +Capabilities of an individual `Earwurm > Stack`. **Initialization:** @@ -179,7 +179,7 @@ soundStack.has('SoundId'); // to `created`. Accepts an optional `id`. Otherwise, `id` is auto // assigned by combining the `id` passed to `new Stack()` with // the total number of sounds created during this stack’s life. -// Will return a `Promise` which resolves to the`Sound` instance. +// Will return a `Promise` which resolves to the `Sound` instance. // You can either chain with `.then()`, or `await` and call // `.play()` once the `Promise` has resolved. soundStack.prepare(optionalId); @@ -265,7 +265,7 @@ soundStack.activeEvents; // Event called whenever the `mute` property changes. (event: 'mute', listener: (muted: boolean) => void) -// Event called whenever an error is occured on the `Stack`. +// Event called whenever an error occurs on the `Stack`. // This could be a result of: failed to load the `path`. (event: 'error', listener: ({id, message}: StackError) => void) ``` diff --git a/docs/contributing.md b/docs/contributing.md index bd35db3..8920a88 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -47,7 +47,6 @@ For a list of all the available commands, please see the root `package.json`. Ov - All `localhost` addresses for running apps will be printed to your terminal when you run `start`. - `lint` checks the codebase for `eslint` errors, but does not fix them. - `lint:fix` automatically fixes any `eslint` errors that can be programatically resolved. -- `format` originally checked the codebase for `prettier` errors, but that is now handled by `eslint` and is performed via the `lint` tasks. - `type-check` checks the codebase for any TypeScript errors, but does not fix them. - `test` fires up the `vitest` server and runs all `*.test.ts` files. - `clean` will delete any `dist` and `coverage` folders. @@ -86,7 +85,7 @@ If you want to preview the production build of the `website`, you can run the fo ```sh pnpm build -pnpm --filter website preview +pnpm preview ``` The `website` should now be available on a locally reachable address, allowing you to test on a `mobile` device. diff --git a/docs/conversion.md b/docs/conversion.md index 8e1cac3..8f48fc7 100644 --- a/docs/conversion.md +++ b/docs/conversion.md @@ -1,8 +1,10 @@ # Converting audio -This documentation will NOT describe how to record audio and export it from any editing software. +> This documentation will NOT describe how to record audio and export it from any editing software. For that, you may want to read: -Quite simply, if you have existing audio files that are not already in `webm` format, you can convert them using a command-line tool called [`ffmpeg`](https://ffmpeg.org/). You can learn some audio-specific commands from the [`ffmpeg` documentation](https://ffmpeg.org/ffmpeg.html#Audio-Options). +If you have existing audio files that are not already in `webm` format, you can convert them using a command-line tool called [`ffmpeg`](https://ffmpeg.org/). If you are on a Mac and using Homebrew, `ffmpeg` is [very easy to install](https://formulae.brew.sh/formula/ffmpeg) as a command-line tool. + +You can learn some audio-specific commands from the [`ffmpeg` documentation](https://ffmpeg.org/ffmpeg.html#Audio-Options). Using `ffmpeg`, you can select an audio file as an “input source”, pass some options that tell `ffmpeg` how you want to transform the audio, and point to an “output source” to save the converted asset. @@ -30,3 +32,18 @@ ffmpeg -i input-file.wav -dash 1 -map_metadata -1 -acodec libopus -ar 48000 -ac - `-ab 96k`: Set the bitrate. `96k` for 2-channel `stereo` sound is probably good, but `128k` might be preferrable. - `-f webm`: Specifiy the output format. - `{output-file.ext}`: End with the file name _(including extension)_ you wish to save. + +## Tricks + +`ffmpeg` may not always be able to convert your source assets. When this happens, it is usually because of some file format conflict. + +Sometimes it helps to use another tool to convert that source asset. If you are on a Mac, you can usually `right-click` an audio file and select `Encode Selected Audio Files`. From there, just choose the highest quality output format. More than likely, this will be `Apple Lossless`. + +As long as that conversion works, you should then be able to point `ffmpeg` at that newly encoded file and proceed with the `.webm` conversion. + +## Resources + +Below are a few places you can go to get royalty-free audio assets: + +- +- diff --git a/docs/design.md b/docs/design.md index 095a911..20b8796 100644 --- a/docs/design.md +++ b/docs/design.md @@ -17,8 +17,8 @@ The quickest way to describe the “structure” of an `Earwurm` is: - The `Manager` contains a library of “stacks”. - A `Stack` contains a queue of “sounds”. - A `Sound` is created and queued within the `Stack`. - - The queue builds up upon `stack.prepare()`. - - The queue decreases upon `sound.stop()` _(or `ended` event)_. + - The queue increases when calling: `stack.prepare()`. + - The queue decreases when calling: `sound.stop()` _(or `ended` event)_. - The queue has a maximum size. - A `Sound` is automatically destroyed as new sounds are added that would exceed that maximum size. - As sounds begin playing, their output travels through a “chain of gain nodes”, the final “destination” being your device’s speakers. @@ -74,16 +74,21 @@ A rough pseudo-code visualization: ```tsx const soundStack = manager.get('MySound'); -const sound = await soundStack?.prepare(); + +// We then make 3 consecutive calls to `prepare` +// on the same `Stack`, produces 3 instances of that `Sound`: +const sound1 = await soundStack?.prepare(); +const sound2 = await soundStack?.prepare(); +const sound3 = await soundStack?.prepare(); // We can imagine some internal code that looks like: const latestId = totalSoundsCreated + 1; const updatedStack = [...stack.queue, new Sound(latestId)]; -// We then make 3 consecutive calls to `play` on the same `Sound`: -sound?.play(); -sound?.play(); -sound?.play(); +// We then make 3 consecutive calls to `play` each `Sound`: +sound1.play(); +sound2.play(); +sound3.play(); // If we were to inspect that `Entry` at this exact moment, // it might look something like this: diff --git a/pkg/earwurm/src/Earwurm.ts b/pkg/earwurm/src/Earwurm.ts index ef22d15..c33c543 100644 --- a/pkg/earwurm/src/Earwurm.ts +++ b/pkg/earwurm/src/Earwurm.ts @@ -14,21 +14,21 @@ import type { } from './types'; export class Earwurm extends EmittenCommon { - private _vol = 1; - private _mute = false; - private _trans = false; - private _playing = false; - private _keys: StackId[] = []; - private _state: ManagerState = 'suspended'; - readonly #context = new AudioContext(); readonly #gainNode = this.#context.createGain(); readonly #request: ManagerConfig['request']; #library: Stack[] = []; + private _vol = 1; + private _mute = false; + private _trans = false; + private _playing = false; + private _keys: StackId[] = []; + // If the `AudioContext` is `running` upon initialization, // then it should be safe to mark audio as “unlocked”. + private _state: ManagerState = this.#context.state || 'suspended'; private _unlocked = this.#context.state === 'running'; constructor(config?: ManagerConfig) { @@ -140,6 +140,7 @@ export class Earwurm extends EmittenCommon { const existingStack = this.get(id); const identicalStack = existingStack?.path === path; + // Only skip updating the `Stack` if both `id + path` are the same. if (identicalStack) return collection; newKeys.push(id); diff --git a/pkg/earwurm/src/Sound.ts b/pkg/earwurm/src/Sound.ts index 35efeaf..9f7c0b2 100644 --- a/pkg/earwurm/src/Sound.ts +++ b/pkg/earwurm/src/Sound.ts @@ -306,7 +306,7 @@ export class Sound extends EmittenCommon { // was "stopped" or came to a natural "end". // We should also include the `iterations` value here so // that we can distinguish from a `loop > stop` having - // played to completion at least one. Lastly, we might + // played to completion at least once. Lastly, we might // also need to indicate if this was a "scratch buffer". this.emit('ended', { id: this.id, diff --git a/pkg/earwurm/vite.config.ts b/pkg/earwurm/vite.config.ts index d30b169..34a4968 100644 --- a/pkg/earwurm/vite.config.ts +++ b/pkg/earwurm/vite.config.ts @@ -4,8 +4,8 @@ import dts from 'vite-plugin-dts'; import pkg from './package.json'; -// TODO: We may need a unique `vitest.config.ts` file for this -// package... but simply adding that file does not appear to work. +// TODO: We may need a unique `vitest.config.ts` file. +// https://vitest.dev/guide/#workspaces-support export default defineConfig({ build: { lib: {