From 77d7001df1d5e4c55f003d3a6d54fb70b0fc628d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 00:24:51 +0000 Subject: [PATCH] refactor(quantities): align audio-engineering types with the vectors branch - Move Cents, Decibels, Gain, NormalizedParameter, ParameterTaper, Percent, QFactor and Semitones from ktsu.Semantics to ktsu.Semantics.Quantities, alongside every other quantity type. - Delete the audio-specific Ratio: the generated Dimensionless Ratio is the single ratio currency. Decibels.ToPower(), Cents/Semitones frequency-ratio conversions and Percent.FromRatio/ToRatio now exchange the generated type, and a Dimensionless x Dimensionless relationship supplies Ratio * Ratio (same-type division yields the storage value, as for every quantity). https://claude.ai/code/session_01AUgbNJjDBDCwcbUfPK3pGW --- .../AudioEngineering/Cents.cs | 5 +- .../AudioEngineering/Decibels.cs | 4 +- Semantics.Quantities/AudioEngineering/Gain.cs | 2 +- .../AudioEngineering/NormalizedParameter.cs | 2 +- .../AudioEngineering/ParameterTaper.cs | 2 +- .../AudioEngineering/Percent.cs | 10 +- .../AudioEngineering/QFactor.cs | 2 +- .../AudioEngineering/Ratio.cs | 103 ------------------ .../AudioEngineering/Semitones.cs | 5 +- .../Ratio.g.cs | 8 ++ .../SignedRatio.g.cs | 8 ++ .../Metadata/dimensions.json | 4 +- Semantics.Test/AudioEngineeringTests.cs | 14 ++- docs/migration-guide-2.0.md | 16 +-- 14 files changed, 60 insertions(+), 125 deletions(-) delete mode 100644 Semantics.Quantities/AudioEngineering/Ratio.cs diff --git a/Semantics.Quantities/AudioEngineering/Cents.cs b/Semantics.Quantities/AudioEngineering/Cents.cs index a53a905..5b32447 100644 --- a/Semantics.Quantities/AudioEngineering/Cents.cs +++ b/Semantics.Quantities/AudioEngineering/Cents.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; using System.Globalization; using System.Numerics; @@ -43,6 +43,7 @@ public readonly record struct Cents(T Value) : IComparable> /// A new . public static Cents FromFrequencyRatio(Ratio ratio) { + ArgumentNullException.ThrowIfNull(ratio); double r = double.CreateChecked(ratio.Value); double cents = 1200.0 * Math.Log2(r); return new(T.CreateChecked(cents)); @@ -62,7 +63,7 @@ public Ratio ToFrequencyRatio() { double cents = double.CreateChecked(Value); double ratio = Math.Pow(2.0, cents / 1200.0); - return new(T.CreateChecked(ratio)); + return Ratio.Create(T.CreateChecked(ratio)); } /// Adds two intervals. diff --git a/Semantics.Quantities/AudioEngineering/Decibels.cs b/Semantics.Quantities/AudioEngineering/Decibels.cs index 4217d44..ce34ffa 100644 --- a/Semantics.Quantities/AudioEngineering/Decibels.cs +++ b/Semantics.Quantities/AudioEngineering/Decibels.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; using System.Globalization; using System.Numerics; @@ -84,7 +84,7 @@ public Ratio ToPower() { double db = double.CreateChecked(Value); double linear = Math.Pow(10.0, db / 10.0); - return new(T.CreateChecked(linear)); + return Ratio.Create(T.CreateChecked(linear)); } /// Adds two decibel levels (cascading two stages multiplies their linear gains). diff --git a/Semantics.Quantities/AudioEngineering/Gain.cs b/Semantics.Quantities/AudioEngineering/Gain.cs index a09b7d2..0073cf6 100644 --- a/Semantics.Quantities/AudioEngineering/Gain.cs +++ b/Semantics.Quantities/AudioEngineering/Gain.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; using System.Globalization; using System.Numerics; diff --git a/Semantics.Quantities/AudioEngineering/NormalizedParameter.cs b/Semantics.Quantities/AudioEngineering/NormalizedParameter.cs index 6b01f06..adc60ed 100644 --- a/Semantics.Quantities/AudioEngineering/NormalizedParameter.cs +++ b/Semantics.Quantities/AudioEngineering/NormalizedParameter.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; using System.Numerics; diff --git a/Semantics.Quantities/AudioEngineering/ParameterTaper.cs b/Semantics.Quantities/AudioEngineering/ParameterTaper.cs index 35cea40..14e87f9 100644 --- a/Semantics.Quantities/AudioEngineering/ParameterTaper.cs +++ b/Semantics.Quantities/AudioEngineering/ParameterTaper.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; /// /// Describes how a maps a host-normalized position in diff --git a/Semantics.Quantities/AudioEngineering/Percent.cs b/Semantics.Quantities/AudioEngineering/Percent.cs index f26fd47..19c71e1 100644 --- a/Semantics.Quantities/AudioEngineering/Percent.cs +++ b/Semantics.Quantities/AudioEngineering/Percent.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; using System.Globalization; using System.Numerics; @@ -39,7 +39,11 @@ public readonly record struct Percent(T Value) : IComparable> /// /// The ratio to convert. /// A new . - public static Percent FromRatio(Ratio ratio) => ratio.ToPercent(); + public static Percent FromRatio(Ratio ratio) + { + ArgumentNullException.ThrowIfNull(ratio); + return FromFraction(ratio.Value); + } /// /// Converts this percentage to a fraction in the range [0, 1] (50% → 0.5). @@ -51,7 +55,7 @@ public readonly record struct Percent(T Value) : IComparable> /// Converts this percentage to a ratio (100% → 1). /// /// The equivalent . - public Ratio ToRatio() => new(ToFraction()); + public Ratio ToRatio() => Ratio.Create(ToFraction()); /// public int CompareTo(Percent other) => Value.CompareTo(other.Value); diff --git a/Semantics.Quantities/AudioEngineering/QFactor.cs b/Semantics.Quantities/AudioEngineering/QFactor.cs index b5954c5..034d598 100644 --- a/Semantics.Quantities/AudioEngineering/QFactor.cs +++ b/Semantics.Quantities/AudioEngineering/QFactor.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; using System.Globalization; using System.Numerics; diff --git a/Semantics.Quantities/AudioEngineering/Ratio.cs b/Semantics.Quantities/AudioEngineering/Ratio.cs deleted file mode 100644 index 320ee66..0000000 --- a/Semantics.Quantities/AudioEngineering/Ratio.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) ktsu.dev -// All rights reserved. -// Licensed under the MIT license. - -namespace ktsu.Semantics; - -using System.Globalization; -using System.Numerics; - -/// -/// Represents a dimensionless ratio between two like quantities. -/// -/// -/// A ratio of 1 means the two quantities are equal. Ratios are the natural currency of audio -/// engineering parameters (gain, mix, feedback, depth) and convert cleanly to and from -/// . -/// -/// The floating-point storage type. -/// The ratio value. -public readonly record struct Ratio(T Value) : IComparable> - where T : struct, INumber -{ - /// Gets a ratio of one (unity). - public static Ratio Unity => new(T.One); - - /// Gets a ratio of zero. - public static Ratio Zero => new(T.Zero); - - /// - /// Creates a ratio from a raw value. - /// - /// The ratio value (1 = unity). - /// A new . - public static Ratio Create(T value) => new(value); - - /// - /// Creates a ratio from a percentage. - /// - /// The percentage (100% = unity). - /// A new . - public static Ratio FromPercent(Percent percent) => percent.ToRatio(); - - /// - /// Converts this ratio to a percentage (1 → 100%). - /// - /// The equivalent . - public Percent ToPercent() => new(Value * T.CreateChecked(100)); - - /// Multiplies two ratios. - /// The left ratio. - /// The right ratio. - /// The product ratio. - public static Ratio operator *(Ratio left, Ratio right) => new(left.Value * right.Value); - - /// Divides two ratios. - /// The numerator ratio. - /// The denominator ratio. - /// The quotient ratio. - public static Ratio operator /(Ratio left, Ratio right) => new(left.Value / right.Value); - - /// Multiplies two ratios (friendly alternate for operator *). - /// The left ratio. - /// The right ratio. - /// The product ratio. - public static Ratio Multiply(Ratio left, Ratio right) => left * right; - - /// Divides two ratios (friendly alternate for operator /). - /// The numerator ratio. - /// The denominator ratio. - /// The quotient ratio. - public static Ratio Divide(Ratio left, Ratio right) => left / right; - - /// - public int CompareTo(Ratio other) => Value.CompareTo(other.Value); - - /// Determines whether one ratio is less than another. - /// The left ratio. - /// The right ratio. - /// if is less than . - public static bool operator <(Ratio left, Ratio right) => left.CompareTo(right) < 0; - - /// Determines whether one ratio is greater than another. - /// The left ratio. - /// The right ratio. - /// if is greater than . - public static bool operator >(Ratio left, Ratio right) => left.CompareTo(right) > 0; - - /// Determines whether one ratio is less than or equal to another. - /// The left ratio. - /// The right ratio. - /// if is less than or equal to . - public static bool operator <=(Ratio left, Ratio right) => left.CompareTo(right) <= 0; - - /// Determines whether one ratio is greater than or equal to another. - /// The left ratio. - /// The right ratio. - /// if is greater than or equal to . - public static bool operator >=(Ratio left, Ratio right) => left.CompareTo(right) >= 0; - - /// Returns a culture-invariant string representation of this ratio. - /// The ratio formatted with an x suffix. - public override string ToString() => string.Create(CultureInfo.InvariantCulture, $"{Value}x"); -} diff --git a/Semantics.Quantities/AudioEngineering/Semitones.cs b/Semantics.Quantities/AudioEngineering/Semitones.cs index f94725d..90a7b34 100644 --- a/Semantics.Quantities/AudioEngineering/Semitones.cs +++ b/Semantics.Quantities/AudioEngineering/Semitones.cs @@ -2,7 +2,7 @@ // All rights reserved. // Licensed under the MIT license. -namespace ktsu.Semantics; +namespace ktsu.Semantics.Quantities; using System.Globalization; using System.Numerics; @@ -47,6 +47,7 @@ public readonly record struct Semitones(T Value) : IComparable> /// A new . public static Semitones FromFrequencyRatio(Ratio ratio) { + ArgumentNullException.ThrowIfNull(ratio); double r = double.CreateChecked(ratio.Value); double semitones = 12.0 * Math.Log2(r); return new(T.CreateChecked(semitones)); @@ -66,7 +67,7 @@ public Ratio ToFrequencyRatio() { double semitones = double.CreateChecked(Value); double ratio = Math.Pow(2.0, semitones / 12.0); - return new(T.CreateChecked(ratio)); + return Ratio.Create(T.CreateChecked(ratio)); } /// Adds two intervals. diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Ratio.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Ratio.g.cs index 7817d8b..3e54d1d 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Ratio.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Ratio.g.cs @@ -95,6 +95,14 @@ public record Ratio : PhysicalQuantity, T>, IVector0, T> /// Magnitude subtraction stays a magnitude (per the unified-vector model). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static Ratio operator -(Ratio left, Ratio right) => Create(T.Abs(left.Quantity - right.Quantity)); +/// + /// Multiplies Ratio by Ratio to produce Ratio. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static Ratio operator *(Ratio left, Ratio right) => Multiply>(left, right); +/// + /// Multiplies Ratio by SignedRatio to produce SignedRatio. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static SignedRatio operator *(Ratio left, SignedRatio right) => Multiply>(left, right); /// /// Divides Ratio by Duration to produce Frequency. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SignedRatio.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SignedRatio.g.cs index 2bd088c..633e74f 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SignedRatio.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SignedRatio.g.cs @@ -86,5 +86,13 @@ public record SignedRatio : PhysicalQuantity, T>, IVector1 /// The non-negative magnitude. public Ratio Magnitude() => Ratio.Create(T.Abs(Value)); +/// + /// Multiplies SignedRatio by Ratio to produce SignedRatio. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static SignedRatio operator *(SignedRatio left, Ratio right) => Multiply>(left, right); +/// + /// Divides SignedRatio by Ratio to produce SignedRatio. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static SignedRatio operator /(SignedRatio left, Ratio right) => Divide>(left, right); }; diff --git a/Semantics.SourceGenerators/Metadata/dimensions.json b/Semantics.SourceGenerators/Metadata/dimensions.json index 24ca0e9..8d5ad3d 100644 --- a/Semantics.SourceGenerators/Metadata/dimensions.json +++ b/Semantics.SourceGenerators/Metadata/dimensions.json @@ -25,7 +25,9 @@ ] } }, - "integrals": [], + "integrals": [ + { "other": "Dimensionless", "result": "Dimensionless" } + ], "derivatives": [], "dotProducts": [], "crossProducts": [] diff --git a/Semantics.Test/AudioEngineeringTests.cs b/Semantics.Test/AudioEngineeringTests.cs index b471c45..d8303f0 100644 --- a/Semantics.Test/AudioEngineeringTests.cs +++ b/Semantics.Test/AudioEngineeringTests.cs @@ -4,6 +4,7 @@ namespace ktsu.Semantics.Test; +using ktsu.Semantics.Quantities; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -74,11 +75,22 @@ public void Cents_OctaveIsFrequencyRatioOfTwo() [TestMethod] public void RatioPercent_RoundTrip() { - Assert.AreEqual(50.0, Ratio.Create(0.5).ToPercent().Value, Tolerance); + Assert.AreEqual(50.0, Percent.FromRatio(Ratio.Create(0.5)).Value, Tolerance); Assert.AreEqual(0.5, Percent.Create(50.0).ToRatio().Value, Tolerance); Assert.AreEqual(0.25, Percent.FromFraction(0.25).ToFraction(), Tolerance); } + [TestMethod] + public void Ratio_Arithmetic_Comes_From_The_Generator() + { + // The audio-specific Ratio struct is gone; the generated Dimensionless + // Ratio supplies the arithmetic. + Ratio mix = Ratio.Create(0.5) * Ratio.Create(0.5); + Assert.AreEqual(0.25, mix.Value, Tolerance); + // Same-type division yields the raw storage ratio, as for every quantity. + Assert.AreEqual(2.0, Ratio.Create(1.0) / Ratio.Create(0.5), Tolerance); + } + [TestMethod] public void QFactor_FromBandwidth_AndBack() { diff --git a/docs/migration-guide-2.0.md b/docs/migration-guide-2.0.md index 3253986..fa90a93 100644 --- a/docs/migration-guide-2.0.md +++ b/docs/migration-guide-2.0.md @@ -15,10 +15,10 @@ For the architecture behind these changes see ## Quick checklist -1. Update the namespace: quantities moved from `ktsu.Semantics` to - `ktsu.Semantics.Quantities` (unit singletons live in - `ktsu.Semantics.Quantities.Units`). The audio-engineering types - (`Decibels`, `Gain`, `NormalizedParameter`, …) remain in `ktsu.Semantics`. +1. Update the namespace: quantities — including the audio-engineering types + (`Decibels`, `Gain`, `NormalizedParameter`, …) — moved from + `ktsu.Semantics` to `ktsu.Semantics.Quantities` (unit singletons live in + `ktsu.Semantics.Quantities.Units`). 2. Rename vector-capable quantities to their explicit form — `Force` becomes `ForceMagnitude` (or `Force1D`/`Force2D`/`Force3D` if you were tracking sign or direction). The full table is below. @@ -38,7 +38,7 @@ For the architecture behind these changes see |---|---| | `ktsu.Semantics` (quantity types) | `ktsu.Semantics.Quantities` | | `ktsu.Semantics` (unit registry) | `ktsu.Semantics.Quantities.Units` | -| `ktsu.Semantics` (audio engineering: `Decibels`, `Gain`, `Cents`, `Semitones`, `Percent`, `QFactor`, `Ratio` (audio), `NormalizedParameter`, `ParameterTaper`) | unchanged | +| `ktsu.Semantics` (audio engineering: `Decibels`, `Gain`, `Cents`, `Semitones`, `Percent`, `QFactor`, `NormalizedParameter`, `ParameterTaper`) | `ktsu.Semantics.Quantities` | | `ktsu.Semantics.Strings`, `ktsu.Semantics.Paths` | unchanged | ## Quantity type renames @@ -204,14 +204,16 @@ different dimensions throws `ArgumentException`; equality across dimensions is | `MolesPerSecond` unit on `ReactionRate` | none — `ReactionRate` is volumetric (mol/(m³·s)); the 1.x unit was mislabelled | | `PhysicalConstants.Conversion.*` | conversions are baked into factories; raw factors live in `conversions.json` | | `SoundPressureLevel.AWeighted()` / `EquivalentLevel` | none (1.x implementations were placeholders) | +| audio-engineering `Ratio` | the generated `Ratio` (Dimensionless V0; non-negative, `Ratio × Ratio` generated, same-type `/` yields the storage value); percentage bridging lives on `Percent.FromRatio`/`ToRatio` | ## What didn't change - `Semantics.Strings` and `Semantics.Paths` — same namespaces, same types; 2.0 only adds validation attributes. - The audio-engineering types added late in 1.x (`Decibels`, `Gain`, - `NormalizedParameter`, `ParameterTaper`, …) ship unchanged in - `ktsu.Semantics`. + `NormalizedParameter`, `ParameterTaper`, …) keep their APIs, but move to + `ktsu.Semantics.Quantities` and exchange ratios through the generated + `Ratio`. - Target frameworks: `net7.0` through `net10.0`. - Quantities remain generic over the numeric storage type (`where T : struct, INumber`).