#include #include #include #include #include #include #include #include #include #include #include "midimcast.h" bool midimcast_debug; // Container of messsage to be relayed struct baton { struct baton *next; size_t midi_size; struct msg msg; }; static void env_read_client(long *const delay, const char **const soundfont) { const char *const delay_str = getenv("RELAY_DELAY"); if (delay_str) { errno = 0; *delay = strtol(delay_str, NULL, 0); if (errno) err_std_nchk(strtol); } else { *delay = 3000; } debug("Using intentional relay delay %ld ms", *delay); *soundfont = getenv("SOUNDFONT"); if (!soundfont) err("`$SOUNDFONT` unset"); debug("Using soundfont %s", *soundfont); } static int in_sock_init(struct sockaddr_in *const addr, const char *const group, const uint16_t port) { const int sock = err_std_neg(socket, AF_INET, SOCK_DGRAM, 0); // Allow immediate address reuse int reuse_addr = true; err_std(setsockopt, sock, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse_addr, sizeof(reuse_addr)); // Bind address to socket memset(addr, 0, sizeof(*addr)); addr->sin_family = AF_INET; addr->sin_addr.s_addr = htonl(INADDR_ANY); addr->sin_port = htons(port); err_std(bind, sock, (struct sockaddr *)addr, sizeof(*addr)); // Join multicast group struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(group); mreq.imr_interface.s_addr = htonl(INADDR_ANY); err_std(setsockopt, sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)); return sock; } static size_t msg_read(struct msg *const msg, const int sock, const struct sockaddr_in *const addr) { socklen_t addr_size = sizeof(*addr); // Overwritten below const ssize_t nread = err_std_neg(recvfrom, sock, msg, sizeof(*msg), 0, (struct sockaddr *)addr, &addr_size); debug("Message length %zu", nread); if (nread > (ssize_t)sizeof(*msg)) err("Message longer than %zu", sizeof(*msg)); const size_t header_size = ((void *)&msg->midi - (void *)&msg->ts); if (nread < (ssize_t)header_size) err("Message shorter than %zu", header_size); msg->ts = le64toh(msg->ts); return nread - header_size; } void baton_read(struct baton *const baton, const int in_sock, const struct sockaddr_in *const addr) { baton->midi_size = msg_read(&baton->msg, in_sock, addr); const uint64_t ts_dst = now(); int64_t latency = (int64_t)ts_dst - (int64_t)baton->msg.ts; debug("Message received: ts_src = %lu, ts_dst = %lu, latency = %ld, midi_size = %lu", baton->msg.ts, ts_dst, latency, baton->midi_size); if (latency < 0) warn("Clocks unsynchronised by over %lu ms", -latency); } void baton_put(struct baton *baton, struct baton **const first_baton) { // TODO: Simplify conditionals struct baton *prev_baton = *first_baton; struct baton *next_baton = *first_baton ? (**first_baton).next : NULL; while (next_baton && next_baton->msg.ts < baton->msg.ts) { prev_baton = next_baton; next_baton = next_baton->next; } baton->next = next_baton; if (prev_baton) prev_baton->next = baton; else *first_baton = baton; } int timeout(const struct baton *const first_baton, const long delay) { if (first_baton) { uint64_t have = now(), want = first_baton->msg.ts + delay; if (have < want) { const int timeout = want - have; debug("Relaying in %d ms", timeout); return timeout; } else { warn("Relaying %lu ms late", have - want); return 0; } } else { debug("Nothing to relay right now"); return -1; } } void baton_write(const struct baton *const baton, FILE *const out_file) { size_t write_count = fwrite(baton->msg.midi, 1, baton->midi_size, out_file); if (write_count < baton->midi_size) err_std_nchk(fwrite); err_std(fflush, out_file); } struct baton *baton_take(struct baton **const first_baton) { struct baton *const first_baton_old = *first_baton; *first_baton = (**first_baton).next; return first_baton_old; } void batons_relay(const char *const mcast_group, const uint16_t mcast_port, const long delay, const int out_fd) { struct sockaddr_in addr; const int in_sock = in_sock_init(&addr, mcast_group, mcast_port); struct pollfd in_sock_poll = {.fd = in_sock, .events = POLLIN}; struct baton *first_baton = NULL; FILE *const out_file = err_std_null(fdopen, out_fd, "w"); for (;;) { if (err_std_neg(poll, &in_sock_poll, 1, timeout(first_baton, delay))) { struct baton *baton = err_std_null(malloc, sizeof(*baton)); baton_read(baton, in_sock, &addr); baton_put(baton, &first_baton); } else { baton_write(first_baton, out_file); free(baton_take(&first_baton)); } } } void synthesise(const int fd, const char *const soundfont) { err_std(dup2, fd, STDIN_FILENO); debug("Starting synthesiser"); err_std(execlp, "fluidsynth", "fluidsynth", "--no-shell", "--server", "--gain=1", "--audio-driver=pulseaudio", "-o", "midi.driver=oss", "-o", "midi.oss.device=/dev/stdin", soundfont, NULL); } int main() { const char *mcast_group; uint16_t mcast_port; long delay; const char *soundfont; env_read_common(&mcast_group, &mcast_port); env_read_client(&delay, &soundfont); int pipes_fds[2]; err_std(pipe, pipes_fds); if (err_std_neg(fork)) batons_relay(mcast_group, mcast_port, delay, pipes_fds[1]); else synthesise(pipes_fds[0], soundfont); }