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
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ class PatternComposer(
private var vibrationEffect: VibrationEffect? = null
private var audioBuffer: ByteArray? = null

private var soundPlayer: AudioHapticPlayer? = null
internal var soundPlayer: AudioHapticPlayer? = null
private set
private var useCoupledHaptics = false

/** [fromMs] starts the pattern that far into its own timeline. */
@JvmOverloads
fun parsePattern(hapticsData: PatternData, fromMs: Long = 0L) {
releaseSound()
val seekedPattern = PatternSeek.patternFrom(hapticsData, fromMs)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrationEffect = try {
Expand Down Expand Up @@ -59,7 +61,6 @@ class PatternComposer(
parsePattern(hapticsData, fromMs)

val seekedSound = PatternSeek.soundFrom(sound, fromMs)
soundPlayer?.release()

useCoupledHaptics =
seekedSound.hapticChannels && isOggUri(seekedSound.uri) && engine.supportsAudioCoupledHaptics()
Expand Down Expand Up @@ -100,8 +101,13 @@ class PatternComposer(
}

fun release() {
releaseSound()
}

private fun releaseSound() {
soundPlayer?.release()
soundPlayer = null
useCoupledHaptics = false
}

private fun summarizePattern(hapticsData: PatternData): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.swmansion.pulsar.composers

import com.swmansion.pulsar.audio.AudioSimulator
import com.swmansion.pulsar.haptics.HapticEngineWrapper
import com.swmansion.pulsar.types.CompatibilityMode
import com.swmansion.pulsar.types.ContinuousPattern
import com.swmansion.pulsar.types.ConfigPoint
import com.swmansion.pulsar.types.PatternData
import com.swmansion.pulsar.types.SoundData
import com.swmansion.pulsar.types.ValuePoint
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class RobolectricPatternComposerSoundTest {

private fun composer(): PatternComposer {
val app = RuntimeEnvironment.getApplication()
return PatternComposer(HapticEngineWrapper(app), AudioSimulator(CompatibilityMode.STANDARD_SUPPORT))
}

private fun pattern() = PatternData(
continuousPattern = ContinuousPattern(
amplitude = listOf(ValuePoint(0L, 1f), ValuePoint(100L, 0f)),
frequency = listOf(ValuePoint(0L, 0.5f)),
),
discretePattern = listOf(ConfigPoint(0L, 1f, 0.3f)),
)

@Test
fun reparsingWithoutSoundDropsThePreviousSound() {
val composer = composer()

composer.parsePatternWithSound(pattern(), SoundData(uri = "/pulsar-test-sound.wav"))
assertNotNull(composer.soundPlayer)

composer.parsePattern(pattern())
assertNull(composer.soundPlayer)
}

@Test
fun reparsingWithSoundReplacesThePreviousSound() {
val composer = composer()

composer.parsePatternWithSound(pattern(), SoundData(uri = "/pulsar-test-sound.wav"))
val first = composer.soundPlayer

composer.parsePatternWithSound(pattern(), SoundData(uri = "/pulsar-other-sound.wav"))

assertNotNull(composer.soundPlayer)
assert(composer.soundPlayer !== first)
}

@Test
fun releaseDropsTheSound() {
val composer = composer()

composer.parsePatternWithSound(pattern(), SoundData(uri = "/pulsar-test-sound.wav"))
composer.release()

assertNull(composer.soundPlayer)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class PulsarPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
override fun onDetachedFromActivityForConfigChanges() {
activity = null
pulsar = null
patternComposers.clear()
releaseComposers()
}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
Expand All @@ -57,6 +57,14 @@ class PulsarPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
override fun onDetachedFromActivity() {
activity = null
pulsar = null
releaseComposers()
}

private fun releaseComposers() {
patternComposers.values.forEach {
it.stop()
it.release()
}
patternComposers.clear()
}

Expand Down Expand Up @@ -380,7 +388,10 @@ class PulsarPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"PatternComposer_release" -> {
val composerId = call.argument<Int>("composerId")
?: return result.error("INVALID_ARGS", "composerId required", null)
patternComposers.remove(composerId)?.stop()
patternComposers.remove(composerId)?.let {
it.stop()
it.release()
}
result.success(null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ private class AndroidPulsarHandle(
private val nativePulsar: AndroidPulsar,
) : PulsarPlatformHandle {
private val presetsHandle by lazy { AndroidPresetsHandle(nativePulsar.getPresets()) }
private val patternComposerHandle by lazy { AndroidPatternComposerHandle(nativePulsar.getPatternComposer()) }

override fun presets(): PulsarPresetsHandle = presetsHandle

override fun patternComposer(): PatternComposerHandle = patternComposerHandle
override fun patternComposer(): PatternComposerHandle =
AndroidPatternComposerHandle(nativePulsar.getPatternComposer())

override fun realtimeComposer(): RealtimeComposerHandle =
AndroidRealtimeComposerHandle(nativePulsar.getRealtimeComposer())
Expand Down Expand Up @@ -362,6 +362,8 @@ private class AndroidPatternComposerHandle(
override fun playAudioOnly() = composer.playAudioOnly()

override fun stop() = composer.stop()

override fun dispose() = composer.release()
}

private class AndroidRealtimeComposerHandle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ internal class IOSPatternComposerHandle(
private var audioResourceId: CHHapticAudioResourceID? = null

override fun parsePattern(pattern: PatternData, fromMs: Long) {
releaseAudio()
releaseCurrentPattern()
parse(PatternSeek.patternFrom(pattern, fromMs), audioEvent = null)
}

override fun parsePatternWithSound(pattern: PatternData, sound: SoundData, fromMs: Long) {
releaseAudio()
releaseCurrentPattern()
parse(
PatternSeek.patternFrom(pattern, fromMs),
audioEvent = makeAudioEvent(PatternSeek.soundFrom(sound, fromMs)),
Expand Down Expand Up @@ -182,7 +182,14 @@ internal class IOSPatternComposerHandle(
}.onFailure { log("could not slice audio window: ${it.message}") }.getOrNull()
}

private fun releaseAudio() {
private fun releaseCurrentPattern() {
continuousPlayerId?.let(engine::removePlayer)
discretePlayerId?.let(engine::removePlayer)
continuousPlayerId = null
discretePlayerId = null
continuousPattern = null
discretePattern = null

audioResourceId?.let { engine.unregisterAudioResource(it) }
audioResourceId = null
tempAudioURL?.path?.let { path ->
Expand Down Expand Up @@ -223,14 +230,8 @@ internal class IOSPatternComposerHandle(

override fun dispose() {
stop()
continuousPlayerId?.let(engine::removePlayer)
discretePlayerId?.let(engine::removePlayer)
continuousPlayerId = null
discretePlayerId = null
continuousPattern = null
discretePattern = null
releaseCurrentPattern()
audioBuffer = null
hasSound = false
releaseAudio()
}
}
Loading