Skip to content

Commit 06dc183

Browse files
committed
feat(synth): play calls AudioContext.resume
It removes a need for AudioPermissionDialog
1 parent 3b18bdd commit 06dc183

10 files changed

Lines changed: 11 additions & 113 deletions

File tree

packages/editor/src/components/audio-permission-dialog/audio-permission-dialog.screen.spec.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

packages/editor/src/components/audio-permission-dialog/audio-permission-dialog.stories.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

packages/editor/src/components/audio-permission-dialog/audio-permission-dialog.tsx

Lines changed: 0 additions & 22 deletions
This file was deleted.

packages/editor/src/components/audio-permission-dialog/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/editor/src/components/editor/editor.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Tooltip } from '@repo/ui-kit/components/tooltip'
44
import { fork } from 'effector'
55
import { Provider } from 'effector-react'
66
import { type FC, memo, useMemo } from 'react'
7-
import { AudioPermissionDialog } from '#components/audio-permission-dialog'
87
import { LoadingIndicator } from '#components/loading-indicator'
98
import { NodeCreationDialog } from '#components/node-creation-dialog'
109
import { type Project, editorModel } from '#model'
@@ -54,7 +53,6 @@ const EditorUI = memo(() => (
5453
<Tooltip.Provider>
5554
<div className="relative size-full">
5655
<LoadingIndicator />
57-
<AudioPermissionDialog />
5856
<NodeCreationDialog />
5957
<EditorPanels />
6058
</div>

packages/editor/src/model/editor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const { $isExternalLoading, $isReadonly, Gate } = gate
3737

3838
const { userRequestedActions } = history
3939

40-
const { $hasAudioPermission, $isPlaying, $playhead, userGrantedAudioPermission, userSetPlayhead } = playback
40+
const { $isPlaying, $playhead, userSetPlayhead } = playback
4141

4242
const { $edges: $synthEdges, $nodes: $synthNodes, $activeNode: $activeSynthNode } = synthTree
4343

@@ -67,7 +67,6 @@ export {
6767
$activeSynthNode,
6868
$beatsPerMinute,
6969
$endTime,
70-
$hasAudioPermission,
7170
$isDirty,
7271
$isExternalLoading,
7372
$isLoaded,
@@ -83,7 +82,6 @@ export {
8382
$timeSignature,
8483
Gate,
8584
userCancelledNodeCreation,
86-
userGrantedAudioPermission,
8785
userHidNotePreview,
8886
userMovedNotePreview,
8987
userMovedSheet,

packages/editor/src/model/playback.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Synth, type SynthState, Time } from '@repo/synth'
22
import { createFactory } from '@withease/factories'
33
import { attach, combine, createEffect, createEvent, createStore, restore, sample, scopeBind } from 'effector'
4-
import { and, condition, interval, not, readonly, spread } from 'patronum'
4+
import { and, condition, interval, readonly, spread } from 'patronum'
55
import type { EditorGate } from './gate'
66

77
export type PlaybackStore = ReturnType<typeof createPlayback>
@@ -15,13 +15,11 @@ export const createPlayback = createFactory((params: Params) => {
1515

1616
const stateChanged = createEvent<SynthState>()
1717

18-
const $hasAudioPermission = createStore(true) // Assume that it's there
1918
const $synth = createStore<Synth | null>(null)
2019
const $state = restore(stateChanged, 'disposed')
2120
const $duration = createStore(Time.note)
2221
const $playhead = createStore(Time.start)
2322

24-
const userGrantedAudioPermission = createEvent()
2523
const userSetPlayhead = createEvent<Time>()
2624

2725
const initialized = createEvent<Synth>()
@@ -33,18 +31,12 @@ export const createPlayback = createFactory((params: Params) => {
3331
const $isIdle = combine($state, state => state === 'idle')
3432
const $isPlaying = combine($state, state => state === 'playing')
3533

36-
const AUDIO_NOT_ALLOWED = new Error()
37-
3834
const initFx = createEffect(() => {
3935
if (typeof window === 'undefined') {
4036
return null
4137
}
4238

4339
const audioContext = new AudioContext()
44-
if (audioContext.state === 'suspended') {
45-
throw AUDIO_NOT_ALLOWED
46-
}
47-
4840
const synth = new Synth(audioContext)
4941

5042
const scopedStateChanged = scopeBind(stateChanged)
@@ -83,20 +75,6 @@ export const createPlayback = createFactory((params: Params) => {
8375
else: disposed,
8476
})
8577

86-
sample({
87-
clock: initFx.failData,
88-
filter: error => error === AUDIO_NOT_ALLOWED,
89-
target: $hasAudioPermission,
90-
fn: () => false,
91-
})
92-
93-
sample({
94-
clock: userGrantedAudioPermission,
95-
filter: not($hasAudioPermission),
96-
target: spread({ hasAudioPermission: $hasAudioPermission, init: initFx }),
97-
fn: () => ({ hasAudioPermission: true, init: undefined }),
98-
})
99-
10078
sample({ clock: initFx.doneData, target: $synth })
10179

10280
sample({
@@ -152,7 +130,6 @@ export const createPlayback = createFactory((params: Params) => {
152130
})
153131

154132
return {
155-
$hasAudioPermission: readonly($hasAudioPermission),
156133
$isIdle: readonly($isIdle),
157134
$isPlaying: readonly($isPlaying),
158135
$playhead: readonly($playhead),
@@ -162,7 +139,6 @@ export const createPlayback = createFactory((params: Params) => {
162139
initialized,
163140
started,
164141
stopped,
165-
userGrantedAudioPermission,
166142
userSetPlayhead,
167143
}
168144
})

packages/synth/src/synth.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,17 @@ export class Synth {
173173
}
174174

175175
/**
176-
* NOTE: may be called multiple times without {@link Synth.stop}
176+
* Starts scheduled time events. Calls {@link Synth#stop} and {@link AudioContext#resume} when needed
177+
* @returns a Promise that resolves then the state becomes 'playing'
177178
*/
178-
play(start = Time.start) {
179+
async play(start = Time.start) {
179180
this.#assertNotDisposed()
180181
this.stop()
181182

183+
if (this.#audioContext instanceof AudioContext && this.#audioContext.state === 'suspended') {
184+
await this.#audioContext.resume()
185+
}
186+
182187
this.#state = 'playing'
183188
this.#playing.emit({ start })
184189
this.#stateChanged.emit({})

packages/synth/src/test/waveform-story.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ export const WaveformStory = <T = {}>(props: Props<T>) => {
5252
sampleRate,
5353
numberOfChannels,
5454
seconds: duration,
55-
audioTree: audioContext => {
55+
audioTree: async audioContext => {
5656
const synth = new Synth(audioContext, { lookAhead: Seconds(0) })
5757
synthTree(synth, synthTreeProps)
5858
secondMarkers.push(...noteMakers.map(time => synth.secondsPerNote.areaBefore(time)))
59-
synth.play()
59+
await synth.play()
6060
},
6161
})
6262

0 commit comments

Comments
 (0)