Skip to content

Commit

Permalink
feat(note): ✨ take temperature into account when dealing with `Freq…
Browse files Browse the repository at this point in the history
…uency` (#455)

* feat(note): ✨ take `temperature` into account when dealing with `Frequency`

* test(pitch): 🧪 add test cases for `temperature` in `frequency`

* docs(pitch): 📖 add examples for `temperature` in `frequency`

* test(closest_pitch): 🧪 add test case for `temperature` in `frequency`

* feat(temperature): ✨ add `Celsius.zero` static constant

* docs(frequency): 📖 improve `TemperatureFrequency` methods documentation and references
  • Loading branch information
albertms10 committed Mar 27, 2024
1 parent f3cb0a6 commit 063679a
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 14 deletions.
1 change: 1 addition & 0 deletions lib/music_notes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export 'src/tuning/cent.dart';
export 'src/tuning/equal_temperament.dart';
export 'src/tuning/just_intonation.dart';
export 'src/tuning/ratio.dart';
export 'src/tuning/temperature.dart';
export 'src/tuning/tuning_system.dart';
7 changes: 5 additions & 2 deletions lib/src/note/closest_pitch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:music_notes/utils.dart';

import '../tuning/cent.dart';
import '../tuning/equal_temperament.dart';
import '../tuning/temperature.dart';
import '../tuning/tuning_system.dart';
import 'frequency.dart';
import 'pitch.dart';
Expand Down Expand Up @@ -58,8 +59,8 @@ class ClosestPitch {
return ClosestPitch(Pitch.parse(match[1]!), cents: cents);
}

/// The [Frequency] of this [ClosestPitch] from [referenceFrequency]
/// and [tuningSystem].
/// The [Frequency] of this [ClosestPitch] from [referenceFrequency],
/// [tuningSystem] and [temperature].
///
/// Example:
/// ```dart
Expand All @@ -68,11 +69,13 @@ class ClosestPitch {
Frequency frequency({
Frequency referenceFrequency = Frequency.reference,
TuningSystem tuningSystem = const EqualTemperament.edo12(),
Celsius temperature = Celsius.reference,
}) =>
Frequency(
pitch.frequency(
referenceFrequency: referenceFrequency,
tuningSystem: tuningSystem,
temperature: temperature,
) *
cents.ratio,
);
Expand Down
34 changes: 29 additions & 5 deletions lib/src/note/frequency.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:meta/meta.dart' show immutable;

import '../tuning/equal_temperament.dart';
import '../tuning/ratio.dart';
import '../tuning/temperature.dart';
import '../tuning/tuning_system.dart';
import 'closest_pitch.dart';
import 'hearing_range.dart';
Expand Down Expand Up @@ -35,8 +36,8 @@ extension type const Frequency._(num hertz) implements num {
/// ```
bool get isHumanAudible => HearingRange.human.isAudible(this);

/// The [ClosestPitch] to this [Frequency] from [referenceFrequency] and
/// [tuningSystem].
/// The [ClosestPitch] to this [Frequency] from [referenceFrequency],
/// [tuningSystem] and [temperature].
///
/// Example:
/// ```dart
Expand All @@ -45,20 +46,24 @@ extension type const Frequency._(num hertz) implements num {
///
/// const Frequency(260).closestPitch()
/// == Note.c.inOctave(4) - const Cent(10.7903)
///
/// const Frequency(440).closestPitch(temperature: const Celsius(24))
/// == Note.a.inOctave(4) - const Cent(12.06)
/// ```
///
/// This method and [ClosestPitch.frequency] are inverses of each other for a
/// specific input `frequency`.
///
/// ```dart
/// const frequency = Frequency(415);
/// frequency.closestPitch().frequency() == frequency;
/// const reference = Frequency(415);
/// reference.closestPitch().frequency() == reference;
/// ```
ClosestPitch closestPitch({
Frequency referenceFrequency = Frequency.reference,
TuningSystem tuningSystem = const EqualTemperament.edo12(),
Celsius temperature = Celsius.reference,
}) {
final cents = Ratio(hertz / referenceFrequency).cents;
final cents = Ratio(at(temperature) / referenceFrequency).cents;
final semitones =
tuningSystem.referencePitch.semitones + (cents / 100).round();

Expand All @@ -69,6 +74,7 @@ extension type const Frequency._(num hertz) implements num {
final closestPitchFrequency = closestPitch.frequency(
referenceFrequency: referenceFrequency,
tuningSystem: tuningSystem,
temperature: temperature,
);
final hertzDelta = hertz - closestPitchFrequency;

Expand Down Expand Up @@ -142,3 +148,21 @@ extension FrequencyIterableExtension on Iterable<Frequency> {
Set<ClosestPitch> get closestPitches =>
map((frequency) => frequency.closestPitch()).toSet();
}

/// A Frequency extension based on temperature.
extension TemperatureFrequency on Frequency {
/// Speed of sound at [Celsius.zero] in m/s.
static const _baseSpeedOfSound = 331.3;

/// The speed of sound in m/s based on [temperature].
///
/// See [Speed of sound in ideal gases and air](https://en.wikipedia.org/wiki/Speed_of_sound#Speed_of_sound_in_ideal_gases_and_air).
static num speedOfSoundAt(Celsius temperature) =>
_baseSpeedOfSound + 0.6 * temperature;

/// This [Frequency] at [temperature], based on [reference].
Frequency at(Celsius temperature, {Celsius reference = Celsius.reference}) =>
Frequency(
hertz * (speedOfSoundAt(temperature) / speedOfSoundAt(reference)),
);
}
19 changes: 14 additions & 5 deletions lib/src/note/pitch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import '../music.dart';
import '../scalable.dart';
import '../tuning/cent.dart';
import '../tuning/equal_temperament.dart';
import '../tuning/temperature.dart';
import '../tuning/tuning_system.dart';
import 'accidental.dart';
import 'base_note.dart';
Expand Down Expand Up @@ -377,8 +378,8 @@ final class Pitch extends Scalable<Pitch> implements Comparable<Pitch> {
);
}

/// The [Frequency] of this [Pitch] from [referenceFrequency] and
/// [tuningSystem].
/// The [Frequency] of this [Pitch] from [referenceFrequency],
/// [tuningSystem] and [frequency].
///
/// Example:
/// ```dart
Expand All @@ -396,18 +397,24 @@ final class Pitch extends Scalable<Pitch> implements Comparable<Pitch> {
/// ) == const Frequency(430.54)
/// ```
///
/// Note.a.inOctave(4).frequency(temperature: const Celsius(18))
/// == const Frequency(438.46)
/// Note.a.inOctave(4).frequency(temperature: const Celsius(24))
/// == const Frequency(443.08)
///
/// This method and [Frequency.closestPitch] are inverses of each other for a
/// specific `pitch`.
///
/// ```dart
/// final pitch = Note.a.inOctave(5);
/// pitch.frequency().closestPitch().pitch == pitch;
/// final reference = Note.a.inOctave(5);
/// reference.frequency().closestPitch().pitch == reference;
/// ```
Frequency frequency({
Frequency referenceFrequency = Frequency.reference,
TuningSystem tuningSystem = const EqualTemperament.edo12(),
Celsius temperature = Celsius.reference,
}) =>
Frequency(referenceFrequency * tuningSystem.ratio(this));
Frequency(referenceFrequency * tuningSystem.ratio(this)).at(temperature);

/// The [ClosestPitch] set of harmonics series [upToIndex] from this [Pitch].
///
Expand All @@ -421,10 +428,12 @@ final class Pitch extends Scalable<Pitch> implements Comparable<Pitch> {
required int upToIndex,
Frequency referenceFrequency = Frequency.reference,
TuningSystem tuningSystem = const EqualTemperament.edo12(),
Celsius temperature = Celsius.reference,
}) =>
frequency(
referenceFrequency: referenceFrequency,
tuningSystem: tuningSystem,
temperature: temperature,
).harmonics(upToIndex: upToIndex).closestPitches;

/// The string representation of this [Pitch] based on [system].
Expand Down
2 changes: 1 addition & 1 deletion lib/src/scale/scale_degree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class ScaleDegree implements Comparable<ScaleDegree> {
semitonesDelta: semitonesDelta ?? this.semitonesDelta,
);

/// Returns the roman numeral of this [ScaleDegree] based on [ordinal].
/// The roman numeral of this [ScaleDegree] based on [ordinal].
///
/// Example:
/// ```dart
Expand Down
2 changes: 1 addition & 1 deletion lib/src/scale/scale_pattern.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ final class ScalePattern {
return Scale(degrees, descendingDegrees).pattern;
}

/// Returns the binary representation of this [ScalePattern].
/// The binary representation of this [ScalePattern].
///
/// This method and [ScalePattern.fromBinary] are inverses of each other.
///
Expand Down
8 changes: 8 additions & 0 deletions lib/src/tuning/temperature.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// The representation of a Celsius temperature.
extension type const Celsius(num degrees) implements num {
/// The absolute zero [Celsius] temperature.
static const zero = Celsius(0);

/// The reference [Celsius] temperature.
static const reference = Celsius(20);
}
7 changes: 7 additions & 0 deletions test/src/note/closest_pitch_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ void main() {
test('returns the same Frequency after Frequency.closestPitch()', () {
const frequency = Frequency(415);
expect(frequency.closestPitch().frequency(), frequency);

expect(
frequency
.closestPitch(temperature: const Celsius(18))
.frequency(temperature: const Celsius(18)),
frequency,
);
});
});

Expand Down
13 changes: 13 additions & 0 deletions test/src/note/frequency_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ void main() {
),
Note.a.inOctave(4) + const Cent(37.63165622959145),
);

expect(
const Frequency(440).closestPitch(temperature: const Celsius(24)),
Note.a.inOctave(4) - const Cent(12.060895566170192),
);
expect(
const Frequency(440).closestPitch(temperature: const Celsius(18)),
Note.a.inOctave(4) + const Cent(6.062103827228064),
);
expect(
const Frequency(256).closestPitch(temperature: const Celsius(18)),
Note.c.inOctave(4) - const Cent(31.569552402363644),
);
});

test('returns the same Frequency after Pitch.frequency()', () {
Expand Down
13 changes: 13 additions & 0 deletions test/src/note/pitch_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,19 @@ void main() {
Note.b.inOctave(4).frequency(),
const Frequency(493.8833012561241),
);

expect(
Note.a.inOctave(4).frequency(temperature: const Celsius(18)),
const Frequency(438.4619866006409),
);
expect(
Note.a.inOctave(4).frequency(temperature: const Celsius(24)),
const Frequency(443.07602679871826),
);
expect(
Note.c.inOctave(4).frequency(temperature: const Celsius(18)),
const Frequency(260.71105706185494),
);
});

test('returns the Frequency of this Pitch at 438 Hz', () {
Expand Down

0 comments on commit 063679a

Please sign in to comment.