From 75c62e88fb1601e2d11f8949b2d6a815401d174e Mon Sep 17 00:00:00 2001 From: d_m Date: Sun, 25 Aug 2024 16:42:21 -0400 Subject: [PATCH] Initial working version. Currently, mp3.rom will start playing a hardcoded file. On Linux, when the uxn11 process ends the mpg123 process will also be killed. The code to do this isn't portable, so on BSD/etc. there will be an orphan process left around if uxn11 is killed. (In both cases an emulator-driven exit will end the subprocess.) The mp3 ROM will currently interpret each line of `mpg123 -R` output, and dispatch to the appropriate subroutine. None of these do anything yet. --- mp3.tal | 131 +++++++++++++++++++++++++++++ src/devices/console.c | 186 ++++++++++++++++++++++++++++++++++++++++++ src/devices/console.h | 1 + src/uxn11.c | 3 + src/uxncli.c | 1 + 5 files changed, 322 insertions(+) create mode 100644 mp3.tal diff --git a/mp3.tal b/mp3.tal new file mode 100644 index 0000000..a2eafce --- /dev/null +++ b/mp3.tal @@ -0,0 +1,131 @@ +( mp3.tal ) + +( commands are ended by newlines: ) +( ) +( help show all these commands ) +( load FILE load FILE and start playing ) +( loadpaused FILE load FILE without playing ) +( pause pause, or unpause, playback ) +( stop stops playback, unloads file ) +( seek # seeks to sample # ) +( seek +N | seek -N seeks forward, or back, N samples ) +( seek +Ns | seek -Ns seeks forward, or back, N seconds ) +( jump # jumps to MPEG frame # ) +( jump +N | jump -N jumps forward, or back, N frames ) +( jump +Ns | jump -Ns jumps forward, or back, N seconds ) +( volume P set volume to P percent, 0-100 ) +( mute mute playback ) +( unmute unmute playback ) +( tag print all ID3 data; seeking back may remove tag data ) + +@Console [ + |10 &vector $2 + |12 &read $1 + ( 13 - 14 padding ) + |15 &live $1 + |15 &exit $1 + |17 &type $1 + |18 &write $1 + |19 &error $1 + ( 1a - 1b padding ) + |1c &addr $2 + |1e &mode $1 + |1f &exec $1 +] + +|0100 + ( initialize buffer ) + ;buffer ;buffer/pos STA2 + + ( run mpg123 ) + ;on-console .Console/vector DEO2 + ;program .Console/addr DEO2 ( cmd addr ) + #03 .Console/mode DEO ( cmd mode ) + #01 .Console/exec DEO ( exec ) + + ;cmd1 print + ;cmd2 print + + BRK + +@printerr ( s* -> ) + LDAk ?{ #0a .Console/error DEO POP2 JMP2r } + LDAk .Console/error DEO INC2 !printerr + +@print ( s* -> ) + LDAk ?{ POP2 JMP2r } + LDAk .Console/write DEO INC2 !print + +@on-console ( -> brk ) + .Console/type DEI #01 EQU ?{ BRK } ( ) + .Console/read DEI #0a EQU ?&newline ( ) + ;buffer/pos LDA2k STH2k ( pos* buf* [buf*] ) + .Console/read DEI STH2r STA ( pos* buf ; buf<-c ) + INC2 SWP2 STA2 BRK ( ; pos<-buf+1 ) + &newline ( ) + #00 ;buffer/pos LDA2 STA on-line ( ; buf<-0, run on-line ) + ;buffer ;buffer/pos STA2 BRK ( ) + + +( called when a newline is reached ) +( buffer is guaranteed to be null-terminated ) +( newline is implied but not included ) +@on-line ( -> ) + ;buffer LDAk LIT "@ EQU ?{ POP2 JMP2r } + INC2k LDA LIT "F EQU ?on-frame + ( INC2k LDA LIT "H EQU ?on-help ) + ( INC2k LDA LIT "I EQU ?on-id3 ) + ( INC2k LDA LIT "P EQU ?on-paused ) + ( INC2k LDA LIT "R EQU ?on-revision ) + ( INC2k LDA LIT "S EQU ?on-status ) + ( INC2k LDA LIT "T EQU ?on-tag ) + !printerr + +( e.g. "@F 184 8713 4.81 227.60" ) +( seen when playing ) +( the fields here are: ) +( @F ) +@on-frame ( buf* -> ) + POP2 JMP2r + +( e.g. "@H HELP/H: command listing (LONG/SHORT forms), command case insensitve" ) +( seen in response to the "help" command ) +@on-help ( buf* -> ) + POP2r JMP2r + +( e.g. "@I ID3v2.artist:Chipzel" ) +( seen when loading a track ) +@on-id3 ( buf* -> ) + POP2 JMP2r + +( e.g. "@P 0" or "@P 1" ) +( seen when pausing or unpausing ) +@on-paused ( buf* -> ) + POP2 JMP2r + +( e.g. "@R MPG123 (ThOr) v10" ) +( seen on start up ) +@on-revision ( buf* -> ) + POP2 JMP2r + +( e.g. "@S 1.0 3 44100 Joint-Stereo 0 1044 2 0 0 0 320 0 1" ) +( seen when playback starts ) +@on-status ( buf* -> ) + POP2 JMP2r + +( e.g. "@T ID3v2.TPE1:" ) +( seen in response to the "tag" command for extended tag info ) +@on-tag ( buf* -> ) + POP2 JMP2r + +@program + "mpg123 20 "-R 00 + +@cmd1 + "loadpaused 20 "tokyo-skies.mp3 0a 00 + +@cmd2 + "pause 0a 00 + + +@buffer $200 &pos $2 ( input buffer ) diff --git a/src/devices/console.c b/src/devices/console.c index 000f23b..fda5443 100644 --- a/src/devices/console.c +++ b/src/devices/console.c @@ -1,5 +1,22 @@ +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L + +#include #include #include +#include +#include +#include + +#ifdef __linux +#include +#include +#endif + +#ifdef __NetBSD__ +#include +#include +#endif #include "../uxn.h" #include "console.h" @@ -15,6 +32,164 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ +/* subprocess support */ +static char *fork_args[4] = {"/bin/sh", "-c", "", NULL}; +static int child_mode; +static int to_child_fd[2]; +static int from_child_fd[2]; +static int saved_in; +static int saved_out; +static pid_t child_pid; + +/* child_mode: + * 0x01: writes to child's stdin + * 0x02: reads from child's stdout + * 0x04: reads from child's stderr + * 0x08: kill previous process (if any) but do not start + * (other bits ignored for now ) + */ + +#define CMD_LIVE 0x15 // 0x00 not started, 0x01 running, 0xff dead +#define CMD_EXIT 0x16 // if dead, exit code of process +#define CMD_ADDR 0x1c // address to read command args from +#define CMD_MODE 0x1e // mode to execute, 0x00 to 0x07 +#define CMD_EXEC 0x1f // write to execute programs, etc + +/* call after we're sure the process has exited */ +static void +clean_after_child(void) +{ + child_pid = 0; + if(child_mode & 0x01) { + close(to_child_fd[1]); + dup2(saved_out, 1); + } + if(child_mode & (0x04 | 0x02)) { + close(from_child_fd[0]); + dup2(saved_in, 0); + } + child_mode = 0; + saved_in = -1; + saved_out = -1; +} + +static void +start_fork_pipe(void) +{ + fflush(stdout); + pid_t pid; + pid_t parent_pid = getpid(); + int addr = PEEK2(&uxn.dev[CMD_ADDR]); + if(child_mode & 0x08) { + uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0x00; + return; + } + if(child_mode & 0x01) { + /* parent writes to child's stdin */ + if(pipe(to_child_fd) == -1) { + uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff; + fprintf(stderr, "pipe error: to child\n"); + return; + } + } + if(child_mode & (0x04 | 0x02)) { + /* parent reads from child's stdout and/or stderr */ + if(pipe(from_child_fd) == -1) { + uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff; + fprintf(stderr, "pipe error: from child\n"); + return; + } + } + + fork_args[2] = (char *)&uxn.ram[addr]; + pid = fork(); + if(pid < 0) { /* failure */ + uxn.dev[CMD_EXIT] = uxn.dev[CMD_LIVE] = 0xff; + fprintf(stderr, "fork failure\n"); + } else if(pid == 0) { /* child */ + +#ifdef __linux__ + int r = prctl(PR_SET_PDEATHSIG, SIGTERM); + if (r == -1) { perror(0); exit(6); } + // test in case the original parent exited just + // before the prctl() call + if (getppid() != parent_pid) exit(13); +#endif + + if(child_mode & 0x01) { + dup2(to_child_fd[0], 0); + close(to_child_fd[1]); + } + if(child_mode & (0x04 | 0x02)) { + if(child_mode & 0x02) dup2(from_child_fd[1], 1); + if(child_mode & 0x04) dup2(from_child_fd[1], 2); + close(from_child_fd[0]); + } + fflush(stdout); + execvp(fork_args[0], fork_args); + exit(1); + } else { /*parent*/ + child_pid = pid; + uxn.dev[CMD_LIVE] = 0x01; + uxn.dev[CMD_EXIT] = 0x00; + if(child_mode & 0x01) { + saved_out = dup(1); + dup2(to_child_fd[1], 1); + close(to_child_fd[0]); + } + if(child_mode & (0x04 | 0x02)) { + saved_in = dup(0); + dup2(from_child_fd[0], 0); + close(from_child_fd[1]); + } + } +} + +static void +check_child(void) +{ + int wstatus; + if(child_pid) { + if(waitpid(child_pid, &wstatus, WNOHANG)) { + uxn.dev[CMD_LIVE] = 0xff; + uxn.dev[CMD_EXIT] = WEXITSTATUS(wstatus); + clean_after_child(); + } else { + uxn.dev[CMD_LIVE] = 0x01; + uxn.dev[CMD_EXIT] = 0x00; + } + } +} + +static void +kill_child(void) +{ + int wstatus; + if(child_pid) { + kill(child_pid, 9); + if(waitpid(child_pid, &wstatus, WNOHANG)) { + uxn.dev[CMD_LIVE] = 0xff; + uxn.dev[CMD_EXIT] = WEXITSTATUS(wstatus); + clean_after_child(); + } + } +} + +static void +start_fork(void) +{ + fflush(stderr); + kill_child(); + child_mode = uxn.dev[CMD_MODE]; + start_fork_pipe(); +} + +void +close_console(void) +{ + kill_child(); +} + int console_input(Uint8 c, int type) { @@ -33,6 +208,16 @@ console_listen(int i, int argc, char **argv) } } +Uint8 +console_dei(Uint8 addr) +{ + switch(addr) { + case CMD_LIVE: + case CMD_EXIT: check_child(); break; + } + return uxn.dev[addr]; +} + void console_deo(Uint8 addr) { @@ -40,5 +225,6 @@ console_deo(Uint8 addr) switch(addr) { case 0x18: fd = stdout, fputc(uxn.dev[0x18], fd), fflush(fd); break; case 0x19: fd = stderr, fputc(uxn.dev[0x19], fd), fflush(fd); break; + case CMD_EXEC: start_fork(); break; } } diff --git a/src/devices/console.h b/src/devices/console.h index be8abc2..53edfd2 100644 --- a/src/devices/console.h +++ b/src/devices/console.h @@ -18,3 +18,4 @@ int console_input(Uint8 c, int type); void console_listen(int i, int argc, char **argv); Uint8 console_dei(Uint8 addr); void console_deo(Uint8 addr); +void close_console(void); diff --git a/src/uxn11.c b/src/uxn11.c index de71966..dbb7643 100644 --- a/src/uxn11.c +++ b/src/uxn11.c @@ -49,6 +49,7 @@ emu_dei(Uint8 addr) { switch(addr & 0xf0) { case 0x00: return system_dei(addr); + case 0x10: return console_dei(addr); case 0x20: return screen_dei(addr); case 0xc0: return datetime_dei(addr); } @@ -89,6 +90,7 @@ emu_resize(int w, int h) static void emu_restart(char *rom, int soft) { + close_console(); screen_resize(WIDTH, HEIGHT, uxn_screen.scale); screen_rect(uxn_screen.bg, 0, 0, uxn_screen.width, uxn_screen.height, 0); screen_rect(uxn_screen.fg, 0, 0, uxn_screen.width, uxn_screen.height, 0); @@ -98,6 +100,7 @@ emu_restart(char *rom, int soft) static int emu_end(void) { + close_console(); free(uxn.ram); XDestroyImage(ximage); XDestroyWindow(display, window); diff --git a/src/uxncli.c b/src/uxncli.c index b6c1e39..6d72c80 100644 --- a/src/uxncli.c +++ b/src/uxncli.c @@ -25,6 +25,7 @@ emu_dei(Uint8 addr) { switch(addr & 0xf0) { case 0x00: return system_dei(addr); + case 0x10: return console_dei(addr); case 0xc0: return datetime_dei(addr); } return uxn.dev[addr];