Skip to content

Commit

Permalink
Merge 400feb5 into ea435e1
Browse files Browse the repository at this point in the history
  • Loading branch information
albertms10 committed Mar 31, 2024
2 parents ea435e1 + 400feb5 commit 8a7ec4e
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 120 deletions.
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,16 +474,25 @@ Get the `Frequency` of a `Pitch`:

```dart
Note.a.inOctave(4).frequency(); // 440
Note.a.inOctave(4).frequency(temperature: const Celsius(18));
// 438.4619866006409
```

Create a `TuningFork` from a `Pitch` and a reference `Frequency`,
or using its shorthand static constants:

```dart
Note.a.inOctave(4).at(const Frequency(438)); // A438
TuningFork.a440; // A440
TuningFork.c256; // C256
```

And use it in a `TuningSystem`:

final tuningSystem =
EqualTemperament.edo12(referencePitch: Note.c.inOctave(4));
```dart
Note.b.flat.inOctave(4).frequency(
referenceFrequency: const Frequency(256),
tuningSystem: tuningSystem,
tuningSystem: const EqualTemperament.edo12(TuningFork.c256),
); // 456.1401436878537
Note.a.inOctave(4).frequency(temperature: const Celsius(18));
// 438.4619866006409
```

Get the closest `Pitch` from a given `Frequency`:
Expand All @@ -494,7 +503,8 @@ const Frequency(314).closestPitch(); // E♭4+16
const Frequency(440).closestPitch(temperature: const Celsius(24)); // A4-12
```

And combining both methods, the harmonic series of a given `Pitch`:
And combining both `frequency` and `closestPitch` methods,
the harmonic series of a given `Pitch`:

```dart
Note.c.inOctave(1).harmonics(upToIndex: 15);
Expand Down
14 changes: 7 additions & 7 deletions example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,17 @@ void main() {

// Frequencies
Note.a.inOctave(4).frequency(); // 440
Note.a.inOctave(4).frequency(temperature: const Celsius(18));
// 438.4619866006409

Note.a.inOctave(4).at(const Frequency(438)); // A438
TuningFork.a440; // A440
TuningFork.c256; // C256

final tuningSystem =
EqualTemperament.edo12(referencePitch: Note.c.inOctave(4));
Note.b.flat.inOctave(4).frequency(
referenceFrequency: const Frequency(256),
tuningSystem: tuningSystem,
tuningSystem: const EqualTemperament.edo12(TuningFork.c256),
); // 456.1401436878537

Note.a.inOctave(4).frequency(temperature: const Celsius(18));
// 438.4619866006409

const Frequency(432).closestPitch(); // A4-32
const Frequency(314).closestPitch(); // E♭4+16
const Frequency(440).closestPitch(temperature: const Celsius(24)); // A4-12
Expand Down
1 change: 1 addition & 0 deletions lib/music_notes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ 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_fork.dart';
export 'src/tuning/tuning_system.dart';
11 changes: 3 additions & 8 deletions lib/src/note/closest_pitch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,19 @@ class ClosestPitch {
return ClosestPitch(Pitch.parse(match[1]!), cents: cents);
}

/// The [Frequency] of this [ClosestPitch] from [referenceFrequency],
/// [tuningSystem] and [temperature].
/// The [Frequency] of this [ClosestPitch] from [tuningSystem] and
/// [temperature].
///
/// Example:
/// ```dart
/// (Note.a.inOctave(4) + const Cent(12)).frequency() == const Frequency(443)
/// ```
Frequency frequency({
Frequency referenceFrequency = Frequency.reference,
TuningSystem tuningSystem = const EqualTemperament.edo12(),
Celsius temperature = Celsius.reference,
}) =>
Frequency(
pitch.frequency(
referenceFrequency: referenceFrequency,
tuningSystem: tuningSystem,
temperature: temperature,
) *
pitch.frequency(tuningSystem: tuningSystem, temperature: temperature) *
cents.ratio,
);

Expand Down
11 changes: 4 additions & 7 deletions lib/src/note/frequency.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ extension type const Frequency._(num hertz) implements num {
/// ```
bool get isHumanAudible => HearingRange.human.isAudible(this);

/// The [ClosestPitch] to this [Frequency] from [referenceFrequency],
/// [tuningSystem] and [temperature].
/// The [ClosestPitch] to this [Frequency] from [tuningSystem] and
/// [temperature].
///
/// Example:
/// ```dart
Expand All @@ -56,20 +56,17 @@ extension type const Frequency._(num hertz) implements num {
/// reference.closestPitch().frequency() == reference;
/// ```
ClosestPitch closestPitch({
Frequency referenceFrequency = Frequency.reference,
TuningSystem tuningSystem = const EqualTemperament.edo12(),
Celsius temperature = Celsius.reference,
}) {
final cents = Ratio(at(temperature) / referenceFrequency).cents;
final semitones =
tuningSystem.referencePitch.semitones + (cents / 100).round();
final cents = Ratio(at(temperature) / tuningSystem.fork.frequency).cents;
final semitones = tuningSystem.fork.pitch.semitones + (cents / 100).round();

final closestPitch = PitchClass(semitones)
.resolveClosestSpelling()
.inOctave(Pitch.octaveFromSemitones(semitones));

final closestPitchFrequency = closestPitch.frequency(
referenceFrequency: referenceFrequency,
tuningSystem: tuningSystem,
temperature: temperature,
);
Expand Down
38 changes: 24 additions & 14 deletions lib/src/note/pitch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '../scalable.dart';
import '../tuning/cent.dart';
import '../tuning/equal_temperament.dart';
import '../tuning/temperature.dart';
import '../tuning/tuning_fork.dart';
import '../tuning/tuning_system.dart';
import 'accidental.dart';
import 'base_note.dart';
Expand Down Expand Up @@ -39,7 +40,10 @@ final class Pitch extends Scalable<Pitch> implements Comparable<Pitch> {
const Pitch(this.note, {required this.octave});

/// The reference [Pitch].
static const reference = Pitch(Note.a, octave: 4);
static const reference = Pitch(Note.a, octave: referenceOctave);

/// The reference octave.
static const referenceOctave = 4;

static const _superPrime = '′';
static const _superPrimeAlt = "'";
Expand Down Expand Up @@ -386,29 +390,28 @@ final class Pitch extends Scalable<Pitch> implements Comparable<Pitch> {
);
}

/// The [Frequency] of this [Pitch] from [referenceFrequency],
/// [tuningSystem] and [frequency].
/// The [Frequency] of this [Pitch] from [tuningSystem] and [temperature].
///
/// Example:
/// ```dart
/// Note.a.inOctave(4).frequency() == const Frequency(440)
/// Note.c.inOctave(4).frequency() == const Frequency(261.63)
///
/// Note.b.flat.inOctave(4).frequency(
/// referenceFrequency: const Frequency(438),
/// tuningSystem: EqualTemperament.edo12(
/// Pitch.reference.at(const Frequency(438)),
/// ),
/// ) == const Frequency(464.04)
///
/// Note.a.inOctave(4).frequency(
/// referenceFrequency: const Frequency(256),
/// tuningSystem:
/// EqualTemperament.edo12(referencePitch: Note.c.inOctave(4)),
/// tuningSystem: const EqualTemperament.edo12(TuningFork.c256),
/// ) == 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`.
Expand All @@ -418,13 +421,23 @@ final class Pitch extends Scalable<Pitch> implements Comparable<Pitch> {
/// 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)).at(temperature);
Frequency(tuningSystem.fork.frequency * tuningSystem.ratio(this))
.at(temperature);

/// Creates a new [TuningFork] from this [Pitch] at a given [frequency].
///
/// Example:
/// ```dart
/// Pitch.reference.at(const Frequency(440)) == TuningFork.a440
/// Note.c.inOctave(4).at(const Frequency(256)) == TuningFork.c256
/// ```
TuningFork at(Frequency frequency) => TuningFork(this, frequency);

/// The [ClosestPitch] set of harmonics series [upToIndex] from this [Pitch].
/// The [ClosestPitch] set of harmonics series [upToIndex] from this [Pitch]
/// from [tuningSystem] and [temperature].
///
/// Example:
/// ```dart
Expand All @@ -434,20 +447,17 @@ final class Pitch extends Scalable<Pitch> implements Comparable<Pitch> {
/// ```
Set<ClosestPitch> harmonics({
required int upToIndex,
Frequency referenceFrequency = Frequency.reference,
TuningSystem tuningSystem = const EqualTemperament.edo12(),
Celsius temperature = Celsius.reference,
}) =>
frequency(
referenceFrequency: referenceFrequency,
tuningSystem: tuningSystem,
// we deliberately omit the temperature here, as the subsequent call to
// `Frequency.closestPitch` will already take it into account.
)
.harmonics(upToIndex: upToIndex)
.map(
(frequency) => frequency.closestPitch(
referenceFrequency: referenceFrequency,
tuningSystem: tuningSystem,
temperature: temperature,
),
Expand Down
22 changes: 12 additions & 10 deletions lib/src/tuning/equal_temperament.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import '../note/base_note.dart';
import '../note/pitch.dart';
import 'cent.dart';
import 'ratio.dart';
import 'tuning_fork.dart';
import 'tuning_system.dart';

/// A representation of an equal temperament tuning system.
Expand All @@ -25,16 +26,18 @@ class EqualTemperament extends TuningSystem {
/// The equal divisions between each [BaseNote] and the next one.
List<int> get steps => UnmodifiableListView(_steps);

/// Creates a new [EqualTemperament] from [_steps] and [referencePitch].
const EqualTemperament(this._steps, {super.referencePitch = Pitch.reference});
/// Creates a new [EqualTemperament] from [_steps] and [fork].
const EqualTemperament(this._steps, {super.fork = TuningFork.a440});

/// See [12 equal temperament](https://en.wikipedia.org/wiki/12_equal_temperament).
const EqualTemperament.edo12({super.referencePitch = Pitch.reference})
: _steps = const [2, 2, 1, 2, 2, 2, 1];
const EqualTemperament.edo12([TuningFork fork = TuningFork.a440])
: _steps = const [2, 2, 1, 2, 2, 2, 1],
super(fork: fork);

/// See [19 equal temperament](https://en.wikipedia.org/wiki/19_equal_temperament).
const EqualTemperament.edo19({super.referencePitch = Pitch.reference})
: _steps = const [3, 3, 2, 3, 3, 3, 2];
const EqualTemperament.edo19([TuningFork fork = TuningFork.a440])
: _steps = const [3, 3, 2, 3, 3, 3, 2],
super(fork: fork);

/// The equal divisions of the octave of this [EqualTemperament].
///
Expand Down Expand Up @@ -70,8 +73,7 @@ class EqualTemperament extends TuningSystem {
Ratio(math.pow(2, semitones / edo));

@override
Ratio ratio(Pitch pitch) =>
ratioFromSemitones(referencePitch.difference(pitch));
Ratio ratio(Pitch pitch) => ratioFromSemitones(fork.pitch.difference(pitch));

/// The reference generator cents.
static const referenceGeneratorCents = Cent(700);
Expand All @@ -86,8 +88,8 @@ class EqualTemperament extends TuningSystem {
bool operator ==(Object other) =>
other is EqualTemperament &&
const ListEquality<int>().equals(_steps, other._steps) &&
referencePitch == other.referencePitch;
fork == other.fork;

@override
int get hashCode => Object.hash(Object.hashAll(_steps), referencePitch);
int get hashCode => Object.hash(Object.hashAll(_steps), fork);
}
18 changes: 8 additions & 10 deletions lib/src/tuning/just_intonation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import 'package:meta/meta.dart' show immutable;

import '../interval/interval.dart';
import '../music.dart';
import '../note/note.dart';
import '../note/pitch.dart';
import 'cent.dart';
import 'ratio.dart';
import 'tuning_fork.dart';
import 'tuning_system.dart';

/// A representation of a just tuning system.
Expand All @@ -19,10 +19,8 @@ import 'tuning_system.dart';
/// * [TuningSystem].
@immutable
sealed class JustIntonation extends TuningSystem {
/// Creates a new [JustIntonation] from [referencePitch].
const JustIntonation({
super.referencePitch = const Pitch(Note.c, octave: 4),
});
/// Creates a new [JustIntonation] from [fork].
const JustIntonation({super.fork = TuningFork.c256});

/// The [Ratio] of an ascending [Interval.P5].
static const ascendingFifthRatio = Ratio(3 / 2);
Expand All @@ -43,12 +41,12 @@ sealed class JustIntonation extends TuningSystem {
/// See [Pythagorean tuning](https://en.wikipedia.org/wiki/Pythagorean_tuning).
@immutable
class PythagoreanTuning extends JustIntonation {
/// Creates a new [PythagoreanTuning] from [referencePitch].
const PythagoreanTuning({super.referencePitch});
/// Creates a new [PythagoreanTuning] from [fork].
const PythagoreanTuning({super.fork});

@override
Ratio ratio(Pitch pitch) {
final distance = referencePitch.note.fifthsDistanceWith(pitch.note);
final distance = fork.pitch.note.fifthsDistanceWith(pitch.note);
var ratio = 1.0;
for (var i = 1; i <= distance.abs(); i++) {
ratio *= distance.isNegative
Expand All @@ -60,11 +58,11 @@ class PythagoreanTuning extends JustIntonation {
}

final octaveDelta =
pitch.interval(referencePitch).semitones.abs() ~/ chromaticDivisions;
pitch.interval(fork.pitch).semitones.abs() ~/ chromaticDivisions;

return Ratio(ratio * math.pow(2, octaveDelta));
}

/// See [Pythagorean comma](https://en.wikipedia.org/wiki/Pythagorean_comma).
Ratio get pythagoreanComma => ratio(referencePitch.transposeBy(-Interval.d2));
Ratio get pythagoreanComma => ratio(fork.pitch.transposeBy(-Interval.d2));
}
Loading

0 comments on commit 8a7ec4e

Please sign in to comment.