looklook 0.6.0

Descriptive signal synthesiser.
// Copyright 2026 Gabriel Bjørnager Jensen.
//
// This file is part of LOOKLOOK.
//
// LOOKLOOK is free software: you can redistribute
// it and/or modify it under the terms of the GNU
// Affero General Public License as published by
// the Free Software Foundation, either version 3
// of the License, or (at your option) any later
// version.
//
// LOOKLOOK is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Af-
// fero General Public License along with LOOKLOOK.
// If not, see <https://www.gnu.org/licenses/>.

//! The [`Mode`] type.

use crate::emu::ffi::ll_mode;

use std::assert_matches;

/// A signal mode.
#[derive(Debug)]
#[derive_const(Clone)]
pub struct Mode {
	/// The amount of channels.
	channel_count: u8,

	/// The frame width.
	width: u16,

	/// The frame height.
	///
	/// For non-video synthing, it is recommended to set
	/// this value to `1`.
	height: u16,

	/// The total frame count.
	frame_count: u32,
}

impl Mode {
	/// The minimum channel count.
	pub const MIN_CHANNEL_COUNT: u32 = 1;

	/// The maximum channel count.
	pub const MAX_CHANNEL_COUNT: u32 = 2_u32.pow(u8::BITS - 1);

	/// The minimum frame width/height.
	pub const MIN_WIDTH: u32 = 1;

	/// The maximum frame width/height.
	pub const MAX_WIDTH: u32 = 2_u32.pow(u16::BITS - 1);

	/// The minimum frame_count.
	pub const MIN_FRAME_COUNT: u32 = 0;

	/// The maximum frame_count.
	pub const MAX_FRAME_COUNT: u32 = 2_u32.pow(u32::BITS - 1);

	/// Constructs a new mode from a C-defined mode.
	///
	/// # Panics
	///
	/// This function will panic if the provided builder
	/// has any undefined or illegal field values.
	#[must_use]
	pub fn from_ffi(mode: &ll_mode) -> Self {
		assert_matches!(
			mode.channel_count,
			1..=Self::MAX_CHANNEL_COUNT,
			"channel count must be non-zero and at most `{}`",
			Self::MAX_CHANNEL_COUNT,
		);

		assert_matches!(
			mode.width,
			1..=Self::MAX_WIDTH,
			"frame width must be non-zero and at most `{}`",
			Self::MAX_WIDTH,
		);

		assert_matches!(
			mode.height,
			1..=Self::MAX_WIDTH,
			"frame height must be non-zero and at most `{}`",
			Self::MAX_WIDTH,
		);

		assert_matches!(
			mode.frame_count,
			1..=Self::MAX_FRAME_COUNT,
			"frame count must be non-zero and at most `{}`",
			Self::MAX_FRAME_COUNT,
		);

		// NOTE: `u128` is large enough to contain the
		// largest, theoretically-possible sample count:
		//
		// channel_count <=                                     128
		// width         <=                                   32768
		// height        <=                                   32768
		// duration      <=                              2147483648
		// render_size   <=                   295147905179352825856
		// u128::MAX     == 340282366920938463463374607431768211455
		let render_size = u128::from(mode.channel_count)
				.wrapping_mul(mode.channel_count.into())
				.wrapping_mul(mode.width.into())
				.wrapping_mul(mode.height.into())
				.wrapping_mul(mode.frame_count.into());

		assert_matches!(
			isize::try_from(render_size),
			Ok(_),
			"render size `{render_size}` may not be greater than `{}`",
			isize::MAX,
		);

		Self {
			channel_count: mode.channel_count as u8,
			width:         mode.width as u16,
			height:        mode.height as u16,
			frame_count:   mode.frame_count,
		}
	}

	/// Computes the amount of samples specified by this
	/// mode.
	#[inline]
	#[must_use]
	pub const fn sample_count(&self) -> usize {
		let channel_count = usize::from(self.channel_count);
		let width         = usize::from(self.width);
		let height        = usize::from(self.height);

		// NOTE: We require that `usize` is at least as
		// wide as `u32`.
		let frame_count = self.frame_count as usize;

		// NOTE: `Mode` constructors guarantee that the
		// result is less than or equal to `isize::MAX`.
		let result = channel_count
			.wrapping_mul(width)
			.wrapping_mul(height)
			.wrapping_mul(frame_count);

		debug_assert!(isize::try_from(result).is_ok());

		result
	}

	/// Retrieves the amount of channels.
	#[inline]
	#[must_use]
	pub const fn channel_count(&self) -> u32 {
		self.channel_count.into()
	}

	/// Retrieves the frame width.
	///
	/// For non-video modes, this is always equal to
	/// `1`.
	#[inline]
	#[must_use]
	pub const fn width(&self) -> u32 {
		self.width.into()
	}

	/// Retrieves the frame height.
	///
	/// For non-video modes, this is always equal to
	/// `1`.
	#[inline]
	#[must_use]
	pub const fn height(&self) -> u32 {
		self.height.into()
	}

	/// Retrieves the total frame count.
	#[inline(always)]
	#[must_use]
	pub const fn frame_count(&self) -> u32 {
		self.frame_count
	}

	/// Computes the amount of samples per frame.
	#[inline]
	#[must_use]
	pub const fn frame_stride(&self) -> u64 {
		u64::from(self.channel_count())
			.wrapping_mul(self.width().into())
			.wrapping_mul(self.height().into())
	}
}

const impl Default for Mode {
	/// Constructs a mode with a sample count of zero.
	#[inline(always)]
	fn default() -> Self {
		Self {
			channel_count: 1,
			width:         1,
			height:        1,
			frame_count:   0,
		}
	}
}