DiamondMusicClip.cs
using System;
namespace Diamonds;
public enum DiamondMusicLayer { Pad, Bass, Drums, Harmony, Lead }
/// <summary>
/// One looping audio clip on the shared 150 BPM timeline. Its loop length is rounded to whole bars and it
/// plays aligned to the run's first bar, so any clip can swap in on a bar line.
/// </summary>
public record struct DiamondMusicClip()
{
public SoundFile Sound { get; set; }
public DiamondMusicLayer Layer { get; set; }
/// <summary>Lowest intensity tier this clip plays in.</summary>
[Range( 0, 3 )] public int MinTier { get; set; } = 0;
/// <summary>Highest intensity tier this clip plays in.</summary>
[Range( 0, 3 )] public int MaxTier { get; set; } = 3;
/// <summary>Only plays in runs whose motif is listed here. Empty plays in every run.</summary>
public List<string> Motifs { get; set; } = new();
/// <summary>Relative chance among the clips eligible for this layer. Zero never plays.</summary>
[Range( 0, 10 )] public float Weight { get; set; } = 1;
/// <summary>Level trim against the other clips, in decibels. Zero plays the file as rendered.</summary>
[Title( "Volume (dB)" ), Range( -24, 12 )] public float VolumeDb { get; set; }
/// <summary>Linear gain for <see cref="VolumeDb"/>.</summary>
public readonly float Gain() => VolumeDb == 0 ? 1 : MathF.Pow( 10, VolumeDb / 20 );
public readonly bool Fits( int tier, string motif )
{
if ( tier < MinTier || tier > MaxTier || Weight <= 0 ) return false;
if ( Motifs is null || Motifs.Count == 0 ) return true;
foreach ( var tag in Motifs )
if ( string.Equals( tag?.Trim(), motif, StringComparison.OrdinalIgnoreCase ) ) return true;
return false;
}
}