Skip to content

Commit

Permalink
refactor!: rename key to tonality and remove both v1 and v2 theory code
Browse files Browse the repository at this point in the history
Changing the hard-coded tonality in the ui/application.zig file does
adjust the spelling as desired. Now we need a GUI to set it.
  • Loading branch information
elasticdog committed Sep 2, 2024
1 parent 273c4e0 commit d160dd9
Show file tree
Hide file tree
Showing 19 changed files with 96 additions and 3,325 deletions.
7 changes: 4 additions & 3 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ const Mouse = @import("ui/mouse.zig").Mouse;
const padding = 16; // pixels

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// var gpa = std.heap.GeneralPurposeAllocator(.{}){};
// const allocator = gpa.allocator();

var mouse: Mouse = .{};
var app = try Application.init(allocator, padding);
// var app = try Application.init(allocator, padding);
var app = try Application.init(padding);
const app_name = "ClefCraft";

var midi_output = try MidiOutput.init(app_name);
Expand Down
17 changes: 6 additions & 11 deletions src/root.zig
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
const std = @import("std");
const testing = std.testing;

pub const Interval = @import("theory_v1/interval.zig").Interval;
pub const Note = @import("theory_v1/note.zig").Note;
pub const Pitch = @import("theory_v1/pitch.zig").Pitch;
pub const Scale = @import("theory_v1/scale.zig").Scale;
pub const Interval = @import("theory/interval.zig").Interval;
pub const Note = @import("theory/note.zig").Note;
pub const Scale = @import("theory/scale.zig").Scale;
pub const Tonality = @import("theory/tonality.zig").Tonality;

test {
// Run all unit tests.
// _ = @import("theory_v1/interval.zig");
// _ = @import("theory_v1/key_signature.zig");
// _ = @import("theory_v1/note.zig");
// _ = @import("theory_v1/pitch.zig");
// _ = @import("theory_v1/scale.zig");
_ = @import("theory/note.zig");
_ = @import("theory/interval.zig");
_ = @import("theory/key.zig");
_ = @import("theory/note.zig");
_ = @import("theory/scale.zig");
_ = @import("theory/tonality.zig");
}
57 changes: 42 additions & 15 deletions src/theory/note.zig
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,47 @@ pub const Note = struct {
// /// Respells a note according to the given musical context.
// pub fn respell(self: Note, context: ???) Note { }

fn formatAccidental(acc: Accidental, use_ascii: bool) []const u8 {
return if (use_ascii)
switch (acc) {
.double_flat => "bb",
.flat => "b",
.natural => "",
.sharp => "#",
.double_sharp => "x",
}
else switch (acc) {
.double_flat => "𝄫",
.flat => "♭",
.natural => "",
.sharp => "♯",
.double_sharp => "𝄪",
};
}

/// Returns a formatter for the note's pitch class representation.
pub fn fmtPitchClass(self: Note) std.fmt.Formatter(formatPitchClass) {
return .{ .data = self };
}

/// Formats the note for output as a pitch class without an octave.
/// Uses Unicode symbols for accidentals by default.
/// If the format specifier 'c' is used, it outputs ASCII symbols instead.
fn formatPitchClass(
self: Note,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
const use_ascii = std.mem.indexOfScalar(u8, fmt, 'c') != null;

try writer.print("{c}{s}", .{
std.ascii.toUpper(@tagName(self.name.ltr)[0]),
formatAccidental(self.name.acc, use_ascii),
});
}

/// Formats the note for output in Scientific Pitch Notation.
/// Uses Unicode symbols for accidentals by default.
/// If the format specifier 'c' is used, it outputs ASCII symbols instead.
Expand All @@ -256,21 +297,7 @@ pub const Note = struct {

try writer.print("{c}{s}{d}", .{
std.ascii.toUpper(@tagName(self.name.ltr)[0]),
if (use_ascii)
switch (self.name.acc) {
.double_flat => "bb",
.flat => "b",
.natural => "",
.sharp => "#",
.double_sharp => "x",
}
else switch (self.name.acc) {
.double_flat => "𝄫",
.flat => "♭",
.natural => "",
.sharp => "♯",
.double_sharp => "𝄪",
},
formatAccidental(self.name.acc, use_ascii),
self.octave(),
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/theory/scale.zig
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ test "creation and usage" {

// // Create a C major scale with key-based spelling
// const c_major_key = Scale.init(c4, ScaleType.major, .{
// .spelling = Spelling.Context.init(.key_based).withKey(Key.C),
// .spelling = Spelling.Context.init(.key_based).withTonality(Tonality.C),
// });

// // Create a scale with custom spelling function
Expand Down
36 changes: 18 additions & 18 deletions src/theory/key.zig → src/theory/tonality.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ const testing = std.testing;
const c = @import("constants.zig");
const Note = @import("note.zig").Note;

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

pub const Mode = enum { major, minor };

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

pub fn sharpsOrFlats(self: Key) i8 {
pub fn sharpsOrFlats(self: Tonality) 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(),
Expand All @@ -25,52 +25,52 @@ pub const Key = struct {
return circle_of_fifths[relative_major_tonic];
}

pub fn accidentals(self: Key) struct { sharps: u3, flats: u3 } {
pub fn accidentals(self: Tonality) 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 {
pub fn spell(self: Tonality, midi: u7) Note {
const sf = self.sharpsOrFlats();
return if (sf >= 0)
Note.spellWithSharps(note.midi)
.{ .midi = midi, .name = Note.spellWithSharps(midi) }
else
Note.spellWithFlats(note.midi);
.{ .midi = midi, .name = Note.spellWithFlats(midi) };
}

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

pub fn relativeMinor(self: Key) Key {
pub fn relativeMinor(self: Tonality) Tonality {
return switch (self.mode) {
.major => Key.init(Note.fromMidi(@intCast(@mod(self.tonic.midi + 9, c.sem_per_oct))), .minor),
.major => Tonality.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);
test "behavior" {
const c_major = Tonality.init(try Note.fromString("C4"), .major);
const a_minor = Tonality.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);
const g_major = Tonality.init(try Note.fromString("G4"), .major);
const e_minor = Tonality.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);
const f_major = Tonality.init(try Note.fromString("F4"), .major);
const d_minor = Tonality.init(try Note.fromString("D4"), .minor);

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

0 comments on commit d160dd9

Please sign in to comment.