Skip to content

Commit

Permalink
doc: add doc comments to all public api functions
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticdog committed Aug 26, 2024
1 parent 931d37b commit cfaaef6
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 23 deletions.
24 changes: 20 additions & 4 deletions src/theory/interval.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub const Interval = struct {
fourteenth,
double_octave,

pub fn fromInt(int: i8) !Number {
fn fromInt(int: i8) !Number {
const minimum = 1;
const maximum = @typeInfo(Number).Enum.fields.len;

Expand All @@ -47,31 +47,35 @@ pub const Interval = struct {
return @enumFromInt(int);
}

pub fn isPerfect(self: Number) bool {
fn isPerfect(self: Number) bool {
return switch (self) {
.unison, .fourth, .fifth, .octave, .eleventh, .twelfth, .double_octave => true,
else => false,
};
}
};

// Convenience constructors.
/// Creates a perfect interval with the given number.
pub fn perf(number: i8) !Interval {
return try create(.perfect, number);
}

/// Creates a major interval with the given number.
pub fn maj(number: i8) !Interval {
return try create(.major, number);
}

/// Creates a minor interval with the given number.
pub fn min(number: i8) !Interval {
return try create(.minor, number);
}

/// Creates an augmented interval with the given number.
pub fn aug(number: i8) !Interval {
return try create(.augmented, number);
}

/// Creates a diminished interval with the given number.
pub fn dim(number: i8) !Interval {
return try create(.diminished, number);
}
Expand All @@ -86,6 +90,7 @@ pub const Interval = struct {
return .{ .quality = quality, .number = number };
}

/// Parses a string representation of an interval and returns the corresponding Interval.
pub fn fromString(str: []const u8) !Interval {
if (str.len < 2) return error.InvalidStringFormat;

Expand Down Expand Up @@ -115,6 +120,7 @@ pub const Interval = struct {
return Number.fromInt(num);
}

/// Returns the number of semitones in the interval.
pub fn getSemitones(self: Interval) i8 {
const base_semitones = baseSemitones(self.number);

Expand Down Expand Up @@ -148,6 +154,7 @@ pub const Interval = struct {
};
}

/// Calculates the interval between two pitches.
pub fn betweenPitches(from: Pitch, to: Pitch) !Interval {
const diatonic_steps = from.diatonicStepsTo(to);
const semitones = from.semitonesTo(to);
Expand Down Expand Up @@ -181,6 +188,7 @@ pub const Interval = struct {
}
}

/// Applies the interval to a given pitch, returning the resulting pitch.
pub fn applyToPitch(self: Interval, pitch: Pitch) !Pitch {
const start_letter = @intFromEnum(pitch.note.letter);
const steps = @intFromEnum(self.number) - 1;
Expand All @@ -202,6 +210,7 @@ pub const Interval = struct {
return .{ .note = new_note, .octave = new_pitch.octave };
}

/// Returns the inversion of the interval.
pub fn invert(self: Interval) Interval {
const new_quality: Quality = switch (self.quality) {
.perfect => .perfect,
Expand Down Expand Up @@ -231,15 +240,17 @@ pub const Interval = struct {
return .{ .quality = new_quality, .number = new_number };
}

/// Checks if the interval is compound (larger than an octave).
pub fn isCompound(self: Interval) bool {
return @intFromEnum(self.number) > constants.diatonic_degrees;
}

/// Checks if the interval is simple (an octave or smaller).
pub fn isSimple(self: Interval) bool {
return !self.isCompound();
}

// Checks if the given combination of quality and number would make a valid interval.
/// Checks if the given combination of quality and number is musically valid.
pub fn isValid(quality: Quality, number: Number) bool {
if (number.isPerfect()) {
return switch (quality) {
Expand All @@ -254,6 +265,9 @@ pub const Interval = struct {
}
}

/// Formats the Interval for output.
///
/// Outputs the interval in standard notation (e.g., "P5" for perfect fifth).
pub fn format(
self: Interval,
comptime fmt: []const u8,
Expand All @@ -274,6 +288,7 @@ pub const Interval = struct {
try writer.print("{s}{d}", .{ quality_str, @intFromEnum(self.number) });
}

/// Returns a formatter for the interval's shorthand representation.
pub fn fmtShorthand(self: Interval) std.fmt.Formatter(formatShorthand) {
return .{ .data = self };
}
Expand All @@ -298,6 +313,7 @@ pub const Interval = struct {
try writer.print("{s}{d}", .{ quality_str, @intFromEnum(self.number) });
}

/// Returns a formatter for the interval's full name.
pub fn fmtName(self: Interval) std.fmt.Formatter(formatName) {
return .{ .data = self };
}
Expand Down
31 changes: 25 additions & 6 deletions src/theory/note.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub const Note = struct {
sharp,
double_sharp,

/// Converts a semitone offset to an Accidental, returning null for natural (0).
///
/// Returns an error if the offset is outside the valid range (-2 to 2).
pub fn fromSemitoneOffset(semitones: i8) !?Accidental {
return switch (semitones) {
-2 => .double_flat,
Expand All @@ -30,6 +33,9 @@ pub const Note = struct {
};
}

/// Returns the semitone offset for this Accidental.
///
/// Double flat: -2, Flat: -1, Natural: 0, Sharp: 1, Double sharp: 2
pub fn getSemitoneOffset(self: Accidental) i8 {
return switch (self) {
.double_flat => -2,
Expand All @@ -41,7 +47,7 @@ pub const Note = struct {
}
};

// Helpers for creating notes and modifying accidentals.
/// Helper constants for creating natural notes.
pub const c = Note{ .letter = .c, .accidental = null };
pub const d = Note{ .letter = .d, .accidental = null };
pub const e = Note{ .letter = .e, .accidental = null };
Expand All @@ -50,28 +56,34 @@ pub const Note = struct {
pub const a = Note{ .letter = .a, .accidental = null };
pub const b = Note{ .letter = .b, .accidental = null };

/// Returns a new Note with the same letter and a double flat accidental.
pub fn doubleFlat(self: Note) Note {
return .{ .letter = self.letter, .accidental = .double_flat };
}

/// Returns a new Note with the same letter and a flat accidental.
pub fn flat(self: Note) Note {
return .{ .letter = self.letter, .accidental = .flat };
}

/// Returns a new Note with the same letter and an explicit natural accidental.
pub fn natural(self: Note) Note {
return .{ .letter = self.letter, .accidental = .natural };
}

/// Returns a new Note with the same letter and a sharp accidental.
pub fn sharp(self: Note) Note {
return .{ .letter = self.letter, .accidental = .sharp };
}

/// Returns a new Note with the same letter and a double sharp accidental.
pub fn doubleSharp(self: Note) Note {
return .{ .letter = self.letter, .accidental = .double_sharp };
}

/// Returns a `Note` based on the given pitch class, using the default mapping.
/// Creates a Note from the given pitch class.
///
/// Uses the simplest default mapping:
/// 0:C, 1:C♯, 2:D, 3:D♯, 4:E, 5:F, 6:F♯, 7:G, 8:G♯, 9:A, 10:A♯, 11:B
pub fn fromPitchClass(pitch_class: u4) Note {
assert(0 <= pitch_class and pitch_class < constants.pitch_classes);
Expand All @@ -95,6 +107,7 @@ pub const Note = struct {
return .{ .letter = letter, .accidental = accidental };
}

/// Parses a string representation of a note and returns the corresponding Note.
pub fn fromString(str: []const u8) !Note {
if (str.len < 1) return error.InvalidStringFormat;

Expand Down Expand Up @@ -144,6 +157,7 @@ pub const Note = struct {
return error.InvalidAccidental;
}

/// Returns the pitch class of the note.
pub fn getPitchClass(self: Note) u4 {
const base_class: u4 = switch (self.letter) {
.c => 0,
Expand All @@ -160,6 +174,9 @@ pub const Note = struct {
return @intCast(result);
}

/// Formats the Note for output.
///
/// Use '{c}' in the format string for ASCII output, otherwise Unicode is used.
pub fn format(
self: Note,
comptime fmt: []const u8,
Expand Down Expand Up @@ -199,6 +216,7 @@ pub const Note = struct {
return note_table[letter_index + row_offset];
}

/// Returns a formatter for the note's German representation.
pub fn fmtGerman(self: Note) std.fmt.Formatter(formatGerman) {
return .{ .data = self };
}
Expand All @@ -215,6 +233,11 @@ pub const Note = struct {
try writer.writeAll(self.formatImpl(.german, encoding));
}

/// Returns a formatter for the note's solfège representation.
pub fn fmtSolfege(self: Note) std.fmt.Formatter(formatSolfege) {
return .{ .data = self };
}

fn formatSolfege(
self: Note,
comptime fmt: []const u8,
Expand All @@ -226,10 +249,6 @@ pub const Note = struct {
const encoding: Encoding = if (use_ascii) .ascii else .unicode;
try writer.writeAll(self.formatImpl(.solfege, encoding));
}

pub fn fmtSolfege(self: Note) std.fmt.Formatter(formatSolfege) {
return .{ .data = self };
}
};

pub const NamingSystem = enum {
Expand Down
23 changes: 18 additions & 5 deletions src/theory/pitch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ const standard_freq = 440.0; // hertz
// The practical range for musical octaves covering MIDI numbers and human hearing.
// On the low side, B#-2 has an effective octave of -1 and would be MIDI number 0.
// On the high side, octave 10 gets us above the typical 20k Hz hearing range.
pub const min_octave: i16 = -2;
pub const max_octave: i16 = 10;
const min_octave: i16 = -2;
const max_octave: i16 = 10;

// Musical pitch representation using Scientific Pitch Notation.
/// Musical pitch representation using Scientific Pitch Notation.
pub const Pitch = struct {
note: Note,
octave: i16,

/// Creates a Pitch from the given frequency in Hz.
pub fn fromFrequency(freq: f64) Pitch {
return fromFrequencyWithReference(freq, standard_pitch, standard_freq);
}

/// Creates a Pitch from the given frequency, using a custom reference pitch and frequency.
pub fn fromFrequencyWithReference(freq: f64, ref_pitch: Pitch, ref_freq: f64) Pitch {
assert(freq > 0);

Expand All @@ -42,6 +44,7 @@ pub const Pitch = struct {
return .{ .note = new_note, .octave = new_octave };
}

/// Creates a Pitch from the given MIDI note number.
pub fn fromMidiNumber(midi_number: u7) Pitch {
const semitones_from_c0 = @as(i16, midi_number) - constants.pitch_classes;
const octave = @divFloor(semitones_from_c0, constants.pitch_classes);
Expand All @@ -51,6 +54,7 @@ pub const Pitch = struct {
return .{ .note = note, .octave = @intCast(octave) };
}

/// Parses a string representation of a pitch and returns the corresponding Pitch.
pub fn fromString(str: []const u8) !Pitch {
if (str.len < 2) return error.InvalidStringFormat;

Expand Down Expand Up @@ -79,18 +83,19 @@ pub const Pitch = struct {
return .{ .note = note, .octave = octave };
}

// pub fn transpose(self: Pitch, semitones: i8) Pitch {}

/// Returns the frequency of the pitch in Hz.
pub fn getFrequency(self: Pitch) f64 {
return self.getFrequencyWithReference(standard_pitch, standard_freq);
}

/// Returns the frequency of the pitch in Hz, using a custom reference pitch and frequency.
pub fn getFrequencyWithReference(self: Pitch, ref_pitch: Pitch, ref_freq: f64) f64 {
const semitones_from_ref: f64 = @floatFromInt(ref_pitch.semitonesTo(self));
const octave_ratio = semitones_from_ref / constants.pitch_classes;
return ref_freq * @exp2(octave_ratio);
}

/// Returns the effective octave of the pitch, accounting for accidentals.
pub fn getEffectiveOctave(self: Pitch) i16 {
var offset: i16 = 0;
if (self.note.accidental) |acc| {
Expand All @@ -103,6 +108,7 @@ pub const Pitch = struct {
return self.octave + offset;
}

/// Converts the pitch to its corresponding MIDI note number.
pub fn toMidiNumber(self: Pitch) !u7 {
const midi_zero_pitch = Pitch{ .note = Note.c, .octave = -1 };
const semitones_above_midi_zero = midi_zero_pitch.semitonesTo(self);
Expand All @@ -114,13 +120,15 @@ pub const Pitch = struct {
return @intCast(semitones_above_midi_zero);
}

/// Checks if this pitch is enharmonic with another pitch.
pub fn isEnharmonic(self: Pitch, other: Pitch) bool {
const same_octave = self.getEffectiveOctave() == other.getEffectiveOctave();
const same_pitch_class = self.note.getPitchClass() == other.note.getPitchClass();

return same_octave and same_pitch_class;
}

/// Calculates the number of diatonic steps between this pitch and another pitch.
pub fn diatonicStepsTo(self: Pitch, other: Pitch) i16 {
const self_letter = @intFromEnum(self.note.letter);
const other_letter = @intFromEnum(other.note.letter);
Expand All @@ -129,10 +137,12 @@ pub const Pitch = struct {
return (other_letter - self_letter) + (octave_diff * constants.diatonic_degrees) + 1;
}

/// Calculates the number of octaves between this pitch and another pitch.
pub fn octavesTo(self: Pitch, other: Pitch) i16 {
return other.getEffectiveOctave() - self.getEffectiveOctave();
}

/// Calculates the number of semitones between this pitch and another pitch.
pub fn semitonesTo(self: Pitch, other: Pitch) i16 {
const self_octave = self.getEffectiveOctave();
const other_octave = other.getEffectiveOctave();
Expand All @@ -143,6 +153,9 @@ pub const Pitch = struct {
(self_octave * constants.pitch_classes + self_pitch_class);
}

/// Formats the Pitch for output.
///
/// Outputs the note followed by the octave number (e.g., "A4" for A in the 4th octave).
pub fn format(
self: Pitch,
comptime fmt: []const u8,
Expand Down
Loading

0 comments on commit cfaaef6

Please sign in to comment.