const std = @import("std"); const ioctl = std.os.linux.ioctl; // Need current local time, which Zig std doesn't support. Need libc const time_h = @cImport( @cInclude("time.h") ); const kd_h = @cImport( @cInclude("linux/kd.h") ); const KDMKTONE = kd_h.KDMKTONE; // const KIOCSOUND = kd_h.KIOCSOUND; const fcntl_h = @cImport( @cInclude("fcntl.h") ); const O_RDWR = fcntl_h.O_RDWR; const O_RDONLY = fcntl_h.O_RDONLY; // From https://github.com/johnath/beep/blob/0d790fa45777896749a885c3b93b2c1476d59f20/beep.c#L48 // Mentioned in console_ioctl(2) but under KIOCSOUND not KDMKTONE. I still don't understand the difference // I think KIOCSOUND just doesn't let you control the time, you have to use a second syscall to stop it // So we're using KDMKTONE const CLOCK_TICK_RATE = 1193180; const Note = enum { B, E, F_sharp, G_sharp, E3 }; fn note_to_freq (note: Note) usize { return switch (note) { // E key .B => 247, // B3 .E => 330, // E4 .G_sharp => 415, // G#4 .F_sharp => 370, // F#4 .E3 => 165 // E3 is used for hour bells, }; } // I spent so long trying to find a better way to do this in Zig // :15 (set 1) const sq_one = [_]Note{.G_sharp, .F_sharp, .E, .B}; // :30 (sets 2 and 3) const sq_two = [_]Note{.E, .G_sharp, .F_sharp, .B, .E, .F_sharp, .G_sharp, .E}; // :45 (sets 4, 5, 1) const sq_three = [_]Note{.G_sharp, .E, .F_sharp, .B, .B, .F_sharp, .G_sharp, .E, .G_sharp, .F_sharp, .E, .B}; // :60 (sets 2, 3, 4, 5) const sq_four = [_]Note{.E, .G_sharp, .F_sharp, .B, .E, .F_sharp, .G_sharp, .G_sharp, .E, .F_sharp, .B, .B, .F_sharp, .G_sharp, .E}; const sequences = [4][]const Note{ sq_one[0..], sq_two[0..], sq_three[0..], sq_four[0..]}; const dur = 500; fn play_sequence(sequence: []const Note) std.os.OpenError!void { for (sequence) |note, i| { try play_tone(if (i % 4 == 3) dur*2 else dur, note_to_freq(note)); if (i % 4 == 3) { // After each quartet, take a full note rest std.time.sleep(1000000 * dur); }else { // Add a quarter note rest between notes std.time.sleep(1000000 * dur / 4); } } } /// Function play_tone /// Takes a time in milliseconds, and a frequency in Hz /// Plays that tone and waits milliseconds before returning fn play_tone (ms: usize, freq: usize) std.os.OpenError!void { //TODO: make sure there's not a tone playing // Error handling, etc // TODO?: Save fd between tones? // O_RDWR is 02 // print("{}\n", .{ @typeInfo( @TypeOf (kd )) }); // Right now I'm using os.open which is open(2), the Linux syscall // Since I'm linking against glibc anyways I think I could use fopen(3) // > Linux reserves the special, nonstandard access mode 3 (binary 11) in flags to mean: check for read and write permission on the file and re- // > turn a file descriptor that can't be used for reading or writing. This nonstandard access mode is used by some Linux drivers to return a // > file descriptor that is to be used only for device-specific ioctl(2) operations. // - open(2) // The third option is for extra options and is unused (for these flags) but Zig won't let me pass only two paramaters like C will const fd: i32 = try std.os.open("/dev/tty0", 3, 0); // i32, u32, usize // fd, request, argp // Instantly returns, but the tone persists for `ms` regardless _ = ioctl(@intCast(i32, fd), KDMKTONE, (ms << 16) + CLOCK_TICK_RATE / freq); //nanoseconds to sleep for std.time.sleep(1000000 * ms); // TODO: close the fd?? } pub fn main() anyerror!void { var rawtime: time_h.time_t = undefined; _ = time_h.time(&rawtime); //localtime returns a c pointer to a tm struct var current_time: [*c]time_h.tm = time_h.localtime( &rawtime ); // 0, 1, 2, or 3 for :15, :30, :45, :00, with rounding to the closest const quarter = (@floatToInt(u8, @round(@intToFloat(f64, current_time.*.tm_min) / 60.0 * 4)) + 3) % 4; std.log.info("Quarter {}", .{ quarter }); try play_sequence(sequences[quarter]); // If it's the 4th quarter (0-index ofc), sound bell if (quarter == 3) { var hours = @mod((current_time.*.tm_hour + 11), 12) + 1; if (current_time.*.tm_min >= 53) { hours += 1; } while (hours > 0) { // Rest before playing hour bells std.time.sleep(1000000 * dur); // Play a whole note chime try play_tone(dur, note_to_freq(.E3)); // Rest 2/4 std.time.sleep(1000000 * dur / 2); hours -= 1; } } }