Skip to content

Commit

Permalink
feat: add basic implementation of a key struct
Browse files Browse the repository at this point in the history
I want to try and see if I can get the MIDI input to reflect a set key
signature. This is a simple first step without needing the have scales
and chords fully designed and implemented.
  • Loading branch information
elasticdog committed Sep 1, 2024
1 parent 294be14 commit dde9162
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ test {
// _ = @import("theory_v1/scale.zig");
_ = @import("theory/note.zig");
_ = @import("theory/interval.zig");
_ = @import("theory/key.zig");
_ = @import("theory/scale.zig");
}
6 changes: 5 additions & 1 deletion src/theory/chord.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ const assert = std.debug.assert;
const log = std.log.scoped(.chord);
const testing = std.testing;

pub const Chord = struct {};
const Note = @import("note.zig").Note;

pub const Chord = struct {
root: Note,
};
77 changes: 76 additions & 1 deletion src/theory/key.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,79 @@ const assert = std.debug.assert;
const log = std.log.scoped(.key);
const testing = std.testing;

pub const Key = struct {};
const c = @import("constants.zig");
const Note = @import("note.zig").Note;

pub const Key = struct {
tonic: Note,
mode: Mode,

pub const Mode = enum { major, minor };

pub fn init(tonic: Note, mode: Mode) Key {
return .{ .tonic = tonic, .mode = mode };
}

pub fn sharpsOrFlats(self: Key) i8 {
const circle_of_fifths = [_]i8{ 0, -5, 2, -3, 4, -1, 6, 1, -4, 3, -2, 5 };
const relative_major_tonic = switch (self.mode) {
.major => self.tonic.pitchClass(),
.minor => @mod(self.tonic.pitchClass() + 3, c.sem_per_oct),
};
return circle_of_fifths[relative_major_tonic];
}

pub fn accidentals(self: Key) struct { sharps: u3, flats: u3 } {
const sf = self.sharpsOrFlats();
return if (sf >= 0)
.{ .sharps = @intCast(sf), .flats = 0 }
else
.{ .sharps = 0, .flats = @intCast(-sf) };
}

pub fn spell(self: Key, note: Note) Note {
const sf = self.sharpsOrFlats();
return if (sf >= 0)
Note.spellWithSharps(note.midi)
else
Note.spellWithFlats(note.midi);
}

pub fn relativeMajor(self: Key) Key {
return switch (self.mode) {
.major => self,
.minor => Key.init(Note.fromMidi(@intCast(@mod(self.tonic.midi + 3, c.sem_per_oct))), .major),
};
}

pub fn relativeMinor(self: Key) Key {
return switch (self.mode) {
.major => Key.init(Note.fromMidi(@intCast(@mod(self.tonic.midi + 9, c.sem_per_oct))), .minor),
.minor => self,
};
}
};

test "Key behavior" {
const c_major = Key.init(try Note.fromString("C4"), .major);
const a_minor = Key.init(try Note.fromString("A3"), .minor);

try testing.expectEqual(0, c_major.sharpsOrFlats());
try testing.expectEqual(0, a_minor.sharpsOrFlats());

const g_major = Key.init(try Note.fromString("G4"), .major);
const e_minor = Key.init(try Note.fromString("E4"), .minor);

try testing.expectEqual(1, g_major.sharpsOrFlats());
try testing.expectEqual(1, e_minor.sharpsOrFlats());

const f_major = Key.init(try Note.fromString("F4"), .major);
const d_minor = Key.init(try Note.fromString("D4"), .minor);

try testing.expectEqual(-1, f_major.sharpsOrFlats());
try testing.expectEqual(-1, d_minor.sharpsOrFlats());

// Test relative key relationships
try testing.expectEqual(c_major.tonic.pitchClass(), a_minor.relativeMajor().tonic.pitchClass());
try testing.expectEqual(a_minor.tonic.pitchClass(), c_major.relativeMinor().tonic.pitchClass());
}
8 changes: 4 additions & 4 deletions src/theory/scale.zig
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ pub const Scale = struct {
};

test "creation and usage" {
const c4 = try Note.fromString("C4");
const db4 = try Note.fromString("Db4");
// const c4 = try Note.fromString("C4");
// const db4 = try Note.fromString("Db4");

try Scale.major(c4).debugPrint(std.testing.allocator);
try Scale.major(db4).debugPrint(std.testing.allocator);
// try Scale.major(c4).debugPrint(std.testing.allocator);
// try Scale.major(db4).debugPrint(std.testing.allocator);
// // Using direct initialization
// const major_scale_db = Scale.init(db4, ScaleTypes.major);
// try major_scale_db.debugPrint(std.testing.allocator);
Expand Down

0 comments on commit dde9162

Please sign in to comment.