diff --git a/Makefile b/Makefile index 52a24b9f..e6ed975f 100644 --- a/Makefile +++ b/Makefile @@ -53,12 +53,17 @@ ifeq (, $(shell which sdl2-config)) $(warning No sdl2-config in $$PATH. Check SDL2 installation in advance) override ENABLE_SDL := 0 endif +ifeq (1, $(shell pkg-config --exists SDL2_mixer; echo $$?)) +$(warning No SDL2_mixer lib installed. Check SDL2_mixer installation in advance) +override ENABLE_SDL := 0 +endif endif $(call set-feature, SDL) ifeq ($(call has, SDL), 1) OBJS_EXT += syscall_sdl.o $(OUT)/syscall_sdl.o: CFLAGS += $(shell sdl2-config --cflags) LDFLAGS += $(shell sdl2-config --libs) +LDFLAGS += $(shell pkg-config --libs SDL2_mixer) endif ENABLE_GDBSTUB ?= 1 diff --git a/README.md b/README.md index f522d761..d79bed18 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,10 @@ Features: ## Build and Verify `rv32emu` relies on certain third-party packages for full functionality and access to all its features. -To ensure proper operation, the target system should have the [SDL2 library](https://www.libsdl.org/) installed. -* macOS: `brew install sdl2` -* Ubuntu Linux / Debian: `sudo apt install libsdl2-dev` +To ensure proper operation, the target system should have the [SDL2 library](https://www.libsdl.org/) +and [SDL2_Mixer library](https://wiki.libsdl.org/SDL2_mixer) installed. +* macOS: `brew install sdl2 sdl2_mixer` +* Ubuntu Linux / Debian: `sudo apt install libsdl2-dev libsdl2-mixer-dev` Build the emulator. ```shell diff --git a/docs/syscall.md b/docs/syscall.md index af74a0a6..ef9dac2e 100644 --- a/docs/syscall.md +++ b/docs/syscall.md @@ -112,9 +112,9 @@ This mechanism allows code executed on a RISC-V target to interact with and util Any other system calls will fail with an "unknown syscall" error. -## Experimental Display and Event System Calls +## Experimental Display, Event, and Sound System Calls -These system calls are solely for the convenience of accessing the [SDL library](https://www.libsdl.org/) and are only intended for the presentation of RISC-V graphics applications. They are not present in the ABI interface of POSIX or Linux. +These system calls are solely for the convenience of accessing the [SDL library](https://www.libsdl.org/) and [SDL2_Mixer](https://wiki.libsdl.org/SDL2_mixer) and are only intended for the presentation of RISC-V graphics applications. They are not present in the ABI interface of POSIX or Linux. ### `draw_frame` - Draw a frame around the SDL window @@ -139,7 +139,7 @@ The user must pass a continuous memory chunk that contains two tightly packed qu An event entry is made up of a 32-bit value representing the event's type and a `union` buffer containing the event's parameters. * `KEY_EVENT`: Either a key is pressed or released. Its value buffer is made up of a 32-bit universal key code and an 8-bit state flag; if the corresponding character of the pressed key is not printable, the bit right after the most significant bit is set; for example, the "a" key's corresponding character is printable, so its keycode is the ASCII code of the "a" character, which is `0x61`. However, because the left shift key doesn't have a corresponding printable key, its hexadecimal value is `0x400000E1`, with the 31 bit is set. -* `MOUSE_MOTION_EVENT`: The cursor is moved during the current frame. This event contains two signed integer value, which is the delta of x position and y position respectively. If the relative mouse mode is enabled, the mouse movement will never be 0 because the cursor is wrapped within the canvas and is repeated whenever the cursor reaches the border. +* `MOUSE_MOTION_EVENT`: The cursor is moved during the current frame. This event contains two signed integer value, which is the delta of x position and y position respectively. If the relative mouse mode is enabled, the mouse movement will never be 0 because the cursor is wrapped within the canvas and is repeated whenever the cursor reaches the border. * `MOUSE_BUTTON_EVENT`: The state of a mouse button has been changed. Its value buffer contains a 8-bit button value(1 is left, 2 is middle, 3 is right and so on) and an 8-bit boolean flag that indicates whether the mouse button is pressed. ### `submit_queue` - Notify the emulator a submission has been pushed into the submission queue @@ -155,3 +155,35 @@ To inform the emulator that a batch of submissions should be processed, the appl The submission entry is structured similarly to an event entry, with a 32-bit type field and an associated dynamic-sized value buffer whose width depends on the type of submission. * `RELATIVE_MODE_SUBMISSION`: Enable or disable the mouse relative mode. If the mouse relative mode is enabled, the mouse cursor is wrapped within the window border, it's associated with an 8-bit wide boolean value that indicates whether the relative mouse mode should be enbled. + +### `control_audio` - control the behavior of music and sound effect(sfx) + +**system call number**: `0xD00D` + +**synopsis**: `void control_audio(int request)` + +The application must prepare the sound data and then give the address of the sound data to register `a1`, the volume of the music to register `a2` (if necessary), and looping to register `a3` (if necessary). in order to ask the emulator to perform some sound operations. The request will be processed as soon as possible. Three different sorts of requests exist: +* `PLAY_MUSIC`: Play the music. If any music is currently playing, it will be changed to something new. In order to avoid blocking on the main thread, the music is played by a new thread. +* `STOP_MUSIC`: Halt the music. +* `SET_MUSIC_VOLUME`: If necessary, adjust the music level at some point. + +The request is similar to how music is managed earlier in the description and supports one type of sound effect request, however it does not support looping: +* `PLAY_SFX`: Play the sound effect, however keep in mind that playing too many at once may cause the channel to run out and the sound effects to stop working. A new thread is used to play the sound effect, and it does so for the same reason as `PLAY_MUSIC`. + +#### Music +Music data is defined in a structure called `musicinfo_t`. The SDL2_mixer library is used by the emulator to play music using the fields `data` and `size` in the structure. + +#### Sound Effect(sfx) +`sfxinfo_t` is a structure that defines sound effect data and size. The `data` and `size` fields of the structure are used to play sound effect with the SDL2_mixer library. + +### `setup_audio` - setup or shutdown sound system + +**system call number**: `0xBABE` + +**synopsis**: `void setup_audio(int request)` + +The majority of games, such as Doom and Quake, will maintain its own sound system or server, hence there should be a pair of initialization and destruct semantics. I believe that we should give users access to activities like startup and termination in order to maintain the semantic clarity. If not, the emulator will repeatedly check whether audio is enabled before playing music or sound effects. + +Requesting the sound system to be turned on or off in the emulator. When the audio device is not in the busy status, the request will be handled. Two categories of requests exist: +* `INIT_AUDIO`: Setup the audio device and the sfx samples buffer. After initialization, open the audio device and get ready to play some music. +* `SHUTDOWN_AUDIO`: Release all the resources that initialized by `INIT_SOUND` request. diff --git a/src/common.h b/src/common.h index 56347342..af1b4218 100644 --- a/src/common.h +++ b/src/common.h @@ -24,10 +24,30 @@ #define __ALIGNED(x) __attribute__((aligned(x))) #elif defined(_MSC_VER) #define __ALIGNED(x) __declspec(align(x)) -#else /* unspported compilers */ +#else /* unsupported compilers */ #define __ALIGNED(x) #endif +/* Packed macro */ +#if defined(__GNUC__) || defined(__clang__) +#define PACKED(name) name __attribute__((packed)) +#elif defined(_MSC_VER) +#define PACKED(name) __pragma(pack(push, 1)) name __pragma(pack(pop)) +#else /* unsupported compilers */ +#define PACKED(name) +#endif + +/* Endianness */ +#if defined(__GNUC__) || defined(__clang__) +#define bswap16(x) __builtin_bswap16(x) +#define bswap32(x) __builtin_bswap32(x) +#else +#define bswap16(x) ((x & 0xff) << 8) | ((x >> 8) & 0xff) +#define bswap32(x) \ + (bswap16(((x & 0xffff) << 16) | ((x >> 16) & 0xffff)) & 0xffff) | \ + (bswap16(((x & 0xffff) << 16) | ((x >> 16) & 0xffff)) & 0xffff) << 16 +#endif + /* The purpose of __builtin_unreachable() is to assist the compiler in: * - Eliminating dead code that the programmer knows will never be executed. * - Linearizing the code by indicating to the compiler that the path is 'cold' @@ -55,7 +75,7 @@ * https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms */ -/* In Visual Stido, __VA_ARGS__ is treated as a separate parameter. */ +/* In Visual Studio, __VA_ARGS__ is treated as a separate parameter. */ #define FIX_VC_BUG(x) x /* catenate */ diff --git a/src/syscall.c b/src/syscall.c index 4a19ce61..f3b46020 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -18,21 +18,23 @@ * system call: name, number */ /* clang-format off */ -#define SUPPORTED_SYSCALLS \ - _(close, 57) \ - _(lseek, 62) \ - _(read, 63) \ - _(write, 64) \ - _(fstat, 80) \ - _(exit, 93) \ - _(gettimeofday, 169) \ - _(brk, 214) \ - _(clock_gettime, 403) \ - _(open, 1024) \ - IIF(RV32_HAS(SDL))( \ - _(draw_frame, 0xBEEF) \ - _(setup_queue, 0xC0DE) \ - _(submit_queue, 0xFEED), \ +#define SUPPORTED_SYSCALLS \ + _(close, 57) \ + _(lseek, 62) \ + _(read, 63) \ + _(write, 64) \ + _(fstat, 80) \ + _(exit, 93) \ + _(gettimeofday, 169) \ + _(brk, 214) \ + _(clock_gettime, 403) \ + _(open, 1024) \ + IIF(RV32_HAS(SDL))( \ + _(draw_frame, 0xBEEF) \ + _(setup_queue, 0xC0DE) \ + _(submit_queue, 0xFEED) \ + _(setup_audio, 0xBABE) \ + _(control_audio, 0xD00D), \ ) /* clang-format on */ @@ -317,6 +319,8 @@ static void syscall_open(riscv_t *rv) extern void syscall_draw_frame(riscv_t *rv); extern void syscall_setup_queue(riscv_t *rv); extern void syscall_submit_queue(riscv_t *rv); +extern void syscall_setup_audio(riscv_t *rv); +extern void syscall_control_audio(riscv_t *rv); #endif void syscall_handler(riscv_t *rv) diff --git a/src/syscall_sdl.c b/src/syscall_sdl.c index ad48a679..0f13dd20 100644 --- a/src/syscall_sdl.c +++ b/src/syscall_sdl.c @@ -7,13 +7,74 @@ #error "Do not manage to build this file unless you enable SDL support." #endif +#include #include #include +#include #include +#include #include "state.h" +/* + * Only the DSITMBK sound effect in DOOM1.WAD uses sample rate 22050 + * nonetheless, since we are playing single-player rather than multiplayer, + * sticking with 11025 is acceptable. + * + * In Quake, most sound effects with sample rate 11025 + */ +#define SAMPLE_RATE 11025 + +/* Most audio device supports stereo */ +#define CHANNEL_USED 2 + +#define CHUNK_SIZE 2048 + +#define MUSIC_MAX_SIZE 65536 + +/* Max size of sound is around 18000 bytes */ +#define SFX_SAMPLE_SIZE 32768 + +/* sound-related request type */ +enum { + INIT_AUDIO, + SHUTDOWN_AUDIO, + PLAY_MUSIC, + PLAY_SFX, + SET_MUSIC_VOLUME, + STOP_MUSIC, +}; + +typedef struct sound { + uint8_t *data; + size_t size; + int looping; + int volume; +} sound_t; + +/* SDL-mixer-related and music-related variables */ +static pthread_t music_thread; +static uint8_t *music_midi_data; +static Mix_Music *mid; + +/* SDL-mixer-related and sfx-related variables */ +static pthread_t sfx_thread; +static Mix_Chunk *sfx_chunk; +static uint8_t *sfx_samples; +static uint32_t nr_sfx_samples; +static int chan; + +typedef struct { + void *data; + int size; +} musicinfo_t; + +typedef struct { + void *data; + int size; +} sfxinfo_t; + enum { KEY_EVENT = 0, MOUSE_MOTION_EVENT = 1, @@ -136,7 +197,7 @@ void syscall_submit_queue(riscv_t *rv); static bool check_sdl(riscv_t *rv, int width, int height) { if (!window) { /* check if video has been initialized. */ - if (SDL_Init(SDL_INIT_VIDEO) != 0) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) { fprintf(stderr, "Failed to call SDL_Init()\n"); exit(1); } @@ -276,3 +337,458 @@ void syscall_submit_queue(riscv_t *rv) } } } + +/* Portions Copyright (C) 2021-2022 Steve Clark + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would + * be appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * This is a straightforward MUS to MIDI converter made for programs + * like DOOM that use MIDI to store sound. + * + * Sfx_handler can handle Quake's sound effects because they are all in WAV + * format. + */ + +typedef PACKED(struct { + char id[4]; + uint16_t score_len; + uint16_t score_start; +}) mus_header_t; + +typedef PACKED(struct { + char id[4]; + int length; + uint16_t type; + uint16_t ntracks; + uint16_t ticks; +}) midi_header_t; + +static const char magic_mus[4] = { + 'M', + 'U', + 'S', + 0x1a, +}; +static const char magic_midi[4] = { + 'M', + 'T', + 'h', + 'd', +}; +static const char magic_track[4] = { + 'M', + 'T', + 'r', + 'k', +}; +static const uint8_t magic_end_of_track[4] = { + 0x00, + 0xff, + 0x2f, + 0x00, +}; + +static const int controller_map[16] = { + -1, 0, 1, 7, 10, 11, 91, 93, 64, 67, 120, 123, 126, 127, 121, -1, +}; + +static uint8_t *midi_data; +static int midi_size; + +static uint8_t *mus_pos; +static int mus_end_of_track; + +static uint8_t delta_bytes[4]; +static int delta_cnt; + +/* maintain a list of channel volume */ +static uint8_t mus_channel[16]; + +/* main conversion routine for MUS to MIDI */ +static void convert(void) +{ + uint8_t data, last, channel; + uint8_t event[3] = {0}; + int count = 0; + + data = *mus_pos++; + last = data & 0x80; + channel = data & 0xf; + + switch (data & 0x70) { + case 0x00: + event[0] = 0x80; + event[1] = *mus_pos++ & 0x7f; + event[2] = mus_channel[channel]; + count = 3; + break; + + case 0x10: + event[0] = 0x90; + data = *mus_pos++; + event[1] = data & 0x7f; + event[2] = data & 0x80 ? *mus_pos++ : mus_channel[channel]; + mus_channel[channel] = event[2]; + count = 3; + break; + + case 0x20: + event[0] = 0xe0; + event[1] = (*mus_pos & 0x01) << 6; + event[2] = *mus_pos++ >> 1; + count = 3; + break; + + case 0x30: + event[0] = 0xb0; + event[1] = controller_map[*mus_pos++ & 0xf]; + event[2] = 0x7f; + count = 3; + break; + + case 0x40: + data = *mus_pos++; + if (data == 0) { + event[0] = 0xc0; + event[1] = *mus_pos++; + count = 2; + break; + } + event[0] = 0xb0; + event[1] = controller_map[data & 0xf]; + event[2] = *mus_pos++; + count = 3; + break; + + case 0x50: + return; + + case 0x60: + mus_end_of_track = 1; + return; + + case 0x70: + mus_pos++; + return; + } + + if (channel == 9) + channel = 15; + else if (channel == 15) + channel = 9; + + event[0] |= channel; + + midi_data = realloc(midi_data, midi_size + delta_cnt + count); + + memcpy(midi_data + midi_size, &delta_bytes, delta_cnt); + midi_size += delta_cnt; + memcpy(midi_data + midi_size, &event, count); + midi_size += count; + + if (last) { + delta_cnt = 0; + do { + data = *mus_pos++; + delta_bytes[delta_cnt] = data; + delta_cnt++; + } while (data & 128); + } else { + delta_bytes[0] = 0; + delta_cnt = 1; + } +} + +uint8_t *mus2midi(uint8_t *data, int *length) +{ + mus_header_t *mus_hdr = (mus_header_t *) data; + midi_header_t midi_hdr; + uint8_t *mid_track_len; + int track_len; + + if (strncmp(mus_hdr->id, magic_mus, 4)) + return NULL; + + if (*length != mus_hdr->score_start + mus_hdr->score_len) + return NULL; + + midi_size = sizeof(midi_header_t); + memcpy(midi_hdr.id, magic_midi, 4); + midi_hdr.length = bswap32(6); + midi_hdr.type = bswap16(0); /* single track should be type 0 */ + midi_hdr.ntracks = bswap16(1); + /* maybe, set 140ppqn and set tempo to 1000000µs */ + midi_hdr.ticks = + bswap16(70); /* 70 ppqn = 140 per second @ tempo = 500000µs (default) */ + midi_data = malloc(midi_size); + memcpy(midi_data, &midi_hdr, midi_size); + + midi_data = realloc(midi_data, midi_size + 8); + memcpy(midi_data + midi_size, magic_track, 4); + midi_size += 4; + mid_track_len = midi_data + midi_size; + midi_size += 4; + + track_len = 0; + + mus_pos = data + mus_hdr->score_start; + mus_end_of_track = 0; + delta_bytes[0] = 0; + delta_cnt = 1; + + for (int i = 0; i < 16; i++) + mus_channel[i] = 0; + + while (!mus_end_of_track) + convert(); + + /* a final delta time must be added prior to the end of track event */ + midi_data = realloc(midi_data, midi_size + delta_cnt); + memcpy(midi_data + midi_size, &delta_bytes, delta_cnt); + midi_size += delta_cnt; + + midi_data = realloc(midi_data, midi_size + 3); + memcpy(midi_data + midi_size, magic_end_of_track + 1, 3); + midi_size += 3; + + track_len = bswap32(midi_size - sizeof(midi_header_t) - 8); + memcpy(mid_track_len, &track_len, 4); + + *length = midi_size; + + return midi_data; +} + +static void *sfx_handler(void *arg) +{ + sound_t *sfx = (sound_t *) arg; + uint8_t *ptr = sfx->data; + + /* parsing sound */ + ptr += 2; /* skip format */ + ptr += 2; /* skip sample rate since SAMPLE_RATE is defined */ + nr_sfx_samples = *(uint32_t *) ptr; + ptr += 4; + ptr += 4; /* skip pad bytes */ + + memcpy(sfx_samples, ptr, sizeof(uint8_t) * nr_sfx_samples); + sfx_chunk = Mix_QuickLoad_RAW(sfx_samples, nr_sfx_samples); + if (!sfx_chunk) + return NULL; + + chan = Mix_PlayChannel(-1, sfx_chunk, 0); + if (chan == -1) + return NULL; + + /* multiplied by 8 because sfx->volume's max is 15 */ + Mix_Volume(chan, sfx->volume * 8); + + return NULL; +} + +static void *music_handler(void *arg) +{ + sound_t *music = (sound_t *) arg; + int looping = music->looping ? -1 : 1; + + free(music_midi_data); + + music_midi_data = mus2midi(music->data, (int *) &music->size); + if (!music_midi_data) { + fprintf(stderr, "mus2midi failed\n"); + return NULL; + } + + SDL_RWops *rwops = SDL_RWFromMem(music_midi_data, music->size); + if (!rwops) { + fprintf(stderr, "SDL_RWFromMem failed: %s\n", SDL_GetError()); + return NULL; + } + + mid = Mix_LoadMUSType_RW(rwops, MUS_MID, SDL_TRUE); + if (!mid) { + fprintf(stderr, "Mix_LoadMUSType_RW failed: %s\n", Mix_GetError()); + return NULL; + } + + /* + * multiplied by 8 because sfx->volume's max is 15 + * further setting volume via syscall_set_music_volume + */ + Mix_VolumeMusic(music->volume * 8); + + if (Mix_PlayMusic(mid, looping) == -1) { + fprintf(stderr, "Mix_PlayMusic failed: %s\n", Mix_GetError()); + return NULL; + } + + return NULL; +} + +static void play_sfx(riscv_t *rv) +{ + state_t *s = rv_userdata(rv); /* access userdata */ + + const uint32_t sfxinfo_addr = (uint32_t) rv_get_reg(rv, rv_reg_a1); + int volume = rv_get_reg(rv, rv_reg_a2); + + sfxinfo_t sfxinfo; + memory_read(s->mem, (uint8_t *) &sfxinfo, sfxinfo_addr, sizeof(sfxinfo_t)); + + /* + * data and size in application must be placed at + * first two fields in structure so that it makes emulator + * compatible to any applications when accessing different sfxinfo_t + * from applications + */ + uint32_t sfx_data_offset = *((uint32_t *) &sfxinfo); + uint32_t sfx_data_size = *(uint32_t *) ((uint32_t *) &sfxinfo + 1); + uint8_t sfx_data[SFX_SAMPLE_SIZE]; + memory_read(s->mem, sfx_data, sfx_data_offset, + sizeof(uint8_t) * sfx_data_size); + + sound_t sfx = { + .data = sfx_data, + .size = sfx_data_size, + .volume = volume, + }; + pthread_create(&sfx_thread, NULL, sfx_handler, &sfx); +} + +static void play_music(riscv_t *rv) +{ + state_t *s = rv_userdata(rv); /* access userdata */ + + const uint32_t musicinfo_addr = (uint32_t) rv_get_reg(rv, rv_reg_a1); + int volume = rv_get_reg(rv, rv_reg_a2); + int looping = rv_get_reg(rv, rv_reg_a3); + + musicinfo_t musicinfo; + memory_read(s->mem, (uint8_t *) &musicinfo, musicinfo_addr, + sizeof(musicinfo_t)); + + /* + * data and size in application must be placed at + * first two fields in structure so that it makes emulator + * compatible to any applications when accessing different sfxinfo_t + * from applications + */ + uint32_t music_data_offset = *((uint32_t *) &musicinfo); + uint32_t music_data_size = *(uint32_t *) ((uint32_t *) &musicinfo + 1); + uint8_t music_data[MUSIC_MAX_SIZE]; + memory_read(s->mem, music_data, music_data_offset, music_data_size); + + sound_t music = { + .data = music_data, + .size = music_data_size, + .looping = looping, + .volume = volume, + }; + pthread_create(&music_thread, NULL, music_handler, &music); +} + +static void stop_music(riscv_t *rv UNUSED) +{ + if (Mix_PlayingMusic()) + Mix_HaltMusic(); +} + +static void set_music_volume(riscv_t *rv) +{ + int volume = rv_get_reg(rv, rv_reg_a1); + + /* multiplied by 8 because volume's max is 15 */ + Mix_VolumeMusic(volume * 8); +} + +static void init_audio() +{ + if (!(SDL_WasInit(-1) & SDL_INIT_AUDIO)) { + if (SDL_Init(SDL_INIT_AUDIO) != 0) { + fprintf(stderr, "Failed to call SDL_Init()\n"); + exit(1); + } + } + + /* sfx samples buffer */ + sfx_samples = malloc(SFX_SAMPLE_SIZE); + + /* Initialize SDL2 Mixer */ + if (Mix_Init(MIX_INIT_MID) != MIX_INIT_MID) { + fprintf(stderr, "Mix_Init failed: %s\n", Mix_GetError()); + exit(1); + } + if (Mix_OpenAudio(SAMPLE_RATE, AUDIO_U8, CHANNEL_USED, CHUNK_SIZE) == -1) { + fprintf(stderr, "Mix_OpenAudio failed: %s\n", Mix_GetError()); + Mix_Quit(); + exit(1); + } +} + +static void shutdown_audio(riscv_t *rv) +{ + stop_music(rv); + Mix_HaltChannel(-1); + Mix_CloseAudio(); + Mix_Quit(); + free(music_midi_data); + free(sfx_samples); +} + +void syscall_setup_audio(riscv_t *rv) +{ + /* setup_audio(request) */ + const int request = rv_get_reg(rv, rv_reg_a0); + + switch (request) { + case INIT_AUDIO: + init_audio(); + break; + case SHUTDOWN_AUDIO: + shutdown_audio(rv); + break; + default: + fprintf(stderr, "unknown sound request\n"); + break; + } +} + +void syscall_control_audio(riscv_t *rv) +{ + /* control_audio(request) */ + const int request = rv_get_reg(rv, rv_reg_a0); + + switch (request) { + case PLAY_MUSIC: + play_music(rv); + break; + case PLAY_SFX: + play_sfx(rv); + break; + case SET_MUSIC_VOLUME: + set_music_volume(rv); + break; + case STOP_MUSIC: + stop_music(rv); + break; + default: + fprintf(stderr, "unknown sound control request\n"); + break; + } +} diff --git a/tests/sound/Makefile b/tests/sound/Makefile new file mode 100644 index 00000000..8a70de9a --- /dev/null +++ b/tests/sound/Makefile @@ -0,0 +1,17 @@ +OUTPUT := sound.elf + +CROSS_COMPILE = riscv32-unknown-elf- + +CC := $(CROSS_COMPILE)gcc + +CFLAGS += -Wall -Wno-format -Wno-unused +CFLAGS += -std=gnu99 +CFLAGS += -march=rv32i -mabi=ilp32 + +all: sound.c + $(CC) $(CFLAGS) -o $(OUTPUT) $^ + +clean: + rm -f $(OUTPUT) $(OBJS) + +.PHONY: all diff --git a/tests/sound/d_e1m1.mus b/tests/sound/d_e1m1.mus new file mode 100644 index 00000000..75646821 Binary files /dev/null and b/tests/sound/d_e1m1.mus differ diff --git a/tests/sound/dspistol b/tests/sound/dspistol new file mode 100644 index 00000000..dfc3a99d Binary files /dev/null and b/tests/sound/dspistol differ diff --git a/tests/sound/sound.c b/tests/sound/sound.c new file mode 100644 index 00000000..93c23553 --- /dev/null +++ b/tests/sound/sound.c @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include +#include + +/* Sound request */ +enum { + INIT_SOUND, + SHUTDOWN_SOUND, + PLAY_MUSIC, + PLAY_SFX, + SET_MUSIC_VOLUME, + STOP_MUSIC, +}; + +typedef struct { + void *data; + int size; +} musicinfo_t; + +typedef struct { + void *data; + int size; +} sfxinfo_t; + +#define MUSIC_MAX_SIZE 65536 +#define SFX_MAX_SIZE 32768 + +sfxinfo_t *sfx; +musicinfo_t *music; + +static char *sfx_src; +static char *music_src; +static int music_delay = 30; +static int music_looping = 0; /* FIXME: parsing looping CLI argument */ +static int music_volume = 3; +static int sfx_volume = 15; + +enum { + MUSIC = 1, + SFX = 2, + INCREASING_MUSIC_VOLUME = 4, + SFX_REPEAT = 8, +}; +static int flag = 0; + +void play_music() +{ + int request_type = PLAY_MUSIC; + + register int a0 asm("a0") = request_type; + register int a1 asm("a1") = (uintptr_t) music; + register int a2 asm("a2") = music_volume; + register int a3 asm("a3") = music_looping; + register int a7 asm("a7") = 0xD00D; + + asm volatile("scall" : "+r"(a0) : "r"(a1), "r"(a2), "r"(a3), "r"(a7)); +} + +void stop_music() +{ + int request_type = STOP_MUSIC; + + register int a0 asm("a0") = request_type; + register int a7 asm("a7") = 0xD00D; + + asm volatile("scall" : "+r"(a0) : "r"(a7)); +} + +void play_sfx() +{ + int request_type = PLAY_SFX; + + register int a0 asm("a0") = request_type; + register int a1 asm("a1") = (uintptr_t) sfx; + register int a2 asm("a2") = sfx_volume; + register int a7 asm("a7") = 0xD00D; + + asm volatile("scall" : "+r"(a0) : "r"(a1), "r"(a2), "r"(a7)); +} + +void set_music_volume() +{ + int request_type = SET_MUSIC_VOLUME; + + register int a0 asm("a0") = request_type; + register int a1 asm("a1") = music_volume; + register int a7 asm("a7") = 0xD00D; + + asm volatile("scall" : "+r"(a0) : "r"(a1), "r"(a7)); +} + +/* + * when emulating the Doom, sfx is in lump + * so sfx_src should be a valid sfx lump + */ +static void load_sfx() +{ + sfx = malloc(sizeof(sfxinfo_t)); + assert(sfx); + sfx->data = malloc(sizeof(uint8_t) * SFX_MAX_SIZE); + assert(sfx->data); + sfx->size = 5669; /* FIXME: hardcoded size */ + + FILE *sfx_file = fopen(sfx_src, "rb"); + assert(sfx_file); + fread(sfx->data, 5669, 1, sfx_file); /* FIXME: hardcoded size */ + fclose(sfx_file); +} + +static void unload_sfx() +{ + free(sfx->data); + free(sfx); +} + +/* when emulating the Doom, music is in lump */ +static void load_music() +{ + music = malloc(sizeof(musicinfo_t)); + assert(music); + music->data = malloc(sizeof(uint8_t) * MUSIC_MAX_SIZE); + assert(music->data); + music->size = 17283; /* FIXME: hardcoded size */ + + FILE *music_file = fopen(music_src, "rb"); + assert(music_file); + fread(music->data, 17283, 1, music_file); /* FIXME: hardcoded size */ + fclose(music_file); +} + +static void unload_music() +{ + free(music->data); + free(music); +} + +void init_sound() +{ + int request_type = INIT_SOUND; + + register int a0 asm("a0") = request_type; + register int a7 asm("a7") = 0xBABE; + + asm volatile("scall" : "+r"(a0) : "r"(a7)); +} + +void shutdown_sound() +{ + int request_type = SHUTDOWN_SOUND; + + register int a0 asm("a0") = request_type; + register int a7 asm("a7") = 0xBABE; + + asm volatile("scall" : "+r"(a0) : "r"(a7)); +} + +/* use '=' to prevent ate by emulator */ +void usage(const char *prog) +{ + fprintf( + stderr, + "Usage: [path of rv32emu] %s [options]\n" + "Options:\n" + " =m [MUS format file]: convert MUS format to MIDI format and play " + "the music\n" + " =s [WAV format file]: play the sound effect\n" + " =d [music delay] : the larger the more longer of playing the " + "music\n" + " =mv [volume] : set volume of music which in range [0 - 15], " + "default is 8\n" + " =sv [volume] : set volume of sfx which in range [0 - 15], " + "default is 15\n" + " =upmv : increasing music volume slowly to show " + "'set_music_volume' effect, \n" + " note: delay must be larger enough to see the " + "effect\n" + " =srep : repeat sfx sound during the play of music\n" + " =h : show this usage\n", + prog); + exit(1); +} + +/* FIXME: more robust parser of CLI arguments */ +static bool parse_args(int argc, char **argv) +{ + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "=m")) { + music_src = argv[i + 1]; + flag |= MUSIC; + } else if (!strcmp(argv[i], "=s")) { + sfx_src = argv[i + 1]; + flag |= SFX; + } else if (!strcmp(argv[i], "=d")) { + music_delay = (int) strtol(argv[i + 1], NULL, 10); + } else if (!strcmp(argv[i], "=upmv")) { + flag |= INCREASING_MUSIC_VOLUME; + } else if (!strcmp(argv[i], "=srep")) { + flag |= SFX_REPEAT; + } else if (!strcmp(argv[i], "=mv")) { + music_volume = (int) strtol(argv[i + 1], NULL, 10); + } else if (!strcmp(argv[i], "=sv")) { + sfx_volume = (int) strtol(argv[i + 1], NULL, 10); + } else if (!strcmp(argv[i], "=h")) { + return false; + } + } + + return true; +} + +/* FIXME: rough simulation of sleep */ +static void busy_loop(int n) +{ + int delay = n * 10000000; + int interval = delay / (15 - music_volume); + + for (int i = 0; i < delay; i++) { + if (flag & INCREASING_MUSIC_VOLUME && flag & MUSIC) { + if (!(i % interval)) { + if (music_volume < 15) { + music_volume++; + set_music_volume(); + } + if (flag & SFX_REPEAT && flag & SFX) + play_sfx(); + } + } + } +} + +static void do_play_sound() +{ + if (flag & MUSIC) + load_music(); + if (flag & SFX) + load_sfx(); + + if (flag & MUSIC) { + if (flag & SFX) + play_sfx(); + play_music(); + busy_loop(music_delay); + stop_music(); + } else if (flag & SFX) { + play_sfx(); + busy_loop(2); + } + + if (flag & MUSIC) + unload_music(); + if (flag & SFX) + unload_sfx(); +} + +int main(int argc, char *argv[]) +{ + if (!parse_args(argc, argv)) + usage(argv[0]); + + if (!(flag & MUSIC || flag & SFX)) { + fprintf(stderr, "At least a music or sound effect should be given\n"); + usage(argv[0]); + } + + init_sound(); + do_play_sound(); + shutdown_sound(); + + return 0; +}