Skip to content

Commit

Permalink
chore: use concise abbreviations consistently
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticdog committed Aug 30, 2024
1 parent 074c439 commit 119eae1
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 77 deletions.
4 changes: 2 additions & 2 deletions src/theory/constants.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pub const midi_max = 127;

/// The number of note letters per octave.
pub const notes_per_oct = 7;
pub const ltr_per_oct = 7;

/// The number of semitones per octave.
pub const semis_per_oct = 12;
pub const sem_per_oct = 12;
49 changes: 24 additions & 25 deletions src/theory/interval.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ pub const Interval = struct {
}

fn isPerfect(num: u7) bool {
const simplified = @mod(num - 1, c.notes_per_oct) + 1;
return simplified == 1 or simplified == 4 or simplified == 5;
const simple_inter = @mod(num - 1, c.ltr_per_oct) + 1;
return simple_inter == 1 or simple_inter == 4 or simple_inter == 5;
}

/// Creates an interval from the given string representation.
Expand Down Expand Up @@ -65,12 +65,12 @@ pub const Interval = struct {
pub fn applyTo(self: Interval, note: Note) !Note {
const new_midi = @as(i16, note.midi) + self.semitones();

const let_index = @intFromEnum(note.name.let);
const new_let_index = @mod(let_index + self.num - 1, c.notes_per_oct);
const new_let: Note.Letter = @enumFromInt(new_let_index);
const ltr_index = @intFromEnum(note.name.ltr);
const new_ltr_index = @mod(ltr_index + self.num - 1, c.ltr_per_oct);
const new_ltr: Note.Letter = @enumFromInt(new_ltr_index);

const expected_pc = new_let.semitones();
const actual_pc = @mod(new_midi, c.semis_per_oct);
const expected_pc = new_ltr.semitones();
const actual_pc = @mod(new_midi, c.sem_per_oct);

const acc_diff = actual_pc - expected_pc;
const new_acc: Note.Accidental = switch (acc_diff) {
Expand All @@ -82,10 +82,9 @@ pub const Interval = struct {
else => return error.InvalidInterval,
};

const new_oct: i8 = @intCast(@divFloor(new_midi, c.semis_per_oct) - 1);
std.debug.print("self: {}, note: {}, new_let: {s}, new_acc: {s}, new_oct: {}\n", .{ self, note, @tagName(new_let), @tagName(new_acc), new_oct });
const new_oct: i8 = @intCast(@divFloor(new_midi, c.sem_per_oct) - 1);

return Note.init(new_let, new_acc, new_oct);
return Note.init(new_ltr, new_acc, new_oct);
}

/// Returns the inversion of the interval.
Expand All @@ -98,23 +97,23 @@ pub const Interval = struct {
.diminished => .augmented,
};

const simplified = @mod(self.num - 1, c.notes_per_oct) + 1;
const oct_offset = @divFloor(self.num - 1, c.notes_per_oct) * c.notes_per_oct;
const simple_inter = @mod(self.num - 1, c.ltr_per_oct) + 1;
const oct_offset = @divFloor(self.num - 1, c.ltr_per_oct) * c.ltr_per_oct;

var new_num: u7 = undefined;

if (simplified == 1) {
if (simple_inter == 1) {
// Special handling for unison and octave-based intervals.
if (self.num == 1) {
// Unison inverts to octave.
new_num = 8;
} else {
// Octave, double octave, etc. invert to the next lower octave.
new_num = @intCast(oct_offset - c.notes_per_oct + 1);
new_num = @intCast(oct_offset - c.ltr_per_oct + 1);
}
} else {
// For all other intervals, inversion adds up to 9
const new_simple = 9 - simplified;
const new_simple = 9 - simple_inter;
new_num = new_simple + oct_offset;
}

Expand All @@ -136,16 +135,16 @@ pub const Interval = struct {
}

fn baseSemitones(num: u7) i8 {
const simple_inter = @mod(num - 1, c.notes_per_oct) + 1;
const simple_inter = @mod(num - 1, c.ltr_per_oct) + 1;
const base_sem = @as(Note.Letter, @enumFromInt(simple_inter - 1)).semitones();
const oct_offset = (num - 1) / c.notes_per_oct * c.semis_per_oct;
const oct_offset = (num - 1) / c.ltr_per_oct * c.sem_per_oct;

return @intCast(base_sem + oct_offset);
}

/// Checks if the interval is compound (larger than an octave).
pub fn isCompound(self: Interval) bool {
return self.semitones() > c.semis_per_oct;
return self.semitones() > c.sem_per_oct;
}

/// Checks if the interval is simple (an octave or smaller).
Expand All @@ -157,21 +156,21 @@ pub const Interval = struct {
/// The interval is always calculated from the lower note to the higher note,
/// regardless of the order of the input arguments.
pub fn between(lhs: Note, rhs: Note) Interval {
const steps = lhs.diatonicStepsTo(rhs);
const semis = lhs.semitonesTo(rhs);
const ltr_steps = lhs.diatonicStepsTo(rhs);
const num: u7 = @intCast(@abs(ltr_steps) + 1);

const num: u7 = @intCast(@abs(steps) + 1);
const base = baseSemitones(num);
const offset = @as(i16, @abs(semis)) - base;
const sem = lhs.semitonesTo(rhs);
const base_sem = baseSemitones(num);
const qual_offset = @as(i16, @abs(sem)) - base_sem;

const qual = if (isPerfect(num))
switch (offset) {
switch (qual_offset) {
0 => Quality.perfect,
1 => Quality.augmented,
-1 => Quality.diminished,
else => unreachable,
}
else switch (offset) {
else switch (qual_offset) {
0 => Quality.major,
-1 => Quality.minor,
1 => Quality.augmented,
Expand Down
100 changes: 50 additions & 50 deletions src/theory/note.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub const Note = struct {
name: Spelling,

pub const Spelling = struct {
let: Letter,
ltr: Letter,
acc: Accidental,
};

Expand All @@ -31,7 +31,7 @@ pub const Note = struct {
a,
b,

/// Converts a Letter to its corresponding semitone value.
/// Converts the letter to its corresponding semitone value.
pub fn semitones(self: Letter) u4 {
return switch (self) {
.c => 0,
Expand All @@ -49,23 +49,23 @@ pub const Note = struct {

/// Creates a note from a letter, accidental, and octave.
/// Returns an error if the resulting note is out of the valid MIDI range.
pub fn init(let: Letter, acc: Accidental, oct: i8) !Note {
const base: i16 = let.semitones();
const offset: i16 = switch (acc) {
pub fn init(ltr: Letter, acc: Accidental, oct: i8) !Note {
const base_sem: i16 = ltr.semitones();
const acc_offset: i16 = switch (acc) {
.double_flat => -2,
.flat => -1,
.natural => 0,
.sharp => 1,
.double_sharp => 2,
};
const oct_semis = (@as(i16, oct) + 1) * c.semis_per_oct;
const oct_sem = (@as(i16, oct) + 1) * c.sem_per_oct;

const midi = base + offset + oct_semis;
const midi = base_sem + acc_offset + oct_sem;

if (midi < 0 or midi > c.midi_max) {
return error.NoteOutOfRange;
}
return .{ .midi = @intCast(midi), .name = .{ .let = let, .acc = acc } };
return .{ .midi = @intCast(midi), .name = .{ .ltr = ltr, .acc = acc } };
}

/// Creates a note from the given frequency in Hz.
Expand All @@ -76,7 +76,7 @@ pub const Note = struct {

const a4_freq = 440.0;
const a4_midi = 69;
const midi_float = a4_midi + c.semis_per_oct * @log2(freq / a4_freq);
const midi_float = a4_midi + c.sem_per_oct * @log2(freq / a4_freq);
const midi: u7 = @intFromFloat(@round(midi_float));

return Note.fromMidi(midi);
Expand All @@ -96,7 +96,7 @@ pub const Note = struct {

var iter = (try std.unicode.Utf8View.init(str)).iterator();

const let: Letter = switch (iter.nextCodepoint().?) {
const ltr: Letter = switch (iter.nextCodepoint().?) {
'C', 'c' => .c,
'D', 'd' => .d,
'E', 'e' => .e,
Expand Down Expand Up @@ -145,7 +145,7 @@ pub const Note = struct {
if (oct_str.len == 0) return error.MissingOctave;
const oct = std.fmt.parseInt(i8, oct_str, 10) catch return error.InvalidOctave;

return Note.init(let, acc, oct);
return Note.init(ltr, acc, oct);
}

/// Returns the frequency of the note in Hz.
Expand All @@ -154,36 +154,36 @@ pub const Note = struct {
const a4_freq = 440.0;
const a4_midi = 69;
const midi: f64 = @floatFromInt(self.midi);
return a4_freq * @exp2((midi - a4_midi) / c.semis_per_oct);
return a4_freq * @exp2((midi - a4_midi) / c.sem_per_oct);
}

/// Returns the octave of the note, accounting for accidentals.
/// Follows Scientific Pitch Notation conventions.
pub fn octave(self: Note) i8 {
const oct = @divFloor(@as(i8, self.midi), c.semis_per_oct) - 1;
const oct = @divFloor(@as(i8, self.midi), c.sem_per_oct) - 1;

// Handle notes that cross octave boundaries.
const offset: i8 = switch (self.name.acc) {
.flat, .double_flat => if (self.name.let == .c) 1 else 0,
.flat, .double_flat => if (self.name.ltr == .c) 1 else 0,
.natural => 0,
.sharp, .double_sharp => if (self.name.let == .b) -1 else 0,
.sharp, .double_sharp => if (self.name.ltr == .b) -1 else 0,
};

return oct + offset;
}

/// Returns the pitch class of the note.
pub fn pitchClass(self: Note) u4 {
return @intCast(@mod(self.midi, c.semis_per_oct));
return @intCast(@mod(self.midi, c.sem_per_oct));
}

/// Calculates the number of diatonic steps between this note and another note.
/// A positive result means the second note is higher, negative means lower.
pub fn diatonicStepsTo(self: Note, other: Note) i8 {
const let_diff = @as(i8, @intFromEnum(other.name.let)) - @as(i8, @intFromEnum(self.name.let));
const ltr_diff = @as(i8, @intFromEnum(other.name.ltr)) - @as(i8, @intFromEnum(self.name.ltr));
const oct_diff = other.octave() - self.octave();

return @intCast(let_diff + oct_diff * c.notes_per_oct);
return @intCast(ltr_diff + oct_diff * c.ltr_per_oct);
}

/// Calculates the number of semitones between this note and another note.
Expand All @@ -200,41 +200,41 @@ pub const Note = struct {
/// Spells a note using sharps based on its MIDI number.
/// For example, MIDI 61 will be spelled as C♯, not D♭.
pub fn spellWithSharps(midi: u7) Spelling {
const pc = @mod(midi, c.semis_per_oct);
const pc = @mod(midi, c.sem_per_oct);
return switch (pc) {
0 => .{ .let = .c, .acc = .natural },
1 => .{ .let = .c, .acc = .sharp },
2 => .{ .let = .d, .acc = .natural },
3 => .{ .let = .d, .acc = .sharp },
4 => .{ .let = .e, .acc = .natural },
5 => .{ .let = .f, .acc = .natural },
6 => .{ .let = .f, .acc = .sharp },
7 => .{ .let = .g, .acc = .natural },
8 => .{ .let = .g, .acc = .sharp },
9 => .{ .let = .a, .acc = .natural },
10 => .{ .let = .a, .acc = .sharp },
11 => .{ .let = .b, .acc = .natural },
0 => .{ .ltr = .c, .acc = .natural },
1 => .{ .ltr = .c, .acc = .sharp },
2 => .{ .ltr = .d, .acc = .natural },
3 => .{ .ltr = .d, .acc = .sharp },
4 => .{ .ltr = .e, .acc = .natural },
5 => .{ .ltr = .f, .acc = .natural },
6 => .{ .ltr = .f, .acc = .sharp },
7 => .{ .ltr = .g, .acc = .natural },
8 => .{ .ltr = .g, .acc = .sharp },
9 => .{ .ltr = .a, .acc = .natural },
10 => .{ .ltr = .a, .acc = .sharp },
11 => .{ .ltr = .b, .acc = .natural },
else => unreachable,
};
}

/// Spells a note using flats based on its MIDI number.
/// For example, MIDI 61 will be spelled as D♭, not C♯.
pub fn spellWithFlats(midi: u7) Spelling {
const pc = @mod(midi, c.semis_per_oct);
const pc = @mod(midi, c.sem_per_oct);
return switch (pc) {
0 => .{ .let = .c, .acc = .natural },
1 => .{ .let = .d, .acc = .flat },
2 => .{ .let = .d, .acc = .natural },
3 => .{ .let = .e, .acc = .flat },
4 => .{ .let = .e, .acc = .natural },
5 => .{ .let = .f, .acc = .natural },
6 => .{ .let = .g, .acc = .flat },
7 => .{ .let = .g, .acc = .natural },
8 => .{ .let = .a, .acc = .flat },
9 => .{ .let = .a, .acc = .natural },
10 => .{ .let = .b, .acc = .flat },
11 => .{ .let = .b, .acc = .natural },
0 => .{ .ltr = .c, .acc = .natural },
1 => .{ .ltr = .d, .acc = .flat },
2 => .{ .ltr = .d, .acc = .natural },
3 => .{ .ltr = .e, .acc = .flat },
4 => .{ .ltr = .e, .acc = .natural },
5 => .{ .ltr = .f, .acc = .natural },
6 => .{ .ltr = .g, .acc = .flat },
7 => .{ .ltr = .g, .acc = .natural },
8 => .{ .ltr = .a, .acc = .flat },
9 => .{ .ltr = .a, .acc = .natural },
10 => .{ .ltr = .b, .acc = .flat },
11 => .{ .ltr = .b, .acc = .natural },
else => unreachable,
};
}
Expand All @@ -255,7 +255,7 @@ pub const Note = struct {
const use_ascii = std.mem.indexOfScalar(u8, fmt, 'c') != null;

try writer.print("{c}{s}{d}", .{
std.ascii.toUpper(@tagName(self.name.let)[0]),
std.ascii.toUpper(@tagName(self.name.ltr)[0]),
if (use_ascii)
switch (self.name.acc) {
.double_flat => "bb",
Expand Down Expand Up @@ -293,31 +293,31 @@ test "initialization" {

test "properties" {
const b3 = try Note.init(.b, .natural, 3);
try testing.expectEqual(Note.Letter.b, b3.name.let);
try testing.expectEqual(Note.Letter.b, b3.name.ltr);
try testing.expectEqual(Note.Accidental.natural, b3.name.acc);
try testing.expectEqual(3, b3.octave());
try testing.expectEqual(11, b3.pitchClass());

const cf4 = try Note.init(.c, .flat, 4);
try testing.expectEqual(Note.Letter.c, cf4.name.let);
try testing.expectEqual(Note.Letter.c, cf4.name.ltr);
try testing.expectEqual(Note.Accidental.flat, cf4.name.acc);
try testing.expectEqual(4, cf4.octave());
try testing.expectEqual(11, cf4.pitchClass());

const c4 = try Note.init(.c, .natural, 4);
try testing.expectEqual(Note.Letter.c, c4.name.let);
try testing.expectEqual(Note.Letter.c, c4.name.ltr);
try testing.expectEqual(Note.Accidental.natural, c4.name.acc);
try testing.expectEqual(4, c4.octave());
try testing.expectEqual(0, c4.pitchClass());

const cs4 = try Note.init(.c, .sharp, 4);
try testing.expectEqual(Note.Letter.c, cs4.name.let);
try testing.expectEqual(Note.Letter.c, cs4.name.ltr);
try testing.expectEqual(Note.Accidental.sharp, cs4.name.acc);
try testing.expectEqual(4, cs4.octave());
try testing.expectEqual(1, cs4.pitchClass());

const df4 = try Note.init(.d, .flat, 4);
try testing.expectEqual(Note.Letter.d, df4.name.let);
try testing.expectEqual(Note.Letter.d, df4.name.ltr);
try testing.expectEqual(Note.Accidental.flat, df4.name.acc);
try testing.expectEqual(4, df4.octave());
try testing.expectEqual(1, df4.pitchClass());
Expand Down

0 comments on commit 119eae1

Please sign in to comment.