looklook 0.5.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::mode::{Builder, SampleDepth};

use std::num::NonZero;

/// A synth mode.
#[derive(Debug)]
#[derive_const(Clone)]
pub struct Mode {
	/// The sampling depth.
	sample_depth: SampleDepth,

	/// The amount of channels.
	channel_count: NonZero<u8>,

	/// The frame width.
	width: NonZero<u16>,

	/// The frame height.
	height: NonZero<u16>,

	/// The total count of frames.
	duration: u64,
}

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

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

	/// Constructs a new mode from a mode builder.
	///
	/// # Panics
	///
	/// This function will panic if the provided builder
	/// has any undefined or illegal field values.
	#[must_use]
	pub const fn from_builder(builder: Builder) -> Self {
		let sample_depth = builder.sample_depth
			.expect("expected sampling depth");

		let sample_depth = SampleDepth::new(sample_depth)
			.expect("sampling depth must be non-zero and at most `8`");

		let channel_count = builder.channel_count
			.expect("expected channel count");

		let channel_count = match channel_count {
			1..Self::MAX_CHANNEL_COUNT => {
				NonZero::<u8>::new(channel_count.truncate()).unwrap()
			}

			_ => {
				panic!("channel count must be non-zero and at most `128`");
			}
		};

		let width = builder.width
			.expect("expected frame width");

		let width = match width {
			1..Self::MAX_WIDTH => {
				NonZero::<u16>::new(width.truncate()).unwrap()
			}

			_ => {
				panic!("frame width must be non-zero and at most `32768`");
			}
		};

		let height = builder.height
			.expect("expected frame height");

		let height = match height {
			1..Self::MAX_WIDTH => {
				NonZero::<u16>::new(height.truncate()).unwrap()
			}

			_ => {
				panic!("frame height must be non-zero and at most `32768`");
			}
		};

		let duration = builder.duration
			.expect("expected render duration");

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

		// FIXME(const-hack): Use formatted assertion in-
		// stead.
		cfg_select! {
			target_pointer_width = "16" => {
				assert!(
					isize::try_from(render_size).is_ok(),
					"render size may not be greater than `32767`",
				);
			}

			target_pointer_width = "32" => {
				assert!(
					isize::try_from(render_size).is_ok(),
					"render size may not be greater than `2147483647`",
				);
			}

			target_pointer_width = "64" => {
				assert!(
					isize::try_from(render_size).is_ok(),
					"render size may not be greater than `9223372036854775807`",
				);
			}

			_ => {
				assert!(
					isize::try_from(render_size).is_ok(),
					"render size may not be greater than `isize::MAX`",
				);
			}
		}

		Self { sample_depth, channel_count, width, height, duration }
	}

	/// Computes the amount of samples suitable
	/// specified by this mode.
	#[inline]
	#[must_use]
	pub const fn sample_count(&self) -> u64 {
		u64::from(self.channel_count().get())
			.wrapping_mul(self.width().get().into())
			.wrapping_mul(self.height().get().into())
			.wrapping_mul(self.duration)
	}

	/// Retrieves the sampling depth.
	#[inline(always)]
	#[must_use]
	pub const fn sample_depth(&self) -> SampleDepth {
		self.sample_depth
	}

	/// Retrieves the amount of channels.
	#[inline]
	#[must_use]
	pub const fn channel_count(&self) -> NonZero<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) -> NonZero<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) -> NonZero<u32> {
		self.height.into()
	}

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

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