130 lines
4.5 KiB
Zig
130 lines
4.5 KiB
Zig
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;
|
|
}
|
|
}
|
|
}
|