SonixMixer

Multi-track audio mixer with synchronized playback.

Quick Start

Kotlin

val mixer = SonixMixer.create()
mixer.addTrack("backing", "/path/to/backing.mp3") // Auto-decodes
mixer.addTrack("vocal", "/path/to/vocal.mp3")
mixer.play()

// Control individual tracks
mixer.setTrackVolume("vocal", 0.5f) // Reduce vocal volume
mixer.fadeTrackVolume("vocal", 0f, 2000) // Fade out over 2 seconds

// Release when done
mixer.release()

Swift

let mixer = SonixMixer.create(
config: .default,
audioSession: .playback
)
await mixer.addTrack(name: "backing", filePath: "/path/to/backing.mp3")
await mixer.addTrack(name: "vocal", filePath: "/path/to/vocal.mp3")
mixer.play()

// Control individual tracks
mixer.setTrackVolume(name: "vocal", volume: 0.5)
mixer.fadeTrackVolume(name: "vocal", targetVolume: 0, durationMs: 2000)

// Release when done
mixer.release()

Builder Pattern (Advanced)

Kotlin

val config = SonixMixerConfig.Builder()
.loopCount(3)
.onPlaybackComplete { println("All loops done!") }
.onLoopComplete { index -> println("Completed loop $index") }
.build()
val mixer = SonixMixer.create(config)

StateFlows

StateFlowTypeDescription
currentTimeLongCurrent playback time in ms
isPlayingBooleanTrue while playing
errorSonixError?Error state

Platform Notes

iOS

  • Uses AVAudioEngine for synchronized playback

  • Automatically configures audio session

Android

  • Software-mixes all tracks into a single AudioTrack (sample-locked by construction)

  • Automatically configures audio focus

Common Pitfalls

  1. addTrack is suspend: Call from coroutine or use addTrack(name, data, rate, channels)

  2. Forgetting to release: Call release() when done

  3. Track names must be unique: Adding a track with same name replaces it

  4. Different sample rates: Tracks are automatically resampled to match

See also

For single-file playback

For click track playback

For configuration options

Types

Link copied to clipboard
object Companion
Link copied to clipboard

Listener interface for playback events.

Properties

Link copied to clipboard

This mixer as a PlaybackInfoProvider for recorder sync.

Link copied to clipboard

Number of completed loop iterations.

Link copied to clipboard
open override val currentTime: StateFlow<Long>

Current playback time in milliseconds.

Link copied to clipboard

Total duration in milliseconds (based on longest track).

Link copied to clipboard
val error: StateFlow<SonixError?>

Error state, null if no error.

Link copied to clipboard
open override val isPlaying: StateFlow<Boolean>

Whether playback is currently active.

Link copied to clipboard

Current loop count setting.

Link copied to clipboard

The route this mixer's audio is currently going out on.

Link copied to clipboard

Pitch shift in semitones (tempo-preserving), applied to every track.

Functions

Link copied to clipboard
suspend fun addTrack(name: String, filePath: String): Boolean

Add track from file path (auto-decodes).

fun addTrack(name: String, data: ByteArray, sampleRate: Int, channels: Int, pitchExempt: Boolean = false): Boolean

Add track from raw PCM data (for advanced use cases).

Link copied to clipboard
open override fun audibleTimeMsAtWallNanos(wallNanos: Long): Long

Audible player time for a wall moment, with the output-latency compensation for the live outputRoute subtracted (SonixPlayer.compensationForRoute) — identical to SonixPlayer.audibleTimeMsAtWallNanos. Both PlaybackController impls must apply it: the live-eval scorer anchors student frames through whichever one plays the reference, and practice plays the reference in this mixer (accompaniment + reference + shruti shift). Compensating only the bare player left the mixer path — i.e. every real practice take — uncompensated.

Link copied to clipboard
fun fadeTrackVolume(name: String, targetVolume: Float, durationMs: Long)

Fade a track's volume smoothly from current to target.

fun fadeTrackVolume(name: String, startVolume: Float, endVolume: Float, durationMs: Long)

Fade a track's volume smoothly from start to end value.

Link copied to clipboard

Get names of all loaded tracks.

Link copied to clipboard
fun hasTrack(name: String): Boolean

Check if a track is loaded.

Link copied to clipboard
open override fun pause()

Pause playback of all tracks.

Link copied to clipboard
open override fun play()

Start playback of all tracks.

Link copied to clipboard
open override fun release()

Release all resources.

Link copied to clipboard
fun removeTrack(name: String)

Remove a track from the mixer.

Link copied to clipboard
fun reset()

Reset playback position to the beginning without stopping.

Link copied to clipboard
open override fun seek(positionMs: Long)

Seek all tracks to a specific position.

Link copied to clipboard

Set a listener for playback events.

Link copied to clipboard

Designate the reference track — typically the lesson track a live evaluator scores against. While one is set, currentTime and audibleTimeMsAtWallNanos ride the DAC-presentation clock of its playback (on Android the single mix sink all tracks share; on iOS the reference player node), so accompaniment and evaluation live on one clock. Without one, time falls back to a wall-clock estimate.

Link copied to clipboard
fun setTrackVolume(name: String, volume: Float)

Set volume for a specific track.

Link copied to clipboard
fun stop()

Stop playback and reset to beginning.