Mercurial > audlegacy-plugins
view src/console/Audacious_Driver.cxx @ 3058:2e649bf16ebc
Robust media change handling written by John Wehle, closes bug #46.
author | Tony Vroon <chainsaw@gentoo.org> |
---|---|
date | Fri, 24 Apr 2009 09:23:20 +0100 |
parents | 13a0e4377c20 |
children |
line wrap: on
line source
/* * Audacious: Cross platform multimedia player * Copyright (c) 2005-2006 Audacious Team * * Driver for Game_Music_Emu library. See details at: * http://www.slack.net/~ant/libs/ */ #include "config.h" #include <glib.h> #include <audlegacy/i18n.h> #include <gtk/gtk.h> extern "C" { #include <audlegacy/plugin.h> #include <audlegacy/output.h> } #include <string.h> #include <stdlib.h> #include <math.h> // configdb and prefs ui #include "Audacious_Config.h" #include "Music_Emu.h" #include "Gzip_Reader.h" int const fade_threshold = 10 * 1000; int const fade_length = 8 * 1000; int const path_max = 4096; AudaciousConsoleConfig audcfg = { 180, FALSE, 32000, 0, 0, FALSE, 0, FALSE }; static GThread* decode_thread; static int console_ip_is_going; static volatile long pending_seek; extern InputPlugin console_ip; static Music_Emu* emu = 0; static blargg_err_t log_err( blargg_err_t err ) { if ( err ) g_critical( "console error: %s\n", err ); return err; } static void log_warning( Music_Emu* emu ) { const char* w = emu->warning(); if ( w ) g_warning( "console warning: %s\n", w ); } static void unload_file() { if ( emu ) log_warning( emu ); gme_delete( emu ); emu = NULL; } // Handles URL parsing, file opening and identification, and file loading. // Keeps file header around when loading rest of file to avoid seeking // and re-reading. class File_Handler { public: gchar* path; // path without track number specification int track; // track number (0 = first track) Music_Emu* emu; // set to 0 to take ownership gme_type_t type; // Parses path and identifies file type File_Handler( const char* path, VFSFile* fd = 0 ); // Creates emulator and returns 0. If this wasn't a music file or // emulator couldn't be created, returns 1. int load( long sample_rate ); // Deletes owned emu and closes file ~File_Handler(); private: char header [4]; Vfs_File_Reader aud_vfs_in; Gzip_Reader in; }; File_Handler::File_Handler( const char* path_in, VFSFile* fd ) { emu = 0; type = 0; track = 0; path = g_strdup( path_in ); if ( !path ) return; // out of memory // extract track number gchar* args = strrchr( path, '?' ); // TODO: use strrchr()? if ( args && g_ascii_isdigit( (guchar) *(args + 1) ) ) { *args = '\0'; // TODO: use func with better error reporting, and perhaps don't // truncate path if there is no number after ? track = atoi( args + 1 ) - 1; } // open vfs if ( fd ) aud_vfs_in.reset( fd ); else if ( log_err( aud_vfs_in.open( path ) ) ) return; // now open gzip_reader on top of vfs if ( log_err( in.open( &aud_vfs_in ) ) ) return; // read and identify header if ( !log_err( in.read( header, sizeof header ) ) ) { type = gme_identify_extension( gme_identify_header( header ) ); if ( !type ) { type = gme_identify_extension( path ); if ( type != gme_gym_type ) // only trust file extension for headerless .gym files type = 0; } } } File_Handler::~File_Handler() { gme_delete( emu ); g_free( path ); } int File_Handler::load( long sample_rate ) { if ( !type ) return 1; emu = gme_new_emu( type, sample_rate ); if ( !emu ) { log_err( "Out of memory" ); return 1; } { // combine header with remaining file data Remaining_Reader reader( header, sizeof header, &in ); if ( log_err( emu->load( reader ) ) ) return 1; } // files can be closed now in.close(); aud_vfs_in.close(); log_warning( emu ); // load .m3u from same directory( replace/add extension with ".m3u") char m3u_path [path_max + 5]; strncpy( m3u_path, path, path_max ); m3u_path [path_max] = 0; // TODO: use better path-building functions char* p = strrchr( m3u_path, '.' ); if ( !p ) p = m3u_path + strlen( m3u_path ); strcpy( p, ".m3u" ); Vfs_File_Reader m3u; if ( !m3u.open( m3u_path ) ) { if ( log_err( emu->load_m3u( m3u ) ) ) // TODO: fail if m3u can't be loaded? log_warning( emu ); // this will log line number of first problem in m3u } return 0; } // Get info static inline gchar *selective_strdup(const gchar *in) { if (in == NULL || *in == '\0') return NULL; return g_strdup(in); } static Tuple* get_track_ti( const char* path, track_info_t const& info, int track ) { Tuple* ti = aud_tuple_new(); if ( ti ) { aud_tuple_associate_string(ti, FIELD_FILE_NAME, NULL, g_path_get_basename(path)); aud_tuple_associate_string(ti, FIELD_FILE_PATH, NULL, g_path_get_dirname(path)); aud_tuple_associate_string(ti, FIELD_ARTIST, NULL, info.author); aud_tuple_associate_string(ti, FIELD_ALBUM, NULL, info.game); aud_tuple_associate_string(ti, -1, "game", info.game); aud_tuple_associate_string(ti, FIELD_TITLE, NULL, info.song ? info.song : g_path_get_basename(path)); if ( info.track_count > 1 ) { aud_tuple_associate_int(ti, FIELD_TRACK_NUMBER, NULL, track + 1); aud_tuple_associate_int(ti, FIELD_SUBSONG_ID, NULL, track + 1); aud_tuple_associate_int(ti, FIELD_SUBSONG_NUM, NULL, info.track_count); ti->nsubtunes = info.track_count; ti->subtunes = NULL; } aud_tuple_associate_string(ti, FIELD_COPYRIGHT, NULL, info.copyright); aud_tuple_associate_string(ti, -1, "console", info.system); aud_tuple_associate_string(ti, FIELD_CODEC, NULL, info.system); aud_tuple_associate_string(ti, FIELD_QUALITY, NULL, "sequenced"); aud_tuple_associate_string(ti, -1, "dumper", info.dumper); aud_tuple_associate_string(ti, FIELD_COMMENT, NULL, info.comment); int length = info.length; if ( length <= 0 ) length = info.intro_length + 2 * info.loop_length; if ( length <= 0 ) length = audcfg.loop_length * 1000; else if ( length >= fade_threshold ) length += fade_length; aud_tuple_associate_int(ti, FIELD_LENGTH, NULL, length); } return ti; } static char* format_and_free_ti( Tuple* ti, int* length ) { char* result = aud_tuple_formatter_make_title_string(ti, aud_get_gentitle_format()); if ( result ) *length = aud_tuple_get_int(ti, FIELD_LENGTH, NULL); aud_tuple_free((void *) ti); return result; } static Tuple *get_song_tuple( gchar *path ) { Tuple* result = NULL; File_Handler fh( path ); if ( !fh.load( gme_info_only ) ) { track_info_t info; if ( !log_err( fh.emu->track_info( &info, fh.track ) ) ) result = get_track_ti( fh.path, info, fh.track ); } return result; } // Playback static void* play_loop_track( gpointer arg ) { InputPlayback *playback = (InputPlayback *) arg; int end_delay = 0; while ( console_ip_is_going ) { // handle pending seek long s = pending_seek; pending_seek = -1; // TODO: use atomic swap if ( s >= 0 ) { playback->output->flush( s * 1000 ); emu->seek( s * 1000 ); } // fill and play buffer of audio // TODO: see if larger buffer helps efficiency int const buf_size = 1024; Music_Emu::sample_t buf [buf_size]; if ( end_delay ) { // TODO: remove delay once host doesn't cut the end of track off if ( !--end_delay ) console_ip_is_going = false; memset( buf, 0, sizeof buf ); } else { emu->play( buf_size, buf ); if ( emu->track_ended() ) { double const seconds = 3; end_delay = emu->sample_rate() * (int) (seconds * 2) / buf_size; } } playback->pass_audio( playback, FMT_S16_NE, 1, sizeof buf, buf, &console_ip_is_going ); } // stop playing unload_file(); playback->output->close_audio(); console_ip_is_going = 0; return NULL; } static void play_file( InputPlayback *playback ) { char* path = playback->filename; unload_file(); // identify file File_Handler fh( path ); if ( !fh.type ) return; // select sample rate long sample_rate = 0; if ( fh.type == gme_spc_type ) sample_rate = 32000; if ( audcfg.resample ) sample_rate = audcfg.resample_rate; if ( !sample_rate ) sample_rate = 44100; // create emulator and load file if ( fh.load( sample_rate ) ) return; // stereo echo depth gme_set_stereo_depth( fh.emu, 1.0 / 100 * audcfg.echo ); // set equalizer if ( audcfg.treble || audcfg.bass ) { Music_Emu::equalizer_t eq; // bass - logarithmic, 2 to 8194 Hz double bass = 1.0 - (audcfg.bass / 200.0 + 0.5); eq.bass = (long) (2.0 + pow( 2.0, bass * 13 )); // treble - -50 to 0 to +5 dB double treble = audcfg.treble / 100.0; eq.treble = treble * (treble < 0 ? 50.0 : 5.0); fh.emu->set_equalizer(eq); } // get info int length = -1; track_info_t info; if ( !log_err( fh.emu->track_info( &info, fh.track ) ) ) { if ( fh.type == gme_spc_type && audcfg.ignore_spc_length ) info.length = -1; Tuple* ti = get_track_ti( fh.path, info, fh.track ); if ( ti ) { char* title = format_and_free_ti( ti, &length ); if ( title ) { playback->set_params( playback, title, length, fh.emu->voice_count() * 1000, sample_rate, 2 ); g_free( title ); } } } // start track if ( log_err( fh.emu->start_track( fh.track ) ) ) return; log_warning( fh.emu ); if ( !playback->output->open_audio( FMT_S16_NE, sample_rate, 2 ) ) return; // set fade time if ( length <= 0 ) length = audcfg.loop_length * 1000; if ( length >= fade_threshold + fade_length ) length -= fade_length / 2; fh.emu->set_fade( length, fade_length ); // take ownership of emu emu = fh.emu; fh.emu = 0; pending_seek = -1; console_ip_is_going = 1; decode_thread = g_thread_self(); playback->set_pb_ready(playback); play_loop_track( playback ); } static void seek( InputPlayback * data, gint time ) { // TODO: use thread-safe atomic set pending_seek = time; } static void console_stop(InputPlayback *playback) { console_ip_is_going = 0; if ( decode_thread ) { g_thread_join( decode_thread ); decode_thread = NULL; } playback->output->close_audio(); unload_file(); } static void console_pause(InputPlayback * playback, gshort p) { playback->output->pause(p); } static int get_time(InputPlayback *playback) { return console_ip_is_going ? playback->output->output_time() : -1; } static Tuple *probe_for_tuple(gchar *filename, VFSFile *fd) { File_Handler fh(filename, fd); if (!fh.type) return NULL; aud_vfs_rewind(fd); return get_song_tuple(filename); } // Setup static void console_init(void) { console_cfg_load(); } void console_aboutbox(void) { static GtkWidget * aboutbox = NULL; if (!aboutbox) { aboutbox = audacious_info_dialog(_("About the Console Music Decoder"), _("Console music decoder engine based on Game_Music_Emu 0.5.2.\n" "Supported formats: AY, GBS, GYM, HES, KSS, NSF, NSFE, SAP, SPC, VGM, VGZ\n" "Audacious implementation by: William Pitcock <nenolod@nenolod.net>, \n" " Shay Green <gblargg@gmail.com>"), _("Ok"), FALSE, NULL, NULL); g_signal_connect(G_OBJECT(aboutbox), "destroy", G_CALLBACK(gtk_widget_destroyed), &aboutbox); } } const gchar *gme_fmts[] = { "ay", "gbs", "gym", "hes", "kss", "nsf", "nsfe", "sap", "spc", "vgm", "vgz", NULL }; InputPlugin console_ip = { NULL, NULL, (gchar *)"Game console audio module decoder", console_init, NULL, console_aboutbox, console_cfg_ui, FALSE, NULL, NULL, play_file, console_stop, console_pause, seek, get_time, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, get_song_tuple, NULL, (gchar **)gme_fmts, NULL, probe_for_tuple, TRUE }; InputPlugin *console_iplist[] = { &console_ip, NULL }; SIMPLE_INPUT_PLUGIN(console, console_iplist);