chime/src/main.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;
}
}
}