Skip to content

Commit

Permalink
fix: ensure accurate scale counts without octave
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticdog committed Aug 26, 2024
1 parent b78c243 commit 931d37b
Showing 1 changed file with 35 additions and 35 deletions.
70 changes: 35 additions & 35 deletions src/theory/scale.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ pub const Scale = struct {
tonic: Note,
pattern: Pattern,
count: usize = undefined,
intervals: ?[13]Interval = null,
notes: ?[13]Note = null,
semitones: ?[13]i8 = null,
intervals: ?[12]Interval = null,
notes: ?[12]Note = null,
semitones: ?[12]i8 = null,

pub const Pattern = enum {
major,
Expand Down Expand Up @@ -41,20 +41,20 @@ pub const Scale = struct {
}

fn generateIntervals(self: *Scale) void {
var intervals: [13]Interval = undefined;
var intervals: [12]Interval = undefined;
const interval_strings = switch (self.pattern) {
.major => &[_][]const u8{ "P1", "M2", "M3", "P4", "P5", "M6", "M7", "P8" },
.natural_minor => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "m6", "m7", "P8" },
.harmonic_minor => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "m6", "M7", "P8" },
.melodic_minor => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "M6", "M7", "P8" },
.pentatonic_major => &[_][]const u8{ "P1", "M2", "M3", "P5", "M6", "P8" },
.pentatonic_minor => &[_][]const u8{ "P1", "m3", "P4", "P5", "m7", "P8" },
.major => &[_][]const u8{ "P1", "M2", "M3", "P4", "P5", "M6", "M7" },
.natural_minor => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "m6", "m7" },
.harmonic_minor => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "m6", "M7" },
.melodic_minor => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "M6", "M7" },
.pentatonic_major => &[_][]const u8{ "P1", "M2", "M3", "P5", "M6" },
.pentatonic_minor => &[_][]const u8{ "P1", "m3", "P4", "P5", "m7" },
.chromatic => &self.generateChromaticIntervals(),
.dorian => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "M6", "m7", "P8" },
.phrygian => &[_][]const u8{ "P1", "m2", "m3", "P4", "P5", "m6", "m7", "P8" },
.lydian => &[_][]const u8{ "P1", "M2", "M3", "A4", "P5", "M6", "M7", "P8" },
.mixolydian => &[_][]const u8{ "P1", "M2", "M3", "P4", "P5", "M6", "m7", "P8" },
.locrian => &[_][]const u8{ "P1", "m2", "m3", "P4", "d5", "m6", "m7", "P8" },
.dorian => &[_][]const u8{ "P1", "M2", "m3", "P4", "P5", "M6", "m7" },
.phrygian => &[_][]const u8{ "P1", "m2", "m3", "P4", "P5", "m6", "m7" },
.lydian => &[_][]const u8{ "P1", "M2", "M3", "A4", "P5", "M6", "M7" },
.mixolydian => &[_][]const u8{ "P1", "M2", "M3", "P4", "P5", "M6", "m7" },
.locrian => &[_][]const u8{ "P1", "m2", "m3", "P4", "d5", "m6", "m7" },
.whole_tone => &self.generateWholeToneIntervals(),
};

Expand All @@ -67,17 +67,17 @@ pub const Scale = struct {
}

