Repository: chrisguidry/measured (language: python)

Issue:
There are many units relating to sound
NIST [mentions several non-SI measurements](https://www.nist.gov/pml/special-publication-811/nist-guide-si-chapter-5-units-outside-si) accepted for use with SI:

* [`bel`/`decibel`](https://en.wikipedia.org/wiki/Decibel)
* [`octave`](https://en.wikipedia.org/wiki/Octave_(electronics))
* [`phon`](https://en.wikipedia.org/wiki/Phon)
* [`sone`](https://en.wikipedia.org/wiki/Sone)

One interesting thing about these units is that they tend to be logarithmic (because they deal with sound), and it's not clear if that will be an issue for the current implementation of `measured`.
Logarithmic units/scales
Several [logarithmic units](https://en.wikipedia.org/wiki/Logarithmic_scale#Logarithmic_units) are popping up:

* Those relating to sound, captured in #39
* The [`Hartley` unit](https://en.wikipedia.org/wiki/Hartley_(unit)) from information theory
* The `Neper` unit of amplitude



Discussion / hints:

Fix patch:
diff --git a/src/measured/__init__.py b/src/measured/__init__.py
index 7bfc16e..f809bbf 100644
--- a/src/measured/__init__.py
+++ b/src/measured/__init__.py
@@ -138,12 +138,18 @@
 
     One (Unit): represents the number 1, expressed as a unit of dimension `Number`
 
+Attributes: Logarithms
+
+    Bel (Logarithm): A logarithmic ratio in base 10
+    Decibel (Logarithm): 1/10th of a Bel
+    Neper (Logarithm): A logarithmic ratio in base _e_
+
 """
 
+import math
 from collections import defaultdict
 from functools import lru_cache, total_ordering
 from importlib.metadata import version
-from math import log, sqrt
 from typing import (
     Any,
     Callable,
@@ -286,12 +292,13 @@ def __new__(
         name: Optional[str] = None,
         symbol: Optional[str] = None,
     ) -> "Dimension":
-        if exponents in cls._known:
-            return cls._known[exponents]
+        key = exponents
+        if key in cls._known:
+            return cls._known[key]
 
         self = super().__new__(cls)
         self._initialized = False
-        cls._known[exponents] = self
+        cls._known[key] = self
         return self
 
     def __init__(
@@ -682,7 +689,7 @@ def __mul__(
                 return Prefix(self.base, self.exponent + other.exponent)
 
             base, exponent = self.base, self.exponent
-            exponent += other.exponent * (log(other.base) / log(self.base))
+            exponent += other.exponent * (math.log(other.base) / math.log(self.base))
 
             return Prefix(base, exponent)
 
@@ -708,7 +715,7 @@ def __truediv__(self, other: "Prefix") -> "Prefix":
             return Prefix(self.base, self.exponent - other.exponent)
 
         base, exponent = self.base, self.exponent
-        exponent -= other.exponent * (log(other.base) / log(self.base))
+        exponent -= other.exponent * (math.log(other.base) / math.log(self.base))
 
         return Prefix(base, exponent)
 
@@ -1453,6 +1460,9 @@ def __lt__(self, other: Any) -> bool:
         except conversions.ConversionNotFound:
             return NotImplemented
 
+    def level(self, unit: "LogarithmicUnit") -> "Level":
+        return unit.level(self)
+
 
 class Logarithm:
     """A `Logarithm` forms a family of [`LogarithmicUnits`][measured.LogarithmicUnit],
@@ -1472,7 +1482,7 @@ class Logarithm:
         [`LogarithmicUnit`][measured.LogarithmicUnit] based on a reference value.  In
         the example below, the reference value is 1 Watt:
 
-        >>> from measured.iec import Decibel
+        >>> from measured import Decibel
         >>> from measured.si import Watt
         >>> dBW = Decibel[1 * Watt]
 
@@ -1483,11 +1493,10 @@ class Logarithm:
         >>> assert 100 * Watt == 20 * dBW
     """
 
-    _known: ClassVar[Dict[Tuple[float, int, Prefix], "Logarithm"]] = {}
+    _known: ClassVar[Dict[Tuple[float, Prefix], "Logarithm"]] = {}
     _initialized: bool
 
     base: float
-    power_ratio: int
     prefix: Prefix
     name: Optional[str]
     symbol: Optional[str]
@@ -1495,23 +1504,22 @@ class Logarithm:
     def __new__(
         cls,
         base: float,
-        power_ratio: int = 1,
         prefix: Prefix = Prefix(0, 0),
         name: Optional[str] = None,
         symbol: Optional[str] = None,
     ) -> "Logarithm":
-        if (base, power_ratio, prefix) in cls._known:
-            return cls._known[(base, power_ratio, prefix)]
+        key = (base, prefix)
+        if key in cls._known:
+            return cls._known[key]
 
         self = super().__new__(cls)
         self._initialized = False
-        cls._known[(base, power_ratio, prefix)] = self
+        cls._known[key] = self
         return self
 
     def __init__(
         self,
         base: float,
-        power_ratio: int = 1,
         prefix: Prefix = Prefix(0, 0),
         name: Optional[str] = None,
         symbol: Optional[str] = None,
@@ -1520,7 +1528,6 @@ def __init__(
             return
 
         self.base = base
-        self.power_ratio = power_ratio
         self.prefix = prefix
         self.name = name
         self.symbol = symbol
@@ -1560,7 +1567,7 @@ class LogarithmicUnit:
         "suffix" (using Python's indexing operator `[]`) to the
         [`Logarithm`][measured.Logarithm] in question:
 
-        >>> from measured.iec import Decibel
+        >>> from measured import Decibel
         >>> from measured.si import Watt, Milli
         >>> dBW = Decibel[1 * Watt]
         >>> dBm = Decibel[1 * Milli * Watt]
@@ -1595,12 +1602,13 @@ def __new__(
         name: Optional[str] = None,
         symbol: Optional[str] = None,
     ) -> "LogarithmicUnit":
-        if (logarithm, reference) in cls._known:
-            return cls._known[(logarithm, reference)]
+        key = (logarithm, reference)
+        if key in cls._known:
+            return cls._known[key]
 
         self = super().__new__(cls)
         self._initialized = False
-        cls._known[(logarithm, reference)] = self
+        cls._known[key] = self
         return self
 
     def __init__(
@@ -1626,6 +1634,14 @@ def alias(self, name: str, symbol: str) -> "LogarithmicUnit":
         self.symbol = symbol
         return self
 
+    @property
+    def power_ratio(self) -> int:
+        """Indicates whether the reference quantity is a power (1) or root-power (2)
+        quantity"""
+        if self.reference.unit.dimension in ROOT_POWER_DIMENSIONS:
+            return 2
+        return 1
+
     __repr__ = formatting.logarithmic_unit_repr
     __str__ = formatting.logarithmic_unit_str
     _repr_pretty_ = formatting.logarithmic_unit_pretty
@@ -1636,6 +1652,16 @@ def __mul__(self, magnitude: Numeric) -> "Level":
 
     __rmul__ = __mul__
 
+    def level(self, quantity: Quantity) -> "Level":
+        base = self.logarithm.base
+        prefix = self.logarithm.prefix
+        power_ratio = self.power_ratio
+        ratio = quantity.in_unit(self.reference.unit) / self.reference
+        magnitude = (
+            power_ratio * (1 / prefix.quantify()) * math.log(ratio.magnitude, base)
+        )
+        return Level(magnitude, self)
+
 
 class Level:
     """A `Level` is analogous to a [`Quantity`][measured.Quantity], but represents a
@@ -1665,10 +1691,10 @@ def quantify(self) -> Quantity:
         """Converts this Level into a Quantity of the reference unit"""
         base = self.unit.logarithm.base
         prefix = self.unit.logarithm.prefix
-        power_ratio = self.unit.logarithm.power_ratio
+        power_ratio = self.unit.power_ratio
         reference = self.unit.reference
         exponent = self.magnitude * prefix.quantify()
-        magnitude: float = base ** (exponent * power_ratio)
+        magnitude: float = base ** (exponent / power_ratio)
         return magnitude * reference
 
     def __add__(self, other: "Level") -> "Level":
@@ -1678,7 +1704,7 @@ def __add__(self, other: "Level") -> "Level":
 
             base = self.unit.logarithm.base
             prefix = self.unit.logarithm.prefix
-            power_ratio = self.unit.logarithm.power_ratio
+            power_ratio = self.unit.power_ratio
 
             left_exponent = self.magnitude * prefix.quantify()
             right_exponent = other.magnitude * prefix.quantify()
@@ -1686,7 +1712,7 @@ def __add__(self, other: "Level") -> "Level":
             left_magnitude: float = base ** (left_exponent * power_ratio)
             right_magnitude: float = base ** (right_exponent * power_ratio)
 
-            magnitude = base * log(left_magnitude + right_magnitude, base)
+            magnitude = base * math.log(left_magnitude + right_magnitude, base)
 
             return Level(magnitude, self.unit)
 
@@ -1699,7 +1725,7 @@ def __sub__(self, other: "Level") -> "Level":
 
             base = self.unit.logarithm.base
             prefix = self.unit.logarithm.prefix
-            power_ratio = self.unit.logarithm.power_ratio
+            power_ratio = self.unit.power_ratio
 
             left_exponent = self.magnitude * prefix.quantify()
             right_exponent = other.magnitude * prefix.quantify()
@@ -1707,7 +1733,7 @@ def __sub__(self, other: "Level") -> "Level":
             left_magnitude: float = base ** (left_exponent * power_ratio)
             right_magnitude: float = base ** (right_exponent * power_ratio)
 
-            magnitude = base * log(left_magnitude - right_magnitude, base)
+            magnitude = base * math.log(left_magnitude - right_magnitude, base)
 
             return Level(magnitude, self.unit)
 
@@ -1951,7 +1977,7 @@ def __mul__(self, other: Union["Measurement", Quantity]) -> "Measurement":
             return NotImplemented
 
         measurand = self.measurand * other.measurand
-        uncertainty = sqrt(
+        uncertainty = math.sqrt(
             measurand.magnitude**2
             * (
                 (self.uncertainty.magnitude**2 / self.measurand.magnitude**2)
@@ -1970,7 +1996,7 @@ def __truediv__(self, other: Union["Measurement", Quantity]) -> "Measurement":
             return NotImplemented
 
         measurand = self.measurand / other.measurand
-        uncertainty = sqrt(
+        uncertainty = math.sqrt(
             measurand.magnitude**2
             * (
                 (self.uncertainty.magnitude**2 / self.measurand.magnitude**2)
@@ -1993,7 +2019,7 @@ def __pow__(self, exponent: int) -> "Measurement":
             return NotImplemented
 
         measurand = self.measurand**exponent
-        uncertainty = sqrt(
+        uncertainty = math.sqrt(
             (exponent * self.measurand.magnitude**2 * self.uncertainty.magnitude) ** 2
         )
         return Measurement(measurand, uncertainty)
@@ -2101,3 +2127,25 @@ def approximately(
 from .parsing import parser  # noqa: E402
 
 One.equals(1 * One)
+
+
+# Logarithms
+# https://en.wikipedia.org/wiki/Decibel
+# https://en.wikipedia.org/wiki/Neper
+
+Neper = Logarithm(base=math.e, name="neper", symbol="Np")
+Octave = Logarithm(base=2, name="octave", symbol="oct")
+Bel = Logarithm(base=10, name="bel", symbol="bel")
+Decibel = (Prefix(10, -1) * Bel).alias(name="decibel", symbol="dB")
+
+# https://en.wikipedia.org/wiki/Power,_root-power,_and_field_quantities
+ROOT_POWER_DIMENSIONS = {
+    Potential,
+    Current,
+    Pressure,
+    Potential / Length,
+    Speed,
+    Charge / Length,
+    Charge / Area,
+    Charge / Volume,
+}
diff --git a/src/measured/acoustics.py b/src/measured/acoustics.py
new file mode 100644
index 0000000..7df9c28
--- /dev/null
+++ b/src/measured/acoustics.py
@@ -0,0 +1,25 @@
+"""
+Units and non-standardized conventions relating to electronics
+
+Attributes: LogarithmicUnits
+
+    dBSPL (LogarithmicUnit): Decibels of Sound `Pressure`, compared to 20 μPa
+"""
+from measured import Decibel
+from measured.si import Meter, Micro, Pascal, Pico, Watt
+
+# https://en.wikipedia.org/wiki/Absolute_threshold_of_hearing
+
+ABSOLUTE_THRESHOLD_OF_HEARING = 20 * Micro * Pascal
+
+# https://en.wikipedia.org/wiki/Sound_pressure#Sound_pressure_level
+
+dBSPL = Decibel[ABSOLUTE_THRESHOLD_OF_HEARING].alias("dBSPL", "dBSPL")
+
+# https://en.wikipedia.org/wiki/Sound_power#Sound_power_level
+
+dBSWL = Decibel[1 * Pico * Watt]
+
+# https://en.wikipedia.org/wiki/Sound_intensity#Sound_intensity_level
+
+dBSIL = Decibel[1 * (Pico * Watt) / Meter**2].alias("dBSIL", "dBSIL")
diff --git a/src/measured/electronics.py b/src/measured/electronics.py
new file mode 100644
index 0000000..f5aa66e
--- /dev/null
+++ b/src/measured/electronics.py
@@ -0,0 +1,13 @@
+"""
+Units and non-standardized conventions relating to electronics
+
+Attributes: LogarithmicUnits
+
+    dBW (LogarithmicUnit): Decibels of `Power`, compared to 1 `Watt`
+"""
+from measured import Decibel
+from measured.si import Watt
+
+# https://en.wikipedia.org/wiki/Decibel#Suffixes_and_reference_values
+
+dBW = Decibel[1 * Watt].alias("dbW", "dbW")
diff --git a/src/measured/formatting.py b/src/measured/formatting.py
index f1e1df2..ecfc986 100644
--- a/src/measured/formatting.py
+++ b/src/measured/formatting.py
@@ -365,7 +365,6 @@ def logarithm_repr(logarithm: "Logarithm") -> str:
         "Logarithm("
         f"prefix={logarithm.prefix!r}, "
         f"base={logarithm.base!r}, "
-        f"power_ratio={logarithm.power_ratio!r}, "
         f"name={logarithm.name!r}, "
         f"symbol={logarithm.symbol!r}"
         ")"
@@ -377,9 +376,8 @@ def logarithm_str(logarithm: "Logarithm") -> str:
     if logarithm.symbol:
         return logarithm.symbol
 
-    power = superscript(logarithm.power_ratio)
     function = "ln" if logarithm.base == math.e else f"log{logarithm.base}"
-    return f"{logarithm.prefix.quantify()} {function}(x{power}/x₀{power})"
+    return f"{logarithm.prefix.quantify()} {function}(x/x₀)"
 
 
 def logarithm_pretty(
@@ -408,8 +406,8 @@ def logarithm_mathml(logarithm: "Logarithm") -> str:
         f"<mi>{function}</mi>"
         "<mo>(</mo>"
         "<mfrac>"
-        f"<mrow><msup><mi>x</mi><mn>{logarithm.power_ratio}</mn></msup></mrow>"
-        f"<mrow><msup><mi>x₀</mi><mn>{logarithm.power_ratio}</mn></msup></mrow>"
+        f"<mi>x</mi>"
+        f"<mi>x₀</mi>"
         "</mfrac>"
         "<mo>)</mo>"
         "</mrow>"
diff --git a/src/measured/iec.py b/src/measured/iec.py
index 6d52dc3..4f14c83 100644
--- a/src/measured/iec.py
+++ b/src/measured/iec.py
@@ -27,26 +27,14 @@
     Zebi (Prefix): 2⁷⁰, symbol `Zi`
     Yobi (Prefix): 2⁸⁰, symbol `Yi`
 
-Attributes: Logarithms
-
-    Bel (Logarithm): A logarithmic ratio in base 10
-    Decibel (Logarithm): 1/10th of a Bel
-    Neper (Logarithm): A logarithmic ratio in base _e_
-
-Attributes: LogarithmicUnits
-
-    dBW (LogarithmicUnit): Decibels of `Power`, compared to 1 `Watt`
-
 """
 
 # https://en.wikipedia.org/wiki/Binary_prefix#Adoption_by_IEC,_NIST_and_ISO
 
-from math import e
-
-from measured.si import Deci, Meter, Second, Watt
+from measured.si import Meter, Second
 from measured.us import Inch
 
-from . import Information, Length, Logarithm, Prefix, Unit
+from . import Information, Length, Prefix, Unit
 
 Kibi = Prefix(2, 10, name="kibi", symbol="Ki")
 Mebi = Prefix(2, 20, name="mebi", symbol="Mi")
@@ -73,16 +61,3 @@
 RackUnit = Length.unit("rack unit", "U")
 RackUnit.equals(1.75 * Inch)
 RackUnit.equals(0.04445 * Meter)
-
-
-# Logarithmic units
-# https://en.wikipedia.org/wiki/Decibel
-# https://en.wikipedia.org/wiki/Neper
-
-Bel = Logarithm(base=10, name="bel", symbol="bel")
-Decibel = (Deci * Bel).alias(name="decibel", symbol="dB")
-Neper = Logarithm(base=e, name="neper", symbol="Np", power_ratio=2)
-
-# https://en.wikipedia.org/wiki/Decibel#Suffixes_and_reference_values
-
-dBW = Decibel[1 * Watt].alias("dbW", "dbW")
diff --git a/src/measured/iso.py b/src/measured/iso.py
index 971b3a6..42757a1 100644
--- a/src/measured/iso.py
+++ b/src/measured/iso.py
@@ -1,7 +1,8 @@
 from measured import Length
-from measured.si import Meter
+from measured.si import Hertz, Meter
 
-# ISO 2848
+# ISO 2848:1984 - Building construction — Modular coordination — Principles and rules
+# https://www.iso.org/standard/7846.html
 
 BasicModule = Length.unit("basic module", "basic-module")
 BasicModule.equals(0.1 * Meter)
@@ -11,3 +12,9 @@
 
 MetricFoot = Length.unit("metric foot", "metric-inch")
 MetricInch.equals(3 * BasicModule)
+
+
+# ISO 16:1975 Acoustics, Standard tuning frequency (Standard musical pitch)
+# http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=3601
+
+A440 = 440 * Hertz
diff --git a/src/measured/music.py b/src/measured/music.py
new file mode 100644
index 0000000..a5b482b
--- /dev/null
+++ b/src/measured/music.py
@@ -0,0 +1,41 @@
+from typing import Dict
+
+from measured import Level, Octave, Prefix
+from measured.iso import A440
+
+Semitone = (Prefix(12, -1) * Octave).alias(name="semitone", symbol="semitone")
+
+ConcertPitch = Semitone[A440]
+
+# https://en.wikipedia.org/wiki/Scientific_pitch_notation
+
+SPN_OCTAVES = range(-1, 11)
+MIDDLE_A_OCTAVE = 4
+
+
+def _octaves(note_offset: int) -> Dict[int, Level]:
+    return {
+        o: (((-MIDDLE_A_OCTAVE + o) * 12) + note_offset) * ConcertPitch
+        for o in SPN_OCTAVES
+    }
+
+
+A_flat = _octaves(-1)
+A = _octaves(0)
+A_sharp = _octaves(+1)
+B_flat = A_sharp
+B = _octaves(+2)
+C = _octaves(-9)
+C_sharp = _octaves(-8)
+D_flat = C_sharp
+D = _octaves(-7)
+D_sharp = _octaves(-6)
+E_flat = D_sharp
+E = _octaves(-5)
+F = _octaves(-4)
+F_sharp = _octaves(-3)
+G_flat = F_sharp
+G = _octaves(-2)
+G_sharp = A_flat
+
+MIDI = [(C[-1].magnitude + i) * ConcertPitch for i in range(128)]
diff --git a/src/measured/systems.py b/src/measured/systems.py
index 0469423..10970c3 100644
--- a/src/measured/systems.py
+++ b/src/measured/systems.py
@@ -8,6 +8,7 @@
     iec,
     iso,
     metric,
+    music,
     natural,
     si,
     troy,


Test patch:
diff --git a/src/measured/pytest.py b/src/measured/pytest.py
index 3a13d96..a76a7f4 100644
--- a/src/measured/pytest.py
+++ b/src/measured/pytest.py
@@ -18,7 +18,8 @@ def pytest_assertrepr_compare(
             return None
 
         comparison = ""
-        difference = ""
+        difference: Optional[Quantity] = None
+        relative = None
 
         if isinstance(left, COMPARABLE) and isinstance(right, COMPARABLE):
             if isinstance(left, Level):
@@ -42,9 +43,9 @@ def pytest_assertrepr_compare(
             if isinstance(right, Level):
                 try:
                     right_comparison = right_quantity.in_unit(left_unit)
-                    difference = str(left_quantity - right_comparison)
+                    difference = left_quantity - right_comparison
                 except ConversionNotFound:
-                    difference = "(conversion not found)"
+                    difference = None
 
             elif isinstance(right, Measurement):
                 try:
@@ -52,23 +53,38 @@ def pytest_assertrepr_compare(
                         measurand=right_quantity.in_unit(left_unit),
                         uncertainty=right.uncertainty.in_unit(left_unit),
                     )
-                    difference = str(left_quantity - right_comparison.measurand)
+                    difference = left_quantity - right_comparison.measurand
                 except ConversionNotFound:
-                    difference = "(conversion not found)"
+                    difference = None
 
             elif isinstance(right, Quantity):
                 try:
                     right_comparison = right.in_unit(left_unit)
-                    difference = str(left_quantity - right_comparison)
+                    difference = left_quantity - right_comparison
                 except ConversionNotFound:
-                    difference = "(conversion not found)"
+                    difference = None
 
             comparison = f"{left} {op} {right_comparison}"
 
+            if difference:
+                if left_quantity.magnitude != 0:
+                    relative = abs(difference.magnitude / left_quantity.magnitude)
+                elif right_quantity.magnitude != 0:
+                    relative = abs(difference.magnitude / right_quantity.magnitude)
+                else:
+                    relative = None
+
+        if difference is None:
+            difference_description = "difference: (conversion not found)"
+        else:
+            difference_description = (
+                f"difference: absolute={difference} relative={relative})"
+            )
+
         return [
             f"{left} {op} {right}",
             comparison,
-            f"difference: {difference}",
+            difference_description,
             "where",
             f"{str(left)!r} is {left!r}",
             "and",
diff --git a/tests/test_acoustics.py b/tests/test_acoustics.py
new file mode 100644
index 0000000..8e6cc82
--- /dev/null
+++ b/tests/test_acoustics.py
@@ -0,0 +1,34 @@
+import pytest
+
+from measured import Level, Pressure, Quantity, approximately
+from measured.acoustics import ABSOLUTE_THRESHOLD_OF_HEARING, dBSPL
+from measured.si import Micro, Pascal
+
+
+def test_auditory_threshold() -> None:
+    assert ABSOLUTE_THRESHOLD_OF_HEARING.unit.dimension == Pressure
+    assert ABSOLUTE_THRESHOLD_OF_HEARING == 20 * Micro * Pascal
+    assert ABSOLUTE_THRESHOLD_OF_HEARING == 0 * dBSPL
+
+
+# https://en.wikipedia.org/wiki/Sound_pressure#Examples_of_sound_pressure
+@pytest.mark.parametrize(
+    "pressure, level, within",
+    [
+        # Microsoft's A-weighted anechoic chamber
+        (1.9e-6 * Pascal, -20.35 * dBSPL, 0.02),
+        # threshold of human hearing
+        (ABSOLUTE_THRESHOLD_OF_HEARING, 0 * dBSPL, 1e-7),
+        # Jackhammer at 1 meter
+        (2 * Pascal, 100 * dBSPL, 1e-7),
+        # Chainsaw at 1 meter
+        (6.32 * Pascal, 110 * dBSPL, 1e-3),
+        # Jet Engine at 1 meter
+        (632 * Pascal, 150 * dBSPL, 1e-3),
+    ],
+)
+def test_examples_of_sound_pressure(
+    pressure: Quantity, level: Level, within: float
+) -> None:
+    assert pressure == approximately(level, within=within)
+    assert level == approximately(pressure, within=within)
diff --git a/tests/test_formatting.py b/tests/test_formatting.py
index 53d1c20..1f6dd81 100644
--- a/tests/test_formatting.py
+++ b/tests/test_formatting.py
@@ -3,9 +3,6 @@
 
 import pytest
 
-from measured import Logarithm
-from measured.iec import Decibel
-
 pytest.importorskip("IPython")
 
 # flake8: noqa: E402 (imports not at top of file)
@@ -17,10 +14,13 @@
 
 from measured import (
     Area,
+    Decibel,
     Dimension,
     Frequency,
     Length,
+    Logarithm,
     Measurement,
+    Neper,
     Number,
     Prefix,
     Quantity,
@@ -30,7 +30,6 @@
     VolumetricFlow,
 )
 from measured.formatting import superscript
-from measured.iec import Decibel, Neper
 from measured.si import Hertz, Kilo, Mega, Meter, Milli, Ohm, Second, Watt
 
 
@@ -97,7 +96,7 @@ def test_strings_of_dimensions(dimension: Dimension, string: str) -> None:
         (Hertz, "Hz"),
         (Hertz**2, "s⁻²"),
         (Decibel[1 * Meter], "dB of 1 m"),
-        (Logarithm(2, 2), "1 log2(x²/x₀²)"),
+        (Logarithm(3), "1 log3(x/x₀)"),
     ],
 )
 def test_strings_of_units(unit: Unit, string: str) -> None:
@@ -254,7 +253,7 @@ def test_mathml_root_is_identifier(formattable: Formattable) -> None:
         Length * Time,
         (Kilo * Meter),
         Meter * Second,
-        Logarithm(2, 2),
+        Logarithm(3),
         Decibel[1 * Meter],  # a dB that we wouldn't have a symbol for
         Neper[1 * Meter],
         30 * Decibel[1 * Watt],
diff --git a/tests/test_levels.py b/tests/test_levels.py
index 46e3ca5..f98b483 100644
--- a/tests/test_levels.py
+++ b/tests/test_levels.py
@@ -1,7 +1,20 @@
 import pytest
-
-from measured import Level, Logarithm, Numeric, Quantity, approximately
-from measured.iec import Bel, Decibel, Neper, dBW
+from pytest import approx
+
+from measured import (
+    Bel,
+    Decibel,
+    Level,
+    Logarithm,
+    Neper,
+    Numeric,
+    Power,
+    Pressure,
+    Quantity,
+    approximately,
+)
+from measured.acoustics import dBSPL
+from measured.electronics import dBW
 from measured.si import Deci, Meter, Micro, Milli, Volt, Watt
 
 
@@ -56,11 +69,41 @@ def test_logarithmic_units_have_names_and_symbols() -> None:
         (0.01 * Watt, -20 * dBW),
     ],
 )
-def test_dBW(power: Quantity, level: Level) -> None:
+def test_quantity_to_level_comparisons(power: Quantity, level: Level) -> None:
     assert power == level
     assert level == power
 
 
+@pytest.mark.parametrize(
+    "power, level",
+    [
+        (1 * Watt, 0 * dBW),
+        (10 * Watt, 10 * dBW),
+        (100 * Watt, 20 * dBW),
+        (0.1 * Watt, -10 * dBW),
+        (0.01 * Watt, -20 * dBW),
+    ],
+)
+def test_producing_level(power: Quantity, level: Level) -> None:
+    assert power.level(dBW).unit is dBW
+    assert power.level(dBW).magnitude == approx(level.magnitude)
+
+
+@pytest.mark.parametrize(
+    "power, level",
+    [
+        (1 * Watt, 0 * dBW),
+        (10 * Watt, 10 * dBW),
+        (100 * Watt, 20 * dBW),
+        (0.1 * Watt, -10 * dBW),
+        (0.01 * Watt, -20 * dBW),
+    ],
+)
+def test_producing_quantity(power: Quantity, level: Level) -> None:
+    assert level.quantify().unit is Watt
+    assert level.quantify().magnitude == power.magnitude
+
+
 @pytest.mark.parametrize(
     "bels, decibels",
     [
@@ -145,3 +188,13 @@ def test_logarithmic_multiplication_only_by_numerics() -> None:
 
     with pytest.raises(TypeError):
         (10 * dBW) / (2 * dBW)  # type: ignore
+
+
+def test_power_ratios() -> None:
+    # https://en.wikipedia.org/wiki/Power,_root-power,_and_field_quantities
+
+    assert dBW.reference.unit.dimension is Power
+    assert dBW.power_ratio == 1
+
+    assert dBSPL.reference.unit.dimension is Pressure
+    assert dBSPL.power_ratio == 2
diff --git a/tests/test_measurement.py b/tests/test_measurement.py
index 0f64355..bd2edc2 100644
--- a/tests/test_measurement.py
+++ b/tests/test_measurement.py
@@ -4,8 +4,7 @@
 import pytest
 from pytest import approx
 
-from measured import Measurement, Quantity, approximately
-from measured.iec import Decibel
+from measured import Decibel, Measurement, Quantity, approximately
 from measured.si import Meter, Second
 from measured.us import Foot
 
diff --git a/tests/test_music.py b/tests/test_music.py
new file mode 100644
index 0000000..55e3252
--- /dev/null
+++ b/tests/test_music.py
@@ -0,0 +1,32 @@
+from measured import approximately, music
+from measured.si import Hertz
+
+
+def test_middle_A() -> None:
+    assert music.A[4] == 440 * Hertz
+
+
+def test_middle_C() -> None:
+    assert music.C[4] == approximately(261.625565 * Hertz)
+
+
+def test_lowest_c() -> None:
+    assert music.C[-1] == approximately(8.175799 * Hertz)
+
+
+def test_highest_b() -> None:
+    assert music.B[10] == approximately(31608.53 * Hertz)
+
+
+def test_synonyms() -> None:
+    assert music.A_sharp == music.B_flat
+    assert music.C_sharp == music.D_flat
+    assert music.D_sharp == music.E_flat
+    assert music.F_sharp == music.G_flat
+    assert music.G_sharp == music.A_flat
+
+
+def test_midi_notes() -> None:
+    assert music.MIDI[0] == music.C[-1]
+    assert music.MIDI[69] == music.A[4]
+    assert music.MIDI[127] == music.G[9]