looklook 0.9.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 ver-
// sion.
//
// LOOKLOOK is distributed in the hope that it will be useful, but WITHOUT ANY WAR-
// RANTY; 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 Affero General Public License along
// with LOOKLOOK. If not, see <https://www.gnu.org/licenses/>.

//! The [`Mode`] type.

/// 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);

	/// 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,
		}
	}
}