// TODO: these patterns change based on the tonic
fn generateChromaticIntervals(self: Scale) [13][]const u8 {
fn generateChromaticIntervals(self: Scale) [12][]const u8 {
_ = self.tonic; // Unused for now
// Placeholder: returns a single hard-coded list
return .{ "P1", "m2", "M2", "m3", "M3", "P4", "A4", "P5", "m6", "M6", "m7", "M7", "P8" };
return .{ "P1", "m2", "M2", "m3", "M3", "P4", "A4", "P5", "m6", "M6", "m7", "M7" };
}

// TODO: these patterns change based on the tonic
fn generateWholeToneIntervals(self: Scale) [7][]const u8 {
fn generateWholeToneIntervals(self: Scale) [6][]const u8 {
_ = self.tonic; // Unused for now
// Placeholder: returns a single hard-coded list
return .{ "P1", "M2", "M3", "A4", "A5", "A6", "P8" };
return .{ "P1", "M2", "M3", "A4", "A5", "A6" };
}

pub fn getIntervals(self: *Scale) []const Interval {
Expand Down Expand Up @@ -114,7 +114,7 @@ pub const Scale = struct {
}

fn generateNotes(self: *Scale) void {
var notes: [13]Note = undefined;
var notes: [12]Note = undefined;
const reference_pitch = Pitch{ .note = self.tonic, .octave = 4 };
const intervals = self.getIntervals();

Expand All @@ -135,11 +135,11 @@ pub const Scale = struct {
if (self.semitones == null) {
self.generateSemitones();
}
return self.semitones.?[0 .. self.count - 1]; // exclude the last interval (P8)
return self.semitones.?[0..self.count];
}

fn generateSemitones(self: *Scale) void {
var semitones: [13]i8 = undefined;
var semitones: [12]i8 = undefined;
var previous_semitones: i8 = 0;
const intervals = self.getIntervals();

Expand All @@ -151,11 +151,15 @@ pub const Scale = struct {
semitones[i] = current_semitones - previous_semitones;
previous_semitones = current_semitones;

log.debug("Interval {}: {} semitones from previous note", .{ i + 2, semitones[i] });
log.debug("Interval {}: {} semitones from previous note", .{ interval, semitones[i] });
}

// Add the final interval (P8) to complete the octave
const octave_semitones = 12;
semitones[intervals.len - 1] = octave_semitones - previous_semitones;

self.semitones = semitones;
log.debug("Generated semitones: {any}", .{self.semitones.?[0 .. self.count - 1]});
log.debug("Generated semitones: {any}", .{self.semitones.?[0..self.count]});
}

// Checks if a note is in the scale.
Expand Down Expand Up @@ -229,8 +233,7 @@ test "scale creation and interval retrieval" {
try testing.expectEqual(try Interval.perf(5), intervals[4]);
try testing.expectEqual(try Interval.maj(6), intervals[5]);
try testing.expectEqual(try Interval.maj(7), intervals[6]);
try testing.expectEqual(try Interval.perf(8), intervals[7]);
try testing.expectEqual(@as(usize, 8), intervals.len);
try testing.expectEqual(@as(usize, 7), intervals.len);
}

test "scale creation and note retrieval" {
Expand All @@ -244,16 +247,13 @@ test "scale creation and note retrieval" {
try testing.expectEqual(Note.g, notes[4]);
try testing.expectEqual(Note.a, notes[5]);
try testing.expectEqual(Note.b, notes[6]);
try testing.expectEqual(Note.c, notes[7]);
try testing.expectEqual(8, notes.len);
try testing.expectEqual(7, notes.len);
}

test "semitones calculation" {
var c_major = Scale.init(Note.c, .major);
const semitones = c_major.getSemitones();

log.debug("C Major scale semitones: {any}", .{semitones});

const expected = [_]i8{ 2, 2, 1, 2, 2, 2, 1 };
try std.testing.expectEqualSlices(i8, &expected, semitones);
}
Expand All @@ -265,16 +265,16 @@ test "scale degrees" {
try testing.expectEqual(4, c_major.degreeOf(Note.f));
try testing.expectEqual(null, c_major.degreeOf(Note.f.sharp()));

try testing.expectEqual(Note.c, c_major.nthDegree(1).?);
try testing.expectEqual(Note.g, c_major.nthDegree(5).?);
try testing.expectEqual(Note.c, c_major.nthDegree(8).?);
try testing.expectEqual(Note.c, c_major.nthDegree(1));
try testing.expectEqual(Note.g, c_major.nthDegree(5));
try testing.expectEqual(null, c_major.nthDegree(8));
}

test "scale spellings" {
var c_major = Scale.init(Note.c, .major);

try testing.expectEqual(Note.e, c_major.getScaleSpelling(Note.f.flat()).?);
try testing.expectEqual(Note.b, c_major.getScaleSpelling(Note.c.flat()).?);
try testing.expectEqual(Note.e, c_major.getScaleSpelling(Note.f.flat()));
try testing.expectEqual(Note.b, c_major.getScaleSpelling(Note.c.flat()));
try testing.expectEqual(null, c_major.getScaleSpelling(Note.f.sharp()));
}

Expand Down

0 comments on commit 931d37b

Please sign in to comment.