Skip to content

Commit

Permalink
feat: add an initial tonality selector
Browse files Browse the repository at this point in the history
This still needs a lot of tweaking and cleanup, but it works!
  • Loading branch information
elasticdog committed Sep 2, 2024
1 parent f02a04f commit f3223cc
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 73 deletions.
3 changes: 2 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");

const rl = @import("raylib");
const rg = @import("raygui");

const Application = @import("ui/application.zig").Application;
const Coord = @import("ui/coord.zig").Coord;
Expand All @@ -22,7 +23,7 @@ pub fn main() !void {
defer midi_output.deinit();

const window_width = app.piano.width() + (padding * 2);
const window_height = app.piano.height() + 140;
const window_height = app.piano.height() + 196;

rl.setConfigFlags(rl.ConfigFlags{ .window_highdpi = true });
rl.initWindow(window_width, window_height, app_name);
Expand Down
4 changes: 2 additions & 2 deletions src/ui/application.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ pub const Application = struct {
tonality_selector: TonalitySelector,

pub fn init(padding: i32) !Application {
const piano = try Piano.init(Coord{ .x = padding, .y = 100 });
const piano = try Piano.init(Coord{ .x = padding, .y = 156 });
const tonality = Tonality.init(try Note.fromString("C4"), .major);
const tonality_selector = TonalitySelector.init(Coord{ .x = padding, .y = 0 }, tonality);
const tonality_selector = TonalitySelector.init(Coord{ .x = padding, .y = 16 }, tonality);

return .{
.piano = piano,
Expand Down
174 changes: 104 additions & 70 deletions src/ui/tonality_selector.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const log = std.log.scoped(.tonality_selector);
const rl = @import("raylib");
const rg = @import("raygui");

Expand All @@ -11,39 +12,58 @@ pub const TonalitySelector = struct {
pos: Coord,
selected_tonality: Tonality,

const button_width = 40;
const button_height = 30;
const button_spacing = 5;
const note_button_width = 40;
const note_button_height = 40;
const mode_button_width = 60;
const mode_button_height = 40;
const button_spacing = 2;
const font_size = 20;

const NoteButton = struct {
label: [*:0]const u8,
note: Note,
x: i32,
y: i32,
};

const natural_notes = [_]NoteButton{
.{ .label = "C", .note = .{ .midi = 60, .name = .{ .ltr = .c, .acc = .natural } } },
.{ .label = "D", .note = .{ .midi = 62, .name = .{ .ltr = .d, .acc = .natural } } },
.{ .label = "E", .note = .{ .midi = 64, .name = .{ .ltr = .e, .acc = .natural } } },
.{ .label = "F", .note = .{ .midi = 65, .name = .{ .ltr = .f, .acc = .natural } } },
.{ .label = "G", .note = .{ .midi = 67, .name = .{ .ltr = .g, .acc = .natural } } },
.{ .label = "A", .note = .{ .midi = 69, .name = .{ .ltr = .a, .acc = .natural } } },
.{ .label = "B", .note = .{ .midi = 71, .name = .{ .ltr = .b, .acc = .natural } } },
const ModeButton = struct {
label: [*:0]const u8,
mode: Tonality.Mode,
x: i32,
y: i32,
};

const sharp_notes = [_]NoteButton{
.{ .label = "C#", .note = .{ .midi = 61, .name = .{ .ltr = .c, .acc = .sharp } } },
.{ .label = "D#", .note = .{ .midi = 63, .name = .{ .ltr = .d, .acc = .sharp } } },
.{ .label = "F#", .note = .{ .midi = 66, .name = .{ .ltr = .f, .acc = .sharp } } },
.{ .label = "G#", .note = .{ .midi = 68, .name = .{ .ltr = .g, .acc = .sharp } } },
.{ .label = "A#", .note = .{ .midi = 70, .name = .{ .ltr = .a, .acc = .sharp } } },
const buttons = blk: {
@setEvalBranchQuota(3000);
const sharps = [_]NoteButton{
.{ .label = "C#", .note = .{ .midi = 61, .name = .{ .ltr = .c, .acc = .sharp } }, .x = 0, .y = 0 },
.{ .label = "D#", .note = .{ .midi = 63, .name = .{ .ltr = .d, .acc = .sharp } }, .x = 1, .y = 0 },
.{ .label = "F#", .note = .{ .midi = 66, .name = .{ .ltr = .f, .acc = .sharp } }, .x = 3, .y = 0 },
.{ .label = "G#", .note = .{ .midi = 68, .name = .{ .ltr = .g, .acc = .sharp } }, .x = 4, .y = 0 },
.{ .label = "A#", .note = .{ .midi = 70, .name = .{ .ltr = .a, .acc = .sharp } }, .x = 5, .y = 0 },
};
const naturals = [_]NoteButton{
.{ .label = "C", .note = .{ .midi = 60, .name = .{ .ltr = .c, .acc = .natural } }, .x = 0, .y = 1 },
.{ .label = "D", .note = .{ .midi = 62, .name = .{ .ltr = .d, .acc = .natural } }, .x = 1, .y = 1 },
.{ .label = "E", .note = .{ .midi = 64, .name = .{ .ltr = .e, .acc = .natural } }, .x = 2, .y = 1 },
.{ .label = "F", .note = .{ .midi = 65, .name = .{ .ltr = .f, .acc = .natural } }, .x = 3, .y = 1 },
.{ .label = "G", .note = .{ .midi = 67, .name = .{ .ltr = .g, .acc = .natural } }, .x = 4, .y = 1 },
.{ .label = "A", .note = .{ .midi = 69, .name = .{ .ltr = .a, .acc = .natural } }, .x = 5, .y = 1 },
.{ .label = "B", .note = .{ .midi = 71, .name = .{ .ltr = .b, .acc = .natural } }, .x = 6, .y = 1 },
};
const flats = [_]NoteButton{
.{ .label = "Db", .note = .{ .midi = 61, .name = .{ .ltr = .d, .acc = .flat } }, .x = 0, .y = 2 },
.{ .label = "Eb", .note = .{ .midi = 63, .name = .{ .ltr = .e, .acc = .flat } }, .x = 1, .y = 2 },
.{ .label = "Gb", .note = .{ .midi = 66, .name = .{ .ltr = .g, .acc = .flat } }, .x = 3, .y = 2 },
.{ .label = "Ab", .note = .{ .midi = 68, .name = .{ .ltr = .a, .acc = .flat } }, .x = 4, .y = 2 },
.{ .label = "Bb", .note = .{ .midi = 70, .name = .{ .ltr = .b, .acc = .flat } }, .x = 5, .y = 2 },
};
break :blk sharps ++ naturals ++ flats;
};

const flat_notes = [_]NoteButton{
.{ .label = "Db", .note = .{ .midi = 61, .name = .{ .ltr = .d, .acc = .flat } } },
.{ .label = "Eb", .note = .{ .midi = 63, .name = .{ .ltr = .e, .acc = .flat } } },
.{ .label = "Gb", .note = .{ .midi = 66, .name = .{ .ltr = .g, .acc = .flat } } },
.{ .label = "Ab", .note = .{ .midi = 68, .name = .{ .ltr = .a, .acc = .flat } } },
.{ .label = "Bb", .note = .{ .midi = 70, .name = .{ .ltr = .b, .acc = .flat } } },
const mode_buttons = [_]ModeButton{
.{ .label = "Major", .mode = .major, .x = 7, .y = 0 },
.{ .label = "Minor", .mode = .minor, .x = 7, .y = 1 },
};

pub fn init(pos: Coord, initial_tonality: Tonality) TonalitySelector {
Expand All @@ -54,70 +74,69 @@ pub const TonalitySelector = struct {
}

pub fn update(self: *TonalitySelector, mouse: Mouse) void {
// Update natural note buttons
for (natural_notes, 0..) |note_button, i| {
if (self.isButtonClicked(@as(i32, @intCast(i)), 1, mouse)) {
self.selected_tonality = Tonality.init(note_button.note, self.selected_tonality.mode);
for (buttons) |button| {
if (self.isButtonClicked(button.x, button.y, mouse)) {
self.selected_tonality.tonic = button.note;
log.debug("tonality set to note: {}", .{button.note.fmtPitchClass()});
}
}

// Update sharp note buttons
for (sharp_notes, 0..) |note_button, i| {
if (self.isButtonClicked(@as(i32, @intCast(i)) * 2 + 1, 0, mouse)) {
self.selected_tonality = Tonality.init(note_button.note, self.selected_tonality.mode);
for (mode_buttons) |button| {
if (self.isModeButtonClicked(button.x, button.y, mouse)) {
self.selected_tonality.mode = button.mode;
log.debug("tonality set to mode: {s}", .{@tagName(button.mode)});
}
}
}

// Update flat note buttons
for (flat_notes, 0..) |note_button, i| {
if (self.isButtonClicked(@as(i32, @intCast(i)) * 2 + 1, 2, mouse)) {
self.selected_tonality = Tonality.init(note_button.note, self.selected_tonality.mode);
}
pub fn draw(self: TonalitySelector) void {
for (buttons) |button| {
_ = self.drawNoteButton(button);
}

// Update major/minor toggle
if (self.isButtonClicked(7, 1, mouse)) {
self.selected_tonality = Tonality.init(self.selected_tonality.tonic, switch (self.selected_tonality.mode) {
.major => .minor,
.minor => .major,
});
for (mode_buttons) |button| {
_ = self.drawModeButton(button);
}
}

pub fn draw(self: TonalitySelector) void {
// Draw natural note buttons
for (natural_notes, 0..) |note_button, i| {
_ = self.drawButton(note_button, @as(i32, @intCast(i)), 1);
}
fn drawNoteButton(self: TonalitySelector, button: NoteButton) bool {
const x_offset: i32 = if (button.note.name.acc != .natural) note_button_width / 2 else 0;
const x = self.pos.x + x_offset + @as(i32, @intCast(button.x)) * (note_button_width + button_spacing);
const y = self.pos.y + @as(i32, @intCast(button.y)) * (note_button_height + button_spacing);

// Draw sharp note buttons
for (sharp_notes, 0..) |note_button, i| {
_ = self.drawButton(note_button, @as(i32, @intCast(i)) * 2 + 1, 0);
}
const is_selected = self.selected_tonality.tonic.isEnharmonic(button.note) and
(button.note.name.acc == self.selected_tonality.tonic.name.acc);

// Draw flat note buttons
for (flat_notes, 0..) |note_button, i| {
_ = self.drawButton(note_button, @as(i32, @intCast(i)) * 2 + 1, 2);
const rect = rl.Rectangle{
.x = @floatFromInt(x),
.y = @floatFromInt(y),
.width = note_button_width,
.height = note_button_height,
};

if (is_selected) {
rg.guiSetState(@intFromEnum(rg.GuiState.state_focused));
} else {
rg.guiSetState(@intFromEnum(rg.GuiState.state_normal));
}

// Draw major/minor toggle
_ = self.drawButton(.{
.label = if (self.selected_tonality.mode == .major) "Major" else "Minor",
.note = self.selected_tonality.tonic,
}, 7, 1);
// rg.guiSetStyle(rg.GuiControl.button, rg.GuiProperty.text_size, font_size);
return rg.guiButton(rect, button.label) == 1;
}

fn drawButton(self: TonalitySelector, note_button: NoteButton, x_index: i32, y_index: i32) bool {
const x = self.pos.x + x_index * (button_width + button_spacing);
const y = self.pos.y + y_index * (button_height + button_spacing);
fn drawModeButton(self: TonalitySelector, button: ModeButton) bool {
const x_offset: i32 = note_button_width / 2;
const x = self.pos.x + x_offset + @as(i32, @intCast(button.x)) * (note_button_width + button_spacing);
const y_offset: i32 = mode_button_height / 2;
const y = self.pos.y + y_offset + @as(i32, @intCast(button.y)) * (note_button_height + button_spacing);

const is_selected = self.selected_tonality.tonic.isEnharmonic(note_button.note);
const is_selected = self.selected_tonality.mode == button.mode;

const rect = rl.Rectangle{
.x = @floatFromInt(x),
.y = @floatFromInt(y),
.width = button_width,
.height = button_height,
.width = mode_button_width,
.height = mode_button_height,
};

if (is_selected) {
Expand All @@ -126,17 +145,32 @@ pub const TonalitySelector = struct {
rg.guiSetState(@intFromEnum(rg.GuiState.state_normal));
}

return rg.guiButton(rect, note_button.label) == 1;
// rg.guiSetStyle(@intFromEnum(rg.GuiControl.button), rg.GuiProperty.text_size, font_size);
return rg.guiButton(rect, button.label) == 1;
}

fn isButtonClicked(self: TonalitySelector, x_index: i32, y_index: i32, mouse: Mouse) bool {
const x = self.pos.x + x_index * (button_width + button_spacing);
const y = self.pos.y + y_index * (button_height + button_spacing);
const x_offset: i32 = if (y_index != 1) note_button_width / 2 else 0;
const x = self.pos.x + x_offset + x_index * (note_button_width + button_spacing);
const y = self.pos.y + y_index * (note_button_height + button_spacing);

return mouse.is_pressed_left and
mouse.pos.x >= x and
mouse.pos.x <= x + note_button_width and
mouse.pos.y >= y and
mouse.pos.y <= y + note_button_height;
}

fn isModeButtonClicked(self: TonalitySelector, x_index: i32, y_index: i32, mouse: Mouse) bool {
const x_offset: i32 = note_button_width / 2;
const x = self.pos.x + x_offset + x_index * (note_button_width + button_spacing);
const y_offset: i32 = mode_button_height / 2;
const y = self.pos.y + y_offset + y_index * (note_button_height + button_spacing);

return mouse.is_pressed_left and
mouse.pos.x >= x and
mouse.pos.x <= x + button_width and
mouse.pos.x <= x + mode_button_width and
mouse.pos.y >= y and
mouse.pos.y <= y + button_height;
mouse.pos.y <= y + mode_button_height;
}
};

0 comments on commit f3223cc

Please sign in to comment.