/* XMMS calls this function to check if filename belongs to this plugin. */ static int uadexmms_is_our_file(char *filename) { int ret; if (strncmp(filename, "uade://", 7) == 0) return TRUE; uade_lock(); /* This is a performance optimization to avoid re-reading uade.conf * when state.config hasn't yet been read. uade_is_our_file() needs the * config. */ if (!state.validconfig) { state.config = config_backup; state.validconfig = 1; /* Verify that this condition is true at most once */ assert(!uade_config_optimization); uade_config_optimization = 1; } ret = uade_is_our_file(filename, 1, &state) ? TRUE : FALSE; uade_unlock(); return ret; }
/* XMMS calls this function when pausing or unpausing */ static void uade_pause(short paused) { uade_lock(); uade_is_paused = paused; uade_unlock(); uade_ip.output->pause(paused); }
int uade_get_min_subsong(int def) { int subsong; uade_lock(); subsong = -1; if (state.song != NULL) subsong = state.song->min_subsong; uade_unlock(); if (subsong == -1) subsong = def; return subsong; }
static int get_next_subsong(void) { int ispaused; uade_lock(); ispaused = uade_is_paused; uade_unlock(); if (ispaused == 0) { int newsubsong; newsubsong = uade_get_cur_subsong(-1); if (newsubsong == -1) return -1; newsubsong++; return newsubsong; } return -1; }
static int get_previous_subsong(void) { int ispaused; uade_lock(); ispaused = uade_is_paused; uade_unlock(); if (ispaused == 0) { int newsubsong; newsubsong = uade_get_cur_subsong(-1); if (newsubsong == -1) return -1; if (newsubsong > uade_get_min_subsong(-1)) { newsubsong--; return newsubsong; } } return -1; }
/* XMMS calls this function periodically to determine current playing time. We use this function to report song name and title after play_file(), and to tell XMMS to end playing if song ends for any reason. */ static int uade_get_time(void) { if (abort_playing || last_beat_played) return -1; if (gui_info_set == 0 && state.song->max_subsong != -1) { uade_lock(); if (state.song->max_subsong != -1) uade_info_string(); gui_info_set = 1; uade_unlock(); file_info_update(gui_module_filename, gui_player_filename, gui_modulename, gui_playername, gui_formatname); } return uade_ip.output->output_time(); }
static void uade_stop(void) { /* Signal other subsystems to proceed to finished state as soon as possible */ abort_playing = 1; /* Wait for playing thread to finish */ if (uade_thread_running) { pthread_join(decode_thread, NULL); uade_thread_running = 0; } uade_gui_close_subsong_win(); if (state.song != NULL) { /* If song ended volutarily, tell the play time for XMMS. */ uade_lock(); if (record_playtime) { int play_time = (state.song->out_bytes * 1000) / (UADE_BYTES_PER_FRAME * state.config.frequency); if (state.song->md5[0] != 0) uade_add_playtime(&state, state.song->md5, play_time); state.song->playtime = play_time; state.song->cur_subsong = state.song->max_subsong; uade_info_string(); } /* We must free uadesong after playthread has finished and additional GUI windows have been closed. */ uade_unalloc_song(&state); uade_unlock(); } uade_ip.output->close_audio(); }
static void uade_ffwd(void) { uade_lock(); uade_seek_forward += 10; uade_unlock(); }
static void uade_play_file(char *filename) { char tempname[PATH_MAX]; char *t; load_config(); uade_lock(); abort_playing = 0; last_beat_played = 0; record_playtime = 0; uade_is_paused = 0; uade_select_sub = -1; uade_seek_forward = 0; assert(state.song == NULL); uade_unlock(); if (strncmp(filename, "uade://", 7) == 0) filename += 7; strlcpy(tempname, filename, sizeof tempname); t = basename(tempname); if (t == NULL) t = filename; strlcpy(gui_filename, t, sizeof gui_filename); gui_info_set = 0; gui_formatname[0] = 0; gui_modulename[0] = 0; gui_playername[0] = 0; gui_module_filename[0] = 0; gui_player_filename[0] = 0; if (!state.pid) { char configname[PATH_MAX]; snprintf(configname, sizeof configname, "%s/uaerc", state.permconfig.basedir.name); uade_spawn(&state, UADE_CONFIG_UADE_CORE, configname); } if (!uade_ip.output->open_audio(sample_format, state.permconfig.frequency, UADE_CHANNELS)) { abort_playing = 1; return; } if (plugin_disabled) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "An error has occured. uade plugin is internally disabled.\n"); goto err; } /* If content db has changed (newer mtime chan previously read) then force a reload */ load_content_db(); /* Save current db if an hour has passed */ if (contentname[0]) { time_t curtime = time(NULL); if (curtime >= (content_mtime + 3600)) { struct stat st; uade_save_content_db(contentname, &state); if (stat(contentname, &st) == 0) content_mtime = st.st_mtime; } } if (initialize_song(filename) == FALSE) goto err; if (pthread_create(&decode_thread, NULL, play_loop, NULL)) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "uade: can't create play_loop() thread\n"); goto err; } uade_thread_running = 1; return; err: /* close audio that was opened */ uade_ip.output->close_audio(); abort_playing = 1; uade_lock(); if (state.song) uade_unalloc_song(&state); uade_unlock(); }
static void *play_loop(void *arg) { enum uade_control_state controlstate = UADE_S_STATE; int ret; int left = 0; uint8_t space[UADE_MAX_MESSAGE_SIZE]; struct uade_msg *um = (struct uade_msg *) space; int subsong_end = 0; uint16_t *sm; int i; unsigned int play_bytes, tailbytes = 0; uint64_t subsong_bytes = 0; char *reason; uint32_t *u32ptr; int writeoffs; int framesize = UADE_CHANNELS * UADE_BYTES_PER_SAMPLE; int song_end_trigger = 0; int64_t skip_bytes = 0; uade_lock(); record_playtime = 1; uade_unlock(); while (1) { if (controlstate == UADE_S_STATE) { assert(left == 0); if (abort_playing) { uade_lock(); record_playtime = 0; uade_unlock(); break; } uade_lock(); if (uade_seek_forward) { skip_bytes += uade_seek_forward * UADE_BYTES_PER_FRAME * state.config.frequency; uade_ip.output->flush(uade_ip.output->written_time() + uade_seek_forward * 1000); uade_seek_forward = 0; } if (uade_select_sub != -1) { state.song->cur_subsong = uade_select_sub; uade_change_subsong(&state); uade_ip.output->flush(0); uade_select_sub = -1; subsong_end = 0; subsong_bytes = 0; /* we do this to avoid timeout, and to not record playtime */ state.song->out_bytes = 0; record_playtime = 0; uade_info_string(); } if (subsong_end && song_end_trigger == 0) { if (state.song->cur_subsong == -1 || state.song->max_subsong == -1) { song_end_trigger = 1; } else { state.song->cur_subsong++; if (state.song->cur_subsong > state.song->max_subsong) { song_end_trigger = 1; } else { int x = 0; uade_change_subsong(&state); while (uade_ip.output->buffer_playing()) { /* Sleep at most 5 secs */ if (x >= 500) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "UADE: blocking work-around activated.\n"); break; } x++; xmms_usleep(10000); } uade_ip.output->flush(0); subsong_end = 0; subsong_bytes = 0; uade_gui_subsong_changed(state.song->cur_subsong); uade_info_string(); } } } uade_unlock(); if (song_end_trigger) { /* We must drain the audio fast if abort_playing happens (e.g. the user changes song when we are here waiting the sound device) */ while (uade_ip.output->buffer_playing() && abort_playing == 0) xmms_usleep(10000); break; } left = uade_read_request(&state.ipc); if (uade_send_short_message(UADE_COMMAND_TOKEN, &state.ipc)) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Can not send token.\n"); return NULL; } controlstate = UADE_R_STATE; } else { if (uade_receive_message(um, sizeof(space), &state.ipc) <= 0) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Can not receive events from uade\n"); exit(1); } switch (um->msgtype) { case UADE_COMMAND_TOKEN: controlstate = UADE_S_STATE; break; case UADE_REPLY_DATA: sm = (uint16_t *) um->data; for (i = 0; i < um->size; i += 2) { *sm = ntohs(*sm); sm++; } if (subsong_end) { play_bytes = tailbytes; tailbytes = 0; } else { play_bytes = um->size; } if (subsong_end == 0 && song_end_trigger == 0 && uade_test_silence(um->data, play_bytes, &state)) { subsong_end = 1; } subsong_bytes += play_bytes; uade_lock(); state.song->out_bytes += play_bytes; uade_unlock(); if (skip_bytes > 0) { if (play_bytes <= skip_bytes) { skip_bytes -= play_bytes; play_bytes = 0; } else { play_bytes -= skip_bytes; skip_bytes = 0; } } uade_effect_run(&state.effects, (int16_t *) um->data, play_bytes / framesize); uade_ip.add_vis_pcm(uade_ip.output->written_time(), sample_format, UADE_CHANNELS, play_bytes, um->data); writeoffs = 0; while (writeoffs < play_bytes) { int writable; while ((writable = uade_ip.output->buffer_free()) <= 0) { if (abort_playing) goto nowrite; xmms_usleep(10000); } if (writable > (play_bytes - writeoffs)) writable = play_bytes - writeoffs; uade_ip.output->write_audio(&um->data[writeoffs], writable); writeoffs += writable; } nowrite: if (state.config.timeout != -1 && state.config.use_timeouts) { if (song_end_trigger == 0) { uade_lock(); if (state.song->out_bytes / (UADE_BYTES_PER_FRAME * state.config.frequency) >= state.config.timeout) { song_end_trigger = 1; record_playtime = 0; } uade_unlock(); } } if (state.config.subsong_timeout != -1 && state.config.use_timeouts) { if (subsong_end == 0 && song_end_trigger == 0) { if (subsong_bytes / (UADE_BYTES_PER_FRAME * state.config.frequency) >= state.config.subsong_timeout) { subsong_end = 1; record_playtime = 0; } } } assert (left >= um->size); left -= um->size; break; case UADE_REPLY_FORMATNAME: uade_check_fix_string(um, 128); strlcpy(gui_formatname, (char *) um->data, sizeof gui_formatname); strlcpy(state.song->formatname, (char *) um->data, sizeof state.song->formatname); break; case UADE_REPLY_MODULENAME: uade_check_fix_string(um, 128); strlcpy(gui_modulename, (char *) um->data, sizeof gui_modulename); strlcpy(state.song->modulename, (char *) um->data, sizeof state.song->modulename); break; case UADE_REPLY_MSG: uade_check_fix_string(um, 128); plugindebug("Message: %s\n", (char *) um->data); break; case UADE_REPLY_PLAYERNAME: uade_check_fix_string(um, 128); strlcpy(gui_playername, (char *) um->data, sizeof gui_playername); strlcpy(state.song->playername, (char *) um->data, sizeof state.song->playername); break; case UADE_REPLY_SONG_END: if (um->size < 9) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Invalid song end reply\n"); exit(1); } tailbytes = ntohl(((uint32_t *) um->data)[0]); /* next ntohl() is only there for a principle. it is not useful */ if (ntohl(((uint32_t *) um->data)[1]) == 0) { /* normal happy song end. go to next subsong if any */ subsong_end = 1; } else { /* unhappy song end (error in the 68k side). skip to next song ignoring possible subsongs */ song_end_trigger = 1; } i = 0; reason = (char *) &um->data[8]; while (reason[i] && i < (um->size - 8)) i++; if (reason[i] != 0 || (i != (um->size - 9))) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Broken reason string with song end notice\n"); exit(1); } break; case UADE_REPLY_SUBSONG_INFO: if (um->size != 12) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "subsong info: too short a message\n"); exit(1); } u32ptr = (uint32_t *) um->data; uade_lock(); state.song->min_subsong = ntohl(u32ptr[0]); state.song->max_subsong = ntohl(u32ptr[1]); state.song->cur_subsong = ntohl(u32ptr[2]); if (!(-1 <= state.song->min_subsong && state.song->min_subsong <= state.song->cur_subsong && state.song->cur_subsong <= state.song->max_subsong)) { int tempmin = state.song->min_subsong, tempmax = state.song->max_subsong; __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "uade: The player is broken. Subsong info does not match with %s.\n", gui_filename); state.song->min_subsong = tempmin <= tempmax ? tempmin : tempmax; state.song->max_subsong = tempmax >= tempmin ? tempmax : tempmin; if (state.song->cur_subsong > state.song->max_subsong) state.song->max_subsong = state.song->cur_subsong; else if (state.song->cur_subsong < state.song->min_subsong) state.song->min_subsong = state.song->cur_subsong; } uade_unlock(); break; default: __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Expected sound data. got %d.\n", um->msgtype); plugin_disabled = 1; return NULL; } } } last_beat_played = 1; if (uade_send_short_message(UADE_COMMAND_REBOOT, &state.ipc)) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Can not send reboot.\n"); return NULL; } if (uade_send_short_message(UADE_COMMAND_TOKEN, &state.ipc)) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Can not send token.\n"); return NULL; } do { ret = uade_receive_message(um, sizeof(space), &state.ipc); if (ret < 0) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Can not receive events from uade.\n"); return NULL; } if (ret == 0) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "End of input after reboot.\n"); return NULL; } } while (um->msgtype != UADE_COMMAND_TOKEN); return NULL; }
/* Analyze file format, and handshake with uadecore. */ static int initialize_song(char *filename) { int ret; char modulename[PATH_MAX]; char playername[PATH_MAX]; char scorename[PATH_MAX]; uade_lock(); state.config = state.permconfig; state.validconfig = 1; state.ep = NULL; state.song = NULL; ret = uade_is_our_file(filename, 0, &state); if (!ret) goto error; strlcpy(modulename, filename, sizeof modulename); strlcpy(gui_module_filename, filename, sizeof gui_module_filename); snprintf(scorename, sizeof scorename, "%s/score", state.config.basedir.name); if (strcmp(state.ep->playername, "custom") == 0) { strlcpy(playername, modulename, sizeof playername); modulename[0] = 0; gui_module_filename[0] = 0; } else { snprintf(playername, sizeof playername, "%s/players/%s", state.config.basedir.name, state.ep->playername); } if (!uade_alloc_song(&state, filename)) goto error; uade_set_ep_attributes(&state); uade_set_song_attributes(&state, playername, sizeof playername); uade_set_effects(&state); strlcpy(gui_player_filename, playername, sizeof gui_player_filename); ret = uade_song_initialization(scorename, playername, modulename, &state); if (ret) { if (ret != UADECORE_CANT_PLAY && ret != UADECORE_INIT_ERROR) { __android_log_print(ANDROID_LOG_VERBOSE, "UADE", "Can not initialize song. Unknown error.\n"); plugin_disabled = 1; } uade_unalloc_song(&state); goto error; } uade_unlock(); return TRUE; error: uade_unlock(); return FALSE; }