// SPDX-FileCopyrightText: 2026 Reactorcoremeltdown
// SPDX-License-Identifier: GPL-3.0-or-later

package space.rcmd.android.sizzle.model

/**
 * The mixer has exactly [CHANNEL_COUNT] channels, one per tracker track. Each
 * channel routes through one instrument and up to four effects, all referenced
 * by their slot index in the [Toolbox] (tab 3). A value of -1 means "no device".
 */
class Mixer {
    // Buses start on the empty MIDI channel 0 (nothing routed) for a clean-slate
    // new project; the user assigns each bus a MIDI channel (and instrument) as
    // they build the song. Notes on channel 0 sound only through a channel-0 bus.
    val channels: List<MixerChannel> = List(CHANNEL_COUNT) { MixerChannel(it, midiChannel = 0) }

    /** True if ANY channel is soloed — used to mute the non-soloed ones. Indexed
     *  loop (not `any {}`) so it allocates no Iterator; called once per audio block. */
    fun anySolo(): Boolean {
        for (i in channels.indices) if (channels[i].solo) return true
        return false
    }

    companion object {
        const val CHANNEL_COUNT = Pattern.TRACK_COUNT // 4, kept in lockstep
        const val FX_SLOTS = 4
        const val NO_DEVICE = -1
        /** Default linear fader level; the fader also snaps back to this. */
        const val DEFAULT_VOLUME = 0.8f
    }
}

class MixerChannel(
    val index: Int,
    /** Toolbox slot index of the instrument feeding this channel, or -1. */
    var instrumentSlot: Int = Mixer.NO_DEVICE,
    /** Toolbox slot indices of up to four insert effects, -1 where unused. */
    val fxSlots: IntArray = IntArray(Mixer.FX_SLOTS) { Mixer.NO_DEVICE },
    /** MIDI channel (1..16) this mixer channel listens/sends on. */
    var midiChannel: Int = 1,
    /** Linear 0..1 fader. */
    var volume: Float = Mixer.DEFAULT_VOLUME,
    var mute: Boolean = false,
    var solo: Boolean = false,
    /** When on, this channel's post-FX output is soft-clipped to +-1 before it hits
     *  the master sum — a per-bus safety limiter. */
    var limiter: Boolean = false,
) {
    /** Whether this channel should actually produce sound given the solo state. */
    fun audible(anySolo: Boolean): Boolean = !mute && (!anySolo || solo)
}
