diff options
author | 2009-12-27 21:39:22 +0100 | |
---|---|---|
committer | 2009-12-27 21:39:22 +0100 | |
commit | 670ad41967e397f7d591b96abf90f092180b7783 (patch) | |
tree | c8d55c243cb618752764f88c822624cbf7b0b891 | |
parent | 84d1d8c7d473e67b056ed7fc1fcd99321036fedf (diff) | |
parent | dc9958e41747704a8095f5cdaae5c99fd0b0f2d1 (diff) |
Merge branch 'guiplug'
Conflicts:
main.c
plugins/vorbis/vorbis.c
193 files changed, 33609 insertions, 2189 deletions
diff --git a/Makefile.am b/Makefile.am index 0bf99b0e..9079ff2d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,3 @@ -## Process this file with automake to produce Makefile.in -#ACLOCAL_AMFLAGS = -I m4 - SUBDIRS = gme/Game_Music_Emu-0.5.2\ gme/Game_Music_Emu-0.5.2/gme\ sid/sidplay-libs-2.1.0\ @@ -9,6 +6,10 @@ SUBDIRS = gme/Game_Music_Emu-0.5.2\ icons\ plugins/hotkeys\ plugins/ffap\ + plugins/nullout\ + plugins/vtx\ + plugins/adplug\ + ${ALSA_DIR}\ ${LFM_DIR}\ ${MPGMAD_DIR}\ ${VORBIS_DIR}\ @@ -16,7 +17,9 @@ SUBDIRS = gme/Game_Music_Emu-0.5.2\ ${WAVPACK_DIR}\ ${SNDFILE_DIR}\ ${VFS_CURL_DIR}\ - ${CDDA_DIR} + ${CDDA_DIR}\ + ${GTKUI_DIR}\ + ${FFMPEG_DIR} dumbpath=@top_srcdir@/dumb sidpath=@top_srcdir@/sid/sidplay-libs-2.1.0 @@ -27,21 +30,17 @@ bin_PROGRAMS = deadbeef deadbeef_SOURCES =\ main.c common.h deadbeef.h\ plugins.c plugins.h moduleconf.h\ - callbacks.c interface.c support.c callbacks.h interface.h support.h\ - playlist.c playlist.h gtkplaylist.c gtkplaylist.h\ + playlist.c playlist.h \ streamer.c streamer.h\ - progress.c progress.h\ codec.c codec.h\ messagepump.c messagepump.h\ conf.c conf.h\ - search.c search.h\ cgme.c cdumb.c csid.cpp\ - palsa.c palsa.h playback.h\ + playback.h\ threading_pthread.c threading.h\ md5/md5.c md5/md5.h md5/md5_loc.h\ volume.c volume.h\ - drawing.h gdkdrawing.c\ - session.h session.c gtksession.c\ + session.h session.c \ junklib.h junklib.c utf8.c utf8.h\ optmath.h\ vfs.c vfs.h vfs_stdio.c\ @@ -59,3 +59,11 @@ Copyright © 1998 - 2006 Conifer Software libsndfile - PCM read/write/conversion library Copyright © 1999-2009 Erik de Castro Lopo <erikd@mega-nerd.com> + + +libayemu - AY/YM sound chip emulator and music file loader +Copyright © 2003-2004 Alexander Sashnov <sashnov@ngs.ru> + + +adplug - Replayer for many OPL2/OPL3 audio file formats. +Copyright © 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. @@ -100,7 +100,7 @@ cdumb_init (DB_playItem_t *it) { plugin.info.bps = 16; plugin.info.channels = 2; - plugin.info.samplerate = deadbeef->playback_get_samplerate (); + plugin.info.samplerate = deadbeef->get_output ()->samplerate (); plugin.info.readpos = 0; if (cdumb_startrenderer () < 0) { @@ -775,7 +775,15 @@ cdumb_insert (DB_playItem_t *after, const char *fname) { it->fname = strdup (fname); DUMB_IT_SIGDATA * itsd = duh_get_it_sigdata(duh); if (itsd->name[0]) { - deadbeef->pl_add_meta (it, "title", convstr ((char*)&itsd->name, sizeof(itsd->name))); + int tl = sizeof(itsd->name); + int i; + for (i = 0; i < tl && itsd->name[i] && itsd->name[i] == ' '; i++); + if (i == tl || !itsd->name[i]) { + deadbeef->pl_add_meta (it, "title", NULL); + } + else { + deadbeef->pl_add_meta (it, "title", convstr ((char*)&itsd->name, sizeof(itsd->name))); + } } else { deadbeef->pl_add_meta (it, "title", NULL); @@ -32,7 +32,8 @@ static float duration; // of current song static int cgme_init (DB_playItem_t *it) { - if (gme_open_file (it->fname, &emu, deadbeef->playback_get_samplerate ())) { + int samplerate = deadbeef->get_output ()->samplerate (); + if (gme_open_file (it->fname, &emu, samplerate)) { return -1; } gme_mute_voices (emu, cgme_voicemask); @@ -41,7 +42,7 @@ cgme_init (DB_playItem_t *it) { gme_track_info (emu, &inf, it->tracknum); plugin.info.bps = 16; plugin.info.channels = 2; - plugin.info.samplerate = deadbeef->playback_get_samplerate (); + plugin.info.samplerate = samplerate; duration = deadbeef->pl_get_item_duration (it); reallength = inf.length; nzerosamples = 0; @@ -127,7 +128,15 @@ cgme_insert (DB_playItem_t *after, const char *fname) { // add metadata deadbeef->pl_add_meta (it, "system", inf.system); deadbeef->pl_add_meta (it, "album", inf.game); - deadbeef->pl_add_meta (it, "title", inf.song); + int tl = sizeof (inf.song); + int i; + for (i = 0; i < tl && inf.song[i] && inf.song[i] == ' '; i++); + if (i == tl || !inf.song[i]) { + deadbeef->pl_add_meta (it, "title", NULL); + } + else { + deadbeef->pl_add_meta (it, "title", inf.song); + } deadbeef->pl_add_meta (it, "artist", inf.author); deadbeef->pl_add_meta (it, "copyright", inf.copyright); deadbeef->pl_add_meta (it, "comment", inf.comment); diff --git a/configure.ac b/configure.ac index 24fe84d6..807f6c8b 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_INIT AC_CONFIG_HEADER(config.h) PACKAGE="deadbeef" -VERSION="0.3.1" +VERSION="guiplug-snap" AM_INIT_AUTOMAKE($PACKAGE,$VERSION) @@ -12,7 +12,6 @@ AC_PROG_CXX AC_STDC_HEADERS AC_PROG_INSTALL AC_PROG_LIBTOOL -LT_INIT AC_CONFIG_MACRO_DIR([m4]) AC_C_BIGENDIAN @@ -39,7 +38,12 @@ test "x$prefix" = xNONE && prefix=$ac_default_prefix CFLAGS="$CFLAGS -D_GNU_SOURCE -DLIBDIR=\\\"$libdir\\\" -DPREFIX=\\\"$prefix\\\"" CPPFLAGS="$CFLAGS" -PKG_CHECK_MODULES(DEPS, gtk+-2.0 >= 2.12 gthread-2.0 glib-2.0 samplerate alsa) +PKG_CHECK_MODULES(DEPS, samplerate) +PKG_CHECK_MODULES(GTKUI_DEPS, gtk+-2.0 >= 2.12 gthread-2.0 glib-2.0, HAVE_GTK=1, NO_GTK=1) +PKG_CHECK_MODULES(ALSA_DEPS, alsa, HAVE_ALSA=1, NO_ALSA=1) +PKG_CHECK_MODULES(FFMPEG_DEPS, libavcodec libavutil libavformat >= 52.0.0, HAVE_FFMPEG=1, NO_FFMPEG=1) +AC_CHECK_LIB([pthread], [main]) +AC_CHECK_LIB([dl], [main]) AX_CHECK_COMPILER_FLAGS(-msse2, HAVE_SSE2=1, []) if test ${HAVE_SSE2}; then @@ -143,6 +147,31 @@ if test ${HAVE_CDIO} && test ${HAVE_CDDB}; then AC_SUBST(CDDA_DIR) fi +dnl gtkui plugin +AM_CONDITIONAL(HAVE_GTK, test $HAVE_GTK) +if test ${HAVE_GTK}; then + GTKUI_DIR="plugins/gtkui" + AC_SUBST(GTKUI_DEPS_CFLAGS) + AC_SUBST(GTKUI_DEPS_LIBS) + AC_SUBST(GTKUI_DIR) +fi + +AM_CONDITIONAL(HAVE_ALSA, test $HAVE_ALSA) +if test ${HAVE_ALSA}; then + ALSA_DIR="plugins/alsa" + AC_SUBST(ALSA_DEPS_CFLAGS) + AC_SUBST(ALSA_DEPS_LIBS) + AC_SUBST(ALSA_DIR) +fi + +AM_CONDITIONAL(HAVE_FFMPEG, test $HAVE_FFMPEG) +if test ${HAVE_FFMPEG}; then + FFMPEG_DIR="plugins/ffmpeg" + AC_SUBST(FFMPEG_DEPS_CFLAGS) + AC_SUBST(FFMPEG_DEPS_LIBS) + AC_SUBST(FFMPEG_DIR) +fi + dnl print summary echo echo "plugin summary:" @@ -159,6 +188,8 @@ AC_DEFUN([PRINT_PLUGIN_INFO], ) PRINT_PLUGIN_INFO([stdio],[Standard IO plugin],[true]) +PRINT_PLUGIN_INFO([nullout],[NULL output],[true]) +PRINT_PLUGIN_INFO([alsa],[ALSA output],[test $HAVE_ALSA]) PRINT_PLUGIN_INFO([sid],[SID player based on libsidplay2],[true]) PRINT_PLUGIN_INFO([gme],[chiptune music player based on GME],[true]) PRINT_PLUGIN_INFO([dumb],[module player based on DUMB library],[true]) @@ -169,9 +200,13 @@ PRINT_PLUGIN_INFO([vorbis],[ogg vorbis player],[test $HAVE_VORBISFILE && test $H PRINT_PLUGIN_INFO([flac],[flac player],[test $HAVE_FLAC]) PRINT_PLUGIN_INFO([wavpack],[wavpack player],[test $HAVE_WAVPACK]) PRINT_PLUGIN_INFO([sndfile],[PCM (wav,aiff,etc) player based on libsndfile],[test $HAVE_SNDFILE]) +PRINT_PLUGIN_INFO([vtx],[vtx file player (ay8910/12 emulation)],[true]) +PRINT_PLUGIN_INFO([adplug],[adplug player (OPL2/OPL3 emulation)],[true]) PRINT_PLUGIN_INFO([vfs_curl],[http/ftp streaming support],[test $HAVE_CURL]) dnl PRINT_PLUGIN_INFO([faad2],[aac/mp4 player],[test $HAVE_FAAD && test $HAVE_MP4FF]) PRINT_PLUGIN_INFO([cdda],[cd audio player],[test $HAVE_CDIO && test $HAVE_CDDB]) +PRINT_PLUGIN_INFO([gtkui],[GTK user interface],[test $HAVE_GTK]) +PRINT_PLUGIN_INFO([ffmpeg],[ffmpeg codecs],[test $HAVE_FFMPEG]) echo AC_OUTPUT([ @@ -182,6 +217,7 @@ gme/Game_Music_Emu-0.5.2/Makefile gme/Game_Music_Emu-0.5.2/gme/Makefile sid/sidplay-libs-2.1.0/Makefile dumb/Makefile +plugins/alsa/Makefile plugins/hotkeys/Makefile plugins/lastfm/Makefile plugins/ffap/Makefile @@ -192,6 +228,11 @@ plugins/wavpack/Makefile plugins/sndfile/Makefile plugins/vfs_curl/Makefile plugins/cdda/Makefile +plugins/gtkui/Makefile +plugins/nullout/Makefile +plugins/vtx/Makefile +plugins/adplug/Makefile +plugins/ffmpeg/Makefile deadbeef.desktop ]) @@ -52,6 +52,11 @@ extern "C" { static const char *exts[] = { "sid",NULL }; const char *filetypes[] = { "SID", NULL }; +static const char settings_dlg[] = + "property \"Enable HVSC\" checkbox hvsc_enable 0;\n" + "property \"HVSC path\" file hvsc_path \"\";\n" +; + // define plugin interface static DB_decoder_t plugin = { { // plugin @@ -62,6 +67,7 @@ static DB_decoder_t plugin = { /* .plugin.version_major = */0, /* .plugin.version_minor = */1, /* .inactive = */0, + /* .plugin.nostop = */0, /* .plugin.name = */"SID decoder", /* .plugin.descr = */"SID player based on libsidplay2", /* .plugin.author = */"Alexey Yakovenko", @@ -70,6 +76,7 @@ static DB_decoder_t plugin = { /* .plugin.start = */NULL, /* .plugin.stop = */csid_stop, /* .plugin.exec_cmdline = */NULL, + /* .plugin.configdialog = */settings_dlg, }, { // info /* .info.bps = */0, @@ -336,7 +343,7 @@ csid_init (DB_playItem_t *it) { resid->create (sidplay->info ().maxsids); // resid->create (1); resid->filter (true); - resid->sampling (deadbeef->playback_get_samplerate ()); + resid->sampling (deadbeef->get_output ()->samplerate ()); duration = deadbeef->pl_get_item_duration (it); tune = new SidTune (it->fname); @@ -344,7 +351,7 @@ csid_init (DB_playItem_t *it) { plugin.info.channels = tune->isStereo () ? 2 : 1; sid2_config_t conf; conf = sidplay->config (); - conf.frequency = deadbeef->playback_get_samplerate (); + conf.frequency = deadbeef->get_output ()->samplerate (); conf.precision = 16; conf.playback = plugin.info.channels == 2 ? sid2_stereo : sid2_mono; conf.sidEmulation = resid; @@ -68,6 +68,11 @@ extern "C" { //////////////////////////// // playlist structures +// iterators +// that's a good candidate for redesign +#define PL_MAIN 0 +#define PL_SEARCH 1 + // playlist item // these are "public" fields, available to plugins typedef struct { @@ -95,15 +100,48 @@ enum { DB_PLUGIN_VFS = 5, }; +// output plugin states +enum output_state_t { + OUTPUT_STATE_STOPPED = 0, + OUTPUT_STATE_PLAYING = 1, + OUTPUT_STATE_PAUSED = 2, +}; + +// playback order +enum playback_order_t { + PLAYBACK_ORDER_LINEAR = 0, + PLAYBACK_ORDER_SHUFFLE = 1, + PLAYBACK_ORDER_RANDOM = 2, +}; + +// playback modes +enum playback_mode_t { + PLAYBACK_MODE_LOOP_ALL = 0, // loop playlist + PLAYBACK_MODE_NOLOOP = 1, // don't loop + PLAYBACK_MODE_LOOP_SINGLE = 2, // loop single track +}; + typedef struct { int event; - double time; + time_t time; } DB_event_t; typedef struct { DB_event_t ev; - DB_playItem_t *song; -} DB_event_song_t; + int index; + DB_playItem_t *track; +} DB_event_track_t; + +typedef struct { + DB_event_t ev; + int from; + int to; +} DB_event_trackchange_t; + +typedef struct { + DB_event_t ev; + int state; +} DB_event_state_t; typedef struct DB_conf_item_s { char *key; @@ -122,11 +160,17 @@ enum { DB_EV_SONGFINISHED = 3, // triggers when song finished playing (for scrobblers and such) DB_EV_TRACKDELETED = 4, // triggers when track is to be deleted from playlist DB_EV_CONFIGCHANGED = 5, // configuration option changed + DB_EV_ACTIVATE = 6, // will be fired every time player is activated + DB_EV_TRACKINFOCHANGED = 7, // notify plugins that trackinfo was changed + DB_EV_PAUSED = 8, // player was paused or unpaused + DB_EV_PLAYLISTCHANGED = 9, // playlist contents were changed + DB_EV_VOLUMECHANGED = 10, // volume was changed + DB_EV_OUTPUTCHANGED = 11, // sound output plugin changed DB_EV_MAX }; // preset columns, working using IDs -enum { +enum pl_column_t { DB_COLUMN_PLAYING = 1, DB_COLUMN_ARTIST_ALBUM = 2, DB_COLUMN_ARTIST = 3, @@ -147,11 +191,6 @@ enum { M_PAUSESONG, M_PLAYRANDOM, M_SONGCHANGED, // p1=from, p2=to - M_ADDDIR, // ctx = pointer to string, which must be freed by g_free - M_ADDFILES, // ctx = GSList pointer, must be freed with g_slist_free - M_ADDDIRS, // ctx = GSList pointer, must be freed with g_slist_free - M_OPENFILES, // ctx = GSList pointer, must be freed with g_slist_free - M_FMDRAGDROP, // ctx = char* ptr, must be freed with standard free, p1 is length of data, p2 is drop_y M_TERMINATE, // must be sent to player thread to terminate M_PLAYLISTREFRESH, M_REINIT_SOUND, @@ -185,6 +224,7 @@ typedef struct { void (*md5) (uint8_t sig[16], const char *in, int len); void (*md5_to_str) (char *str, const uint8_t sig[16]); // playback control + struct DB_output_s* (*get_output) (void); void (*playback_next) (void); void (*playback_prev) (void); void (*playback_pause) (void); @@ -193,13 +233,22 @@ typedef struct { void (*playback_random) (void); float (*playback_get_pos) (void); // [0..100] void (*playback_set_pos) (float pos); // [0..100] - int (*playback_get_samplerate) (void); // output samplerate - void (*playback_update_bitrate) (float bitrate); + // streamer access + // FIXME: needs to be thread-safe + DB_playItem_t *(*streamer_get_playing_track) (void); + DB_playItem_t *(*streamer_get_streaming_track) (void); + float (*streamer_get_playpos) (void); + void (*streamer_seek) (float time); + int (*streamer_ok_to_read) (int len); + void (*streamer_reset) (int full); + int (*streamer_read) (char *bytes, int size); + void (*streamer_set_bitrate) (int bitrate); + int (*streamer_get_apx_bitrate) (void); // process control const char *(*get_config_dir) (void); void (*quit) (void); // threading - intptr_t (*thread_start) (void (*fn)(uintptr_t ctx), uintptr_t ctx); + intptr_t (*thread_start) (void (*fn)(void *ctx), void *ctx); int (*thread_join) (intptr_t tid); uintptr_t (*mutex_create) (void); void (*mutex_free) (uintptr_t mtx); @@ -214,14 +263,70 @@ typedef struct { DB_playItem_t * (*pl_item_alloc) (void); void (*pl_item_free) (DB_playItem_t *it); void (*pl_item_copy) (DB_playItem_t *out, DB_playItem_t *in); + int (*pl_add_file) (const char *fname, int (*cb)(DB_playItem_t *it, void *data), void *user_data); + int (*pl_add_dir) (const char *dirname, int (*cb)(DB_playItem_t *it, void *data), void *user_data); DB_playItem_t *(*pl_insert_item) (DB_playItem_t *after, DB_playItem_t *it); + DB_playItem_t *(*pl_insert_dir) (DB_playItem_t *after, const char *dirname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data); + DB_playItem_t *(*pl_insert_file) (DB_playItem_t *after, const char *fname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data); int (*pl_get_idx_of) (DB_playItem_t *it); + DB_playItem_t * (*pl_get_for_idx) (int idx); + DB_playItem_t * (*pl_get_for_idx_and_iter) (int idx, int iter); + float (*pl_get_totaltime) (void); + int (*pl_getcount) (void); + DB_playItem_t *(*pl_getcurrent) (void); + int (*pl_delete_selected) (void); + void (*pl_set_cursor) (int iter, int cursor); + int (*pl_get_cursor) (int iter); + void (*pl_set_selected) (DB_playItem_t *it, int sel); + int (*pl_is_selected) (DB_playItem_t *it); + void (*pl_free) (void); + int (*pl_load) (const char *name); + int (*pl_save) (const char *name); + void (*pl_select_all) (void); + void (*pl_crop_selected) (void); + int (*pl_getselcount) (void); + DB_playItem_t *(*pl_get_first) (int iter); + DB_playItem_t *(*pl_get_last) (int iter); + DB_playItem_t *(*pl_get_next) (DB_playItem_t *it, int iter); + DB_playItem_t *(*pl_get_prev) (DB_playItem_t *it, int iter); + /* + this function formats line for display in playlist + @it pointer to playlist item + @s output buffer + @size size of output buffer + @id one of IDs defined in pl_column_id_t enum, can be -1 + @fmt format string, used if id is -1 + format is printf-alike. specification: + %a artist + %t title + %b album + %n track + %l length (duration) + more to come + */ + int (*pl_format_title) (DB_playItem_t *it, char *s, int size, int id, const char *fmt); + void (*pl_format_item_display_name) (DB_playItem_t *it, char *str, int len); +// void (*pl_set_next) (DB_playItem_t *it, DB_playItem_t *next, int iter); +// void (*pl_set_prev) (DB_playItem_t *it, DB_playItem_t *prev, int iter); +// void (*pl_set_head) (DB_playItem_t *it, int iter); +// void (*pl_set_tail) (DB_playItem_t *it, int iter); +// DB_playItem_t* (*pl_get_head) (void); +// DB_playItem_t* (*pl_get_tail) (void); + void (*pl_move_items) (int iter, DB_playItem_t *drop_before, uint32_t *indexes, int count); + int (*pl_process_search) (const char *text); // metainfo void (*pl_add_meta) (DB_playItem_t *it, const char *key, const char *value); const char *(*pl_find_meta) (DB_playItem_t *song, const char *meta); void (*pl_delete_all_meta) (DB_playItem_t *it); void (*pl_set_item_duration) (DB_playItem_t *it, float duration); float (*pl_get_item_duration) (DB_playItem_t *it); + void (*pl_sort) (int iter, int id, const char *format, int ascending); + // playqueue support + int (*pl_playqueue_push) (DB_playItem_t *it); + void (*pl_playqueue_clear) (void); + void (*pl_playqueue_pop) (void); + void (*pl_playqueue_remove) (DB_playItem_t *it); + int (*pl_playqueue_test) (DB_playItem_t *it); // cuesheet support DB_playItem_t *(*pl_insert_cue_from_buffer) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate); DB_playItem_t * (*pl_insert_cue) (DB_playItem_t *after, const char *filename, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate); @@ -230,6 +335,7 @@ typedef struct { float (*volume_get_db) (void); void (*volume_set_amp) (float amp); float (*volume_get_amp) (void); + float (*volume_get_min_db) (void); // junk reading int (*junk_read_id3v1) (DB_playItem_t *it, DB_FILE *fp); int (*junk_read_id3v2) (DB_playItem_t *it, DB_FILE *fp); @@ -254,10 +360,14 @@ typedef struct { float (*conf_get_float) (const char *key, float def); int (*conf_get_int) (const char *key, int def); void (*conf_set_str) (const char *key, const char *val); + void (*conf_set_int) (const char *key, int val); DB_conf_item_t * (*conf_find) (const char *group, DB_conf_item_t *prev); - // gui locking - void (*gui_lock) (void); - void (*gui_unlock) (void); + void (*conf_remove_items) (const char *key); + // plugin communication + struct DB_decoder_s **(*plug_get_decoder_list) (void); + struct DB_output_s **(*plug_get_output_list) (void); + struct DB_plugin_s **(*plug_get_list) (void); + int (*plug_activate) (struct DB_plugin_s *p, int activate); // exporting plugin conf options for gui // all exported options are grouped by plugin, and will be available to user // from gui @@ -281,6 +391,8 @@ typedef struct DB_plugin_s { int16_t version_minor; // may be deactivated on failures after load int inactive; + // prevent plugin from being dynamically stopped + int nostop; // any of those can be left NULL // though it's much better to fill them with something useful const char *name; @@ -297,6 +409,9 @@ typedef struct DB_plugin_s { // cmdline is 0-separated list of strings, guaranteed to have 0 at the end // cmdline_size is number of bytes pointed by cmdline int (*exec_cmdline) (const char *cmdline, int cmdline_size); + // plugin configuration dialog is constructed from this data + // can be NULL + const char *configdialog; } DB_plugin_t; typedef struct { @@ -352,19 +467,21 @@ typedef struct DB_decoder_s { } DB_decoder_t; // output plugin -typedef struct { +typedef struct DB_output_s { DB_plugin_t plugin; // init is called once at plugin activation - int (*init) (void (*callback)(char *stream, int len)); + int (*init) (void); // free is called if output plugin was changed to another, or unload is about to happen int (*free) (void); + // reconfigure output to another samplerate, if supported + int (*change_rate) (int rate); // play, stop, pause, unpause are called by deadbeef in response to user // events, or as part of streaming process - // state must be 0 for stopped, 1 for playing and 2 for paused int (*play) (void); int (*stop) (void); int (*pause) (void); int (*unpause) (void); + // one of output_state_t enum values int (*state) (void); // following functions must return output sampling rate, bits per sample and number // of channels @@ -372,7 +489,9 @@ typedef struct { int (*bitspersample) (void); int (*channels) (void); // must return 0 for little endian output, or 1 for big endian - int (*endianess) (void); + int (*endianness) (void); + // soundcard enumeration (can be NULL) + void (*enum_soundcards) (void (*callback)(const char *name, const char *desc, void*), void *userdata); } DB_output_t; // dsp plugin @@ -412,6 +531,13 @@ typedef struct DB_vfs_s { unsigned streaming : 1; } DB_vfs_t; +// gui plugin +// implements pretty much anything it wants +// works mostly like misc plugin, except we need separate type for that +typedef struct DB_gui_s { + DB_plugin_t plugin; +} DB_gui_t; + #ifdef __cplusplus } #endif diff --git a/decoder_template.c b/decoder_template.c new file mode 100644 index 00000000..2f47582e --- /dev/null +++ b/decoder_template.c @@ -0,0 +1,205 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +// this is a decoder plugin skeleton +// use to create new decoder plugins + +#include "../../deadbeef.h" + +static DB_decoder_t plugin; +static DB_functions_t *deadbeef; + +static const char * exts[] = { "example", NULL }; // e.g. mp3 +static const char *filetypes[] = { "example", NULL }; // e.g. MP3 + +static int +example_init (DB_playItem_t *it) { + // prepare to decode the track + // return -1 on failure + + // fill in mandatory plugin fields + plugin.info.bps = deadbeef->get_output ()->bitspersample (); + plugin.info.channels = deadbeef->get_output ()->channels (); + plugin.info.samplerate = decoder->samplerate; + plugin.info.readpos = 0; + + return 0; +} + +static void +example_free (void) { + // free everything allocated in _init +} + +static int +example_read_int16 (char *bytes, int size) { + // try decode `size' bytes + // return number of decoded bytes + // return 0 on EOF + + plugin.info.readpos = (float)currentsample / plugin.info.samplerate; + return size; +} + +static int +example_seek_sample (int sample) { + // seek to specified sample (frame) + // return 0 on success + // return -1 on failure + + // update readpos + plugin.info.readpos = (float)sample / plugin.info.samplerate; + return 0; +} + +static int +example_seek (float time) { + // seek to specified time in seconds + // return 0 on success + // return -1 on failure + return example_seek_sample (time * plugin.info.samplerate); + return 0; +} + +static DB_playItem_t * +example_insert (DB_playItem_t *after, const char *fname) { + // read information from the track + // load/process cuesheet if exists + // insert track into playlist + // return track pointer on success + // return NULL on failure + +example: + + // open file + DB_FILE *fp = deadbeef->fopen (fname); + if (!fp) { + trace ("example: failed to fopen %s\n", fname); + return NULL; + } + // setup decoder to use vfs + decoder_callbacks_t cb = { + open = vfs_open_wrapper, + .... etc .... + } + decoder_info_t *di = decoder_open_callbacks (&cb); + if (!di) { + trace ("example: failed to init decoder\n"); + return NULL; + } + // read track info/tags + track_info_t ti; + if (decoder_read_info (&ti) < 0) { + trace ("example: failed to read info\n"); + decoder_free (di); + return NULL; + } + + // now we should have track duration, and can try loading cuesheet + // 1st try embedded cuesheet + if (ti.embeddedcuesheet[0]) { + DB_playItem_t *cue = deadbeef->pl_insert_cue_from_buffer (after, fname, ti.embeddedcuesheet, strlen (ti.embeddedcuesheet), &plugin, plugin.filetypes[0], ti.total_num_samples, ti.samplerate); + if (cue) { + // cuesheet loaded + decoder_free (di); + return cue; + } + } + + // embedded cuesheet not found, try external one + DB_playItem_t *cue = deadbeef->pl_insert_cue (after, fname, &plugin, plugin.filetypes[0], ti.total_num_samples, ti.samplerate); + if (cue) { + // cuesheet loaded + decoder_free (di); + return cue; + } + + // no cuesheet, prepare track for addition + DB_playItem_t *it = deadbeef->pl_item_alloc (); + it->decoder = &plugin; + it->fname = strdup (fname); + it->filetype = filetypes[0]; + deadbeef->pl_set_item_duration (it, (float)ti.total_num_samples/ti.samplerate); + + // add metainfo + if (!strlen (ti.title)) { + // title is empty, this call will set track title to filename without extension + deadbeef->pl_add_meta (it, "title", NULL); + } + else { + deadbeef->pl_add_meta (it, "title", ti.title); + } + deadbeef->pl_add_meta (it, "artist", ti.artist); + // ... etc ... + + // free decoder + decoder_free (di); + + // now the track is ready, insert into playlist + after = deadbeef->pl_insert_item (after, it); + return after; +} + +static int +example_start (void) { + // do one-time plugin initialization here + // e.g. starting threads for background processing, subscribing to events, etc + // return 0 on success + // return -1 on failure + return 0; +} +static int +example_stop (void) { + // undo everything done in _start here + // return 0 on success + // return -1 on failure + return 0; +} + +// define plugin interface +static DB_decoder_t plugin = { + DB_PLUGIN_SET_API_VERSION + .plugin.version_major = 0, + .plugin.version_minor = 1, + .plugin.type = DB_PLUGIN_DECODER, + .plugin.name = "short plugin name", + .plugin.descr = "plugin description", + .plugin.author = "author name", + .plugin.email = "author email", + .plugin.website = "author/plugin website", + .plugin.start = example_start, + .plugin.stop = example_stop, + .init = example_init, + .free = example_free, + .read_int16 = example_read_int16, +// .read_float32 = example_read_float32, + .seek = example_seek, + .seek_sample = example_seek_sample, + .insert = example_insert, + .exts = exts, + .id = "example", + .filetypes = filetypes +}; + +DB_plugin_t * +example_load (DB_functions_t *api) { + deadbeef = api; + return DB_PLUGIN (&plugin); +} + diff --git a/dumb/dumb-kode54/src/it/reads3m.c b/dumb/dumb-kode54/src/it/reads3m.c index 283becb1..0dac8331 100644 --- a/dumb/dumb-kode54/src/it/reads3m.c +++ b/dumb/dumb-kode54/src/it/reads3m.c @@ -61,7 +61,7 @@ static int it_s3m_read_sample_header(IT_SAMPLE *sample, long *offset, unsigned c sample->name[28] = 0;
dumbfile_skip(f, 4);
sample->flags &= ~IT_SAMPLE_EXISTS;
- return dumbfile_error(f);
+ return -1; // return error so that another plugin could pick that file up
}
*offset = dumbfile_getc(f) << 20;
@@ -730,12 +730,15 @@ static DUMB_IT_SIGDATA *it_s3m_load_sigdata(DUMBFILE *f, int * cwtv) break;
case S3M_COMPONENT_SAMPLE:
- if (it_s3m_read_sample_header(&sigdata->sample[component[n].n], &offset, &sample_pack[component[n].n], *cwtv, f)) {
+ {
+ int err = it_s3m_read_sample_header(&sigdata->sample[component[n].n], &offset, &sample_pack[component[n].n], *cwtv, f);
+ if (err) {
free(buffer);
free(component);
_dumb_it_unload_sigdata(sigdata);
return NULL;
}
+ }
if (sigdata->sample[component[n].n].flags & IT_SAMPLE_EXISTS) {
short *sample;
diff --git a/gtksession.c b/gtksession.c deleted file mode 100644 index e24706b6..00000000 --- a/gtksession.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - DeaDBeeF - ultimate music player for GNU/Linux systems with X11 - Copyright (C) 2009 Alexey Yakovenko - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ -#include <stdio.h> -#include <gtk/gtk.h> -#ifdef HAVE_CONFIG_H -# include <config.h> -#endif -#include "session.h" -#include "conf.h" - -void -session_capture_window_attrs (uintptr_t window) { - GtkWindow *wnd = GTK_WINDOW (window); - int win_attrs[4]; - gtk_window_get_position (wnd, &win_attrs[0], &win_attrs[1]); - gtk_window_get_size (wnd, &win_attrs[2], &win_attrs[3]); - conf_set_int ("mainwin.geometry.x", win_attrs[0]); - conf_set_int ("mainwin.geometry.y", win_attrs[1]); - conf_set_int ("mainwin.geometry.w", win_attrs[2]); - conf_set_int ("mainwin.geometry.h", win_attrs[3]); -} - -void -session_restore_window_attrs (uintptr_t window) { - GtkWindow *wnd = GTK_WINDOW (window); - gtk_window_move (wnd, conf_get_int ("mainwin.geometry.x", 40), conf_get_int ("mainwin.geometry.y", 40)); - gtk_window_resize (wnd, conf_get_int ("mainwin.geometry.w", 500), conf_get_int ("mainwin.geometry.h", 300)); -} @@ -27,7 +27,7 @@ hotkeys.key8 XF86AudioLowerVolume: volume_down full list of actions: -toggle_pause, play, prev, next, stop, play_random, seek_fwd, seek_back, volume_up, volume_down +toggle_pause, play, prev, next, stop, play_random, seek_fwd, seek_back, volume_up, volume_down, toggle_stop_after_current LAST.FM PLUGIN CONFIGURATION @@ -270,9 +270,13 @@ convstr_id3v2_2to3 (const unsigned char* str, int sz) { if (*str == 1) { if (str[1] == 0xff && str[2] == 0xfe) { enc = "UCS-2LE"; + str += 2; + sz -= 2; } else if (str[2] == 0xff && str[1] == 0xfe) { enc = "UCS-2BE"; + str += 2; + sz -= 2; } else { trace ("invalid ucs-2 signature %x %x\n", (int)str[1], (int)str[2]); @@ -15,12 +15,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <gtk/gtk.h> #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #include <time.h> +#include <limits.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/types.h> @@ -33,19 +33,13 @@ #ifdef HAVE_CONFIG_H # include <config.h> #endif -#include "interface.h" -#include "callbacks.h" -#include "support.h" #include "playlist.h" #include "playback.h" #include "unistd.h" #include "threading.h" #include "messagepump.h" -#include "gtkplaylist.h" #include "codec.h" #include "streamer.h" -#include "search.h" -#include "progress.h" #include "conf.h" #include "volume.h" #include "session.h" @@ -61,174 +55,134 @@ char dbconfdir[1024]; // $HOME/.config/deadbeef char defpl[1024]; // $HOME/.config/deadbeef/default.dbpl char sessfile[1024]; // $HOME/.config/deadbeef/session -// main widgets -GtkWidget *mainwin; -GtkWidget *searchwin; -GtkStatusIcon *trayicon; -GtkWidget *traymenu; - -// playlist configuration structures -gtkplaylist_t main_playlist; -gtkplaylist_t search_playlist; - -// update status bar and window title -static int sb_context_id = -1; -static char sb_text[512]; -static float last_songpos = -1; - -void -update_songinfo (void) { - char sbtext_new[512] = "-"; - float songpos = last_songpos; - - int daystotal = (int)pl_totaltime / (3600*24); - int hourtotal = ((int)pl_totaltime / 3600) % 24; - int mintotal = ((int)pl_totaltime/60) % 60; - int sectotal = ((int)pl_totaltime) % 60; - - char totaltime_str[512] = ""; - if (daystotal == 0) - snprintf (totaltime_str, sizeof (totaltime_str), "%d:%02d:%02d", hourtotal, mintotal, sectotal); - - else if (daystotal == 1) - snprintf (totaltime_str, sizeof (totaltime_str), "1 day %d:%02d:%02d", hourtotal, mintotal, sectotal); - - else - snprintf (totaltime_str, sizeof (totaltime_str), "%d days %d:%02d:%02d", daystotal, hourtotal, mintotal, sectotal); - - if (p_isstopped ()) { - snprintf (sbtext_new, sizeof (sbtext_new), "Stopped | %d tracks | %s total playtime", pl_getcount (), totaltime_str); - songpos = 0; - } - else if (str_playing_song.decoder) { -// codec_lock (); - DB_decoder_t *c = str_playing_song.decoder; - float playpos = streamer_get_playpos (); - int minpos = playpos / 60; - int secpos = playpos - minpos * 60; - int mindur = str_playing_song._duration / 60; - int secdur = str_playing_song._duration - mindur * 60; - - const char *mode = c->info.channels == 1 ? "Mono" : "Stereo"; - int samplerate = c->info.samplerate; - int bitspersample = c->info.bps; - songpos = playpos; -// codec_unlock (); - - char t[100]; - if (str_playing_song._duration >= 0) { - snprintf (t, sizeof (t), "%d:%02d", mindur, secdur); - } - else { - strcpy (t, "-:--"); - } - - char sbitrate[20] = ""; -#if 0 // NOTE: do not enable that for stable branch yet - int bitrate = streamer_get_bitrate (); - if (bitrate > 0) { - snprintf (sbitrate, sizeof (sbitrate), "%d kbps ", bitrate); - } -#endif - const char *spaused = p_ispaused () ? "Paused | " : ""; - snprintf (sbtext_new, sizeof (sbtext_new), "%s%s %s| %dHz | %d bit | %s | %d:%02d / %s | %d tracks | %s total playtime", spaused, str_playing_song.filetype ? str_playing_song.filetype:"-", sbitrate, samplerate, bitspersample, mode, minpos, secpos, t, pl_getcount (), totaltime_str); - } - - if (strcmp (sbtext_new, sb_text)) { - strcpy (sb_text, sbtext_new); - - // form statusline - GDK_THREADS_ENTER(); - // FIXME: don't update if window is not visible - GtkStatusbar *sb = GTK_STATUSBAR (lookup_widget (mainwin, "statusbar")); - if (sb_context_id == -1) { - sb_context_id = gtk_statusbar_get_context_id (sb, "msg"); +// client-side commandline support +// -1 error, program must exit with error code -1 +// 0 proceed normally as nothing happened +// 1 no error, but program must exit with error code 0 +int +client_exec_command_line (const char *cmdline, int len) { + const uint8_t *parg = (const uint8_t *)cmdline; + const uint8_t *pend = cmdline + len; + int exitcode = 0; + int queue = 0; + while (parg < pend) { + // if (filter == 1) { + // help, version and nowplaying are executed with any filter + if (!strcmp (parg, "--help") || !strcmp (parg, "-h")) { + fprintf (stderr, "Usage: deadbeef [options] [file(s)]\n"); + fprintf (stderr, "Options:\n"); + fprintf (stderr, " --help or -h Print help (this message) and exit\n"); + fprintf (stderr, " --quit Quit player\n"); + fprintf (stderr, " --version Print version info and exit\n"); + fprintf (stderr, " --play Start playback\n"); + fprintf (stderr, " --stop Stop playback\n"); + fprintf (stderr, " --pause Pause playback\n"); + fprintf (stderr, " --next Next song in playlist\n"); + fprintf (stderr, " --prev Previous song in playlist\n"); + fprintf (stderr, " --random Random song in playlist\n"); + fprintf (stderr, " --queue Append file(s) to existing playlist\n"); + fprintf (stderr, " --nowplaying FMT Print formatted track name to stdout\n"); + fprintf (stderr, " FMT %%-syntax: [a]rtist, [t]itle, al[b]um, [l]ength, track[n]umber\n"); + fprintf (stderr, " e.g.: --nowplaying \"%%a - %%t\" should print \"artist - title\"\n"); + return 1; } - - gtk_statusbar_pop (sb, sb_context_id); - gtk_statusbar_push (sb, sb_context_id, sb_text); - - GDK_THREADS_LEAVE(); - } - - if (songpos != last_songpos) { - void seekbar_draw (GtkWidget *widget); - void seekbar_expose (GtkWidget *widget, int x, int y, int w, int h); - if (mainwin) { - GtkWidget *widget = lookup_widget (mainwin, "seekbar"); - // translate volume to seekbar pixels - songpos /= str_playing_song._duration; - songpos *= widget->allocation.width; - if ((int)(songpos*2) != (int)(last_songpos*2)) { - GDK_THREADS_ENTER(); - seekbar_draw (widget); - seekbar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height); - GDK_THREADS_LEAVE(); - last_songpos = songpos; - } + else if (!strcmp (parg, "--version")) { + fprintf (stderr, "DeaDBeeF %s Copyright (C) 2009 Alexey Yakovenko\n", VERSION); + return 1; } + parg += strlen (parg); + parg++; } + return 0; } - +// this function executes server-side commands only +// must be called only from within server // -1 error, program must exit with error code -1 // 0 proceed normally as nothing happened // 1 no error, but program must exit with error code 0 -// 2 no error, start playback immediately after startup -// 3 no error, don't start playback immediately after startup +// 2 don't load playlist on startup +// when executed in remote server -- error code will be ignored int -exec_command_line (const char *cmdline, int len, int filter) { +server_exec_command_line (const char *cmdline, int len, char *sendback, int sbsize) { + if (sendback) { + sendback[0] = 0; + } const uint8_t *parg = (const uint8_t *)cmdline; const uint8_t *pend = cmdline + len; int exitcode = 0; int queue = 0; while (parg < pend) { - if (filter == 1) { - if (!strcmp (parg, "--help") || !strcmp (parg, "-h")) { - printf ("Usage: deadbeef [options] [file(s)]\n"); - printf ("Options:\n"); - printf (" --help or -h Print help (this message) and exit\n"); - printf (" --version Print version info and exit\n"); - printf (" --play Start playback\n"); - printf (" --stop Stop playback\n"); - printf (" --pause Pause playback\n"); - printf (" --next Next song in playlist\n"); - printf (" --prev Previous song in playlist\n"); - printf (" --random Random song in playlist\n"); - printf (" --queue Append file(s) to existing playlist\n"); - return 1; - } - else if (!strcmp (parg, "--version")) { - printf ("DeaDBeeF %s Copyright (C) 2009 Alexey Yakovenko\n", VERSION); - return 1; - } - } - else if (filter == 0) { - if (!strcmp (parg, "--next")) { - messagepump_push (M_NEXTSONG, 0, 0, 0); - } - else if (!strcmp (parg, "--prev")) { - messagepump_push (M_PREVSONG, 0, 0, 0); - } - else if (!strcmp (parg, "--play")) { - messagepump_push (M_PLAYSONG, 0, 0, 0); - } - else if (!strcmp (parg, "--stop")) { - messagepump_push (M_STOPSONG, 0, 0, 0); - } - else if (!strcmp (parg, "--pause")) { - messagepump_push (M_PAUSESONG, 0, 0, 0); - } - else if (!strcmp (parg, "--random")) { - messagepump_push (M_PLAYRANDOM, 0, 0, 0); + if (!strcmp (parg, "--nowplaying")) { + parg += strlen (parg); + parg++; + if (parg >= pend) { + if (sendback) { + snprintf (sendback, sbsize, "error --nowplaying expects format argument\n"); + return 0; + } + else { + fprintf (stderr, "--nowplaying expects format argument\n"); + return -1; + } } - else if (!strcmp (parg, "--queue")) { - queue = 1; + if (sendback) { + playItem_t *curr = streamer_get_playing_track (); + if (curr && curr->decoder) { + const char np[] = "nowplaying "; + memcpy (sendback, np, sizeof (np)-1); + pl_format_title (curr, sendback+sizeof(np)-1, sbsize-sizeof(np)+1, -1, parg); + } + else { + strcpy (sendback, "nowplaying nothing"); + } } - else if (parg[0] != '-') { - break; + else { + char out[2048]; + playItem_t *curr = streamer_get_playing_track (); + if (curr && curr->decoder) { + pl_format_title (curr, out, sizeof (out), -1, parg); + } + else { + strcpy (out, "nothing"); + } + printf (out); + return 1; // exit } } + else if (!strcmp (parg, "--next")) { + messagepump_push (M_NEXTSONG, 0, 0, 0); + return 0; + } + else if (!strcmp (parg, "--prev")) { + messagepump_push (M_PREVSONG, 0, 0, 0); + return 0; + } + else if (!strcmp (parg, "--play")) { + messagepump_push (M_PLAYSONG, 0, 0, 0); + return 0; + } + else if (!strcmp (parg, "--stop")) { + messagepump_push (M_STOPSONG, 0, 0, 0); + return 0; + } + else if (!strcmp (parg, "--pause")) { + messagepump_push (M_PAUSESONG, 0, 0, 0); + return 0; + } + else if (!strcmp (parg, "--random")) { + messagepump_push (M_PLAYRANDOM, 0, 0, 0); + return 0; + } + else if (!strcmp (parg, "--queue")) { + queue = 1; + } + else if (!strcmp (parg, "--quit")) { + messagepump_push (M_TERMINATE, 0, 0, 0); + } + else if (parg[0] != '-') { + break; // unknown option is filename + } parg += strlen (parg); parg++; } @@ -236,7 +190,7 @@ exec_command_line (const char *cmdline, int len, int filter) { // add files if (!queue) { pl_free (); - main_playlist.row = -1; + pl_reset_cursor (); } while (parg < pend) { char resolved[PATH_MAX]; @@ -247,23 +201,19 @@ exec_command_line (const char *cmdline, int len, int filter) { else { pname = parg; } - if (pl_add_file (pname, NULL, NULL) >= 0) { - if (queue) { - exitcode = 3; - } - else { - exitcode = 2; - } + if (pl_add_file (pname, NULL, NULL) < 0) { + fprintf (stderr, "failed to add file %s\n", pname); } parg += strlen (parg); parg++; } - } - if (exitcode == 2 || exitcode == 3) { - // added some files, need to redraw messagepump_push (M_PLAYLISTREFRESH, 0, 0, 0); + if (!queue) { + messagepump_push (M_PLAYSONG, 0, 0, 0); + return 2; // don't reload playlist at startup + } } - return exitcode; + return 0; } static struct sockaddr_un srv_local; @@ -313,24 +263,24 @@ server_update (void) { } else if (s2 != -1) { char str[2048]; + char sendback[1024]; int size; if ((size = recv (s2, str, 2048, 0)) >= 0) { if (size == 1 && str[0] == 0) { - GDK_THREADS_ENTER(); - gtk_widget_show (mainwin); - gtk_window_present (GTK_WINDOW (mainwin)); - GDK_THREADS_LEAVE(); + // FIXME: that should be called right after activation of gui plugin + plug_trigger_event (DB_EV_ACTIVATE, 0); } else { - int res = exec_command_line (str, size, 0); - if (res == 2) { - GDK_THREADS_ENTER(); - gtkpl_playsong (&main_playlist); - GDK_THREADS_LEAVE(); - } + server_exec_command_line (str, size, sendback, sizeof (sendback)); } } - send (s2, "", 1, 0); + if (sendback[0]) { + // send nowplaying back to client + send (s2, sendback, strlen (sendback)+1, 0); + } + else { + send (s2, "", 1, 0); + } close(s2); } return 0; @@ -347,7 +297,6 @@ player_thread (uintptr_t ctx) { messagepump_push (M_TERMINATE, 0, 0, 0); } } - plug_trigger_event (DB_EV_FRAMEUPDATE, 0); uint32_t msg; uintptr_t ctx; uint32_t p1; @@ -355,213 +304,61 @@ player_thread (uintptr_t ctx) { while (messagepump_pop(&msg, &ctx, &p1, &p2) != -1) { switch (msg) { case M_REINIT_SOUND: - { - int play = 0; - if (!palsa_ispaused () && !palsa_isstopped ()) { - play = 1; - } - - palsa_free (); - palsa_init (); - if (play) { - palsa_play (); - } - } + plug_reinit_sound (); break; case M_TERMINATE: - GDK_THREADS_ENTER(); - gtk_widget_hide (mainwin); - gtk_main_quit (); - GDK_THREADS_LEAVE(); return; case M_SONGCHANGED: - { - int from = p1; - int to = p2; - gtkpl_songchanged_wrapper (from, to); - plug_trigger_event (DB_EV_SONGCHANGED, 0); - } + plug_trigger_event_trackchange (p1, p2); break; case M_PLAYSONG: - gtkpl_playsong (&main_playlist); - if (playlist_current_ptr) { - GDK_THREADS_ENTER(); - gtkpl_redraw_pl_row (&main_playlist, pl_get_idx_of (playlist_current_ptr), playlist_current_ptr); - GDK_THREADS_LEAVE(); - } + streamer_play_current_track (); break; case M_TRACKCHANGED: - { - playItem_t *it = pl_get_for_idx (p1); - if (it) { - GDK_THREADS_ENTER(); - gtkpl_redraw_pl_row (&main_playlist, p1, it); - if (it == playlist_current_ptr) { - gtkpl_current_track_changed (it); - } - GDK_THREADS_LEAVE(); - } - } + plug_trigger_event_trackinfochanged (p1); break; case M_PLAYSONGNUM: - GDK_THREADS_ENTER(); - gtkpl_playsongnum (p1); - GDK_THREADS_LEAVE(); + p_stop (); + streamer_set_nextsong (p1, 1); break; case M_STOPSONG: - //p_stop (); streamer_set_nextsong (-2, 0); break; case M_NEXTSONG: - GDK_THREADS_ENTER(); p_stop (); pl_nextsong (1); - GDK_THREADS_LEAVE(); break; case M_PREVSONG: - GDK_THREADS_ENTER(); p_stop (); pl_prevsong (); - GDK_THREADS_LEAVE(); break; case M_PAUSESONG: - if (p_ispaused ()) { + if (p_get_state () == OUTPUT_STATE_PAUSED) { p_unpause (); + plug_trigger_event_paused (0); } else { p_pause (); + plug_trigger_event_paused (1); } - - GDK_THREADS_ENTER(); - if (playlist_current_ptr) { - gtkpl_redraw_pl_row (&main_playlist, pl_get_idx_of (playlist_current_ptr), playlist_current_ptr); - } - GDK_THREADS_LEAVE(); break; case M_PLAYRANDOM: - GDK_THREADS_ENTER(); - gtkpl_randomsong (); - GDK_THREADS_LEAVE(); - break; - case M_ADDDIR: - { - // long time processing -// float t1 = (float)clock () / CLOCKS_PER_SEC; - gtkpl_add_dir (&main_playlist, (char *)ctx); -// float t2 = (float)clock () / CLOCKS_PER_SEC; -// printf ("time: %f\n", t2-t1); - } - break; - case M_ADDDIRS: - { - // long time processing -// float t1 = (float)clock () / CLOCKS_PER_SEC; - gtkpl_add_dirs (&main_playlist, (GSList *)ctx); -// float t2 = (float)clock () / CLOCKS_PER_SEC; -// printf ("time: %f\n", t2-t1); - } - break; - case M_ADDFILES: - gtkpl_add_files (&main_playlist, (GSList *)ctx); - break; - case M_OPENFILES: p_stop (); - gtkpl_add_files (&main_playlist, (GSList *)ctx); - gtkpl_playsong (&main_playlist); - break; - case M_FMDRAGDROP: - gtkpl_add_fm_dropped_files (&main_playlist, (char *)ctx, p1, p2); + pl_randomsong (); break; case M_PLAYLISTREFRESH: - GDK_THREADS_ENTER(); - playlist_refresh (); - search_refresh (); - GDK_THREADS_LEAVE(); + plug_trigger_event_playlistchanged (); break; case M_CONFIGCHANGED: - palsa_configchanged (); + //plug_get_output ()->configchanged (); streamer_configchanged (); plug_trigger_event (DB_EV_CONFIGCHANGED, 0); break; } } usleep(50000); - update_songinfo (); - } -} - -gboolean -on_trayicon_scroll_event (GtkWidget *widget, - GdkEventScroll *event, - gpointer user_data) -{ - float vol = volume_get_db (); - if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_RIGHT) { - vol += 1; - } - else if (event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_LEFT) { - vol -= 1; - } - if (vol > 0) { - vol = 0; - } - else if (vol < -60) { - vol = -60; - } - volume_set_db (vol); - GtkWidget *volumebar = lookup_widget (mainwin, "volumebar"); - volumebar_draw (volumebar); - volumebar_expose (volumebar, 0, 0, volumebar->allocation.width, volumebar->allocation.height); - return FALSE; -} - -#if GTK_MINOR_VERSION<=14 -gboolean -on_trayicon_activate (GtkWidget *widget, - GdkEvent *event, - gpointer user_data) -{ - if (GTK_WIDGET_VISIBLE (mainwin)) { - gtk_widget_hide (mainwin); - } - else { - gtk_widget_show (mainwin); - session_restore_window_attrs ((uintptr_t)mainwin); - gtk_window_present (GTK_WINDOW (mainwin)); - } - return FALSE; -} -#endif - -gboolean -on_trayicon_button_press_event (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data) -{ - if (event->button == 1) { - if (GTK_WIDGET_VISIBLE (mainwin)) { - gtk_widget_hide (mainwin); - } - else { - gtk_widget_show (mainwin); - session_restore_window_attrs ((uintptr_t)mainwin); - gtk_window_present (GTK_WINDOW (mainwin)); - } - } - else if (event->button == 2) { - messagepump_push (M_PAUSESONG, 0, 0, 0); + plug_trigger_event (DB_EV_FRAMEUPDATE, 0); } - return FALSE; -} - -gboolean -on_trayicon_popup_menu (GtkWidget *widget, - guint button, - guint time, - gpointer user_data) -{ - gtk_menu_popup (GTK_MENU (traymenu), NULL, NULL, gtk_status_icon_position_menu, trayicon, button, time); - return FALSE; } void @@ -635,6 +432,9 @@ main (int argc, char *argv[]) { // path of this process if (argv[i][0] != '-' && realpath (argv[i], resolved)) { len = strlen (resolved); + if (len >= size) { + break; + } memcpy (p, resolved, len+1); } else { @@ -644,10 +444,15 @@ main (int argc, char *argv[]) { size -= len; } size = 2048 - size + 1; - if (exec_command_line (cmdline, size, 1) == 1) { - return 0; // if it was help request - } } + int res = client_exec_command_line (cmdline, size); + if (res == 1) { + return 0; + } + else if (res < 0) { + return res; + } + // try to connect to remote player int s, t, len; struct sockaddr_un remote; @@ -672,111 +477,93 @@ main (int argc, char *argv[]) { perror ("send"); exit (-1); } - char out[1]; - if (recv(s, out, 1, 0) == -1) { + char out[2048]; + if (recv(s, out, sizeof (out), 0) == -1) { fprintf (stderr, "failed to pass args to remote!\n"); exit (-1); } + else { + // check if that's nowplaying response + const char np[] = "nowplaying "; + const char err[] = "error "; + if (!strncmp (out, np, sizeof (np)-1)) { + const char *prn = &out[sizeof (np)-1]; + printf (prn); + } + else if (!strncmp (out, err, sizeof (err)-1)) { + const char *prn = &out[sizeof (err)-1]; + fprintf (stderr, prn); + } + else if (out[0]) { + fprintf (stderr, "got unkown response:\n%s\n", out); + } + } close (s); exit (0); } close(s); signal (SIGTERM, sigterm_handler); + + conf_load (); // required by some plugin at startup + messagepump_init (); // required to push messages while handling commandline + plug_load_all (); // required to add files to playlist from commandline + + // execute server commands in local context + int noloadpl = 0; + if (argc > 1) { + int res = server_exec_command_line (cmdline, size, NULL, 0); + // some of the server commands ran on 1st instance should terminate it + if (res == 2) { + noloadpl = 1; + } + else if (res > 0) { + exit (0); + } + else if (res < 0) { + exit (-1); + } + } + // become a server server_start (); - conf_load (); - plug_load_all (); - pl_load (defpl); + // start all subsystems + volume_set_db (conf_get_float ("playback.volume", 0)); + if (!noloadpl) { + pl_load (defpl); + } + plug_trigger_event_playlistchanged (); session_load (sessfile); - messagepump_init (); codec_init_locking (); streamer_init (); -// p_init (); - thread_start (player_thread, 0); - - g_thread_init (NULL); - add_pixmap_directory (PREFIX "/share/deadbeef/pixmaps"); - gdk_threads_init (); - gdk_threads_enter (); - gtk_set_locale (); - gtk_init (&argc, &argv); - - // system tray icon - traymenu = create_traymenu (); - GdkPixbuf *trayicon_pixbuf = create_pixbuf ("play_24.png"); - trayicon = gtk_status_icon_new_from_pixbuf (trayicon_pixbuf); - set_tray_tooltip ("DeaDBeeF"); - //gtk_status_icon_set_title (GTK_STATUS_ICON (trayicon), "DeaDBeeF"); -#if GTK_MINOR_VERSION <= 14 - g_signal_connect ((gpointer)trayicon, "activate", G_CALLBACK (on_trayicon_activate), NULL); -#else - g_signal_connect ((gpointer)trayicon, "scroll_event", G_CALLBACK (on_trayicon_scroll_event), NULL); - g_signal_connect ((gpointer)trayicon, "button_press_event", G_CALLBACK (on_trayicon_button_press_event), NULL); -#endif - g_signal_connect ((gpointer)trayicon, "popup_menu", G_CALLBACK (on_trayicon_popup_menu), NULL); - - gtkpl_init (); - mainwin = create_mainwin (); - GdkPixbuf *mainwin_icon_pixbuf; - mainwin_icon_pixbuf = create_pixbuf ("play_24.png"); - if (mainwin_icon_pixbuf) - { - gtk_window_set_icon (GTK_WINDOW (mainwin), mainwin_icon_pixbuf); - gdk_pixbuf_unref (mainwin_icon_pixbuf); - } - session_restore_window_attrs ((uintptr_t)mainwin); - volume_set_db (conf_get_float ("playback.volume", 0)); - // order and looping - const char *orderwidgets[3] = { "order_linear", "order_shuffle", "order_random" }; - const char *loopingwidgets[3] = { "loop_all", "loop_disable", "loop_single" }; - const char *w; - w = orderwidgets[conf_get_int ("playback.order", 0)]; - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE); - w = loopingwidgets[conf_get_int ("playback.loop", 0)]; - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE); - gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "scroll_follows_playback")), conf_get_int ("playlist.scroll.followplayback", 0) ? TRUE : FALSE); - - searchwin = create_searchwin (); - gtk_window_set_transient_for (GTK_WINDOW (searchwin), GTK_WINDOW (mainwin)); - extern void main_playlist_init (GtkWidget *widget); - main_playlist_init (lookup_widget (mainwin, "playlist")); - extern void search_playlist_init (GtkWidget *widget); - search_playlist_init (lookup_widget (searchwin, "searchlist")); - - progress_init (); - - if (argc > 1) { - int res = exec_command_line (cmdline, size, 0); - if (res == -1) { - server_close (); - return -1; - } - if (res == 2) { - messagepump_push (M_PLAYSONG, 0, 0, 0); - } - } + // this runs in main thread (blocks right here) + player_thread (0); - gtk_widget_show (mainwin); - gtk_main (); // save config pl_save (defpl); conf_save (); + // stop receiving messages from outside server_close (); - // at this point we can simply do exit(0), but let's clean up for debugging - gtkpl_free (&main_playlist); - gtkpl_free (&search_playlist); - gdk_threads_leave (); - messagepump_free (); + + // stop streaming and playback before unloading plugins + p_stop (); p_free (); streamer_free (); + + // plugins might still hood references to playitems, + // and query configuration in background + // so unload everything 1st before final cleanup + plug_unload_all (); + + // at this point we can simply do exit(0), but let's clean up for debugging codec_free_locking (); session_save (sessfile); pl_free (); conf_free (); - plug_unload_all (); + messagepump_free (); + fprintf (stderr, "hej-hej!\n"); return 0; } @@ -18,27 +18,16 @@ #ifndef __PLAYBACK_H #define __PLAYBACK_H -#if USE_SDL -#include "psdl.h" -#define p_init psdl_init -#define p_free psdl_free -#define p_play psdl_play -#define p_stop psdl_stop -#define p_ispaused psdl_ispaused -#define p_pause psdl_pause -#define p_unpause psdl_unpause -#define p_get_rate psdl_get_rate -#else -#include "palsa.h" -#define p_init palsa_init -#define p_free palsa_free -#define p_play palsa_play -#define p_stop palsa_stop -#define p_ispaused palsa_ispaused -#define p_pause palsa_pause -#define p_unpause palsa_unpause -#define p_get_rate palsa_get_rate -#define p_isstopped palsa_isstopped -#endif +#define p_init plug_get_output ()->init +#define p_free plug_get_output ()->free +#define p_play plug_get_output ()->play +#define p_stop plug_get_output ()->stop +#define p_pause plug_get_output ()->pause +#define p_unpause plug_get_output ()->unpause +#define p_get_rate plug_get_output ()->samplerate +#define p_get_state plug_get_output ()->state + +#define p_isstopped() (plug_get_output ()->state () == OUTPUT_STATE_STOPPED) +#define p_ispaused() (plug_get_output ()->state () == OUTPUT_STATE_PAUSED) #endif // __PLAYBACK_H @@ -35,6 +35,7 @@ #include "junklib.h" #include "vfs.h" #include "conf.h" +#include "utf8.h" // 1.0->1.1 changelog: // added sample-accurate seek positions for sub-tracks @@ -50,11 +51,15 @@ playItem_t *playlist_head[PL_MAX_ITERATORS]; playItem_t *playlist_tail[PL_MAX_ITERATORS]; +int playlist_current_row[PL_MAX_ITERATORS]; + playItem_t *playlist_current_ptr; -int pl_count = 0; -float pl_totaltime = 0; -//static int pl_order = 0; // 0 = linear, 1 = shuffle, 2 = random -//static int pl_loop_mode = 0; // 0 = loop, 1 = don't loop, 2 = loop single +static int pl_count = 0; +static float pl_totaltime = 0; + +#define PLAYQUEUE_SIZE 100 +static playItem_t *playqueue[100]; +static int playqueue_count = 0; void pl_free (void) { @@ -122,39 +127,22 @@ pl_get_value_from_cue (const char *p, int sz, char *out) { static float pl_cue_parse_time (const char *p) { - char tmp[3] = {0}; - const char *next = p; - int s; - while (*next && *next != ':') { - next++; - } - if ((next - p) != 2) { + char *endptr; + long mins = strtol(p, &endptr, 10); + if (endptr - p < 2 || *endptr != ':') { return -1; } - strncpy (tmp, p, 2); - tmp[next-p] = 0; - float mins = atoi (tmp); - next++; - p = next; - while (*next && *next != ':') { - next++; - } - if ((next - p) != 2) { + p = endptr + 1; + long sec = strtol(p, &endptr, 10); + if (endptr - p != 2 || *endptr != ':') { return -1; } - strncpy (tmp, p, 2); - float sec = atoi (tmp); - next++; - p = next; - while (*next && *next != ':') { - next++; - } - if ((next - p) != 2) { + p = endptr + 1; + long frm = strtol(p, &endptr, 10); + if (endptr - p != 2 || *endptr != '\0') { return -1; } - strncpy (tmp, p, 2); - float frm = atoi (tmp); - return mins * 60 + sec + frm / 75.f; + return mins * 60.f + sec + frm / 75.f; } static playItem_t * @@ -177,7 +165,7 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c } *p = 0; // check that indexes have valid timestamps - float f_index00 = index00[0] ? pl_cue_parse_time (index00) : 0; + //float f_index00 = index00[0] ? pl_cue_parse_time (index00) : 0; float f_index01 = index01[0] ? pl_cue_parse_time (index01) : 0; float f_pregap = pregap[0] ? pl_cue_parse_time (pregap) : 0; if (*prev) { @@ -225,12 +213,7 @@ pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, c it->decoder = decoder; it->fname = strdup (fname); it->tracknum = atoi (track); - float t = 0; - if (index01[0]) { - t = f_index01; - } - it->startsample = t * samplerate; - + it->startsample = index01[0] ? f_index01 * samplerate : 0; it->endsample = -1; // will be filled by next read, or by decoder it->filetype = ftype; after = pl_insert_item (after, it); @@ -261,7 +244,7 @@ pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t * } // skip linebreak(s) while (p - buffer < buffersize && *p < 0x20) { - *p++; + p++; } if (p-buffer > 2048) { // huge string, ignore buffer = p; @@ -474,7 +457,6 @@ pl_insert_pls (playItem_t *after, const char *fname, int *pabort, int (*cb)(play char url[1024] = ""; char title[1024] = ""; char length[20] = ""; - int nfile = 1; while (p < end) { if (p >= end) { break; @@ -622,12 +604,10 @@ pl_insert_file (playItem_t *after, const char *fname, int *pabort, int (*cb)(pla const char **exts = decoders[i]->exts; for (int e = 0; exts[e]; e++) { if (!strcasecmp (exts[e], eol)) { - playItem_t *inserted = NULL; - if ((inserted = (playItem_t *)decoders[i]->insert (DB_PLAYITEM (after), fname)) != NULL) { - if (cb) { - if (cb (inserted, user_data) < 0) { - *pabort = 1; - } + playItem_t *inserted = (playItem_t *)decoders[i]->insert (DB_PLAYITEM (after), fname); + if (inserted != NULL) { + if (cb && cb (inserted, user_data) < 0) { + *pabort = 1; } return inserted; } @@ -635,7 +615,7 @@ pl_insert_file (playItem_t *after, const char *fname, int *pabort, int (*cb)(pla } } } - fprintf (stderr, "no decoder found for %s\n", fname); + trace ("no decoder found for %s\n", fname); return NULL; } @@ -667,10 +647,8 @@ pl_insert_dir (playItem_t *after, const char *dirname, int *pabort, int (*cb)(pl // no hidden files if (namelist[i]->d_name[0] != '.') { - char fullname[1024]; - strcpy (fullname, dirname); - strncat (fullname, "/", 1024); - strncat (fullname, namelist[i]->d_name, 1024); + char fullname[PATH_MAX]; + snprintf (fullname, sizeof (fullname), "%s/%s", dirname, namelist[i]->d_name); playItem_t *inserted = pl_insert_dir (after, fullname, pabort, cb, user_data); if (!inserted) { inserted = pl_insert_file (after, fullname, pabort, cb, user_data); @@ -716,6 +694,7 @@ pl_remove (playItem_t *it) { if (playlist_current_ptr == it) { playlist_current_ptr = NULL; } + pl_playqueue_remove (it); // remove from linear list if (it->prev[PL_MAIN]) { @@ -760,16 +739,21 @@ pl_getselcount (void) { } playItem_t * -pl_get_for_idx (int idx) { - playItem_t *it = playlist_head[PL_MAIN]; +pl_get_for_idx_and_iter (int idx, int iter) { + playItem_t *it = playlist_head[iter]; while (idx--) { if (!it) return NULL; - it = it->next[PL_MAIN]; + it = it->next[iter]; } return it; } +playItem_t * +pl_get_for_idx (int idx) { + return pl_get_for_idx_and_iter (idx, PL_MAIN); +} + int pl_get_idx_of (playItem_t *it) { playItem_t *c = playlist_head[PL_MAIN]; @@ -784,19 +768,6 @@ pl_get_idx_of (playItem_t *it) { return idx; } -int -pl_append_item (playItem_t *it) { - if (!playlist_tail[PL_MAIN]) { - playlist_tail[PL_MAIN] = playlist_head[PL_MAIN] = it; - } - else { - playlist_tail[PL_MAIN]->next[PL_MAIN] = it; - it->prev[PL_MAIN] = playlist_tail[PL_MAIN]; - playlist_tail[PL_MAIN] = it; - } - pl_count++; -} - playItem_t * pl_insert_item (playItem_t *after, playItem_t *it) { if (!after) { @@ -901,13 +872,14 @@ pl_item_free (playItem_t *it) { int pl_prevsong (void) { + pl_playqueue_clear (); if (!playlist_head[PL_MAIN]) { streamer_set_nextsong (-2, 1); return 0; } int pl_order = conf_get_int ("playback.order", 0); int pl_loop_mode = conf_get_int ("playback.loop", 0); - if (pl_order == 1) { // shuffle + if (pl_order == PLAYBACK_ORDER_SHUFFLE) { // shuffle if (!playlist_current_ptr) { return pl_nextsong (1); } @@ -917,7 +889,6 @@ pl_prevsong (void) { int rating = playlist_current_ptr->shufflerating; playItem_t *pmax = NULL; // played maximum playItem_t *amax = NULL; // absolute maximum - playItem_t *i = NULL; for (playItem_t *i = playlist_head[PL_MAIN]; i; i = i->next[PL_MAIN]) { if (i != playlist_current_ptr && i->played && (!amax || i->shufflerating > amax->shufflerating)) { amax = i; @@ -932,7 +903,7 @@ pl_prevsong (void) { playItem_t *it = pmax; if (!it) { // that means 1st in playlist, take amax - if (pl_loop_mode == 0) { + if (pl_loop_mode == PLAYBACK_MODE_LOOP_ALL) { if (!amax) { pl_reshuffle (NULL, &amax); } @@ -948,13 +919,13 @@ pl_prevsong (void) { return 0; } } - else if (pl_order == 0) { // linear + else if (pl_order == PLAYBACK_ORDER_LINEAR) { // linear playItem_t *it = NULL; if (playlist_current_ptr) { it = playlist_current_ptr->prev[PL_MAIN]; } if (!it) { - if (pl_loop_mode == 0) { + if (pl_loop_mode == PLAYBACK_MODE_LOOP_ALL) { it = playlist_tail[PL_MAIN]; } } @@ -965,7 +936,7 @@ pl_prevsong (void) { streamer_set_nextsong (r, 1); return 0; } - else if (pl_order == 2) { // random + else if (pl_order == PLAYBACK_ORDER_RANDOM) { // random pl_randomsong (); } return -1; @@ -973,6 +944,14 @@ pl_prevsong (void) { int pl_nextsong (int reason) { + if (playqueue_count > 0) { + playItem_t *it = playqueue[0]; + pl_playqueue_pop (); + int r = pl_get_idx_of (it); + streamer_set_nextsong (r, 1); + return 0; + } + playItem_t *curr = streamer_get_streaming_track (); if (!playlist_head[PL_MAIN]) { streamer_set_nextsong (-2, 1); @@ -980,11 +959,10 @@ pl_nextsong (int reason) { } int pl_order = conf_get_int ("playback.order", 0); int pl_loop_mode = conf_get_int ("playback.loop", 0); - if (pl_order == 1) { // shuffle + if (pl_order == PLAYBACK_ORDER_SHUFFLE) { // shuffle if (!curr) { // find minimal notplayed playItem_t *pmin = NULL; // notplayed minimum - playItem_t *i = NULL; for (playItem_t *i = playlist_head[PL_MAIN]; i; i = i->next[PL_MAIN]) { if (i->played) { continue; @@ -996,7 +974,7 @@ pl_nextsong (int reason) { playItem_t *it = pmin; if (!it) { // all songs played, reshuffle and try again - if (pl_loop_mode == 0) { // loop + if (pl_loop_mode == PLAYBACK_MODE_LOOP_ALL) { // loop pl_reshuffle (&it, NULL); } } @@ -1009,7 +987,7 @@ pl_nextsong (int reason) { } else { trace ("pl_next_song: reason=%d, loop=%d\n", reason, pl_loop_mode); - if (reason == 0 && pl_loop_mode == 2) { // song finished, loop mode is "loop 1 track" + if (reason == 0 && pl_loop_mode == PLAYBACK_MODE_LOOP_SINGLE) { // song finished, loop mode is "loop 1 track" int r = pl_get_idx_of (curr); streamer_set_nextsong (r, 1); return 0; @@ -1017,7 +995,6 @@ pl_nextsong (int reason) { // find minimal notplayed above current int rating = curr->shufflerating; playItem_t *pmin = NULL; // notplayed minimum - playItem_t *i = NULL; for (playItem_t *i = playlist_head[PL_MAIN]; i; i = i->next[PL_MAIN]) { if (i->played || i->shufflerating < rating) { continue; @@ -1030,7 +1007,7 @@ pl_nextsong (int reason) { if (!it) { trace ("all songs played! reshuffle\n"); // all songs played, reshuffle and try again - if (pl_loop_mode == 0) { // loop + if (pl_loop_mode == PLAYBACK_MODE_LOOP_ALL) { // loop pl_reshuffle (&it, NULL); } } @@ -1042,10 +1019,10 @@ pl_nextsong (int reason) { return 0; } } - else if (pl_order == 0) { // linear + else if (pl_order == PLAYBACK_ORDER_LINEAR) { // linear playItem_t *it = NULL; if (curr) { - if (reason == 0 && pl_loop_mode == 2) { // loop same track + if (reason == 0 && pl_loop_mode == PLAYBACK_MODE_LOOP_SINGLE) { // loop same track int r = pl_get_idx_of (curr); streamer_set_nextsong (r, 1); return 0; @@ -1053,7 +1030,8 @@ pl_nextsong (int reason) { it = curr->next[PL_MAIN]; } if (!it) { - if (pl_loop_mode == 0) { + trace ("pl_nextsong: was last track\n"); + if (pl_loop_mode == PLAYBACK_MODE_LOOP_ALL) { it = playlist_head[PL_MAIN]; } else { @@ -1068,8 +1046,8 @@ pl_nextsong (int reason) { streamer_set_nextsong (r, 1); return 0; } - else if (pl_order == 2) { // random - if (reason == 0 && pl_loop_mode == 2 && curr) { + else if (pl_order == PLAYBACK_ORDER_RANDOM) { // random + if (reason == 0 && pl_loop_mode == PLAYBACK_MODE_LOOP_SINGLE && curr) { int r = pl_get_idx_of (curr); streamer_set_nextsong (r, 1); return 0; @@ -1103,7 +1081,6 @@ pl_add_meta (playItem_t *it, const char *key, const char *value) { char str[256]; if (!value || !*value) { if (!strcasecmp (key, "title")) { - int len = 256; // cut filename without path and extension const char *pext = it->fname + strlen (it->fname) - 1; while (pext >= it->fname && *pext != '.') { @@ -1460,6 +1437,7 @@ pl_load (const char *fname) { "track", "band", "cuesheet", + "copyright", NULL }; @@ -1569,7 +1547,123 @@ pl_get_item_duration (playItem_t *it) { } int -pl_format_title (playItem_t *it, char *s, int size, const char *fmt) { +pl_format_item_queue (playItem_t *it, char *s, int size) { + *s = 0; + if (!playqueue_count) { + return 0; + } + int init = 1; + int initsize = size; + int len; + for (int i = 0; i < playqueue_count; i++) { + if (size <= 0) { + break; + } + if (playqueue[i] == it) { + if (init) { + init = 0; + s[0] = '('; + s++; + size--; + len = snprintf (s, size, "%d", i+1); + } + else { + len = snprintf (s, size, ",%d", i+1); + } + s += len; + size -= len; + } + } + if (size != initsize && size > 0) { + len = snprintf (s, size, ")"); + s += len; + size -= len; + } + return initsize-size; +} + +static const char * +pl_get_meta_cached (playItem_t *it, const char *meta, const char *ret, const char *def) { + if (!ret) { + ret = pl_find_meta (it, meta); + if (!ret) { + ret = def; + } + } + return ret; +} + +static const char * +pl_format_duration (playItem_t *it, const char *ret, char *dur, int size) { + if (ret) { + return ret; + } + if (it->_duration >= 0) { + int hourdur = it->_duration / (60 * 60); + int mindur = (it->_duration - hourdur * 60 * 60) / 60; + int secdur = it->_duration - hourdur*60*60 - mindur * 60; + + if (hourdur) { + snprintf (dur, size, "%d:%02d:%02d", hourdur, mindur, secdur); + } + else { + snprintf (dur, size, "%d:%02d", mindur, secdur); + } + } + else { + strcpy (dur, "-:--"); + } + return dur; +} + +int +pl_format_title (playItem_t *it, char *s, int size, int id, const char *fmt) { + char dur[50]; + const char *artist = NULL; + const char *album = NULL; + const char *track = NULL; + const char *title = NULL; + const char *duration = NULL; + + if (id != -1) { + const char *text = NULL; + switch (id) { + case DB_COLUMN_PLAYING: + return pl_format_item_queue (it, s, size); + case DB_COLUMN_ARTIST_ALBUM: + { + char artistalbum[1024]; + artist = pl_get_meta_cached (it, "artist", artist, "?"); + album = pl_get_meta_cached (it, "album", album, "?"); + snprintf (artistalbum, sizeof (artistalbum), "%s - %s", artist, album); + text = artistalbum; + } + break; + case DB_COLUMN_ARTIST: + text = (artist = pl_get_meta_cached (it, "artist", artist, "?")); + break; + case DB_COLUMN_ALBUM: + text = (album = pl_get_meta_cached (it, "album", artist, "?")); + break; + case DB_COLUMN_TITLE: + text = (title = pl_get_meta_cached (it, "title", artist, "?")); + break; + case DB_COLUMN_DURATION: + text = (duration = pl_format_duration (it, duration, dur, sizeof (dur))); + break; + case DB_COLUMN_TRACK: + text = (track = pl_get_meta_cached (it, "track", track, "")); + break; + } + if (text) { + strncpy (s, text, size); + return strlen (s); + } + else { + s[0] = 0; + } + return 0; + } int n = size-1; while (*fmt && n) { if (*fmt != '%') { @@ -1583,35 +1677,19 @@ pl_format_title (playItem_t *it, char *s, int size, const char *fmt) { break; } else if (*fmt == 'a') { - meta = "artist"; + meta = (artist = pl_get_meta_cached (it, "artist", artist, "?")); } else if (*fmt == 't') { - meta = "title"; + meta = (title = pl_get_meta_cached (it, "title", title, "?")); } else if (*fmt == 'b') { - meta = "album"; + meta = (album = pl_get_meta_cached (it, "album", album, "?")); } else if (*fmt == 'n') { - meta = "track"; + meta = (track = pl_get_meta_cached (it, "track", track, "")); } else if (*fmt == 'l') { - char dur[50]; - if (it->_duration >= 0) { - int hourdur = it->_duration / (60 * 60); - int mindur = (it->_duration - hourdur * 60 * 60) / 60; - int secdur = it->_duration - hourdur*60*60 - mindur * 60; - - if (hourdur) { - snprintf (dur, sizeof (dur), "%d:%02d:%02d", hourdur, mindur, secdur); - } - else { - snprintf (dur, sizeof (dur), "%d:%02d", mindur, secdur); - } - } - else { - strcpy (dur, "-:--"); - } - const char *value = dur; + const char *value = (duration = pl_format_duration (it, duration, dur, sizeof (dur))); while (n > 0 && *value) { *s++ = *value++; n--; @@ -1623,10 +1701,7 @@ pl_format_title (playItem_t *it, char *s, int size, const char *fmt) { } if (meta) { - const char *value = pl_find_meta (it, meta); - if (!value) { - value = "?"; - } + const char *value = meta; while (n > 0 && *value) { *s++ = *value++; n--; @@ -1641,6 +1716,259 @@ pl_format_title (playItem_t *it, char *s, int size, const char *fmt) { } void -pl_sort (const char *meta) { +pl_sort (int iter, int id, const char *format, int ascending) { + int sorted = 0; + do { + sorted = 1; + playItem_t *it; + for (it = playlist_head[iter]; it; it = it->next[iter]) { + playItem_t *next = it->next[iter]; + if (!next) { + break; + } + char title1[1024]; + char title2[1024]; + pl_format_title (it, title1, sizeof (title1), id, format); + pl_format_title (next, title2, sizeof (title2), id, format); +// const char *meta1 = pl_find_meta (it, meta); +// const char *meta2 = pl_find_meta (next, meta); + int cmp = ascending ? strcmp (title1, title2) < 0 : strcmp (title1, title2) > 0; + if (cmp) { +// printf ("%p %p swapping %s and %s\n", it, next, meta1, meta2); + sorted = 0; + // swap them + if (it->prev[iter]) { + it->prev[iter]->next[iter] = next; +// printf ("it->prev->next = it->next\n"); + } + else { + playlist_head[iter] = next; + next->prev[iter] = NULL; +// printf ("head = it->next\n"); + } + if (next->next[iter]) { + next->next[iter]->prev[iter] = it; +// printf ("it->next->next->prev = it\n"); + } + else { + playlist_tail[iter] = it; + it->next[iter] = NULL; +// printf ("tail = it\n"); + } + playItem_t *it_prev = it->prev[iter]; + it->next[iter] = next->next[iter]; + it->prev[iter] = next; + next->next[iter] = it; + next->prev[iter] = it_prev; + it = next; + } + } + } while (!sorted); +} + +void +pl_reset_cursor (void) { + int i; + for (i = 0; i < PL_MAX_ITERATORS; i++) { + playlist_current_row[i] = -1; + } +} + +float +pl_get_totaltime (void) { + return pl_totaltime; +} + +playItem_t * +pl_getcurrent (void) { + return playlist_current_ptr; +} + +void +pl_set_selected (playItem_t *it, int sel) { + it->selected = sel; +} + +int +pl_is_selected (playItem_t *it) { + return it->selected; +} + +playItem_t * +pl_get_first (int iter) { + return playlist_head[iter]; +} + +playItem_t * +pl_get_last (int iter) { + return playlist_tail[iter]; +} + +playItem_t * +pl_get_next (playItem_t *it, int iter) { + return it ? it->next[iter] : NULL; +} + +playItem_t * +pl_get_prev (playItem_t *it, int iter) { + return it ? it->prev[iter] : NULL; +} + +int +pl_get_cursor (int iter) { + return playlist_current_row[iter]; } +void +pl_set_cursor (int iter, int cursor) { + playlist_current_row[iter] = cursor; +} + +// this function must move items in playlist +// list of items is indexes[count] +// drop_before is insertion point +void +pl_move_items (int iter, playItem_t *drop_before, uint32_t *indexes, int count) { + // unlink items from playlist, and link together + playItem_t *head = NULL; + playItem_t *tail = NULL; + int processed = 0; + int idx = 0; + playItem_t *next = NULL; + for (playItem_t *it = playlist_head[iter]; it && processed < count; it = next, idx++) { + next = it->next[iter]; + if (idx == indexes[processed]) { + if (it->prev[iter]) { + it->prev[iter]->next[iter] = it->next[iter]; + } + else { + playlist_head[iter] = it->next[iter]; + } + if (it->next[iter]) { + it->next[iter]->prev[iter] = it->prev[iter]; + } + else { + playlist_tail[iter] = it->prev[iter]; + } + if (tail) { + tail->next[iter] = it; + it->prev[iter] = tail; + tail = it; + } + else { + head = tail = it; + it->prev[iter] = it->next[iter] = NULL; + } + processed++; + } + } + // find insertion point + playItem_t *drop_after = NULL; + if (drop_before) { + drop_after = drop_before->prev[iter]; + } + else { + drop_after = playlist_tail[iter]; + } + // insert in between + head->prev[iter] = drop_after; + if (drop_after) { + drop_after->next[iter] = head; + } + else { + playlist_head[iter] = head; + } + tail->next[iter] = drop_before; + if (drop_before) { + drop_before->prev[iter] = tail; + } + else { + playlist_tail[iter] = tail; + } +} + +int +pl_process_search (const char *text) { + playlist_head[PL_SEARCH] = NULL; + playlist_tail[PL_SEARCH] = NULL; + int search_count = 0; + if (*text) { + for (playItem_t *it = playlist_head[PL_MAIN]; it; it = it->next[PL_MAIN]) { + it->selected = 0; + for (metaInfo_t *m = it->meta; m; m = m->next) { +// if (strcasestr (m->value, text)) { + if (utfcasestr (m->value, text)) { + // add to list + it->next[PL_SEARCH] = NULL; + if (playlist_tail[PL_SEARCH]) { + playlist_tail[PL_SEARCH]->next[PL_SEARCH] = it; + playlist_tail[PL_SEARCH] = it; + } + else { + playlist_head[PL_SEARCH] = playlist_tail[PL_SEARCH] = it; + } + it->selected = 1; + search_count++; + break; + } + } + } + } + return search_count; +} + +int +pl_playqueue_push (playItem_t *it) { + if (playqueue_count == PLAYQUEUE_SIZE) { + trace ("playqueue is full\n"); + return -1; + } + playqueue[playqueue_count++] = it; + return 0; +} + +void +pl_playqueue_clear (void) { + playqueue_count = 0; +} + +void +pl_playqueue_pop (void) { + if (!playqueue_count) { + return; + } + if (playqueue_count == 1) { + playqueue_count = 0; + return; + } + memmove (&playqueue[0], &playqueue[1], (playqueue_count-1) * sizeof (playItem_t*)); + playqueue_count--; +} + +void +pl_playqueue_remove (playItem_t *it) { + for (;;) { + int i; + for (i = 0; i < playqueue_count; i++) { + if (playqueue[i] == it) { + if (i < playqueue_count-1) { + memmove (&playqueue[i], &playqueue[i+1], (playqueue_count-i) * sizeof (playItem_t*)); + } + playqueue_count--; + break; + } + } + if (i == playqueue_count) { + break; + } + } +} +int +pl_playqueue_test (playItem_t *it) { + for (int i = 0; i < playqueue_count; i++) { + if (playqueue[i] == it) { + return i; + } + } + return -1; +} @@ -28,8 +28,6 @@ typedef struct metaInfo_s { } metaInfo_t; #define PL_MAX_ITERATORS 2 -#define PL_MAIN 0 -#define PL_SEARCH 1 typedef struct playItem_s { char *fname; // full pathname @@ -57,11 +55,9 @@ typedef struct playItem_s { extern playItem_t *playlist_head[PL_MAX_ITERATORS]; // head of linked list extern playItem_t *playlist_tail[PL_MAX_ITERATORS]; // tail of linked list +extern int playlist_current_row[PL_MAX_ITERATORS]; // current row (cursor) extern playItem_t *playlist_current_ptr; // pointer to a real current playlist item (or NULL) -extern int pl_count; -extern float pl_totaltime; - int pl_add_dir (const char *dirname, int (*cb)(playItem_t *it, void *data), void *user_data); @@ -78,9 +74,6 @@ playItem_t * pl_insert_item (playItem_t *after, playItem_t *it); int -pl_append_item (playItem_t *it); - -int pl_remove (playItem_t *i); playItem_t * @@ -104,6 +97,9 @@ pl_getselcount (void); playItem_t * pl_get_for_idx (int idx); +playItem_t * +pl_get_for_idx_and_iter (int idx, int iter); + int pl_get_idx_of (playItem_t *it); @@ -168,6 +164,64 @@ pl_get_item_duration (playItem_t *it); // returns number of characters printed, not including trailing 0 // [a]rtist, [t]itle, al[b]um, [l]ength, track[n]umber int -pl_format_title (playItem_t *it, char *s, int size, const char *fmt); +pl_format_title (playItem_t *it, char *s, int size, int id, const char *fmt); + +void +pl_reset_cursor (void); + +float +pl_get_totaltime (void); + +playItem_t * +pl_getcurrent (void); + +void +pl_set_selected (playItem_t *it, int sel); + +int +pl_is_selected (playItem_t *it); + +playItem_t * +pl_get_first (int iter); + +playItem_t * +pl_get_last (int iter); + +playItem_t * +pl_get_next (playItem_t *it, int iter); + +playItem_t * +pl_get_prev (playItem_t *it, int iter); + +int +pl_get_cursor (int iter); + +void +pl_set_cursor (int iter, int cursor); + +void +pl_move_items (int iter, playItem_t *drop_before, uint32_t *indexes, int count); + +int +pl_process_search (const char *text); + +void +pl_sort (int iter, int id, const char *format, int ascending); + +// playqueue support functions +int +pl_playqueue_push (playItem_t *it); + +void +pl_playqueue_clear (void); + +void +pl_playqueue_pop (void); + +void +pl_playqueue_remove (playItem_t *it); + +int +pl_playqueue_test (playItem_t *it); #endif // __PLAYLIST_H @@ -22,7 +22,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <gtk/gtk.h> #ifdef HAVE_CONFIG_H # include <config.h> #endif @@ -30,7 +29,6 @@ #include "md5/md5.h" #include "messagepump.h" #include "threading.h" -#include "progress.h" #include "playlist.h" #include "volume.h" #include "streamer.h" @@ -53,6 +51,7 @@ static DB_functions_t deadbeef_api = { .ev_unsubscribe = plug_ev_unsubscribe, .md5 = plug_md5, .md5_to_str = plug_md5_to_str, + .get_output = plug_get_output, .playback_next = plug_playback_next, .playback_prev = plug_playback_prev, .playback_pause = plug_playback_pause, @@ -61,8 +60,17 @@ static DB_functions_t deadbeef_api = { .playback_random = plug_playback_random, .playback_get_pos = plug_playback_get_pos, .playback_set_pos = plug_playback_set_pos, - .playback_get_samplerate = p_get_rate, - .playback_update_bitrate = streamer_update_bitrate, + // streamer access + .streamer_get_playing_track = (DB_playItem_t *(*) (void))streamer_get_playing_track, + .streamer_get_streaming_track = (DB_playItem_t *(*) (void))streamer_get_streaming_track, + .streamer_get_playpos = streamer_get_playpos, + .streamer_seek = streamer_set_seek, + .streamer_ok_to_read = streamer_ok_to_read, + .streamer_reset = streamer_reset, + .streamer_read = streamer_read, + .streamer_set_bitrate = streamer_set_bitrate, + .streamer_get_apx_bitrate = streamer_get_apx_bitrate, + // process control .get_config_dir = plug_get_config_dir, .quit = plug_quit, // threading @@ -81,10 +89,39 @@ static DB_functions_t deadbeef_api = { .pl_item_alloc = (DB_playItem_t* (*)(void))pl_item_alloc, .pl_item_free = (void (*)(DB_playItem_t *))pl_item_free, .pl_item_copy = (void (*)(DB_playItem_t *, DB_playItem_t *))pl_item_copy, + .pl_add_file = (int (*) (const char *, int (*cb)(DB_playItem_t *it, void *data), void *))pl_add_file, + .pl_add_dir = (int (*) (const char *dirname, int (*cb)(DB_playItem_t *it, void *data), void *user_data))pl_add_dir, .pl_insert_item = (DB_playItem_t *(*) (DB_playItem_t *after, DB_playItem_t *it))pl_insert_item, + .pl_insert_dir = (DB_playItem_t *(*) (DB_playItem_t *after, const char *dirname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data))pl_insert_dir, + .pl_insert_file = (DB_playItem_t *(*) (DB_playItem_t *after, const char *fname, int *pabort, int (*cb)(DB_playItem_t *it, void *data), void *user_data))pl_insert_file, .pl_get_idx_of = (int (*) (DB_playItem_t *it))pl_get_idx_of, + .pl_get_for_idx = (DB_playItem_t * (*)(int))pl_get_for_idx, + .pl_get_for_idx_and_iter = (DB_playItem_t * (*) (int idx, int iter))pl_get_for_idx_and_iter, .pl_set_item_duration = (void (*) (DB_playItem_t *it, float duration))pl_set_item_duration, .pl_get_item_duration = (float (*) (DB_playItem_t *it))pl_get_item_duration, + .pl_sort = pl_sort, + .pl_get_totaltime = pl_get_totaltime, + .pl_getcount = pl_getcount, + .pl_getcurrent = (DB_playItem_t *(*)(void))pl_getcurrent, + .pl_delete_selected = pl_delete_selected, + .pl_set_cursor = pl_set_cursor, + .pl_get_cursor = pl_get_cursor, + .pl_set_selected = (void (*) (DB_playItem_t *, int))pl_set_selected, + .pl_is_selected = (int (*) (DB_playItem_t *))pl_is_selected, + .pl_free = pl_free, + .pl_load = pl_load, + .pl_save = pl_save, + .pl_select_all = pl_select_all, + .pl_crop_selected = pl_crop_selected, + .pl_getselcount = pl_getselcount, + .pl_get_first = (DB_playItem_t *(*) (int))pl_get_first, + .pl_get_last = (DB_playItem_t *(*) (int))pl_get_last, + .pl_get_next = (DB_playItem_t *(*) (DB_playItem_t *, int))pl_get_next, + .pl_get_prev = (DB_playItem_t *(*) (DB_playItem_t *, int))pl_get_prev, + .pl_format_title = (int (*) (DB_playItem_t *it, char *s, int size, int id, const char *fmt))pl_format_title, + .pl_format_item_display_name = (void (*) (DB_playItem_t *it, char *str, int len))pl_format_item_display_name, + .pl_move_items = (void (*) (int iter, DB_playItem_t *drop_before, uint32_t *indexes, int count))pl_move_items, + .pl_process_search = pl_process_search, // metainfo .pl_add_meta = (void (*) (DB_playItem_t *, const char *, const char *))pl_add_meta, .pl_find_meta = (const char *(*) (DB_playItem_t *, const char *))pl_find_meta, @@ -92,11 +129,17 @@ static DB_functions_t deadbeef_api = { // cuesheet support .pl_insert_cue_from_buffer = (DB_playItem_t *(*) (DB_playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype, int numsamples, int samplerate))pl_insert_cue_from_buffer, .pl_insert_cue = (DB_playItem_t *(*)(DB_playItem_t *, const char *, struct DB_decoder_s *, const char *ftype, int numsamples, int samplerate))pl_insert_cue, + .pl_playqueue_push = (int (*) (DB_playItem_t *))pl_playqueue_push, + .pl_playqueue_clear = pl_playqueue_clear, + .pl_playqueue_pop = pl_playqueue_pop, + .pl_playqueue_remove = (void (*) (DB_playItem_t *))pl_playqueue_remove, + .pl_playqueue_test = (int (*) (DB_playItem_t *))pl_playqueue_test, // volume control .volume_set_db = plug_volume_set_db, .volume_get_db = volume_get_db, .volume_set_amp = plug_volume_set_amp, .volume_get_amp = volume_get_amp, + .volume_get_min_db = volume_get_min_db, // junk reading .junk_read_id3v1 = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_read_id3v1, .junk_read_id3v2 = (int (*)(DB_playItem_t *it, DB_FILE *fp))junk_read_id3v2, @@ -121,9 +164,14 @@ static DB_functions_t deadbeef_api = { .conf_get_float = conf_get_float, .conf_get_int = conf_get_int, .conf_set_str = conf_set_str, + .conf_set_int = conf_set_int, .conf_find = conf_find, - .gui_lock = plug_gui_lock, - .gui_unlock = plug_gui_unlock, + .conf_remove_items = conf_remove_items, + // plugin communication + .plug_get_decoder_list = plug_get_decoder_list, + .plug_get_output_list = plug_get_output_list, + .plug_get_list = plug_get_list, + .plug_activate = plug_activate, }; DB_functions_t *deadbeef = &deadbeef_api; @@ -134,18 +182,15 @@ plug_get_config_dir (void) { } void -volumebar_notify_changed (void); - -void plug_volume_set_db (float db) { volume_set_db (db); - volumebar_notify_changed (); + plug_trigger_event_volumechanged (); } void plug_volume_set_amp (float amp) { volume_set_amp (amp); - volumebar_notify_changed (); + plug_trigger_event_volumechanged (); } #define MAX_PLUGINS 100 @@ -157,15 +202,9 @@ DB_decoder_t *g_decoder_plugins[MAX_DECODER_PLUGINS+1]; #define MAX_VFS_PLUGINS 10 DB_vfs_t *g_vfs_plugins[MAX_VFS_PLUGINS+1]; -void -plug_gui_lock (void) { - gdk_threads_enter (); -} - -void -plug_gui_unlock (void) { - gdk_threads_leave (); -} +#define MAX_OUTPUT_PLUGINS 10 +DB_output_t *g_output_plugins[MAX_OUTPUT_PLUGINS+1]; +DB_output_t *output_plugin = NULL; void plug_md5 (uint8_t sig[16], const char *in, int len) { @@ -277,43 +316,90 @@ plug_playback_set_pos (float pos) { void plug_quit (void) { - progress_abort (); + // FIXME progress_abort (); messagepump_push (M_TERMINATE, 0, 0, 0); } /////// non-api functions (plugin support) void -plug_trigger_event (int ev, uintptr_t param) { +plug_event_call (DB_event_t *ev) { + ev->time = time (NULL); +// printf ("plug_event_call enter %d\n", ev->event); mutex_lock (mutex); + for (int i = 0; i < MAX_HANDLERS; i++) { + if (handlers[ev->event][i].plugin && !handlers[ev->event][i].plugin->inactive) { + handlers[ev->event][i].callback (ev, handlers[ev->event][i].data); + } + } + mutex_unlock (mutex); +// printf ("plug_event_call leave %d\n", ev->event); +} + +void +plug_trigger_event (int ev, uintptr_t param) { DB_event_t *event; switch (ev) { case DB_EV_SONGSTARTED: case DB_EV_SONGFINISHED: { - DB_event_song_t *pev = malloc (sizeof (DB_event_song_t)); - pev->song = DB_PLAYITEM (&str_playing_song); + DB_event_track_t *pev = alloca (sizeof (DB_event_track_t)); + pev->index = -1; + pev->track = DB_PLAYITEM (&str_playing_song); event = DB_EVENT (pev); } break; case DB_EV_TRACKDELETED: { - DB_event_song_t *pev = malloc (sizeof (DB_event_song_t)); - pev->song = DB_PLAYITEM (param); + DB_event_track_t *pev = alloca (sizeof (DB_event_track_t)); + pev->index = -1; // FIXME + pev->track = DB_PLAYITEM (param); event = DB_EVENT (pev); } break; default: - event = malloc (sizeof (DB_event_t)); + event = alloca (sizeof (DB_event_t)); } event->event = ev; - event->time = (double)clock () / CLOCKS_PER_SEC; - for (int i = 0; i < MAX_HANDLERS; i++) { - if (handlers[ev][i].plugin && !handlers[ev][i].plugin->inactive) { - handlers[ev][i].callback (event, handlers[ev][i].data); - } - } - free (event); - mutex_unlock (mutex); + plug_event_call (event); +} + +void +plug_trigger_event_trackchange (int from, int to) { + DB_event_trackchange_t event; + event.ev.event = DB_EV_SONGCHANGED; + event.from = from; + event.to = to; + plug_event_call (DB_EVENT (&event)); +} +void +plug_trigger_event_trackinfochanged (int trk) { + DB_event_track_t event; + event.ev.event = DB_EV_TRACKINFOCHANGED; + event.index = trk; + event.track = DB_PLAYITEM (pl_get_for_idx (trk)); + plug_event_call (DB_EVENT (&event)); +} + +void +plug_trigger_event_paused (int paused) { + DB_event_state_t event; + event.ev.event = DB_EV_PAUSED; + event.state = paused; + plug_event_call (DB_EVENT (&event)); +} + +void +plug_trigger_event_playlistchanged (void) { + DB_event_t event; + event.event = DB_EV_PLAYLISTCHANGED; + plug_event_call (DB_EVENT (&event)); +} + +void +plug_trigger_event_volumechanged (void) { + DB_event_t event; + event.event = DB_EV_VOLUMECHANGED; + plug_event_call (DB_EVENT (&event)); } int @@ -437,18 +523,16 @@ plug_load_all (void) { fprintf (stderr, "plugin %s is blacklisted in config file\n", d_name); break; } - char fullname[1024]; - strcpy (fullname, plugdir); - strncat (fullname, "/", 1024); - strncat (fullname, d_name, 1024); - printf ("loading plugin %s\n", d_name); + char fullname[PATH_MAX]; + snprintf (fullname, PATH_MAX, "%s/%s", plugdir, d_name); + fprintf (stderr, "loading plugin %s\n", d_name); void *handle = dlopen (fullname, RTLD_NOW); if (!handle) { fprintf (stderr, "dlopen error: %s\n", dlerror ()); break; } d_name[l-3] = 0; - printf ("module name is %s\n", d_name); + fprintf (stderr, "module name is %s\n", d_name); strcat (d_name, "_load"); DB_plugin_t *(*plug_load)(DB_functions_t *api) = dlsym (handle, d_name); if (!plug_load) { @@ -481,6 +565,7 @@ plug_load_all (void) { int numplugins = 0; int numdecoders = 0; int numvfs = 0; + int numoutput = 0; for (plugin_t *plug = plugins; plug; plug = plug->next) { g_plugins[numplugins++] = plug->plugin; if (plug->plugin->type == DB_PLUGIN_DECODER) { @@ -497,11 +582,25 @@ plug_load_all (void) { } g_vfs_plugins[numvfs++] = (DB_vfs_t *)plug->plugin; } + else if (plug->plugin->type == DB_PLUGIN_OUTPUT) { + fprintf (stderr, "found output plugin %s\n", plug->plugin->name); + if (numvfs >= MAX_OUTPUT_PLUGINS) { + break; + } + g_output_plugins[numoutput++] = (DB_output_t *)plug->plugin; + } } // fprintf (stderr, "numplugins: %d, numdecoders: %d, numvfs: %d\n", numplugins, numdecoders, numvfs); g_plugins[numplugins] = NULL; g_decoder_plugins[numdecoders] = NULL; g_vfs_plugins[numvfs] = NULL; + g_output_plugins[numoutput] = NULL; + + // select output plugin + if (plug_select_output () < 0) { + fprintf (stderr, "failed to find output plugin!\n"); + exit (-1); + } } void @@ -509,7 +608,9 @@ plug_unload_all (void) { while (plugins) { plugin_t *next = plugins->next; if (plugins->plugin->stop) { + fprintf (stderr, "stopping %s...", plugins->plugin->name); plugins->plugin->stop (); + fprintf (stderr, " [OK]\n"); } if (plugins->handle) { dlclose (plugins->handle); @@ -517,6 +618,7 @@ plug_unload_all (void) { plugins = next; } mutex_free (mutex); + fprintf (stderr, "all plugins had been unloaded\n"); } struct DB_decoder_s ** @@ -524,6 +626,11 @@ plug_get_decoder_list (void) { return g_decoder_plugins; } +struct DB_output_s ** +plug_get_output_list (void) { + return g_output_plugins; +} + struct DB_vfs_s ** plug_get_vfs_list (void) { return g_vfs_plugins; @@ -533,3 +640,92 @@ struct DB_plugin_s ** plug_get_list (void) { return g_plugins; } + +int +plug_activate (DB_plugin_t *plug, int activate) { + if (plug->inactive && !activate) { + return -1; + } + if (!plug->inactive && activate) { + return -1; + } + if (activate) { + if (plug->start) { + if (!plug->start ()) { + plug->inactive = 0; + } + else { + fprintf (stderr, "failed to start plugin %s\n", plug->name); + return -1; + } + return 0; + } + else { + return -1; + } + } + else { + if (plug->stop) { + if (!plug->stop ()) { + plug->inactive = 1; + } + else { + fprintf (stderr, "failed to stop plugin %s\n", plug->name); + return -1; + } + return 0; + } + else { + return -1; + } + } +} + +DB_output_t * +plug_get_output (void) { + return output_plugin; +} + +int +plug_select_output (void) { + const char *outplugname = conf_get_str ("output_plugin", "ALSA output plugin"); + for (int i = 0; g_output_plugins[i]; i++) { + DB_output_t *p = g_output_plugins[i]; + if (!strcmp (p->plugin.name, outplugname)) { + fprintf (stderr, "selected output plugin: %s\n", outplugname); + output_plugin = p; + break; + } + } + if (!output_plugin) { + output_plugin = g_output_plugins[0]; + if (output_plugin) { + fprintf (stderr, "selected output plugin: %s\n", output_plugin->plugin.name); + conf_set_str ("output_plugin", output_plugin->plugin.name); + } + } + if (!output_plugin) { + return -1; + } + plug_trigger_event (DB_EV_OUTPUTCHANGED, 0); + return 0; +} + +void +plug_reinit_sound (void) { + int state = p_get_state (); + + p_free (); + + DB_output_t *prev = plug_get_output (); + if (plug_select_output () < 0) { + const char *outplugname = conf_get_str ("output_plugin", "ALSA output plugin"); + fprintf (stderr, "failed to select output plugin %s\nreverted to %s\n", outplugname, prev->plugin.name); + output_plugin = prev; + } + p_init (); + + if (state != OUTPUT_STATE_PAUSED && state != OUTPUT_STATE_STOPPED) { + p_play (); + } +} @@ -38,6 +38,21 @@ void plug_trigger_event (int ev, uintptr_t param); void +plug_trigger_event_trackchange (int from, int to); + +void +plug_trigger_event_trackinfochanged (int trk); + +void +plug_trigger_event_paused (int paused); + +void +plug_trigger_event_playlistchanged (void); + +void +plug_trigger_event_volumechanged (void); + +void plug_md5 (uint8_t sig[16], const char *in, int len); void @@ -76,6 +91,9 @@ plug_get_list (void); struct DB_decoder_s ** plug_get_decoder_list (void); +struct DB_output_s ** +plug_get_output_list (void); + struct DB_vfs_s ** plug_get_vfs_list (void); @@ -88,10 +106,16 @@ plug_volume_set_amp (float amp); const char * plug_get_config_dir (void); -void -plug_gui_lock (void); +int +plug_activate (DB_plugin_t *plug, int activate); + +DB_output_t * +plug_get_output (void); void -plug_gui_unlock (void); +plug_reinit_sound (void); + +int +plug_select_output (void); #endif // __PLUGINS_H diff --git a/plugins/adplug/Makefile.am b/plugins/adplug/Makefile.am new file mode 100644 index 00000000..52cbb90d --- /dev/null +++ b/plugins/adplug/Makefile.am @@ -0,0 +1,71 @@ +adlibdir = $(libdir)/$(PACKAGE) +pkglib_LTLIBRARIES = adplug.la +adplug_la_SOURCES = adplug-db.cpp\ + plugin.c\ + adplug/adplug.cpp\ + adplug/emuopl.cpp\ + adplug/fmopl.c\ + adplug/diskopl.cpp\ + adplug/debug.c\ + adplug/debug.h\ + adplug/fprovide.cpp\ + adplug/player.cpp\ + adplug/database.cpp\ + adplug/hsc.cpp\ + adplug/sng.cpp\ + adplug/imf.cpp\ + adplug/players.cpp\ + adplug/protrack.cpp\ + adplug/a2m.cpp\ + adplug/adtrack.cpp\ + adplug/amd.cpp\ + adplug/bam.cpp\ + adplug/d00.cpp\ + adplug/dfm.cpp\ + adplug/hsp.cpp\ + adplug/ksm.cpp\ + adplug/mad.cpp\ + adplug/mid.cpp\ + adplug/mkj.cpp\ + adplug/cff.cpp\ + adplug/dmo.cpp\ + adplug/s3m.cpp\ + adplug/dtm.cpp\ + adplug/fmc.cpp\ + adplug/mtk.cpp\ + adplug/rad.cpp\ + adplug/raw.cpp\ + adplug/sa2.cpp\ + adplug/xad.cpp\ + adplug/flash.cpp\ + adplug/bmf.cpp\ + adplug/hybrid.cpp\ + adplug/hyp.cpp\ + adplug/psi.cpp\ + adplug/rat.cpp\ + adplug/u6m.cpp\ + adplug/rol.cpp\ + adplug/mididata.h\ + adplug/xsm.cpp\ + adplug/adlibemu.c\ + adplug/dro.cpp\ + adplug/lds.cpp\ + adplug/realopl.cpp\ + adplug/analopl.cpp\ + adplug/temuopl.cpp\ + adplug/msc.cpp\ + adplug/rix.cpp\ + adplug/adl.cpp\ + libbinio/binfile.h\ + libbinio/binio.h\ + libbinio/binstr.h\ + libbinio/binwrap.h\ + libbinio/binfile.cpp\ + libbinio/binio.cpp\ + libbinio/binstr.cpp\ + libbinio/binwrap.cpp + +adplug_la_LDFLAGS = -module + +AM_CXXFLAGS = $(CFLAGS) -Dstricmp=strcasecmp -DVERSION=\"2.1\" -I adplug -I libbinio +AM_CFLAGS = $(CFLAGS) -std=c99 diff --git a/plugins/adplug/adplug-db.cpp b/plugins/adplug/adplug-db.cpp new file mode 100644 index 00000000..6c15804d --- /dev/null +++ b/plugins/adplug/adplug-db.cpp @@ -0,0 +1,251 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include <stdio.h> +#include <string.h> +#include "../../deadbeef.h" +#include "adplug.h" +#include "emuopl.h" +#include "silentopl.h" + +#define min(x,y) ((x)<(y)?(x):(y)) +#define max(x,y) ((x)>(y)?(x):(y)) + +#define trace(...) { fprintf (stderr, __VA_ARGS__); } +//#define trace(fmt,...) + +extern "C" { + +extern DB_decoder_t adplug_plugin; +static DB_functions_t *deadbeef; + + +const char *adplug_exts[] = { "A2M", "ADL", "AMD", "BAM", "CFF", "CMF", "D00", "DFM", "DMO", "DRO", "DTM", "HSC", "HSP", "IMF", "KSM", "LAA", "LDS", "M", "MAD", "MID", "MKJ", "MSC", "MTK", "RAD", "RAW", "RIX", "ROL", "S3M", "SA2", "SAT", "SCI", "SNG", "SNG", "SNG", "XAD", "XMS", "XSM", NULL }; + +const char *adplug_filetypes[] = { "A2M", "ADL", "AMD", "BAM", "CFF", "CMF", "D00", "DFM", "DMO", "DRO", "DTM", "HSC", "HSP", "IMF", "KSM", "LAA", "LDS", "M", "MAD", "MID", "MKJ", "MSC", "MTK", "RAD", "RAW", "RIX", "ROL", "S3M", "SA2", "SAT", "SCI", "SNG", "SNG", "SNG", "XAD", "XMS", "XSM", NULL }; + +static CEmuopl *opl; +static CPlayer *decoder; +static int totalsamples; +static int currentsample; +static int subsong; +static int toadd; + +int +adplug_init (DB_playItem_t *it) { + // prepare to decode the track + // return -1 on failure + + int samplerate = deadbeef->get_output ()->samplerate (); + int bps = deadbeef->get_output ()->bitspersample (); + opl = new CEmuopl (samplerate, bps, deadbeef->get_output ()->channels () == 2); + decoder = CAdPlug::factory (it->fname, opl, CAdPlug::players); + if (!decoder) { + trace ("adplug: failed to open %s\n", it->fname); + return NULL; + } + + subsong = it->tracknum; + decoder->rewind (subsong); + totalsamples = decoder->songlength (subsong) * samplerate / 1000; + currentsample = 0; + toadd = 0; + + // fill in mandatory plugin fields + adplug_plugin.info.bps = bps; + adplug_plugin.info.channels = deadbeef->get_output ()->channels (); + adplug_plugin.info.samplerate = samplerate; + adplug_plugin.info.readpos = 0; + + return 0; +} + +void +adplug_free (void) { + // free everything allocated in _init + if (decoder) { + delete decoder; + decoder = NULL; + } + if (opl) { + delete opl; + opl = NULL; + } +} + +int +adplug_read_int16 (char *bytes, int size) { + // try decode `size' bytes + // return number of decoded bytes + // return 0 on EOF + bool playing = true; + int i; + int sampsize = 4; + + int initsize = size; + if (currentsample + size/4 >= totalsamples) { + // clip + size = (totalsamples - currentsample) * sampsize; + } + + int towrite = size/sampsize; + char *sndbufpos = bytes; + + + while (towrite > 0) + { + while (toadd < 0) + { + toadd += adplug_plugin.info.samplerate; + playing = decoder->update (); +// decoder->time_ms += 1000 / plr.p->getrefresh (); + } + i = min (towrite, (long) (toadd / decoder->getrefresh () + 4) & ~3); + opl->update ((short *) sndbufpos, i); + sndbufpos += i * sampsize; + towrite -= i; + toadd -= (long) (decoder->getrefresh () * i); + } + currentsample += size/4; + adplug_plugin.info.readpos = (float)currentsample / adplug_plugin.info.samplerate; + return initsize-size; +} + +int +adplug_seek_sample (int sample) { + // seek to specified sample (frame) + // return 0 on success + // return -1 on failure + + if (sample < currentsample) { + decoder->rewind (subsong); + currentsample = 0; + } + + while (currentsample < sample) { + decoder->update (); + currentsample += 1000/decoder->getrefresh (); + } + if (currentsample >= totalsamples) { + return -1; + } + toadd = 0; + + // update readpos + adplug_plugin.info.readpos = (float)currentsample / adplug_plugin.info.samplerate; + return 0; +} + +int +adplug_seek (float time) { + // seek to specified time in seconds + // return 0 on success + // return -1 on failure + return adplug_seek_sample (time * adplug_plugin.info.samplerate); + return 0; +} + +static const char * +adplug_get_extension (const char *fname) { + const char *e = fname + strlen (fname); + while (*e != '.' && e != fname) { + e--; + } + if (*e == '.') { + e++; + // now find ext in list + for (int i = 0; adplug_exts[i]; i++) { + if (!strcasecmp (e, adplug_exts[i])) { + return adplug_filetypes[i]; + } + } + } + return "adplug-unknown"; +} + +DB_playItem_t * +adplug_insert (DB_playItem_t *after, const char *fname) { + // read information from the track + // load/process cuesheet if exists + // insert track into playlist + // return track pointer on success + // return NULL on failure + + CSilentopl opl; + CPlayer *p = CAdPlug::factory (fname, &opl, CAdPlug::players); + if (!p) { + trace ("adplug: failed to open %s\n", fname); + return NULL; + } + + int subsongs = p->getsubsongs (); + for (int i = 0; i < subsongs; i++) { + // prepare track for addition + DB_playItem_t *it = deadbeef->pl_item_alloc (); + it->decoder = &adplug_plugin; + it->fname = strdup (fname); + it->filetype = adplug_get_extension (fname); + it->tracknum = i; + deadbeef->pl_set_item_duration (it, p->songlength (i)/1000.f); + // add metainfo + if (!p->gettitle().empty()) { + deadbeef->pl_add_meta (it, "title", p->gettitle().c_str()); + } + else if (!p->getdesc().empty()) { + deadbeef->pl_add_meta (it, "title", p->getdesc().c_str()); + } + else { + deadbeef->pl_add_meta (it, "title", NULL); + } + deadbeef->pl_add_meta (it, "artist", p->getauthor().c_str()); + // insert + after = deadbeef->pl_insert_item (after, it); + } + + // free decoder + delete p; + + // now the track is ready, insert into playlist + return after; +} + +int +adplug_start (void) { + // do one-time plugin initialization here + // e.g. starting threads for background processing, subscribing to events, etc + // return 0 on success + // return -1 on failure + return 0; +} + +int +adplug_stop (void) { + // undo everything done in _start here + // return 0 on success + // return -1 on failure + return 0; +} + +DB_plugin_t * +adplug_load (DB_functions_t *api) { + deadbeef = api; + return DB_PLUGIN (&adplug_plugin); +} + +} diff --git a/plugins/adplug/adplug/COPYING b/plugins/adplug/adplug/COPYING new file mode 100644 index 00000000..aaf2c633 --- /dev/null +++ b/plugins/adplug/adplug/COPYING @@ -0,0 +1,509 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. +^L + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. +^L + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. +^L + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. +^L + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. +^L + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. +^L + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. +^L + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS +^L + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/plugins/adplug/adplug/a2m.cpp b/plugins/adplug/adplug/a2m.cpp new file mode 100644 index 00000000..3fd68d98 --- /dev/null +++ b/plugins/adplug/adplug/a2m.cpp @@ -0,0 +1,483 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * a2m.cpp - A2M Loader by Simon Peter <dn.tlp@gmx.net> + * + * NOTES: + * This loader detects and loads version 1, 4, 5 & 8 files. + * + * version 1-4 files: + * Following commands are ignored: FF1 - FF9, FAx - FEx + * + * version 5-8 files: + * Instrument panning is ignored. Flags byte is ignored. + * Following commands are ignored: Gxy, Hxy, Kxy - &xy + */ + +#include <string.h> +#include "a2m.h" + +const unsigned int Ca2mLoader::MAXFREQ = 2000, +Ca2mLoader::MINCOPY = ADPLUG_A2M_MINCOPY, +Ca2mLoader::MAXCOPY = ADPLUG_A2M_MAXCOPY, +Ca2mLoader::COPYRANGES = ADPLUG_A2M_COPYRANGES, +Ca2mLoader::CODESPERRANGE = ADPLUG_A2M_CODESPERRANGE, +Ca2mLoader::TERMINATE = 256, +Ca2mLoader::FIRSTCODE = ADPLUG_A2M_FIRSTCODE, +Ca2mLoader::MAXCHAR = FIRSTCODE + COPYRANGES * CODESPERRANGE - 1, +Ca2mLoader::SUCCMAX = MAXCHAR + 1, +Ca2mLoader::TWICEMAX = ADPLUG_A2M_TWICEMAX, +Ca2mLoader::ROOT = 1, Ca2mLoader::MAXBUF = 42 * 1024, +Ca2mLoader::MAXDISTANCE = 21389, Ca2mLoader::MAXSIZE = 21389 + MAXCOPY; + +const unsigned short Ca2mLoader::bitvalue[14] = + {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192}; + +const signed short Ca2mLoader::copybits[COPYRANGES] = + {4, 6, 8, 10, 12, 14}; + +const signed short Ca2mLoader::copymin[COPYRANGES] = + {0, 16, 80, 336, 1360, 5456}; + +CPlayer *Ca2mLoader::factory(Copl *newopl) +{ + return new Ca2mLoader(newopl); +} + +bool Ca2mLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + char id[10]; + int i,j,k,t; + unsigned int l; + unsigned char *org, *orgptr, flags = 0, numpats, version; + unsigned long crc, alength; + unsigned short len[9], *secdata, *secptr; + const unsigned char convfx[16] = {0,1,2,23,24,3,5,4,6,9,17,13,11,19,7,14}; + const unsigned char convinf1[16] = {0,1,2,6,7,8,9,4,5,3,10,11,12,13,14,15}; + const unsigned char newconvfx[] = {0,1,2,3,4,5,6,23,24,21,10,11,17,13,7,19, + 255,255,22,25,255,15,255,255,255,255,255, + 255,255,255,255,255,255,255,255,14,255}; + + // read header + f->readString(id, 10); crc = f->readInt(4); + version = f->readInt(1); numpats = f->readInt(1); + + // file validation section + if(strncmp(id,"_A2module_",10) || (version != 1 && version != 5 && + version != 4 && version != 8)) { + fp.close(f); + return false; + } + + // load, depack & convert section + nop = numpats; length = 128; restartpos = 0; + if(version < 5) { + for(i=0;i<5;i++) len[i] = f->readInt(2); + t = 9; + } else { // version >= 5 + for(i=0;i<9;i++) len[i] = f->readInt(2); + t = 18; + } + + // block 0 + secdata = new unsigned short [len[0] / 2]; + if(version == 1 || version == 5) { + for(i=0;i<len[0]/2;i++) secdata[i] = f->readInt(2); + org = new unsigned char [MAXBUF]; orgptr = org; + sixdepak(secdata,org,len[0]); + } else { + orgptr = (unsigned char *)secdata; + for(i=0;i<len[0];i++) orgptr[i] = f->readInt(1); + } + memcpy(songname,orgptr,43); orgptr += 43; + memcpy(author,orgptr,43); orgptr += 43; + memcpy(instname,orgptr,250*33); orgptr += 250*33; + + for(i=0;i<250;i++) { // instruments + inst[i].data[0] = *(orgptr+i*13+10); + inst[i].data[1] = *(orgptr+i*13); + inst[i].data[2] = *(orgptr+i*13+1); + inst[i].data[3] = *(orgptr+i*13+4); + inst[i].data[4] = *(orgptr+i*13+5); + inst[i].data[5] = *(orgptr+i*13+6); + inst[i].data[6] = *(orgptr+i*13+7); + inst[i].data[7] = *(orgptr+i*13+8); + inst[i].data[8] = *(orgptr+i*13+9); + inst[i].data[9] = *(orgptr+i*13+2); + inst[i].data[10] = *(orgptr+i*13+3); + + if(version < 5) + inst[i].misc = *(orgptr+i*13+11); + else { // version >= 5 -> OPL3 format + int pan = *(orgptr+i*13+11); + + if(pan) + inst[i].data[0] |= (pan & 3) << 4; // set pan + else + inst[i].data[0] |= 48; // enable both speakers + } + + inst[i].slide = *(orgptr+i*13+12); + } + + orgptr += 250*13; + memcpy(order,orgptr,128); orgptr += 128; + bpm = *orgptr; orgptr++; + initspeed = *orgptr; orgptr++; + if(version >= 5) flags = *orgptr; + if(version == 1 || version == 5) delete [] org; + delete [] secdata; + + // blocks 1-4 or 1-8 + alength = len[1]; + for(i = 0; i < (version < 5 ? numpats / 16 : numpats / 8); i++) + alength += len[i+2]; + + secdata = new unsigned short [alength / 2]; + if(version == 1 || version == 5) { + for(l=0;l<alength/2;l++) secdata[l] = f->readInt(2); + org = new unsigned char [MAXBUF * (numpats / (version == 1 ? 16 : 8) + 1)]; + orgptr = org; secptr = secdata; + orgptr += sixdepak(secptr,orgptr,len[1]); secptr += len[1] / 2; + if(version == 1) { + if(numpats > 16) + orgptr += sixdepak(secptr,orgptr,len[2]); secptr += len[2] / 2; + if(numpats > 32) + orgptr += sixdepak(secptr,orgptr,len[3]); secptr += len[3] / 2; + if(numpats > 48) + sixdepak(secptr,orgptr,len[4]); + } else { + if(numpats > 8) + orgptr += sixdepak(secptr,orgptr,len[2]); secptr += len[2] / 2; + if(numpats > 16) + orgptr += sixdepak(secptr,orgptr,len[3]); secptr += len[3] / 2; + if(numpats > 24) + orgptr += sixdepak(secptr,orgptr,len[4]); secptr += len[4] / 2; + if(numpats > 32) + orgptr += sixdepak(secptr,orgptr,len[5]); secptr += len[5] / 2; + if(numpats > 40) + orgptr += sixdepak(secptr,orgptr,len[6]); secptr += len[6] / 2; + if(numpats > 48) + orgptr += sixdepak(secptr,orgptr,len[7]); secptr += len[7] / 2; + if(numpats > 56) + sixdepak(secptr,orgptr,len[8]); + } + delete [] secdata; + } else { + org = (unsigned char *)secdata; + for(l=0;l<alength;l++) org[l] = f->readInt(1); + } + + if(version < 5) { + for(i=0;i<numpats;i++) + for(j=0;j<64;j++) + for(k=0;k<9;k++) { + struct Tracks *track = &tracks[i * 9 + k][j]; + unsigned char *o = &org[i*64*t*4+j*t*4+k*4]; + + track->note = o[0] == 255 ? 127 : o[0]; + track->inst = o[1]; + track->command = convfx[o[2]]; + track->param2 = o[3] & 0x0f; + if(track->command != 14) + track->param1 = o[3] >> 4; + else { + track->param1 = convinf1[o[3] >> 4]; + if(track->param1 == 15 && !track->param2) { // convert key-off + track->command = 8; + track->param1 = 0; + track->param2 = 0; + } + } + if(track->command == 14) { + switch(track->param1) { + case 2: // convert define waveform + track->command = 25; + track->param1 = track->param2; + track->param2 = 0xf; + break; + case 8: // convert volume slide up + track->command = 26; + track->param1 = track->param2; + track->param2 = 0; + break; + case 9: // convert volume slide down + track->command = 26; + track->param1 = 0; + break; + } + } + } + } else { // version >= 5 + realloc_patterns(64, 64, 18); + + for(i=0;i<numpats;i++) + for(j=0;j<18;j++) + for(k=0;k<64;k++) { + struct Tracks *track = &tracks[i * 18 + j][k]; + unsigned char *o = &org[i*64*t*4+j*64*4+k*4]; + + track->note = o[0] == 255 ? 127 : o[0]; + track->inst = o[1]; + track->command = newconvfx[o[2]]; + track->param1 = o[3] >> 4; + track->param2 = o[3] & 0x0f; + + // Convert '&' command + if(o[2] == 36) + switch(track->param1) { + case 0: // pattern delay (frames) + track->command = 29; + track->param1 = 0; + // param2 already set correctly + break; + + case 1: // pattern delay (rows) + track->command = 14; + track->param1 = 8; + // param2 already set correctly + break; + } + } + } + + init_trackord(); + + if(version == 1 || version == 5) + delete [] org; + else + delete [] secdata; + + // Process flags + if(version >= 5) { + CmodPlayer::flags |= Opl3; // All versions >= 5 are OPL3 + if(flags & 8) CmodPlayer::flags |= Tremolo; // Tremolo depth + if(flags & 16) CmodPlayer::flags |= Vibrato; // Vibrato depth + } + + fp.close(f); + rewind(0); + return true; +} + +float Ca2mLoader::getrefresh() +{ + if(tempo != 18) + return (float) (tempo); + else + return 18.2f; +} + +/*** private methods *************************************/ + +void Ca2mLoader::inittree() +{ + unsigned short i; + + for(i=2;i<=TWICEMAX;i++) { + dad[i] = i / 2; + freq[i] = 1; + } + + for(i=1;i<=MAXCHAR;i++) { + leftc[i] = 2 * i; + rghtc[i] = 2 * i + 1; + } +} + +void Ca2mLoader::updatefreq(unsigned short a,unsigned short b) +{ + do { + freq[dad[a]] = freq[a] + freq[b]; + a = dad[a]; + if(a != ROOT) + if(leftc[dad[a]] == a) + b = rghtc[dad[a]]; + else + b = leftc[dad[a]]; + } while(a != ROOT); + + if(freq[ROOT] == MAXFREQ) + for(a=1;a<=TWICEMAX;a++) + freq[a] >>= 1; +} + +void Ca2mLoader::updatemodel(unsigned short code) +{ + unsigned short a=code+SUCCMAX,b,c,code1,code2; + + freq[a]++; + if(dad[a] != ROOT) { + code1 = dad[a]; + if(leftc[code1] == a) + updatefreq(a,rghtc[code1]); + else + updatefreq(a,leftc[code1]); + + do { + code2 = dad[code1]; + if(leftc[code2] == code1) + b = rghtc[code2]; + else + b = leftc[code2]; + + if(freq[a] > freq[b]) { + if(leftc[code2] == code1) + rghtc[code2] = a; + else + leftc[code2] = a; + + if(leftc[code1] == a) { + leftc[code1] = b; + c = rghtc[code1]; + } else { + rghtc[code1] = b; + c = leftc[code1]; + } + + dad[b] = code1; + dad[a] = code2; + updatefreq(b,c); + a = b; + } + + a = dad[a]; + code1 = dad[a]; + } while(code1 != ROOT); + } +} + +unsigned short Ca2mLoader::inputcode(unsigned short bits) +{ + unsigned short i,code=0; + + for(i=1;i<=bits;i++) { + if(!ibitcount) { + if(ibitcount == MAXBUF) + ibufcount = 0; + ibitbuffer = wdbuf[ibufcount]; + ibufcount++; + ibitcount = 15; + } else + ibitcount--; + + if(ibitbuffer > 0x7fff) + code |= bitvalue[i-1]; + ibitbuffer <<= 1; + } + + return code; +} + +unsigned short Ca2mLoader::uncompress() +{ + unsigned short a=1; + + do { + if(!ibitcount) { + if(ibufcount == MAXBUF) + ibufcount = 0; + ibitbuffer = wdbuf[ibufcount]; + ibufcount++; + ibitcount = 15; + } else + ibitcount--; + + if(ibitbuffer > 0x7fff) + a = rghtc[a]; + else + a = leftc[a]; + ibitbuffer <<= 1; + } while(a <= MAXCHAR); + + a -= SUCCMAX; + updatemodel(a); + return a; +} + +void Ca2mLoader::decode() +{ + unsigned short i,j,k,t,c,count=0,dist,len,index; + + inittree(); + c = uncompress(); + + while(c != TERMINATE) { + if(c < 256) { + obuf[obufcount] = (unsigned char)c; + obufcount++; + if(obufcount == MAXBUF) { + output_size = MAXBUF; + obufcount = 0; + } + + buf[count] = (unsigned char)c; + count++; + if(count == MAXSIZE) + count = 0; + } else { + t = c - FIRSTCODE; + index = t / CODESPERRANGE; + len = t + MINCOPY - index * CODESPERRANGE; + dist = inputcode(copybits[index]) + len + copymin[index]; + + j = count; + k = count - dist; + if(count < dist) + k += MAXSIZE; + + for(i=0;i<=len-1;i++) { + obuf[obufcount] = buf[k]; + obufcount++; + if(obufcount == MAXBUF) { + output_size = MAXBUF; + obufcount = 0; + } + + buf[j] = buf[k]; + j++; k++; + if(j == MAXSIZE) j = 0; + if(k == MAXSIZE) k = 0; + } + + count += len; + if(count >= MAXSIZE) + count -= MAXSIZE; + } + c = uncompress(); + } + output_size = obufcount; +} + +unsigned short Ca2mLoader::sixdepak(unsigned short *source, unsigned char *dest, + unsigned short size) +{ + if((unsigned int)size + 4096 > MAXBUF) + return 0; + + buf = new unsigned char [MAXSIZE]; + input_size = size; + ibitcount = 0; ibitbuffer = 0; + obufcount = 0; ibufcount = 0; + wdbuf = source; obuf = dest; + + decode(); + delete [] buf; + return output_size; +} diff --git a/plugins/adplug/adplug/a2m.h b/plugins/adplug/adplug/a2m.h new file mode 100644 index 00000000..9e032f61 --- /dev/null +++ b/plugins/adplug/adplug/a2m.h @@ -0,0 +1,83 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * a2m.h - A2M Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_A2MLOADER +#define H_ADPLUG_A2MLOADER + +#include "protrack.h" + +class Ca2mLoader: public CmodPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + Ca2mLoader(Copl *newopl): CmodPlayer(newopl) + { } + + bool load(const std::string &filename, const CFileProvider &fp); + float getrefresh(); + + std::string gettype() + { return std::string("AdLib Tracker 2"); } + std::string gettitle() + { if(*songname) return std::string(songname,1,*songname); else return std::string(); } + std::string getauthor() + { if(*author) return std::string(author,1,*author); else return std::string(); } + unsigned int getinstruments() + { return 250; } + std::string getinstrument(unsigned int n) + { return std::string(instname[n],1,*instname[n]); } + +private: + +#define ADPLUG_A2M_COPYRANGES 6 +#define ADPLUG_A2M_FIRSTCODE 257 +#define ADPLUG_A2M_MINCOPY 3 +#define ADPLUG_A2M_MAXCOPY 255 +#define ADPLUG_A2M_CODESPERRANGE (ADPLUG_A2M_MAXCOPY - ADPLUG_A2M_MINCOPY + 1) +#define ADPLUG_A2M_MAXCHAR (ADPLUG_A2M_FIRSTCODE + ADPLUG_A2M_COPYRANGES * ADPLUG_A2M_CODESPERRANGE - 1) +#define ADPLUG_A2M_TWICEMAX (2 * ADPLUG_A2M_MAXCHAR + 1) + + static const unsigned int MAXFREQ, MINCOPY, MAXCOPY, COPYRANGES, + CODESPERRANGE, TERMINATE, FIRSTCODE, MAXCHAR, SUCCMAX, TWICEMAX, ROOT, + MAXBUF, MAXDISTANCE, MAXSIZE; + + static const unsigned short bitvalue[14]; + static const signed short copybits[ADPLUG_A2M_COPYRANGES], + copymin[ADPLUG_A2M_COPYRANGES]; + + void inittree(); + void updatefreq(unsigned short a,unsigned short b); + void updatemodel(unsigned short code); + unsigned short inputcode(unsigned short bits); + unsigned short uncompress(); + void decode(); + unsigned short sixdepak(unsigned short *source,unsigned char *dest,unsigned short size); + + char songname[43], author[43], instname[250][33]; + + unsigned short ibitcount, ibitbuffer, ibufcount, obufcount, input_size, + output_size, leftc[ADPLUG_A2M_MAXCHAR+1], rghtc[ADPLUG_A2M_MAXCHAR+1], + dad[ADPLUG_A2M_TWICEMAX+1], freq[ADPLUG_A2M_TWICEMAX+1], *wdbuf; + unsigned char *obuf, *buf; +}; + +#endif diff --git a/plugins/adplug/adplug/adl.cpp b/plugins/adplug/adplug/adl.cpp new file mode 100644 index 00000000..40896fdb --- /dev/null +++ b/plugins/adplug/adplug/adl.cpp @@ -0,0 +1,2431 @@ +/* + * adl.cpp - ADL player adaption by Simon Peter <dn.tlp@gmx.net> + * + * Original ADL player by Torbjorn Andersson and Johannes Schickel + * 'lordhoto' <lordhoto at scummvm dot org> of the ScummVM project. + */ + +/* ScummVM - Scumm Interpreter + * + * This file is licensed under both GPL and LGPL + * Copyright (C) 2006 The ScummVM project + * Copyright (C) 2006 Torbjorn Andersson and Johannes Schickel + * + * GPL License + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * LPGL License + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * $URL: https://svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/engines/kyra/sound_adlib.cpp $ + * $Id: adl.cpp,v 1.9 2006/08/16 00:20:45 dynamite Exp $ + * + */ + +#include <inttypes.h> +#include <stdarg.h> +#include <assert.h> +#include <string.h> + +#include "adl.h" +#include "debug.h" + +#ifdef ADL_DEBUG +# define warning(...) AdPlug_LogWrite(__VA_ARGS__); \ +AdPlug_LogWrite("\n") + +# define debugC(i1, i2, ...) AdPlug_LogWrite(__VA_ARGS__); \ +AdPlug_LogWrite("\n") +#else +# define kDebugLevelSound 1 + +static inline void warning(const char *str, ...) +{ +} + +static inline void debugC(int i1, int i2, const char *str, ...) +{ +} +#endif + +// #define warning(...) +// #define debugC(i1, i2, ...) + +#define ARRAYSIZE(x) ((int)(sizeof(x) / sizeof(x[0]))) + +// Basic Adlib Programming: +// http://www.gamedev.net/reference/articles/article446.asp + +#define CALLBACKS_PER_SECOND 72 + +typedef uint8_t uint8; +typedef int8_t int8; +typedef uint16_t uint16; +typedef int16_t int16; +typedef uint32_t uint32; +typedef int32_t int32; +typedef uint8_t byte; + +static inline uint16 READ_LE_UINT16(const void *ptr) { + const byte *b = (const byte *)ptr; + return (b[1] << 8) + b[0]; +} + +static inline uint16 READ_BE_UINT16(const void *ptr) { + const byte *b = (const byte *)ptr; + return (b[0] << 8) + b[1]; +} + +class AdlibDriver { +public: + AdlibDriver(Copl *opl); + ~AdlibDriver(); + + int callback(int opcode, ...); + void callback(); + + // AudioStream API + // int readBuffer(int16 *buffer, const int numSamples) { + // int32 samplesLeft = numSamples; + // memset(buffer, 0, sizeof(int16) * numSamples); + // while (samplesLeft) { + // if (!_samplesTillCallback) { + // callback(); + // _samplesTillCallback = _samplesPerCallback; + // _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; + // if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) { + // _samplesTillCallback++; + // _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND; + // } + // } + + // int32 render = MIN(samplesLeft, _samplesTillCallback); + // samplesLeft -= render; + // _samplesTillCallback -= render; + // YM3812UpdateOne(_adlib, buffer, render); + // buffer += render; + // } + // return numSamples; + // } + + bool isStereo() const { return false; } + bool endOfData() const { return false; } + // int getRate() const { return _mixer->getOutputRate(); } + + struct OpcodeEntry { + typedef int (AdlibDriver::*DriverOpcode)(va_list &list); + DriverOpcode function; + const char *name; + }; + + void setupOpcodeList(); + const OpcodeEntry *_opcodeList; + int _opcodesEntries; + + int snd_ret0x100(va_list &list); + int snd_ret0x1983(va_list &list); + int snd_initDriver(va_list &list); + int snd_deinitDriver(va_list &list); + int snd_setSoundData(va_list &list); + int snd_unkOpcode1(va_list &list); + int snd_startSong(va_list &list); + int snd_unkOpcode2(va_list &list); + int snd_unkOpcode3(va_list &list); + int snd_readByte(va_list &list); + int snd_writeByte(va_list &list); + int snd_getSoundTrigger(va_list &list); + int snd_unkOpcode4(va_list &list); + int snd_dummy(va_list &list); + int snd_getNullvar4(va_list &list); + int snd_setNullvar3(va_list &list); + int snd_setFlag(va_list &list); + int snd_clearFlag(va_list &list); + + // These variables have not yet been named, but some of them are partly + // known nevertheless: + // + // unk16 - Sound-related. Possibly some sort of pitch bend. + // unk18 - Sound-effect. Used for secondaryEffect1() + // unk19 - Sound-effect. Used for secondaryEffect1() + // unk20 - Sound-effect. Used for secondaryEffect1() + // unk21 - Sound-effect. Used for secondaryEffect1() + // unk22 - Sound-effect. Used for secondaryEffect1() + // unk29 - Sound-effect. Used for primaryEffect1() + // unk30 - Sound-effect. Used for primaryEffect1() + // unk31 - Sound-effect. Used for primaryEffect1() + // unk32 - Sound-effect. Used for primaryEffect2() + // unk33 - Sound-effect. Used for primaryEffect2() + // unk34 - Sound-effect. Used for primaryEffect2() + // unk35 - Sound-effect. Used for primaryEffect2() + // unk36 - Sound-effect. Used for primaryEffect2() + // unk37 - Sound-effect. Used for primaryEffect2() + // unk38 - Sound-effect. Used for primaryEffect2() + // unk39 - Currently unused, except for updateCallback56() + // unk40 - Currently unused, except for updateCallback56() + // unk41 - Sound-effect. Used for primaryEffect2() + + struct Channel { + uint8 opExtraLevel2; + uint8 *dataptr; + uint8 duration; + uint8 repeatCounter; + int8 baseOctave; + uint8 priority; + uint8 dataptrStackPos; + uint8 *dataptrStack[4]; + int8 baseNote; + uint8 unk29; + uint8 unk31; + uint16 unk30; + uint16 unk37; + uint8 unk33; + uint8 unk34; + uint8 unk35; + uint8 unk36; + uint8 unk32; + uint8 unk41; + uint8 unk38; + uint8 opExtraLevel1; + uint8 spacing2; + uint8 baseFreq; + uint8 tempo; + uint8 position; + uint8 regAx; + uint8 regBx; + typedef void (AdlibDriver::*Callback)(Channel&); + Callback primaryEffect; + Callback secondaryEffect; + uint8 fractionalSpacing; + uint8 opLevel1; + uint8 opLevel2; + uint8 opExtraLevel3; + uint8 twoChan; + uint8 unk39; + uint8 unk40; + uint8 spacing1; + uint8 durationRandomness; + uint8 unk19; + uint8 unk18; + int8 unk20; + int8 unk21; + uint8 unk22; + uint16 offset; + uint8 tempoReset; + uint8 rawNote; + int8 unk16; + }; + + void primaryEffect1(Channel &channel); + void primaryEffect2(Channel &channel); + void secondaryEffect1(Channel &channel); + + void resetAdlibState(); + void writeOPL(byte reg, byte val); + void initChannel(Channel &channel); + void noteOff(Channel &channel); + void unkOutput2(uint8 num); + + uint16 getRandomNr(); + void setupDuration(uint8 duration, Channel &channel); + + void setupNote(uint8 rawNote, Channel &channel, bool flag = false); + void setupInstrument(uint8 regOffset, uint8 *dataptr, Channel &channel); + void noteOn(Channel &channel); + + void adjustVolume(Channel &channel); + + uint8 calculateOpLevel1(Channel &channel); + uint8 calculateOpLevel2(Channel &channel); + + uint16 checkValue(int16 val) { + if (val < 0) + val = 0; + else if (val > 0x3F) + val = 0x3F; + return val; + } + + // The sound data has at least two lookup tables: + // + // * One for programs, starting at offset 0. + // * One for instruments, starting at offset 500. + + uint8 *getProgram(int progId) { + return _soundData + READ_LE_UINT16(_soundData + 2 * progId); + } + + uint8 *getInstrument(int instrumentId) { + return _soundData + READ_LE_UINT16(_soundData + 500 + 2 * instrumentId); + } + + void setupPrograms(); + void executePrograms(); + + struct ParserOpcode { + typedef int (AdlibDriver::*POpcode)(uint8 *&dataptr, Channel &channel, uint8 value); + POpcode function; + const char *name; + }; + + void setupParserOpcodeTable(); + const ParserOpcode *_parserOpcodeTable; + int _parserOpcodeTableSize; + + int update_setRepeat(uint8 *&dataptr, Channel &channel, uint8 value); + int update_checkRepeat(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupProgram(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setNoteSpacing(uint8 *&dataptr, Channel &channel, uint8 value); + int update_jump(uint8 *&dataptr, Channel &channel, uint8 value); + int update_jumpToSubroutine(uint8 *&dataptr, Channel &channel, uint8 value); + int update_returnFromSubroutine(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setBaseOctave(uint8 *&dataptr, Channel &channel, uint8 value); + int update_stopChannel(uint8 *&dataptr, Channel &channel, uint8 value); + int update_playRest(uint8 *&dataptr, Channel &channel, uint8 value); + int update_writeAdlib(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupNoteAndDuration(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setBaseNote(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupSecondaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value); + int update_stopOtherChannel(uint8 *&dataptr, Channel &channel, uint8 value); + int update_waitForEndOfProgram(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupInstrument(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupPrimaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value); + int update_removePrimaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setBaseFreq(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupPrimaryEffect2(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setPriority(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback23(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback24(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setExtraLevel1(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupDuration(uint8 *&dataptr, Channel &channel, uint8 value); + int update_playNote(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setFractionalNoteSpacing(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setTempo(uint8 *&dataptr, Channel &channel, uint8 value); + int update_removeSecondaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setChannelTempo(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setExtraLevel3(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setExtraLevel2(uint8 *&dataptr, Channel &channel, uint8 value); + int update_changeExtraLevel2(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setAMDepth(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setVibratoDepth(uint8 *&dataptr, Channel &channel, uint8 value); + int update_changeExtraLevel1(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback38(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback39(uint8 *&dataptr, Channel &channel, uint8 value); + int update_removePrimaryEffect2(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback41(uint8 *&dataptr, Channel &channel, uint8 value); + int update_resetToGlobalTempo(uint8 *&dataptr, Channel &channel, uint8 value); + int update_nop1(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setDurationRandomness(uint8 *&dataptr, Channel &channel, uint8 value); + int update_changeChannelTempo(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback46(uint8 *&dataptr, Channel &channel, uint8 value); + int update_nop2(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setupRhythmSection(uint8 *&dataptr, Channel &channel, uint8 value); + int update_playRhythmSection(uint8 *&dataptr, Channel &channel, uint8 value); + int update_removeRhythmSection(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback51(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback52(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback53(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setSoundTrigger(uint8 *&dataptr, Channel &channel, uint8 value); + int update_setTempoReset(uint8 *&dataptr, Channel &channel, uint8 value); + int updateCallback56(uint8 *&dataptr, Channel &channel, uint8 value); + + // These variables have not yet been named, but some of them are partly + // known nevertheless: + // + // _unkValue1 - Unknown. Used for updating _unkValue2 + // _unkValue2 - Unknown. Used for updating _unkValue4 + // _unkValue3 - Unknown. Used for updating _unkValue2 + // _unkValue4 - Unknown. Used for updating _unkValue5 + // _unkValue5 - Unknown. Used for controlling updateCallback24(). + // _unkValue6 - Unknown. Rhythm section volume? + // _unkValue7 - Unknown. Rhythm section volume? + // _unkValue8 - Unknown. Rhythm section volume? + // _unkValue9 - Unknown. Rhythm section volume? + // _unkValue10 - Unknown. Rhythm section volume? + // _unkValue11 - Unknown. Rhythm section volume? + // _unkValue12 - Unknown. Rhythm section volume? + // _unkValue13 - Unknown. Rhythm section volume? + // _unkValue14 - Unknown. Rhythm section volume? + // _unkValue15 - Unknown. Rhythm section volume? + // _unkValue16 - Unknown. Rhythm section volume? + // _unkValue17 - Unknown. Rhythm section volume? + // _unkValue18 - Unknown. Rhythm section volume? + // _unkValue19 - Unknown. Rhythm section volume? + // _unkValue20 - Unknown. Rhythm section volume? + // _unkTable[] - Probably frequences for the 12-tone scale. + // _unkTable2[] - Unknown. Currently only used by updateCallback46() + // _unkTable2_1[] - One of the tables in _unkTable2[] + // _unkTable2_2[] - One of the tables in _unkTable2[] + // _unkTable2_3[] - One of the tables in _unkTable2[] + + int32 _samplesPerCallback; + int32 _samplesPerCallbackRemainder; + int32 _samplesTillCallback; + int32 _samplesTillCallbackRemainder; + + int _lastProcessed; + int8 _flagTrigger; + int _curChannel; + uint8 _soundTrigger; + int _soundsPlaying; + + uint16 _rnd; + + uint8 _unkValue1; + uint8 _unkValue2; + uint8 _unkValue3; + uint8 _unkValue4; + uint8 _unkValue5; + uint8 _unkValue6; + uint8 _unkValue7; + uint8 _unkValue8; + uint8 _unkValue9; + uint8 _unkValue10; + uint8 _unkValue11; + uint8 _unkValue12; + uint8 _unkValue13; + uint8 _unkValue14; + uint8 _unkValue15; + uint8 _unkValue16; + uint8 _unkValue17; + uint8 _unkValue18; + uint8 _unkValue19; + uint8 _unkValue20; + + int _flags; + + uint8 *_soundData; + + uint8 _soundIdTable[0x10]; + Channel _channels[10]; + + uint8 _vibratoAndAMDepthBits; + uint8 _rhythmSectionBits; + + uint8 _curRegOffset; + uint8 _tempo; + + const uint8 *_tablePtr1; + const uint8 *_tablePtr2; + + static const uint8 _regOffset[]; + static const uint16 _unkTable[]; + static const uint8 *_unkTable2[]; + static const uint8 _unkTable2_1[]; + static const uint8 _unkTable2_2[]; + static const uint8 _unkTable2_3[]; + static const uint8 _unkTables[][32]; + + Copl *opl; +}; + +AdlibDriver::AdlibDriver(Copl *newopl) + : opl(newopl) +{ + setupOpcodeList(); + setupParserOpcodeTable(); + + // _mixer = mixer; + + _flags = 0; + // _adlib = makeAdlibOPL(getRate()); + // assert(_adlib); + + memset(_channels, 0, sizeof(_channels)); + _soundData = 0; + + _vibratoAndAMDepthBits = _curRegOffset = 0; + + _lastProcessed = _flagTrigger = _curChannel = _rhythmSectionBits = 0; + _soundsPlaying = 0; + _rnd = 0x1234; + + _tempo = 0; + _soundTrigger = 0; + + _unkValue3 = 0xFF; + _unkValue1 = _unkValue2 = _unkValue4 = _unkValue5 = 0; + _unkValue6 = _unkValue7 = _unkValue8 = _unkValue9 = _unkValue10 = 0; + _unkValue11 = _unkValue12 = _unkValue13 = _unkValue14 = _unkValue15 = + _unkValue16 = _unkValue17 = _unkValue18 = _unkValue19 = _unkValue20 = 0; + + _tablePtr1 = _tablePtr2 = 0; + + // _mixer->setupPremix(this); + + // _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND; + // _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND; + _samplesTillCallback = 0; + _samplesTillCallbackRemainder = 0; +} + +AdlibDriver::~AdlibDriver() { + // _mixer->setupPremix(0); + // OPLDestroy(_adlib); + // _adlib = 0; +} + +int AdlibDriver::callback(int opcode, ...) { + // lock(); + if (opcode >= _opcodesEntries || opcode < 0) { + warning("AdlibDriver: calling unknown opcode '%d'", opcode); + return 0; + } + + debugC(9, kDebugLevelSound, "Calling opcode '%s' (%d)", _opcodeList[opcode].name, opcode); + + va_list args; + va_start(args, opcode); + int returnValue = (this->*(_opcodeList[opcode].function))(args); + va_end(args); + // unlock(); + return returnValue; +} + +// Opcodes + +int AdlibDriver::snd_ret0x100(va_list &list) { + return 0x100; +} + +int AdlibDriver::snd_ret0x1983(va_list &list) { + return 0x1983; +} + +int AdlibDriver::snd_initDriver(va_list &list) { + _lastProcessed = _soundsPlaying = 0; + resetAdlibState(); + return 0; +} + +int AdlibDriver::snd_deinitDriver(va_list &list) { + resetAdlibState(); + return 0; +} + +int AdlibDriver::snd_setSoundData(va_list &list) { + if (_soundData) { + delete [] _soundData; + _soundData = 0; + } + _soundData = va_arg(list, uint8*); + return 0; +} + +int AdlibDriver::snd_unkOpcode1(va_list &list) { + warning("unimplemented snd_unkOpcode1"); + return 0; +} + +int AdlibDriver::snd_startSong(va_list &list) { + int songId = va_arg(list, int); + _flags |= 8; + _flagTrigger = 1; + + uint8 *ptr = getProgram(songId); + uint8 chan = *ptr; + + if ((songId << 1) != 0) { + if (chan == 9) { + if (_flags & 2) + return 0; + } else { + if (_flags & 1) + return 0; + } + } + + _soundIdTable[_soundsPlaying++] = songId; + _soundsPlaying &= 0x0F; + + return 0; +} + +int AdlibDriver::snd_unkOpcode2(va_list &list) { + warning("unimplemented snd_unkOpcode2"); + return 0; +} + +int AdlibDriver::snd_unkOpcode3(va_list &list) { + int value = va_arg(list, int); + int loop = value; + if (value < 0) { + value = 0; + loop = 9; + } + loop -= value; + ++loop; + + while (loop--) { + _curChannel = value; + Channel &channel = _channels[_curChannel]; + channel.priority = 0; + channel.dataptr = 0; + if (value != 9) { + noteOff(channel); + } + ++value; + } + + return 0; +} + +int AdlibDriver::snd_readByte(va_list &list) { + int a = va_arg(list, int); + int b = va_arg(list, int); + uint8 *ptr = getProgram(a) + b; + return *ptr; +} + +int AdlibDriver::snd_writeByte(va_list &list) { + int a = va_arg(list, int); + int b = va_arg(list, int); + int c = va_arg(list, int); + uint8 *ptr = getProgram(a) + b; + uint8 oldValue = *ptr; + *ptr = (uint8)c; + return oldValue; +} + +int AdlibDriver::snd_getSoundTrigger(va_list &list) { + return _soundTrigger; +} + +int AdlibDriver::snd_unkOpcode4(va_list &list) { + warning("unimplemented snd_unkOpcode4"); + return 0; +} + +int AdlibDriver::snd_dummy(va_list &list) { + return 0; +} + +int AdlibDriver::snd_getNullvar4(va_list &list) { + warning("unimplemented snd_getNullvar4"); + return 0; +} + +int AdlibDriver::snd_setNullvar3(va_list &list) { + warning("unimplemented snd_setNullvar3"); + return 0; +} + +int AdlibDriver::snd_setFlag(va_list &list) { + int oldFlags = _flags; + _flags |= va_arg(list, int); + return oldFlags; +} + +int AdlibDriver::snd_clearFlag(va_list &list) { + int oldFlags = _flags; + _flags &= ~(va_arg(list, int)); + return oldFlags; +} + +// timer callback + +void AdlibDriver::callback() { + // lock(); + --_flagTrigger; + if (_flagTrigger < 0) + _flags &= ~8; + setupPrograms(); + executePrograms(); + + uint8 temp = _unkValue3; + _unkValue3 += _tempo; + if (_unkValue3 < temp) { + if (!(--_unkValue2)) { + _unkValue2 = _unkValue1; + ++_unkValue4; + } + } + // unlock(); +} + +void AdlibDriver::setupPrograms() { + while (_lastProcessed != _soundsPlaying) { + uint8 *ptr = getProgram(_soundIdTable[_lastProcessed]); + uint8 chan = *ptr++; + uint8 priority = *ptr++; + + // Only start this sound if its priority is higher than the one + // already playing. + + Channel &channel = _channels[chan]; + + if (priority >= channel.priority) { + initChannel(channel); + channel.priority = priority; + channel.dataptr = ptr; + channel.tempo = 0xFF; + channel.position = 0xFF; + channel.duration = 1; + unkOutput2(chan); + } + + ++_lastProcessed; + _lastProcessed &= 0x0F; + } +} + +// A few words on opcode parsing and timing: +// +// First of all, We simulate a timer callback 72 times per second. Each timeout +// we update each channel that has something to play. +// +// Each channel has its own individual tempo, which is added to its position. +// This will frequently cause the position to "wrap around" but that is +// intentional. In fact, it's the signal to go ahead and do more stuff with +// that channel. +// +// Each channel also has a duration, indicating how much time is left on the +// its current task. This duration is decreased by one. As long as it still has +// not reached zero, the only thing that can happen is that the note is turned +// off depending on manual or automatic note spacing. Once the duration reaches +// zero, a new set of musical opcodes are executed. +// +// An opcode is one byte, followed by a variable number of parameters. Since +// most opcodes have at least one one-byte parameter, we read that as well. Any +// opcode that doesn't have that one parameter is responsible for moving the +// data pointer back again. +// +// If the most significant bit of the opcode is 1, it's a function; call it. +// The opcode functions return either 0 (continue), 1 (stop) or 2 (stop, and do +// not run the effects callbacks). +// +// If the most significant bit of the opcode is 0, it's a note, and the first +// parameter is its duration. (There are cases where the duration is modified +// but that's an exception.) The note opcode is assumed to return 1, and is the +// last opcode unless its duration is zero. +// +// Finally, most of the times that the callback is called, it will invoke the +// effects callbacks. The final opcode in a set can prevent this, if it's a +// function and it returns anything other than 1. + +void AdlibDriver::executePrograms() { + // Each channel runs its own program. There are ten channels: One for + // each Adlib channel (0-8), plus one "control channel" (9) which is + // the one that tells the other channels what to do. + + for (_curChannel = 9; _curChannel >= 0; --_curChannel) { + int result = 1; + + if (!_channels[_curChannel].dataptr) { + continue; + } + + Channel &channel = _channels[_curChannel]; + _curRegOffset = _regOffset[_curChannel]; + + if (channel.tempoReset) { + channel.tempo = _tempo; + } + + uint8 backup = channel.position; + channel.position += channel.tempo; + if (channel.position < backup) { + if (--channel.duration) { + if (channel.duration == channel.spacing2) + noteOff(channel); + if (channel.duration == channel.spacing1 && _curChannel != 9) + noteOff(channel); + } else { + // An opcode is not allowed to modify its own + // data pointer except through the 'dataptr' + // parameter. To enforce that, we have to work + // on a copy of the data pointer. + // + // This fixes a subtle music bug where the + // wrong music would play when getting the + // quill in Kyra 1. + uint8 *dataptr = channel.dataptr; + while (dataptr) { + uint8 opcode = *dataptr++; + uint8 param = *dataptr++; + + if (opcode & 0x80) { + opcode &= 0x7F; + if (opcode >= _parserOpcodeTableSize) + opcode = _parserOpcodeTableSize - 1; + debugC(9, kDebugLevelSound, "Calling opcode '%s' (%d) (channel: %d)", _parserOpcodeTable[opcode].name, opcode, _curChannel); + result = (this->*(_parserOpcodeTable[opcode].function))(dataptr, channel, param); + channel.dataptr = dataptr; + if (result) + break; + } else { + debugC(9, kDebugLevelSound, "Note on opcode 0x%02X (duration: %d) (channel: %d)", opcode, param, _curChannel); + setupNote(opcode, channel); + noteOn(channel); + setupDuration(param, channel); + if (param) { + channel.dataptr = dataptr; + break; + } + } + } + } + } + + if (result == 1) { + if (channel.primaryEffect) + (this->*(channel.primaryEffect))(channel); + if (channel.secondaryEffect) + (this->*(channel.secondaryEffect))(channel); + } + } +} + +// + +void AdlibDriver::resetAdlibState() { + debugC(9, kDebugLevelSound, "resetAdlibState()"); + _rnd = 0x1234; + + // Authorize the control of the waveforms + writeOPL(0x01, 0x20); + + // Select FM music mode + writeOPL(0x08, 0x00); + + // I would guess the main purpose of this is to turn off the rhythm, + // thus allowing us to use 9 melodic voices instead of 6. + writeOPL(0xBD, 0x00); + + int loop = 10; + while (loop--) { + if (loop != 9) { + // Silence the channel + writeOPL(0x40 + _regOffset[loop], 0x3F); + writeOPL(0x43 + _regOffset[loop], 0x3F); + } + initChannel(_channels[loop]); + } +} + +// Old calling style: output0x388(0xABCD) +// New calling style: writeOPL(0xAB, 0xCD) + +void AdlibDriver::writeOPL(byte reg, byte val) { + opl->write(reg, val); +} + +void AdlibDriver::initChannel(Channel &channel) { + debugC(9, kDebugLevelSound, "initChannel(%lu)", (long)(&channel - _channels)); + memset(&channel.dataptr, 0, sizeof(Channel) - ((char*)&channel.dataptr - (char*)&channel)); + + channel.tempo = 0xFF; + channel.priority = 0; + // normally here are nullfuncs but we set 0 for now + channel.primaryEffect = 0; + channel.secondaryEffect = 0; + channel.spacing1 = 1; +} + +void AdlibDriver::noteOff(Channel &channel) { + debugC(9, kDebugLevelSound, "noteOff(%lu)", (long)(&channel - _channels)); + + // The control channel has no corresponding Adlib channel + + if (_curChannel >= 9) + return; + + // When the rhythm section is enabled, channels 6, 7 and 8 are special. + + if (_rhythmSectionBits && _curChannel >= 6) + return; + + // This means the "Key On" bit will always be 0 + channel.regBx &= 0xDF; + + // Octave / F-Number / Key-On + writeOPL(0xB0 + _curChannel, channel.regBx); +} + +void AdlibDriver::unkOutput2(uint8 chan) { + debugC(9, kDebugLevelSound, "unkOutput2(%d)", chan); + + // The control channel has no corresponding Adlib channel + + if (chan >= 9) + return; + + // I believe this has to do with channels 6, 7, and 8 being special + // when Adlib's rhythm section is enabled. + + if (_rhythmSectionBits && chan >= 6) + return; + + uint8 offset = _regOffset[chan]; + + // The channel is cleared: First the attack/delay rate, then the + // sustain level/release rate, and finally the note is turned off. + + writeOPL(0x60 + offset, 0xFF); + writeOPL(0x63 + offset, 0xFF); + + writeOPL(0x80 + offset, 0xFF); + writeOPL(0x83 + offset, 0xFF); + + writeOPL(0xB0 + chan, 0x00); + + // ...and then the note is turned on again, with whatever value is + // still lurking in the A0 + chan register, but everything else - + // including the two most significant frequency bit, and the octave - + // set to zero. + // + // This is very strange behaviour, and causes problems with the ancient + // FMOPL code we borrowed from AdPlug. I've added a workaround. See + // fmopl.cpp for more details. + // + // More recent versions of the MAME FMOPL don't seem to have this + // problem, but cannot currently be used because of licensing and + // performance issues. + // + // Ken Silverman's Adlib emulator (which can be found on his Web page - + // http://www.advsys.net/ken - and as part of AdPlug) also seems to be + // immune, but is apparently not as feature complete as MAME's. + + writeOPL(0xB0 + chan, 0x20); +} + +// I believe this is a random number generator. It actually does seem to +// generate an even distribution of almost all numbers from 0 through 65535, +// though in my tests some numbers were never generated. + +uint16 AdlibDriver::getRandomNr() { + _rnd += 0x9248; + uint16 lowBits = _rnd & 7; + _rnd >>= 3; + _rnd |= (lowBits << 13); + return _rnd; +} + +void AdlibDriver::setupDuration(uint8 duration, Channel &channel) { + debugC(9, kDebugLevelSound, "setupDuration(%d, %lu)", duration, (long)(&channel - _channels)); + if (channel.durationRandomness) { + channel.duration = duration + (getRandomNr() & channel.durationRandomness); + return; + } + if (channel.fractionalSpacing) { + channel.spacing2 = (duration >> 3) * channel.fractionalSpacing; + } + channel.duration = duration; +} + +// This function may or may not play the note. It's usually followed by a call +// to noteOn(), which will always play the current note. + +void AdlibDriver::setupNote(uint8 rawNote, Channel &channel, bool flag) { + debugC(9, kDebugLevelSound, "setupNote(%d, %lu)", rawNote, (long)(&channel - _channels)); + + channel.rawNote = rawNote; + + int8 note = (rawNote & 0x0F) + channel.baseNote; + int8 octave = ((rawNote + channel.baseOctave) >> 4) & 0x0F; + + // There are only twelve notes. If we go outside that, we have to + // adjust the note and octave. + + if (note >= 12) { + note -= 12; + octave++; + } else if (note < 0) { + note += 12; + octave--; + } + + // The calculation of frequency looks quite different from the original + // disassembly at a first glance, but when you consider that the + // largest possible value would be 0x0246 + 0xFF + 0x47 (and that's if + // baseFreq is unsigned), freq is still a 10-bit value, just as it + // should be to fit in the Ax and Bx registers. + // + // If it were larger than that, it could have overflowed into the + // octave bits, and that could possibly have been used in some sound. + // But as it is now, I can't see any way it would happen. + + uint16 freq = _unkTable[note] + channel.baseFreq; + + // When called from callback 41, the behaviour is slightly different: + // We adjust the frequency, even when channel.unk16 is 0. + + if (channel.unk16 || flag) { + const uint8 *table; + + if (channel.unk16 >= 0) { + table = _unkTables[(channel.rawNote & 0x0F) + 2]; + freq += table[channel.unk16]; + } else { + table = _unkTables[channel.rawNote & 0x0F]; + freq -= table[-channel.unk16]; + } + } + + channel.regAx = freq & 0xFF; + channel.regBx = (channel.regBx & 0x20) | (octave << 2) | ((freq >> 8) & 0x03); + + // Keep the note on or off + writeOPL(0xA0 + _curChannel, channel.regAx); + writeOPL(0xB0 + _curChannel, channel.regBx); +} + +void AdlibDriver::setupInstrument(uint8 regOffset, uint8 *dataptr, Channel &channel) { + debugC(9, kDebugLevelSound, "setupInstrument(%d, %p, %lu)", regOffset, (const void *)dataptr, (long)(&channel - _channels)); + // Amplitude Modulation / Vibrato / Envelope Generator Type / + // Keyboard Scaling Rate / Modulator Frequency Multiple + writeOPL(0x20 + regOffset, *dataptr++); + writeOPL(0x23 + regOffset, *dataptr++); + + uint8 temp = *dataptr++; + + // Feedback / Algorithm + + // It is very likely that _curChannel really does refer to the same + // channel as regOffset, but there's only one Cx register per channel. + + writeOPL(0xC0 + _curChannel, temp); + + // The algorithm bit. I don't pretend to understand this fully, but + // "If set to 0, operator 1 modulates operator 2. In this case, + // operator 2 is the only one producing sound. If set to 1, both + // operators produce sound directly. Complex sounds are more easily + // created if the algorithm is set to 0." + + channel.twoChan = temp & 1; + + // Waveform Select + writeOPL(0xE0 + regOffset, *dataptr++); + writeOPL(0xE3 + regOffset, *dataptr++); + + channel.opLevel1 = *dataptr++; + channel.opLevel2 = *dataptr++; + + // Level Key Scaling / Total Level + writeOPL(0x40 + regOffset, calculateOpLevel1(channel)); + writeOPL(0x43 + regOffset, calculateOpLevel2(channel)); + + // Attack Rate / Decay Rate + writeOPL(0x60 + regOffset, *dataptr++); + writeOPL(0x63 + regOffset, *dataptr++); + + // Sustain Level / Release Rate + writeOPL(0x80 + regOffset, *dataptr++); + writeOPL(0x83 + regOffset, *dataptr++); +} + +// Apart from playing the note, this function also updates the variables for +// primary effect 2. + +void AdlibDriver::noteOn(Channel &channel) { + debugC(9, kDebugLevelSound, "noteOn(%lu)", (long)(&channel - _channels)); + + // The "note on" bit is set, and the current note is played. + + channel.regBx |= 0x20; + writeOPL(0xB0 + _curChannel, channel.regBx); + + int8 shift = 9 - channel.unk33; + uint16 temp = channel.regAx | (channel.regBx << 8); + channel.unk37 = ((temp & 0x3FF) >> shift) & 0xFF; + channel.unk38 = channel.unk36; +} + +void AdlibDriver::adjustVolume(Channel &channel) { + debugC(9, kDebugLevelSound, "adjustVolume(%lu)", (long)(&channel - _channels)); + // Level Key Scaling / Total Level + + writeOPL(0x43 + _regOffset[_curChannel], calculateOpLevel2(channel)); + if (channel.twoChan) + writeOPL(0x40 + _regOffset[_curChannel], calculateOpLevel1(channel)); +} + +// This is presumably only used for some sound effects, e.g. Malcolm blowing up +// the trees in the intro (but not the effect where he "booby-traps" the big +// tree) and turning Kallak to stone. Related functions and variables: +// +// update_setupPrimaryEffect1() +// - Initialises unk29, unk30 and unk31 +// - unk29 is not further modified +// - unk30 is not further modified, except by update_removePrimaryEffect1() +// +// update_removePrimaryEffect1() +// - Deinitialises unk30 +// +// unk29 - determines how often the notes are played +// unk30 - modifies the frequency +// unk31 - determines how often the notes are played + +void AdlibDriver::primaryEffect1(Channel &channel) { + debugC(9, kDebugLevelSound, "Calling primaryEffect1 (channel: %d)", _curChannel); + uint8 temp = channel.unk31; + channel.unk31 += channel.unk29; + if (channel.unk31 >= temp) + return; + + // Initialise unk1 to the current frequency + uint16 unk1 = ((channel.regBx & 3) << 8) | channel.regAx; + + // This is presumably to shift the "note on" bit so far to the left + // that it won't be affected by any of the calculations below. + uint16 unk2 = ((channel.regBx & 0x20) << 8) | (channel.regBx & 0x1C); + + int16 unk3 = (int16)channel.unk30; + + if (unk3 >= 0) { + unk1 += unk3; + if (unk1 >= 734) { + // The new frequency is too high. Shift it down and go + // up one octave. + unk1 >>= 1; + if (!(unk1 & 0x3FF)) + ++unk1; + unk2 = (unk2 & 0xFF00) | ((unk2 + 4) & 0xFF); + unk2 &= 0xFF1C; + } + } else { + unk1 += unk3; + if (unk1 < 388) { + // The new frequency is too low. Shift it up and go + // down one octave. + unk1 <<= 1; + if (!(unk1 & 0x3FF)) + --unk1; + unk2 = (unk2 & 0xFF00) | ((unk2 - 4) & 0xFF); + unk2 &= 0xFF1C; + } + } + + // Make sure that the new frequency is still a 10-bit value. + unk1 &= 0x3FF; + + writeOPL(0xA0 + _curChannel, unk1 & 0xFF); + channel.regAx = unk1 & 0xFF; + + // Shift down the "note on" bit again. + uint8 value = unk1 >> 8; + value |= (unk2 >> 8) & 0xFF; + value |= unk2 & 0xFF; + + writeOPL(0xB0 + _curChannel, value); + channel.regBx = value; +} + +// This is presumably only used for some sound effects, e.g. Malcolm entering +// and leaving Kallak's hut. Related functions and variables: +// +// update_setupPrimaryEffect2() +// - Initialises unk32, unk33, unk34, unk35 and unk36 +// - unk32 is not further modified +// - unk33 is not further modified +// - unk34 is a countdown that gets reinitialised to unk35 on zero +// - unk35 is based on unk34 and not further modified +// - unk36 is not further modified +// +// noteOn() +// - Plays the current note +// - Updates unk37 with a new (lower?) frequency +// - Copies unk36 to unk38. The unk38 variable is a countdown. +// +// unk32 - determines how often the notes are played +// unk33 - modifies the frequency +// unk34 - countdown, updates frequency on zero +// unk35 - initialiser for unk34 countdown +// unk36 - initialiser for unk38 countdown +// unk37 - frequency +// unk38 - countdown, begins playing on zero +// unk41 - determines how often the notes are played +// +// Note that unk41 is never initialised. Not that it should matter much, but it +// is a bit sloppy. + +void AdlibDriver::primaryEffect2(Channel &channel) { + debugC(9, kDebugLevelSound, "Calling primaryEffect2 (channel: %d)", _curChannel); + if (channel.unk38) { + --channel.unk38; + return; + } + + uint8 temp = channel.unk41; + channel.unk41 += channel.unk32; + if (channel.unk41 < temp) { + uint16 unk1 = channel.unk37; + if (!(--channel.unk34)) { + unk1 ^= 0xFFFF; + ++unk1; + channel.unk37 = unk1; + channel.unk34 = channel.unk35; + } + + uint16 unk2 = (channel.regAx | (channel.regBx << 8)) & 0x3FF; + unk2 += unk1; + + channel.regAx = unk2 & 0xFF; + channel.regBx = (channel.regBx & 0xFC) | (unk2 >> 8); + + // Octave / F-Number / Key-On + writeOPL(0xA0 + _curChannel, channel.regAx); + writeOPL(0xB0 + _curChannel, channel.regBx); + } +} + +// I don't know where this is used. The same operation is performed several +// times on the current channel, using a chunk of the _soundData[] buffer for +// parameters. The parameters are used starting at the end of the chunk. +// +// Since we use _curRegOffset to specify the final register, it's quite +// unlikely that this function is ever used to play notes. It's probably only +// used to modify the sound. Another thing that supports this idea is that it +// can be combined with any of the effects callbacks above. +// +// Related functions and variables: +// +// update_setupSecondaryEffect1() +// - Initialies unk18, unk19, unk20, unk21, unk22 and offset +// - unk19 is not further modified +// - unk20 is not further modified +// - unk22 is not further modified +// - offset is not further modified +// +// unk18 - determines how often the operation is performed +// unk19 - determines how often the operation is performed +// unk20 - the start index into the data chunk +// unk21 - the current index into the data chunk +// unk22 - the operation to perform +// offset - the offset to the data chunk + +void AdlibDriver::secondaryEffect1(Channel &channel) { + debugC(9, kDebugLevelSound, "Calling secondaryEffect1 (channel: %d)", _curChannel); + uint8 temp = channel.unk18; + channel.unk18 += channel.unk19; + if (channel.unk18 < temp) { + if (--channel.unk21 < 0) { + channel.unk21 = channel.unk20; + } + writeOPL(channel.unk22 + _curRegOffset, _soundData[channel.offset + channel.unk21]); + } +} + +uint8 AdlibDriver::calculateOpLevel1(Channel &channel) { + int8 value = channel.opLevel1 & 0x3F; + + if (channel.twoChan) { + value += channel.opExtraLevel1; + value += channel.opExtraLevel2; + value += channel.opExtraLevel3; + } + + // Preserve the scaling level bits from opLevel1 + + return checkValue(value) | (channel.opLevel1 & 0xC0); +} + +uint8 AdlibDriver::calculateOpLevel2(Channel &channel) { + int8 value = channel.opLevel2 & 0x3F; + + value += channel.opExtraLevel1; + value += channel.opExtraLevel2; + value += channel.opExtraLevel3; + + // Preserve the scaling level bits from opLevel2 + + return checkValue(value) | (channel.opLevel2 & 0xC0); +} + +// parser opcodes + +int AdlibDriver::update_setRepeat(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.repeatCounter = value; + return 0; +} + +int AdlibDriver::update_checkRepeat(uint8 *&dataptr, Channel &channel, uint8 value) { + ++dataptr; + if (--channel.repeatCounter) { + int16 add = READ_LE_UINT16(dataptr - 2); + dataptr += add; + } + return 0; +} + +int AdlibDriver::update_setupProgram(uint8 *&dataptr, Channel &channel, uint8 value) { + if (value == 0xFF) + return 0; + + uint8 *ptr = getProgram(value); + uint8 chan = *ptr++; + uint8 priority = *ptr++; + + Channel &channel2 = _channels[chan]; + + if (priority >= channel2.priority) { + _flagTrigger = 1; + _flags |= 8; + initChannel(channel2); + channel2.priority = priority; + channel2.dataptr = ptr; + channel2.tempo = 0xFF; + channel2.position = 0xFF; + channel2.duration = 1; + unkOutput2(chan); + } + + return 0; +} + +int AdlibDriver::update_setNoteSpacing(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.spacing1 = value; + return 0; +} + +int AdlibDriver::update_jump(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + int16 add = READ_LE_UINT16(dataptr); dataptr += 2; + dataptr += add; + return 0; +} + +int AdlibDriver::update_jumpToSubroutine(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + int16 add = READ_LE_UINT16(dataptr); dataptr += 2; + channel.dataptrStack[channel.dataptrStackPos++] = dataptr; + dataptr += add; + return 0; +} + +int AdlibDriver::update_returnFromSubroutine(uint8 *&dataptr, Channel &channel, uint8 value) { + dataptr = channel.dataptrStack[--channel.dataptrStackPos]; + return 0; +} + +int AdlibDriver::update_setBaseOctave(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.baseOctave = value; + return 0; +} + +int AdlibDriver::update_stopChannel(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.priority = 0; + if (_curChannel != 9) { + noteOff(channel); + } + dataptr = 0; + return 2; +} + +int AdlibDriver::update_playRest(uint8 *&dataptr, Channel &channel, uint8 value) { + setupDuration(value, channel); + noteOff(channel); + return (value != 0); +} + +int AdlibDriver::update_writeAdlib(uint8 *&dataptr, Channel &channel, uint8 value) { + writeOPL(value, *dataptr++); + return 0; +} + +int AdlibDriver::update_setupNoteAndDuration(uint8 *&dataptr, Channel &channel, uint8 value) { + setupNote(value, channel); + value = *dataptr++; + setupDuration(value, channel); + return (value != 0); +} + +int AdlibDriver::update_setBaseNote(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.baseNote = value; + return 0; +} + +int AdlibDriver::update_setupSecondaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk18 = value; + channel.unk19 = value; + channel.unk20 = channel.unk21 = *dataptr++; + channel.unk22 = *dataptr++; + channel.offset = READ_LE_UINT16(dataptr); dataptr += 2; + channel.secondaryEffect = &AdlibDriver::secondaryEffect1; + return 0; +} + +int AdlibDriver::update_stopOtherChannel(uint8 *&dataptr, Channel &channel, uint8 value) { + Channel &channel2 = _channels[value]; + channel2.duration = 0; + channel2.priority = 0; + channel2.dataptr = 0; + return 0; +} + +int AdlibDriver::update_waitForEndOfProgram(uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 *ptr = getProgram(value); + uint8 chan = *ptr; + + if (!_channels[chan].dataptr) { + return 0; + } + + dataptr -= 2; + return 2; +} + +int AdlibDriver::update_setupInstrument(uint8 *&dataptr, Channel &channel, uint8 value) { + setupInstrument(_curRegOffset, getInstrument(value), channel); + return 0; +} + +int AdlibDriver::update_setupPrimaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk29 = value; + channel.unk30 = READ_BE_UINT16(dataptr); + dataptr += 2; + channel.primaryEffect = &AdlibDriver::primaryEffect1; + channel.unk31 = 0xFF; + return 0; +} + +int AdlibDriver::update_removePrimaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.primaryEffect = 0; + channel.unk30 = 0; + return 0; +} + +int AdlibDriver::update_setBaseFreq(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.baseFreq = value; + return 0; +} + +int AdlibDriver::update_setupPrimaryEffect2(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk32 = value; + channel.unk33 = *dataptr++; + uint8 temp = *dataptr++; + channel.unk34 = temp + 1; + channel.unk35 = temp << 1; + channel.unk36 = *dataptr++; + channel.primaryEffect = &AdlibDriver::primaryEffect2; + return 0; +} + +int AdlibDriver::update_setPriority(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.priority = value; + return 0; +} + +int AdlibDriver::updateCallback23(uint8 *&dataptr, Channel &channel, uint8 value) { + value >>= 1; + _unkValue1 = _unkValue2 = value; + _unkValue3 = 0xFF; + _unkValue4 = _unkValue5 = 0; + return 0; +} + +int AdlibDriver::updateCallback24(uint8 *&dataptr, Channel &channel, uint8 value) { + if (_unkValue5) { + if (_unkValue4 & value) { + _unkValue5 = 0; + return 0; + } + } + + if (!(value & _unkValue4)) { + ++_unkValue5; + } + + dataptr -= 2; + channel.duration = 1; + return 2; +} + +int AdlibDriver::update_setExtraLevel1(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.opExtraLevel1 = value; + adjustVolume(channel); + return 0; +} + +int AdlibDriver::update_setupDuration(uint8 *&dataptr, Channel &channel, uint8 value) { + setupDuration(value, channel); + return (value != 0); +} + +int AdlibDriver::update_playNote(uint8 *&dataptr, Channel &channel, uint8 value) { + setupDuration(value, channel); + noteOn(channel); + return (value != 0); +} + +int AdlibDriver::update_setFractionalNoteSpacing(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.fractionalSpacing = value & 7; + return 0; +} + +int AdlibDriver::update_setTempo(uint8 *&dataptr, Channel &channel, uint8 value) { + _tempo = value; + return 0; +} + +int AdlibDriver::update_removeSecondaryEffect1(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.secondaryEffect = 0; + return 0; +} + +int AdlibDriver::update_setChannelTempo(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.tempo = value; + return 0; +} + +int AdlibDriver::update_setExtraLevel3(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.opExtraLevel3 = value; + return 0; +} + +int AdlibDriver::update_setExtraLevel2(uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + + _curChannel = value; + Channel &channel2 = _channels[value]; + channel2.opExtraLevel2 = *dataptr++; + adjustVolume(channel2); + + _curChannel = channelBackUp; + return 0; +} + +int AdlibDriver::update_changeExtraLevel2(uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + + _curChannel = value; + Channel &channel2 = _channels[value]; + channel2.opExtraLevel2 += *dataptr++; + adjustVolume(channel2); + + _curChannel = channelBackUp; + return 0; +} + +// Apart from initialising to zero, these two functions are the only ones that +// modify _vibratoAndAMDepthBits. + +int AdlibDriver::update_setAMDepth(uint8 *&dataptr, Channel &channel, uint8 value) { + if (value & 1) + _vibratoAndAMDepthBits |= 0x80; + else + _vibratoAndAMDepthBits &= 0x7F; + + writeOPL(0xBD, _vibratoAndAMDepthBits); + return 0; +} + +int AdlibDriver::update_setVibratoDepth(uint8 *&dataptr, Channel &channel, uint8 value) { + if (value & 1) + _vibratoAndAMDepthBits |= 0x40; + else + _vibratoAndAMDepthBits &= 0xBF; + + writeOPL(0xBD, _vibratoAndAMDepthBits); + return 0; +} + +int AdlibDriver::update_changeExtraLevel1(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.opExtraLevel1 += value; + adjustVolume(channel); + return 0; +} + +int AdlibDriver::updateCallback38(uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + + _curChannel = value; + Channel &channel2 = _channels[value]; + channel2.duration = channel2.priority = 0; + channel2.dataptr = 0; + channel2.opExtraLevel2 = 0; + + if (value != 9) { + uint8 outValue = _regOffset[value]; + + // Feedback strength / Connection type + writeOPL(0xC0 + _curChannel, 0x00); + + // Key scaling level / Operator output level + writeOPL(0x43 + outValue, 0x3F); + + // Sustain Level / Release Rate + writeOPL(0x83 + outValue, 0xFF); + + // Key On / Octave / Frequency + writeOPL(0xB0 + _curChannel, 0x00); + } + + _curChannel = channelBackUp; + return 0; +} + +int AdlibDriver::updateCallback39(uint8 *&dataptr, Channel &channel, uint8 value) { + uint16 unk = *dataptr++; + unk |= value << 8; + unk &= getRandomNr(); + + uint16 unk2 = ((channel.regBx & 0x1F) << 8) | channel.regAx; + unk2 += unk; + unk2 |= ((channel.regBx & 0x20) << 8); + + // Frequency + writeOPL(0xA0 + _curChannel, unk2 & 0xFF); + + // Key On / Octave / Frequency + writeOPL(0xB0 + _curChannel, (unk2 & 0xFF00) >> 8); + + return 0; +} + +int AdlibDriver::update_removePrimaryEffect2(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.primaryEffect = 0; + return 0; +} + +int AdlibDriver::updateCallback41(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk16 = value; + setupNote(channel.rawNote, channel, true); + return 0; +} + +int AdlibDriver::update_resetToGlobalTempo(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + channel.tempo = _tempo; + return 0; +} + +int AdlibDriver::update_nop1(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + return 0; +} + +int AdlibDriver::update_setDurationRandomness(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.durationRandomness = value; + return 0; +} + +int AdlibDriver::update_changeChannelTempo(uint8 *&dataptr, Channel &channel, uint8 value) { + int tempo = channel.tempo + (int8)value; + + if (tempo <= 0) + tempo = 1; + else if (tempo > 255) + tempo = 255; + + channel.tempo = tempo; + return 0; +} + +int AdlibDriver::updateCallback46(uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 entry = *dataptr++; + _tablePtr1 = _unkTable2[entry++]; + _tablePtr2 = _unkTable2[entry]; + if (value == 2) { + // Frequency + writeOPL(0xA0, _tablePtr2[0]); + } + return 0; +} + +// TODO: This is really the same as update_nop1(), so they should be combined +// into one single update_nop(). + +int AdlibDriver::update_nop2(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + return 0; +} + +int AdlibDriver::update_setupRhythmSection(uint8 *&dataptr, Channel &channel, uint8 value) { + int channelBackUp = _curChannel; + int regOffsetBackUp = _curRegOffset; + + _curChannel = 6; + _curRegOffset = _regOffset[6]; + + setupInstrument(_curRegOffset, getInstrument(value), channel); + _unkValue6 = channel.opLevel2; + + _curChannel = 7; + _curRegOffset = _regOffset[7]; + + setupInstrument(_curRegOffset, getInstrument(*dataptr++), channel); + _unkValue7 = channel.opLevel1; + _unkValue8 = channel.opLevel2; + + _curChannel = 8; + _curRegOffset = _regOffset[8]; + + setupInstrument(_curRegOffset, getInstrument(*dataptr++), channel); + _unkValue9 = channel.opLevel1; + _unkValue10 = channel.opLevel2; + + // Octave / F-Number / Key-On for channels 6, 7 and 8 + + _channels[6].regBx = *dataptr++ & 0x2F; + writeOPL(0xB6, _channels[6].regBx); + writeOPL(0xA6, *dataptr++); + + _channels[7].regBx = *dataptr++ & 0x2F; + writeOPL(0xB7, _channels[7].regBx); + writeOPL(0xA7, *dataptr++); + + _channels[8].regBx = *dataptr++ & 0x2F; + writeOPL(0xB8, _channels[8].regBx); + writeOPL(0xA8, *dataptr++); + + _rhythmSectionBits = 0x20; + + _curRegOffset = regOffsetBackUp; + _curChannel = channelBackUp; + return 0; +} + +int AdlibDriver::update_playRhythmSection(uint8 *&dataptr, Channel &channel, uint8 value) { + // Any instrument that we want to play, and which was already playing, + // is temporarily keyed off. Instruments that were off already, or + // which we don't want to play, retain their old on/off status. This is + // probably so that the instrument's envelope is played from its + // beginning again... + + writeOPL(0xBD, (_rhythmSectionBits & ~(value & 0x1F)) | 0x20); + + // ...but since we only set the rhythm instrument bits, and never clear + // them (until the entire rhythm section is disabled), I'm not sure how + // useful the cleverness above is. We could perhaps simply turn off all + // the rhythm instruments instead. + + _rhythmSectionBits |= value; + + writeOPL(0xBD, _vibratoAndAMDepthBits | 0x20 | _rhythmSectionBits); + return 0; +} + +int AdlibDriver::update_removeRhythmSection(uint8 *&dataptr, Channel &channel, uint8 value) { + --dataptr; + _rhythmSectionBits = 0; + + // All the rhythm bits are cleared. The AM and Vibrato depth bits + // remain unchanged. + + writeOPL(0xBD, _vibratoAndAMDepthBits); + return 0; +} + +int AdlibDriver::updateCallback51(uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 value2 = *dataptr++; + + if (value & 1) { + _unkValue12 = value2; + + // Channel 7, op1: Level Key Scaling / Total Level + writeOPL(0x51, checkValue(value2 + _unkValue7 + _unkValue11 + _unkValue12)); + } + + if (value & 2) { + _unkValue14 = value2; + + // Channel 8, op2: Level Key Scaling / Total Level + writeOPL(0x55, checkValue(value2 + _unkValue10 + _unkValue13 + _unkValue14)); + } + + if (value & 4) { + _unkValue15 = value2; + + // Channel 8, op1: Level Key Scaling / Total Level + writeOPL(0x52, checkValue(value2 + _unkValue9 + _unkValue16 + _unkValue15)); + } + + if (value & 8) { + _unkValue18 = value2; + + // Channel 7, op2: Level Key Scaling / Total Level + writeOPL(0x54, checkValue(value2 + _unkValue8 + _unkValue17 + _unkValue18)); + } + + if (value & 16) { + _unkValue20 = value2; + + // Channel 6, op2: Level Key Scaling / Total Level + writeOPL(0x53, checkValue(value2 + _unkValue6 + _unkValue19 + _unkValue20)); + } + + return 0; +} + +int AdlibDriver::updateCallback52(uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 value2 = *dataptr++; + + if (value & 1) { + _unkValue11 = checkValue(value2 + _unkValue7 + _unkValue11 + _unkValue12); + + // Channel 7, op1: Level Key Scaling / Total Level + writeOPL(0x51, _unkValue11); + } + + if (value & 2) { + _unkValue13 = checkValue(value2 + _unkValue10 + _unkValue13 + _unkValue14); + + // Channel 8, op2: Level Key Scaling / Total Level + writeOPL(0x55, _unkValue13); + } + + if (value & 4) { + _unkValue16 = checkValue(value2 + _unkValue9 + _unkValue16 + _unkValue15); + + // Channel 8, op1: Level Key Scaling / Total Level + writeOPL(0x52, _unkValue16); + } + + if (value & 8) { + _unkValue17 = checkValue(value2 + _unkValue8 + _unkValue17 + _unkValue18); + + // Channel 7, op2: Level Key Scaling / Total Level + writeOPL(0x54, _unkValue17); + } + + if (value & 16) { + _unkValue19 = checkValue(value2 + _unkValue6 + _unkValue19 + _unkValue20); + + // Channel 6, op2: Level Key Scaling / Total Level + writeOPL(0x53, _unkValue19); + } + + return 0; +} + +int AdlibDriver::updateCallback53(uint8 *&dataptr, Channel &channel, uint8 value) { + uint8 value2 = *dataptr++; + + if (value & 1) { + _unkValue11 = value2; + + // Channel 7, op1: Level Key Scaling / Total Level + writeOPL(0x51, checkValue(value2 + _unkValue7 + _unkValue12)); + } + + if (value & 2) { + _unkValue13 = value2; + + // Channel 8, op2: Level Key Scaling / Total Level + writeOPL(0x55, checkValue(value2 + _unkValue10 + _unkValue14)); + } + + if (value & 4) { + _unkValue16 = value2; + + // Channel 8, op1: Level Key Scaling / Total Level + writeOPL(0x52, checkValue(value2 + _unkValue9 + _unkValue15)); + } + + if (value & 8) { + _unkValue17 = value2; + + // Channel 7, op2: Level Key Scaling / Total Level + writeOPL(0x54, checkValue(value2 + _unkValue8 + _unkValue18)); + } + + if (value & 16) { + _unkValue19 = value2; + + // Channel 6, op2: Level Key Scaling / Total Level + writeOPL(0x53, checkValue(value2 + _unkValue6 + _unkValue20)); + } + + return 0; +} + +int AdlibDriver::update_setSoundTrigger(uint8 *&dataptr, Channel &channel, uint8 value) { + _soundTrigger = value; + return 0; +} + +int AdlibDriver::update_setTempoReset(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.tempoReset = value; + return 0; +} + +int AdlibDriver::updateCallback56(uint8 *&dataptr, Channel &channel, uint8 value) { + channel.unk39 = value; + channel.unk40 = *dataptr++; + return 0; +} + +// static res + +#define COMMAND(x) { &AdlibDriver::x, #x } + +void AdlibDriver::setupOpcodeList() { + static const OpcodeEntry opcodeList[] = { + COMMAND(snd_ret0x100), + COMMAND(snd_ret0x1983), + COMMAND(snd_initDriver), + COMMAND(snd_deinitDriver), + COMMAND(snd_setSoundData), + COMMAND(snd_unkOpcode1), + COMMAND(snd_startSong), + COMMAND(snd_unkOpcode2), + COMMAND(snd_unkOpcode3), + COMMAND(snd_readByte), + COMMAND(snd_writeByte), + COMMAND(snd_getSoundTrigger), + COMMAND(snd_unkOpcode4), + COMMAND(snd_dummy), + COMMAND(snd_getNullvar4), + COMMAND(snd_setNullvar3), + COMMAND(snd_setFlag), + COMMAND(snd_clearFlag) + }; + + _opcodeList = opcodeList; + _opcodesEntries = ARRAYSIZE(opcodeList); +} + +void AdlibDriver::setupParserOpcodeTable() { + static const ParserOpcode parserOpcodeTable[] = { + // 0 + COMMAND(update_setRepeat), + COMMAND(update_checkRepeat), + COMMAND(update_setupProgram), + COMMAND(update_setNoteSpacing), + + // 4 + COMMAND(update_jump), + COMMAND(update_jumpToSubroutine), + COMMAND(update_returnFromSubroutine), + COMMAND(update_setBaseOctave), + + // 8 + COMMAND(update_stopChannel), + COMMAND(update_playRest), + COMMAND(update_writeAdlib), + COMMAND(update_setupNoteAndDuration), + + // 12 + COMMAND(update_setBaseNote), + COMMAND(update_setupSecondaryEffect1), + COMMAND(update_stopOtherChannel), + COMMAND(update_waitForEndOfProgram), + + // 16 + COMMAND(update_setupInstrument), + COMMAND(update_setupPrimaryEffect1), + COMMAND(update_removePrimaryEffect1), + COMMAND(update_setBaseFreq), + + // 20 + COMMAND(update_stopChannel), + COMMAND(update_setupPrimaryEffect2), + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + + // 24 + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + COMMAND(update_setPriority), + COMMAND(update_stopChannel), + + // 28 + COMMAND(updateCallback23), + COMMAND(updateCallback24), + COMMAND(update_setExtraLevel1), + COMMAND(update_stopChannel), + + // 32 + COMMAND(update_setupDuration), + COMMAND(update_playNote), + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + + // 36 + COMMAND(update_setFractionalNoteSpacing), + COMMAND(update_stopChannel), + COMMAND(update_setTempo), + COMMAND(update_removeSecondaryEffect1), + + // 40 + COMMAND(update_stopChannel), + COMMAND(update_setChannelTempo), + COMMAND(update_stopChannel), + COMMAND(update_setExtraLevel3), + + // 44 + COMMAND(update_setExtraLevel2), + COMMAND(update_changeExtraLevel2), + COMMAND(update_setAMDepth), + COMMAND(update_setVibratoDepth), + + // 48 + COMMAND(update_changeExtraLevel1), + COMMAND(update_stopChannel), + COMMAND(update_stopChannel), + COMMAND(updateCallback38), + + // 52 + COMMAND(update_stopChannel), + COMMAND(updateCallback39), + COMMAND(update_removePrimaryEffect2), + COMMAND(update_stopChannel), + + // 56 + COMMAND(update_stopChannel), + COMMAND(updateCallback41), + COMMAND(update_resetToGlobalTempo), + COMMAND(update_nop1), + + // 60 + COMMAND(update_setDurationRandomness), + COMMAND(update_changeChannelTempo), + COMMAND(update_stopChannel), + COMMAND(updateCallback46), + + // 64 + COMMAND(update_nop2), + COMMAND(update_setupRhythmSection), + COMMAND(update_playRhythmSection), + COMMAND(update_removeRhythmSection), + + // 68 + COMMAND(updateCallback51), + COMMAND(updateCallback52), + COMMAND(updateCallback53), + COMMAND(update_setSoundTrigger), + + // 72 + COMMAND(update_setTempoReset), + COMMAND(updateCallback56), + COMMAND(update_stopChannel) + }; + + _parserOpcodeTable = parserOpcodeTable; + _parserOpcodeTableSize = ARRAYSIZE(parserOpcodeTable); +} +#undef COMMAND + +// This table holds the register offset for operator 1 for each of the nine +// channels. To get the register offset for operator 2, simply add 3. + +const uint8 AdlibDriver::_regOffset[] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, + 0x12 +}; + +// Given the size of this table, and the range of its values, it's probably the +// F-Numbers (10 bits) for the notes of the 12-tone scale. However, it does not +// match the table in the Adlib documentation I've seen. + +const uint16 AdlibDriver::_unkTable[] = { + 0x0134, 0x0147, 0x015A, 0x016F, 0x0184, 0x019C, 0x01B4, 0x01CE, 0x01E9, + 0x0207, 0x0225, 0x0246 +}; + +// These tables are currently only used by updateCallback46(), which only ever +// uses the first element of one of the sub-tables. + +const uint8 *AdlibDriver::_unkTable2[] = { + AdlibDriver::_unkTable2_1, + AdlibDriver::_unkTable2_2, + AdlibDriver::_unkTable2_1, + AdlibDriver::_unkTable2_2, + AdlibDriver::_unkTable2_3, + AdlibDriver::_unkTable2_2 +}; + +const uint8 AdlibDriver::_unkTable2_1[] = { + 0x50, 0x50, 0x4F, 0x4F, 0x4E, 0x4E, 0x4D, 0x4D, + 0x4C, 0x4C, 0x4B, 0x4B, 0x4A, 0x4A, 0x49, 0x49, + 0x48, 0x48, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45, + 0x44, 0x44, 0x43, 0x43, 0x42, 0x42, 0x41, 0x41, + 0x40, 0x40, 0x3F, 0x3F, 0x3E, 0x3E, 0x3D, 0x3D, + 0x3C, 0x3C, 0x3B, 0x3B, 0x3A, 0x3A, 0x39, 0x39, + 0x38, 0x38, 0x37, 0x37, 0x36, 0x36, 0x35, 0x35, + 0x34, 0x34, 0x33, 0x33, 0x32, 0x32, 0x31, 0x31, + 0x30, 0x30, 0x2F, 0x2F, 0x2E, 0x2E, 0x2D, 0x2D, + 0x2C, 0x2C, 0x2B, 0x2B, 0x2A, 0x2A, 0x29, 0x29, + 0x28, 0x28, 0x27, 0x27, 0x26, 0x26, 0x25, 0x25, + 0x24, 0x24, 0x23, 0x23, 0x22, 0x22, 0x21, 0x21, + 0x20, 0x20, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D, + 0x1C, 0x1C, 0x1B, 0x1B, 0x1A, 0x1A, 0x19, 0x19, + 0x18, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, + 0x14, 0x14, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11, + 0x10, 0x10 +}; + +// no don't ask me WHY this table exsits! +const uint8 AdlibDriver::_unkTable2_2[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x6F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F +}; + +const uint8 AdlibDriver::_unkTable2_3[] = { + 0x40, 0x40, 0x40, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E, + 0x3E, 0x3D, 0x3D, 0x3D, 0x3C, 0x3C, 0x3C, 0x3B, + 0x3B, 0x3B, 0x3A, 0x3A, 0x3A, 0x39, 0x39, 0x39, + 0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36, + 0x36, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x33, + 0x33, 0x33, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, + 0x30, 0x30, 0x30, 0x2F, 0x2F, 0x2F, 0x2E, 0x2E, + 0x2E, 0x2D, 0x2D, 0x2D, 0x2C, 0x2C, 0x2C, 0x2B, + 0x2B, 0x2B, 0x2A, 0x2A, 0x2A, 0x29, 0x29, 0x29, + 0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26, + 0x26, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x23, + 0x23, 0x23, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21, + 0x20, 0x20, 0x20, 0x1F, 0x1F, 0x1F, 0x1E, 0x1E, + 0x1E, 0x1D, 0x1D, 0x1D, 0x1C, 0x1C, 0x1C, 0x1B, + 0x1B, 0x1B, 0x1A, 0x1A, 0x1A, 0x19, 0x19, 0x19, + 0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x16, 0x16, + 0x16, 0x15 +}; + +// This table is used to modify the frequency of the notes, depending on the +// note value and unk16. In theory, we could very well try to access memory +// outside this table, but in reality that probably won't happen. +// +// This could be some sort of pitch bend, but I have yet to see it used for +// anything so it's hard to say. + +const uint8 AdlibDriver::_unkTables[][32] = { + // 0 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x19, + 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21 }, + // 1 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x09, + 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x22, 0x24 }, + // 2 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09, + 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x19, 0x1A, 0x1C, 0x1D, + 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26 }, + // 3 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1D, + 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x25, 0x27, 0x28 }, + // 4 + { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x13, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x28, 0x2A }, + // 5 + { 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D }, + // 6 + { 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, + 0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1E, 0x21, 0x24, + 0x25, 0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30 }, + // 7 + { 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, 0x18, + 0x19, 0x1A, 0x1C, 0x1D, 0x1F, 0x21, 0x23, 0x25, + 0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30, 0x32 }, + // 8 + { 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x17, 0x1A, + 0x19, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x25, 0x28, + 0x29, 0x2A, 0x2B, 0x2D, 0x2F, 0x31, 0x33, 0x35 }, + // 9 + { 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E, + 0x0F, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1B, + 0x1C, 0x1D, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x29, + 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x39 }, + // 10 + { 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E, + 0x0F, 0x10, 0x12, 0x14, 0x16, 0x19, 0x1B, 0x1E, + 0x1F, 0x21, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D, + 0x2E, 0x2F, 0x31, 0x32, 0x34, 0x36, 0x39, 0x3C }, + // 11 + { 0x00, 0x01, 0x03, 0x05, 0x07, 0x0A, 0x0C, 0x0F, + 0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1E, + 0x1F, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2B, 0x2E, + 0x2F, 0x30, 0x32, 0x34, 0x36, 0x39, 0x3C, 0x3F }, + // 12 + { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x10, + 0x11, 0x12, 0x14, 0x16, 0x18, 0x1B, 0x1E, 0x21, + 0x22, 0x23, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32, + 0x33, 0x34, 0x36, 0x38, 0x3B, 0x34, 0x41, 0x44 }, + // 13 + { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x11, + 0x12, 0x13, 0x15, 0x17, 0x1A, 0x1D, 0x20, 0x23, + 0x24, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32, 0x35, + 0x36, 0x37, 0x39, 0x3B, 0x3E, 0x41, 0x44, 0x47 } +}; + +// #pragma mark - + +// At the time of writing, the only known case where Kyra 1 uses sound triggers +// is in the castle, to cycle between three different songs. + +const int CadlPlayer::_kyra1SoundTriggers[] = { + 0, 4, 5, 3 +}; + +const int CadlPlayer::_kyra1NumSoundTriggers = ARRAYSIZE(CadlPlayer::_kyra1SoundTriggers); + +CadlPlayer::CadlPlayer(Copl *newopl) + : CPlayer(newopl), numsubsongs(0), _trackEntries(), _soundDataPtr(0) +{ + memset(_trackEntries, 0, sizeof(_trackEntries)); + _driver = new AdlibDriver(newopl); + assert(_driver); + + _sfxPlayingSound = -1; + // _soundFileLoaded = ""; + + _soundTriggers = _kyra1SoundTriggers; + _numSoundTriggers = _kyra1NumSoundTriggers; + + init(); +} + +CadlPlayer::~CadlPlayer() { + delete [] _soundDataPtr; + delete _driver; +} + +bool CadlPlayer::init() { + _driver->callback(2); + _driver->callback(16, int(4)); + return true; +} + +void CadlPlayer::process() { + uint8 trigger = _driver->callback(11); + + if (trigger < _numSoundTriggers) { + int soundId = _soundTriggers[trigger]; + + if (soundId) { + playTrack(soundId); + } + } else { + warning("Unknown sound trigger %d", trigger); + // TODO: At this point, we really want to clear the trigger... + } +} + +// void CadlPlayer::setVolume(int volume) { +// } + +// int CadlPlayer::getVolume() { +// return 0; +// } + +// void CadlPlayer::loadMusicFile(const char *file) { +// loadSoundFile(file); +// } + +void CadlPlayer::playTrack(uint8 track) { + play(track); +} + +// void CadlPlayer::haltTrack() { +// unk1(); +// unk2(); +// //_engine->_system->delayMillis(3 * 60); +// } + +void CadlPlayer::playSoundEffect(uint8_t track) { + play(track); +} + +void CadlPlayer::play(uint8_t track) { + uint8 soundId = _trackEntries[track]; + if ((int8)soundId == -1 || !_soundDataPtr) + return; + soundId &= 0xFF; + _driver->callback(16, 0); + // while ((_driver->callback(16, 0) & 8)) { + // We call the system delay and not the game delay to avoid concurrency issues. + // _engine->_system->delayMillis(10); + // } + if (_sfxPlayingSound != -1) { + // Restore the sounds's normal values. + _driver->callback(10, _sfxPlayingSound, int(1), int(_sfxPriority)); + _driver->callback(10, _sfxPlayingSound, int(3), int(_sfxFourthByteOfSong)); + _sfxPlayingSound = -1; + } + + int chan = _driver->callback(9, soundId, int(0)); + + if (chan != 9) { + _sfxPlayingSound = soundId; + _sfxPriority = _driver->callback(9, soundId, int(1)); + _sfxFourthByteOfSong = _driver->callback(9, soundId, int(3)); + + // In the cases I've seen, the mysterious fourth byte has been + // the parameter for the update_setExtraLevel3() callback. + // + // The extra level is part of the channels "total level", which + // is a six-bit value where larger values means softer volume. + // + // So what seems to be happening here is that sounds which are + // started by this function are given a slightly lower priority + // and a slightly higher (i.e. softer) extra level 3 than they + // would have if they were started from anywhere else. Strange. + + int newVal = ((((-_sfxFourthByteOfSong) + 63) * 0xFF) >> 8) & 0xFF; + newVal = -newVal + 63; + _driver->callback(10, soundId, int(3), newVal); + newVal = ((_sfxPriority * 0xFF) >> 8) & 0xFF; + _driver->callback(10, soundId, int(1), newVal); + } + + _driver->callback(6, soundId); +} + +// void CadlPlayer::beginFadeOut() { +// playSoundEffect(1); +// } + +bool CadlPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); + + // file validation section + if(!f || !fp.extension(filename, ".adl")) { + fp.close(f); + return false; + } + + // if (_soundFileLoaded == file) + // return; + + // if (_soundDataPtr) { + // haltTrack(); + // } + + uint8 *file_data = 0; uint32 file_size = 0; + + // char filename[25]; + // sprintf(filename, "%s.ADL", file); + + // file_data = _engine->resource()->fileData(filename, &file_size); + // if (!file_data) { + // warning("Couldn't find music file: '%s'", filename); + // return; + // } + + unk2(); + unk1(); + + file_size = fp.filesize(f); + file_data = new uint8 [file_size]; + f->readString((char *)file_data, file_size); + + _driver->callback(8, int(-1)); + _soundDataPtr = 0; + + uint8 *p = file_data; + memcpy(_trackEntries, p, 120*sizeof(uint8)); + p += 120; + + int soundDataSize = file_size - 120; + + _soundDataPtr = new uint8[soundDataSize]; + assert(_soundDataPtr); + + memcpy(_soundDataPtr, p, soundDataSize*sizeof(uint8)); + + delete [] file_data; + file_data = p = 0; + file_size = 0; + + _driver->callback(4, _soundDataPtr); + + // _soundFileLoaded = file; + + for(int i = 0; i < 200; i++) + if(_trackEntries[i] != 0xff) + numsubsongs = i + 1; + + fp.close(f); + return true; +} + +void CadlPlayer::rewind(int subsong) +{ + opl->init(); + opl->write(1,32); + playSoundEffect(subsong); + cursubsong = subsong; + update(); +} + +unsigned int CadlPlayer::getsubsongs() +{ + return numsubsongs; +} + +bool CadlPlayer::update() +{ + bool songend = true; + +// if(_trackEntries[cursubsong] == 0xff) +// return false; + + _driver->callback(); + + for(int i = 0; i < 10; i++) + if(_driver->_channels[i].dataptr != NULL) + songend = false; + + return !songend; +} + +void CadlPlayer::unk1() { + playSoundEffect(0); + //_engine->_system->delayMillis(5 * 60); +} + +void CadlPlayer::unk2() { + playSoundEffect(0); +} + +CPlayer *CadlPlayer::factory(Copl *newopl) +{ + return new CadlPlayer(newopl); +} diff --git a/plugins/adplug/adplug/adl.h b/plugins/adplug/adplug/adl.h new file mode 100644 index 00000000..b0030c43 --- /dev/null +++ b/plugins/adplug/adplug/adl.h @@ -0,0 +1,65 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * adl.h - ADL player adaption by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_ADLPLAYER +#define H_ADPLUG_ADLPLAYER + +#include <inttypes.h> + +#include "player.h" + +class AdlibDriver; + +class CadlPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CadlPlayer(Copl *newopl); + ~CadlPlayer(); + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + + // refresh rate is fixed at 72Hz + float getrefresh() + { + return 72.0f; + } + + unsigned int getsubsongs(); + std::string gettype() { return std::string("Westwood ADL"); } + + private: + int numsubsongs, cursubsong; + + AdlibDriver *_driver; + + uint8_t _trackEntries[120]; + uint8_t *_soundDataPtr; + int _sfxPlayingSound; + + uint8_t _sfxPriority; + uint8_t _sfxFourthByteOfSong; + + int _numSoundTriggers; + const int *_soundTriggers; + + static const int _kyra1NumSoundTriggers; + static const int _kyra1SoundTriggers[]; + + bool init(); + void process(); + void playTrack(uint8_t track); + void playSoundEffect(uint8_t track); + void play(uint8_t track); + void unk1(); + void unk2(); +}; + +#endif diff --git a/plugins/adplug/adplug/adlibemu.c b/plugins/adplug/adplug/adlibemu.c new file mode 100644 index 00000000..c35d6da7 --- /dev/null +++ b/plugins/adplug/adplug/adlibemu.c @@ -0,0 +1,600 @@ +/* + * ADLIBEMU.C + * Copyright (C) 1998-2001 Ken Silverman + * Ken Silverman's official web site: "http://www.advsys.net/ken" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +This file is a digital Adlib emulator for OPL2 and possibly OPL3 + +Features that could be added in a future version: +- Amplitude and Frequency Vibrato Bits (not hard, but a big speed hit) +- Global Keyboard Split Number Bit (need to research this one some more) +- 2nd Adlib chip for OPL3 (simply need to make my cell array bigger) +- Advanced connection modes of OPL3 (Just need to add more "docell" cases) +- L/R Stereo bits of OPL3 (Need adlibgetsample to return stereo) + +Features that aren't worth supporting: +- Anything related to adlib timers&interrupts (Sorry - I always used IRQ0) +- Composite sine wave mode (CSM) (Supported only on ancient cards) + +I'm not sure about a few things in my code: +- Attack curve. What function is this anyway? I chose to use an order-3 + polynomial to approximate but this doesn't seem right. +- Attack/Decay/Release constants - my constants may not be exact +- What should ADJUSTSPEED be? +- Haven't verified that Global Keyboard Split Number Bit works yet +- Some of the drums don't always sound right. It's pretty hard to guess + the exact waveform of drums when you look at random data which is + slightly randomized due to digital ADC recording. +- Adlib seems to have a lot more treble than my emulator does. I'm not + sure if this is simply unfixable due to the sound blaster's different + filtering on FM and digital playback or if it's a serious bug in my + code. +*/ + +#include <math.h> +#include <string.h> + +#if !defined(max) && !defined(__cplusplus) +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#if !defined(min) && !defined(__cplusplus) +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#define PI 3.141592653589793 +#define MAXCELLS 18 +#define WAVPREC 2048 + +static float AMPSCALE=(8192.0); +#define FRQSCALE (49716/512.0) + +//Constants for Ken's Awe32, on a PII-266 (Ken says: Use these for KSM's!) +#define MODFACTOR 4.0 //How much of modulator cell goes into carrier +#define MFBFACTOR 1.0 //How much feedback goes back into modulator +#define ADJUSTSPEED 0.75 //0<=x<=1 Simulate finite rate of change of state + +//Constants for Ken's Awe64G, on a P-133 +//#define MODFACTOR 4.25 //How much of modulator cell goes into carrier +//#define MFBFACTOR 0.5 //How much feedback goes back into modulator +//#define ADJUSTSPEED 0.85 //0<=x<=1 Simulate finite rate of change of state + +typedef struct +{ + float val, t, tinc, vol, sustain, amp, mfb; + float a0, a1, a2, a3, decaymul, releasemul; + short *waveform; + long wavemask; + void (*cellfunc)(void *, float); + unsigned char flags, dum0, dum1, dum2; +} celltype; + +static long numspeakers, bytespersample; +static float recipsamp; +static celltype cell[MAXCELLS]; +static signed short wavtable[WAVPREC*3]; +static float kslmul[4] = {0.0,0.5,0.25,1.0}; +static float frqmul[16] = {.5,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15}, nfrqmul[16]; +static unsigned char adlibreg[256], ksl[8][16]; +static unsigned char modulatorbase[9] = {0,1,2,8,9,10,16,17,18}; +static unsigned char odrumstat = 0; +static unsigned char base2cell[22] = {0,1,2,0,1,2,0,0,3,4,5,3,4,5,0,0,6,7,8,6,7,8}; + +float lvol[9] = {1,1,1,1,1,1,1,1,1}; //Volume multiplier on left speaker +float rvol[9] = {1,1,1,1,1,1,1,1,1}; //Volume multiplier on right speaker +long lplc[9] = {0,0,0,0,0,0,0,0,0}; //Samples to delay on left speaker +long rplc[9] = {0,0,0,0,0,0,0,0,0}; //Samples to delay on right speaker + +long nlvol[9], nrvol[9]; +long nlplc[9], nrplc[9]; +long rend = 0; +#define FIFOSIZ 256 +static float *rptr[9], *nrptr[9]; +static float rbuf[9][FIFOSIZ*2]; +static float snd[FIFOSIZ*2]; + +#ifndef USING_ASM +#define _inline +#endif + +#ifdef USING_ASM +static _inline void ftol (float f, long *a) +{ + _asm + { + mov eax, a + fld f + fistp dword ptr [eax] + } +} +#else +static void ftol(float f, long *a) { + *a=f; +} +#endif + +#define ctc ((celltype *)c) //A rare attempt to make code easier to read! +void docell4 (void *c, float modulator) { } +void docell3 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} +void docell2 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + + if (*(long *)&ctc->amp <= 0x37800000) + { + ctc->amp = 0; + ctc->cellfunc = docell4; + } + ctc->amp *= ctc->releasemul; + + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} +void docell1 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + + if ((*(long *)&ctc->amp) <= (*(long *)&ctc->sustain)) + { + if (ctc->flags&32) + { + ctc->amp = ctc->sustain; + ctc->cellfunc = docell3; + } + else + ctc->cellfunc = docell2; + } + else + ctc->amp *= ctc->decaymul; + + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} +void docell0 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + + ctc->amp = ((ctc->a3*ctc->amp + ctc->a2)*ctc->amp + ctc->a1)*ctc->amp + ctc->a0; + if ((*(long *)&ctc->amp) > 0x3f800000) + { + ctc->amp = 1; + ctc->cellfunc = docell1; + } + + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} + + +static long waveform[8] = {WAVPREC,WAVPREC>>1,WAVPREC,(WAVPREC*3)>>2,0,0,(WAVPREC*5)>>2,WAVPREC<<1}; +static long wavemask[8] = {WAVPREC-1,WAVPREC-1,(WAVPREC>>1)-1,(WAVPREC>>1)-1,WAVPREC-1,((WAVPREC*3)>>2)-1,WAVPREC>>1,WAVPREC-1}; +static long wavestart[8] = {0,WAVPREC>>1,0,WAVPREC>>2,0,0,0,WAVPREC>>3}; +static float attackconst[4] = {1/2.82624,1/2.25280,1/1.88416,1/1.59744}; +static float decrelconst[4] = {1/39.28064,1/31.41608,1/26.17344,1/22.44608}; +void cellon (long i, long j, celltype *c, unsigned char iscarrier) +{ + long frn, oct, toff; + float f; + + frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0]; + oct = ((((long)adlibreg[i+0xb0])>>2)&7); + toff = (oct<<1) + ((frn>>9)&((frn>>8)|(((adlibreg[8]>>6)&1)^1))); + if (!(adlibreg[j+0x20]&16)) toff >>= 2; + + f = pow(2.0,(adlibreg[j+0x60]>>4)+(toff>>2)-1)*attackconst[toff&3]*recipsamp; + c->a0 = .0377*f; c->a1 = 10.73*f+1; c->a2 = -17.57*f; c->a3 = 7.42*f; + f = -7.4493*decrelconst[toff&3]*recipsamp; + c->decaymul = pow(2.0,f*pow(2.0,(adlibreg[j+0x60]&15)+(toff>>2))); + c->releasemul = pow(2.0,f*pow(2.0,(adlibreg[j+0x80]&15)+(toff>>2))); + c->wavemask = wavemask[adlibreg[j+0xe0]&7]; + c->waveform = &wavtable[waveform[adlibreg[j+0xe0]&7]]; + if (!(adlibreg[1]&0x20)) c->waveform = &wavtable[WAVPREC]; + c->t = wavestart[adlibreg[j+0xe0]&7]; + c->flags = adlibreg[j+0x20]; + c->cellfunc = docell0; + c->tinc = (float)(frn<<oct)*nfrqmul[adlibreg[j+0x20]&15]; + c->vol = pow(2.0,((float)(adlibreg[j+0x40]&63) + + (float)kslmul[adlibreg[j+0x40]>>6]*ksl[oct][frn>>6]) * -.125 - 14); + c->sustain = pow(2.0,(float)(adlibreg[j+0x80]>>4) * -.5); + if (!iscarrier) c->amp = 0; + c->mfb = pow(2.0,((adlibreg[i+0xc0]>>1)&7)+5)*(WAVPREC/2048.0)*MFBFACTOR; + if (!(adlibreg[i+0xc0]&14)) c->mfb = 0; + c->val = 0; +} + +//This function (and bug fix) written by Chris Moeller +void cellfreq (signed long i, signed long j, celltype *c) +{ + long frn, oct; + + frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0]; + oct = ((((long)adlibreg[i+0xb0])>>2)&7); + + c->tinc = (float)(frn<<oct)*nfrqmul[adlibreg[j+0x20]&15]; + c->vol = pow(2.0,((float)(adlibreg[j+0x40]&63) + + (float)kslmul[adlibreg[j+0x40]>>6]*ksl[oct][frn>>6]) * -.125 - 14); +} + +static long initfirstime = 0; +void adlibinit (long dasamplerate, long danumspeakers, long dabytespersample) +{ + long i, j, frn, oct; + + memset((void *)adlibreg,0,sizeof(adlibreg)); + memset((void *)cell,0,sizeof(celltype)*MAXCELLS); + memset((void *)rbuf,0,sizeof(rbuf)); + rend = 0; odrumstat = 0; + + for(i=0;i<MAXCELLS;i++) + { + cell[i].cellfunc = docell4; + cell[i].amp = 0; + cell[i].vol = 0; + cell[i].t = 0; + cell[i].tinc = 0; + cell[i].wavemask = 0; + cell[i].waveform = &wavtable[WAVPREC]; + } + + numspeakers = danumspeakers; + bytespersample = dabytespersample; + + recipsamp = 1.0 / (float)dasamplerate; + for(i=15;i>=0;i--) nfrqmul[i] = frqmul[i]*recipsamp*FRQSCALE*(WAVPREC/2048.0); + + if (!initfirstime) + { + initfirstime = 1; + + for(i=0;i<(WAVPREC>>1);i++) + { + wavtable[i] = + wavtable[(i<<1) +WAVPREC] = (signed short)(16384*sin((float)((i<<1) )*PI*2/WAVPREC)); + wavtable[(i<<1)+1+WAVPREC] = (signed short)(16384*sin((float)((i<<1)+1)*PI*2/WAVPREC)); + } + for(i=0;i<(WAVPREC>>3);i++) + { + wavtable[i+(WAVPREC<<1)] = wavtable[i+(WAVPREC>>3)]-16384; + wavtable[i+((WAVPREC*17)>>3)] = wavtable[i+(WAVPREC>>2)]+16384; + } + + //[table in book]*8/3 + ksl[7][0] = 0; ksl[7][1] = 24; ksl[7][2] = 32; ksl[7][3] = 37; + ksl[7][4] = 40; ksl[7][5] = 43; ksl[7][6] = 45; ksl[7][7] = 47; + ksl[7][8] = 48; for(i=9;i<16;i++) ksl[7][i] = i+41; + for(j=6;j>=0;j--) + for(i=0;i<16;i++) + { + oct = (long)ksl[j+1][i]-8; if (oct < 0) oct = 0; + ksl[j][i] = (unsigned char)oct; + } + } + else + { + for(i=0;i<9;i++) + { + frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0]; + oct = ((((long)adlibreg[i+0xb0])>>2)&7); + cell[i].tinc = (float)(frn<<oct)*nfrqmul[adlibreg[modulatorbase[i]+0x20]&15]; + } + } +} + +void adlib0 (long i, long v) +{ + unsigned char tmp = adlibreg[i]; + adlibreg[i] = v; + + if (i == 0xbd) + { + if ((v&16) > (odrumstat&16)) //BassDrum + { + cellon(6,16,&cell[6],0); + cellon(6,19,&cell[15],1); + cell[15].vol *= 2; + } + if ((v&8) > (odrumstat&8)) //Snare + { + cellon(16,20,&cell[16],0); + cell[16].tinc *= 2*(nfrqmul[adlibreg[17+0x20]&15] / nfrqmul[adlibreg[20+0x20]&15]); + if (((adlibreg[20+0xe0]&7) >= 3) && ((adlibreg[20+0xe0]&7) <= 5)) cell[16].vol = 0; + cell[16].vol *= 2; + } + if ((v&4) > (odrumstat&4)) //TomTom + { + cellon(8,18,&cell[8],0); + cell[8].vol *= 2; + } + if ((v&2) > (odrumstat&2)) //Cymbal + { + cellon(17,21,&cell[17],0); + + cell[17].wavemask = wavemask[5]; + cell[17].waveform = &wavtable[waveform[5]]; + cell[17].tinc *= 16; cell[17].vol *= 2; + + //cell[17].waveform = &wavtable[WAVPREC]; cell[17].wavemask = 0; + //if (((adlibreg[21+0xe0]&7) == 0) || ((adlibreg[21+0xe0]&7) == 6)) + // cell[17].waveform = &wavtable[(WAVPREC*7)>>2]; + //if (((adlibreg[21+0xe0]&7) == 2) || ((adlibreg[21+0xe0]&7) == 3)) + // cell[17].waveform = &wavtable[(WAVPREC*5)>>2]; + } + if ((v&1) > (odrumstat&1)) //Hihat + { + cellon(7,17,&cell[7],0); + if (((adlibreg[17+0xe0]&7) == 1) || ((adlibreg[17+0xe0]&7) == 4) || + ((adlibreg[17+0xe0]&7) == 5) || ((adlibreg[17+0xe0]&7) == 7)) cell[7].vol = 0; + if ((adlibreg[17+0xe0]&7) == 6) { cell[7].wavemask = 0; cell[7].waveform = &wavtable[(WAVPREC*7)>>2]; } + } + + odrumstat = v; + } + else if (((unsigned)(i-0x40) < (unsigned)22) && ((i&7) < 6)) + { + if ((i&7) < 3) // Modulator + cellfreq(base2cell[i-0x40],i-0x40,&cell[base2cell[i-0x40]]); + else // Carrier + cellfreq(base2cell[i-0x40],i-0x40,&cell[base2cell[i-0x40]+9]); + } + else if ((unsigned)(i-0xa0) < (unsigned)9) + { + cellfreq(i-0xa0,modulatorbase[i-0xa0],&cell[i-0xa0]); + cellfreq(i-0xa0,modulatorbase[i-0xa0]+3,&cell[i-0xa0+9]); + } + else if ((unsigned)(i-0xb0) < (unsigned)9) + { + if ((v&32) > (tmp&32)) + { + cellon(i-0xb0,modulatorbase[i-0xb0],&cell[i-0xb0],0); + cellon(i-0xb0,modulatorbase[i-0xb0]+3,&cell[i-0xb0+9],1); + } + else if ((v&32) < (tmp&32)) + cell[i-0xb0].cellfunc = cell[i-0xb0+9].cellfunc = docell2; + cellfreq(i-0xb0,modulatorbase[i-0xb0],&cell[i-0xb0]); + cellfreq(i-0xb0,modulatorbase[i-0xb0]+3,&cell[i-0xb0+9]); + } + + //outdata(i,v); +} + +#ifdef USING_ASM +static long fpuasm; +static float fakeadd = 8388608.0+128.0; +static _inline void clipit8 (float f, long a) +{ + _asm + { + mov edi, a + fld dword ptr f + fadd dword ptr fakeadd + fstp dword ptr fpuasm + mov eax, fpuasm + test eax, 0x007fff00 + jz short skipit + shr eax, 16 + xor eax, -1 + skipit: mov byte ptr [edi], al + } +} + +static _inline void clipit16 (float f, long a) +{ + _asm + { + mov eax, a + fld dword ptr f + fist word ptr [eax] + cmp word ptr [eax], 0x8000 + jne short skipit2 + fst dword ptr [fpuasm] + cmp fpuasm, 0x80000000 + sbb word ptr [eax], 0 + skipit2: fstp st + } +} +#else +static void clipit8(float f,unsigned char *a) { + f/=256.0; + f+=128.0; + if (f>254.5) *a=255; + else if (f<0.5) *a=0; + else *a=f; +} + +static void clipit16(float f,short *a) { + if (f>32766.5) *a=32767; + else if (f<-32767.5) *a=-32768; + else *a=f; +} +#endif + +void adlibsetvolume(int i) { + AMPSCALE=i; +} + +void adlibgetsample (unsigned char *sndptr, long numbytes) +{ + long i, j, k=0, ns, endsamples, rptrs, numsamples; + celltype *cptr; + float f; + short *sndptr2=(short *)sndptr; + + numsamples = (numbytes>>(numspeakers+bytespersample-2)); + + if (bytespersample == 1) f = AMPSCALE/256.0; else f = AMPSCALE; + if (numspeakers == 1) + { + nlvol[0] = lvol[0]*f; + for(i=0;i<9;i++) rptr[i] = &rbuf[0][0]; + rptrs = 1; + } + else + { + rptrs = 0; + for(i=0;i<9;i++) + { + if ((!i) || (lvol[i] != lvol[i-1]) || (rvol[i] != rvol[i-1]) || + (lplc[i] != lplc[i-1]) || (rplc[i] != rplc[i-1])) + { + nlvol[rptrs] = lvol[i]*f; + nrvol[rptrs] = rvol[i]*f; + nlplc[rptrs] = rend-min(max(lplc[i],0),FIFOSIZ); + nrplc[rptrs] = rend-min(max(rplc[i],0),FIFOSIZ); + rptrs++; + } + rptr[i] = &rbuf[rptrs-1][0]; + } + } + + + //CPU time used to be somewhat less when emulator was only mono! + // Because of no delay fifos! + + for(ns=0;ns<numsamples;ns+=endsamples) + { + endsamples = min(FIFOSIZ*2-rend,FIFOSIZ); + endsamples = min(endsamples,numsamples-ns); + + for(i=0;i<9;i++) + nrptr[i] = &rptr[i][rend]; + for(i=0;i<rptrs;i++) + memset((void *)&rbuf[i][rend],0,endsamples*sizeof(float)); + + if (adlibreg[0xbd]&0x20) + { + //BassDrum (j=6) + if (cell[15].cellfunc != docell4) + { + if (adlibreg[0xc6]&1) + { + for(i=0;i<endsamples;i++) + { + (cell[15].cellfunc)((void *)&cell[15],0.0); + nrptr[6][i] += cell[15].val; + } + } + else + { + for(i=0;i<endsamples;i++) + { + (cell[6].cellfunc)((void *)&cell[6],cell[6].val*cell[6].mfb); + (cell[15].cellfunc)((void *)&cell[15],cell[6].val*WAVPREC*MODFACTOR); + nrptr[6][i] += cell[15].val; + } + } + } + + //Snare/Hihat (j=7), Cymbal/TomTom (j=8) + if ((cell[7].cellfunc != docell4) || (cell[8].cellfunc != docell4) || (cell[16].cellfunc != docell4) || (cell[17].cellfunc != docell4)) + { + for(i=0;i<endsamples;i++) + { + k = k*1664525+1013904223; + (cell[16].cellfunc)((void *)&cell[16],k&((WAVPREC>>1)-1)); //Snare + (cell[7].cellfunc)((void *)&cell[7],k&(WAVPREC-1)); //Hihat + (cell[17].cellfunc)((void *)&cell[17],k&((WAVPREC>>3)-1)); //Cymbal + (cell[8].cellfunc)((void *)&cell[8],0.0); //TomTom + nrptr[7][i] += cell[7].val + cell[16].val; + nrptr[8][i] += cell[8].val + cell[17].val; + } + } + } + for(j=9-1;j>=0;j--) + { + if ((adlibreg[0xbd]&0x20) && (j >= 6) && (j < 9)) continue; + + cptr = &cell[j]; k = j; + if (adlibreg[0xc0+k]&1) + { + if ((cptr[9].cellfunc == docell4) && (cptr->cellfunc == docell4)) continue; + for(i=0;i<endsamples;i++) + { + (cptr->cellfunc)((void *)cptr,cptr->val*cptr->mfb); + (cptr->cellfunc)((void *)&cptr[9],0); + nrptr[j][i] += cptr[9].val + cptr->val; + } + } + else + { + if (cptr[9].cellfunc == docell4) continue; + for(i=0;i<endsamples;i++) + { + (cptr->cellfunc)((void *)cptr,cptr->val*cptr->mfb); + (cptr[9].cellfunc)((void *)&cptr[9],cptr->val*WAVPREC*MODFACTOR); + nrptr[j][i] += cptr[9].val; + } + } + } + + if (numspeakers == 1) + { + if (bytespersample == 1) + { + for(i=endsamples-1;i>=0;i--) + clipit8(nrptr[0][i]*nlvol[0],sndptr+1); + } + else + { + for(i=endsamples-1;i>=0;i--) + clipit16(nrptr[0][i]*nlvol[0],sndptr2+i); + } + } + else + { + memset((void *)snd,0,endsamples*sizeof(float)*2); + for(j=0;j<rptrs;j++) + { + for(i=0;i<endsamples;i++) + { + snd[(i<<1) ] += rbuf[j][(nlplc[j]+i)&(FIFOSIZ*2-1)]*nlvol[j]; + snd[(i<<1)+1] += rbuf[j][(nrplc[j]+i)&(FIFOSIZ*2-1)]*nrvol[j]; + } + nlplc[j] += endsamples; + nrplc[j] += endsamples; + } + + if (bytespersample == 1) + { + for(i=(endsamples<<1)-1;i>=0;i--) + clipit8(snd[i],sndptr+i); + } + else + { + for(i=(endsamples<<1)-1;i>=0;i--) + clipit16(snd[i],sndptr2+i); + } + } + + sndptr = sndptr+(numspeakers*endsamples); + sndptr2 = sndptr2+(numspeakers*endsamples); + rend = ((rend+endsamples)&(FIFOSIZ*2-1)); + } +} diff --git a/plugins/adplug/adplug/adlibemu.h b/plugins/adplug/adplug/adlibemu.h new file mode 100644 index 00000000..8600d787 --- /dev/null +++ b/plugins/adplug/adplug/adlibemu.h @@ -0,0 +1,26 @@ +/* + * ADLIBEMU.H + * Copyright (C) 1998-2001 Ken Silverman + * Ken Silverman's official web site: "http://www.advsys.net/ken" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +void adlibinit(long dasamplerate,long danumspeakers,long dabytespersample); +void adlib0(long i,long v); +void adlibgetsample(void *sndptr,long numbytes); +void adlibsetvolume(int i); +void randoinsts(); +extern float lvol[9],rvol[9],lplc[9],rplc[9]; diff --git a/plugins/adplug/adplug/adplug.cpp b/plugins/adplug/adplug/adplug.cpp new file mode 100644 index 00000000..32fea695 --- /dev/null +++ b/plugins/adplug/adplug/adplug.cpp @@ -0,0 +1,182 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * adplug.cpp - CAdPlug utility class, by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string> +#include <binfile.h> +#include <string.h> + +#include "adplug.h" +#include "debug.h" + +/***** Replayer includes *****/ + +#include "hsc.h" +#include "amd.h" +#include "a2m.h" +#include "imf.h" +#include "sng.h" +#include "adtrack.h" +#include "bam.h" +#include "d00.h" +#include "dfm.h" +#include "hsp.h" +#include "ksm.h" +#include "mad.h" +#include "mid.h" +#include "mkj.h" +#include "cff.h" +#include "dmo.h" +#include "s3m.h" +#include "dtm.h" +#include "fmc.h" +#include "mtk.h" +#include "rad.h" +#include "raw.h" +#include "sa2.h" +#include "bmf.h" +#include "flash.h" +#include "hybrid.h" +#include "hyp.h" +#include "psi.h" +#include "rat.h" +#include "lds.h" +#include "u6m.h" +#include "rol.h" +#include "xsm.h" +#include "dro.h" +#include "msc.h" +#include "rix.h" +#include "adl.h" + +/***** CAdPlug *****/ + +// List of all players that come with the standard AdPlug distribution +const CPlayerDesc CAdPlug::allplayers[] = { + CPlayerDesc(ChscPlayer::factory, "HSC-Tracker", ".hsc\0"), + CPlayerDesc(CsngPlayer::factory, "SNGPlay", ".sng\0"), + CPlayerDesc(CimfPlayer::factory, "Apogee IMF", ".imf\0.wlf\0.adlib\0"), + CPlayerDesc(Ca2mLoader::factory, "Adlib Tracker 2", ".a2m\0"), + CPlayerDesc(CadtrackLoader::factory, "Adlib Tracker", ".sng\0"), + CPlayerDesc(CamdLoader::factory, "AMUSIC", ".amd\0"), + CPlayerDesc(CbamPlayer::factory, "Bob's Adlib Music", ".bam\0"), + CPlayerDesc(Cd00Player::factory, "Packed EdLib", ".d00\0"), + CPlayerDesc(CdfmLoader::factory, "Digital-FM", ".dfm\0"), + CPlayerDesc(ChspLoader::factory, "HSC Packed", ".hsp\0"), + CPlayerDesc(CksmPlayer::factory, "Ken Silverman Music", ".ksm\0"), + CPlayerDesc(CmadLoader::factory, "Mlat Adlib Tracker", ".mad\0"), + CPlayerDesc(CmidPlayer::factory, "MIDI", ".mid\0.cmf\0.sci\0.laa\0"), + CPlayerDesc(CmkjPlayer::factory, "MKJamz", ".mkj\0"), + CPlayerDesc(CcffLoader::factory, "Boomtracker", ".cff\0"), + CPlayerDesc(CdmoLoader::factory, "TwinTeam", ".dmo\0"), + CPlayerDesc(Cs3mPlayer::factory, "Scream Tracker 3", ".s3m\0"), + CPlayerDesc(CdtmLoader::factory, "DeFy Adlib Tracker", ".dtm\0"), + CPlayerDesc(CfmcLoader::factory, "Faust Music Creator", ".sng\0"), + CPlayerDesc(CmtkLoader::factory, "MPU-401 Trakker", ".mtk\0"), + CPlayerDesc(CradLoader::factory, "Reality Adlib Tracker", ".rad\0"), + CPlayerDesc(CrawPlayer::factory, "RdosPlay RAW", ".raw\0"), + CPlayerDesc(Csa2Loader::factory, "Surprise! Adlib Tracker", ".sat\0.sa2\0"), + CPlayerDesc(CxadbmfPlayer::factory, "BMF Adlib Tracker", ".xad\0"), + CPlayerDesc(CxadflashPlayer::factory, "Flash", ".xad\0"), + CPlayerDesc(CxadhybridPlayer::factory, "Hybrid", ".xad\0"), + CPlayerDesc(CxadhypPlayer::factory, "Hypnosis", ".xad\0"), + CPlayerDesc(CxadpsiPlayer::factory, "PSI", ".xad\0"), + CPlayerDesc(CxadratPlayer::factory, "rat", ".xad\0"), + CPlayerDesc(CldsPlayer::factory, "LOUDNESS Sound System", ".lds\0"), + CPlayerDesc(Cu6mPlayer::factory, "Ultima 6 Music", ".m\0"), + CPlayerDesc(CrolPlayer::factory, "Adlib Visual Composer", ".rol\0"), + CPlayerDesc(CxsmPlayer::factory, "eXtra Simple Music", ".xsm\0"), + CPlayerDesc(CdroPlayer::factory, "DOSBox Raw OPL", ".dro\0"), + CPlayerDesc(CmscPlayer::factory, "Adlib MSC Player", ".msc\0"), + CPlayerDesc(CrixPlayer::factory, "Softstar RIX OPL Music", ".rix\0"), + CPlayerDesc(CadlPlayer::factory, "Westwood ADL", ".adl\0"), + CPlayerDesc() +}; + +const CPlayers &CAdPlug::init_players(const CPlayerDesc pd[]) +{ + static CPlayers initplayers; + unsigned int i; + + for(i = 0; pd[i].factory; i++) + initplayers.push_back(&pd[i]); + + return initplayers; +} + +const CPlayers CAdPlug::players = CAdPlug::init_players(CAdPlug::allplayers); +CAdPlugDatabase *CAdPlug::database = 0; + +CPlayer *CAdPlug::factory(const char *fn, Copl *opl, const CPlayers &pl, + const CFileProvider &fp) +{ + CPlayer *p; + CPlayers::const_iterator i; + unsigned int j; + + AdPlug_LogWrite("*** CAdPlug::factory(\"%s\",opl,fp) ***\n", fn); + + // Try a direct hit by file extension + for(i = pl.begin(); i != pl.end(); i++) + for(j = 0; (*i)->get_extension(j); j++) + if(fp.extension(fn, (*i)->get_extension(j))) { + AdPlug_LogWrite("Trying direct hit: %s\n", (*i)->filetype.c_str()); + if((p = (*i)->factory(opl))) + if(p->load(fn, fp)) { + AdPlug_LogWrite("got it!\n"); + AdPlug_LogWrite("--- CAdPlug::factory ---\n"); + return p; + } else + delete p; + } + + // Try all players, one by one + for(i = pl.begin(); i != pl.end(); i++) { + AdPlug_LogWrite("Trying: %s\n", (*i)->filetype.c_str()); + if((p = (*i)->factory(opl))) + if(p->load(fn, fp)) { + AdPlug_LogWrite("got it!\n"); + AdPlug_LogWrite("--- CAdPlug::factory ---\n"); + return p; + } else + delete p; + } + + // Unknown file + AdPlug_LogWrite("End of list!\n"); + AdPlug_LogWrite("--- CAdPlug::factory ---\n"); + return 0; +} + +void CAdPlug::set_database(CAdPlugDatabase *db) +{ + database = db; +} + +std::string CAdPlug::get_version() +{ + return std::string(VERSION); +} + +void CAdPlug::debug_output(const std::string &filename) +{ + AdPlug_LogFile(filename.c_str()); + AdPlug_LogWrite("CAdPlug::debug_output(\"%s\"): Redirected.\n",filename.c_str()); +} diff --git a/plugins/adplug/adplug/adplug.h b/plugins/adplug/adplug/adplug.h new file mode 100644 index 00000000..54ee042a --- /dev/null +++ b/plugins/adplug/adplug/adplug.h @@ -0,0 +1,55 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * adplug.h - AdPlug main header file, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_ADPLUG +#define H_ADPLUG_ADPLUG + +#include <string> + +#include "player.h" +#include "opl.h" +#include "fprovide.h" +#include "players.h" +#include "database.h" + +class CAdPlug +{ + friend CPlayer::CPlayer(Copl *newopl); + +public: + static const CPlayers players; + + static CPlayer *factory(const char *fn, Copl *opl, + const CPlayers &pl = players, + const CFileProvider &fp = CProvider_Filesystem()); + + static void set_database(CAdPlugDatabase *db); + static std::string get_version(); + static void debug_output(const std::string &filename); + +private: + static CAdPlugDatabase *database; + static const CPlayerDesc allplayers[]; + + static const CPlayers &init_players(const CPlayerDesc pd[]); +}; + +#endif diff --git a/plugins/adplug/adplug/adtrack.cpp b/plugins/adplug/adplug/adtrack.cpp new file mode 100644 index 00000000..d3f3490a --- /dev/null +++ b/plugins/adplug/adplug/adtrack.cpp @@ -0,0 +1,176 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * adtrack.cpp - Adlib Tracker 1.0 Loader by Simon Peter <dn.tlp@gmx.net> + * + * NOTES: + * The original Adlib Tracker 1.0 is behaving a little different from the + * official spec: The 'octave' integer from the instrument file is stored + * "minus 1" from the actual value, underflowing from 0 to 0xffff. + * + * I also noticed that my player is playing everything transposed a few tones + * higher than the original tracker. As far as i can see, my player perfectly + * follows the official spec, so it "must" be the tracker that does something + * wrong here... + */ + +#include <stdlib.h> +#include <string.h> + +#include "adtrack.h" +#include "debug.h" + +/*** Public methods ***/ + +CPlayer *CadtrackLoader::factory(Copl *newopl) +{ + return new CadtrackLoader(newopl); +} + +bool CadtrackLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + binistream *instf; + char note[2]; + unsigned short rwp; + unsigned char chp, octave, pnote = 0; + int i,j; + AdTrackInst myinst; + + // file validation + if(!fp.extension(filename, ".sng") || fp.filesize(f) != 36000) + { fp.close(f); return false; } + + // check for instruments file + std::string instfilename(filename, 0, filename.find_last_of('.')); + instfilename += ".ins"; + AdPlug_LogWrite("CadtrackLoader::load(,\"%s\"): Checking for \"%s\"...\n", + filename.c_str(), instfilename.c_str()); + instf = fp.open(instfilename); + if(!instf || fp.filesize(instf) != 468) { fp.close(f); return false; } + + // give CmodPlayer a hint on what we're up to + realloc_patterns(1,1000,9); realloc_instruments(9); realloc_order(1); + init_trackord(); flags = NoKeyOn; + (*order) = 0; length = 1; restartpos = 0; bpm = 120; initspeed = 3; + + // load instruments from instruments file + for(i=0;i<9;i++) { + for(j=0;j<2;j++) { + myinst.op[j].appampmod = instf->readInt(2); + myinst.op[j].appvib = instf->readInt(2); + myinst.op[j].maintsuslvl = instf->readInt(2); + myinst.op[j].keybscale = instf->readInt(2); + myinst.op[j].octave = instf->readInt(2); + myinst.op[j].freqrisevollvldn = instf->readInt(2); + myinst.op[j].softness = instf->readInt(2); + myinst.op[j].attack = instf->readInt(2); + myinst.op[j].decay = instf->readInt(2); + myinst.op[j].release = instf->readInt(2); + myinst.op[j].sustain = instf->readInt(2); + myinst.op[j].feedback = instf->readInt(2); + myinst.op[j].waveform = instf->readInt(2); + } + convert_instrument(i, &myinst); + } + fp.close(instf); + + // load file + for(rwp=0;rwp<1000;rwp++) + for(chp=0;chp<9;chp++) { + // read next record + f->readString(note, 2); octave = f->readInt(1); f->ignore(); + switch(*note) { + case 'C': if(note[1] == '#') pnote = 2; else pnote = 1; break; + case 'D': if(note[1] == '#') pnote = 4; else pnote = 3; break; + case 'E': pnote = 5; break; + case 'F': if(note[1] == '#') pnote = 7; else pnote = 6; break; + case 'G': if(note[1] == '#') pnote = 9; else pnote = 8; break; + case 'A': if(note[1] == '#') pnote = 11; else pnote = 10; break; + case 'B': pnote = 12; break; + case '\0': + if(note[1] == '\0') + tracks[chp][rwp].note = 127; + else { + fp.close(f); + return false; + } + break; + default: fp.close(f); return false; + } + if((*note) != '\0') { + tracks[chp][rwp].note = pnote + (octave * 12); + tracks[chp][rwp].inst = chp + 1; + } + } + + fp.close(f); + rewind(0); + return true; +} + +float CadtrackLoader::getrefresh() +{ + return 18.2f; +} + +/*** Private methods ***/ + +void CadtrackLoader::convert_instrument(unsigned int n, AdTrackInst *i) +{ + // Carrier "Amp Mod / Vib / Env Type / KSR / Multiple" register + inst[n].data[2] = i->op[Carrier].appampmod ? 1 << 7 : 0; + inst[n].data[2] += i->op[Carrier].appvib ? 1 << 6 : 0; + inst[n].data[2] += i->op[Carrier].maintsuslvl ? 1 << 5 : 0; + inst[n].data[2] += i->op[Carrier].keybscale ? 1 << 4 : 0; + inst[n].data[2] += (i->op[Carrier].octave + 1) & 0xffff; // Bug in original tracker + // Modulator... + inst[n].data[1] = i->op[Modulator].appampmod ? 1 << 7 : 0; + inst[n].data[1] += i->op[Modulator].appvib ? 1 << 6 : 0; + inst[n].data[1] += i->op[Modulator].maintsuslvl ? 1 << 5 : 0; + inst[n].data[1] += i->op[Modulator].keybscale ? 1 << 4 : 0; + inst[n].data[1] += (i->op[Modulator].octave + 1) & 0xffff; // Bug in original tracker + + // Carrier "Key Scaling / Level" register + inst[n].data[10] = (i->op[Carrier].freqrisevollvldn & 3) << 6; + inst[n].data[10] += i->op[Carrier].softness & 63; + // Modulator... + inst[n].data[9] = (i->op[Modulator].freqrisevollvldn & 3) << 6; + inst[n].data[9] += i->op[Modulator].softness & 63; + + // Carrier "Attack / Decay" register + inst[n].data[4] = (i->op[Carrier].attack & 0x0f) << 4; + inst[n].data[4] += i->op[Carrier].decay & 0x0f; + // Modulator... + inst[n].data[3] = (i->op[Modulator].attack & 0x0f) << 4; + inst[n].data[3] += i->op[Modulator].decay & 0x0f; + + // Carrier "Release / Sustain" register + inst[n].data[6] = (i->op[Carrier].release & 0x0f) << 4; + inst[n].data[6] += i->op[Carrier].sustain & 0x0f; + // Modulator... + inst[n].data[5] = (i->op[Modulator].release & 0x0f) << 4; + inst[n].data[5] += i->op[Modulator].sustain & 0x0f; + + // Channel "Feedback / Connection" register + inst[n].data[0] = (i->op[Carrier].feedback & 7) << 1; + + // Carrier/Modulator "Wave Select" registers + inst[n].data[8] = i->op[Carrier].waveform & 3; + inst[n].data[7] = i->op[Modulator].waveform & 3; +} diff --git a/plugins/adplug/adplug/adtrack.h b/plugins/adplug/adplug/adtrack.h new file mode 100644 index 00000000..289491ff --- /dev/null +++ b/plugins/adplug/adplug/adtrack.h @@ -0,0 +1,53 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * adtrack.h - Adlib Tracker 1.0 Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include "protrack.h" + +class CadtrackLoader: public CmodPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CadtrackLoader(Copl *newopl) + : CmodPlayer(newopl) + { }; + + bool load(const std::string &filename, const CFileProvider &fp); + float getrefresh(); + + std::string gettype() + { return std::string("Adlib Tracker 1.0"); }; + unsigned int getinstruments() + { return 9; }; + +private: + enum Operators {Carrier = 1, Modulator = 0}; + + typedef struct { + struct { + unsigned short appampmod, appvib, maintsuslvl, keybscale, octave, + freqrisevollvldn, softness, attack, decay, release, sustain, + feedback, waveform; + } op[2]; + } AdTrackInst; + + void convert_instrument(unsigned int n, AdTrackInst *i); +}; diff --git a/plugins/adplug/adplug/amd.cpp b/plugins/adplug/adplug/amd.cpp new file mode 100644 index 00000000..4f844227 --- /dev/null +++ b/plugins/adplug/adplug/amd.cpp @@ -0,0 +1,193 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * amd.cpp - AMD Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> + +#include "amd.h" +#include "debug.h" + +CPlayer *CamdLoader::factory(Copl *newopl) +{ + return new CamdLoader(newopl); +} + +bool CamdLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + struct { + char id[9]; + unsigned char version; + } header; + int i, j, k, t, numtrax, maxi = 0; + unsigned char buf, buf2, buf3; + const unsigned char convfx[10] = {0,1,2,9,17,11,13,18,3,14}; + const unsigned char convvol[64] = { + 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 0xa, 0xa, 0xb, + 0xc, 0xc, 0xd, 0xe, 0xe, 0xf, 0x10, 0x10, 0x11, 0x12, 0x13, 0x14, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x21, + 0x22, 0x23, 0x25, 0x26, 0x28, 0x29, 0x2b, 0x2d, 0x2e, 0x30, 0x32, 0x35, + 0x37, 0x3a, 0x3c, 0x3f + }; + + // file validation section + if(fp.filesize(f) < 1072) { fp.close(f); return false; } + f->seek(1062); f->readString(header.id, 9); + header.version = f->readInt(1); + if(strncmp(header.id, "<o\xefQU\xeeRoR", 9) && + strncmp(header.id, "MaDoKaN96", 9)) { fp.close(f); return false; } + + // load section + memset(inst, 0, sizeof(inst)); + f->seek(0); + f->readString(songname, sizeof(songname)); + f->readString(author, sizeof(author)); + for(i = 0; i < 26; i++) { + f->readString(instname[i], 23); + for(j = 0; j < 11; j++) inst[i].data[j] = f->readInt(1); + } + length = f->readInt(1); nop = f->readInt(1) + 1; + for(i=0;i<128;i++) order[i] = f->readInt(1); + f->seek(10, binio::Add); + if(header.version == 0x10) { // unpacked module + maxi = nop * 9; + for(i=0;i<64*9;i++) + trackord[i/9][i%9] = i+1; + t = 0; + while(!f->ateof()) { + for(j=0;j<64;j++) + for(i=t;i<t+9;i++) { + buf = f->readInt(1); + tracks[i][j].param2 = (buf&127) % 10; + tracks[i][j].param1 = (buf&127) / 10; + buf = f->readInt(1); + tracks[i][j].inst = buf >> 4; + tracks[i][j].command = buf & 0x0f; + buf = f->readInt(1); + if(buf >> 4) // fix bug in AMD save routine + tracks[i][j].note = ((buf & 14) >> 1) * 12 + (buf >> 4); + else + tracks[i][j].note = 0; + tracks[i][j].inst += (buf & 1) << 4; + } + t += 9; + } + } else { // packed module + for(i=0;i<nop;i++) + for(j=0;j<9;j++) + trackord[i][j] = f->readInt(2) + 1; + numtrax = f->readInt(2); + for(k=0;k<numtrax;k++) { + i = f->readInt(2); + if(i > 575) i = 575; // fix corrupted modules + maxi = (i + 1 > maxi ? i + 1 : maxi); + j = 0; + do { + buf = f->readInt(1); + if(buf & 128) { + for(t = j; t < j + (buf & 127) && t < 64; t++) { + tracks[i][t].command = 0; + tracks[i][t].inst = 0; + tracks[i][t].note = 0; + tracks[i][t].param1 = 0; + tracks[i][t].param2 = 0; + } + j += buf & 127; + continue; + } + tracks[i][j].param2 = buf % 10; + tracks[i][j].param1 = buf / 10; + buf = f->readInt(1); + tracks[i][j].inst = buf >> 4; + tracks[i][j].command = buf & 0x0f; + buf = f->readInt(1); + if(buf >> 4) // fix bug in AMD save routine + tracks[i][j].note = ((buf & 14) >> 1) * 12 + (buf >> 4); + else + tracks[i][j].note = 0; + tracks[i][j].inst += (buf & 1) << 4; + j++; + } while(j<64); + } + } + fp.close(f); + + // convert to protracker replay data + bpm = 50; restartpos = 0; flags = Decimal; + for(i=0;i<26;i++) { // convert instruments + buf = inst[i].data[0]; + buf2 = inst[i].data[1]; + inst[i].data[0] = inst[i].data[10]; + inst[i].data[1] = buf; + buf = inst[i].data[2]; + inst[i].data[2] = inst[i].data[5]; + buf3 = inst[i].data[3]; + inst[i].data[3] = buf; + buf = inst[i].data[4]; + inst[i].data[4] = inst[i].data[7]; + inst[i].data[5] = buf3; + buf3 = inst[i].data[6]; + inst[i].data[6] = inst[i].data[8]; + inst[i].data[7] = buf; + inst[i].data[8] = inst[i].data[9]; + inst[i].data[9] = buf2; + inst[i].data[10] = buf3; + for(j=0;j<23;j++) // convert names + if(instname[i][j] == '\xff') + instname[i][j] = '\x20'; + } + for(i=0;i<maxi;i++) // convert patterns + for(j=0;j<64;j++) { + tracks[i][j].command = convfx[tracks[i][j].command]; + // extended command + if(tracks[i][j].command == 14) { + if(tracks[i][j].param1 == 2) { + tracks[i][j].command = 10; + tracks[i][j].param1 = tracks[i][j].param2; + tracks[i][j].param2 = 0; + } + + if(tracks[i][j].param1 == 3) { + tracks[i][j].command = 10; + tracks[i][j].param1 = 0; + } + } + + // fix volume + if(tracks[i][j].command == 17) { + int vol = convvol[tracks[i][j].param1 * 10 + tracks[i][j].param2]; + + if(vol > 63) vol = 63; + tracks[i][j].param1 = vol / 10; + tracks[i][j].param2 = vol % 10; + } + } + + rewind(0); + return true; +} + +float CamdLoader::getrefresh() +{ + if(tempo) + return (float) (tempo); + else + return 18.2f; +} diff --git a/plugins/adplug/adplug/amd.h b/plugins/adplug/adplug/amd.h new file mode 100644 index 00000000..daa630b6 --- /dev/null +++ b/plugins/adplug/adplug/amd.h @@ -0,0 +1,49 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * amd.h - AMD Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include "protrack.h" + +class CamdLoader: public CmodPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CamdLoader(Copl *newopl) + : CmodPlayer(newopl) + { }; + + bool load(const std::string &filename, const CFileProvider &fp); + float getrefresh(); + + std::string gettype() + { return std::string("AMUSIC Adlib Tracker"); }; + std::string gettitle() + { return std::string(songname,0,24); }; + std::string getauthor() + { return std::string(author,0,24); }; + unsigned int getinstruments() + { return 26; }; + std::string getinstrument(unsigned int n) + { return std::string(instname[n],0,23); }; + +private: + char songname[24],author[24],instname[26][23]; +}; diff --git a/plugins/adplug/adplug/analopl.cpp b/plugins/adplug/adplug/analopl.cpp new file mode 100644 index 00000000..1a7527df --- /dev/null +++ b/plugins/adplug/adplug/analopl.cpp @@ -0,0 +1,67 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * analopl.cpp - Spectrum analyzing hardware OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "analopl.h" + +CAnalopl::CAnalopl(unsigned short initport) + : CRealopl(initport) +{ + for(int i = 0; i < 9; i++) { + keyregs[0][i][0] = 0; + keyregs[0][i][1] = 0; + keyregs[1][i][0] = 0; + keyregs[1][i][1] = 0; + } +} + +void CAnalopl::write(int reg, int val) +{ + if(nowrite) return; + + if(reg >= 0xb0 && reg <= 0xb8) { + if(!keyregs[currChip][reg - 0xb0][0] && (val & 32)) + keyregs[currChip][reg - 0xb0][1] = 1; + else + keyregs[currChip][reg - 0xb0][1] = 0; + keyregs[currChip][reg - 0xb0][0] = val & 32; + } + + CRealopl::write(reg, val); +} + +int CAnalopl::getcarriervol(unsigned int v, unsigned int c) +{ + return (hardvols[c][op_table[v]+3][0] & 63); +} + +int CAnalopl::getmodulatorvol(unsigned int v, unsigned int c) +{ + return (hardvols[c][op_table[v]][0] & 63); +} + +bool CAnalopl::getkeyon(unsigned int v, unsigned int c) +{ + if(keyregs[c][v][1]) { + keyregs[c][v][1] = 0; + return true; + } else + return false; +} diff --git a/plugins/adplug/adplug/analopl.h b/plugins/adplug/adplug/analopl.h new file mode 100644 index 00000000..314c4247 --- /dev/null +++ b/plugins/adplug/adplug/analopl.h @@ -0,0 +1,44 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * analopl.h - Spectrum analyzing hardware OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_ANALOPL +#define H_ADPLUG_ANALOPL + +#include "realopl.h" + +class CAnalopl: public CRealopl +{ + public: + CAnalopl(unsigned short initport = DFL_ADLIBPORT); // initport = OPL2 hardware baseport + + // get carrier volume of adlib voice v on chip c + int getcarriervol(unsigned int v, unsigned int c = 0); + // get modulator volume of adlib voice v on chip c + int getmodulatorvol(unsigned int v, unsigned int c = 0); + bool getkeyon(unsigned int v, unsigned int c = 0); + + void write(int reg, int val); + + protected: + unsigned char keyregs[2][9][2]; // shadow key register +}; + +#endif diff --git a/plugins/adplug/adplug/bam.cpp b/plugins/adplug/adplug/bam.cpp new file mode 100644 index 00000000..ea1aca01 --- /dev/null +++ b/plugins/adplug/adplug/bam.cpp @@ -0,0 +1,203 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * bam.cpp - Bob's Adlib Music Player, by Simon Peter <dn.tlp@gmx.net> + * + * NOTES: + * In my player, the loop counter is stored with the label. This can be + * dangerous for some situations (see below), but there shouldn't be any BAM + * files triggering this situation. + * + * From SourceForge Bug #476088: + * ----------------------------- + * Using just one loop counter for each label, my player can't + * handle files that loop twice to the same label (if that's at + * all possible with BAM). Imagine the following situation: + * + * ... [*] ---- [<- *] ---- [<- *] ... + * ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | + * +---|----+-----|-----+-----|----+--- normal song data + * +----------|-----------|-------- label 1 + * +-----------+-------- loop points to label 1 + * + * both loop points loop to the same label. Storing the loop + * count with the label would cause chaos with the counter, + * when the player executes the inner jump. + * ------------------ + * Not to worry. my reference implementation of BAM does not + * support the multiple loop situation you describe, and + * neither do any BAM-creation programs. Then both loops point + * to the same label, the inner loop's counter is just allowed + * to clobber the outer loop's counter. No stack is neccisary. + */ + +#include <string.h> +#include "bam.h" + +const unsigned short CbamPlayer::freq[] = {172,182,193,205,217,230,243,258,274, +290,307,326,345,365,387,410,435,460,489,517,547,580,614,651,1369,1389,1411, +1434,1459,1484,1513,1541,1571,1604,1638,1675,2393,2413,2435,2458,2483,2508, +2537,2565,2595,2628,2662,2699,3417,3437,3459,3482,3507,3532,3561,3589,3619, +3652,3686,3723,4441,4461,4483,4506,4531,4556,4585,4613,4643,4676,4710,4747, +5465,5485,5507,5530,5555,5580,5609,5637,5667,5700,5734,5771,6489,6509,6531, +6554,6579,6604,6633,6661,6691,6724,6758,6795,7513,7533,7555,7578,7603,7628, +7657,7685,7715,7748,7782,7819,7858,7898,7942,7988,8037,8089,8143,8191,8191, +8191,8191,8191,8191,8191,8191,8191,8191,8191,8191}; + +CPlayer *CbamPlayer::factory(Copl *newopl) +{ + return new CbamPlayer(newopl); +} + +bool CbamPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + char id[4]; + unsigned int i; + + size = fp.filesize(f) - 4; // filesize minus header + f->readString(id, 4); + if(strncmp(id,"CBMF",4)) { fp.close(f); return false; } + + song = new unsigned char [size]; + for(i = 0; i < size; i++) song[i] = f->readInt(1); + + fp.close(f); + rewind(0); + return true; +} + +bool CbamPlayer::update() +{ + unsigned char cmd,c; + + if(del) { + del--; + return !songend; + } + + if(pos >= size) { // EOF detection + pos = 0; + songend = true; + } + + while(song[pos] < 128) { + cmd = song[pos] & 240; + c = song[pos] & 15; + switch(cmd) { + case 0: // stop song + pos = 0; + songend = true; + break; + case 16: // start note + if(c < 9) { + opl->write(0xa0 + c, freq[song[++pos]] & 255); + opl->write(0xb0 + c, (freq[song[pos]] >> 8) + 32); + } else + pos++; + pos++; + break; + case 32: // stop note + if(c < 9) + opl->write(0xb0 + c, 0); + pos++; + break; + case 48: // define instrument + if(c < 9) { + opl->write(0x20 + op_table[c],song[pos+1]); + opl->write(0x23 + op_table[c],song[pos+2]); + opl->write(0x40 + op_table[c],song[pos+3]); + opl->write(0x43 + op_table[c],song[pos+4]); + opl->write(0x60 + op_table[c],song[pos+5]); + opl->write(0x63 + op_table[c],song[pos+6]); + opl->write(0x80 + op_table[c],song[pos+7]); + opl->write(0x83 + op_table[c],song[pos+8]); + opl->write(0xe0 + op_table[c],song[pos+9]); + opl->write(0xe3 + op_table[c],song[pos+10]); + opl->write(0xc0 + c,song[pos+11]); + } + pos += 12; + break; + case 80: // set label + label[c].target = ++pos; + label[c].defined = true; + break; + case 96: // jump + if(label[c].defined) + switch(song[pos+1]) { + case 254: // infinite loop + if(label[c].defined) { + pos = label[c].target; + songend = true; + break; + } + // fall through... + case 255: // chorus + if(!chorus && label[c].defined) { + chorus = true; + gosub = pos + 2; + pos = label[c].target; + break; + } + // fall through... + case 0: // end of loop + pos += 2; + break; + default: // finite loop + if(!label[c].count) { // loop elapsed + label[c].count = 255; + pos += 2; + break; + } + if(label[c].count < 255) // loop defined + label[c].count--; + else // loop undefined + label[c].count = song[pos+1] - 1; + pos = label[c].target; + break; + } + break; + case 112: // end of chorus + if(chorus) { + pos = gosub; + chorus = false; + } else + pos++; + break; + default: // reserved command (skip) + pos++; + break; + } + } + if(song[pos] >= 128) { // wait + del = song[pos] - 127; + pos++; + } + return !songend; +} + +void CbamPlayer::rewind(int subsong) +{ + int i; + + pos = 0; songend = false; del = 0; gosub = 0; chorus = false; + memset(label, 0, sizeof(label)); label[0].defined = true; + for(i = 0; i < 16; i++) label[i].count = 255; // 255 = undefined + opl->init(); opl->write(1,32); +} diff --git a/plugins/adplug/adplug/bam.h b/plugins/adplug/adplug/bam.h new file mode 100644 index 00000000..53f321b2 --- /dev/null +++ b/plugins/adplug/adplug/bam.h @@ -0,0 +1,56 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * bam.h - Bob's Adlib Music Player, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "player.h" + +class CbamPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CbamPlayer(Copl *newopl) + : CPlayer(newopl), song(0) + { }; + ~CbamPlayer() + { if(song) delete [] song; }; + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh() + { return 25.0f; }; + + std::string gettype() + { return std::string("Bob's Adlib Music"); }; + +private: + static const unsigned short freq[]; + + unsigned char *song, del; + unsigned long pos, size, gosub; + bool songend, chorus; + + struct { + unsigned long target; + bool defined; + unsigned char count; + } label[16]; +}; diff --git a/plugins/adplug/adplug/bmf.cpp b/plugins/adplug/adplug/bmf.cpp new file mode 100644 index 00000000..df403c86 --- /dev/null +++ b/plugins/adplug/adplug/bmf.cpp @@ -0,0 +1,597 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003, 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] BMF player, by Riven the Mage <riven@ok.ru> + */ + +/* + - discovery - + + file(s) : GAMESNET.COM + type : GamesNet advertising intro + tune : by (?)The Brain [Razor 1911] + player : ver.0.9b by Hammer + + file(s) : 2FAST4U.COM + type : Ford Knox BBStro + tune : by The Brain [Razor 1911] + player : ver.1.1 by ? + comment : in original player at 9th channel the feedback adlib register is not C8 but C6. + + file(s) : DATURA.COM + type : Datura BBStro + tune : by The Brain [Razor 1911] + player : ver.1.2 by ? + comment : inaccurate replaying, because constant outport; in original player it can be 380 or 382. +*/ + +#include <string.h> +#include "bmf.h" +#include "debug.h" + +const unsigned char CxadbmfPlayer::bmf_adlib_registers[117] = +{ + 0x20, 0x23, 0x40, 0x43, 0x60, 0x63, 0x80, 0x83, 0xA0, 0xB0, 0xC0, 0xE0, 0xE3, + 0x21, 0x24, 0x41, 0x44, 0x61, 0x64, 0x81, 0x84, 0xA1, 0xB1, 0xC1, 0xE1, 0xE4, + 0x22, 0x25, 0x42, 0x45, 0x62, 0x65, 0x82, 0x85, 0xA2, 0xB2, 0xC2, 0xE2, 0xE5, + 0x28, 0x2B, 0x48, 0x4B, 0x68, 0x6B, 0x88, 0x8B, 0xA3, 0xB3, 0xC3, 0xE8, 0xEB, + 0x29, 0x2C, 0x49, 0x4C, 0x69, 0x6C, 0x89, 0x8C, 0xA4, 0xB4, 0xC4, 0xE9, 0xEC, + 0x2A, 0x2D, 0x4A, 0x4D, 0x6A, 0x6D, 0x8A, 0x8D, 0xA5, 0xB5, 0xC5, 0xEA, 0xED, + 0x30, 0x33, 0x50, 0x53, 0x70, 0x73, 0x90, 0x93, 0xA6, 0xB6, 0xC6, 0xF0, 0xF3, + 0x31, 0x34, 0x51, 0x54, 0x71, 0x74, 0x91, 0x94, 0xA7, 0xB7, 0xC7, 0xF1, 0xF4, + 0x32, 0x35, 0x52, 0x55, 0x72, 0x75, 0x92, 0x95, 0xA8, 0xB8, 0xC8, 0xF2, 0xF5 +}; + +const unsigned short CxadbmfPlayer::bmf_notes[12] = +{ + 0x157, 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287 +}; + +/* for 1.1 */ +const unsigned short CxadbmfPlayer::bmf_notes_2[12] = +{ + 0x159, 0x16D, 0x183, 0x19A, 0x1B2, 0x1CC, 0x1E8, 0x205, 0x223, 0x244, 0x267, 0x28B +}; + +const unsigned char CxadbmfPlayer::bmf_default_instrument[13] = +{ + 0x01, 0x01, 0x3F, 0x3F, 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +CPlayer *CxadbmfPlayer::factory(Copl *newopl) +{ + return new CxadbmfPlayer(newopl); +} + +bool CxadbmfPlayer::xadplayer_load() +{ + unsigned short ptr = 0; + int i; + + if(xad.fmt != BMF) + return false; + +#ifdef DEBUG + AdPlug_LogWrite("\nbmf_load():\n\n"); +#endif + if (!strncmp((char *)&tune[0],"BMF1.2",6)) + { + bmf.version = BMF1_2; + bmf.timer = 70.0f; + } + else if (!strncmp((char *)&tune[0],"BMF1.1",6)) + { + bmf.version = BMF1_1; + bmf.timer = 60.0f; + } + else + { + bmf.version = BMF0_9B; + bmf.timer = 18.2f; + } + + // copy title & author + if (bmf.version > BMF0_9B) + { + ptr = 6; + + strncpy(bmf.title,(char *)&tune[ptr],36); + + while (tune[ptr]) { ptr++; } + ptr++; + + strncpy(bmf.author,(char *)&tune[ptr],36); + + while (tune[ptr]) { ptr++; } + ptr++; + } + else + { + strncpy(bmf.title,xad.title,36); + strncpy(bmf.author,xad.author,36); + } + + // speed + if (bmf.version > BMF0_9B) + bmf.speed = tune[ptr++]; + else + bmf.speed = ((tune[ptr++] << 8) / 3) >> 8; // strange, yeh ? + + // load instruments + if (bmf.version > BMF0_9B) + { + unsigned long iflags = (tune[ptr] << 24) | (tune[ptr+1] << 16) | (tune[ptr+2] << 8) | tune[ptr+3]; + ptr+=4; + + for(i=0;i<32;i++) + if (iflags & (1 << (31-i))) + { + strcpy(bmf.instruments[i].name, (char *)&tune[ptr]); + memcpy(bmf.instruments[i].data, &tune[ptr+11], 13); + ptr += 24; + } + else + { + bmf.instruments[i].name[0] = 0; + + if (bmf.version == BMF1_1) + for(int j=0;j<13;j++) + bmf.instruments[i].data[j] = bmf_default_instrument[j]; + else + for(int j=0;j<13;j++) + bmf.instruments[i].data[j] = 0; + } + } + else + { + ptr = 6; + + for(i=0;i<32;i++) + { + bmf.instruments[i].name[0] = 0; + memcpy(bmf.instruments[tune[ptr]].data, &tune[ptr+2],13); // bug no.1 (no instrument-table-end detection) + ptr+=15; + } + } + + // load streams + if (bmf.version > BMF0_9B) + { + unsigned long sflags = (tune[ptr] << 24) | (tune[ptr+1] << 16) | (tune[ptr+2] << 8) | tune[ptr+3]; + ptr+=4; + + for(i=0;i<9;i++) + if (sflags & (1 << (31-i))) + ptr+=__bmf_convert_stream(&tune[ptr],i); + else + bmf.streams[i][0].cmd = 0xFF; + } + else + { + for(i=0;i<tune[5];i++) + ptr+=__bmf_convert_stream(&tune[ptr],i); + + for(i=tune[5];i<9;i++) + bmf.streams[i][0].cmd = 0xFF; + } + + return true; +} + +void CxadbmfPlayer::xadplayer_rewind(int subsong) +{ + int i,j; + + for(i=0; i<9; i++) + { + bmf.channel[i].stream_position = 0; + bmf.channel[i].delay = 0; + bmf.channel[i].loop_position = 0; + bmf.channel[i].loop_counter = 0; + } + + plr.speed = bmf.speed; +#ifdef DEBUG + AdPlug_LogWrite("speed: %x\n",plr.speed); +#endif + + bmf.active_streams = 9; + + // OPL initialization + if (bmf.version > BMF0_9B) + { + opl_write(0x01, 0x20); + + /* 1.1 */ + if (bmf.version == BMF1_1) + for(i=0;i<9;i++) + for(j=0;j<13;j++) + opl_write(bmf_adlib_registers[13*i+j], bmf_default_instrument[j]); + /* 1.2 */ + else if (bmf.version == BMF1_2) + for(i=0x20; i<0x100; i++) + opl_write(i,0xFF); // very interesting, really! + } + + /* ALL */ + + opl_write(0x08, 0x00); + opl_write(0xBD, 0xC0); +} + +void CxadbmfPlayer::xadplayer_update() +{ + for(int i=0;i<9;i++) + if (bmf.channel[i].stream_position != 0xFFFF) + if (bmf.channel[i].delay) + bmf.channel[i].delay--; + else + { +#ifdef DEBUG + AdPlug_LogWrite("channel %02X:\n", i); +#endif + bmf_event event; + + // process so-called cross-events + while (true) + { + memcpy(&event, &bmf.streams[i][bmf.channel[i].stream_position], sizeof(bmf_event)); +#ifdef DEBUG + AdPlug_LogWrite("%02X %02X %02X %02X %02X %02X\n", + event.note,event.delay,event.volume,event.instrument, + event.cmd,event.cmd_data); +#endif + + if (event.cmd == 0xFF) + { + bmf.channel[i].stream_position = 0xFFFF; + bmf.active_streams--; + break; + } + else if (event.cmd == 0xFE) + { + bmf.channel[i].loop_position = bmf.channel[i].stream_position+1; + bmf.channel[i].loop_counter = event.cmd_data; + } + else if (event.cmd == 0xFD) + { + if (bmf.channel[i].loop_counter) + { + bmf.channel[i].stream_position = bmf.channel[i].loop_position-1; + bmf.channel[i].loop_counter--; + } + } + else + break; + + bmf.channel[i].stream_position++; + } // while (true) + + // process normal event + unsigned short pos = bmf.channel[i].stream_position; + + if (pos != 0xFFFF) + { + bmf.channel[i].delay = bmf.streams[i][pos].delay; + + // command ? + if (bmf.streams[i][pos].cmd) + { + unsigned char cmd = bmf.streams[i][pos].cmd; + + // 0x01: Set Modulator Volume + if (cmd == 0x01) + { + unsigned char reg = bmf_adlib_registers[13*i+2]; + + opl_write(reg, (adlib[reg] | 0x3F) - bmf.streams[i][pos].cmd_data); + } + // 0x10: Set Speed + else if (cmd == 0x10) + { + plr.speed = bmf.streams[i][pos].cmd_data; + plr.speed_counter = plr.speed; + } + } // if (bmf.streams[i][pos].cmd) + + // instrument ? + if (bmf.streams[i][pos].instrument) + { + unsigned char ins = bmf.streams[i][pos].instrument-1; + + if (bmf.version != BMF1_1) + opl_write(0xB0+i, adlib[0xB0+i] & 0xDF); + + for(int j=0;j<13;j++) + opl_write(bmf_adlib_registers[i*13+j], bmf.instruments[ins].data[j]); + } // if (bmf.streams[i][pos].instrument) + + // volume ? + if (bmf.streams[i][pos].volume) + { + unsigned char vol = bmf.streams[i][pos].volume-1; + unsigned char reg = bmf_adlib_registers[13*i+3]; + + opl_write(reg, (adlib[reg] | 0x3F) - vol); + } // if (bmf.streams[i][pos].volume) + + // note ? + if (bmf.streams[i][pos].note) + { + unsigned short note = bmf.streams[i][pos].note; + unsigned short freq = 0; + + // mute channel + opl_write(0xB0+i, adlib[0xB0+i] & 0xDF); + + // get frequency + if (bmf.version == BMF1_1) + { + if (note <= 0x60) + freq = bmf_notes_2[--note % 12]; + } + else + { + if (note != 0x7F) + freq = bmf_notes[--note % 12]; + } + + // play note + if (freq) + { + opl_write(0xB0+i, (freq >> 8) | ((note / 12) << 2) | 0x20); + opl_write(0xA0+i, freq & 0xFF); + } + } // if (bmf.streams[i][pos].note) + + bmf.channel[i].stream_position++; + } // if (pos != 0xFFFF) + + } // if (!bmf.channel[i].delay) + + // is module loop ? + if (!bmf.active_streams) + { + for(int j=0;j<9;j++) + bmf.channel[j].stream_position = 0; + + bmf.active_streams = 9; + + plr.looping = 1; + } +} + +float CxadbmfPlayer::xadplayer_getrefresh() +{ + return bmf.timer; +} + +std::string CxadbmfPlayer::xadplayer_gettype() +{ + return std::string("xad: BMF Adlib Tracker"); +} + +std::string CxadbmfPlayer::xadplayer_gettitle() +{ + return std::string(bmf.title); +} + +std::string CxadbmfPlayer::xadplayer_getauthor() +{ + return std::string(bmf.author); +} + +unsigned int CxadbmfPlayer::xadplayer_getinstruments() +{ + return 32; +} + +std::string CxadbmfPlayer::xadplayer_getinstrument(unsigned int i) +{ + return std::string(bmf.instruments[i].name); +} + +/* -------- Internal Functions ---------------------------- */ + +int CxadbmfPlayer::__bmf_convert_stream(unsigned char *stream, int channel) +{ +#ifdef DEBUG + AdPlug_LogWrite("channel %02X (note,delay,volume,instrument,command,command_data):\n",channel); + unsigned char *last = stream; +#endif + unsigned char *stream_start = stream; + + int pos = 0; + + while (true) + { + memset(&bmf.streams[channel][pos], 0, sizeof(bmf_event)); + + bool is_cmd = false; + + if (*stream == 0xFE) + { + // 0xFE -> 0xFF: End of Stream + bmf.streams[channel][pos].cmd = 0xFF; + + stream++; + + break; + } + else if (*stream == 0xFC) + { + // 0xFC -> 0xFE xx: Save Loop Position + bmf.streams[channel][pos].cmd = 0xFE; + bmf.streams[channel][pos].cmd_data = (*(stream+1) & ((bmf.version == BMF0_9B) ? 0x7F : 0x3F)) - 1; + + stream+=2; + } + else if (*stream == 0x7D) + { + // 0x7D -> 0xFD: Loop Saved Position + bmf.streams[channel][pos].cmd = 0xFD; + + stream++; + } + else + { + if (*stream & 0x80) + { + if (*(stream+1) & 0x80) + { + if (*(stream+1) & 0x40) + { + // byte0: 1aaaaaaa = NOTE + bmf.streams[channel][pos].note = *stream & 0x7F; + // byte1: 11bbbbbb = DELAY + bmf.streams[channel][pos].delay = *(stream+1) & 0x3F; + // byte2: cccccccc = COMMAND + + stream+=2; + + is_cmd = true; + } + else + { + // byte0: 1aaaaaaa = NOTE + bmf.streams[channel][pos].note = *stream & 0x7F; + // byte1: 11bbbbbb = DELAY + bmf.streams[channel][pos].delay = *(stream+1) & 0x3F; + + stream+=2; + } // if (*(stream+1) & 0x40) + } + else + { + // byte0: 1aaaaaaa = NOTE + bmf.streams[channel][pos].note = *stream & 0x7F; + // byte1: 0bbbbbbb = COMMAND + + stream++; + + is_cmd = true; + } // if (*(stream+1) & 0x80) + } + else + { + // byte0: 0aaaaaaa = NOTE + bmf.streams[channel][pos].note = *stream & 0x7F; + + stream++; + } // if (*stream & 0x80) + } // if (*stream == 0xFE) + + // is command ? + if (is_cmd) + { + + /* ALL */ + + if ((0x20 <= *stream) && (*stream <= 0x3F)) + { + // 0x20 or higher; 0x3F or lower: Set Instrument + bmf.streams[channel][pos].instrument = *stream - 0x20 + 1; + + stream++; + } + else if (0x40 <= *stream) + { + // 0x40 or higher: Set Volume + bmf.streams[channel][pos].volume = *stream - 0x40 + 1; + + stream++; + } + else + { + + /* 0.9b */ + + if (bmf.version == BMF0_9B) + if (*stream < 0x20) + { + // 0x1F or lower: ? + stream++; + } + + /* 1.2 */ + + if (bmf.version == BMF1_2) + if (*stream == 0x01) + { + // 0x01: Set Modulator Volume -> 0x01 + bmf.streams[channel][pos].cmd = 0x01; + bmf.streams[channel][pos].cmd_data = *(stream+1); + + stream+=2; + } + else if (*stream == 0x02) + { + // 0x02: ? + stream+=2; + } + else if (*stream == 0x03) + { + // 0x03: ? + stream+=2; + } + else if (*stream == 0x04) + { + // 0x04: Set Speed -> 0x10 + bmf.streams[channel][pos].cmd = 0x10; + bmf.streams[channel][pos].cmd_data = *(stream+1); + + stream+=2; + } + else if (*stream == 0x05) + { + // 0x05: Set Carrier Volume (port 380) + bmf.streams[channel][pos].volume = *(stream+1) + 1; + + stream+=2; + } + else if (*stream == 0x06) + { + // 0x06: Set Carrier Volume (port 382) + bmf.streams[channel][pos].volume = *(stream+1) + 1; + + stream+=2; + } // if (bmf.version == BMF1_2) + + } // if ((0x20 <= *stream) && (*stream <= 0x3F)) + + } // if (is_cmd) + +#ifdef DEBUG + AdPlug_LogWrite("%02X %02X %02X %02X %02X %02X <---- ", + bmf.streams[channel][pos].note, + bmf.streams[channel][pos].delay, + bmf.streams[channel][pos].volume, + bmf.streams[channel][pos].instrument, + bmf.streams[channel][pos].cmd, + bmf.streams[channel][pos].cmd_data + ); + for(int zz=0;zz<(stream-last);zz++) + AdPlug_LogWrite("%02X ",last[zz]); + AdPlug_LogWrite("\n"); + last=stream; +#endif + pos++; + } // while (true) + + return (stream - stream_start); +} diff --git a/plugins/adplug/adplug/bmf.h b/plugins/adplug/adplug/bmf.h new file mode 100644 index 00000000..12cbdfd3 --- /dev/null +++ b/plugins/adplug/adplug/bmf.h @@ -0,0 +1,91 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] BMF player, by Riven the Mage <riven@ok.ru> + */ + +#include "xad.h" + +class CxadbmfPlayer: public CxadPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CxadbmfPlayer(Copl *newopl): CxadPlayer(newopl) + { }; + ~CxadbmfPlayer() + { }; + +protected: + enum { BMF0_9B, BMF1_1, BMF1_2 }; + // + struct bmf_event + { + unsigned char note; + unsigned char delay; + unsigned char volume; + unsigned char instrument; + unsigned char cmd; + unsigned char cmd_data; + }; + + struct + { + unsigned char version; + char title[36]; + char author[36]; + float timer; + unsigned char speed; + + struct + { + char name[11]; + unsigned char data[13]; + } instruments[32]; + + bmf_event streams[9][1024]; + + int active_streams; + + struct + { + unsigned short stream_position; + unsigned char delay; + unsigned short loop_position; + unsigned char loop_counter; + } channel[9]; + } bmf; + // + bool xadplayer_load(); + void xadplayer_rewind(int subsong); + void xadplayer_update(); + float xadplayer_getrefresh(); + std::string xadplayer_gettype(); + std::string xadplayer_gettitle(); + std::string xadplayer_getauthor(); + std::string xadplayer_getinstrument(unsigned int i); + unsigned int xadplayer_getinstruments(); + // +private: + static const unsigned char bmf_adlib_registers[117]; + static const unsigned short bmf_notes[12]; + static const unsigned short bmf_notes_2[12]; + static const unsigned char bmf_default_instrument[13]; + + int __bmf_convert_stream(unsigned char *stream, int channel); +}; diff --git a/plugins/adplug/adplug/cff.cpp b/plugins/adplug/adplug/cff.cpp new file mode 100644 index 00000000..37197fd9 --- /dev/null +++ b/plugins/adplug/adplug/cff.cpp @@ -0,0 +1,508 @@ +/* + AdPlug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + cff.cpp - BoomTracker loader by Riven the Mage <riven@ok.ru> +*/ +/* + NOTE: Conversion of slides is not 100% accurate. Original volume slides + have effect on carrier volume only. Also, original arpeggio, frequency & volume + slides use previous effect data instead of current. +*/ + +#include <stdlib.h> +#include <string.h> + +#include "cff.h" + +/* -------- Public Methods -------------------------------- */ + +CPlayer *CcffLoader::factory(Copl *newopl) +{ + return new CcffLoader(newopl); +} + +bool CcffLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + const unsigned char conv_inst[11] = { 2,1,10,9,4,3,6,5,0,8,7 }; + const unsigned short conv_note[12] = { 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287, 0x2AE }; + + int i,j,k,t=0; + + // '<CUD-FM-File>' - signed ? + f->readString(header.id, 16); + header.version = f->readInt(1); header.size = f->readInt(2); + header.packed = f->readInt(1); f->readString((char *)header.reserved, 12); + if (memcmp(header.id,"<CUD-FM-File>""\x1A\xDE\xE0",16)) + { fp.close(f); return false; } + + unsigned char *module = new unsigned char [0x10000]; + + // packed ? + if (header.packed) + { + cff_unpacker *unpacker = new cff_unpacker; + + unsigned char *packed_module = new unsigned char [header.size + 4]; + + memset(packed_module,0,header.size + 4); + + f->readString((char *)packed_module, header.size); + fp.close(f); + + if (!unpacker->unpack(packed_module,module)) + { + delete unpacker; + delete packed_module; + delete module; + return false; + } + + delete unpacker; + delete packed_module; + + if (memcmp(&module[0x5E1],"CUD-FM-File - SEND A POSTCARD -",31)) + { + delete module; + return false; + } + } + else + { + f->readString((char *)module, header.size); + fp.close(f); + } + + // init CmodPlayer + realloc_instruments(47); + realloc_order(64); + realloc_patterns(36,64,9); + init_notetable(conv_note); + init_trackord(); + + // load instruments + for (i=0;i<47;i++) + { + memcpy(&instruments[i],&module[i*32],sizeof(cff_instrument)); + + for (j=0;j<11;j++) + inst[i].data[conv_inst[j]] = instruments[i].data[j]; + + instruments[i].name[20] = 0; + } + + // number of patterns + nop = module[0x5E0]; + + // load title & author + memcpy(song_title,&module[0x614],20); + memcpy(song_author,&module[0x600],20); + + // load order + memcpy(order,&module[0x628],64); + + // load tracks + for (i=0;i<nop;i++) + { + unsigned char old_event_byte2[9]; + + memset(old_event_byte2,0,9); + + for (j=0;j<9;j++) + { + for (k=0;k<64;k++) + { + cff_event *event = (cff_event *)&module[0x669 + ((i*64+k)*9+j)*3]; + + // convert note + if (event->byte0 == 0x6D) + tracks[t][k].note = 127; + else + if (event->byte0) + tracks[t][k].note = event->byte0; + + if (event->byte2) + old_event_byte2[j] = event->byte2; + + // convert effect + switch (event->byte1) + { + case 'I': // set instrument + tracks[t][k].inst = event->byte2 + 1; + tracks[t][k].param1 = tracks[t][k].param2 = 0; + break; + + case 'H': // set tempo + tracks[t][k].command = 7; + if (event->byte2 < 16) + { + tracks[t][k].param1 = 0x07; + tracks[t][k].param2 = 0x0D; + } + break; + + case 'A': // set speed + tracks[t][k].command = 19; + tracks[t][k].param1 = event->byte2 >> 4; + tracks[t][k].param2 = event->byte2 & 15; + break; + + case 'L': // pattern break + tracks[t][k].command = 13; + tracks[t][k].param1 = event->byte2 >> 4; + tracks[t][k].param2 = event->byte2 & 15; + break; + + case 'K': // order jump + tracks[t][k].command = 11; + tracks[t][k].param1 = event->byte2 >> 4; + tracks[t][k].param2 = event->byte2 & 15; + break; + + case 'M': // set vibrato/tremolo + tracks[t][k].command = 27; + tracks[t][k].param1 = event->byte2 >> 4; + tracks[t][k].param2 = event->byte2 & 15; + break; + + case 'C': // set modulator volume + tracks[t][k].command = 21; + tracks[t][k].param1 = (0x3F - event->byte2) >> 4; + tracks[t][k].param2 = (0x3F - event->byte2) & 15; + break; + + case 'G': // set carrier volume + tracks[t][k].command = 22; + tracks[t][k].param1 = (0x3F - event->byte2) >> 4; + tracks[t][k].param2 = (0x3F - event->byte2) & 15; + break; + + case 'B': // set carrier waveform + tracks[t][k].command = 25; + tracks[t][k].param1 = event->byte2; + tracks[t][k].param2 = 0x0F; + break; + + case 'E': // fine frequency slide down + tracks[t][k].command = 24; + tracks[t][k].param1 = old_event_byte2[j] >> 4; + tracks[t][k].param2 = old_event_byte2[j] & 15; + break; + + case 'F': // fine frequency slide up + tracks[t][k].command = 23; + tracks[t][k].param1 = old_event_byte2[j] >> 4; + tracks[t][k].param2 = old_event_byte2[j] & 15; + break; + + case 'D': // fine volume slide + tracks[t][k].command = 14; + if (old_event_byte2[j] & 15) + { + // slide down + tracks[t][k].param1 = 5; + tracks[t][k].param2 = old_event_byte2[j] & 15; + } + else + { + // slide up + tracks[t][k].param1 = 4; + tracks[t][k].param2 = old_event_byte2[j] >> 4; + } + break; + + case 'J': // arpeggio + tracks[t][k].param1 = old_event_byte2[j] >> 4; + tracks[t][k].param2 = old_event_byte2[j] & 15; + break; + } + } + + t++; + } + } + + delete [] module; + + // order loop + restartpos = 0; + + // order length + for (i=0;i<64;i++) + { + if (order[i] >= 0x80) + { + length = i; + break; + } + } + + // default tempo + bpm = 0x7D; + + rewind(0); + + return true; +} + +void CcffLoader::rewind(int subsong) +{ + CmodPlayer::rewind(subsong); + + // default instruments + for (int i=0;i<9;i++) + { + channel[i].inst = i; + + channel[i].vol1 = 63 - (inst[i].data[10] & 63); + channel[i].vol2 = 63 - (inst[i].data[9] & 63); + } +} + +std::string CcffLoader::gettype() +{ + if (header.packed) + return std::string("BoomTracker 4, packed"); + else + return std::string("BoomTracker 4"); +} + +std::string CcffLoader::gettitle() +{ + return std::string(song_title,20); +} + +std::string CcffLoader::getauthor() +{ + return std::string(song_author,20); +} + +std::string CcffLoader::getinstrument(unsigned int n) +{ + return std::string(instruments[n].name); +} + +unsigned int CcffLoader::getinstruments() +{ + return 47; +} + +/* -------- Private Methods ------------------------------- */ + +#ifdef _WIN32 +#pragma warning(disable:4244) +#pragma warning(disable:4018) +#endif + +/* + Lempel-Ziv-Tyr ;-) +*/ +long CcffLoader::cff_unpacker::unpack(unsigned char *ibuf, unsigned char *obuf) +{ + if (memcmp(ibuf,"YsComp""\x07""CUD1997""\x1A\x04",16)) + return 0; + + input = ibuf + 16; + output = obuf; + + output_length = 0; + + heap = (unsigned char *)malloc(0x10000); + dictionary = (unsigned char **)malloc(sizeof(unsigned char *)*0x8000); + + memset(heap,0,0x10000); + memset(dictionary,0,0x8000); + + cleanup(); + if(!startup()) + goto out; + + // LZW + while (1) + { + new_code = get_code(); + + // 0x00: end of data + if (new_code == 0) + break; + + // 0x01: end of block + if (new_code == 1) + { + cleanup(); + if(!startup()) + goto out; + + continue; + } + + // 0x02: expand code length + if (new_code == 2) + { + code_length++; + + continue; + } + + // 0x03: RLE + if (new_code == 3) + { + unsigned char old_code_length = code_length; + + code_length = 2; + + unsigned char repeat_length = get_code() + 1; + + code_length = 4 << get_code(); + + unsigned long repeat_counter = get_code(); + + if(output_length + repeat_counter * repeat_length > 0x10000) { + output_length = 0; + goto out; + } + + for (unsigned int i=0;i<repeat_counter*repeat_length;i++) + output[output_length++] = output[output_length - repeat_length]; + + code_length = old_code_length; + + if(!startup()) + goto out; + + continue; + } + + if (new_code >= (0x104 + dictionary_length)) + { + // dictionary <- old.code.string + old.code.char + the_string[++the_string[0]] = the_string[1]; + } + else + { + // dictionary <- old.code.string + new.code.char + unsigned char temp_string[256]; + + translate_code(new_code,temp_string); + + the_string[++the_string[0]] = temp_string[1]; + } + + expand_dictionary(the_string); + + // output <- new.code.string + translate_code(new_code,the_string); + + if(output_length + the_string[0] > 0x10000) { + output_length = 0; + goto out; + } + + for (int i=0;i<the_string[0];i++) + output[output_length++] = the_string[i+1]; + + old_code = new_code; + } + + out: + free(heap); + free(dictionary); + return output_length; +} + +unsigned long CcffLoader::cff_unpacker::get_code() +{ + unsigned long code; + + while (bits_left < code_length) + { + bits_buffer |= ((*input++) << bits_left); + bits_left += 8; + } + + code = bits_buffer & ((1 << code_length) - 1); + + bits_buffer >>= code_length; + bits_left -= code_length; + + return code; +} + +void CcffLoader::cff_unpacker::translate_code(unsigned long code, unsigned char *string) +{ + unsigned char translated_string[256]; + + if (code >= 0x104) + { + memcpy(translated_string,dictionary[code - 0x104],(*(dictionary[code - 0x104])) + 1); + } + else + { + translated_string[0] = 1; + translated_string[1] = (code - 4) & 0xFF; + } + + memcpy(string,translated_string,256); +} + +void CcffLoader::cff_unpacker::cleanup() +{ + code_length = 9; + + bits_buffer = 0; + bits_left = 0; + + heap_length = 0; + dictionary_length = 0; +} + +int CcffLoader::cff_unpacker::startup() +{ + old_code = get_code(); + + translate_code(old_code,the_string); + + if(output_length + the_string[0] > 0x10000) { + output_length = 0; + return 0; + } + + for (int i=0;i<the_string[0];i++) + output[output_length++] = the_string[i+1]; + + return 1; +} + +void CcffLoader::cff_unpacker::expand_dictionary(unsigned char *string) +{ + if (string[0] >= 0xF0) + return; + + memcpy(&heap[heap_length],string,string[0] + 1); + + dictionary[dictionary_length] = &heap[heap_length]; + + dictionary_length++; + + heap_length += (string[0] + 1); +} + +#ifdef _WIN32 +#pragma warning(default:4244) +#pragma warning(default:4018) +#endif diff --git a/plugins/adplug/adplug/cff.h b/plugins/adplug/adplug/cff.h new file mode 100644 index 00000000..04ead8bb --- /dev/null +++ b/plugins/adplug/adplug/cff.h @@ -0,0 +1,103 @@ +/* + AdPlug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + cff.h - BoomTracker loader by Riven the Mage <riven@ok.ru> +*/ + +#include "protrack.h" + +class CcffLoader: public CmodPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CcffLoader(Copl *newopl) : CmodPlayer(newopl) { }; + + bool load(const std::string &filename, const CFileProvider &fp); + void rewind(int subsong); + + std::string gettype(); + std::string gettitle(); + std::string getauthor(); + std::string getinstrument(unsigned int n); + unsigned int getinstruments(); + + private: + + class cff_unpacker + { + public: + + long unpack(unsigned char *ibuf, unsigned char *obuf); + + private: + + unsigned long get_code(); + void translate_code(unsigned long code, unsigned char *string); + + void cleanup(); + int startup(); + + void expand_dictionary(unsigned char *string); + + unsigned char *input; + unsigned char *output; + + long output_length; + + unsigned char code_length; + + unsigned long bits_buffer; + unsigned int bits_left; + + unsigned char *heap; + unsigned char **dictionary; + + unsigned int heap_length; + unsigned int dictionary_length; + + unsigned long old_code,new_code; + + unsigned char the_string[256]; + }; + + struct cff_header + { + char id[16]; + unsigned char version; + unsigned short size; + unsigned char packed; + unsigned char reserved[12]; + } header; + + struct cff_instrument + { + unsigned char data[12]; + char name[21]; + } instruments[47]; + + char song_title[20]; + char song_author[20]; + + struct cff_event + { + unsigned char byte0; + unsigned char byte1; + unsigned char byte2; + }; +}; diff --git a/plugins/adplug/adplug/d00.cpp b/plugins/adplug/adplug/d00.cpp new file mode 100644 index 00000000..7aa042d1 --- /dev/null +++ b/plugins/adplug/adplug/d00.cpp @@ -0,0 +1,545 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * d00.c - D00 Player by Simon Peter <dn.tlp@gmx.net> + * + * NOTES: + * Sorry for the goto's, but the code looks so much nicer now. + * I tried it with while loops but it was just a mess. If you + * can come up with a nicer solution, just tell me. + * + * BUGS: + * Hard restart SR is sometimes wrong + */ + +#include <string.h> +#include <stdio.h> +#include <inttypes.h> + +#include "debug.h" +#include "d00.h" + +#define HIBYTE(val) (val >> 8) +#define LOBYTE(val) (val & 0xff) + +static const unsigned short notetable[12] = // D00 note table + {340,363,385,408,432,458,485,514,544,577,611,647}; + +static inline uint16_t LE_WORD(const uint16_t *val) +{ + const uint8_t *b = (const uint8_t *)val; + return (b[1] << 8) + b[0]; +} + +/*** public methods *************************************/ + +CPlayer *Cd00Player::factory(Copl *newopl) +{ + return new Cd00Player(newopl); +} + +bool Cd00Player::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + d00header *checkhead; + d00header1 *ch; + unsigned long filesize; + int i,ver1=0; + char *str; + + // file validation section + checkhead = new d00header; + f->readString((char *)checkhead, sizeof(d00header)); + + // Check for version 2-4 header + if(strncmp(checkhead->id,"JCH\x26\x02\x66",6) || checkhead->type || + !checkhead->subsongs || checkhead->soundcard) { + // Check for version 0 or 1 header (and .d00 file extension) + delete checkhead; + if(!fp.extension(filename, ".d00")) { fp.close(f); return false; } + ch = new d00header1; + f->seek(0); f->readString((char *)ch, sizeof(d00header1)); + if(ch->version > 1 || !ch->subsongs) + { delete ch; fp.close(f); return false; } + delete ch; + ver1 = 1; + } else + delete checkhead; + + AdPlug_LogWrite("Cd00Player::load(f,\"%s\"): %s format D00 file detected!\n", + filename.c_str(), ver1 ? "Old" : "New"); + + // load section + filesize = fp.filesize(f); f->seek(0); + filedata = new char [filesize + 1]; // 1 byte is needed for old-style DataInfo block + f->readString((char *)filedata, filesize); + fp.close(f); + if(!ver1) { // version 2 and above + header = (struct d00header *)filedata; + version = header->version; + datainfo = (char *)filedata + LE_WORD(&header->infoptr); + inst = (struct Sinsts *)((char *)filedata + LE_WORD(&header->instptr)); + seqptr = (unsigned short *)((char *)filedata + LE_WORD(&header->seqptr)); + for(i=31;i>=0;i--) // erase whitespace + if(header->songname[i] == ' ') + header->songname[i] = '\0'; + else + break; + for(i=31;i>=0;i--) + if(header->author[i] == ' ') + header->author[i] = '\0'; + else + break; + } else { // version 1 + header1 = (struct d00header1 *)filedata; + version = header1->version; + datainfo = (char *)filedata + LE_WORD(&header1->infoptr); + inst = (struct Sinsts *)((char *)filedata + LE_WORD(&header1->instptr)); + seqptr = (unsigned short *)((char *)filedata + LE_WORD(&header1->seqptr)); + } + switch(version) { + case 0: + levpuls = 0; + spfx = 0; + header1->speed = 70; // v0 files default to 70Hz + break; + case 1: + levpuls = (struct Slevpuls *)((char *)filedata + LE_WORD(&header1->lpulptr)); + spfx = 0; + break; + case 2: + levpuls = (struct Slevpuls *)((char *)filedata + LE_WORD(&header->spfxptr)); + spfx = 0; + break; + case 3: + spfx = 0; + levpuls = 0; + break; + case 4: + spfx = (struct Sspfx *)((char *)filedata + LE_WORD(&header->spfxptr)); + levpuls = 0; + break; + } + if((str = strstr(datainfo,"\xff\xff"))) + while((*str == '\xff' || *str == ' ') && str >= datainfo) { + *str = '\0'; str--; + } + else // old-style block + memset((char *)filedata+filesize,0,1); + + rewind(0); + return true; +} + +bool Cd00Player::update() +{ + unsigned char c,cnt,trackend=0,fx,note; + unsigned short ord,*patt,buf,fxop,pattpos; + + // effect handling (timer dependant) + for(c=0;c<9;c++) { + channel[c].slideval += channel[c].slide; setfreq(c); // sliding + vibrato(c); // vibrato + + if(channel[c].spfx != 0xffff) { // SpFX + if(channel[c].fxdel) + channel[c].fxdel--; + else { + channel[c].spfx = LE_WORD(&spfx[channel[c].spfx].ptr); + channel[c].fxdel = spfx[channel[c].spfx].duration; + channel[c].inst = LE_WORD(&spfx[channel[c].spfx].instnr) & 0xfff; + if(spfx[channel[c].spfx].modlev != 0xff) + channel[c].modvol = spfx[channel[c].spfx].modlev; + setinst(c); + if(LE_WORD(&spfx[channel[c].spfx].instnr) & 0x8000) // locked frequency + note = spfx[channel[c].spfx].halfnote; + else // unlocked frequency + note = spfx[channel[c].spfx].halfnote + channel[c].note; + channel[c].freq = notetable[note%12] + ((note/12) << 10); + setfreq(c); + } + channel[c].modvol += spfx[channel[c].spfx].modlevadd; channel[c].modvol &= 63; + setvolume(c); + } + + if(channel[c].levpuls != 0xff) // Levelpuls + if(channel[c].frameskip) + channel[c].frameskip--; + else { + channel[c].frameskip = inst[channel[c].inst].timer; + if(channel[c].fxdel) + channel[c].fxdel--; + else { + channel[c].levpuls = levpuls[channel[c].levpuls].ptr - 1; + channel[c].fxdel = levpuls[channel[c].levpuls].duration; + if(levpuls[channel[c].levpuls].level != 0xff) + channel[c].modvol = levpuls[channel[c].levpuls].level; + } + channel[c].modvol += levpuls[channel[c].levpuls].voladd; channel[c].modvol &= 63; + setvolume(c); + } + } + + // song handling + for(c=0;c<9;c++) + if(version < 3 ? channel[c].del : channel[c].del <= 0x7f) { + if(version == 4) // v4: hard restart SR + if(channel[c].del == inst[channel[c].inst].timer) + if(channel[c].nextnote) + opl->write(0x83 + op_table[c], inst[channel[c].inst].sr); + if(version < 3) + channel[c].del--; + else + if(channel[c].speed) + channel[c].del += channel[c].speed; + else { + channel[c].seqend = 1; + continue; + } + } else { + if(channel[c].speed) { + if(version < 3) + channel[c].del = channel[c].speed; + else { + channel[c].del &= 0x7f; + channel[c].del += channel[c].speed; + } + } else { + channel[c].seqend = 1; + continue; + } + if(channel[c].rhcnt) { // process pending REST/HOLD events + channel[c].rhcnt--; + continue; + } + readorder: // process arrangement (orderlist) + ord = LE_WORD(&channel[c].order[channel[c].ordpos]); + switch(ord) { + case 0xfffe: channel[c].seqend = 1; continue; // end of arrangement stream + case 0xffff: // jump to order + channel[c].ordpos = LE_WORD(&channel[c].order[channel[c].ordpos + 1]); + channel[c].seqend = 1; + goto readorder; + default: + if(ord >= 0x9000) { // set speed + channel[c].speed = ord & 0xff; + ord = LE_WORD(&channel[c].order[channel[c].ordpos - 1]); + channel[c].ordpos++; + } else + if(ord >= 0x8000) { // transpose track + channel[c].transpose = ord & 0xff; + if(ord & 0x100) + channel[c].transpose = -channel[c].transpose; + ord = LE_WORD(&channel[c].order[++channel[c].ordpos]); + } + patt = (unsigned short *)((char *)filedata + LE_WORD(&seqptr[ord])); + break; + } + channel[c].fxflag = 0; + readseq: // process sequence (pattern) + if(!version) // v0: always initialize rhcnt + channel[c].rhcnt = channel[c].irhcnt; + pattpos = LE_WORD(&patt[channel[c].pattpos]); + if(pattpos == 0xffff) { // pattern ended? + channel[c].pattpos = 0; + channel[c].ordpos++; + goto readorder; + } + cnt = HIBYTE(pattpos); + note = LOBYTE(pattpos); + fx = pattpos >> 12; + fxop = pattpos & 0x0fff; + channel[c].pattpos++; pattpos = LE_WORD(&patt[channel[c].pattpos]); + channel[c].nextnote = LOBYTE(pattpos) & 0x7f; + if(version ? cnt < 0x40 : !fx) { // note event + switch(note) { + case 0: // REST event + case 0x80: + if(!note || version) { + channel[c].key = 0; + setfreq(c); + } + // fall through... + case 0x7e: // HOLD event + if(version) + channel[c].rhcnt = cnt; + channel[c].nextnote = 0; + break; + default: // play note + // restart fx + if(!(channel[c].fxflag & 1)) + channel[c].vibdepth = 0; + if(!(channel[c].fxflag & 2)) + channel[c].slideval = channel[c].slide = 0; + + if(version) { // note handling for v1 and above + if(note > 0x80) // locked note (no channel transpose) + note -= 0x80; + else // unlocked note + note += channel[c].transpose; + channel[c].note = note; // remember note for SpFX + + if(channel[c].ispfx != 0xffff && cnt < 0x20) { // reset SpFX + channel[c].spfx = channel[c].ispfx; + if(LE_WORD(&spfx[channel[c].spfx].instnr) & 0x8000) // locked frequency + note = spfx[channel[c].spfx].halfnote; + else // unlocked frequency + note += spfx[channel[c].spfx].halfnote; + channel[c].inst = LE_WORD(&spfx[channel[c].spfx].instnr) & 0xfff; + channel[c].fxdel = spfx[channel[c].spfx].duration; + if(spfx[channel[c].spfx].modlev != 0xff) + channel[c].modvol = spfx[channel[c].spfx].modlev; + else + channel[c].modvol = inst[channel[c].inst].data[7] & 63; + } + + if(channel[c].ilevpuls != 0xff && cnt < 0x20) { // reset LevelPuls + channel[c].levpuls = channel[c].ilevpuls; + channel[c].fxdel = levpuls[channel[c].levpuls].duration; + channel[c].frameskip = inst[channel[c].inst].timer; + if(levpuls[channel[c].levpuls].level != 0xff) + channel[c].modvol = levpuls[channel[c].levpuls].level; + else + channel[c].modvol = inst[channel[c].inst].data[7] & 63; + } + + channel[c].freq = notetable[note%12] + ((note/12) << 10); + if(cnt < 0x20) // normal note + playnote(c); + else { // tienote + setfreq(c); + cnt -= 0x20; // make count proper + } + channel[c].rhcnt = cnt; + } else { // note handling for v0 + if(cnt < 2) // unlocked note + note += channel[c].transpose; + channel[c].note = note; + + channel[c].freq = notetable[note%12] + ((note/12) << 10); + if(cnt == 1) // tienote + setfreq(c); + else // normal note + playnote(c); + } + break; + } + continue; // event is complete + } else { // effect event + switch(fx) { + case 6: // Cut/Stop Voice + buf = channel[c].inst; + channel[c].inst = 0; + playnote(c); + channel[c].inst = buf; + channel[c].rhcnt = fxop; + continue; // no note follows this event + case 7: // Vibrato + channel[c].vibspeed = fxop & 0xff; + channel[c].vibdepth = fxop >> 8; + channel[c].trigger = fxop >> 9; + channel[c].fxflag |= 1; + break; + case 8: // v0: Duration + if(!version) + channel[c].irhcnt = fxop; + break; + case 9: // New Level + channel[c].vol = fxop & 63; + if(channel[c].vol + channel[c].cvol < 63) // apply channel volume + channel[c].vol += channel[c].cvol; + else + channel[c].vol = 63; + setvolume(c); + break; + case 0xb: // v4: Set SpFX + if(version == 4) + channel[c].ispfx = fxop; + break; + case 0xc: // Set Instrument + channel[c].ispfx = 0xffff; + channel[c].spfx = 0xffff; + channel[c].inst = fxop; + channel[c].modvol = inst[fxop].data[7] & 63; + if(version < 3 && version && inst[fxop].tunelev) // Set LevelPuls + channel[c].ilevpuls = inst[fxop].tunelev - 1; + else { + channel[c].ilevpuls = 0xff; + channel[c].levpuls = 0xff; + } + break; + case 0xd: // Slide up + channel[c].slide = fxop; + channel[c].fxflag |= 2; + break; + case 0xe: // Slide down + channel[c].slide = -fxop; + channel[c].fxflag |= 2; + break; + } + goto readseq; // event is incomplete, note follows + } + } + + for(c=0;c<9;c++) + if(channel[c].seqend) + trackend++; + if(trackend == 9) + songend = 1; + + return !songend; +} + +void Cd00Player::rewind(int subsong) +{ + struct Stpoin { + unsigned short ptr[9]; + unsigned char volume[9],dummy[5]; + } *tpoin; + int i; + + if(version > 1) { // do nothing if subsong > number of subsongs + if(subsong >= header->subsongs) + return; + } else + if(subsong >= header1->subsongs) + return; + + memset(channel,0,sizeof(channel)); + if(version > 1) + tpoin = (struct Stpoin *)((char *)filedata + LE_WORD(&header->tpoin)); + else + tpoin = (struct Stpoin *)((char *)filedata + LE_WORD(&header1->tpoin)); + for(i=0;i<9;i++) { + if(LE_WORD(&tpoin[subsong].ptr[i])) { // track enabled + channel[i].speed = LE_WORD((unsigned short *) + ((char *)filedata + LE_WORD(&tpoin[subsong].ptr[i]))); + channel[i].order = (unsigned short *) + ((char *)filedata + LE_WORD(&tpoin[subsong].ptr[i]) + 2); + } else { // track disabled + channel[i].speed = 0; + channel[i].order = 0; + } + channel[i].ispfx = 0xffff; channel[i].spfx = 0xffff; // no SpFX + channel[i].ilevpuls = 0xff; channel[i].levpuls = 0xff; // no LevelPuls + channel[i].cvol = tpoin[subsong].volume[i] & 0x7f; // our player may savely ignore bit 7 + channel[i].vol = channel[i].cvol; // initialize volume + } + songend = 0; + opl->init(); opl->write(1,32); // reset OPL chip +} + +std::string Cd00Player::gettype() +{ + char tmpstr[40]; + + sprintf(tmpstr,"EdLib packed (version %d)",version > 1 ? header->version : header1->version); + return std::string(tmpstr); +} + +float Cd00Player::getrefresh() +{ + if(version > 1) + return header->speed; + else + return header1->speed; +} + +unsigned int Cd00Player::getsubsongs() +{ + if(version <= 1) // return number of subsongs + return header1->subsongs; + else + return header->subsongs; +} + +/*** private methods *************************************/ + +void Cd00Player::setvolume(unsigned char chan) +{ + unsigned char op = op_table[chan]; + unsigned short insnr = channel[chan].inst; + + opl->write(0x43 + op,(int)(63-((63-(inst[insnr].data[2] & 63))/63.0)*(63-channel[chan].vol)) + + (inst[insnr].data[2] & 192)); + if(inst[insnr].data[10] & 1) + opl->write(0x40 + op,(int)(63-((63-channel[chan].modvol)/63.0)*(63-channel[chan].vol)) + + (inst[insnr].data[7] & 192)); + else + opl->write(0x40 + op,channel[chan].modvol + (inst[insnr].data[7] & 192)); +} + +void Cd00Player::setfreq(unsigned char chan) +{ + unsigned short freq = channel[chan].freq; + + if(version == 4) // v4: apply instrument finetune + freq += inst[channel[chan].inst].tunelev; + + freq += channel[chan].slideval; + opl->write(0xa0 + chan, freq & 255); + if(channel[chan].key) + opl->write(0xb0 + chan, ((freq >> 8) & 31) | 32); + else + opl->write(0xb0 + chan, (freq >> 8) & 31); +} + +void Cd00Player::setinst(unsigned char chan) +{ + unsigned char op = op_table[chan]; + unsigned short insnr = channel[chan].inst; + + // set instrument data + opl->write(0x63 + op, inst[insnr].data[0]); + opl->write(0x83 + op, inst[insnr].data[1]); + opl->write(0x23 + op, inst[insnr].data[3]); + opl->write(0xe3 + op, inst[insnr].data[4]); + opl->write(0x60 + op, inst[insnr].data[5]); + opl->write(0x80 + op, inst[insnr].data[6]); + opl->write(0x20 + op, inst[insnr].data[8]); + opl->write(0xe0 + op, inst[insnr].data[9]); + if(version) + opl->write(0xc0 + chan, inst[insnr].data[10]); + else + opl->write(0xc0 + chan, (inst[insnr].data[10] << 1) + (inst[insnr].tunelev & 1)); +} + +void Cd00Player::playnote(unsigned char chan) +{ + // set misc vars & play + opl->write(0xb0 + chan, 0); // stop old note + setinst(chan); + channel[chan].key = 1; + setfreq(chan); + setvolume(chan); +} + +void Cd00Player::vibrato(unsigned char chan) +{ + if(!channel[chan].vibdepth) + return; + + if(channel[chan].trigger) + channel[chan].trigger--; + else { + channel[chan].trigger = channel[chan].vibdepth; + channel[chan].vibspeed = -channel[chan].vibspeed; + } + channel[chan].freq += channel[chan].vibspeed; + setfreq(chan); +} diff --git a/plugins/adplug/adplug/d00.h b/plugins/adplug/adplug/d00.h new file mode 100644 index 00000000..50b0de3c --- /dev/null +++ b/plugins/adplug/adplug/d00.h @@ -0,0 +1,109 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * d00.h - D00 Player by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_D00 +#define H_D00 + +#include "player.h" + +class Cd00Player: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + Cd00Player(Copl *newopl) + : CPlayer(newopl), filedata(0) + { }; + ~Cd00Player() + { if(filedata) delete [] filedata; }; + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype(); + std::string gettitle() + { if(version > 1) return std::string(header->songname); else return std::string(); }; + std::string getauthor() + { if(version > 1) return std::string(header->author); else return std::string(); }; + std::string getdesc() + { if(*datainfo) return std::string(datainfo); else return std::string(); }; + unsigned int getsubsongs(); + + protected: +#pragma pack(1) + struct d00header { + char id[6]; + unsigned char type,version,speed,subsongs,soundcard; + char songname[32],author[32],dummy[32]; + unsigned short tpoin,seqptr,instptr,infoptr,spfxptr,endmark; + }; + + struct d00header1 { + unsigned char version,speed,subsongs; + unsigned short tpoin,seqptr,instptr,infoptr,lpulptr,endmark; + }; +#pragma pack() + + struct { + unsigned short *order,ordpos,pattpos,del,speed,rhcnt,key,freq,inst, + spfx,ispfx,irhcnt; + signed short transpose,slide,slideval,vibspeed; + unsigned char seqend,vol,vibdepth,fxdel,modvol,cvol,levpuls, + frameskip,nextnote,note,ilevpuls,trigger,fxflag; + } channel[9]; + + struct Sinsts { + unsigned char data[11],tunelev,timer,sr,dummy[2]; + } *inst; + + struct Sspfx { + unsigned short instnr; + signed char halfnote; + unsigned char modlev; + signed char modlevadd; + unsigned char duration; + unsigned short ptr; + } *spfx; + + struct Slevpuls { + unsigned char level; + signed char voladd; + unsigned char duration,ptr; + } *levpuls; + + unsigned char songend,version; + char *datainfo; + unsigned short *seqptr; + d00header *header; + d00header1 *header1; + char *filedata; + + private: + void setvolume(unsigned char chan); + void setfreq(unsigned char chan); + void setinst(unsigned char chan); + void playnote(unsigned char chan); + void vibrato(unsigned char chan); +}; + +#endif diff --git a/plugins/adplug/adplug/database.cpp b/plugins/adplug/adplug/database.cpp new file mode 100644 index 00000000..0a2abdad --- /dev/null +++ b/plugins/adplug/adplug/database.cpp @@ -0,0 +1,426 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (c) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * database.cpp - AdPlug database class + * Copyright (c) 2002 Riven the Mage <riven@ok.ru> + * Copyright (c) 2002, 2003, 2006 Simon Peter <dn.tlp@gmx.net> + */ + +#include <binio.h> +#include <binfile.h> +#include <string.h> + +#include "database.h" + +#define DB_FILEID_V10 "AdPlug Module Information Database 1.0\x10" + +/***** CAdPlugDatabase *****/ + +const unsigned short CAdPlugDatabase::hash_radix = 0xfff1; // should be prime + +CAdPlugDatabase::CAdPlugDatabase() + : linear_index(0), linear_logic_length(0), linear_length(0) +{ + db_linear = new DB_Bucket * [hash_radix]; + db_hashed = new DB_Bucket * [hash_radix]; + memset(db_linear, 0, sizeof(DB_Bucket *) * hash_radix); + memset(db_hashed, 0, sizeof(DB_Bucket *) * hash_radix); +} + +CAdPlugDatabase::~CAdPlugDatabase() +{ + unsigned long i; + + for(i = 0; i < linear_length; i++) + delete db_linear[i]; + + delete [] db_linear; + delete [] db_hashed; +} + +bool CAdPlugDatabase::load(std::string db_name) +{ + binifstream f(db_name); + if(f.error()) return false; + return load(f); +} + +bool CAdPlugDatabase::load(binistream &f) +{ + unsigned int idlen = strlen(DB_FILEID_V10); + char *id = new char [idlen]; + unsigned long length; + + // Open database as little endian with IEEE floats + f.setFlag(binio::BigEndian, false); f.setFlag(binio::FloatIEEE); + + f.readString(id,idlen); + if(memcmp(id,DB_FILEID_V10,idlen)) { + delete [] id; + return false; + } + delete [] id; + length = f.readInt(4); + + // read records + for(unsigned long i = 0; i < length; i++) + insert(CRecord::factory(f)); + + return true; +} + +bool CAdPlugDatabase::save(std::string db_name) +{ + binofstream f(db_name); + if(f.error()) return false; + return save(f); +} + +bool CAdPlugDatabase::save(binostream &f) +{ + unsigned long i; + + // Save database as little endian with IEEE floats + f.setFlag(binio::BigEndian, false); f.setFlag(binio::FloatIEEE); + + f.writeString(DB_FILEID_V10); + f.writeInt(linear_logic_length, 4); + + // write records + for(i = 0; i < linear_length; i++) + if(!db_linear[i]->deleted) + db_linear[i]->record->write(f); + + return true; +} + +CAdPlugDatabase::CRecord *CAdPlugDatabase::search(CKey const &key) +{ + if(lookup(key)) return get_record(); else return 0; +} + +bool CAdPlugDatabase::lookup(CKey const &key) +{ + unsigned long index = make_hash(key); + if(!db_hashed[index]) return false; + + // immediate hit ? + DB_Bucket *bucket = db_hashed[index]; + + if(!bucket->deleted && bucket->record->key == key) { + linear_index = bucket->index; + return true; + } + + // in-chain hit ? + bucket = db_hashed[index]->chain; + + while(bucket) { + if(!bucket->deleted && bucket->record->key == key) { + linear_index = bucket->index; + return true; + } + + bucket = bucket->chain; + } + + return false; +} + +bool CAdPlugDatabase::insert(CRecord *record) +{ + long index; + + // sanity checks + if(!record) return false; // null-pointer given + if(linear_length == hash_radix) return false; // max. db size exceeded + if(lookup(record->key)) return false; // record already in db + + // make bucket + DB_Bucket *bucket = new DB_Bucket(linear_length, record); + if(!bucket) return false; + + // add to linear list + db_linear[linear_length] = bucket; + linear_logic_length++; linear_length++; + + // add to hashed list + index = make_hash(record->key); + + if(!db_hashed[index]) // First entry in hashtable + db_hashed[index] = bucket; + else { // Add entry in chained list + DB_Bucket *chain = db_hashed[index]; + + while(chain->chain) chain = chain->chain; + chain->chain = bucket; + } + + return true; +} + +void CAdPlugDatabase::wipe(CRecord *record) +{ + if(!lookup(record->key)) return; + wipe(); +} + +void CAdPlugDatabase::wipe() +{ + if(!linear_length) return; + + DB_Bucket *bucket = db_linear[linear_index]; + + if(!bucket->deleted) { + delete bucket->record; + linear_logic_length--; + bucket->deleted = true; + } +} + +CAdPlugDatabase::CRecord *CAdPlugDatabase::get_record() +{ + if(!linear_length) return 0; + return db_linear[linear_index]->record; +} + +bool CAdPlugDatabase::go_forward() +{ + if(linear_index + 1 < linear_length) { + linear_index++; + return true; + } else + return false; +} + +bool CAdPlugDatabase::go_backward() +{ + if(!linear_index) return false; + linear_index--; + return true; +} + +void CAdPlugDatabase::goto_begin() +{ + if(linear_length) linear_index = 0; +} + +void CAdPlugDatabase::goto_end() +{ + if(linear_length) linear_index = linear_length - 1; +} + +inline unsigned long CAdPlugDatabase::make_hash(CKey const &key) +{ + return (key.crc32 + key.crc16) % hash_radix; +} + +/***** CAdPlugDatabase::DB_Bucket *****/ + +CAdPlugDatabase::DB_Bucket::DB_Bucket(unsigned long nindex, CRecord *newrecord, DB_Bucket *newchain) + : index(nindex), deleted(false), chain(newchain), record(newrecord) +{ +} + +CAdPlugDatabase::DB_Bucket::~DB_Bucket() +{ + if(!deleted) delete record; +} + +/***** CAdPlugDatabase::CRecord *****/ + +CAdPlugDatabase::CRecord *CAdPlugDatabase::CRecord::factory(RecordType type) +{ + switch(type) { + case Plain: return new CPlainRecord; + case SongInfo: return new CInfoRecord; + case ClockSpeed: return new CClockRecord; + default: return 0; + } +} + +CAdPlugDatabase::CRecord *CAdPlugDatabase::CRecord::factory(binistream &in) +{ + RecordType type; + unsigned long size; + CRecord *rec; + + type = (RecordType)in.readInt(1); size = in.readInt(4); + rec = factory(type); + + if(rec) { + rec->key.crc16 = in.readInt(2); rec->key.crc32 = in.readInt(4); + rec->filetype = in.readString('\0'); rec->comment = in.readString('\0'); + rec->read_own(in); + return rec; + } else { + // skip this record, cause we don't know about it + in.seek(size, binio::Add); + return 0; + } +} + +void CAdPlugDatabase::CRecord::write(binostream &out) +{ + out.writeInt(type, 1); + out.writeInt(get_size() + filetype.length() + comment.length() + 8, 4); + out.writeInt(key.crc16, 2); out.writeInt(key.crc32, 4); + out.writeString(filetype); out.writeInt('\0', 1); + out.writeString(comment); out.writeInt('\0', 1); + + write_own(out); +} + +bool CAdPlugDatabase::CRecord::user_read(std::istream &in, std::ostream &out) +{ + return user_read_own(in, out); +} + +bool CAdPlugDatabase::CRecord::user_write(std::ostream &out) +{ + out << "Record type: "; + switch(type) { + case Plain: out << "Plain"; break; + case SongInfo: out << "SongInfo"; break; + case ClockSpeed: out << "ClockSpeed"; break; + default: out << "*** Unknown ***"; break; + } + out << std::endl; + out << "Key: " << std::hex << key.crc16 << ":" << key.crc32 << std::dec << std::endl; + out << "File type: " << filetype << std::endl; + out << "Comment: " << comment << std::endl; + + return user_write_own(out); +} + +/***** CAdPlugDatabase::CRecord::CKey *****/ + +CAdPlugDatabase::CKey::CKey(binistream &buf) +{ + make(buf); +} + +bool CAdPlugDatabase::CKey::operator==(const CKey &key) +{ + return ((crc16 == key.crc16) && (crc32 == key.crc32)); +} + +void CAdPlugDatabase::CKey::make(binistream &buf) +// Key is CRC16:CRC32 pair. CRC16 and CRC32 calculation routines (c) Zhengxi +{ + static const unsigned short magic16 = 0xa001; + static const unsigned long magic32 = 0xedb88320; + + crc16 = 0; crc32 = ~0; + + while(!buf.eof()) + { + unsigned char byte = buf.readInt(1); + + for (int j=0;j<8;j++) + { + if ((crc16 ^ byte) & 1) + crc16 = (crc16 >> 1) ^ magic16; + else + crc16 >>= 1; + + if ((crc32 ^ byte) & 1) + crc32 = (crc32 >> 1) ^ magic32; + else + crc32 >>= 1; + + byte >>= 1; + } + } + + crc16 &= 0xffff; + crc32 = ~crc32; +} + +/***** CInfoRecord *****/ + +CInfoRecord::CInfoRecord() +{ + type = SongInfo; +} + +void CInfoRecord::read_own(binistream &in) +{ + title = in.readString('\0'); + author = in.readString('\0'); +} + +void CInfoRecord::write_own(binostream &out) +{ + out.writeString(title); out.writeInt('\0', 1); + out.writeString(author); out.writeInt('\0', 1); +} + +unsigned long CInfoRecord::get_size() +{ + return title.length() + author.length() + 2; +} + +bool CInfoRecord::user_read_own(std::istream &in, std::ostream &out) +{ + out << "Title: "; in >> title; + out << "Author: "; in >> author; + return true; +} + +bool CInfoRecord::user_write_own(std::ostream &out) +{ + out << "Title: " << title << std::endl; + out << "Author: " << author << std::endl; + return true; +} + +/***** CClockRecord *****/ + +CClockRecord::CClockRecord() + : clock(0.0f) +{ + type = ClockSpeed; +} + +void CClockRecord::read_own(binistream &in) +{ + clock = in.readFloat(binio::Single); +} + +void CClockRecord::write_own(binostream &out) +{ + out.writeFloat(clock, binio::Single); +} + +unsigned long CClockRecord::get_size() +{ + return 4; +} + +bool CClockRecord::user_read_own(std::istream &in, std::ostream &out) +{ + out << "Clockspeed: "; in >> clock; + return true; +} + +bool CClockRecord::user_write_own(std::ostream &out) +{ + out << "Clock speed: " << clock << " Hz" << std::endl; + return true; +} diff --git a/plugins/adplug/adplug/database.h b/plugins/adplug/adplug/database.h new file mode 100644 index 00000000..050e8959 --- /dev/null +++ b/plugins/adplug/adplug/database.h @@ -0,0 +1,169 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (c) 1999 - 2003 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * database.h - AdPlug database class + * Copyright (c) 2002 Riven the Mage <riven@ok.ru> + * Copyright (c) 2002, 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_DATABASE +#define H_ADPLUG_DATABASE + +#include <iostream> +#include <string> +#include <binio.h> + +class CAdPlugDatabase +{ +public: + class CKey + { + public: + unsigned short crc16; + unsigned long crc32; + + CKey() {}; + CKey(binistream &in); + + bool operator==(const CKey &key); + + private: + void make(binistream &in); + }; + + class CRecord + { + public: + typedef enum { Plain, SongInfo, ClockSpeed } RecordType; + + RecordType type; + CKey key; + std::string filetype, comment; + + static CRecord *factory(RecordType type); + static CRecord *factory(binistream &in); + + CRecord() {} + virtual ~CRecord() {} + + void write(binostream &out); + + bool user_read(std::istream &in, std::ostream &out); + bool user_write(std::ostream &out); + + protected: + virtual void read_own(binistream &in) = 0; + virtual void write_own(binostream &out) = 0; + virtual unsigned long get_size() = 0; + virtual bool user_read_own(std::istream &in, std::ostream &out) = 0; + virtual bool user_write_own(std::ostream &out) = 0; + }; + + CAdPlugDatabase(); + ~CAdPlugDatabase(); + + bool load(std::string db_name); + bool load(binistream &f); + bool save(std::string db_name); + bool save(binostream &f); + + bool insert(CRecord *record); + + void wipe(CRecord *record); + void wipe(); + + CRecord *search(CKey const &key); + bool lookup(CKey const &key); + + CRecord *get_record(); + + bool go_forward(); + bool go_backward(); + + void goto_begin(); + void goto_end(); + +private: + static const unsigned short hash_radix; + + class DB_Bucket + { + public: + unsigned long index; + bool deleted; + DB_Bucket *chain; + + CRecord *record; + + DB_Bucket(unsigned long nindex, CRecord *newrecord, DB_Bucket *newchain = 0); + ~DB_Bucket(); + }; + + DB_Bucket **db_linear; + DB_Bucket **db_hashed; + + unsigned long linear_index, linear_logic_length, linear_length; + + unsigned long make_hash(CKey const &key); +}; + +class CPlainRecord: public CAdPlugDatabase::CRecord +{ +public: + CPlainRecord() { type = Plain; } + +protected: + virtual void read_own(binistream &in) {} + virtual void write_own(binostream &out) {} + virtual unsigned long get_size() { return 0; } + virtual bool user_read_own(std::istream &in, std::ostream &out) { return true; } + virtual bool user_write_own(std::ostream &out) { return true; } +}; + +class CInfoRecord: public CAdPlugDatabase::CRecord +{ +public: + std::string title; + std::string author; + + CInfoRecord(); + +protected: + virtual void read_own(binistream &in); + virtual void write_own(binostream &out); + virtual unsigned long get_size(); + virtual bool user_read_own(std::istream &in, std::ostream &out); + virtual bool user_write_own(std::ostream &out); +}; + +class CClockRecord: public CAdPlugDatabase::CRecord +{ +public: + float clock; + + CClockRecord(); + +protected: + virtual void read_own(binistream &in); + virtual void write_own(binostream &out); + virtual unsigned long get_size(); + virtual bool user_read_own(std::istream &in, std::ostream &out); + virtual bool user_write_own(std::ostream &out); +}; + +#endif diff --git a/plugins/adplug/adplug/debug.c b/plugins/adplug/adplug/debug.c new file mode 100644 index 00000000..bd6598da --- /dev/null +++ b/plugins/adplug/adplug/debug.c @@ -0,0 +1,57 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2002 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * debug.h - AdPlug Debug Logger + * Copyright (c) 2002 Riven the Mage <riven@ok.ru> + * Copyright (c) 2002 Simon Peter <dn.tlp@gmx.net> + */ + +#ifdef DEBUG + +#include <stdio.h> +#include <stdarg.h> + +static FILE *log = NULL; + +void AdPlug_LogFile(const char *filename) +{ + if(log) fclose(log); + log = fopen(filename,"wt"); +} + +void AdPlug_LogWrite(const char *fmt, ...) +{ + va_list argptr; + + va_start(argptr, fmt); + + if(log) { + vfprintf(log, fmt, argptr); + fflush(log); + } else + vfprintf(stderr, fmt, argptr); + + va_end(argptr); +} + +#else + +void AdPlug_LogFile(char *filename) { } +void AdPlug_LogWrite(char *fmt, ...) { } + +#endif diff --git a/plugins/adplug/adplug/debug.h b/plugins/adplug/adplug/debug.h new file mode 100644 index 00000000..e5832f49 --- /dev/null +++ b/plugins/adplug/adplug/debug.h @@ -0,0 +1,41 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2002 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * debug.h - AdPlug Debug Logger + * Copyright (c) 2002 Riven the Mage <riven@ok.ru> + * Copyright (c) 2002 Simon Peter <dn.tlp@gmx.net> + * + * NOTES: + * This debug logger is used throughout AdPlug to log debug output to stderr + * (the default) or to a user-specified logfile. + * + * To use it, AdPlug has to be compiled with debug logging support enabled. + * This is done by defining the DEBUG macro with every source-file. The + * LogFile() function can be used to specify a logfile to write to. + */ + +#ifndef H_DEBUG +#define H_DEBUG + +extern "C" +{ + void AdPlug_LogFile(const char *filename); + void AdPlug_LogWrite(const char *fmt, ...); +} + +#endif diff --git a/plugins/adplug/adplug/dfm.cpp b/plugins/adplug/adplug/dfm.cpp new file mode 100644 index 00000000..4a70a032 --- /dev/null +++ b/plugins/adplug/adplug/dfm.cpp @@ -0,0 +1,115 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * dfm.cpp - Digital-FM Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include <stdio.h> +#include <string.h> + +#include "dfm.h" +#include "debug.h" + +CPlayer *CdfmLoader::factory(Copl *newopl) +{ + return new CdfmLoader(newopl); +} + +bool CdfmLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + unsigned char npats,n,note,fx,c,r,param; + unsigned int i; + const unsigned char convfx[8] = {255,255,17,19,23,24,255,13}; + + // file validation + f->readString(header.id, 4); + header.hiver = f->readInt(1); header.lover = f->readInt(1); + if(strncmp(header.id,"DFM\x1a",4) || header.hiver > 1) + { fp.close(f); return false; } + + // load + restartpos = 0; flags = Standard; bpm = 0; + init_trackord(); + f->readString(songinfo, 33); + initspeed = f->readInt(1); + for(i = 0; i < 32; i++) + f->readString(instname[i], 12); + for(i = 0; i < 32; i++) { + inst[i].data[1] = f->readInt(1); + inst[i].data[2] = f->readInt(1); + inst[i].data[9] = f->readInt(1); + inst[i].data[10] = f->readInt(1); + inst[i].data[3] = f->readInt(1); + inst[i].data[4] = f->readInt(1); + inst[i].data[5] = f->readInt(1); + inst[i].data[6] = f->readInt(1); + inst[i].data[7] = f->readInt(1); + inst[i].data[8] = f->readInt(1); + inst[i].data[0] = f->readInt(1); + } + for(i = 0; i < 128; i++) order[i] = f->readInt(1); + for(i = 0; i < 128 && order[i] != 128; i++) ; length = i; + npats = f->readInt(1); + for(i = 0; i < npats; i++) { + n = f->readInt(1); + for(r = 0; r < 64; r++) + for(c = 0; c < 9; c++) { + note = f->readInt(1); + if((note & 15) == 15) + tracks[n*9+c][r].note = 127; // key off + else + tracks[n*9+c][r].note = ((note & 127) >> 4) * 12 + (note & 15); + if(note & 128) { // additional effect byte + fx = f->readInt(1); + if(fx >> 5 == 1) + tracks[n*9+c][r].inst = (fx & 31) + 1; + else { + tracks[n*9+c][r].command = convfx[fx >> 5]; + if(tracks[n*9+c][r].command == 17) { // set volume + param = fx & 31; + param = 63 - param * 2; + tracks[n*9+c][r].param1 = param >> 4; + tracks[n*9+c][r].param2 = param & 15; + } else { + tracks[n*9+c][r].param1 = (fx & 31) >> 4; + tracks[n*9+c][r].param2 = fx & 15; + } + } + } + + } + } + + fp.close(f); + rewind(0); + return true; +} + +std::string CdfmLoader::gettype() +{ + char tmpstr[20]; + + sprintf(tmpstr,"Digital-FM %d.%d",header.hiver,header.lover); + return std::string(tmpstr); +} + +float CdfmLoader::getrefresh() +{ + return 125.0f; +} diff --git a/plugins/adplug/adplug/dfm.h b/plugins/adplug/adplug/dfm.h new file mode 100644 index 00000000..4212b2fe --- /dev/null +++ b/plugins/adplug/adplug/dfm.h @@ -0,0 +1,52 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * dfm.h - Digital-FM Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include "protrack.h" + +class CdfmLoader: public CmodPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CdfmLoader(Copl *newopl) + : CmodPlayer(newopl) + { }; + + bool load(const std::string &filename, const CFileProvider &fp); + float getrefresh(); + + std::string gettype(); + unsigned int getinstruments() + { return 32; }; + std::string getinstrument(unsigned int n) + { if(*instname[n]) return std::string(instname[n],1,*instname[n]); else return std::string(); }; + std::string getdesc() + { return std::string(songinfo,1,*songinfo); }; + +private: + struct { + char id[4]; + unsigned char hiver,lover; + } header; + + char songinfo[33]; + char instname[32][12]; +}; diff --git a/plugins/adplug/adplug/diskopl.cpp b/plugins/adplug/adplug/diskopl.cpp new file mode 100644 index 00000000..3296c0a1 --- /dev/null +++ b/plugins/adplug/adplug/diskopl.cpp @@ -0,0 +1,90 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * diskopl.cpp - Disk Writer OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "diskopl.h" + +//static const unsigned short note_table[12] = {363,385,408,432,458,485,514,544,577,611,647,686}; +const unsigned char CDiskopl::op_table[9] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12}; + +CDiskopl::CDiskopl(std::string filename) + : old_freq(0.0f), del(1), nowrite(false) +{ + unsigned short clock = 0xffff; + + currType = TYPE_OPL3; + f = fopen(filename.c_str(),"wb"); + fwrite("RAWADATA",8,1,f); + fwrite(&clock,sizeof(clock),1,f); +} + +CDiskopl::~CDiskopl() +{ + fclose(f); +} + +void CDiskopl::update(CPlayer *p) +{ + unsigned short clock; + unsigned int wait; + + if(p->getrefresh() != old_freq) { + old_freq = p->getrefresh(); + del = wait = (unsigned int)(18.2f / old_freq); + clock = (unsigned short)(1192737/(old_freq*(wait+1))); + fputc(0,f); fputc(2,f); + fwrite(&clock,2,1,f); + } + if(!nowrite) { + fputc(del+1,f); + fputc(0,f); + } +} + +void CDiskopl::setchip(int n) +{ + Copl::setchip(n); + + if(!nowrite) { + fputc(currChip + 1, f); + fputc(2, f); + } +} + +void CDiskopl::write(int reg, int val) +{ + if(!nowrite) + diskwrite(reg,val); +} + +void CDiskopl::init() +{ + for (int i=0;i<9;i++) { // stop instruments + diskwrite(0xb0 + i,0); // key off + diskwrite(0x80 + op_table[i],0xff); // fastest release + } + diskwrite(0xbd,0); // clear misc. register +} + +void CDiskopl::diskwrite(int reg, int val) +{ + fputc(val,f); + fputc(reg,f); +} diff --git a/plugins/adplug/adplug/diskopl.h b/plugins/adplug/adplug/diskopl.h new file mode 100644 index 00000000..929f2906 --- /dev/null +++ b/plugins/adplug/adplug/diskopl.h @@ -0,0 +1,52 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * diskopl.h - Disk Writer OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string> +#include <stdio.h> +#include "opl.h" +#include "player.h" + +class CDiskopl: public Copl +{ + public: + CDiskopl(std::string filename); + virtual ~CDiskopl(); + + void update(CPlayer *p); // write to file + void setnowrite(bool nw = true) // set file write status + { nowrite = nw; }; + + void setchip(int n); + + // template methods + void write(int reg, int val); + void init(); + + private: + static const unsigned char op_table[9]; + + FILE *f; + float old_freq; + unsigned char del; + bool nowrite; // don't write to file, if true + + void diskwrite(int reg, int val); +}; diff --git a/plugins/adplug/adplug/dmo.cpp b/plugins/adplug/adplug/dmo.cpp new file mode 100644 index 00000000..c25293c4 --- /dev/null +++ b/plugins/adplug/adplug/dmo.cpp @@ -0,0 +1,403 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2004, 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + dmo.cpp - TwinTeam loader by Riven the Mage <riven@ok.ru> +*/ +/* + NOTES: + Panning is ignored. + + A WORD ist 16 bits, a DWORD is 32 bits and a BYTE is 8 bits in this context. +*/ + +#include <string.h> +#include <binstr.h> + +#include "dmo.h" +#include "debug.h" + +#define LOWORD(l) ((l) & 0xffff) +#define HIWORD(l) ((l) >> 16) +#define LOBYTE(w) ((w) & 0xff) +#define HIBYTE(w) ((w) >> 8) + +#define ARRAY_AS_DWORD(a, i) \ +((a[i + 3] << 24) + (a[i + 2] << 16) + (a[i + 1] << 8) + a[i]) +#define ARRAY_AS_WORD(a, i) ((a[i + 1] << 8) + a[i]) + +#define CHARP_AS_WORD(p) (((*(p + 1)) << 8) + (*p)) + +/* -------- Public Methods -------------------------------- */ + +CPlayer *CdmoLoader::factory(Copl *newopl) +{ + return new CdmoLoader(newopl); +} + +bool CdmoLoader::load(const std::string &filename, const CFileProvider &fp) +{ + int i,j; + binistream *f; + + // check header + dmo_unpacker *unpacker = new dmo_unpacker; + unsigned char chkhdr[16]; + + if(!fp.extension(filename, ".dmo")) return false; + f = fp.open(filename); if(!f) return false; + + f->readString((char *)chkhdr, 16); + + if (!unpacker->decrypt(chkhdr, 16)) + { + delete unpacker; + fp.close(f); + return false; + } + + // get file size + long packed_length = fp.filesize(f); + f->seek(0); + + unsigned char *packed_module = new unsigned char [packed_length]; + + // load file + f->readString((char *)packed_module, packed_length); + fp.close(f); + + // decrypt + unpacker->decrypt(packed_module,packed_length); + + long unpacked_length = 0x2000 * ARRAY_AS_WORD(packed_module, 12); + unsigned char *module = new unsigned char [unpacked_length]; + + // unpack + if (!unpacker->unpack(packed_module+12,module,unpacked_length)) + { + delete unpacker; + delete [] packed_module; + delete [] module; + return false; + } + + delete unpacker; + delete [] packed_module; + + // "TwinTeam" - signed ? + if (memcmp(module,"TwinTeam Module File""\x0D\x0A",22)) + { + delete module; + return false; + } + + // load header + binisstream uf(module, unpacked_length); + uf.setFlag(binio::BigEndian, false); uf.setFlag(binio::FloatIEEE); + + memset(&header,0,sizeof(s3mheader)); + + uf.ignore(22); // ignore DMO header ID string + uf.readString(header.name, 28); + + uf.ignore(2); // _unk_1 + header.ordnum = uf.readInt(2); + header.insnum = uf.readInt(2); + header.patnum = uf.readInt(2); + uf.ignore(2); // _unk_2 + header.is = uf.readInt(2); + header.it = uf.readInt(2); + + memset(header.chanset,0xFF,32); + + for (i=0;i<9;i++) + header.chanset[i] = 0x10 + i; + + uf.ignore(32); // ignore panning settings for all 32 channels + + // load orders + for(i = 0; i < 256; i++) orders[i] = uf.readInt(1); + + orders[header.ordnum] = 0xFF; + + // load pattern lengths + unsigned short my_patlen[100]; + for(i = 0; i < 100; i++) my_patlen[i] = uf.readInt(2); + + // load instruments + for (i = 0; i < header.insnum; i++) + { + memset(&inst[i],0,sizeof(s3minst)); + + uf.readString(inst[i].name, 28); + + inst[i].volume = uf.readInt(1); + inst[i].dsk = uf.readInt(1); + inst[i].c2spd = uf.readInt(4); + inst[i].type = uf.readInt(1); + inst[i].d00 = uf.readInt(1); + inst[i].d01 = uf.readInt(1); + inst[i].d02 = uf.readInt(1); + inst[i].d03 = uf.readInt(1); + inst[i].d04 = uf.readInt(1); + inst[i].d05 = uf.readInt(1); + inst[i].d06 = uf.readInt(1); + inst[i].d07 = uf.readInt(1); + inst[i].d08 = uf.readInt(1); + inst[i].d09 = uf.readInt(1); + inst[i].d0a = uf.readInt(1); + /* + * Originally, riven sets d0b = d0a and ignores 1 byte in the + * stream, but i guess this was a typo, so i read it here. + */ + inst[i].d0b = uf.readInt(1); + } + + // load patterns + for (i = 0; i < header.patnum; i++) { + long cur_pos = uf.pos(); + + for (j = 0; j < 64; j++) { + while (1) { + unsigned char token = uf.readInt(1); + + if (!token) + break; + + unsigned char chan = token & 31; + + // note + instrument ? + if (token & 32) { + unsigned char bufbyte = uf.readInt(1); + + pattern[i][j][chan].note = bufbyte & 15; + pattern[i][j][chan].oct = bufbyte >> 4; + pattern[i][j][chan].instrument = uf.readInt(1); + } + + // volume ? + if (token & 64) + pattern[i][j][chan].volume = uf.readInt(1); + + // command ? + if (token & 128) { + pattern[i][j][chan].command = uf.readInt(1); + pattern[i][j][chan].info = uf.readInt(1); + } + } + } + + uf.seek(cur_pos + my_patlen[i]); + } + + delete [] module; + rewind(0); + return true; +} + +std::string CdmoLoader::gettype() +{ + return std::string("TwinTeam (packed S3M)"); +} + +std::string CdmoLoader::getauthor() +{ + /* + All available .DMO modules written by one composer. And because all .DMO + stuff was lost due to hd crash (TwinTeam guys said this), there are + never(?) be another. + */ + return std::string("Benjamin GERARDIN"); +} + +/* -------- Private Methods ------------------------------- */ + +unsigned short CdmoLoader::dmo_unpacker::brand(unsigned short range) +{ + unsigned short ax,bx,cx,dx; + + ax = LOWORD(bseed); + bx = HIWORD(bseed); + cx = ax; + ax = LOWORD(cx * 0x8405); + dx = HIWORD(cx * 0x8405); + cx <<= 3; + cx = (((HIBYTE(cx) + LOBYTE(cx)) & 0xFF) << 8) + LOBYTE(cx); + dx += cx; + dx += bx; + bx <<= 2; + dx += bx; + dx = (((HIBYTE(dx) + LOBYTE(bx)) & 0xFF) << 8) + LOBYTE(dx); + bx <<= 5; + dx = (((HIBYTE(dx) + LOBYTE(bx)) & 0xFF) << 8) + LOBYTE(dx); + ax += 1; + if (!ax) dx += 1; + + // leave it that way or amd64 might get it wrong + bseed = dx; + bseed <<= 16; + bseed += ax; + + return HIWORD(HIWORD(LOWORD(bseed) * range) + HIWORD(bseed) * range); +} + +bool CdmoLoader::dmo_unpacker::decrypt(unsigned char *buf, long len) +{ + unsigned long seed = 0; + int i; + + bseed = ARRAY_AS_DWORD(buf, 0); + + for (i=0; i < ARRAY_AS_WORD(buf, 4) + 1; i++) + seed += brand(0xffff); + + bseed = seed ^ ARRAY_AS_DWORD(buf, 6); + + if (ARRAY_AS_WORD(buf, 10) != brand(0xffff)) + return false; + + for (i=0;i<(len-12);i++) + buf[12+i] ^= brand(0x100); + + buf[len - 2] = buf[len - 1] = 0; + + return true; +} + +short CdmoLoader::dmo_unpacker::unpack_block(unsigned char *ibuf, long ilen, unsigned char *obuf) +{ + unsigned char code,par1,par2; + unsigned short ax,bx,cx; + + unsigned char *ipos = ibuf; + unsigned char *opos = obuf; + + // LZ77 child + while (ipos - ibuf < ilen) + { + code = *ipos++; + + // 00xxxxxx: copy (xxxxxx + 1) bytes + if ((code >> 6) == 0) + { + cx = (code & 0x3F) + 1; + + if(opos + cx >= oend) + return -1; + + for (int i=0;i<cx;i++) + *opos++ = *ipos++; + + continue; + } + + // 01xxxxxx xxxyyyyy: copy (Y + 3) bytes from (X + 1) + if ((code >> 6) == 1) + { + par1 = *ipos++; + + ax = ((code & 0x3F) << 3) + ((par1 & 0xE0) >> 5) + 1; + cx = (par1 & 0x1F) + 3; + + if(opos + cx >= oend) + return -1; + + for(int i=0;i<cx;i++) + *opos++ = *(opos - ax); + + continue; + } + + // 10xxxxxx xyyyzzzz: copy (Y + 3) bytes from (X + 1); copy Z bytes + if ((code >> 6) == 2) + { + int i; + + par1 = *ipos++; + + ax = ((code & 0x3F) << 1) + (par1 >> 7) + 1; + cx = ((par1 & 0x70) >> 4) + 3; + bx = par1 & 0x0F; + + if(opos + bx + cx >= oend) + return -1; + + for(i=0;i<cx;i++) + *opos++ = *(opos - ax); + + for (i=0;i<bx;i++) + *opos++ = *ipos++; + + continue; + } + + // 11xxxxxx xxxxxxxy yyyyzzzz: copy (Y + 4) from X; copy Z bytes + if ((code >> 6) == 3) + { + int i; + + par1 = *ipos++; + par2 = *ipos++; + + bx = ((code & 0x3F) << 7) + (par1 >> 1); + cx = ((par1 & 0x01) << 4) + (par2 >> 4) + 4; + ax = par2 & 0x0F; + + if(opos + ax + cx >= oend) + return -1; + + for(i=0;i<cx;i++) + *opos++ = *(opos - bx); + + for (i=0;i<ax;i++) + *opos++ = *ipos++; + + continue; + } + } + + return opos - obuf; +} + +long CdmoLoader::dmo_unpacker::unpack(unsigned char *ibuf, unsigned char *obuf, + unsigned long outputsize) +{ + long olen = 0; + unsigned short block_count = CHARP_AS_WORD(ibuf); + + ibuf += 2; + unsigned char *block_length = ibuf; + ibuf += 2 * block_count; + + oend = obuf + outputsize; + + for (int i=0;i<block_count;i++) + { + unsigned short bul = CHARP_AS_WORD(ibuf); + + if(unpack_block(ibuf + 2,CHARP_AS_WORD(block_length) - 2,obuf) != bul) + return 0; + + obuf += bul; + olen += bul; + + ibuf += CHARP_AS_WORD(block_length); + block_length += 2; + } + + return olen; +} diff --git a/plugins/adplug/adplug/dmo.h b/plugins/adplug/adplug/dmo.h new file mode 100644 index 00000000..8591cfcc --- /dev/null +++ b/plugins/adplug/adplug/dmo.h @@ -0,0 +1,51 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + dmo.cpp - TwinTeam loader by Riven the Mage <riven@ok.ru> +*/ + +#include "s3m.h" + +class CdmoLoader: public Cs3mPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CdmoLoader(Copl *newopl) : Cs3mPlayer(newopl) { }; + + bool load(const std::string &filename, const CFileProvider &fp); + + std::string gettype(); + std::string getauthor(); + + private: + + class dmo_unpacker { + public: + bool decrypt(unsigned char *buf, long len); + long unpack(unsigned char *ibuf, unsigned char *obuf, + unsigned long outputsize); + + private: + unsigned short brand(unsigned short range); + short unpack_block(unsigned char *ibuf, long ilen, unsigned char *obuf); + + unsigned long bseed; + unsigned char *oend; + }; +}; diff --git a/plugins/adplug/adplug/dro.cpp b/plugins/adplug/adplug/dro.cpp new file mode 100644 index 00000000..eb97c0f8 --- /dev/null +++ b/plugins/adplug/adplug/dro.cpp @@ -0,0 +1,130 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * dro.c - DOSBox Raw OPL Player by Sjoerd van der Berg <harekiet@zophar.net> + * + * upgraded by matthew gambrell <zeromus@zeromus.org> + * + * NOTES: 3-oct-04: the DRO format is not yet finalized. beware. + */ + +#include <stdio.h> +#include <string.h> + +#include "dro.h" + +/*** public methods *************************************/ + +CPlayer *CdroPlayer::factory(Copl *newopl) +{ + return new CdroPlayer(newopl); +} + +CdroPlayer::CdroPlayer(Copl *newopl) + : CPlayer(newopl), data(0) +{ + if(opl->gettype() == Copl::TYPE_OPL2) + opl3_mode = 0; + else + opl3_mode = 1; +} + +bool CdroPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + char id[8]; + unsigned long i; + + // file validation section + f->readString(id, 8); + if(strncmp(id,"DBRAWOPL",8)) { fp.close (f); return false; } + int version = f->readInt(4); // not very useful just yet + if(version != 0x10000) { fp.close(f); return false; } + + // load section + mstotal = f->readInt(4); // Total milliseconds in file + length = f->readInt(4); // Total data bytes in file + f->ignore(1); // Type of opl data this can contain - ignored + data = new unsigned char [length]; + for (i=0;i<length;i++) + data[i]=f->readInt(1); + fp.close(f); + rewind(0); + return true; +} + +bool CdroPlayer::update() +{ + if (delay>500) { + delay-=500; + return true; + } else + delay=0; + + while (pos < length) { + unsigned char cmd = data[pos++]; + switch(cmd) { + case 0: + delay = 1 + data[pos++]; + return true; + case 1: + delay = 1 + data[pos] + (data[pos+1]<<8); + pos+=2; + return true; + case 2: + index = 0; + opl->setchip(0); + break; + case 3: + index = 1; + opl->setchip(1); + break; + default: + if(cmd==4) cmd = data[pos++]; //data override + if(index == 0 || opl3_mode) + opl->write(cmd,data[pos++]); + break; + } + } + + return pos<length; +} + +void CdroPlayer::rewind(int subsong) +{ + delay=1; + pos = index = 0; + opl->init(); + + //dro assumes all registers are initialized to 0 + //registers not initialized to 0 will be corrected + //in the data stream + for(int i=0;i<256;i++) + opl->write(i,0); + + opl->setchip(1); + for(int i=0;i<256;i++) + opl->write(i,0); + opl->setchip(0); +} + +float CdroPlayer::getrefresh() +{ + if (delay > 500) return 1000 / 500; + else return 1000 / (double)delay; +} diff --git a/plugins/adplug/adplug/dro.h b/plugins/adplug/adplug/dro.h new file mode 100644 index 00000000..0f59e7b1 --- /dev/null +++ b/plugins/adplug/adplug/dro.h @@ -0,0 +1,52 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * dro.h - DOSBox Raw OPL Player by Sjoerd van der Berg <harekiet@zophar.net> + */ + +#include "player.h" + +class CdroPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CdroPlayer(Copl *newopl); + ~CdroPlayer() + { + if(data) + delete [] data; + } + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype() + { + return std::string("DOSBox Raw OPL"); + } + + protected: + unsigned char *data; + unsigned long pos,length; + unsigned long msdone,mstotal; + unsigned short delay; + unsigned char index, opl3_mode; +}; diff --git a/plugins/adplug/adplug/dtm.cpp b/plugins/adplug/adplug/dtm.cpp new file mode 100644 index 00000000..3d022ae8 --- /dev/null +++ b/plugins/adplug/adplug/dtm.cpp @@ -0,0 +1,317 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + dtm.cpp - DTM loader by Riven the Mage <riven@ok.ru> +*/ +/* + NOTE: Panning (Ex) effect is ignored. +*/ + +#include <string.h> +#include "dtm.h" + +/* -------- Public Methods -------------------------------- */ + +CPlayer *CdtmLoader::factory(Copl *newopl) +{ + return new CdtmLoader(newopl); +} + +bool CdtmLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + const unsigned char conv_inst[11] = { 2,1,10,9,4,3,6,5,0,8,7 }; + const unsigned short conv_note[12] = { 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287, 0x2AE }; + int i,j,k,t=0; + + // read header + f->readString(header.id, 12); + header.version = f->readInt(1); + f->readString(header.title, 20); f->readString(header.author, 20); + header.numpat = f->readInt(1); header.numinst = f->readInt(1); + + // signature exists ? good version ? + if(memcmp(header.id,"DeFy DTM ",9) || header.version != 0x10) + { fp.close (f); return false; } + + header.numinst++; + + // load description + memset(desc,0,80*16); + + char bufstr[80]; + + for (i=0;i<16;i++) + { + // get line length + unsigned char bufstr_length = f->readInt(1); + + if(bufstr_length > 80) { + fp.close(f); + return false; + } + + // read line + if (bufstr_length) + { + f->readString(bufstr,bufstr_length); + + for (j=0;j<bufstr_length;j++) + if (!bufstr[j]) + bufstr[j] = 0x20; + + bufstr[bufstr_length] = 0; + + strcat(desc,bufstr); + } + + strcat(desc,"\n"); + } + + // init CmodPlayer + realloc_instruments(header.numinst); + realloc_order(100); + realloc_patterns(header.numpat,64,9); + init_notetable(conv_note); + init_trackord(); + + // load instruments + for (i=0;i<header.numinst;i++) + { + unsigned char name_length = f->readInt(1); + + if (name_length) + f->readString(instruments[i].name, name_length); + + instruments[i].name[name_length] = 0; + + for(j = 0; j < 12; j++) + instruments[i].data[j] = f->readInt(1); + + for (j=0;j<11;j++) + inst[i].data[conv_inst[j]] = instruments[i].data[j]; + } + + // load order + for(i = 0; i < 100; i++) order[i] = f->readInt(1); + + nop = header.numpat; + + unsigned char *pattern = new unsigned char [0x480]; + + // load tracks + for (i=0;i<nop;i++) + { + unsigned short packed_length; + + packed_length = f->readInt(2); + + unsigned char *packed_pattern = new unsigned char [packed_length]; + + for(j = 0; j < packed_length; j++) + packed_pattern[j] = f->readInt(1); + + long unpacked_length = unpack_pattern(packed_pattern,packed_length,pattern,0x480); + + delete [] packed_pattern; + + if (!unpacked_length) + { + delete pattern; + fp.close(f); + return false; + } + + // convert pattern + for (j=0;j<9;j++) + { + for (k=0;k<64;k++) + { + dtm_event *event = (dtm_event *)&pattern[(k*9+j)*2]; + + // instrument + if (event->byte0 == 0x80) + { + if (event->byte1 <= 0x80) + tracks[t][k].inst = event->byte1 + 1; + } + + // note + effect + else + { + tracks[t][k].note = event->byte0; + + if ((event->byte0 != 0) && (event->byte0 != 127)) + tracks[t][k].note++; + + // convert effects + switch (event->byte1 >> 4) + { + case 0x0: // pattern break + if ((event->byte1 & 15) == 1) + tracks[t][k].command = 13; + break; + + case 0x1: // freq. slide up + tracks[t][k].command = 28; + tracks[t][k].param1 = event->byte1 & 15; + break; + + case 0x2: // freq. slide down + tracks[t][k].command = 28; + tracks[t][k].param2 = event->byte1 & 15; + break; + + case 0xA: // set carrier volume + case 0xC: // set instrument volume + tracks[t][k].command = 22; + tracks[t][k].param1 = (0x3F - (event->byte1 & 15)) >> 4; + tracks[t][k].param2 = (0x3F - (event->byte1 & 15)) & 15; + break; + + case 0xB: // set modulator volume + tracks[t][k].command = 21; + tracks[t][k].param1 = (0x3F - (event->byte1 & 15)) >> 4; + tracks[t][k].param2 = (0x3F - (event->byte1 & 15)) & 15; + break; + + case 0xE: // set panning + break; + + case 0xF: // set speed + tracks[t][k].command = 13; + tracks[t][k].param2 = event->byte1 & 15; + break; + } + } + } + + t++; + } + } + + delete [] pattern; + fp.close(f); + + // order length + for (i=0;i<100;i++) + { + if (order[i] >= 0x80) + { + length = i; + + if (order[i] == 0xFF) + restartpos = 0; + else + restartpos = order[i] - 0x80; + + break; + } + } + + // initial speed + initspeed = 2; + + rewind(0); + + return true; +} + +void CdtmLoader::rewind(int subsong) +{ + CmodPlayer::rewind(subsong); + + // default instruments + for (int i=0;i<9;i++) + { + channel[i].inst = i; + + channel[i].vol1 = 63 - (inst[i].data[10] & 63); + channel[i].vol2 = 63 - (inst[i].data[9] & 63); + } +} + +float CdtmLoader::getrefresh() +{ + return 18.2f; +} + +std::string CdtmLoader::gettype() +{ + return std::string("DeFy Adlib Tracker"); +} + +std::string CdtmLoader::gettitle() +{ + return std::string(header.title); +} + +std::string CdtmLoader::getauthor() +{ + return std::string(header.author); +} + +std::string CdtmLoader::getdesc() +{ + return std::string(desc); +} + +std::string CdtmLoader::getinstrument(unsigned int n) +{ + return std::string(instruments[n].name); +} + +unsigned int CdtmLoader::getinstruments() +{ + return header.numinst; +} + +/* -------- Private Methods ------------------------------- */ + +long CdtmLoader::unpack_pattern(unsigned char *ibuf, long ilen, unsigned char *obuf, long olen) +{ + unsigned char *input = ibuf; + unsigned char *output = obuf; + + long input_length = 0; + long output_length = 0; + + unsigned char repeat_byte, repeat_counter; + + // RLE + while (input_length < ilen) + { + repeat_byte = input[input_length++]; + + if ((repeat_byte & 0xF0) == 0xD0) + { + repeat_counter = repeat_byte & 15; + repeat_byte = input[input_length++]; + } + else + repeat_counter = 1; + + for (int i=0;i<repeat_counter;i++) + { + if (output_length < olen) + output[output_length++] = repeat_byte; + } + } + + return output_length; +} diff --git a/plugins/adplug/adplug/dtm.h b/plugins/adplug/adplug/dtm.h new file mode 100644 index 00000000..8429bc4f --- /dev/null +++ b/plugins/adplug/adplug/dtm.h @@ -0,0 +1,69 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + dtm.h - DTM loader by Riven the Mage <riven@ok.ru> +*/ + +#include "protrack.h" + +class CdtmLoader: public CmodPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CdtmLoader(Copl *newopl) : CmodPlayer(newopl) { }; + + bool load(const std::string &filename, const CFileProvider &fp); + void rewind(int subsong); + float getrefresh(); + + std::string gettype(); + std::string gettitle(); + std::string getauthor(); + std::string getdesc(); + std::string getinstrument(unsigned int n); + unsigned int getinstruments(); + + private: + + struct dtm_header + { + char id[12]; + unsigned char version; + char title[20]; + char author[20]; + unsigned char numpat; + unsigned char numinst; + } header; + + char desc[80*16]; + + struct dtm_instrument + { + char name[13]; + unsigned char data[12]; + } instruments[128]; + + struct dtm_event + { + unsigned char byte0; + unsigned char byte1; + }; + + long unpack_pattern(unsigned char *ibuf, long ilen, unsigned char *obuf, long olen); +}; diff --git a/plugins/adplug/adplug/emuopl.cpp b/plugins/adplug/adplug/emuopl.cpp new file mode 100644 index 00000000..0f751e16 --- /dev/null +++ b/plugins/adplug/adplug/emuopl.cpp @@ -0,0 +1,147 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * emuopl.cpp - Emulated OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "emuopl.h" + +CEmuopl::CEmuopl(int rate, bool bit16, bool usestereo) + : use16bit(bit16), stereo(usestereo), mixbufSamples(0) +{ + opl[0] = OPLCreate(OPL_TYPE_YM3812, 3579545, rate); + opl[1] = OPLCreate(OPL_TYPE_YM3812, 3579545, rate); + + currType = TYPE_DUAL_OPL2; + + init(); +} + +CEmuopl::~CEmuopl() +{ + OPLDestroy(opl[0]); OPLDestroy(opl[1]); + + if(mixbufSamples) { + delete [] mixbuf0; + delete [] mixbuf1; + } +} + +void CEmuopl::update(short *buf, int samples) +{ + int i; + + //ensure that our mix buffers are adequately sized + if(mixbufSamples < samples) { + if(mixbufSamples) { delete[] mixbuf0; delete[] mixbuf1; } + mixbufSamples = samples; + + //*2 = make room for stereo, if we need it + mixbuf0 = new short[samples*2]; + mixbuf1 = new short[samples*2]; + } + + //data should be rendered to outbuf + //tempbuf should be used as a temporary buffer + //if we are supposed to generate 16bit output, + //then outbuf may point directly to the actual waveform output "buf" + //if we are supposed to generate 8bit output, + //then outbuf cannot point to "buf" (because there will not be enough room) + //and so it must point to a mixbuf instead-- + //it will be reduced to 8bit and put in "buf" later + short *outbuf; + short *tempbuf=mixbuf0; + short *tempbuf2=mixbuf1; + if(use16bit) outbuf = buf; + else outbuf = mixbuf1; + //...there is a potentially confusing situation where mixbuf1 can be aliased. + //beware. it is a little loony. + + //all of the following rendering code produces 16bit output + + switch(currType) { + case TYPE_OPL2: + //for opl2 mode: + //render chip0 to the output buffer + YM3812UpdateOne(opl[0],outbuf,samples); + + //if we are supposed to output stereo, + //then we need to dup the mono channel + if(stereo) + for(i=samples-1;i>=0;i--) { + outbuf[i*2] = outbuf[i]; + outbuf[i*2+1] = outbuf[i]; + } + break; + + case TYPE_OPL3: // unsupported + break; + + case TYPE_DUAL_OPL2: + //for dual opl2 mode: + //render each chip to a different tempbuffer + YM3812UpdateOne(opl[0],tempbuf2,samples); + YM3812UpdateOne(opl[1],tempbuf,samples); + + //output stereo: + //then we need to interleave the two buffers + if(stereo){ + //first, spread tempbuf's samples across left channel + //left channel + for(i=0;i<samples;i++) + outbuf[i*2] = tempbuf2[i]; + //next, insert the samples from tempbuf2 into right channel + for(i=0;i<samples;i++) + outbuf[i*2+1] = tempbuf[i]; + } else + //output mono: + //then we need to mix the two buffers into buf + for(i=0;i<samples;i++) + outbuf[i] = (tempbuf[i]>>1) + (tempbuf2[i]>>1); + break; + } + + //now reduce to 8bit if we need to + if(!use16bit) + for(i=0;i<(stereo ? samples*2 : samples);i++) + ((char *)buf)[i] = (outbuf[i] >> 8) ^ 0x80; +} + +void CEmuopl::write(int reg, int val) +{ + switch(currType){ + case TYPE_OPL2: + case TYPE_DUAL_OPL2: + OPLWrite(opl[currChip], 0, reg); + OPLWrite(opl[currChip], 1, val); + break; + case TYPE_OPL3: // unsupported + break; + } +} + +void CEmuopl::init() +{ + OPLResetChip(opl[0]); OPLResetChip(opl[1]); + currChip = 0; +} + +void CEmuopl::settype(ChipType type) +{ + currType = type; +} diff --git a/plugins/adplug/adplug/emuopl.h b/plugins/adplug/adplug/emuopl.h new file mode 100644 index 00000000..7cd2479b --- /dev/null +++ b/plugins/adplug/adplug/emuopl.h @@ -0,0 +1,49 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * emuopl.h - Emulated OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_EMUOPL +#define H_ADPLUG_EMUOPL + +#include "opl.h" +extern "C" { +#include "fmopl.h" +} + +class CEmuopl: public Copl +{ + public: + CEmuopl(int rate, bool bit16, bool usestereo); // rate = sample rate + virtual ~CEmuopl(); + + void update(short *buf, int samples); // fill buffer + void write(int reg, int val); + + void init(); + void settype(ChipType type); + + private: + bool use16bit, stereo; + FM_OPL *opl[2]; // OPL2 emulator data + short *mixbuf0, *mixbuf1; + int mixbufSamples; +}; + +#endif diff --git a/plugins/adplug/adplug/flash.cpp b/plugins/adplug/adplug/flash.cpp new file mode 100644 index 00000000..a6bf511c --- /dev/null +++ b/plugins/adplug/adplug/flash.cpp @@ -0,0 +1,230 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] FLASH player, by Riven the Mage <riven@ok.ru> + */ + +/* + - discovery - + + file(s) : LA-INTRO.EXE + type : Lunatic Asylum BBStro + tune : by Rogue [Logic Design] + player : by Flash [Logic Design] +*/ + +#include "flash.h" +#include "debug.h" + +const unsigned char CxadflashPlayer::flash_adlib_registers[99] = +{ + 0x23, 0x20, 0x43, 0x40, 0x63, 0x60, 0x83, 0x80, 0xC0, 0xE3, 0xE0, + 0x24, 0x21, 0x44, 0x41, 0x64, 0x61, 0x84, 0x81, 0xC1, 0xE4, 0xE1, + 0x25, 0x22, 0x45, 0x42, 0x65, 0x62, 0x85, 0x82, 0xC2, 0xE5, 0xE2, + 0x2B, 0x28, 0x4B, 0x48, 0x6B, 0x68, 0x8B, 0x88, 0xC3, 0xEB, 0xE8, + 0x2C, 0x29, 0x4C, 0x49, 0x6C, 0x69, 0x8C, 0x89, 0xC4, 0xEC, 0xE9, + 0x2D, 0x2A, 0x4D, 0x4A, 0x6D, 0x6A, 0x8D, 0x8A, 0xC5, 0xED, 0xEA, + 0x33, 0x30, 0x53, 0x50, 0x73, 0x70, 0x93, 0x90, 0xC6, 0xF3, 0xF0, + 0x34, 0x31, 0x54, 0x51, 0x74, 0x71, 0x94, 0x91, 0xC7, 0xF4, 0xF1, + 0x35, 0x32, 0x55, 0x52, 0x75, 0x72, 0x95, 0x92, 0xC8, 0xF5, 0xF2 +}; + +const unsigned short CxadflashPlayer::flash_notes_encoded[268] = +{ + 0x000, + 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700, 0x800, 0x900, 0xA00, 0xB00, 0xC00, + 0x101, 0x201, 0x301, 0x401, 0x501, 0x601, 0x701, 0x801, 0x901, 0xA01, 0xB01, 0xC01, + 0x102, 0x202, 0x302, 0x402, 0x502, 0x602, 0x702, 0x802, 0x902, 0xA02, 0xB02, 0xC02, + 0x103, 0x203, 0x303, 0x403, 0x503, 0x603, 0x703, 0x803, 0x903, 0xA03, 0xB03, 0xC03, + 0x104, 0x204, 0x304, 0x404, 0x504, 0x604, 0x704, 0x804, 0x904, 0xA04, 0xB04, 0xC04, + 0x105, 0x205, 0x305, 0x405, 0x505, 0x605, 0x705, 0x805, 0x905, 0xA05, 0xB05, 0xC05, + 0x106, 0x206, 0x306, 0x406, 0x506, 0x606, 0x706, 0x806, 0x906, 0xA06, 0xB06, 0xC06, + 0x107, 0x207, 0x307, 0x407, 0x507, 0x607, 0x707, 0x807, 0x907, 0xA07, 0xB07, 0xC07, + 0x108, 0x208, 0x308, 0x408, 0x508, 0x608, 0x708, 0x808, 0x908, 0xA08, 0xB08, 0xC08, + 0x109, 0x209, 0x309, 0x409, 0x509, 0x609, 0x709, 0x809, 0x909, 0xA09, 0xB09, 0xC09, + 0x10A, 0x20A, 0x30A, 0x40A, 0x50A, 0x60A, 0x70A, 0x80A, 0x90A, 0xA0A, 0xB0A, 0xC0A, + 0x10B, 0x20B, 0x30B, 0x40B, 0x50B, 0x60B, 0x70B, 0x80B, 0x90B, 0xA0B, 0xB0B, 0xC0B, + 0x10C, 0x20C, 0x30C, 0x40C, 0x50C, 0x60C, 0x70C, 0x80C, 0x90C, 0xA0C, 0xB0C, 0xC0C, + 0x10D, 0x20D, 0x30D, 0x40D, 0x50D, 0x60D, 0x70D, 0x80D, 0x90D, 0xA0D, 0xB0D, 0xC0D, + 0x10E, 0x20E, 0x30E, 0x40E, 0x50E, 0x60E, 0x70E, 0x80E, 0x90E, 0xA0E, 0xB0E, 0xC0E, + 0x10F, 0x20F, 0x30F, 0x40F, 0x50F, 0x60F, 0x70F, 0x80F, 0x90F, 0xA0F, 0xB0F, 0xC0F, + 0x110, 0x210, 0x310, 0x410, 0x510, 0x610, 0x710, 0x810, 0x910, 0xA10, 0xB10, 0xC10, + 0x111, 0x211, 0x311, 0x411, 0x511, 0x611, 0x711, 0x811, 0x911, 0xA11, 0xB11, 0xC11, + 0x112, 0x212, 0x312, 0x412, 0x512, 0x612, 0x712, 0x812, 0x912, 0xA12, 0xB12, 0xC12, + 0x113, 0x213, 0x313, 0x413, 0x513, 0x613, 0x713, 0x813, 0x913, 0xA13, 0xB13, 0xC13, + 0x114, 0x214, 0x314, 0x414, 0x514, 0x614, 0x714, 0x814, 0x914, 0xA14, 0xB14, 0xC14, + 0x115, 0x215, 0x315 +}; + +const unsigned short CxadflashPlayer::flash_notes[12] = +{ + 0x157, 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287 +}; + +const unsigned char CxadflashPlayer::flash_default_instrument[8] = +{ + 0x00, 0x00, 0x3F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF +}; + +CPlayer *CxadflashPlayer::factory(Copl *newopl) +{ + return new CxadflashPlayer(newopl); +} + +void CxadflashPlayer::xadplayer_rewind(int subsong) +{ + int i; + + plr.speed = xad.speed; + + flash.order_pos = 0; + flash.pattern_pos = 0; + + opl_write(0x08, 0x00); + opl_write(0xBD, 0x00); + + // assign default instrument + for(i=0; i<9; i++) + { + opl_write(0xA0+i, 0x00); + opl_write(0xB0+i, 0x00); + } + + // assign instruments + for(i=0; i<9; i++) + for(int j=0; j<11; j++) + opl_write(flash_adlib_registers[i*11+j], tune[i*12+j]); +} + +void CxadflashPlayer::xadplayer_update() +{ + unsigned short event_pos = (tune[0x600+flash.order_pos]*1152) + \ + (flash.pattern_pos*18) + \ + 0x633; + + for (int i=0; i<9; i++) + { + unsigned short flash_channel_freq = (adlib[0xB0+i] << 8) + adlib[0xA0+i]; + + unsigned char event_b0 = tune[event_pos++]; + unsigned char event_b1 = tune[event_pos++]; +#ifdef DEBUG + AdPlug_LogWrite("channel %02X, event %02X %02X:\n",i+1,event_b0,event_b1); +#endif + + if (event_b0 == 0x80) // 0.0x80: Set Instrument + { + for(int j=0; j<11; j++) + opl_write(flash_adlib_registers[i*11+j], tune[event_b1*12+j]); + } + else + { + if (event_b1 == 0x01) + flash.pattern_pos = 0x3F; // 1.0x01: Pattern Break + + unsigned char fx = (event_b1 >> 4); + unsigned char fx_p = (event_b1 & 0x0F); + + switch(fx) + { + case 0x0A: // 1.0xAy: Set Carrier volume + opl_write(flash_adlib_registers[11*i+2], fx_p << 2); + break; + case 0x0B: // 1.0xBy: Set Modulator volume + opl_write(flash_adlib_registers[11*i+3], fx_p << 2); + break; + case 0x0C: // 1.0xCy: Set both operators volume + opl_write(flash_adlib_registers[11*i+2], fx_p << 2); + opl_write(flash_adlib_registers[11*i+3], fx_p << 2); + break; +// case 0x0E: // 1.0xEy: ? (increase some value) + case 0x0F: // 1.0xFy: Set Speed + plr.speed = (fx_p + 1); + break; + } + + if (event_b0) + { + // mute channel + opl_write(0xA0+i, adlib[0xA0+i]); + opl_write(0xB0+i, adlib[0xB0+i] & 0xDF); + + // is note ? + if (event_b0 != 0x7F) + { + unsigned short note_encoded = flash_notes_encoded[event_b0]; + unsigned short freq = flash_notes[(note_encoded >> 8) - 1]; + + flash_channel_freq = freq | ((note_encoded & 0xFF) << 10) | 0x2000; + + opl_write(0xA0+i, flash_channel_freq & 0xFF); + opl_write(0xB0+i, flash_channel_freq >> 8); + } + } + + if (fx == 0x01) // 1.0x1y: Fine Frequency Slide Up + { + flash_channel_freq += (fx_p << 1); + + opl_write(0xA0+i, flash_channel_freq & 0xFF); + opl_write(0xB0+i, flash_channel_freq >> 8); + } + else if (fx == 0x02) // 1.0x2y: Fine Frequency Slide Down + { + flash_channel_freq -= (fx_p << 1); + + opl_write(0xA0+i, flash_channel_freq & 0xFF); + opl_write(0xB0+i, flash_channel_freq >> 8); + } + } + } + + // next row + flash.pattern_pos++; + + // end of pattern ? + if (flash.pattern_pos >= 0x40) + { + flash.pattern_pos = 0; + + flash.order_pos++; + + // end of module ? + if (tune[0x600+flash.order_pos] == 0xFF) + { + flash.order_pos = 0; + + plr.looping = 1; + } + } +} + +float CxadflashPlayer::xadplayer_getrefresh() +{ + return 17.5f; +} + +std::string CxadflashPlayer::xadplayer_gettype() +{ + return std::string("xad: flash player"); +} + +unsigned int CxadflashPlayer::xadplayer_getinstruments() +{ + return 32; +} diff --git a/plugins/adplug/adplug/flash.h b/plugins/adplug/adplug/flash.h new file mode 100644 index 00000000..a3471df4 --- /dev/null +++ b/plugins/adplug/adplug/flash.h @@ -0,0 +1,57 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] FLASH player, by Riven the Mage <riven@ok.ru> + */ + +#include "xad.h" + +class CxadflashPlayer: public CxadPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CxadflashPlayer(Copl *newopl): CxadPlayer(newopl) + { }; + +protected: + struct + { + unsigned char order_pos; + unsigned char pattern_pos; + } flash; + // + bool xadplayer_load() + { + if(xad.fmt == FLASH) + return true; + else + return false; + } + void xadplayer_rewind(int subsong); + void xadplayer_update(); + float xadplayer_getrefresh(); + std::string xadplayer_gettype(); + unsigned int xadplayer_getinstruments(); + +private: + static const unsigned char flash_adlib_registers[99]; + static const unsigned short flash_notes_encoded[268]; + static const unsigned short flash_notes[12]; + static const unsigned char flash_default_instrument[8]; +}; diff --git a/plugins/adplug/adplug/fmc.cpp b/plugins/adplug/adplug/fmc.cpp new file mode 100644 index 00000000..c6839a61 --- /dev/null +++ b/plugins/adplug/adplug/fmc.cpp @@ -0,0 +1,224 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2007 Simon Peter <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + fmc.cpp - FMC Loader by Riven the Mage <riven@ok.ru> +*/ + +#include <string.h> +#include "fmc.h" + +/* -------- Public Methods -------------------------------- */ + +CPlayer *CfmcLoader::factory(Copl *newopl) +{ + return new CfmcLoader(newopl); +} + +bool CfmcLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + const unsigned char conv_fx[16] = {0,1,2,3,4,8,255,255,255,255,26,11,12,13,14,15}; + + int i,j,k,t=0; + + // read header + f->readString(header.id, 4); + f->readString(header.title, 21); + header.numchan = f->readInt(1); + + // 'FMC!' - signed ? + if (strncmp(header.id,"FMC!",4)) { fp.close(f); return false; } + + // init CmodPlayer + realloc_instruments(32); + realloc_order(256); + realloc_patterns(64,64,header.numchan); + init_trackord(); + + // load order + for(i = 0; i < 256; i++) order[i] = f->readInt(1); + + f->ignore(2); + + // load instruments + for(i = 0; i < 32; i++) { + instruments[i].synthesis = f->readInt(1); + instruments[i].feedback = f->readInt(1); + + instruments[i].mod_attack = f->readInt(1); + instruments[i].mod_decay = f->readInt(1); + instruments[i].mod_sustain = f->readInt(1); + instruments[i].mod_release = f->readInt(1); + instruments[i].mod_volume = f->readInt(1); + instruments[i].mod_ksl = f->readInt(1); + instruments[i].mod_freq_multi = f->readInt(1); + instruments[i].mod_waveform = f->readInt(1); + instruments[i].mod_sustain_sound = f->readInt(1); + instruments[i].mod_ksr = f->readInt(1); + instruments[i].mod_vibrato = f->readInt(1); + instruments[i].mod_tremolo = f->readInt(1); + + instruments[i].car_attack = f->readInt(1); + instruments[i].car_decay = f->readInt(1); + instruments[i].car_sustain = f->readInt(1); + instruments[i].car_release = f->readInt(1); + instruments[i].car_volume = f->readInt(1); + instruments[i].car_ksl = f->readInt(1); + instruments[i].car_freq_multi = f->readInt(1); + instruments[i].car_waveform = f->readInt(1); + instruments[i].car_sustain_sound = f->readInt(1); + instruments[i].car_ksr = f->readInt(1); + instruments[i].car_vibrato = f->readInt(1); + instruments[i].car_tremolo = f->readInt(1); + + instruments[i].pitch_shift = f->readInt(1); + + f->readString(instruments[i].name, 21); + } + + // load tracks + for (i=0;i<64;i++) + { + if(f->ateof()) break; + + for (j=0;j<header.numchan;j++) + { + for (k=0;k<64;k++) + { + fmc_event event; + + // read event + event.byte0 = f->readInt(1); + event.byte1 = f->readInt(1); + event.byte2 = f->readInt(1); + + // convert event + tracks[t][k].note = event.byte0 & 0x7F; + tracks[t][k].inst = ((event.byte0 & 0x80) >> 3) + (event.byte1 >> 4) + 1; + tracks[t][k].command = conv_fx[event.byte1 & 0x0F]; + tracks[t][k].param1 = event.byte2 >> 4; + tracks[t][k].param2 = event.byte2 & 0x0F; + + // fix effects + if (tracks[t][k].command == 0x0E) // 0x0E (14): Retrig + tracks[t][k].param1 = 3; + if (tracks[t][k].command == 0x1A) // 0x1A (26): Volume Slide + if (tracks[t][k].param1 > tracks[t][k].param2) + { + tracks[t][k].param1 -= tracks[t][k].param2; + tracks[t][k].param2 = 0; + } + else + { + tracks[t][k].param2 -= tracks[t][k].param1; + tracks[t][k].param1 = 0; + } + } + + t++; + } + } + fp.close(f); + + // convert instruments + for (i=0;i<31;i++) + buildinst(i); + + // order length + for (i=0;i<256;i++) + { + if (order[i] >= 0xFE) + { + length = i; + break; + } + } + + // data for Protracker + activechan = (0xffffffff >> (32 - header.numchan)) << (32 - header.numchan); + nop = t / header.numchan; + restartpos = 0; + + // flags + flags = Faust; + + rewind(0); + + return true; +} + +float CfmcLoader::getrefresh() +{ + return 50.0f; +} + +std::string CfmcLoader::gettype() +{ + return std::string("Faust Music Creator"); +} + +std::string CfmcLoader::gettitle() +{ + return std::string(header.title); +} + +std::string CfmcLoader::getinstrument(unsigned int n) +{ + return std::string(instruments[n].name); +} + +unsigned int CfmcLoader::getinstruments() +{ + return 32; +} + +/* -------- Private Methods ------------------------------- */ + +void CfmcLoader::buildinst(unsigned char i) +{ + inst[i].data[0] = ((instruments[i].synthesis & 1) ^ 1); + inst[i].data[0] |= ((instruments[i].feedback & 7) << 1); + + inst[i].data[3] = ((instruments[i].mod_attack & 15) << 4); + inst[i].data[3] |= (instruments[i].mod_decay & 15); + inst[i].data[5] = ((15 - (instruments[i].mod_sustain & 15)) << 4); + inst[i].data[5] |= (instruments[i].mod_release & 15); + inst[i].data[9] = (63 - (instruments[i].mod_volume & 63)); + inst[i].data[9] |= ((instruments[i].mod_ksl & 3) << 6); + inst[i].data[1] = (instruments[i].mod_freq_multi & 15); + inst[i].data[7] = (instruments[i].mod_waveform & 3); + inst[i].data[1] |= ((instruments[i].mod_sustain_sound & 1) << 5); + inst[i].data[1] |= ((instruments[i].mod_ksr & 1) << 4); + inst[i].data[1] |= ((instruments[i].mod_vibrato & 1) << 6); + inst[i].data[1] |= ((instruments[i].mod_tremolo & 1) << 7); + + inst[i].data[4] = ((instruments[i].car_attack & 15) << 4); + inst[i].data[4] |= (instruments[i].car_decay & 15); + inst[i].data[6] = ((15 - (instruments[i].car_sustain & 15)) << 4); + inst[i].data[6] |= (instruments[i].car_release & 15); + inst[i].data[10] = (63 - (instruments[i].car_volume & 63)); + inst[i].data[10] |= ((instruments[i].car_ksl & 3) << 6); + inst[i].data[2] = (instruments[i].car_freq_multi & 15); + inst[i].data[8] = (instruments[i].car_waveform & 3); + inst[i].data[2] |= ((instruments[i].car_sustain_sound & 1) << 5); + inst[i].data[2] |= ((instruments[i].car_ksr & 1) << 4); + inst[i].data[2] |= ((instruments[i].car_vibrato & 1) << 6); + inst[i].data[2] |= ((instruments[i].car_tremolo & 1) << 7); + + inst[i].slide = instruments[i].pitch_shift; +} diff --git a/plugins/adplug/adplug/fmc.h b/plugins/adplug/adplug/fmc.h new file mode 100644 index 00000000..ae7d5c5b --- /dev/null +++ b/plugins/adplug/adplug/fmc.h @@ -0,0 +1,91 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + fmc.h - FMC loader by Riven the Mage <riven@ok.ru> +*/ + +#include "protrack.h" + +class CfmcLoader: public CmodPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CfmcLoader(Copl *newopl) : CmodPlayer(newopl) { }; + + bool load(const std::string &filename, const CFileProvider &fp); + float getrefresh(); + + std::string gettype(); + std::string gettitle(); + std::string getinstrument(unsigned int n); + unsigned int getinstruments(); + + private: + + struct fmc_event + { + unsigned char byte0; + unsigned char byte1; + unsigned char byte2; + }; + + struct fmc_header + { + char id[4]; + char title[21]; + unsigned char numchan; + } header; + + struct fmc_instrument + { + unsigned char synthesis; + unsigned char feedback; + + unsigned char mod_attack; + unsigned char mod_decay; + unsigned char mod_sustain; + unsigned char mod_release; + unsigned char mod_volume; + unsigned char mod_ksl; + unsigned char mod_freq_multi; + unsigned char mod_waveform; + unsigned char mod_sustain_sound; + unsigned char mod_ksr; + unsigned char mod_vibrato; + unsigned char mod_tremolo; + unsigned char car_attack; + unsigned char car_decay; + unsigned char car_sustain; + unsigned char car_release; + unsigned char car_volume; + unsigned char car_ksl; + unsigned char car_freq_multi; + unsigned char car_waveform; + unsigned char car_sustain_sound; + unsigned char car_ksr; + unsigned char car_vibrato; + unsigned char car_tremolo; + + signed char pitch_shift; + + char name[21]; + } instruments[32]; + + void buildinst(unsigned char i); +}; diff --git a/plugins/adplug/adplug/fmopl.c b/plugins/adplug/adplug/fmopl.c new file mode 100644 index 00000000..2b0e82b0 --- /dev/null +++ b/plugins/adplug/adplug/fmopl.c @@ -0,0 +1,1390 @@ +/* +** +** File: fmopl.c -- software implementation of FM sound generator +** +** Copyright (C) 1999,2000 Tatsuyuki Satoh , MultiArcadeMachineEmurator development +** +** Version 0.37a +** +*/ + +/* + preliminary : + Problem : + note: +*/ + +/* This version of fmopl.c is a fork of the MAME one, relicensed under the LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define INLINE __inline +#define HAS_YM3812 1 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <math.h> +//#include "driver.h" /* use M.A.M.E. */ +#include "fmopl.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +/* -------------------- for debug --------------------- */ +/* #define OPL_OUTPUT_LOG */ +#ifdef OPL_OUTPUT_LOG +static FILE *opl_dbg_fp = NULL; +static FM_OPL *opl_dbg_opl[16]; +static int opl_dbg_maxchip,opl_dbg_chip; +#endif + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define DELTAT_MIXING_LEVEL (1) /* DELTA-T ADPCM MIXING LEVEL */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<<OPL_OUTSB) +#define OPL_MINOUT (-0x8000<<OPL_OUTSB) + +/* -------------------- quality selection --------------------- */ + +/* sinwave entries */ +/* used static memory = SIN_ENT * 4 (byte) */ +#define SIN_ENT 2048 + +/* output level entries (envelope,sinwave) */ +/* envelope counter lower bits */ +#define ENV_BITS 16 +/* envelope output entries */ +#define EG_ENT 4096 +/* used dynamic memory = EG_ENT*4*4(byte)or EG_ENT*6*4(byte) */ +/* used static memory = EG_ENT*4 (byte) */ + +#define EG_OFF ((2*EG_ENT)<<ENV_BITS) /* OFF */ +#define EG_DED EG_OFF +#define EG_DST (EG_ENT<<ENV_BITS) /* DECAY START */ +#define EG_AED EG_DST +#define EG_AST 0 /* ATTACK START */ + +#define EG_STEP (96.0/EG_ENT) /* OPL is 0.1875 dB step */ + +/* LFO table entries */ +#define VIB_ENT 512 +#define VIB_SHIFT (32-9) +#define AMS_ENT 512 +#define AMS_SHIFT (32-9) + +#define VIB_RATE 256 + +/* -------------------- local defines , macros --------------------- */ + +/* register number to channel number , slot offset */ +#define SLOT1 0 +#define SLOT2 1 + +/* envelope phase */ +#define ENV_MOD_RR 0x00 +#define ENV_MOD_DR 0x01 +#define ENV_MOD_AR 0x02 + +/* -------------------- tables --------------------- */ +static const int slot_array[32]= +{ + 0, 2, 4, 1, 3, 5,-1,-1, + 6, 8,10, 7, 9,11,-1,-1, + 12,14,16,13,15,17,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1 +}; + +/* key scale level */ +/* table is 3dB/OCT , DV converts this in TL step at 6dB/OCT */ +#define DV (EG_STEP/2) +static const UINT32 KSL_TABLE[8*16]= +{ + /* OCT 0 */ + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + /* OCT 1 */ + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.750/DV, 1.125/DV, 1.500/DV, + 1.875/DV, 2.250/DV, 2.625/DV, 3.000/DV, + /* OCT 2 */ + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 1.125/DV, 1.875/DV, 2.625/DV, + 3.000/DV, 3.750/DV, 4.125/DV, 4.500/DV, + 4.875/DV, 5.250/DV, 5.625/DV, 6.000/DV, + /* OCT 3 */ + 0.000/DV, 0.000/DV, 0.000/DV, 1.875/DV, + 3.000/DV, 4.125/DV, 4.875/DV, 5.625/DV, + 6.000/DV, 6.750/DV, 7.125/DV, 7.500/DV, + 7.875/DV, 8.250/DV, 8.625/DV, 9.000/DV, + /* OCT 4 */ + 0.000/DV, 0.000/DV, 3.000/DV, 4.875/DV, + 6.000/DV, 7.125/DV, 7.875/DV, 8.625/DV, + 9.000/DV, 9.750/DV,10.125/DV,10.500/DV, + 10.875/DV,11.250/DV,11.625/DV,12.000/DV, + /* OCT 5 */ + 0.000/DV, 3.000/DV, 6.000/DV, 7.875/DV, + 9.000/DV,10.125/DV,10.875/DV,11.625/DV, + 12.000/DV,12.750/DV,13.125/DV,13.500/DV, + 13.875/DV,14.250/DV,14.625/DV,15.000/DV, + /* OCT 6 */ + 0.000/DV, 6.000/DV, 9.000/DV,10.875/DV, + 12.000/DV,13.125/DV,13.875/DV,14.625/DV, + 15.000/DV,15.750/DV,16.125/DV,16.500/DV, + 16.875/DV,17.250/DV,17.625/DV,18.000/DV, + /* OCT 7 */ + 0.000/DV, 9.000/DV,12.000/DV,13.875/DV, + 15.000/DV,16.125/DV,16.875/DV,17.625/DV, + 18.000/DV,18.750/DV,19.125/DV,19.500/DV, + 19.875/DV,20.250/DV,20.625/DV,21.000/DV +}; +#undef DV + +/* sustain lebel table (3db per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ +#define SC(db) (db*((3/EG_STEP)*(1<<ENV_BITS)))+EG_DST +static const INT32 SL_TABLE[16]={ + SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), + SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) +}; +#undef SC + +#define TL_MAX (EG_ENT*2) /* limit(tl + ksr + envelope) + sinwave */ +/* TotalLevel : 48 24 12 6 3 1.5 0.75 (dB) */ +/* TL_TABLE[ 0 to TL_MAX ] : plus section */ +/* TL_TABLE[ TL_MAX to TL_MAX+TL_MAX-1 ] : minus section */ +static INT32 *TL_TABLE; + +/* pointers to TL_TABLE with sinwave output offset */ +static INT32 **SIN_TABLE; + +/* LFO table */ +static INT32 *AMS_TABLE; +static INT32 *VIB_TABLE; + +/* envelope output curve table */ +/* attack + decay + OFF */ +static INT32 ENV_CURVE[2*EG_ENT+1]; + +/* multiple table */ +#define ML 2 +static const UINT32 MUL_TABLE[16]= { +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15 */ + 0.50*ML, 1.00*ML, 2.00*ML, 3.00*ML, 4.00*ML, 5.00*ML, 6.00*ML, 7.00*ML, + 8.00*ML, 9.00*ML,10.00*ML,10.00*ML,12.00*ML,12.00*ML,15.00*ML,15.00*ML +}; +#undef ML + +/* dummy attack / decay rate ( when rate == 0 ) */ +static INT32 RATE_0[16]= +{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +/* -------------------- static state --------------------- */ + +/* lock level of common table */ +static int num_lock = 0; + +/* work table */ +static void *cur_chip = NULL; /* current chip point */ +/* currenct chip state */ +/* static OPLSAMPLE *bufL,*bufR; */ +static OPL_CH *S_CH; +static OPL_CH *E_CH; +OPL_SLOT *SLOT7_1,*SLOT7_2,*SLOT8_1,*SLOT8_2; + +static INT32 outd[1]; +static INT32 ams; +static INT32 vib; +INT32 *ams_table; +INT32 *vib_table; +static INT32 amsIncr; +static INT32 vibIncr; +static INT32 feedback2; /* connect for SLOT 2 */ + +/* log output level */ +#define LOG_ERR 3 /* ERROR */ +#define LOG_WAR 2 /* WARNING */ +#define LOG_INF 1 /* INFORMATION */ + +//#define LOG_LEVEL LOG_INF +#define LOG_LEVEL LOG_ERR + +//#define LOG(n,x) if( (n)>=LOG_LEVEL ) logerror x +#define LOG(n,x) + +/* --------------------- subroutines --------------------- */ + +INLINE int Limit( int val, int max, int min ) { + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + +/* status set and IRQ handling */ +INLINE void OPL_STATUS_SET(FM_OPL *OPL,int flag) +{ + /* set status flag */ + OPL->status |= flag; + if(!(OPL->status & 0x80)) + { + if(OPL->status & OPL->statusmask) + { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +INLINE void OPL_STATUS_RESET(FM_OPL *OPL,int flag) +{ + /* reset status flag */ + OPL->status &=~flag; + if((OPL->status & 0x80)) + { + if (!(OPL->status & OPL->statusmask) ) + { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +INLINE void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag) +{ + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + +/* ----- key on ----- */ +INLINE void OPL_KEYON(OPL_SLOT *SLOT) +{ + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} +/* ----- key off ----- */ +INLINE void OPL_KEYOFF(OPL_SLOT *SLOT) +{ + if( SLOT->evm > ENV_MOD_RR) + { + /* set envelope counter from envleope output */ + SLOT->evm = ENV_MOD_RR; + if( !(SLOT->evc&EG_DST) ) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<<ENV_BITS) + EG_DST; + SLOT->evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ +/* return : envelope output */ +INLINE UINT32 OPL_CALC_SLOT( OPL_SLOT *SLOT ) +{ + /* calcrate envelope generator */ + if( (SLOT->evc+=SLOT->evs) >= SLOT->eve ) + { + switch( SLOT->evm ){ + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if(SLOT->eg_typ) + { + SLOT->evs = 0; + } + else + { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF+1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL+ENV_CURVE[SLOT->evc>>ENV_BITS]+(SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void set_algorythm( OPL_CH *CH) +{ + INT32 *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +INLINE void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT) +{ + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +INLINE void set_mul(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->mul = MUL_TABLE[v&0x0f]; + SLOT->KSR = (v&0x10) ? 0 : 2; + SLOT->eg_typ = (v&0x20)>>5; + SLOT->vib = (v&0x40); + SLOT->ams = (v&0x80); + CALC_FCSLOT(CH,SLOT); +} + +/* set ksl & tl */ +INLINE void set_ksl_tl(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ksl = v>>6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (v&0x3f)*(0.75/EG_STEP); /* 0.75db step */ + + if( !(OPL->mode&0x80) ) + { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +INLINE void set_ar_dr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ar = v>>4; + int dr = v&0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar<<2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_AR ) SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_DR ) SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +INLINE void set_sl_rr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int sl = v>>4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if( SLOT->evm == ENV_MOD_DR ) SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_RR ) SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt+con)/(0x1000000/SIN_ENT))&(SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +INLINE void OPL_CALC_CH( OPL_CH *CH ) +{ + UINT32 env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH->FB) + { + int feedback1 = (CH->op1_out[0]+CH->op1_out[1])>>CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT,env_out,feedback1); + } + else + { + *CH->connect1 += OP_OUT(SLOT,env_out,0); + } + }else + { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT,env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +INLINE void OPL_CALC_RH( OPL_CH *CH ) +{ + UINT32 env_tam,env_sd,env_top,env_hh; + int whitenoise = (rand()&1)*(WHITE_NOISE_db/EG_STEP); + INT32 tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH[6].FB) + { + int feedback1 = (CH[6].op1_out[0]+CH[6].op1_out[1])>>CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT,env_out,feedback1); + } + else + { + feedback2 = OP_OUT(SLOT,env_out,0); + } + }else + { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT,env_out, feedback2)*2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd =OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam=OPL_CALC_SLOT(SLOT8_1); + env_top=OPL_CALC_SLOT(SLOT8_2); + env_hh =OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if(SLOT7_1->vib) SLOT7_1->Cnt += (2*SLOT7_1->Incr*vib/VIB_RATE); + else SLOT7_1->Cnt += 2*SLOT7_1->Incr; + if(SLOT7_2->vib) SLOT7_2->Cnt += ((CH[7].fc*8)*vib/VIB_RATE); + else SLOT7_2->Cnt += (CH[7].fc*8); + if(SLOT8_1->vib) SLOT8_1->Cnt += (SLOT8_1->Incr*vib/VIB_RATE); + else SLOT8_1->Cnt += SLOT8_1->Incr; + if(SLOT8_2->vib) SLOT8_2->Cnt += ((CH[8].fc*48)*vib/VIB_RATE); + else SLOT8_2->Cnt += (CH[8].fc*48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if( env_sd < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_1,env_sd, 0)*8; + /* TAM */ + if( env_tam < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT8_1,env_tam, 0)*2; + /* TOP-CY */ + if( env_top < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_2,env_top,tone8)*2; + /* HH */ + if( env_hh < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_2,env_hh,tone8)*2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables( FM_OPL *OPL , int ARRATE , int DRRATE ) +{ + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0;i < 4;i++) OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4;i <= 60;i++){ + rate = OPL->freqbase; /* frequency rate */ + if( i < 60 ) rate *= 1.0+(i&3)*0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1<<((i>>2)-1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT<<ENV_BITS); + OPL->AR_TABLE[i] = rate / ARRATE; + OPL->DR_TABLE[i] = rate / DRRATE; + } + for (i = 60;i < 76;i++) + { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +#if 0 + for (i = 0;i < 64 ;i++){ /* make for overflow area */ + LOG(LOG_WAR,("rate %2d , ar %f ms , dr %f ms \n",i, + ((double)(EG_ENT<<ENV_BITS) / OPL->AR_TABLE[i]) * (1000.0 / OPL->rate), + ((double)(EG_ENT<<ENV_BITS) / OPL->DR_TABLE[i]) * (1000.0 / OPL->rate) )); + } +#endif +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable( void ) +{ + int s,t; + double rate; + int i,j; + double pom; + + /* allocate dynamic tables */ + if( (TL_TABLE = malloc(TL_MAX*2*sizeof(INT32))) == NULL) + return 0; + if( (SIN_TABLE = malloc(SIN_ENT*4 *sizeof(INT32 *))) == NULL) + { + free(TL_TABLE); + return 0; + } + if( (AMS_TABLE = malloc(AMS_ENT*2 *sizeof(INT32))) == NULL) + { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + if( (VIB_TABLE = malloc(VIB_ENT*2 *sizeof(INT32))) == NULL) + { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0;t < EG_ENT-1 ;t++){ + rate = ((1<<TL_BITS)-1)/pow(10,EG_STEP*t/20); /* dB -> voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX+t] = -TL_TABLE[t]; +/* LOG(LOG_INF,("TotalLevel(%3d) = %x\n",t,TL_TABLE[t]));*/ + } + /* fill volume off area */ + for ( t = EG_ENT-1; t < TL_MAX ;t++){ + TL_TABLE[t] = TL_TABLE[TL_MAX+t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT/2] = &TL_TABLE[EG_ENT-1]; + for (s = 1;s <= SIN_ENT/4;s++){ + pom = sin(2*PI*s/SIN_ENT); /* sin */ + pom = 20*log10(1/pom); /* decibel */ + j = pom / EG_STEP; /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT/2-s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT/2+s] = SIN_TABLE[SIN_ENT -s] = &TL_TABLE[TL_MAX+j]; +/* LOG(LOG_INF,("sin(%3d) = %f:%f db\n",s,pom,(double)j * EG_STEP));*/ + } + for (s = 0;s < SIN_ENT;s++) + { + SIN_TABLE[SIN_ENT*1+s] = s<(SIN_ENT/2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT*2+s] = SIN_TABLE[s % (SIN_ENT/2)]; + SIN_TABLE[SIN_ENT*3+s] = (s/(SIN_ENT/4))&1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT*2+s]; + } + + /* envelope counter -> envelope output table */ + for (i=0; i<EG_ENT; i++) + { + /* ATTACK curve */ + pom = pow( ((double)(EG_ENT-1-i)/EG_ENT) , 8 ) * EG_ENT; + /* if( pom >= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST>>ENV_BITS)+i]= i; + } + /* off */ + ENV_CURVE[EG_OFF>>ENV_BITS]= EG_ENT-1; + /* make LFO ams table */ + for (i=0; i<AMS_ENT; i++) + { + pom = (1.0+sin(2*PI*i/AMS_ENT))/2; /* sin */ + AMS_TABLE[i] = (1.0/EG_STEP)*pom; /* 1dB */ + AMS_TABLE[AMS_ENT+i] = (4.8/EG_STEP)*pom; /* 4.8dB */ + } + /* make LFO vibrate table */ + for (i=0; i<VIB_ENT; i++) + { + /* 100cent = 1seminote = 6% ?? */ + pom = (double)VIB_RATE*0.06*sin(2*PI*i/VIB_ENT); /* +-100sect step */ + VIB_TABLE[i] = VIB_RATE + (pom*0.07); /* +- 7cent */ + VIB_TABLE[VIB_ENT+i] = VIB_RATE + (pom*0.14); /* +-14cent */ + /* LOG(LOG_INF,("vib %d=%d\n",i,VIB_TABLE[VIB_ENT+i])); */ + } + return 1; +} + + +static void OPLCloseTable( void ) +{ + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + free(VIB_TABLE); +} + +/* CSM Key Controll */ +INLINE void CSMKeyControll(OPL_CH *CH) +{ + OPL_SLOT *slot1 = &CH->SLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initalize(FM_OPL *OPL) +{ + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables( OPL , OPL_ARRATE , OPL_DRRATE ); + /* make fnumber -> increment counter table */ + for( fn=0 ; fn < 1024 ; fn++ ) + { + OPL->FN_TABLE[fn] = OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2; + } + /* LFO freq.table */ + OPL->amsIncr = OPL->rate ? (double)AMS_ENT*(1<<AMS_SHIFT) / OPL->rate * 3.7 * ((double)OPL->clock/3600000) : 0; + OPL->vibIncr = OPL->rate ? (double)VIB_ENT*(1<<VIB_SHIFT) / OPL->rate * 6.4 * ((double)OPL->clock/3600000) : 0; +} + +/* ---------- write a OPL registers ---------- */ +static void OPLWriteReg(FM_OPL *OPL, int r, int v) +{ + OPL_CH *CH; + int slot; + int block_fnum; + + switch(r&0xe0) + { + case 0x00: /* 00-1f:controll */ + switch(r&0x1f) + { + case 0x01: + /* wave selector enable */ + if(OPL->type&OPL_TYPE_WAVESEL) + { + OPL->wavesel = v&0x20; + if(!OPL->wavesel) + { + /* preset compatible mode */ + int c; + for(c=0;c<OPL->max_ch;c++) + { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v)*4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v)*16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v&0x80) + { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL,0x7f); + } + else + { /* set IRQ mask ,timer enable*/ + UINT8 st1 = v&1; + UINT8 st2 = (v>>1)&1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL,v&0x78); + OPL_STATUSMASK_SET(OPL,((~v)&0x78)|0x01); + /* timer 2 */ + if(OPL->st[1] != st2) + { + double interval = st2 ? (double)OPL->T[1]*OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+1,interval); + } + /* timer 1 */ + if(OPL->st[0] != st1) + { + double interval = st1 ? (double)OPL->T[0]*OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+0,interval); + } + } + return; +#if BUILD_Y8950 + case 0x06: /* Key Board OUT */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_w) + OPL->keyboardhandler_w(OPL->keyboard_param,v); + else + LOG(LOG_WAR,("OPL:write unmapped KEYBOARD port\n")); + } + return; + case 0x07: /* DELTA-T controll : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + return; + case 0x08: /* MODE,DELTA-T : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ + OPL->mode = v; + v&=0x1f; /* for DELTA-T unit */ + case 0x09: /* START ADD */ + case 0x0a: + case 0x0b: /* STOP ADD */ + case 0x0c: + case 0x0d: /* PRESCALE */ + case 0x0e: + case 0x0f: /* ADPCM data */ + case 0x10: /* DELTA-N */ + case 0x11: /* DELTA-N */ + case 0x12: /* EG-CTRL */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + return; +#if 0 + case 0x15: /* DAC data */ + case 0x16: + case 0x17: /* SHIFT */ + return; + case 0x18: /* I/O CTRL (Direction) */ + if(OPL->type&OPL_TYPE_IO) + OPL->portDirection = v&0x0f; + return; + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + OPL->portLatch = v; + if(OPL->porthandler_w) + OPL->porthandler_w(OPL->port_param,v&OPL->portDirection); + } + return; + case 0x1a: /* PCM data */ + return; +#endif +#endif + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch(r) + { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + UINT8 rkey = OPL->rythm^v; + OPL->ams_table = &AMS_TABLE[v&0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v&0x40 ? VIB_ENT : 0]; + OPL->rythm = v&0x3f; + if(OPL->rythm&0x20) + { +#if 0 + usrintf_showmessage("OPL Rythm mode select"); +#endif + /* BD key on/off */ + if(rkey&0x10) + { + if(v&0x10) + { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } + else + { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if(rkey&0x08) + { + if(v&0x08) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if(rkey&0x04) + { + if(v&0x04) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if(rkey&0x02) + { + if(v&0x02) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if(rkey&0x01) + { + if(v&0x01) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + } + /* keyon,block,fnum */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + if(!(r&0x10)) + { /* a0-a8 */ + block_fnum = (CH->block_fnum&0x1f00) | v; + } + else + { /* b0-b8 */ + int keyon = (v>>5)&1; + block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); + if(CH->keyon != keyon) + { + if( (CH->keyon=keyon) ) + { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } + else + { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if(CH->block_fnum != block_fnum) + { + int blockRv = 7-(block_fnum>>10); + int fnum = block_fnum&0x3ff; + CH->block_fnum = block_fnum; + + CH->ksl_base = KSL_TABLE[block_fnum>>6]; + CH->fc = OPL->FN_TABLE[fnum]>>blockRv; + CH->kcode = CH->block_fnum>>9; + if( (OPL->mode&0x40) && CH->block_fnum&0x100) CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v>>1)&7; + CH->FB = feedback ? (8+1) - feedback : 0; + CH->CON = v&1; + set_algorythm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r&0x1f]; + if(slot == -1) return; + CH = &OPL->P_CH[slot/2]; + if(OPL->wavesel) + { + /* LOG(LOG_INF,("OPL SLOT %d wave select %d\n",slot,v&3)); */ + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v&0x03)*SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) +{ + num_lock++; + if(num_lock>1) return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if( !OPLOpenTable() ) + { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) +{ + if(num_lock) num_lock--; + if(num_lock) return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +#if (BUILD_YM3812 || BUILD_YM3526) +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length) +{ + int i; + int data; + OPLSAMPLE *buf = buffer; + UINT32 amsCnt = OPL->amsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rythm = OPL->rythm&0x20; + OPL_CH *CH,*R_CH; + + if( (void *)OPL != cur_chip ){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for( i=0; i < length ; i++ ) + { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT]; + vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for(CH=S_CH ; CH < R_CH ; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(S_CH); + /* limit check */ + data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT ); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + for(opl_dbg_chip=0;opl_dbg_chip<opl_dbg_maxchip;opl_dbg_chip++) + if( opl_dbg_opl[opl_dbg_chip] == OPL) break; + fprintf(opl_dbg_fp,"%c%c%c",0x20+opl_dbg_chip,length&0xff,length/256); + } +#endif +} +#endif /* (BUILD_YM3812 || BUILD_YM3526) */ + +#if BUILD_Y8950 + +void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length) +{ + int i; + int data; + OPLSAMPLE *buf = buffer; + UINT32 amsCnt = OPL->amsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rythm = OPL->rythm&0x20; + OPL_CH *CH,*R_CH; + YM_DELTAT *DELTAT = OPL->deltat; + + /* setup DELTA-T unit */ + YM_DELTAT_DECODE_PRESET(DELTAT); + + if( (void *)OPL != cur_chip ){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for( i=0; i < length ; i++ ) + { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT]; + vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT]; + outd[0] = 0; + /* deltaT ADPCM */ + if( DELTAT->portstate ) + YM_DELTAT_ADPCM_CALC(DELTAT); + /* FM part */ + for(CH=S_CH ; CH < R_CH ; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(S_CH); + /* limit check */ + data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT ); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; + /* deltaT START flag */ + if( !DELTAT->portstate ) + OPL->status &= 0xfe; +} +#endif + +/* ---------- reset one of chip ---------- */ +void OPLResetChip(FM_OPL *OPL) +{ + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL,0x7f); + /* reset with register write */ + OPLWriteReg(OPL,0x01,0); /* wabesel disable */ + OPLWriteReg(OPL,0x02,0); /* Timer1 */ + OPLWriteReg(OPL,0x03,0); /* Timer2 */ + OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */ + for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0); + /* reset OPerator paramater */ + for( c = 0 ; c < OPL->max_ch ; c++ ) + { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for(s = 0 ; s < 2 ; s++ ) + { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF+1; + CH->SLOT[s].evs = 0; + } + } +#if BUILD_Y8950 + if(OPL->type&OPL_TYPE_ADPCM) + { + YM_DELTAT *DELTAT = OPL->deltat; + + DELTAT->freqbase = OPL->freqbase; + DELTAT->output_pointer = outd; + DELTAT->portshift = 5; + DELTAT->output_range = DELTAT_MIXING_LEVEL<<TL_BITS; + YM_DELTAT_ADPCM_Reset(DELTAT,0); + } +#endif +} + +/* ---------- Create one of vietual YM3812 ---------- */ +/* 'rate' is sampling rate and 'bufsiz' is the size of the */ +FM_OPL *OPLCreate(int type, int clock, int rate) +{ + char *ptr; + FM_OPL *OPL; + int state_size; + int max_ch = 9; /* normaly 9 channels */ + + if( OPL_LockTable() ==-1) return NULL; + /* allocate OPL state space */ + state_size = sizeof(FM_OPL); + state_size += sizeof(OPL_CH)*max_ch; +#if BUILD_Y8950 + if(type&OPL_TYPE_ADPCM) state_size+= sizeof(YM_DELTAT); +#endif + /* allocate memory block */ + ptr = malloc(state_size); + if(ptr==NULL) return NULL; + /* clear */ + memset(ptr,0,state_size); + OPL = (FM_OPL *)ptr; ptr+=sizeof(FM_OPL); + OPL->P_CH = (OPL_CH *)ptr; ptr+=sizeof(OPL_CH)*max_ch; +#if BUILD_Y8950 + if(type&OPL_TYPE_ADPCM) OPL->deltat = (YM_DELTAT *)ptr; ptr+=sizeof(YM_DELTAT); +#endif + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + /* init grobal tables */ + OPL_initalize(OPL); + /* reset chip */ + OPLResetChip(OPL); +#ifdef OPL_OUTPUT_LOG + if(!opl_dbg_fp) + { + opl_dbg_fp = fopen("opllog.opl","wb"); + opl_dbg_maxchip = 0; + } + if(opl_dbg_fp) + { + opl_dbg_opl[opl_dbg_maxchip] = OPL; + fprintf(opl_dbg_fp,"%c%c%c%c%c%c",0x00+opl_dbg_maxchip, + type, + clock&0xff, + (clock/0x100)&0xff, + (clock/0x10000)&0xff, + (clock/0x1000000)&0xff); + opl_dbg_maxchip++; + } +#endif + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) +{ +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + fclose(opl_dbg_fp); + opl_dbg_fp = NULL; + } +#endif + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ + +void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset) +{ + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} +void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param) +{ + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} +void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param) +{ + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} +#if BUILD_Y8950 +void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param) +{ + OPL->porthandler_w = PortHandler_w; + OPL->porthandler_r = PortHandler_r; + OPL->port_param = param; +} + +void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param) +{ + OPL->keyboardhandler_w = KeyboardHandler_w; + OPL->keyboardhandler_r = KeyboardHandler_r; + OPL->keyboard_param = param; +} +#endif +/* ---------- YM3812 I/O interface ---------- */ +int OPLWrite(FM_OPL *OPL,int a,int v) +{ + if( !(a&1) ) + { /* address port */ + OPL->address = v & 0xff; + } + else + { /* data port */ + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + for(opl_dbg_chip=0;opl_dbg_chip<opl_dbg_maxchip;opl_dbg_chip++) + if( opl_dbg_opl[opl_dbg_chip] == OPL) break; + fprintf(opl_dbg_fp,"%c%c%c",0x10+opl_dbg_chip,OPL->address,v); + } +#endif + OPLWriteReg(OPL,OPL->address,v); + } + return OPL->status>>7; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) +{ + if( !(a&1) ) + { /* status port */ + return OPL->status & (OPL->statusmask|0x80); + } + /* data port */ + switch(OPL->address) + { + case 0x05: /* KeyBoard IN */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_r) + return OPL->keyboardhandler_r(OPL->keyboard_param); + else + LOG(LOG_WAR,("OPL:read unmapped KEYBOARD port\n")); + } + return 0; +#if 0 + case 0x0f: /* ADPCM-DATA */ + return 0; +#endif + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + if(OPL->porthandler_r) + return OPL->porthandler_r(OPL->port_param); + else + LOG(LOG_WAR,("OPL:read unmapped I/O port\n")); + } + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + } + return 0; +} + +int OPLTimerOver(FM_OPL *OPL,int c) +{ + if( c ) + { /* Timer B */ + OPL_STATUS_SET(OPL,0x20); + } + else + { /* Timer A */ + OPL_STATUS_SET(OPL,0x40); + /* CSM mode key,TL controll */ + if( OPL->mode & 0x80 ) + { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch=0;ch<9;ch++) + CSMKeyControll( &OPL->P_CH[ch] ); + } + } + /* reload timer */ + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+c,(double)OPL->T[c]*OPL->TimerBase); + return OPL->status>>7; +} diff --git a/plugins/adplug/adplug/fmopl.h b/plugins/adplug/adplug/fmopl.h new file mode 100644 index 00000000..a01ff902 --- /dev/null +++ b/plugins/adplug/adplug/fmopl.h @@ -0,0 +1,174 @@ +#ifndef __FMOPL_H_ +#define __FMOPL_H_ + +/* --- select emulation chips --- */ +#define BUILD_YM3812 (HAS_YM3812) +//#define BUILD_YM3526 (HAS_YM3526) +//#define BUILD_Y8950 (HAS_Y8950) + +/* --- system optimize --- */ +/* select bit size of output : 8 or 16 */ +#define OPL_OUTPUT_BIT 16 + +/* compiler dependence */ +#ifndef OSD_CPU_H +#define OSD_CPU_H +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ +#endif + +#if (OPL_OUTPUT_BIT==16) +typedef INT16 OPLSAMPLE; +#endif +#if (OPL_OUTPUT_BIT==8) +typedef unsigned char OPLSAMPLE; +#endif + + +#if BUILD_Y8950 +#include "ymdeltat.h" +#endif + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); +typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data); +typedef unsigned char (*OPL_PORTHANDLER_R)(int param); + +/* !!!!! here is private section , do not access there member direct !!!!! */ + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ +#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ +#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ +#define OPL_TYPE_IO 0x08 /* I/O port */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + INT32 TL; /* total level :TL << 8 */ + INT32 TLL; /* adjusted now TL */ + UINT8 KSR; /* key scale rate :(shift down bit) */ + INT32 *AR; /* attack rate :&AR_TABLE[AR<<2] */ + INT32 *DR; /* decay rate :&DR_TALBE[DR<<2] */ + INT32 SL; /* sustin level :SL_TALBE[SL] */ + INT32 *RR; /* release rate :&DR_TABLE[RR<<2] */ + UINT8 ksl; /* keyscale level :(shift down bits) */ + UINT8 ksr; /* key scale rate :kcode>>KSR */ + UINT32 mul; /* multiple :ML_TABLE[ML] */ + UINT32 Cnt; /* frequency count : */ + UINT32 Incr; /* frequency step : */ + /* envelope generator state */ + UINT8 eg_typ; /* envelope type flag */ + UINT8 evm; /* envelope phase */ + INT32 evc; /* envelope counter */ + INT32 eve; /* envelope counter end point */ + INT32 evs; /* envelope counter step */ + INT32 evsa; /* envelope step for AR :AR[ksr] */ + INT32 evsd; /* envelope step for DR :DR[ksr] */ + INT32 evsr; /* envelope step for RR :RR[ksr] */ + /* LFO */ + UINT8 ams; /* ams flag */ + UINT8 vib; /* vibrate flag */ + /* wave selector */ + INT32 **wavetable; +}OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + UINT8 CON; /* connection type */ + UINT8 FB; /* feed back :(shift down bit) */ + INT32 *connect1; /* slot1 output pointer */ + INT32 *connect2; /* slot2 output pointer */ + INT32 op1_out[2]; /* slot1 output for selfeedback */ + /* phase generator state */ + UINT32 block_fnum; /* block+fnum : */ + UINT8 kcode; /* key code : KeyScaleCode */ + UINT32 fc; /* Freq. Increment base */ + UINT32 ksl_base; /* KeyScaleLevel Base step */ + UINT8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + UINT8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT8 statusmask; /* status mask */ + UINT32 mode; /* Reg.08 : CSM , notesel,etc. */ + /* Timer */ + int T[2]; /* timer counter */ + UINT8 st[2]; /* timer enable */ + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + /* Rythm sention */ + UINT8 rythm; /* Rythm mode , key flag */ +#if BUILD_Y8950 + /* Delta-T ADPCM unit (Y8950) */ + YM_DELTAT *deltat; /* DELTA-T ADPCM */ +#endif + /* Keyboard / I/O interface unit (Y8950) */ + UINT8 portDirection; + UINT8 portLatch; + OPL_PORTHANDLER_R porthandler_r; + OPL_PORTHANDLER_W porthandler_w; + int port_param; + OPL_PORTHANDLER_R keyboardhandler_r; + OPL_PORTHANDLER_W keyboardhandler_w; + int keyboard_param; + /* time tables */ + INT32 AR_TABLE[75]; /* atttack rate tables */ + INT32 DR_TABLE[75]; /* decay rate tables */ + UINT32 FN_TABLE[1024]; /* fnumber -> increment counter */ + /* LFO */ + INT32 *ams_table; + INT32 *vib_table; + INT32 amsCnt; + INT32 amsIncr; + INT32 vibCnt; + INT32 vibIncr; + /* wave selector enable flag */ + UINT8 wavesel; + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) +#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param); +void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param); +/* Y8950 port handlers */ +void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param); +void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL,int a,int v); +unsigned char OPLRead(FM_OPL *OPL,int a); +int OPLTimerOver(FM_OPL *OPL,int c); + +/* YM3626/YM3812 local section */ +void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +#endif diff --git a/plugins/adplug/adplug/fprovide.cpp b/plugins/adplug/adplug/fprovide.cpp new file mode 100644 index 00000000..cbe94e2f --- /dev/null +++ b/plugins/adplug/adplug/fprovide.cpp @@ -0,0 +1,76 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2002 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * fprovide.cpp - File provider class framework, by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> +#include <binio.h> +#include <binfile.h> + +#include "fprovide.h" + +/***** CFileProvider *****/ + +bool CFileProvider::extension(const std::string &filename, + const std::string &extension) +{ + const char *fname = filename.c_str(), *ext = extension.c_str(); + + if(strlen(fname) < strlen(ext) || + stricmp(fname + strlen(fname) - strlen(ext), ext)) + return false; + else + return true; +} + +unsigned long CFileProvider::filesize(binistream *f) +{ + unsigned long oldpos = f->pos(), size; + + f->seek(0, binio::End); + size = f->pos(); + f->seek(oldpos, binio::Set); + + return size; +} + +/***** CProvider_Filesystem *****/ + +binistream *CProvider_Filesystem::open(std::string filename) const +{ + binifstream *f = new binifstream(filename); + + if(!f) return 0; + if(f->error()) { delete f; return 0; } + + // Open all files as little endian with IEEE floats by default + f->setFlag(binio::BigEndian, false); f->setFlag(binio::FloatIEEE); + + return f; +} + +void CProvider_Filesystem::close(binistream *f) const +{ + binifstream *ff = (binifstream *)f; + + if(f) { + ff->close(); + delete ff; + } +} diff --git a/plugins/adplug/adplug/fprovide.h b/plugins/adplug/adplug/fprovide.h new file mode 100644 index 00000000..2f1a0b62 --- /dev/null +++ b/plugins/adplug/adplug/fprovide.h @@ -0,0 +1,50 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * fprovide.h - File provider class framework, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_FILEPROVIDER +#define H_ADPLUG_FILEPROVIDER + +#include <string> +#include <binio.h> + +class CFileProvider +{ +public: + virtual ~CFileProvider() + { + } + + virtual binistream *open(std::string filename) const = 0; + virtual void close(binistream *) const = 0; + + static bool extension(const std::string &filename, + const std::string &extension); + static unsigned long filesize(binistream *f); +}; + +class CProvider_Filesystem: public CFileProvider +{ +public: + virtual binistream *open(std::string filename) const; + virtual void close(binistream *f) const; +}; + +#endif diff --git a/plugins/adplug/adplug/hsc.cpp b/plugins/adplug/adplug/hsc.cpp new file mode 100644 index 00000000..1a912ff1 --- /dev/null +++ b/plugins/adplug/adplug/hsc.cpp @@ -0,0 +1,317 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * hsc.cpp - HSC Player by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> + +#include "hsc.h" +#include "debug.h" + +/*** public methods **************************************/ + +CPlayer *ChscPlayer::factory(Copl *newopl) +{ + return new ChscPlayer(newopl); +} + +bool ChscPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); + int i; + + // file validation section + if(!f || !fp.extension(filename, ".hsc") || fp.filesize(f) > 59187) { + AdPlug_LogWrite("ChscPlayer::load(\"%s\"): Not a HSC file!\n", filename.c_str()); + fp.close(f); + return false; + } + + // load section + for(i=0;i<128*12;i++) // load instruments + *((unsigned char *)instr + i) = f->readInt(1); + for (i=0;i<128;i++) { // correct instruments + instr[i][2] ^= (instr[i][2] & 0x40) << 1; + instr[i][3] ^= (instr[i][3] & 0x40) << 1; + instr[i][11] >>= 4; // slide + } + for(i=0;i<51;i++) song[i] = f->readInt(1); // load tracklist + for(i=0;i<50*64*9;i++) // load patterns + *((char *)patterns + i) = f->readInt(1); + + fp.close(f); + rewind(0); // rewind module + return true; +} + +bool ChscPlayer::update() +{ + // general vars + unsigned char chan,pattnr,note,effect,eff_op,inst,vol,Okt,db; + unsigned short Fnr; + unsigned long pattoff; + + del--; // player speed handling + if(del) + return !songend; // nothing done + + if(fadein) // fade-in handling + fadein--; + + pattnr = song[songpos]; + if(pattnr == 0xff) { // arrangement handling + songend = 1; // set end-flag + songpos = 0; + pattnr = song[songpos]; + } else + if ((pattnr & 128) && (pattnr <= 0xb1)) { // goto pattern "nr" + songpos = song[songpos] & 127; + pattpos = 0; + pattnr = song[songpos]; + songend = 1; + } + + pattoff = pattpos*9; + for (chan=0;chan<9;chan++) { // handle all channels + note = patterns[pattnr][pattoff].note; + effect = patterns[pattnr][pattoff].effect; + pattoff++; + + if(note & 128) { // set instrument + setinstr(chan,effect); + continue; + } + eff_op = effect & 0x0f; + inst = channel[chan].inst; + if(note) + channel[chan].slide = 0; + + switch (effect & 0xf0) { // effect handling + case 0: // global effect + /* The following fx are unimplemented on purpose: + * 02 - Slide Mainvolume up + * 03 - Slide Mainvolume down (here: fade in) + * 04 - Set Mainvolume to 0 + * + * This is because i've never seen any HSC modules using the fx this way. + * All modules use the fx the way, i've implemented it. + */ + switch(eff_op) { + case 1: pattbreak++; break; // jump to next pattern + case 3: fadein = 31; break; // fade in (divided by 2) + case 5: mode6 = 1; break; // 6 voice mode on + case 6: mode6 = 0; break; // 6 voice mode off + } + break; + case 0x20: + case 0x10: // manual slides + if (effect & 0x10) { + channel[chan].freq += eff_op; + channel[chan].slide += eff_op; + } else { + channel[chan].freq -= eff_op; + channel[chan].slide -= eff_op; + } + if(!note) + setfreq(chan,channel[chan].freq); + break; + case 0x50: // set percussion instrument (unimplemented) + break; + case 0x60: // set feedback + opl->write(0xc0 + chan, (instr[channel[chan].inst][8] & 1) + (eff_op << 1)); + break; + case 0xa0: // set carrier volume + vol = eff_op << 2; + opl->write(0x43 + op_table[chan], vol | (instr[channel[chan].inst][2] & ~63)); + break; + case 0xb0: // set modulator volume + vol = eff_op << 2; + if (instr[inst][8] & 1) + opl->write(0x40 + op_table[chan], vol | (instr[channel[chan].inst][3] & ~63)); + else + opl->write(0x40 + op_table[chan],vol | (instr[inst][3] & ~63)); + break; + case 0xc0: // set instrument volume + db = eff_op << 2; + opl->write(0x43 + op_table[chan], db | (instr[channel[chan].inst][2] & ~63)); + if (instr[inst][8] & 1) + opl->write(0x40 + op_table[chan], db | (instr[channel[chan].inst][3] & ~63)); + break; + case 0xd0: pattbreak++; songpos = eff_op; songend = 1; break; // position jump + case 0xf0: // set speed + speed = eff_op; + del = ++speed; + break; + } + + if(fadein) // fade-in volume setting + setvolume(chan,fadein*2,fadein*2); + + if(!note) // note handling + continue; + note--; + + if ((note == 0x7f-1) || ((note/12) & ~7)) { // pause (7fh) + adl_freq[chan] &= ~32; + opl->write(0xb0 + chan,adl_freq[chan]); + continue; + } + + // play the note + if(mtkmode) // imitate MPU-401 Trakker bug + note--; + Okt = ((note/12) & 7) << 2; + Fnr = note_table[(note % 12)] + instr[inst][11] + channel[chan].slide; + channel[chan].freq = Fnr; + if(!mode6 || chan < 6) + adl_freq[chan] = Okt | 32; + else + adl_freq[chan] = Okt; // never set key for drums + opl->write(0xb0 + chan, 0); + setfreq(chan,Fnr); + if(mode6) { + switch(chan) { // play drums + case 6: opl->write(0xbd,bd & ~16); bd |= 48; break; // bass drum + case 7: opl->write(0xbd,bd & ~1); bd |= 33; break; // hihat + case 8: opl->write(0xbd,bd & ~2); bd |= 34; break; // cymbal + } + opl->write(0xbd,bd); + } + } + + del = speed; // player speed-timing + if(pattbreak) { // do post-effect handling + pattpos=0; // pattern break! + pattbreak=0; + songpos++; + songpos %= 50; + if(!songpos) + songend = 1; + } else { + pattpos++; + pattpos &= 63; // advance in pattern data + if (!pattpos) { + songpos++; + songpos %= 50; + if(!songpos) + songend = 1; + } + } + return !songend; // still playing +} + +void ChscPlayer::rewind(int subsong) +{ + int i; // counter + + // rewind HSC player + pattpos = 0; songpos = 0; pattbreak = 0; speed = 2; + del = 1; songend = 0; mode6 = 0; bd = 0; fadein = 0; + + opl->init(); // reset OPL chip + opl->write(1,32); opl->write(8,128); opl->write(0xbd,0); + + for(i=0;i<9;i++) + setinstr((char) i,(char) i); // init channels +} + +unsigned int ChscPlayer::getpatterns() +{ + unsigned char poscnt,pattcnt=0; + + // count patterns + for(poscnt=0;poscnt<51 && song[poscnt] != 0xff;poscnt++) + if(song[poscnt] > pattcnt) + pattcnt = song[poscnt]; + + return (pattcnt+1); +} + +unsigned int ChscPlayer::getorders() +{ + unsigned char poscnt; + + // count positions + for(poscnt=0;poscnt<51;poscnt++) + if(song[poscnt] == 0xff) + break; + + return poscnt; +} + +unsigned int ChscPlayer::getinstruments() +{ + unsigned char instcnt,instnum=0,i; + bool isinst; + + // count instruments + for(instcnt=0;instcnt<128;instcnt++) { + isinst = false; + for(i=0;i<12;i++) + if(instr[instcnt][i]) + isinst = true; + if(isinst) + instnum++; + } + + return instnum; +} + +/*** private methods *************************************/ + +void ChscPlayer::setfreq(unsigned char chan, unsigned short freq) +{ + adl_freq[chan] = (adl_freq[chan] & ~3) | (freq >> 8); + + opl->write(0xa0 + chan, freq & 0xff); + opl->write(0xb0 + chan, adl_freq[chan]); +} + +void ChscPlayer::setvolume(unsigned char chan, int volc, int volm) +{ + unsigned char *ins = instr[channel[chan].inst]; + char op = op_table[chan]; + + opl->write(0x43 + op,volc | (ins[2] & ~63)); + if (ins[8] & 1) // carrier + opl->write(0x40 + op,volm | (ins[3] & ~63)); + else + opl->write(0x40 + op, ins[3]); // modulator +} + +void ChscPlayer::setinstr(unsigned char chan, unsigned char insnr) +{ + unsigned char *ins = instr[insnr]; + char op = op_table[chan]; + + channel[chan].inst = insnr; // set internal instrument + opl->write(0xb0 + chan,0); // stop old note + + // set instrument + opl->write(0xc0 + chan, ins[8]); + opl->write(0x23 + op, ins[0]); // carrier + opl->write(0x20 + op, ins[1]); // modulator + opl->write(0x63 + op, ins[4]); // bits 0..3 = decay; 4..7 = attack + opl->write(0x60 + op, ins[5]); + opl->write(0x83 + op, ins[6]); // 0..3 = release; 4..7 = sustain + opl->write(0x80 + op, ins[7]); + opl->write(0xe3 + op, ins[9]); // bits 0..1 = Wellenform + opl->write(0xe0 + op, ins[10]); + setvolume(chan, ins[2] & 63, ins[3] & 63); +} diff --git a/plugins/adplug/adplug/hsc.h b/plugins/adplug/adplug/hsc.h new file mode 100644 index 00000000..d09b8906 --- /dev/null +++ b/plugins/adplug/adplug/hsc.h @@ -0,0 +1,75 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2002 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * hsc.h - HSC Player by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_HSCPLAYER +#define H_ADPLUG_HSCPLAYER + +#include "player.h" + +class ChscPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + ChscPlayer(Copl *newopl): CPlayer(newopl), mtkmode(0) {} + + bool load(const std::string &fn, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh() { return 18.2f; }; // refresh rate is fixed at 18.2Hz + + std::string gettype() { return std::string("HSC Adlib Composer / HSC-Tracker"); } + unsigned int getpatterns(); + unsigned int getpattern() { return song[songpos]; } + unsigned int getorders(); + unsigned int getorder() { return songpos; } + unsigned int getrow() { return pattpos; } + unsigned int getspeed() { return speed; } + unsigned int getinstruments(); + + protected: + struct hscnote { + unsigned char note, effect; + }; // note type in HSC pattern + + struct hscchan { + unsigned char inst; // current instrument + signed char slide; // used for manual slide-effects + unsigned short freq; // actual replaying frequency + }; // HSC channel data + + hscchan channel[9]; // player channel-info + unsigned char instr[128][12]; // instrument data + unsigned char song[0x80]; // song-arrangement (MPU-401 Trakker enhanced) + hscnote patterns[50][64*9]; // pattern data + unsigned char pattpos,songpos, // various bytes & flags + pattbreak,songend,mode6,bd,fadein; + unsigned int speed,del; + unsigned char adl_freq[9]; // adlib frequency registers + int mtkmode; // flag: MPU-401 Trakker mode on/off + + private: + void setfreq(unsigned char chan, unsigned short freq); + void setvolume(unsigned char chan, int volc, int volm); + void setinstr(unsigned char chan, unsigned char insnr); +}; + +#endif diff --git a/plugins/adplug/adplug/hsp.cpp b/plugins/adplug/adplug/hsp.cpp new file mode 100644 index 00000000..0430f6d7 --- /dev/null +++ b/plugins/adplug/adplug/hsp.cpp @@ -0,0 +1,68 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * hsp.cpp - HSP Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> + +#include "hsp.h" + +CPlayer *ChspLoader::factory(Copl *newopl) +{ + return new ChspLoader(newopl); +} + +bool ChspLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + unsigned long i, j, orgsize, filesize; + unsigned char *cmp, *org; + + // file validation section + if(!fp.extension(filename, ".hsp")) { fp.close(f); return false; } + + filesize = fp.filesize(f); + orgsize = f->readInt(2); + if(orgsize > 59187) { fp.close(f); return false; } + + // load section + cmp = new unsigned char[filesize]; + for(i = 0; i < filesize; i++) cmp[i] = f->readInt(1); + fp.close(f); + + org = new unsigned char[orgsize]; + for(i = 0, j = 0; i < filesize; j += cmp[i], i += 2) { // RLE decompress + if(j >= orgsize) break; // memory boundary check + memset(org + j, cmp[i + 1], j + cmp[i] < orgsize ? cmp[i] : orgsize - j - 1); + } + delete [] cmp; + + memcpy(instr, org, 128 * 12); // instruments + for(i = 0; i < 128; i++) { // correct instruments + instr[i][2] ^= (instr[i][2] & 0x40) << 1; + instr[i][3] ^= (instr[i][3] & 0x40) << 1; + instr[i][11] >>= 4; // slide + } + memcpy(song, org + 128 * 12, 51); // tracklist + memcpy(patterns, org + 128 * 12 + 51, orgsize - 128 * 12 - 51); // patterns + delete [] org; + + rewind(0); + return true; +} diff --git a/plugins/adplug/adplug/hsp.h b/plugins/adplug/adplug/hsp.h new file mode 100644 index 00000000..86fa9d69 --- /dev/null +++ b/plugins/adplug/adplug/hsp.h @@ -0,0 +1,39 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * hsp.h: HSC Packed Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_HSPLOADER +#define H_ADPLUG_HSPLOADER + +#include "hsc.h" + +class ChspLoader: public ChscPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + ChspLoader(Copl *newopl) + : ChscPlayer(newopl) + {}; + + bool load(const std::string &filename, const CFileProvider &fp); +}; + +#endif diff --git a/plugins/adplug/adplug/hybrid.cpp b/plugins/adplug/adplug/hybrid.cpp new file mode 100644 index 00000000..cff08c88 --- /dev/null +++ b/plugins/adplug/adplug/hybrid.cpp @@ -0,0 +1,248 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] HYBRID player, by Riven the Mage <riven@ok.ru> + */ + +/* + - discovery - + + file(s) : HYBRID.EXE + type : Hybrid cracktro for Apache Longbow CD-RIP + tune : from 'Mig-29 Super Fulcrum' game by Domark + player : from 'Mig-29 Super Fulcrum' game by Domark +*/ + +#include "hybrid.h" +#include "debug.h" + +const unsigned char CxadhybridPlayer::hyb_adlib_registers[99] = +{ + 0xE0, 0x60, 0x80, 0x20, 0x40, 0xE3, 0x63, 0x83, 0x23, 0x43, 0xC0, + 0xE1, 0x61, 0x81, 0x21, 0x41, 0xE4, 0x64, 0x84, 0x24, 0x44, 0xC1, + 0xE2, 0x62, 0x82, 0x22, 0x42, 0xE5, 0x65, 0x85, 0x25, 0x45, 0xC2, + 0xE8, 0x68, 0x88, 0x28, 0x48, 0xEB, 0x6B, 0x8B, 0x2B, 0x4B, 0xC3, + 0xE9, 0x69, 0x89, 0x29, 0x49, 0xEC, 0x6C, 0x8C, 0x2C, 0x4C, 0xC4, + 0xEA, 0x6A, 0x8A, 0x2A, 0x4A, 0xED, 0x6D, 0x8D, 0x2D, 0x4D, 0xC5, + 0xF0, 0x70, 0x90, 0x30, 0x50, 0xF3, 0x73, 0x93, 0x33, 0x53, 0xC6, + 0xF1, 0x71, 0x91, 0x31, 0x51, 0xF4, 0x74, 0x94, 0x34, 0x54, 0xC7, + 0xF2, 0x72, 0x92, 0x32, 0x52, 0xF5, 0x75, 0x95, 0x35, 0x55, 0xC8 +}; + +const unsigned short CxadhybridPlayer::hyb_notes[98] = +{ + 0x0000, 0x0000, + 0x016B, 0x0181, 0x0198, 0x01B0, 0x01CA, 0x01E5, 0x0202, 0x0220, 0x0241, 0x0263, 0x0287, 0x02AE, + 0x056B, 0x0581, 0x0598, 0x05B0, 0x05CA, 0x05E5, 0x0602, 0x0620, 0x0641, 0x0663, 0x0687, 0x06AE, + 0x096B, 0x0981, 0x0998, 0x09B0, 0x09CA, 0x09E5, 0x0A02, 0x0A20, 0x0A41, 0x0A63, 0x0A87, 0x0AAE, + 0x0D6B, 0x0D81, 0x0D98, 0x0DB0, 0x0DCA, 0x0DE5, 0x0E02, 0x0E20, 0x0E41, 0x0E63, 0x0E87, 0x0EAE, + 0x116B, 0x1181, 0x1198, 0x11B0, 0x11CA, 0x11E5, 0x1202, 0x1220, 0x1241, 0x1263, 0x1287, 0x12AE, + 0x156B, 0x1581, 0x1598, 0x15B0, 0x15CA, 0x15E5, 0x1602, 0x1620, 0x1641, 0x1663, 0x1687, 0x16AE, + 0x196B, 0x1981, 0x1998, 0x19B0, 0x19CA, 0x19E5, 0x1A02, 0x1A20, 0x1A41, 0x1A63, 0x1A87, 0x1AAE, + 0x1D6B, 0x1D81, 0x1D98, 0x1DB0, 0x1DCA, 0x1DE5, 0x1E02, 0x1E20, 0x1E41, 0x1E63, 0x1E87, 0x1EAE +}; + +const unsigned char CxadhybridPlayer::hyb_default_instrument[11] = +{ + 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00 +}; + +CPlayer *CxadhybridPlayer::factory(Copl *newopl) +{ + return new CxadhybridPlayer(newopl); +} + +bool CxadhybridPlayer::xadplayer_load() +{ + if(xad.fmt != HYBRID) + return false; + + // load instruments + hyb.inst = (hyb_instrument *)&tune[0]; + + // load order + hyb.order = &tune[0x1D4]; + + return true; +} + +void CxadhybridPlayer::xadplayer_rewind(int subsong) +{ + int i; + + hyb.order_pos = 0; + hyb.pattern_pos = 0; + + hyb.speed = 6; + hyb.speed_counter = 1; + + plr.speed = 1; + + // init channel data + for(i=0;i<9;i++) + { + hyb.channel[i].freq = 0x2000; + hyb.channel[i].freq_slide = 0x0000; + } + + // basic OPL init + opl_write(0x01, 0x20); + opl_write(0xBD, 0x40); + opl_write(0x08, 0x00); + + // init OPL channels + for(i=0;i<9;i++) + { + for(int j=0;j<11;j++) + opl_write(hyb_adlib_registers[i*11+j], 0x00 /* hyb_default_instrument[j] */ ); + + opl_write(0xA0+i, 0x00); + opl_write(0xB0+i, 0x20); + } +} + +void CxadhybridPlayer::xadplayer_update() +{ + int i,j; + unsigned char patpos,ordpos; + + if (--hyb.speed_counter) + goto update_slides; + + hyb.speed_counter = hyb.speed; + + patpos = hyb.pattern_pos; + ordpos = hyb.order_pos; + + // process channels + for(i=0;i<9;i++) + { + unsigned char *pos = &tune[0xADE + (hyb.order[hyb.order_pos*9 + i] * 64 * 2) + (patpos * 2)]; + // read event + unsigned short event = (pos[1] << 8) + pos[0]; + +#ifdef DEBUG + AdPlug_LogWrite("track %02X, channel %02X, event %04X:\n", hyb.order[hyb.order_pos*9 + i], i, event ); +#endif + + // calculate variables + unsigned char note = event >> 9; + unsigned char ins = ((event & 0x01F0) >> 4); + unsigned char slide = event & 0x000F; + + // play event + switch(note) + { + case 0x7D: // 0x7D: Set Speed + hyb.speed = event & 0xFF; + break; + case 0x7E: // 0x7E: Jump Position + hyb.order_pos = event & 0xFF; + hyb.pattern_pos = 0x3F; + + // jumpback ? + if (hyb.order_pos <= ordpos) + plr.looping = 1; + + break; + case 0x7F: // 0x7F: Pattern Break + hyb.pattern_pos = 0x3F; + break; + default: + + // is instrument ? + if (ins) + for(j=0;j<11;j++) + opl_write(hyb_adlib_registers[i*11+j], *((unsigned char *)&hyb.inst[ins-1] + 7 + j)); // +7 = skip name... + + // is note ? + if (note) + { + hyb.channel[i].freq = hyb_notes[note]; + hyb.channel[i].freq_slide = 0; + } + + // is slide ? + if (slide) + { + hyb.channel[i].freq_slide = (((slide >> 3) * -1) * (slide & 7)) << 1; + + if (slide & 0x80) + slide = -(slide & 7); + } + + // set frequency + if (!(hyb.channel[i].freq & 0x2000)) + { + opl_write(0xA0+i, hyb.channel[i].freq & 0xFF); + opl_write(0xB0+i, hyb.channel[i].freq >> 8); + + hyb.channel[i].freq |= 0x2000; + + opl_write(0xA0+i, hyb.channel[i].freq & 0xFF); + opl_write(0xB0+i, hyb.channel[i].freq >> 8); + } + + break; + } + } + + hyb.pattern_pos++; + + // end of pattern ? + if (hyb.pattern_pos >= 0x40) + { + hyb.pattern_pos = 0; + + hyb.order_pos++; + } + +update_slides: +#ifdef DEBUG + AdPlug_LogWrite("slides:\n"); +#endif + // update fine frequency slides + for(i=0;i<9;i++) + if (hyb.channel[i].freq_slide) + { + hyb.channel[i].freq = (((hyb.channel[i].freq & 0x1FFF) + hyb.channel[i].freq_slide) & 0x1FFF) | 0x2000; + + opl_write(0xA0+i, hyb.channel[i].freq & 0xFF); + opl_write(0xB0+i, hyb.channel[i].freq >> 8); + } +} + +float CxadhybridPlayer::xadplayer_getrefresh() +{ + return 50.0f; +} + +std::string CxadhybridPlayer::xadplayer_gettype() +{ + return (std::string("xad: hybrid player")); +} + +std::string CxadhybridPlayer::xadplayer_getinstrument(unsigned int i) +{ + return (std::string(hyb.inst[i].name,7)); +} + +unsigned int CxadhybridPlayer::xadplayer_getinstruments() +{ + return 26; +} diff --git a/plugins/adplug/adplug/hybrid.h b/plugins/adplug/adplug/hybrid.h new file mode 100644 index 00000000..65d39d34 --- /dev/null +++ b/plugins/adplug/adplug/hybrid.h @@ -0,0 +1,80 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] HYBRID player, by Riven the Mage <riven@ok.ru> + */ + +#include "xad.h" + +class CxadhybridPlayer: public CxadPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CxadhybridPlayer(Copl *newopl): CxadPlayer(newopl) + { } + +protected: + struct hyb_instrument + { + char name[7]; + unsigned char mod_wave; + unsigned char mod_AD; + unsigned char mod_SR; + unsigned char mod_crtl; + unsigned char mod_volume; + unsigned char car_wave; + unsigned char car_AD; + unsigned char car_SR; + unsigned char car_crtl; + unsigned char car_volume; + unsigned char connect; + }; + + struct + { + unsigned char order_pos; + unsigned char pattern_pos; + + unsigned char *order; + + hyb_instrument *inst; + + struct + { + unsigned short freq; + unsigned short freq_slide; + } channel[9]; + + unsigned char speed; + unsigned char speed_counter; + } hyb; + // + bool xadplayer_load(); + void xadplayer_rewind(int subsong); + void xadplayer_update(); + float xadplayer_getrefresh(); + std::string xadplayer_gettype(); + std::string xadplayer_getinstrument(unsigned int i); + unsigned int xadplayer_getinstruments(); + +private: + static const unsigned char hyb_adlib_registers[99]; + static const unsigned short hyb_notes[98]; + static const unsigned char hyb_default_instrument[11]; +}; diff --git a/plugins/adplug/adplug/hyp.cpp b/plugins/adplug/adplug/hyp.cpp new file mode 100644 index 00000000..7ed3a315 --- /dev/null +++ b/plugins/adplug/adplug/hyp.cpp @@ -0,0 +1,125 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] HYP player, by Riven the Mage <riven@ok.ru> + */ + +/* + - discovery - + + file(s) : HT-EF2.COM, HT-EF3.COM + type : Eiserne Front BBStro + tune : by Shadowdancer [Hypnosis] + player : by (?)Hetero [LKCC/SAC] +*/ + +#include "hyp.h" +#include "debug.h" + +const unsigned char CxadhypPlayer::hyp_adlib_registers[99] = +{ + 0x20, 0x23, 0x40, 0x43, 0x60, 0x63, 0x80, 0x83, 0xA0, 0xB0, 0xC0, + 0x21, 0x24, 0x41, 0x44, 0x61, 0x64, 0x81, 0x84, 0xA1, 0xB1, 0xC1, + 0x22, 0x25, 0x42, 0x45, 0x62, 0x65, 0x82, 0x85, 0xA2, 0xB2, 0xC2, + 0x28, 0x2B, 0x48, 0x4B, 0x68, 0x6B, 0x88, 0x8B, 0xA3, 0xB3, 0xC3, + 0x29, 0x2C, 0x49, 0x4C, 0x69, 0x6C, 0x89, 0x8C, 0xA4, 0xB4, 0xC4, + 0x2A, 0x2D, 0x4A, 0x4D, 0x6A, 0x6D, 0x8A, 0x8D, 0xA5, 0xB5, 0xC5, + 0x30, 0x33, 0x50, 0x53, 0x70, 0x73, 0x90, 0x93, 0xA6, 0xB6, 0xC6, + 0x31, 0x34, 0x51, 0x54, 0x71, 0x74, 0x91, 0x94, 0xA7, 0xB7, 0xC7, + 0x32, 0x35, 0x52, 0x55, 0x72, 0x75, 0x92, 0x95, 0xA8, 0xB8, 0xC8 +}; + +const unsigned short CxadhypPlayer::hyp_notes[73] = +{ + 0x0000, // by riven + 0x0956, 0x096B, 0x0980, 0x0998, 0x09B1, 0x09C9, 0x09E5, 0x0A03, 0x0A21, + 0x0A41, 0x0A63, 0x0A86, 0x0D56, 0x0D6B, 0x0D80, 0x0D98, 0x0DB1, 0x0DC9, + 0x0DE5, 0x0E03, 0x0E21, 0x0E41, 0x0E63, 0x0E86, 0x1156, 0x116B, 0x1180, + 0x1198, 0x11B1, 0x11C9, 0x11E5, 0x1203, 0x1221, 0x1241, 0x1263, 0x1286, + 0x1556, 0x156B, 0x1580, 0x1598, 0x15B1, 0x15C9, 0x15E5, 0x1603, 0x1621, + 0x1641, 0x1663, 0x1686, 0x1956, 0x196B, 0x1980, 0x1998, 0x19B1, 0x19C9, + 0x19E5, 0x1A03, 0x1A21, 0x1A41, 0x1A63, 0x1A86, 0x1D56, 0x1D6B, 0x1D80, + 0x1D98, 0x1DB1, 0x1DC9, 0x1DE5, 0x1E03, 0x1E21, 0x1E41, 0x1E63, 0x1E86 +}; + +CPlayer *CxadhypPlayer::factory(Copl *newopl) +{ + return new CxadhypPlayer(newopl); +} + +void CxadhypPlayer::xadplayer_rewind(int subsong) +{ + int i; + + plr.speed = tune[5]; + + opl_write(0xBD,0xC0); + + for(i=0; i<9; i++) + adlib[0xB0+i] = 0; + + // define instruments + for(i=0; i<99; i++) + opl_write(hyp_adlib_registers[i], tune[6+i]); + + hyp.pointer = 0x69; +} + +void CxadhypPlayer::xadplayer_update() +{ + for(int i=0; i<9; i++) + { + unsigned char event = tune[hyp.pointer++]; + + if (event) + { + unsigned short freq = hyp_notes[event & 0x3F]; + + unsigned char lofreq = (freq & 0xFF); + unsigned char hifreq = (freq >> 8); + + opl_write(0xB0+i, adlib[0xB0+i]); + + if (!(event & 0x40)) + { + opl_write(0xA0+i, lofreq); + opl_write(0xB0+i, hifreq | 0x20); + } + + adlib[0xB0+i] &= 0xDF; + } + } + + hyp.pointer += 3; + + if (hyp.pointer >= tune_size) + { + hyp.pointer = 0x69; + plr.looping = 1; + } +} + +float CxadhypPlayer::xadplayer_getrefresh() +{ + return 60.0f; +} + +std::string CxadhypPlayer::xadplayer_gettype() +{ + return std::string("xad: hypnosis player"); +} diff --git a/plugins/adplug/adplug/hyp.h b/plugins/adplug/adplug/hyp.h new file mode 100644 index 00000000..e2a5bf5a --- /dev/null +++ b/plugins/adplug/adplug/hyp.h @@ -0,0 +1,53 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] HYP player, by Riven the Mage <riven@ok.ru> + */ + +#include "xad.h" + +class CxadhypPlayer: public CxadPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CxadhypPlayer(Copl *newopl): CxadPlayer(newopl) + { } + +protected: + struct + { + unsigned short pointer; + } hyp; + // + bool xadplayer_load() + { + if(xad.fmt == HYP) + return true; + else + return false; + } + void xadplayer_rewind(int subsong); + void xadplayer_update(); + float xadplayer_getrefresh(); + std::string xadplayer_gettype(); + +private: + static const unsigned char hyp_adlib_registers[99]; + static const unsigned short hyp_notes[73]; +}; diff --git a/plugins/adplug/adplug/imf.cpp b/plugins/adplug/adplug/imf.cpp new file mode 100644 index 00000000..37af7aca --- /dev/null +++ b/plugins/adplug/adplug/imf.cpp @@ -0,0 +1,196 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * imf.cpp - IMF Player by Simon Peter <dn.tlp@gmx.net> + * + * FILE FORMAT: + * There seem to be 2 different flavors of IMF formats out there. One version + * contains just the raw IMF music data. In this case, the first word of the + * file is always 0 (because the music data starts this way). This is already + * the music data! So read in the entire file and play it. + * + * If this word is greater than 0, it specifies the size of the following + * song data in bytes. In this case, the file has a footer that contains + * arbitrary infos about it. Mostly, this is plain ASCII text with some words + * of the author. Read and play the specified amount of song data and display + * the remaining data as ASCII text. + * + * NOTES: + * This player handles the two above mentioned formats, as well as a third + * type, invented by Martin Fernandez <mfernan@cnba.uba.ar>, that's got a + * proper header to add title/game name information. After the header starts + * the normal IMF file in one of the two above mentioned formats. + * + * This player also handles a special footer format by Adam Nielsen, + * which has defined fields of information about the song, the author + * and more. + */ + +#include <string.h> + +#include "imf.h" +#include "database.h" + +/*** public methods *************************************/ + +CPlayer *CimfPlayer::factory(Copl *newopl) +{ + return new CimfPlayer(newopl); +} + +bool CimfPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + unsigned long fsize, flsize, mfsize = 0; + unsigned int i; + + // file validation section + { + char header[5]; + int version; + + f->readString(header, 5); + version = f->readInt(1); + + if(strncmp(header, "ADLIB", 5) || version != 1) { + if(!fp.extension(filename, ".imf") && !fp.extension(filename, ".wlf")) { + // It's no IMF file at all + fp.close(f); + return false; + } else + f->seek(0); // It's a normal IMF file + } else { + // It's a IMF file with header + track_name = f->readString('\0'); + game_name = f->readString('\0'); + f->ignore(1); + mfsize = f->pos() + 2; + } + } + + // load section + if(mfsize) + fsize = f->readInt(4); + else + fsize = f->readInt(2); + flsize = fp.filesize(f); + if(!fsize) { // footerless file (raw music data) + if(mfsize) + f->seek(-4, binio::Add); + else + f->seek(-2, binio::Add); + size = (flsize - mfsize) / 4; + } else // file has got a footer + size = fsize / 4; + + data = new Sdata[size]; + for(i = 0; i < size; i++) { + data[i].reg = f->readInt(1); data[i].val = f->readInt(1); + data[i].time = f->readInt(2); + } + + // read footer, if any + if(fsize && (fsize < flsize - 2 - mfsize)) + if(f->readInt(1) == 0x1a) { + // Adam Nielsen's footer format + track_name = f->readString(); + author_name = f->readString(); + remarks = f->readString(); + } else { + // Generic footer + unsigned long footerlen = flsize - fsize - 2 - mfsize; + + footer = new char[footerlen + 1]; + f->readString(footer, footerlen); + footer[footerlen] = '\0'; // Make ASCIIZ string + } + + rate = getrate(filename, fp, f); + fp.close(f); + rewind(0); + return true; +} + +bool CimfPlayer::update() +{ + do { + opl->write(data[pos].reg,data[pos].val); + del = data[pos].time; + pos++; + } while(!del && pos < size); + + if(pos >= size) { + pos = 0; + songend = true; + } + else timer = rate / (float)del; + + return !songend; +} + +void CimfPlayer::rewind(int subsong) +{ + pos = 0; del = 0; timer = rate; songend = false; + opl->init(); opl->write(1,32); // go to OPL2 mode +} + +std::string CimfPlayer::gettitle() +{ + std::string title; + + title = track_name; + + if(!track_name.empty() && !game_name.empty()) + title += " - "; + + title += game_name; + + return title; +} + +std::string CimfPlayer::getdesc() +{ + std::string desc; + + if(footer) + desc = std::string(footer); + + if(!remarks.empty() && footer) + desc += "\n\n"; + + desc += remarks; + + return desc; +} + +/*** private methods *************************************/ + +float CimfPlayer::getrate(const std::string &filename, const CFileProvider &fp, binistream *f) +{ + if(db) { // Database available + f->seek(0, binio::Set); + CClockRecord *record = (CClockRecord *)db->search(CAdPlugDatabase::CKey(*f)); + if (record && record->type == CAdPlugDatabase::CRecord::ClockSpeed) + return record->clock; + } + + // Otherwise the database is either unavailable, or there's no entry for this file + if (fp.extension(filename, ".imf")) return 560.0f; + if (fp.extension(filename, ".wlf")) return 700.0f; + return 700.0f; // default speed for unknown files that aren't .IMF or .WLF +} diff --git a/plugins/adplug/adplug/imf.h b/plugins/adplug/adplug/imf.h new file mode 100644 index 00000000..4c7d0daf --- /dev/null +++ b/plugins/adplug/adplug/imf.h @@ -0,0 +1,68 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * imf.h - IMF Player by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_IMFPLAYER +#define H_ADPLUG_IMFPLAYER + +#include "player.h" + +class CimfPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CimfPlayer(Copl *newopl) + : CPlayer(newopl), footer(0), data(0) + { } + ~CimfPlayer() + { if(data) delete [] data; if(footer) delete [] footer; }; + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh() + { return timer; }; + + std::string gettype() + { return std::string("IMF File Format"); } + std::string gettitle(); + std::string getauthor() + { return author_name; } + std::string getdesc(); + +protected: + unsigned long pos, size; + unsigned short del; + bool songend; + float rate, timer; + char *footer; + std::string track_name, game_name, author_name, remarks; + + struct Sdata { + unsigned char reg, val; + unsigned short time; + } *data; + +private: + float getrate(const std::string &filename, const CFileProvider &fp, binistream *f); +}; + +#endif diff --git a/plugins/adplug/adplug/kemuopl.h b/plugins/adplug/adplug/kemuopl.h new file mode 100644 index 00000000..d2ca6e28 --- /dev/null +++ b/plugins/adplug/adplug/kemuopl.h @@ -0,0 +1,61 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * kemuopl.h - Emulated OPL using Ken Silverman's emulator, by Simon Peter + * <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_KEMUOPL +#define H_ADPLUG_KEMUOPL + +#include "opl.h" +extern "C" { +#include "adlibemu.h" +} + +class CKemuopl: public Copl +{ +public: + CKemuopl(int rate, bool bit16, bool usestereo) + : use16bit(bit16), stereo(usestereo) + { + adlibinit(rate, usestereo ? 2 : 1, bit16 ? 2 : 1); + currType = TYPE_OPL2; + }; + + void update(short *buf, int samples) + { + if(use16bit) samples *= 2; + if(stereo) samples *= 2; + adlibgetsample(buf, samples); + } + + // template methods + void write(int reg, int val) + { + if(currChip == 0) + adlib0(reg, val); + }; + + void init() {}; + +private: + bool use16bit,stereo; +}; + +#endif diff --git a/plugins/adplug/adplug/ksm.cpp b/plugins/adplug/adplug/ksm.cpp new file mode 100644 index 00000000..c12f3d8f --- /dev/null +++ b/plugins/adplug/adplug/ksm.cpp @@ -0,0 +1,336 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ksm.cpp - KSM Player for AdPlug by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> + +#include "ksm.h" +#include "debug.h" + +const unsigned int CksmPlayer::adlibfreq[63] = { + 0, + 2390,2411,2434,2456,2480,2506,2533,2562,2592,2625,2659,2695, + 3414,3435,3458,3480,3504,3530,3557,3586,3616,3649,3683,3719, + 4438,4459,4482,4504,4528,4554,4581,4610,4640,4673,4707,4743, + 5462,5483,5506,5528,5552,5578,5605,5634,5664,5697,5731,5767, + 6486,6507,6530,6552,6576,6602,6629,6658,6688,6721,6755,6791, + 7510}; + +/*** public methods **************************************/ + +CPlayer *CksmPlayer::factory(Copl *newopl) +{ + return new CksmPlayer(newopl); +} + +bool CksmPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f; + int i; + char *fn = new char[filename.length() + 9]; + + // file validation section + if(!fp.extension(filename, ".ksm")) { + AdPlug_LogWrite("CksmPlayer::load(,\"%s\"): File doesn't have '.ksm' " + "extension! Rejected!\n", filename.c_str()); + return false; + } + AdPlug_LogWrite("*** CksmPlayer::load(,\"%s\") ***\n", filename.c_str()); + + // Load instruments from 'insts.dat' + strcpy(fn, filename.c_str()); + for(i = strlen(fn) - 1; i >= 0; i--) + if(fn[i] == '/' || fn[i] == '\\') + break; + strcpy(fn + i + 1, "insts.dat"); + AdPlug_LogWrite("Instruments file: \"%s\"\n", fn); + f = fp.open(fn); + delete [] fn; + if(!f) { + AdPlug_LogWrite("Couldn't open instruments file! Aborting!\n"); + AdPlug_LogWrite("--- CksmPlayer::load ---\n"); + return false; + } + loadinsts(f); + fp.close(f); + + f = fp.open(filename); if(!f) return false; + for(i = 0; i < 16; i++) trinst[i] = f->readInt(1); + for(i = 0; i < 16; i++) trquant[i] = f->readInt(1); + for(i = 0; i < 16; i++) trchan[i] = f->readInt(1); + f->ignore(16); + for(i = 0; i < 16; i++) trvol[i] = f->readInt(1); + numnotes = f->readInt(2); + note = new unsigned long [numnotes]; + for(i = 0; i < numnotes; i++) note[i] = f->readInt(4); + fp.close(f); + + if(!trchan[11]) { + drumstat = 0; + numchans = 9; + } else { + drumstat = 32; + numchans = 6; + } + + rewind(0); + AdPlug_LogWrite("--- CksmPlayer::load ---\n"); + return true; +} + +bool CksmPlayer::update() +{ + int quanter,chan,drumnum,freq,track,volevel,volval; + unsigned int i,j,bufnum; + unsigned long temp,templong; + + count++; + if (count >= countstop) + { + bufnum = 0; + while (count >= countstop) + { + templong = note[nownote]; + track = (int)((templong>>8)&15); + if ((templong&192) == 0) + { + i = 0; + + while ((i < numchans) && + ((chanfreq[i] != (templong&63)) || + (chantrack[i] != ((templong>>8)&15)))) + i++; + if (i < numchans) + { + databuf[bufnum] = (char)0; bufnum++; + databuf[bufnum] = (unsigned char)(0xb0+i); bufnum++; + databuf[bufnum] = (unsigned char)((adlibfreq[templong&63]>>8)&223); bufnum++; + chanfreq[i] = 0; + chanage[i] = 0; + } + } + else + { + volevel = trvol[track]; + if ((templong&192) == 128) + { + volevel -= 4; + if (volevel < 0) + volevel = 0; + } + if ((templong&192) == 192) + { + volevel += 4; + if (volevel > 63) + volevel = 63; + } + if (track < 11) + { + temp = 0; + i = numchans; + for(j=0;j<numchans;j++) + if ((countstop - chanage[j] >= temp) && (chantrack[j] == track)) + { + temp = countstop - chanage[j]; + i = j; + } + if (i < numchans) + { + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0xb0+i); bufnum++; + databuf[bufnum] = (unsigned char)0; bufnum++; + volval = (inst[trinst[track]][1]&192)+(volevel^63); + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0x40+op_table[i]+3); bufnum++; + databuf[bufnum] = (unsigned char)volval; bufnum++; + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0xa0+i); bufnum++; + databuf[bufnum] = (unsigned char)(adlibfreq[templong&63]&255); bufnum++; + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0xb0+i); bufnum++; + databuf[bufnum] = (unsigned char)((adlibfreq[templong&63]>>8)|32); bufnum++; + chanfreq[i] = templong&63; + chanage[i] = countstop; + } + } + else if ((drumstat&32) > 0) + { + freq = adlibfreq[templong&63]; + switch(track) + { + case 11: drumnum = 16; chan = 6; freq -= 2048; break; + case 12: drumnum = 8; chan = 7; freq -= 2048; break; + case 13: drumnum = 4; chan = 8; break; + case 14: drumnum = 2; chan = 8; break; + case 15: drumnum = 1; chan = 7; freq -= 2048; break; + } + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0xa0+chan); bufnum++; + databuf[bufnum] = (unsigned char)(freq&255); bufnum++; + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0xb0+chan); bufnum++; + databuf[bufnum] = (unsigned char)((freq>>8)&223); bufnum++; + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0xbd); bufnum++; + databuf[bufnum] = (unsigned char)(drumstat&(255-drumnum)); bufnum++; + drumstat |= drumnum; + if ((track == 11) || (track == 12) || (track == 14)) + { + volval = (inst[trinst[track]][1]&192)+(volevel^63); + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0x40+op_table[chan]+3); bufnum++; + databuf[bufnum] = (unsigned char)(volval); bufnum++; + } + else + { + volval = (inst[trinst[track]][6]&192)+(volevel^63); + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0x40+op_table[chan]); bufnum++; + databuf[bufnum] = (unsigned char)(volval); bufnum++; + } + databuf[bufnum] = (char)0, bufnum++; + databuf[bufnum] = (unsigned char)(0xbd); bufnum++; + databuf[bufnum] = (unsigned char)(drumstat); bufnum++; + } + } + nownote++; + if (nownote >= numnotes) { + nownote = 0; + songend = true; + } + templong = note[nownote]; + if (nownote == 0) + count = (templong>>12)-1; + quanter = (240/trquant[(templong>>8)&15]); + countstop = (((templong>>12)+(quanter>>1)) / quanter) * quanter; + } + for(i=0;i<bufnum;i+=3) + opl->write(databuf[i+1],databuf[i+2]); + } + return !songend; +} + +void CksmPlayer::rewind(int subsong) +{ + unsigned int i,j,k; + unsigned char instbuf[11]; + unsigned long templong; + + songend = false; + opl->init(); opl->write(1,32); opl->write(4,0); opl->write(8,0); opl->write(0xbd,drumstat); + + if (trchan[11] == 1) { + for(i=0;i<11;i++) + instbuf[i] = inst[trinst[11]][i]; + instbuf[1] = ((instbuf[1]&192)|(trvol[11])^63); + setinst(6,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]); + for(i=0;i<5;i++) + instbuf[i] = inst[trinst[12]][i]; + for(i=5;i<11;i++) + instbuf[i] = inst[trinst[15]][i]; + instbuf[1] = ((instbuf[1]&192)|(trvol[12])^63); + instbuf[6] = ((instbuf[6]&192)|(trvol[15])^63); + setinst(7,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]); + for(i=0;i<5;i++) + instbuf[i] = inst[trinst[14]][i]; + for(i=5;i<11;i++) + instbuf[i] = inst[trinst[13]][i]; + instbuf[1] = ((instbuf[1]&192)|(trvol[14])^63); + instbuf[6] = ((instbuf[6]&192)|(trvol[13])^63); + setinst(8,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]); + } + + for(i=0;i<numchans;i++) + { + chantrack[i] = 0; + chanage[i] = 0; + } + j = 0; + for(i=0;i<16;i++) + if ((trchan[i] > 0) && (j < numchans)) + { + k = trchan[i]; + while ((j < numchans) && (k > 0)) + { + chantrack[j] = i; + k--; + j++; + } + } + for(i=0;i<numchans;i++) + { + for(j=0;j<11;j++) + instbuf[j] = inst[trinst[chantrack[i]]][j]; + instbuf[1] = ((instbuf[1]&192)|(63-trvol[chantrack[i]])); + setinst(i,instbuf[0],instbuf[1],instbuf[2],instbuf[3],instbuf[4],instbuf[5],instbuf[6],instbuf[7],instbuf[8],instbuf[9],instbuf[10]); + chanfreq[i] = 0; + } + k = 0; + templong = *note; + count = (templong>>12)-1; + countstop = (templong>>12)-1; + nownote = 0; +} + +std::string CksmPlayer::getinstrument(unsigned int n) +{ + if(trchan[n]) + return std::string(instname[trinst[n]]); + else + return std::string(); +} + +/*** private methods *************************************/ + +void CksmPlayer::loadinsts(binistream *f) +{ + int i, j; + + for(i = 0; i < 256; i++) { + f->readString(instname[i], 20); + for(j = 0; j < 11; j++) inst[i][j] = f->readInt(1); + f->ignore(2); + } +} + +void CksmPlayer::setinst(int chan, + unsigned char v0,unsigned char v1,unsigned char v2, + unsigned char v3,unsigned char v4,unsigned char v5, + unsigned char v6,unsigned char v7,unsigned char v8, + unsigned char v9,unsigned char v10) +{ + int offs; + + opl->write(0xa0+chan,0); + opl->write(0xb0+chan,0); + opl->write(0xc0+chan,v10); + offs = op_table[chan]; + opl->write(0x20+offs,v5); + opl->write(0x40+offs,v6); + opl->write(0x60+offs,v7); + opl->write(0x80+offs,v8); + opl->write(0xe0+offs,v9); + offs+=3; + opl->write(0x20+offs,v0); + opl->write(0x40+offs,v1); + opl->write(0x60+offs,v2); + opl->write(0x80+offs,v3); + opl->write(0xe0+offs,v4); +} diff --git a/plugins/adplug/adplug/ksm.h b/plugins/adplug/adplug/ksm.h new file mode 100644 index 00000000..98d643ea --- /dev/null +++ b/plugins/adplug/adplug/ksm.h @@ -0,0 +1,62 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * ksm.h - KSM Player for AdPlug by Simon Peter <dn.tlp@gmx.net> + */ + +#include "player.h" + +class CksmPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CksmPlayer(Copl *newopl) + : CPlayer(newopl), note(0) + { }; + ~CksmPlayer() + { if(note) delete [] note; }; + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh() + { return 240.0f; }; + + std::string gettype() + { return std::string("Ken Silverman's Music Format"); }; + unsigned int getinstruments() + { return 16; }; + std::string getinstrument(unsigned int n); + +private: + static const unsigned int adlibfreq[63]; + + unsigned long count,countstop,chanage[18],*note; + unsigned short numnotes; + unsigned int nownote,numchans,drumstat; + unsigned char trinst[16],trquant[16],trchan[16],trvol[16],inst[256][11],databuf[2048],chanfreq[18],chantrack[18]; + char instname[256][20]; + + bool songend; + + void loadinsts(binistream *f); + void setinst(int chan,unsigned char v0,unsigned char v1,unsigned char v2,unsigned char v3, + unsigned char v4,unsigned char v5,unsigned char v6,unsigned char v7, + unsigned char v8,unsigned char v9,unsigned char v10); +}; diff --git a/plugins/adplug/adplug/lds.cpp b/plugins/adplug/adplug/lds.cpp new file mode 100644 index 00000000..fb70a067 --- /dev/null +++ b/plugins/adplug/adplug/lds.cpp @@ -0,0 +1,676 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * lds.cpp - LOUDNESS Player by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> + +#include "lds.h" +#include "debug.h" + +// Note frequency table (16 notes / octave) +const unsigned short CldsPlayer::frequency[] = { + 343, 344, 345, 347, 348, 349, 350, 352, 353, 354, 356, 357, 358, + 359, 361, 362, 363, 365, 366, 367, 369, 370, 371, 373, 374, 375, + 377, 378, 379, 381, 382, 384, 385, 386, 388, 389, 391, 392, 393, + 395, 396, 398, 399, 401, 402, 403, 405, 406, 408, 409, 411, 412, + 414, 415, 417, 418, 420, 421, 423, 424, 426, 427, 429, 430, 432, + 434, 435, 437, 438, 440, 442, 443, 445, 446, 448, 450, 451, 453, + 454, 456, 458, 459, 461, 463, 464, 466, 468, 469, 471, 473, 475, + 476, 478, 480, 481, 483, 485, 487, 488, 490, 492, 494, 496, 497, + 499, 501, 503, 505, 506, 508, 510, 512, 514, 516, 518, 519, 521, + 523, 525, 527, 529, 531, 533, 535, 537, 538, 540, 542, 544, 546, + 548, 550, 552, 554, 556, 558, 560, 562, 564, 566, 568, 571, 573, + 575, 577, 579, 581, 583, 585, 587, 589, 591, 594, 596, 598, 600, + 602, 604, 607, 609, 611, 613, 615, 618, 620, 622, 624, 627, 629, + 631, 633, 636, 638, 640, 643, 645, 647, 650, 652, 654, 657, 659, + 662, 664, 666, 669, 671, 674, 676, 678, 681, 683 +}; + +// Vibrato (sine) table +const unsigned char CldsPlayer::vibtab[] = { + 0, 13, 25, 37, 50, 62, 74, 86, 98, 109, 120, 131, 142, 152, 162, + 171, 180, 189, 197, 205, 212, 219, 225, 231, 236, 240, 244, 247, + 250, 252, 254, 255, 255, 255, 254, 252, 250, 247, 244, 240, 236, + 231, 225, 219, 212, 205, 197, 189, 180, 171, 162, 152, 142, 131, + 120, 109, 98, 86, 74, 62, 50, 37, 25, 13 +}; + +// Tremolo (sine * sine) table +const unsigned char CldsPlayer::tremtab[] = { + 0, 0, 1, 1, 2, 4, 5, 7, 10, 12, 15, 18, 21, 25, 29, 33, 37, 42, 47, + 52, 57, 62, 67, 73, 79, 85, 90, 97, 103, 109, 115, 121, 128, 134, + 140, 146, 152, 158, 165, 170, 176, 182, 188, 193, 198, 203, 208, + 213, 218, 222, 226, 230, 234, 237, 240, 243, 245, 248, 250, 251, + 253, 254, 254, 255, 255, 255, 254, 254, 253, 251, 250, 248, 245, + 243, 240, 237, 234, 230, 226, 222, 218, 213, 208, 203, 198, 193, + 188, 182, 176, 170, 165, 158, 152, 146, 140, 134, 127, 121, 115, + 109, 103, 97, 90, 85, 79, 73, 67, 62, 57, 52, 47, 42, 37, 33, 29, + 25, 21, 18, 15, 12, 10, 7, 5, 4, 2, 1, 1, 0 +}; + +// 'maxsound' is maximum number of patches (instruments) +// 'maxpos' is maximum number of entries in position list (orderlist) +const unsigned short CldsPlayer::maxsound = 0x3f, CldsPlayer::maxpos = 0xff; + +/*** public methods *************************************/ + +CldsPlayer::CldsPlayer(Copl *newopl) + : CPlayer(newopl), soundbank(0), positions(0), patterns(0) +{ +} + +CldsPlayer::~CldsPlayer() +{ + if(soundbank) delete [] soundbank; + if(positions) delete [] positions; + if(patterns) delete [] patterns; +} + +bool CldsPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f; + unsigned int i, j; + SoundBank *sb; + + // file validation section (actually just an extension check) + if(!fp.extension(filename, ".lds")) return false; + f = fp.open(filename); if(!f) return false; + + // file load section (header) + mode = f->readInt(1); + if(mode > 2) { fp.close(f); return false; } + speed = f->readInt(2); + tempo = f->readInt(1); + pattlen = f->readInt(1); + for(i = 0; i < 9; i++) chandelay[i] = f->readInt(1); + regbd = f->readInt(1); + + // load patches + numpatch = f->readInt(2); + soundbank = new SoundBank[numpatch]; + for(i = 0; i < numpatch; i++) { + sb = &soundbank[i]; + sb->mod_misc = f->readInt(1); sb->mod_vol = f->readInt(1); + sb->mod_ad = f->readInt(1); sb->mod_sr = f->readInt(1); + sb->mod_wave = f->readInt(1); sb->car_misc = f->readInt(1); + sb->car_vol = f->readInt(1); sb->car_ad = f->readInt(1); + sb->car_sr = f->readInt(1); sb->car_wave = f->readInt(1); + sb->feedback = f->readInt(1); sb->keyoff = f->readInt(1); + sb->portamento = f->readInt(1); sb->glide = f->readInt(1); + sb->finetune = f->readInt(1); sb->vibrato = f->readInt(1); + sb->vibdelay = f->readInt(1); sb->mod_trem = f->readInt(1); + sb->car_trem = f->readInt(1); sb->tremwait = f->readInt(1); + sb->arpeggio = f->readInt(1); + for(j = 0; j < 12; j++) sb->arp_tab[j] = f->readInt(1); + sb->start = f->readInt(2); sb->size = f->readInt(2); + sb->fms = f->readInt(1); sb->transp = f->readInt(2); + sb->midinst = f->readInt(1); sb->midvelo = f->readInt(1); + sb->midkey = f->readInt(1); sb->midtrans = f->readInt(1); + sb->middum1 = f->readInt(1); sb->middum2 = f->readInt(1); + } + + // load positions + numposi = f->readInt(2); + positions = new Position[9 * numposi]; + for(i = 0; i < numposi; i++) + for(j = 0; j < 9; j++) { + /* + * patnum is a pointer inside the pattern space, but patterns are 16bit + * word fields anyway, so it ought to be an even number (hopefully) and + * we can just divide it by 2 to get our array index of 16bit words. + */ + positions[i * 9 + j].patnum = f->readInt(2) / 2; + positions[i * 9 + j].transpose = f->readInt(1); + } + + AdPlug_LogWrite("CldsPlayer::load(\"%s\",fp): loading LOUDNESS file: mode = " + "%d, pattlen = %d, numpatch = %d, numposi = %d\n", + filename.c_str(), mode, pattlen, numpatch, numposi); + + // load patterns + f->ignore(2); // ignore # of digital sounds (not played by this player) + patterns = new unsigned short[(fp.filesize(f) - f->pos()) / 2 + 1]; + for(i = 0; !f->eof(); i++) + patterns[i] = f->readInt(2); + + fp.close(f); + rewind(0); + return true; +} + +bool CldsPlayer::update() +{ + unsigned short comword, freq, octave, chan, tune, wibc, tremc, arpreg; + bool vbreak; + unsigned char level, regnum, comhi, comlo; + int i; + Channel *c; + + if(!playing) return false; + + // handle fading + if(fadeonoff) + if(fadeonoff <= 128) { + if(allvolume > fadeonoff || allvolume == 0) + allvolume -= fadeonoff; + else { + allvolume = 1; + fadeonoff = 0; + if(hardfade != 0) { + playing = false; + hardfade = 0; + for(i = 0; i < 9; i++) + channel[i].keycount = 1; + } + } + } else + if(((allvolume + (0x100 - fadeonoff)) & 0xff) <= mainvolume) + allvolume += 0x100 - fadeonoff; + else { + allvolume = mainvolume; + fadeonoff = 0; + } + + // handle channel delay + for(chan = 0; chan < 9; chan++) { + c = &channel[chan]; + if(c->chancheat.chandelay) + if(!(--c->chancheat.chandelay)) + playsound(c->chancheat.sound, chan, c->chancheat.high); + } + + // handle notes + if(!tempo_now) { + vbreak = false; + for(chan = 0; chan < 9; chan++) { + c = &channel[chan]; + if(!c->packwait) { + unsigned short patnum = positions[posplay * 9 + chan].patnum; + unsigned char transpose = positions[posplay * 9 + chan].transpose; + + comword = patterns[patnum + c->packpos]; + comhi = comword >> 8; comlo = comword & 0xff; + if(comword) + if(comhi == 0x80) + c->packwait = comlo; + else + if(comhi >= 0x80) { + switch(comhi) { + case 0xff: + c->volcar = (((c->volcar & 0x3f) * comlo) >> 6) & 0x3f; + if(fmchip[0xc0 + chan] & 1) + c->volmod = (((c->volmod & 0x3f) * comlo) >> 6) & 0x3f; + break; + case 0xfe: + tempo = comword & 0x3f; + break; + case 0xfd: + c->nextvol = comlo; + break; + case 0xfc: + playing = false; + // in real player there's also full keyoff here, but we don't need it + break; + case 0xfb: + c->keycount = 1; + break; + case 0xfa: + vbreak = true; + jumppos = (posplay + 1) & maxpos; + break; + case 0xf9: + vbreak = true; + jumppos = comlo & maxpos; + jumping = 1; + if(jumppos < posplay) songlooped = true; + break; + case 0xf8: + c->lasttune = 0; + break; + case 0xf7: + c->vibwait = 0; + // PASCAL: c->vibspeed = ((comlo >> 4) & 15) + 2; + c->vibspeed = (comlo >> 4) + 2; + c->vibrate = (comlo & 15) + 1; + break; + case 0xf6: + c->glideto = comlo; + break; + case 0xf5: + c->finetune = comlo; + break; + case 0xf4: + if(!hardfade) { + allvolume = mainvolume = comlo; + fadeonoff = 0; + } + break; + case 0xf3: + if(!hardfade) fadeonoff = comlo; + break; + case 0xf2: + c->trmstay = comlo; + break; + case 0xf1: // panorama + case 0xf0: // progch + // MIDI commands (unhandled) + AdPlug_LogWrite("CldsPlayer(): not handling MIDI command 0x%x, " + "value = 0x%x\n", comhi); + break; + default: + if(comhi < 0xa0) + c->glideto = comhi & 0x1f; + else + AdPlug_LogWrite("CldsPlayer(): unknown command 0x%x encountered!" + " value = 0x%x\n", comhi, comlo); + break; + } + } else { + unsigned char sound; + unsigned short high; + signed char transp = transpose & 127; + + /* + * Originally, in assembler code, the player first shifted + * logically left the transpose byte by 1 and then shifted + * arithmetically right the same byte to achieve the final, + * signed transpose value. Since we can't do arithmetic shifts + * in C, we just duplicate the 7th bit into the 8th one and + * discard the 8th one completely. + */ + + if(transpose & 64) transp |= 128; + + if(transpose & 128) { + sound = (comlo + transp) & maxsound; + high = comhi << 4; + } else { + sound = comlo & maxsound; + high = (comhi + transp) << 4; + } + + /* + PASCAL: + sound = comlo & maxsound; + high = (comhi + (((transpose + 0x24) & 0xff) - 0x24)) << 4; + */ + + if(!chandelay[chan]) + playsound(sound, chan, high); + else { + c->chancheat.chandelay = chandelay[chan]; + c->chancheat.sound = sound; + c->chancheat.high = high; + } + } + + c->packpos++; + } else + c->packwait--; + } + + tempo_now = tempo; + /* + The continue table is updated here, but this is only used in the + original player, which can be paused in the middle of a song and then + unpaused. Since AdPlug does all this for us automatically, we don't + have a continue table here. The continue table update code is noted + here for reference only. + + if(!pattplay) { + conttab[speed & maxcont].position = posplay & 0xff; + conttab[speed & maxcont].tempo = tempo; + } + */ + pattplay++; + if(vbreak) { + pattplay = 0; + for(i = 0; i < 9; i++) channel[i].packpos = channel[i].packwait = 0; + posplay = jumppos; + } else + if(pattplay >= pattlen) { + pattplay = 0; + for(i = 0; i < 9; i++) channel[i].packpos = channel[i].packwait = 0; + posplay = (posplay + 1) & maxpos; + } + } else + tempo_now--; + + // make effects + for(chan = 0; chan < 9; chan++) { + c = &channel[chan]; + regnum = op_table[chan]; + if(c->keycount > 0) { + if(c->keycount == 1) + setregs_adv(0xb0 + chan, 0xdf, 0); + c->keycount--; + } + + // arpeggio + if(c->arp_size == 0) + arpreg = 0; + else { + arpreg = c->arp_tab[c->arp_pos] << 4; + if(arpreg == 0x800) { + if(c->arp_pos > 0) c->arp_tab[0] = c->arp_tab[c->arp_pos - 1]; + c->arp_size = 1; c->arp_pos = 0; + arpreg = c->arp_tab[0] << 4; + } + + if(c->arp_count == c->arp_speed) { + c->arp_pos++; + if(c->arp_pos >= c->arp_size) c->arp_pos = 0; + c->arp_count = 0; + } else + c->arp_count++; + } + + // glide & portamento + if(c->lasttune && (c->lasttune != c->gototune)) { + if(c->lasttune > c->gototune) { + if(c->lasttune - c->gototune < c->portspeed) + c->lasttune = c->gototune; + else + c->lasttune -= c->portspeed; + } else { + if(c->gototune - c->lasttune < c->portspeed) + c->lasttune = c->gototune; + else + c->lasttune += c->portspeed; + } + + if(arpreg >= 0x800) + arpreg = c->lasttune - (arpreg ^ 0xff0) - 16; + else + arpreg += c->lasttune; + + freq = frequency[arpreg % (12 * 16)]; + octave = arpreg / (12 * 16) - 1; + setregs(0xa0 + chan, freq & 0xff); + setregs_adv(0xb0 + chan, 0x20, ((octave << 2) + (freq >> 8)) & 0xdf); + } else { + // vibrato + if(!c->vibwait) { + if(c->vibrate) { + wibc = vibtab[c->vibcount & 0x3f] * c->vibrate; + + if((c->vibcount & 0x40) == 0) + tune = c->lasttune + (wibc >> 8); + else + tune = c->lasttune - (wibc >> 8); + + if(arpreg >= 0x800) + tune = tune - (arpreg ^ 0xff0) - 16; + else + tune += arpreg; + + freq = frequency[tune % (12 * 16)]; + octave = tune / (12 * 16) - 1; + setregs(0xa0 + chan, freq & 0xff); + setregs_adv(0xb0 + chan, 0x20, ((octave << 2) + (freq >> 8)) & 0xdf); + c->vibcount += c->vibspeed; + } else + if(c->arp_size != 0) { // no vibrato, just arpeggio + if(arpreg >= 0x800) + tune = c->lasttune - (arpreg ^ 0xff0) - 16; + else + tune = c->lasttune + arpreg; + + freq = frequency[tune % (12 * 16)]; + octave = tune / (12 * 16) - 1; + setregs(0xa0 + chan, freq & 0xff); + setregs_adv(0xb0 + chan, 0x20, ((octave << 2) + (freq >> 8)) & 0xdf); + } + } else { // no vibrato, just arpeggio + c->vibwait--; + + if(c->arp_size != 0) { + if(arpreg >= 0x800) + tune = c->lasttune - (arpreg ^ 0xff0) - 16; + else + tune = c->lasttune + arpreg; + + freq = frequency[tune % (12 * 16)]; + octave = tune / (12 * 16) - 1; + setregs(0xa0 + chan, freq & 0xff); + setregs_adv(0xb0 + chan, 0x20, ((octave << 2) + (freq >> 8)) & 0xdf); + } + } + } + + // tremolo (modulator) + if(!c->trmwait) { + if(c->trmrate) { + tremc = tremtab[c->trmcount & 0x7f] * c->trmrate; + if((tremc >> 8) <= (c->volmod & 0x3f)) + level = (c->volmod & 0x3f) - (tremc >> 8); + else + level = 0; + + if(allvolume != 0 && (fmchip[0xc0 + chan] & 1)) + setregs_adv(0x40 + regnum, 0xc0, ((level * allvolume) >> 8) ^ 0x3f); + else + setregs_adv(0x40 + regnum, 0xc0, level ^ 0x3f); + + c->trmcount += c->trmspeed; + } else + if(allvolume != 0 && (fmchip[0xc0 + chan] & 1)) + setregs_adv(0x40 + regnum, 0xc0, ((((c->volmod & 0x3f) * allvolume) >> 8) ^ 0x3f) & 0x3f); + else + setregs_adv(0x40 + regnum, 0xc0, (c->volmod ^ 0x3f) & 0x3f); + } else { + c->trmwait--; + if(allvolume != 0 && (fmchip[0xc0 + chan] & 1)) + setregs_adv(0x40 + regnum, 0xc0, ((((c->volmod & 0x3f) * allvolume) >> 8) ^ 0x3f) & 0x3f); + } + + // tremolo (carrier) + if(!c->trcwait) { + if(c->trcrate) { + tremc = tremtab[c->trccount & 0x7f] * c->trcrate; + if((tremc >> 8) <= (c->volcar & 0x3f)) + level = (c->volcar & 0x3f) - (tremc >> 8); + else + level = 0; + + if(allvolume != 0) + setregs_adv(0x43 + regnum, 0xc0, ((level * allvolume) >> 8) ^ 0x3f); + else + setregs_adv(0x43 + regnum, 0xc0, level ^ 0x3f); + c->trccount += c->trcspeed; + } else + if(allvolume != 0) + setregs_adv(0x43 + regnum, 0xc0, ((((c->volcar & 0x3f) * allvolume) >> 8) ^ 0x3f) & 0x3f); + else + setregs_adv(0x43 + regnum, 0xc0, (c->volcar ^ 0x3f) & 0x3f); + } else { + c->trcwait--; + if(allvolume != 0) + setregs_adv(0x43 + regnum, 0xc0, ((((c->volcar & 0x3f) * allvolume) >> 8) ^ 0x3f) & 0x3f); + } + } + + return (!playing || songlooped) ? false : true; +} + +void CldsPlayer::rewind(int subsong) +{ + int i; + + // init all with 0 + tempo_now = 3; playing = true; songlooped = false; + jumping = fadeonoff = allvolume = hardfade = pattplay = posplay = jumppos = + mainvolume = 0; + memset(channel, 0, sizeof(channel)); + memset(fmchip, 0, sizeof(fmchip)); + + // OPL2 init + opl->init(); // Reset OPL chip + opl->write(1, 0x20); + opl->write(8, 0); + opl->write(0xbd, regbd); + + for(i = 0; i < 9; i++) { + opl->write(0x20 + op_table[i], 0); + opl->write(0x23 + op_table[i], 0); + opl->write(0x40 + op_table[i], 0x3f); + opl->write(0x43 + op_table[i], 0x3f); + opl->write(0x60 + op_table[i], 0xff); + opl->write(0x63 + op_table[i], 0xff); + opl->write(0x80 + op_table[i], 0xff); + opl->write(0x83 + op_table[i], 0xff); + opl->write(0xe0 + op_table[i], 0); + opl->write(0xe3 + op_table[i], 0); + opl->write(0xa0 + i, 0); + opl->write(0xb0 + i, 0); + opl->write(0xc0 + i, 0); + } +} + +/*** private methods *************************************/ + +void CldsPlayer::playsound(int inst_number, int channel_number, int tunehigh) +{ + Channel *c = &channel[channel_number]; // current channel + SoundBank *i = &soundbank[inst_number]; // current instrument + unsigned int regnum = op_table[channel_number]; // channel's OPL2 register + unsigned char volcalc, octave; + unsigned short freq; + + // set fine tune + tunehigh += ((i->finetune + c->finetune + 0x80) & 0xff) - 0x80; + + // arpeggio handling + if(!i->arpeggio) { + unsigned short arpcalc = i->arp_tab[0] << 4; + + if(arpcalc > 0x800) + tunehigh = tunehigh - (arpcalc ^ 0xff0) - 16; + else + tunehigh += arpcalc; + } + + // glide handling + if(c->glideto != 0) { + c->gototune = tunehigh; + c->portspeed = c->glideto; + c->glideto = c->finetune = 0; + return; + } + + // set modulator registers + setregs(0x20 + regnum, i->mod_misc); + volcalc = i->mod_vol; + if(!c->nextvol || !(i->feedback & 1)) + c->volmod = volcalc; + else + c->volmod = (volcalc & 0xc0) | ((((volcalc & 0x3f) * c->nextvol) >> 6)); + + if((i->feedback & 1) == 1 && allvolume != 0) + setregs(0x40 + regnum, ((c->volmod & 0xc0) | (((c->volmod & 0x3f) * allvolume) >> 8)) ^ 0x3f); + else + setregs(0x40 + regnum, c->volmod ^ 0x3f); + setregs(0x60 + regnum, i->mod_ad); + setregs(0x80 + regnum, i->mod_sr); + setregs(0xe0 + regnum, i->mod_wave); + + // Set carrier registers + setregs(0x23 + regnum, i->car_misc); + volcalc = i->car_vol; + if(!c->nextvol) + c->volcar = volcalc; + else + c->volcar = (volcalc & 0xc0) | ((((volcalc & 0x3f) * c->nextvol) >> 6)); + + if(allvolume) + setregs(0x43 + regnum, ((c->volcar & 0xc0) | (((c->volcar & 0x3f) * allvolume) >> 8)) ^ 0x3f); + else + setregs(0x43 + regnum, c->volcar ^ 0x3f); + setregs(0x63 + regnum, i->car_ad); + setregs(0x83 + regnum, i->car_sr); + setregs(0xe3 + regnum, i->car_wave); + setregs(0xc0 + channel_number, i->feedback); + setregs_adv(0xb0 + channel_number, 0xdf, 0); // key off + + freq = frequency[tunehigh % (12 * 16)]; + octave = tunehigh / (12 * 16) - 1; + if(!i->glide) { + if(!i->portamento || !c->lasttune) { + setregs(0xa0 + channel_number, freq & 0xff); + setregs(0xb0 + channel_number, (octave << 2) + 0x20 + (freq >> 8)); + c->lasttune = c->gototune = tunehigh; + } else { + c->gototune = tunehigh; + c->portspeed = i->portamento; + setregs_adv(0xb0 + channel_number, 0xdf, 0x20); // key on + } + } else { + setregs(0xa0 + channel_number, freq & 0xff); + setregs(0xb0 + channel_number, (octave << 2) + 0x20 + (freq >> 8)); + c->lasttune = tunehigh; + c->gototune = tunehigh + ((i->glide + 0x80) & 0xff) - 0x80; // set destination + c->portspeed = i->portamento; + } + + if(!i->vibrato) + c->vibwait = c->vibspeed = c->vibrate = 0; + else { + c->vibwait = i->vibdelay; + // PASCAL: c->vibspeed = ((i->vibrato >> 4) & 15) + 1; + c->vibspeed = (i->vibrato >> 4) + 2; + c->vibrate = (i->vibrato & 15) + 1; + } + + if(!(c->trmstay & 0xf0)) { + c->trmwait = (i->tremwait & 0xf0) >> 3; + // PASCAL: c->trmspeed = (i->mod_trem >> 4) & 15; + c->trmspeed = i->mod_trem >> 4; + c->trmrate = i->mod_trem & 15; + c->trmcount = 0; + } + + if(!(c->trmstay & 0x0f)) { + c->trcwait = (i->tremwait & 15) << 1; + // PASCAL: c->trcspeed = (i->car_trem >> 4) & 15; + c->trcspeed = i->car_trem >> 4; + c->trcrate = i->car_trem & 15; + c->trccount = 0; + } + + c->arp_size = i->arpeggio & 15; + c->arp_speed = i->arpeggio >> 4; + memcpy(c->arp_tab, i->arp_tab, 12); + c->keycount = i->keyoff; + c->nextvol = c->glideto = c->finetune = c->vibcount = c->arp_pos = c->arp_count = 0; +} + +inline void CldsPlayer::setregs(unsigned char reg, unsigned char val) +{ + if(fmchip[reg] == val) return; + + fmchip[reg] = val; + opl->write(reg, val); +} + +inline void CldsPlayer::setregs_adv(unsigned char reg, unsigned char mask, + unsigned char val) +{ + setregs(reg, (fmchip[reg] & mask) | val); +} diff --git a/plugins/adplug/adplug/lds.h b/plugins/adplug/adplug/lds.h new file mode 100644 index 00000000..ec61ad29 --- /dev/null +++ b/plugins/adplug/adplug/lds.h @@ -0,0 +1,91 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * lds.h - LOUDNESS Player by Simon Peter <dn.tlp@gmx.net> + */ + +#include "player.h" + +class CldsPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl) { return new CldsPlayer(newopl); } + + CldsPlayer(Copl *newopl); + virtual ~CldsPlayer(); + + bool load(const std::string &fn, const CFileProvider &fp); + virtual bool update(); + virtual void rewind(int subsong = -1); + float getrefresh() { return 70.0f; } + + std::string gettype() { return std::string("LOUDNESS Sound System"); } + unsigned int getorders() { return numposi; } + unsigned int getorder() { return posplay; } + unsigned int getrow() { return pattplay; } + unsigned int getspeed() { return speed; } + unsigned int getinstruments() { return numpatch; } + + private: + typedef struct { + unsigned char mod_misc, mod_vol, mod_ad, mod_sr, mod_wave, + car_misc, car_vol, car_ad, car_sr, car_wave, feedback, keyoff, + portamento, glide, finetune, vibrato, vibdelay, mod_trem, car_trem, + tremwait, arpeggio, arp_tab[12]; + unsigned short start, size; + unsigned char fms; + unsigned short transp; + unsigned char midinst, midvelo, midkey, midtrans, middum1, middum2; + } SoundBank; + + typedef struct { + unsigned short gototune, lasttune, packpos; + unsigned char finetune, glideto, portspeed, nextvol, volmod, volcar, + vibwait, vibspeed, vibrate, trmstay, trmwait, trmspeed, trmrate, trmcount, + trcwait, trcspeed, trcrate, trccount, arp_size, arp_speed, keycount, + vibcount, arp_pos, arp_count, packwait, arp_tab[12]; + + struct { + unsigned char chandelay, sound; + unsigned short high; + } chancheat; + } Channel; + + typedef struct { + unsigned short patnum; + unsigned char transpose; + } Position; + + static const unsigned short frequency[]; + static const unsigned char vibtab[], tremtab[]; + static const unsigned short maxsound, maxpos; + + SoundBank *soundbank; + Channel channel[9]; + Position *positions; + unsigned char fmchip[0xff], jumping, fadeonoff, allvolume, hardfade, + tempo_now, pattplay, tempo, regbd, chandelay[9], mode, pattlen; + unsigned short posplay, jumppos, *patterns, speed; + bool playing, songlooped; + unsigned int numpatch, numposi, patterns_size, mainvolume; + + void playsound(int inst_number, int channel_number, int tunehigh); + inline void setregs(unsigned char reg, unsigned char val); + inline void setregs_adv(unsigned char reg, unsigned char mask, + unsigned char val); +}; diff --git a/plugins/adplug/adplug/mad.cpp b/plugins/adplug/adplug/mad.cpp new file mode 100644 index 00000000..d5a60c43 --- /dev/null +++ b/plugins/adplug/adplug/mad.cpp @@ -0,0 +1,127 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + mad.cpp - MAD loader by Riven the Mage <riven@ok.ru> +*/ + +#include <string.h> +#include "mad.h" + +/* -------- Public Methods -------------------------------- */ + +CPlayer *CmadLoader::factory(Copl *newopl) +{ + return new CmadLoader(newopl); +} + +bool CmadLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + const unsigned char conv_inst[10] = { 2,1,10,9,4,3,6,5,8,7 }; + unsigned int i, j, k, t = 0; + + // 'MAD+' - signed ? + char id[4]; f->readString(id, 4); + if (strncmp(id,"MAD+",4)) { fp.close(f); return false; } + + // load instruments + for(i = 0; i < 9; i++) { + f->readString(instruments[i].name, 8); + for(j = 0; j < 12; j++) instruments[i].data[j] = f->readInt(1); + } + + f->ignore(1); + + // data for Protracker + length = f->readInt(1); nop = f->readInt(1); timer = f->readInt(1); + + // init CmodPlayer + realloc_instruments(9); + realloc_order(length); + realloc_patterns(nop,32,9); + init_trackord(); + + // load tracks + for(i = 0; i < nop; i++) + for(k = 0; k < 32; k++) + for(j = 0; j < 9; j++) { + t = i * 9 + j; + + // read event + unsigned char event = f->readInt(1); + + // convert event + if (event < 0x61) + tracks[t][k].note = event; + if (event == 0xFF) // 0xFF: Release note + tracks[t][k].command = 8; + if (event == 0xFE) // 0xFE: Pattern Break + tracks[t][k].command = 13; + } + + // load order + for(i = 0; i < length; i++) order[i] = f->readInt(1) - 1; + + fp.close(f); + + // convert instruments + for(i = 0; i < 9; i++) + for(j = 0; j < 10; j++) + inst[i].data[conv_inst[j]] = instruments[i].data[j]; + + // data for Protracker + restartpos = 0; + initspeed = 1; + + rewind(0); + return true; +} + +void CmadLoader::rewind(int subsong) +{ + CmodPlayer::rewind(subsong); + + // default instruments + for (int i=0;i<9;i++) + { + channel[i].inst = i; + + channel[i].vol1 = 63 - (inst[i].data[10] & 63); + channel[i].vol2 = 63 - (inst[i].data[9] & 63); + } +} + +float CmadLoader::getrefresh() +{ + return (float)timer; +} + +std::string CmadLoader::gettype() +{ + return std::string("Mlat Adlib Tracker"); +} + +std::string CmadLoader::getinstrument(unsigned int n) +{ + return std::string(instruments[n].name,8); +} + +unsigned int CmadLoader::getinstruments() +{ + return 9; +} diff --git a/plugins/adplug/adplug/mad.h b/plugins/adplug/adplug/mad.h new file mode 100644 index 00000000..98d2a445 --- /dev/null +++ b/plugins/adplug/adplug/mad.h @@ -0,0 +1,48 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + mad.h - MAD loader by Riven the Mage <riven@ok.ru> +*/ + +#include "protrack.h" + +class CmadLoader: public CmodPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CmadLoader(Copl *newopl) : CmodPlayer(newopl) { }; + + bool load(const std::string &filename, const CFileProvider &fp); + void rewind(int subsong); + float getrefresh(); + + std::string gettype(); + std::string getinstrument(unsigned int n); + unsigned int getinstruments(); + +private: + + struct mad_instrument + { + char name[8]; + unsigned char data[12]; // last two unused + } instruments[9]; + + unsigned char timer; +}; diff --git a/plugins/adplug/adplug/mid.cpp b/plugins/adplug/adplug/mid.cpp new file mode 100644 index 00000000..e3dd88c5 --- /dev/null +++ b/plugins/adplug/adplug/mid.cpp @@ -0,0 +1,1090 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * MIDI & MIDI-like file player - Last Update: 10/15/2005 + * by Phil Hassey - www.imitationpickles.org + * philhassey@hotmail.com + * + * Can play the following + * .LAA - a raw save of a Lucas Arts Adlib music + * or + * a raw save of a LucasFilm Adlib music + * .MID - a "midi" save of a Lucas Arts Adlib music + * - or general MIDI files + * .CMF - Creative Music Format + * .SCI - the sierra "midi" format. + * Files must be in the form + * xxxNAME.sci + * So that the loader can load the right patch file: + * xxxPATCH.003 (patch.003 must be saved from the + * sierra resource from each game.) + * + * 6/2/2000: v1.0 relased by phil hassey + * Status: LAA is almost perfect + * - some volumes are a bit off (intrument too quiet) + * MID is fine (who wants to listen to MIDI vid adlib anyway) + * CMF is okay (still needs the adlib rythm mode implemented + * for real) + * 6/6/2000: + * Status: SCI: there are two SCI formats, orginal and advanced. + * original: (Found in SCI/EGA Sierra Adventures) + * played almost perfectly, I believe + * there is one mistake in the instrument + * loader that causes some sounds to + * not be quite right. Most sounds are fine. + * advanced: (Found in SCI/VGA Sierra Adventures) + * These are multi-track files. (Thus the + * player had to be modified to work with + * them.) This works fine. + * There are also multiple tunes in each file. + * I think some of them are supposed to be + * played at the same time, but I'm not sure + * when. + * 8/16/2000: + * Status: LAA: now EGA and VGA lucas games work pretty well + * + * 10/15/2005: Changes by Simon Peter + * Added rhythm mode support for CMF format. + * + * Other acknowledgements: + * Allegro - for the midi instruments and the midi volume table + * SCUMM Revisited - for getting the .LAA / .MIDs out of those + * LucasArts files. + * FreeSCI - for some information on the sci music files + * SD - the SCI Decoder (to get all .sci out of the Sierra files) + */ + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <string.h> +#include "mid.h" +#include "mididata.h" + +/*#define TESTING*/ +#ifdef TESTING +#define midiprintf printf +#else +void CmidPlayer::midiprintf(const char *format, ...) + { + } +#endif + +#define LUCAS_STYLE 1 +#define CMF_STYLE 2 +#define MIDI_STYLE 4 +#define SIERRA_STYLE 8 + +// AdLib melodic and rhythm mode defines +#define ADLIB_MELODIC 0 +#define ADLIB_RYTHM 1 + +// File types +#define FILE_LUCAS 1 +#define FILE_MIDI 2 +#define FILE_CMF 3 +#define FILE_SIERRA 4 +#define FILE_ADVSIERRA 5 +#define FILE_OLDLUCAS 6 + +// AdLib standard operator table +const unsigned char CmidPlayer::adlib_opadd[] = {0x00 ,0x01 ,0x02 ,0x08 ,0x09 ,0x0A ,0x10 ,0x11 ,0x12}; + +// dunno +const int CmidPlayer::ops[] = {0x20,0x20,0x40,0x40,0x60,0x60,0x80,0x80,0xe0,0xe0,0xc0}; + +// map CMF drum channels 12 - 15 to corresponding AdLib drum operators +// bass drum (channel 11) not mapped, cause it's handled like a normal instrument +const int CmidPlayer::map_chan[] = { 0x14, 0x12, 0x15, 0x11 }; + +// Standard AdLib frequency table +const int CmidPlayer::fnums[] = { 0x16b,0x181,0x198,0x1b0,0x1ca,0x1e5,0x202,0x220,0x241,0x263,0x287,0x2ae }; + +// Map CMF drum channels 11 - 15 to corresponding AdLib drum channels +const int CmidPlayer::percussion_map[] = { 6, 7, 8, 8, 7 }; + +CPlayer *CmidPlayer::factory(Copl *newopl) +{ + return new CmidPlayer(newopl); +} + +CmidPlayer::CmidPlayer(Copl *newopl) + : CPlayer(newopl), author(&emptystr), title(&emptystr), remarks(&emptystr), + emptystr('\0'), flen(0), data(0) +{ +} + +unsigned char CmidPlayer::datalook(long pos) +{ + if (pos<0 || pos >= flen) return(0); + return(data[pos]); +} + +unsigned long CmidPlayer::getnexti(unsigned long num) +{ + unsigned long v=0; + unsigned long i; + + for (i=0; i<num; i++) + { + v+=(datalook(pos)<<(8*i)); pos++; + } + return(v); +} + +unsigned long CmidPlayer::getnext(unsigned long num) +{ + unsigned long v=0; + unsigned long i; + + for (i=0; i<num; i++) + { + v<<=8; + v+=datalook(pos); pos++; + } + return(v); +} + +unsigned long CmidPlayer::getval() +{ + int v=0; + unsigned char b; + + b=(unsigned char)getnext(1); + v=b&0x7f; + while ((b&0x80) !=0) + { + b=(unsigned char)getnext(1); + v = (v << 7) + (b & 0x7F); + } + return(v); +} + +bool CmidPlayer::load_sierra_ins(const std::string &fname, const CFileProvider &fp) +{ + long i,j,k,l; + unsigned char ins[28]; + char *pfilename; + binistream *f; + + pfilename = (char *)malloc(fname.length()+9); + strcpy(pfilename,fname.c_str()); + j=0; + for(i=strlen(pfilename)-1; i >= 0; i--) + if(pfilename[i] == '/' || pfilename[i] == '\\') { + j = i+1; + break; + } + sprintf(pfilename+j+3,"patch.003"); + + f = fp.open(pfilename); + free(pfilename); + if(!f) return false; + + f->ignore(2); + stins = 0; + for (i=0; i<2; i++) + { + for (k=0; k<48; k++) + { + l=i*48+k; + midiprintf ("\n%2d: ",l); + for (j=0; j<28; j++) + ins[j] = f->readInt(1); + + myinsbank[l][0]= + (ins[9]*0x80) + (ins[10]*0x40) + + (ins[5]*0x20) + (ins[11]*0x10) + + ins[1]; //1=ins5 + myinsbank[l][1]= + (ins[22]*0x80) + (ins[23]*0x40) + + (ins[18]*0x20) + (ins[24]*0x10) + + ins[14]; //1=ins18 + + myinsbank[l][2]=(ins[0]<<6)+ins[8]; + myinsbank[l][3]=(ins[13]<<6)+ins[21]; + + myinsbank[l][4]=(ins[3]<<4)+ins[6]; + myinsbank[l][5]=(ins[16]<<4)+ins[19]; + myinsbank[l][6]=(ins[4]<<4)+ins[7]; + myinsbank[l][7]=(ins[17]<<4)+ins[20]; + + myinsbank[l][8]=ins[26]; + myinsbank[l][9]=ins[27]; + + myinsbank[l][10]=((ins[2]<<1))+(1-(ins[12]&1)); + //(ins[12] ? 0:1)+((ins[2]<<1)); + + for (j=0; j<11; j++) + midiprintf ("%02X ",myinsbank[l][j]); + stins++; + } + f->ignore(2); + } + + fp.close(f); + memcpy(smyinsbank, myinsbank, 128 * 16); + return true; +} + +void CmidPlayer::sierra_next_section() +{ + int i,j; + + for (i=0; i<16; i++) + track[i].on=0; + + midiprintf("\n\nnext adv sierra section:\n"); + + pos=sierra_pos; + i=0;j=0; + while (i!=0xff) + { + getnext(1); + curtrack=j; j++; + track[curtrack].on=1; + track[curtrack].spos = getnext(1); + track[curtrack].spos += (getnext(1) << 8) + 4; //4 best usually +3? not 0,1,2 or 5 +// track[curtrack].spos=getnext(1)+(getnext(1)<<8)+4; // dynamite!: doesn't optimize correctly!! + track[curtrack].tend=flen; //0xFC will kill it + track[curtrack].iwait=0; + track[curtrack].pv=0; + midiprintf ("track %d starts at %lx\n",curtrack,track[curtrack].spos); + + getnext(2); + i=getnext(1); + } + getnext(2); + deltas=0x20; + sierra_pos=pos; + //getch(); + + fwait=0; + doing=1; +} + +bool CmidPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + int good; + unsigned char s[6]; + + f->readString((char *)s, 6); + good=0; + subsongs=0; + switch(s[0]) + { + case 'A': + if (s[1]=='D' && s[2]=='L') good=FILE_LUCAS; + break; + case 'M': + if (s[1]=='T' && s[2]=='h' && s[3]=='d') good=FILE_MIDI; + break; + case 'C': + if (s[1]=='T' && s[2]=='M' && s[3]=='F') good=FILE_CMF; + break; + case 0x84: + if (s[1]==0x00 && load_sierra_ins(filename, fp)) + if (s[2]==0xf0) + good=FILE_ADVSIERRA; + else + good=FILE_SIERRA; + break; + default: + if (s[4]=='A' && s[5]=='D') good=FILE_OLDLUCAS; + break; + } + + if (good!=0) + subsongs=1; + else { + fp.close(f); + return false; + } + + type=good; + f->seek(0); + flen = fp.filesize(f); + data = new unsigned char [flen]; + f->readString((char *)data, flen); + + fp.close(f); + rewind(0); + return true; +} + +void CmidPlayer::midi_write_adlib(unsigned int r, unsigned char v) +{ + opl->write(r,v); + adlib_data[r]=v; +} + +void CmidPlayer::midi_fm_instrument(int voice, unsigned char *inst) +{ + if ((adlib_style&SIERRA_STYLE)!=0) + midi_write_adlib(0xbd,0); //just gotta make sure this happens.. + //'cause who knows when it'll be + //reset otherwise. + + + midi_write_adlib(0x20+adlib_opadd[voice],inst[0]); + midi_write_adlib(0x23+adlib_opadd[voice],inst[1]); + + if ((adlib_style&LUCAS_STYLE)!=0) + { + midi_write_adlib(0x43+adlib_opadd[voice],0x3f); + if ((inst[10] & 1)==0) + midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); + else + midi_write_adlib(0x40+adlib_opadd[voice],0x3f); + } + else + { + if ((adlib_style&SIERRA_STYLE)!=0) + { + midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); + midi_write_adlib(0x43+adlib_opadd[voice],inst[3]); + } + else + { + midi_write_adlib(0x40+adlib_opadd[voice],inst[2]); + if ((inst[10] & 1)==0) + midi_write_adlib(0x43+adlib_opadd[voice],inst[3]); + else + midi_write_adlib(0x43+adlib_opadd[voice],0); + } + } + + midi_write_adlib(0x60+adlib_opadd[voice],inst[4]); + midi_write_adlib(0x63+adlib_opadd[voice],inst[5]); + midi_write_adlib(0x80+adlib_opadd[voice],inst[6]); + midi_write_adlib(0x83+adlib_opadd[voice],inst[7]); + midi_write_adlib(0xe0+adlib_opadd[voice],inst[8]); + midi_write_adlib(0xe3+adlib_opadd[voice],inst[9]); + + midi_write_adlib(0xc0+voice,inst[10]); +} + +void CmidPlayer::midi_fm_percussion(int ch, unsigned char *inst) +{ + int opadd = map_chan[ch - 12]; + + midi_write_adlib(0x20 + opadd, inst[0]); + midi_write_adlib(0x40 + opadd, inst[2]); + midi_write_adlib(0x60 + opadd, inst[4]); + midi_write_adlib(0x80 + opadd, inst[6]); + midi_write_adlib(0xe0 + opadd, inst[8]); + midi_write_adlib(0xc0 + opadd, inst[10]); +} + +void CmidPlayer::midi_fm_volume(int voice, int volume) +{ + int vol; + + if ((adlib_style&SIERRA_STYLE)==0) //sierra likes it loud! + { + vol=volume>>2; + + if ((adlib_style&LUCAS_STYLE)!=0) + { + if ((adlib_data[0xc0+voice]&1)==1) + midi_write_adlib(0x40+adlib_opadd[voice], (unsigned char)((63-vol) | + (adlib_data[0x40+adlib_opadd[voice]]&0xc0))); + midi_write_adlib(0x43+adlib_opadd[voice], (unsigned char)((63-vol) | + (adlib_data[0x43+adlib_opadd[voice]]&0xc0))); + } + else + { + if ((adlib_data[0xc0+voice]&1)==1) + midi_write_adlib(0x40+adlib_opadd[voice], (unsigned char)((63-vol) | + (adlib_data[0x40+adlib_opadd[voice]]&0xc0))); + midi_write_adlib(0x43+adlib_opadd[voice], (unsigned char)((63-vol) | + (adlib_data[0x43+adlib_opadd[voice]]&0xc0))); + } + } +} + +void CmidPlayer::midi_fm_playnote(int voice, int note, int volume) +{ + int freq=fnums[note%12]; + int oct=note/12; + int c; + + midi_fm_volume(voice,volume); + midi_write_adlib(0xa0+voice,(unsigned char)(freq&0xff)); + + c=((freq&0x300) >> 8)+(oct<<2) + (adlib_mode == ADLIB_MELODIC || voice < 6 ? (1<<5) : 0); + midi_write_adlib(0xb0+voice,(unsigned char)c); +} + +void CmidPlayer::midi_fm_endnote(int voice) +{ + //midi_fm_volume(voice,0); + //midi_write_adlib(0xb0+voice,0); + + midi_write_adlib(0xb0+voice,(unsigned char)(adlib_data[0xb0+voice]&(255-32))); +} + +void CmidPlayer::midi_fm_reset() +{ + int i; + + opl->init(); + + for (i=0; i<256; i++) + midi_write_adlib(i,0); + + midi_write_adlib(0x01, 0x20); + midi_write_adlib(0xBD,0xc0); +} + +bool CmidPlayer::update() +{ + long w,v,note,vel,ctrl,nv,x,l,lnum; + int i=0,j,c; + int on,onl,numchan; + int ret; + + if (doing == 1) + { + // just get the first wait and ignore it :> + for (curtrack=0; curtrack<16; curtrack++) + if (track[curtrack].on) + { + pos=track[curtrack].pos; + if (type != FILE_SIERRA && type !=FILE_ADVSIERRA) + track[curtrack].iwait+=getval(); + else + track[curtrack].iwait+=getnext(1); + track[curtrack].pos=pos; + } + doing=0; + } + + iwait=0; + ret=1; + + while (iwait==0 && ret==1) + { + for (curtrack=0; curtrack<16; curtrack++) + if (track[curtrack].on && track[curtrack].iwait==0 && + track[curtrack].pos < track[curtrack].tend) + { + pos=track[curtrack].pos; + + v=getnext(1); + + // This is to do implied MIDI events. + if (v<0x80) {v=track[curtrack].pv; pos--;} + track[curtrack].pv=(unsigned char)v; + + c=v&0x0f; + midiprintf ("[%2X]",v); + switch(v&0xf0) + { + case 0x80: /*note off*/ + note=getnext(1); vel=getnext(1); + for (i=0; i<9; i++) + if (chp[i][0]==c && chp[i][1]==note) + { + midi_fm_endnote(i); + chp[i][0]=-1; + } + break; + case 0x90: /*note on*/ + // doing=0; + note=getnext(1); vel=getnext(1); + + if(adlib_mode == ADLIB_RYTHM) + numchan = 6; + else + numchan = 9; + + if (ch[c].on!=0) + { + for (i=0; i<18; i++) + chp[i][2]++; + + if(c < 11 || adlib_mode == ADLIB_MELODIC) { + j=0; + on=-1;onl=0; + for (i=0; i<numchan; i++) + if (chp[i][0]==-1 && chp[i][2]>onl) + { onl=chp[i][2]; on=i; j=1; } + + if (on==-1) + { + onl=0; + for (i=0; i<numchan; i++) + if (chp[i][2]>onl) + { onl=chp[i][2]; on=i; } + } + + if (j==0) + midi_fm_endnote(on); + } else + on = percussion_map[c - 11]; + + if (vel!=0 && ch[c].inum>=0 && ch[c].inum<128) + { + if (adlib_mode == ADLIB_MELODIC || c < 12) + midi_fm_instrument(on,ch[c].ins); + else + midi_fm_percussion(c, ch[c].ins); + + if ((adlib_style&MIDI_STYLE)!=0) + { + nv=((ch[c].vol*vel)/128); + if ((adlib_style&LUCAS_STYLE)!=0) + nv*=2; + if (nv>127) nv=127; + nv=my_midi_fm_vol_table[nv]; + if ((adlib_style&LUCAS_STYLE)!=0) + nv=(int)((float)sqrt((float)nv)*11); + } + else + { + nv=vel; + } + + midi_fm_playnote(on,note+ch[c].nshift,nv*2); + chp[on][0]=c; + chp[on][1]=note; + chp[on][2]=0; + + if(adlib_mode == ADLIB_RYTHM && c >= 11) { + midi_write_adlib(0xbd, adlib_data[0xbd] & ~(0x10 >> (c - 11))); + midi_write_adlib(0xbd, adlib_data[0xbd] | (0x10 >> (c - 11))); + } + + } + else + { + if (vel==0) //same code as end note + { + for (i=0; i<9; i++) + if (chp[i][0]==c && chp[i][1]==note) + { + // midi_fm_volume(i,0); // really end the note + midi_fm_endnote(i); + chp[i][0]=-1; + } + } + else + { // i forget what this is for. + chp[on][0]=-1; + chp[on][2]=0; + } + } + midiprintf(" [%d:%d:%d:%d]\n",c,ch[c].inum,note,vel); + } + else + midiprintf ("off"); + break; + case 0xa0: /*key after touch */ + note=getnext(1); vel=getnext(1); + /* //this might all be good + for (i=0; i<9; i++) + if (chp[i][0]==c & chp[i][1]==note) + +midi_fm_playnote(i,note+cnote[c],my_midi_fm_vol_table[(cvols[c]*vel)/128]*2); + */ + break; + case 0xb0: /*control change .. pitch bend? */ + ctrl=getnext(1); vel=getnext(1); + + switch(ctrl) + { + case 0x07: + midiprintf ("(pb:%d: %d %d)",c,ctrl,vel); + ch[c].vol=vel; + midiprintf("vol"); + break; + case 0x67: + midiprintf ("\n\nhere:%d\n\n",vel); + if ((adlib_style&CMF_STYLE)!=0) { + adlib_mode=vel; + if(adlib_mode == ADLIB_RYTHM) + midi_write_adlib(0xbd, adlib_data[0xbd] | (1 << 5)); + else + midi_write_adlib(0xbd, adlib_data[0xbd] & ~(1 << 5)); + } + break; + } + break; + case 0xc0: /*patch change*/ + x=getnext(1); + ch[c].inum=x; + for (j=0; j<11; j++) + ch[c].ins[j]=myinsbank[ch[c].inum][j]; + break; + case 0xd0: /*chanel touch*/ + x=getnext(1); + break; + case 0xe0: /*pitch wheel*/ + x=getnext(1); + x=getnext(1); + break; + case 0xf0: + switch(v) + { + case 0xf0: + case 0xf7: /*sysex*/ + l=getval(); + if (datalook(pos+l)==0xf7) + i=1; + midiprintf("{%d}",l); + midiprintf("\n"); + + if (datalook(pos)==0x7d && + datalook(pos+1)==0x10 && + datalook(pos+2)<16) + { + adlib_style=LUCAS_STYLE|MIDI_STYLE; + for (i=0; i<l; i++) + { + midiprintf ("%x ",datalook(pos+i)); + if ((i-3)%10 == 0) midiprintf("\n"); + } + midiprintf ("\n"); + getnext(1); + getnext(1); + c=getnext(1); + getnext(1); + + // getnext(22); //temp + ch[c].ins[0]=(unsigned char)((getnext(1)<<4)+getnext(1)); + ch[c].ins[2]=(unsigned char)(0xff-(((getnext(1)<<4)+getnext(1))&0x3f)); + ch[c].ins[4]=(unsigned char)(0xff-((getnext(1)<<4)+getnext(1))); + ch[c].ins[6]=(unsigned char)(0xff-((getnext(1)<<4)+getnext(1))); + ch[c].ins[8]=(unsigned char)((getnext(1)<<4)+getnext(1)); + + ch[c].ins[1]=(unsigned char)((getnext(1)<<4)+getnext(1)); + ch[c].ins[3]=(unsigned char)(0xff-(((getnext(1)<<4)+getnext(1))&0x3f)); + ch[c].ins[5]=(unsigned char)(0xff-((getnext(1)<<4)+getnext(1))); + ch[c].ins[7]=(unsigned char)(0xff-((getnext(1)<<4)+getnext(1))); + ch[c].ins[9]=(unsigned char)((getnext(1)<<4)+getnext(1)); + + i=(getnext(1)<<4)+getnext(1); + ch[c].ins[10]=i; + + //if ((i&1)==1) ch[c].ins[10]=1; + + midiprintf ("\n%d: ",c); + for (i=0; i<11; i++) + midiprintf ("%2X ",ch[c].ins[i]); + getnext(l-26); + } + else + { + midiprintf("\n"); + for (j=0; j<l; j++) + midiprintf ("%2X ",getnext(1)); + } + + midiprintf("\n"); + if(i==1) + getnext(1); + break; + case 0xf1: + break; + case 0xf2: + getnext(2); + break; + case 0xf3: + getnext(1); + break; + case 0xf4: + break; + case 0xf5: + break; + case 0xf6: /*something*/ + case 0xf8: + case 0xfa: + case 0xfb: + case 0xfc: + //this ends the track for sierra. + if (type == FILE_SIERRA || + type == FILE_ADVSIERRA) + { + track[curtrack].tend=pos; + midiprintf ("endmark: %ld -- %lx\n",pos,pos); + } + break; + case 0xfe: + break; + case 0xfd: + break; + case 0xff: + v=getnext(1); + l=getval(); + midiprintf ("\n"); + midiprintf("{%X_%X}",v,l); + if (v==0x51) + { + lnum=getnext(l); + msqtr=lnum; /*set tempo*/ + midiprintf ("(qtr=%ld)",msqtr); + } + else + { + for (i=0; i<l; i++) + midiprintf ("%2X ",getnext(1)); + } + break; + } + break; + default: midiprintf("!",v); /* if we get down here, a error occurred */ + break; + } + + if (pos < track[curtrack].tend) + { + if (type != FILE_SIERRA && type !=FILE_ADVSIERRA) + w=getval(); + else + w=getnext(1); + track[curtrack].iwait=w; + /* + if (w!=0) + { + midiprintf("\n<%d>",w); + f = +((float)w/(float)deltas)*((float)msqtr/(float)1000000); + if (doing==1) f=0; //not playing yet. don't wait yet + } + */ + } + else + track[curtrack].iwait=0; + + track[curtrack].pos=pos; + } + + + ret=0; //end of song. + iwait=0; + for (curtrack=0; curtrack<16; curtrack++) + if (track[curtrack].on == 1 && + track[curtrack].pos < track[curtrack].tend) + ret=1; //not yet.. + + if (ret==1) + { + iwait=0xffffff; // bigger than any wait can be! + for (curtrack=0; curtrack<16; curtrack++) + if (track[curtrack].on == 1 && + track[curtrack].pos < track[curtrack].tend && + track[curtrack].iwait < iwait) + iwait=track[curtrack].iwait; + } + } + + + if (iwait !=0 && ret==1) + { + for (curtrack=0; curtrack<16; curtrack++) + if (track[curtrack].on) + track[curtrack].iwait-=iwait; + + +fwait=1.0f/(((float)iwait/(float)deltas)*((float)msqtr/(float)1000000)); + } + else + fwait=50; // 1/50th of a second + + midiprintf ("\n"); + for (i=0; i<16; i++) + if (track[i].on) + if (track[i].pos < track[i].tend) + midiprintf ("<%d>",track[i].iwait); + else + midiprintf("stop"); + + /* + if (ret==0 && type==FILE_ADVSIERRA) + if (datalook(sierra_pos-2)!=0xff) + { + midiprintf ("next sectoin!"); + sierra_next_section(p); + fwait=50; + ret=1; + } + */ + + if(ret) + return true; + else + return false; +} + +float CmidPlayer::getrefresh() +{ + return (fwait > 0.01f ? fwait : 0.01f); +} + +void CmidPlayer::rewind(int subsong) +{ + long i,j,n,m,l; + long o_sierra_pos; + unsigned char ins[16]; + + pos=0; tins=0; + adlib_style=MIDI_STYLE|CMF_STYLE; + adlib_mode=ADLIB_MELODIC; + for (i=0; i<128; i++) + for (j=0; j<16; j++) + myinsbank[i][j]=midi_fm_instruments[i][j]; + for (i=0; i<16; i++) + { + ch[i].inum=0; + for (j=0; j<11; j++) + ch[i].ins[j]=myinsbank[ch[i].inum][j]; + ch[i].vol=127; + ch[i].nshift=-25; + ch[i].on=1; + } + + /* General init */ + for (i=0; i<9; i++) + { + chp[i][0]=-1; + chp[i][2]=0; + } + + deltas=250; // just a number, not a standard + msqtr=500000; + fwait=123; // gotta be a small thing.. sorta like nothing + iwait=0; + + subsongs=1; + + for (i=0; i<16; i++) + { + track[i].tend=0; + track[i].spos=0; + track[i].pos=0; + track[i].iwait=0; + track[i].on=0; + track[i].pv=0; + } + curtrack=0; + + /* specific to file-type init */ + + pos=0; + i=getnext(1); + switch(type) + { + case FILE_LUCAS: + getnext(24); //skip junk and get to the midi. + adlib_style=LUCAS_STYLE|MIDI_STYLE; + //note: no break, we go right into midi headers... + case FILE_MIDI: + if (type != FILE_LUCAS) + tins=128; + getnext(11); /*skip header*/ + deltas=getnext(2); + midiprintf ("deltas:%ld\n",deltas); + getnext(4); + + curtrack=0; + track[curtrack].on=1; + track[curtrack].tend=getnext(4); + track[curtrack].spos=pos; + midiprintf ("tracklen:%ld\n",track[curtrack].tend); + break; + case FILE_CMF: + getnext(3); // ctmf + getnexti(2); //version + n=getnexti(2); // instrument offset + m=getnexti(2); // music offset + deltas=getnexti(2); //ticks/qtr note + msqtr=1000000/getnexti(2)*deltas; + //the stuff in the cmf is click ticks per second.. + + i=getnexti(2); + if(i) title = (char *)data+i; + i=getnexti(2); + if(i) author = (char *)data+i; + i=getnexti(2); + if(i) remarks = (char *)data+i; + + getnext(16); // channel in use table .. + i=getnexti(2); // num instr + if (i>128) i=128; // to ward of bad numbers... + getnexti(2); //basic tempo + + midiprintf("\nioff:%d\nmoff%d\ndeltas:%ld\nmsqtr:%ld\nnumi:%d\n", + n,m,deltas,msqtr,i); + pos=n; // jump to instruments + tins=i; + for (j=0; j<i; j++) + { + midiprintf ("\n%d: ",j); + for (l=0; l<16; l++) + { + myinsbank[j][l]=(unsigned char)getnext(1); + midiprintf ("%2X ",myinsbank[j][l]); + } + } + + for (i=0; i<16; i++) + ch[i].nshift=-13; + + adlib_style=CMF_STYLE; + + curtrack=0; + track[curtrack].on=1; + track[curtrack].tend=flen; // music until the end of the file + track[curtrack].spos=m; //jump to midi music + break; + case FILE_OLDLUCAS: + msqtr=250000; + pos=9; + deltas=getnext(1); + + i=8; + pos=0x19; // jump to instruments + tins=i; + for (j=0; j<i; j++) + { + midiprintf ("\n%d: ",j); + for (l=0; l<16; l++) + ins[l]=(unsigned char)getnext(1); + + myinsbank[j][10]=ins[2]; + myinsbank[j][0]=ins[3]; + myinsbank[j][2]=ins[4]; + myinsbank[j][4]=ins[5]; + myinsbank[j][6]=ins[6]; + myinsbank[j][8]=ins[7]; + myinsbank[j][1]=ins[8]; + myinsbank[j][3]=ins[9]; + myinsbank[j][5]=ins[10]; + myinsbank[j][7]=ins[11]; + myinsbank[j][9]=ins[12]; + + for (l=0; l<11; l++) + midiprintf ("%2X ",myinsbank[j][l]); + } + + for (i=0; i<16; i++) + { + if (i<tins) + { + ch[i].inum=i; + for (j=0; j<11; j++) + ch[i].ins[j]=myinsbank[ch[i].inum][j]; + } + } + + adlib_style=LUCAS_STYLE|MIDI_STYLE; + + curtrack=0; + track[curtrack].on=1; + track[curtrack].tend=flen; // music until the end of the file + track[curtrack].spos=0x98; //jump to midi music + break; + case FILE_ADVSIERRA: + memcpy(myinsbank, smyinsbank, 128 * 16); + tins = stins; + deltas=0x20; + getnext(11); //worthless empty space and "stuff" :) + + o_sierra_pos=sierra_pos=pos; + sierra_next_section(); + while (datalook(sierra_pos-2)!=0xff) + { + sierra_next_section(); + subsongs++; + } + + if (subsong < 0 || subsong >= subsongs) subsong=0; + + sierra_pos=o_sierra_pos; + sierra_next_section(); + i=0; + while (i != subsong) + { + sierra_next_section(); + i++; + } + + adlib_style=SIERRA_STYLE|MIDI_STYLE; //advanced sierra tunes use volume + break; + case FILE_SIERRA: + memcpy(myinsbank, smyinsbank, 128 * 16); + tins = stins; + getnext(2); + deltas=0x20; + + curtrack=0; + track[curtrack].on=1; + track[curtrack].tend=flen; // music until the end of the file + + for (i=0; i<16; i++) + { + ch[i].nshift=-13; + ch[i].on=getnext(1); + ch[i].inum=getnext(1); + for (j=0; j<11; j++) + ch[i].ins[j]=myinsbank[ch[i].inum][j]; + } + + track[curtrack].spos=pos; + adlib_style=SIERRA_STYLE|MIDI_STYLE; + break; + } + + +/* sprintf(info,"%s\r\nTicks/Quarter Note: %ld\r\n",info,deltas); + sprintf(info,"%sms/Quarter Note: %ld",info,msqtr); */ + + for (i=0; i<16; i++) + if (track[i].on) + { + track[i].pos=track[i].spos; + track[i].pv=0; + track[i].iwait=0; + } + + doing=1; + midi_fm_reset(); +} + +std::string CmidPlayer::gettype() +{ + switch(type) { + case FILE_LUCAS: + return std::string("LucasArts AdLib MIDI"); + case FILE_MIDI: + return std::string("General MIDI"); + case FILE_CMF: + return std::string("Creative Music Format (CMF MIDI)"); + case FILE_OLDLUCAS: + return std::string("Lucasfilm Adlib MIDI"); + case FILE_ADVSIERRA: + return std::string("Sierra On-Line VGA MIDI"); + case FILE_SIERRA: + return std::string("Sierra On-Line EGA MIDI"); + default: + return std::string("MIDI unknown"); + } +} diff --git a/plugins/adplug/adplug/mid.h b/plugins/adplug/adplug/mid.h new file mode 100644 index 00000000..d28f7252 --- /dev/null +++ b/plugins/adplug/adplug/mid.h @@ -0,0 +1,112 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * mid.h - LAA, SCI, MID & CMF Player by Philip Hassey <philhassey@hotmail.com> + */ + +#include "player.h" + +class CmidPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CmidPlayer(Copl *newopl); + ~CmidPlayer() + { if(data) delete [] data; } + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype(); + std::string gettitle() + { return std::string(title); } + std::string getauthor() + { return std::string(author); } + std::string getdesc() + { return std::string(remarks); } + unsigned int getinstruments() + { return tins; } + unsigned int getsubsongs() + { return subsongs; } + + protected: + static const unsigned char adlib_opadd[]; + static const int ops[], map_chan[], fnums[], percussion_map[]; + + struct midi_channel { + int inum; + unsigned char ins[11]; + int vol; + int nshift; + int on; + }; + + struct midi_track { + unsigned long tend; + unsigned long spos; + unsigned long pos; + unsigned long iwait; + int on; + unsigned char pv; + }; + + char *author,*title,*remarks,emptystr; + long flen; + unsigned long pos; + unsigned long sierra_pos; //sierras gotta be special.. :> + int subsongs; + unsigned char *data; + + unsigned char adlib_data[256]; + int adlib_style; + int adlib_mode; + unsigned char myinsbank[128][16], smyinsbank[128][16]; + midi_channel ch[16]; + int chp[18][3]; + + long deltas; + long msqtr; + + midi_track track[16]; + unsigned int curtrack; + + float fwait; + unsigned long iwait; + int doing; + + int type,tins,stins; + + private: + bool load_sierra_ins(const std::string &fname, const CFileProvider &fp); + void midiprintf(const char *format, ...); + unsigned char datalook(long pos); + unsigned long getnexti(unsigned long num); + unsigned long getnext(unsigned long num); + unsigned long getval(); + void sierra_next_section(); + void midi_write_adlib(unsigned int r, unsigned char v); + void midi_fm_instrument(int voice, unsigned char *inst); + void midi_fm_percussion(int ch, unsigned char *inst); + void midi_fm_volume(int voice, int volume); + void midi_fm_playnote(int voice, int note, int volume); + void midi_fm_endnote(int voice); + void midi_fm_reset(); +}; diff --git a/plugins/adplug/adplug/mididata.h b/plugins/adplug/adplug/mididata.h new file mode 100644 index 00000000..2a83cd99 --- /dev/null +++ b/plugins/adplug/adplug/mididata.h @@ -0,0 +1,174 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999, 2000, 2001 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * FM instrument definitions below borrowed from the Allegro library by + * Phil Hassey, <philhassey@hotmail.com> - see "adplug/players/mid.cpp" + * for further acknowledgements. + */ + +unsigned char midi_fm_instruments[128][14] = +{ + + /* This set of GM instrument patches was provided by Jorrit Rouwe... + */ + + { 0x21, 0x21, 0x8f, 0x0c, 0xf2, 0xf2, 0x45, 0x76, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Acoustic Grand */ + { 0x31, 0x21, 0x4b, 0x09, 0xf2, 0xf2, 0x54, 0x56, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Bright Acoustic */ + { 0x31, 0x21, 0x49, 0x09, 0xf2, 0xf2, 0x55, 0x76, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Electric Grand */ + { 0xb1, 0x61, 0x0e, 0x09, 0xf2, 0xf3, 0x3b, 0x0b, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Honky-Tonk */ + { 0x01, 0x21, 0x57, 0x09, 0xf1, 0xf1, 0x38, 0x28, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Piano 1 */ + { 0x01, 0x21, 0x93, 0x09, 0xf1, 0xf1, 0x38, 0x28, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Piano 2 */ + { 0x21, 0x36, 0x80, 0x17, 0xa2, 0xf1, 0x01, 0xd5, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Harpsichord */ + { 0x01, 0x01, 0x92, 0x09, 0xc2, 0xc2, 0xa8, 0x58, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Clav */ + { 0x0c, 0x81, 0x5c, 0x09, 0xf6, 0xf3, 0x54, 0xb5, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Celesta */ + { 0x07, 0x11, 0x97, 0x89, 0xf6, 0xf5, 0x32, 0x11, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Glockenspiel */ + { 0x17, 0x01, 0x21, 0x09, 0x56, 0xf6, 0x04, 0x04, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Music Box */ + { 0x18, 0x81, 0x62, 0x09, 0xf3, 0xf2, 0xe6, 0xf6, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Vibraphone */ + { 0x18, 0x21, 0x23, 0x09, 0xf7, 0xe5, 0x55, 0xd8, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Marimba */ + { 0x15, 0x01, 0x91, 0x09, 0xf6, 0xf6, 0xa6, 0xe6, 0x00, 0x00, 0x04, 0, 0, 0 }, /* Xylophone */ + { 0x45, 0x81, 0x59, 0x89, 0xd3, 0xa3, 0x82, 0xe3, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Tubular Bells */ + { 0x03, 0x81, 0x49, 0x89, 0x74, 0xb3, 0x55, 0x05, 0x01, 0x00, 0x04, 0, 0, 0 }, /* Dulcimer */ + { 0x71, 0x31, 0x92, 0x09, 0xf6, 0xf1, 0x14, 0x07, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Drawbar Organ */ + { 0x72, 0x30, 0x14, 0x09, 0xc7, 0xc7, 0x58, 0x08, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Percussive Organ */ + { 0x70, 0xb1, 0x44, 0x09, 0xaa, 0x8a, 0x18, 0x08, 0x00, 0x00, 0x04, 0, 0, 0 }, /* Rock Organ */ + { 0x23, 0xb1, 0x93, 0x09, 0x97, 0x55, 0x23, 0x14, 0x01, 0x00, 0x04, 0, 0, 0 }, /* Church Organ */ + { 0x61, 0xb1, 0x13, 0x89, 0x97, 0x55, 0x04, 0x04, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Reed Organ */ + { 0x24, 0xb1, 0x48, 0x09, 0x98, 0x46, 0x2a, 0x1a, 0x01, 0x00, 0x0c, 0, 0, 0 }, /* Accoridan */ + { 0x61, 0x21, 0x13, 0x09, 0x91, 0x61, 0x06, 0x07, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Harmonica */ + { 0x21, 0xa1, 0x13, 0x92, 0x71, 0x61, 0x06, 0x07, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Tango Accordian */ + { 0x02, 0x41, 0x9c, 0x89, 0xf3, 0xf3, 0x94, 0xc8, 0x01, 0x00, 0x0c, 0, 0, 0 }, /* Acoustic Guitar(nylon) */ + { 0x03, 0x11, 0x54, 0x09, 0xf3, 0xf1, 0x9a, 0xe7, 0x01, 0x00, 0x0c, 0, 0, 0 }, /* Acoustic Guitar(steel) */ + { 0x23, 0x21, 0x5f, 0x09, 0xf1, 0xf2, 0x3a, 0xf8, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Guitar(jazz) */ + { 0x03, 0x21, 0x87, 0x89, 0xf6, 0xf3, 0x22, 0xf8, 0x01, 0x00, 0x06, 0, 0, 0 }, /* Electric Guitar(clean) */ + { 0x03, 0x21, 0x47, 0x09, 0xf9, 0xf6, 0x54, 0x3a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Guitar(muted) */ + { 0x23, 0x21, 0x4a, 0x0e, 0x91, 0x84, 0x41, 0x19, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Overdriven Guitar */ + { 0x23, 0x21, 0x4a, 0x09, 0x95, 0x94, 0x19, 0x19, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Distortion Guitar */ + { 0x09, 0x84, 0xa1, 0x89, 0x20, 0xd1, 0x4f, 0xf8, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Guitar Harmonics */ + { 0x21, 0xa2, 0x1e, 0x09, 0x94, 0xc3, 0x06, 0xa6, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Acoustic Bass */ + { 0x31, 0x31, 0x12, 0x09, 0xf1, 0xf1, 0x28, 0x18, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Electric Bass(finger) */ + { 0x31, 0x31, 0x8d, 0x09, 0xf1, 0xf1, 0xe8, 0x78, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Electric Bass(pick) */ + { 0x31, 0x32, 0x5b, 0x09, 0x51, 0x71, 0x28, 0x48, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Fretless Bass */ + { 0x01, 0x21, 0x8b, 0x49, 0xa1, 0xf2, 0x9a, 0xdf, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Slap Bass 1 */ + { 0x21, 0x21, 0x8b, 0x11, 0xa2, 0xa1, 0x16, 0xdf, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Slap Bass 2 */ + { 0x31, 0x31, 0x8b, 0x09, 0xf4, 0xf1, 0xe8, 0x78, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Synth Bass 1 */ + { 0x31, 0x31, 0x12, 0x09, 0xf1, 0xf1, 0x28, 0x18, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Synth Bass 2 */ + { 0x31, 0x21, 0x15, 0x09, 0xdd, 0x56, 0x13, 0x26, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Violin */ + { 0x31, 0x21, 0x16, 0x09, 0xdd, 0x66, 0x13, 0x06, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Viola */ + { 0x71, 0x31, 0x49, 0x09, 0xd1, 0x61, 0x1c, 0x0c, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Cello */ + { 0x21, 0x23, 0x4d, 0x89, 0x71, 0x72, 0x12, 0x06, 0x01, 0x00, 0x02, 0, 0, 0 }, /* Contrabass */ + { 0xf1, 0xe1, 0x40, 0x09, 0xf1, 0x6f, 0x21, 0x16, 0x01, 0x00, 0x02, 0, 0, 0 }, /* Tremolo Strings */ + { 0x02, 0x01, 0x1a, 0x89, 0xf5, 0x85, 0x75, 0x35, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Pizzicato Strings */ + { 0x02, 0x01, 0x1d, 0x89, 0xf5, 0xf3, 0x75, 0xf4, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Orchestral Strings */ + { 0x10, 0x11, 0x41, 0x09, 0xf5, 0xf2, 0x05, 0xc3, 0x01, 0x00, 0x02, 0, 0, 0 }, /* Timpani */ + { 0x21, 0xa2, 0x9b, 0x0a, 0xb1, 0x72, 0x25, 0x08, 0x01, 0x00, 0x0e, 0, 0, 0 }, /* String Ensemble 1 */ + { 0xa1, 0x21, 0x98, 0x09, 0x7f, 0x3f, 0x03, 0x07, 0x01, 0x01, 0x00, 0, 0, 0 }, /* String Ensemble 2 */ + { 0xa1, 0x61, 0x93, 0x09, 0xc1, 0x4f, 0x12, 0x05, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* SynthStrings 1 */ + { 0x21, 0x61, 0x18, 0x09, 0xc1, 0x4f, 0x22, 0x05, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* SynthStrings 2 */ + { 0x31, 0x72, 0x5b, 0x8c, 0xf4, 0x8a, 0x15, 0x05, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Choir Aahs */ + { 0xa1, 0x61, 0x90, 0x09, 0x74, 0x71, 0x39, 0x67, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Voice Oohs */ + { 0x71, 0x72, 0x57, 0x09, 0x54, 0x7a, 0x05, 0x05, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Synth Voice */ + { 0x90, 0x41, 0x00, 0x09, 0x54, 0xa5, 0x63, 0x45, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Orchestra Hit */ + { 0x21, 0x21, 0x92, 0x0a, 0x85, 0x8f, 0x17, 0x09, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Trumpet */ + { 0x21, 0x21, 0x94, 0x0e, 0x75, 0x8f, 0x17, 0x09, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Trombone */ + { 0x21, 0x61, 0x94, 0x09, 0x76, 0x82, 0x15, 0x37, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Tuba */ + { 0x31, 0x21, 0x43, 0x09, 0x9e, 0x62, 0x17, 0x2c, 0x01, 0x01, 0x02, 0, 0, 0 }, /* Muted Trumpet */ + { 0x21, 0x21, 0x9b, 0x09, 0x61, 0x7f, 0x6a, 0x0a, 0x00, 0x00, 0x02, 0, 0, 0 }, /* French Horn */ + { 0x61, 0x22, 0x8a, 0x0f, 0x75, 0x74, 0x1f, 0x0f, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Brass Section */ + { 0xa1, 0x21, 0x86, 0x8c, 0x72, 0x71, 0x55, 0x18, 0x01, 0x00, 0x00, 0, 0, 0 }, /* SynthBrass 1 */ + { 0x21, 0x21, 0x4d, 0x09, 0x54, 0xa6, 0x3c, 0x1c, 0x00, 0x00, 0x08, 0, 0, 0 }, /* SynthBrass 2 */ + { 0x31, 0x61, 0x8f, 0x09, 0x93, 0x72, 0x02, 0x0b, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Soprano Sax */ + { 0x31, 0x61, 0x8e, 0x09, 0x93, 0x72, 0x03, 0x09, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Alto Sax */ + { 0x31, 0x61, 0x91, 0x09, 0x93, 0x82, 0x03, 0x09, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Tenor Sax */ + { 0x31, 0x61, 0x8e, 0x09, 0x93, 0x72, 0x0f, 0x0f, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Baritone Sax */ + { 0x21, 0x21, 0x4b, 0x09, 0xaa, 0x8f, 0x16, 0x0a, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Oboe */ + { 0x31, 0x21, 0x90, 0x09, 0x7e, 0x8b, 0x17, 0x0c, 0x01, 0x01, 0x06, 0, 0, 0 }, /* English Horn */ + { 0x31, 0x32, 0x81, 0x09, 0x75, 0x61, 0x19, 0x19, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Bassoon */ + { 0x32, 0x21, 0x90, 0x09, 0x9b, 0x72, 0x21, 0x17, 0x00, 0x00, 0x04, 0, 0, 0 }, /* Clarinet */ + { 0xe1, 0xe1, 0x1f, 0x09, 0x85, 0x65, 0x5f, 0x1a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Piccolo */ + { 0xe1, 0xe1, 0x46, 0x09, 0x88, 0x65, 0x5f, 0x1a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Flute */ + { 0xa1, 0x21, 0x9c, 0x09, 0x75, 0x75, 0x1f, 0x0a, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Recorder */ + { 0x31, 0x21, 0x8b, 0x09, 0x84, 0x65, 0x58, 0x1a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Pan Flute */ + { 0xe1, 0xa1, 0x4c, 0x09, 0x66, 0x65, 0x56, 0x26, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Blown Bottle */ + { 0x62, 0xa1, 0xcb, 0x09, 0x76, 0x55, 0x46, 0x36, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Skakuhachi */ + { 0x62, 0xa1, 0xa2, 0x09, 0x57, 0x56, 0x07, 0x07, 0x00, 0x00, 0x0b, 0, 0, 0 }, /* Whistle */ + { 0x62, 0xa1, 0x9c, 0x09, 0x77, 0x76, 0x07, 0x07, 0x00, 0x00, 0x0b, 0, 0, 0 }, /* Ocarina */ + { 0x22, 0x21, 0x59, 0x09, 0xff, 0xff, 0x03, 0x0f, 0x02, 0x00, 0x00, 0, 0, 0 }, /* Lead 1 (square) */ + { 0x21, 0x21, 0x0e, 0x09, 0xff, 0xff, 0x0f, 0x0f, 0x01, 0x01, 0x00, 0, 0, 0 }, /* Lead 2 (sawtooth) */ + { 0x22, 0x21, 0x46, 0x89, 0x86, 0x64, 0x55, 0x18, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Lead 3 (calliope) */ + { 0x21, 0xa1, 0x45, 0x09, 0x66, 0x96, 0x12, 0x0a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Lead 4 (chiff) */ + { 0x21, 0x22, 0x8b, 0x09, 0x92, 0x91, 0x2a, 0x2a, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Lead 5 (charang) */ + { 0xa2, 0x61, 0x9e, 0x49, 0xdf, 0x6f, 0x05, 0x07, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Lead 6 (voice) */ + { 0x20, 0x60, 0x1a, 0x09, 0xef, 0x8f, 0x01, 0x06, 0x00, 0x02, 0x00, 0, 0, 0 }, /* Lead 7 (fifths) */ + { 0x21, 0x21, 0x8f, 0x86, 0xf1, 0xf4, 0x29, 0x09, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Lead 8 (bass+lead) */ + { 0x77, 0xa1, 0xa5, 0x09, 0x53, 0xa0, 0x94, 0x05, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Pad 1 (new age) */ + { 0x61, 0xb1, 0x1f, 0x89, 0xa8, 0x25, 0x11, 0x03, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Pad 2 (warm) */ + { 0x61, 0x61, 0x17, 0x09, 0x91, 0x55, 0x34, 0x16, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Pad 3 (polysynth) */ + { 0x71, 0x72, 0x5d, 0x09, 0x54, 0x6a, 0x01, 0x03, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Pad 4 (choir) */ + { 0x21, 0xa2, 0x97, 0x09, 0x21, 0x42, 0x43, 0x35, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Pad 5 (bowed) */ + { 0xa1, 0x21, 0x1c, 0x09, 0xa1, 0x31, 0x77, 0x47, 0x01, 0x01, 0x00, 0, 0, 0 }, /* Pad 6 (metallic) */ + { 0x21, 0x61, 0x89, 0x0c, 0x11, 0x42, 0x33, 0x25, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Pad 7 (halo) */ + { 0xa1, 0x21, 0x15, 0x09, 0x11, 0xcf, 0x47, 0x07, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Pad 8 (sweep) */ + { 0x3a, 0x51, 0xce, 0x09, 0xf8, 0x86, 0xf6, 0x02, 0x00, 0x00, 0x02, 0, 0, 0 }, /* FX 1 (rain) */ + { 0x21, 0x21, 0x15, 0x09, 0x21, 0x41, 0x23, 0x13, 0x01, 0x00, 0x00, 0, 0, 0 }, /* FX 2 (soundtrack) */ + { 0x06, 0x01, 0x5b, 0x09, 0x74, 0xa5, 0x95, 0x72, 0x00, 0x00, 0x00, 0, 0, 0 }, /* FX 3 (crystal) */ + { 0x22, 0x61, 0x92, 0x8c, 0xb1, 0xf2, 0x81, 0x26, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* FX 4 (atmosphere) */ + { 0x41, 0x42, 0x4d, 0x09, 0xf1, 0xf2, 0x51, 0xf5, 0x01, 0x00, 0x00, 0, 0, 0 }, /* FX 5 (brightness) */ + { 0x61, 0xa3, 0x94, 0x89, 0x11, 0x11, 0x51, 0x13, 0x01, 0x00, 0x06, 0, 0, 0 }, /* FX 6 (goblins) */ + { 0x61, 0xa1, 0x8c, 0x89, 0x11, 0x1d, 0x31, 0x03, 0x00, 0x00, 0x06, 0, 0, 0 }, /* FX 7 (echoes) */ + { 0xa4, 0x61, 0x4c, 0x09, 0xf3, 0x81, 0x73, 0x23, 0x01, 0x00, 0x04, 0, 0, 0 }, /* FX 8 (sci-fi) */ + { 0x02, 0x07, 0x85, 0x0c, 0xd2, 0xf2, 0x53, 0xf6, 0x00, 0x01, 0x00, 0, 0, 0 }, /* Sitar */ + { 0x11, 0x13, 0x0c, 0x89, 0xa3, 0xa2, 0x11, 0xe5, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Banjo */ + { 0x11, 0x11, 0x06, 0x09, 0xf6, 0xf2, 0x41, 0xe6, 0x01, 0x02, 0x04, 0, 0, 0 }, /* Shamisen */ + { 0x93, 0x91, 0x91, 0x09, 0xd4, 0xeb, 0x32, 0x11, 0x00, 0x01, 0x08, 0, 0, 0 }, /* Koto */ + { 0x04, 0x01, 0x4f, 0x09, 0xfa, 0xc2, 0x56, 0x05, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Kalimba */ + { 0x21, 0x22, 0x49, 0x09, 0x7c, 0x6f, 0x20, 0x0c, 0x00, 0x01, 0x06, 0, 0, 0 }, /* Bagpipe */ + { 0x31, 0x21, 0x85, 0x09, 0xdd, 0x56, 0x33, 0x16, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Fiddle */ + { 0x20, 0x21, 0x04, 0x8a, 0xda, 0x8f, 0x05, 0x0b, 0x02, 0x00, 0x06, 0, 0, 0 }, /* Shanai */ + { 0x05, 0x03, 0x6a, 0x89, 0xf1, 0xc3, 0xe5, 0xe5, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Tinkle Bell */ + { 0x07, 0x02, 0x15, 0x09, 0xec, 0xf8, 0x26, 0x16, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Agogo */ + { 0x05, 0x01, 0x9d, 0x09, 0x67, 0xdf, 0x35, 0x05, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Steel Drums */ + { 0x18, 0x12, 0x96, 0x09, 0xfa, 0xf8, 0x28, 0xe5, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Woodblock */ + { 0x10, 0x00, 0x86, 0x0c, 0xa8, 0xfa, 0x07, 0x03, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Taiko Drum */ + { 0x11, 0x10, 0x41, 0x0c, 0xf8, 0xf3, 0x47, 0x03, 0x02, 0x00, 0x04, 0, 0, 0 }, /* Melodic Tom */ + { 0x01, 0x10, 0x8e, 0x09, 0xf1, 0xf3, 0x06, 0x02, 0x02, 0x00, 0x0e, 0, 0, 0 }, /* Synth Drum */ + { 0x0e, 0xc0, 0x00, 0x09, 0x1f, 0x1f, 0x00, 0xff, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Reverse Cymbal */ + { 0x06, 0x03, 0x80, 0x91, 0xf8, 0x56, 0x24, 0x84, 0x00, 0x02, 0x0e, 0, 0, 0 }, /* Guitar Fret Noise */ + { 0x0e, 0xd0, 0x00, 0x0e, 0xf8, 0x34, 0x00, 0x04, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Breath Noise */ + { 0x0e, 0xc0, 0x00, 0x09, 0xf6, 0x1f, 0x00, 0x02, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Seashore */ + { 0xd5, 0xda, 0x95, 0x49, 0x37, 0x56, 0xa3, 0x37, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Bird Tweet */ + { 0x35, 0x14, 0x5c, 0x11, 0xb2, 0xf4, 0x61, 0x15, 0x02, 0x00, 0x0a, 0, 0, 0 }, /* Telephone ring */ + { 0x0e, 0xd0, 0x00, 0x09, 0xf6, 0x4f, 0x00, 0xf5, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Helicopter */ + { 0x26, 0xe4, 0x00, 0x09, 0xff, 0x12, 0x01, 0x16, 0x00, 0x01, 0x0e, 0, 0, 0 }, /* Applause */ + { 0x00, 0x00, 0x00, 0x09, 0xf3, 0xf6, 0xf0, 0xc9, 0x00, 0x02, 0x0e, 0, 0, 0 } /* Gunshot */ + +}; + +/* logarithmic relationship between midi and FM volumes */ +static int my_midi_fm_vol_table[128] = { + 0, 11, 16, 19, 22, 25, 27, 29, 32, 33, 35, 37, 39, 40, 42, 43, + 45, 46, 48, 49, 50, 51, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 64, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 75, 76, 77, + 78, 79, 80, 80, 81, 82, 83, 83, 84, 85, 86, 86, 87, 88, 89, 89, + 90, 91, 91, 92, 93, 93, 94, 95, 96, 96, 97, 97, 98, 99, 99, 100, + 101, 101, 102, 103, 103, 104, 104, 105, 106, 106, 107, 107, 108, + 109, 109, 110, 110, 111, 112, 112, 113, 113, 114, 114, 115, 115, + 116, 117, 117, 118, 118, 119, 119, 120, 120, 121, 121, 122, 122, + 123, 123, 124, 124, 125, 125, 126, 126, 127 +}; + diff --git a/plugins/adplug/adplug/mkj.cpp b/plugins/adplug/adplug/mkj.cpp new file mode 100644 index 00000000..32d7e27c --- /dev/null +++ b/plugins/adplug/adplug/mkj.cpp @@ -0,0 +1,164 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * mkj.cpp - MKJamz Player, by Simon Peter <dn.tlp@gmx.net> + */ + +#include <assert.h> +#include <string.h> + +#include "mkj.h" +#include "debug.h" + +CPlayer *CmkjPlayer::factory(Copl *newopl) +{ + return new CmkjPlayer(newopl); +} + +bool CmkjPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + char id[6]; + float ver; + int i, j; + short inst[8]; + + // file validation + f->readString(id, 6); + if(strncmp(id,"MKJamz",6)) { fp.close(f); return false; } + ver = f->readFloat(binio::Single); + if(ver > 1.12) { fp.close(f); return false; } + + // load + maxchannel = f->readInt(2); + opl->init(); opl->write(1, 32); + for(i = 0; i < maxchannel; i++) { + for(j = 0; j < 8; j++) inst[j] = f->readInt(2); + opl->write(0x20+op_table[i],inst[4]); + opl->write(0x23+op_table[i],inst[0]); + opl->write(0x40+op_table[i],inst[5]); + opl->write(0x43+op_table[i],inst[1]); + opl->write(0x60+op_table[i],inst[6]); + opl->write(0x63+op_table[i],inst[2]); + opl->write(0x80+op_table[i],inst[7]); + opl->write(0x83+op_table[i],inst[3]); + } + maxnotes = f->readInt(2); + songbuf = new short [(maxchannel+1)*maxnotes]; + for(i = 0; i < maxchannel; i++) channel[i].defined = f->readInt(2); + for(i = 0; i < (maxchannel + 1) * maxnotes; i++) + songbuf[i] = f->readInt(2); + + AdPlug_LogWrite("CmkjPlayer::load(\"%s\"): loaded file ver %.2f, %d channels," + " %d notes/channel.\n", filename.c_str(), ver, maxchannel, + maxnotes); + fp.close(f); + rewind(0); + return true; +} + +bool CmkjPlayer::update() +{ + int c, i; + short note; + + for(c = 0; c < maxchannel; c++) { + if(!channel[c].defined) // skip if channel is disabled + continue; + + if(channel[c].pstat) { + channel[c].pstat--; + continue; + } + + opl->write(0xb0 + c, 0); // key off + do { + assert(channel[c].songptr < (maxchannel + 1) * maxnotes); + note = songbuf[channel[c].songptr]; + if(channel[c].songptr - c > maxchannel) + if(note && note < 250) + channel[c].pstat = channel[c].speed; + switch(note) { + // normal notes + case 68: opl->write(0xa0 + c,0x81); opl->write(0xb0 + c,0x21 + 4 * channel[c].octave); break; + case 69: opl->write(0xa0 + c,0xb0); opl->write(0xb0 + c,0x21 + 4 * channel[c].octave); break; + case 70: opl->write(0xa0 + c,0xca); opl->write(0xb0 + c,0x21 + 4 * channel[c].octave); break; + case 71: opl->write(0xa0 + c,0x2); opl->write(0xb0 + c,0x22 + 4 * channel[c].octave); break; + case 65: opl->write(0xa0 + c,0x41); opl->write(0xb0 + c,0x22 + 4 * channel[c].octave); break; + case 66: opl->write(0xa0 + c,0x87); opl->write(0xb0 + c,0x22 + 4 * channel[c].octave); break; + case 67: opl->write(0xa0 + c,0xae); opl->write(0xb0 + c,0x22 + 4 * channel[c].octave); break; + case 17: opl->write(0xa0 + c,0x6b); opl->write(0xb0 + c,0x21 + 4 * channel[c].octave); break; + case 18: opl->write(0xa0 + c,0x98); opl->write(0xb0 + c,0x21 + 4 * channel[c].octave); break; + case 20: opl->write(0xa0 + c,0xe5); opl->write(0xb0 + c,0x21 + 4 * channel[c].octave); break; + case 21: opl->write(0xa0 + c,0x20); opl->write(0xb0 + c,0x22 + 4 * channel[c].octave); break; + case 15: opl->write(0xa0 + c,0x63); opl->write(0xb0 + c,0x22 + 4 * channel[c].octave); break; + case 255: // delay + channel[c].songptr += maxchannel; + channel[c].pstat = songbuf[channel[c].songptr]; + break; + case 254: // set octave + channel[c].songptr += maxchannel; + channel[c].octave = songbuf[channel[c].songptr]; + break; + case 253: // set speed + channel[c].songptr += maxchannel; + channel[c].speed = songbuf[channel[c].songptr]; + break; + case 252: // set waveform + channel[c].songptr += maxchannel; + channel[c].waveform = songbuf[channel[c].songptr] - 300; + if(c > 2) + opl->write(0xe0 + c + (c+6),channel[c].waveform); + else + opl->write(0xe0 + c,channel[c].waveform); + break; + case 251: // song end + for(i = 0; i < maxchannel; i++) channel[i].songptr = i; + songend = true; + return false; + } + + if(channel[c].songptr - c < maxnotes) + channel[c].songptr += maxchannel; + else + channel[c].songptr = c; + } while(!channel[c].pstat); + } + + return !songend; +} + +void CmkjPlayer::rewind(int subsong) +{ + int i; + + for(i = 0; i < maxchannel; i++) { + channel[i].pstat = 0; + channel[i].speed = 0; + channel[i].waveform = 0; + channel[i].songptr = i; + channel[i].octave = 4; + } + + songend = false; +} + +float CmkjPlayer::getrefresh() +{ + return 100.0f; +} diff --git a/plugins/adplug/adplug/mkj.h b/plugins/adplug/adplug/mkj.h new file mode 100644 index 00000000..a7a2ffad --- /dev/null +++ b/plugins/adplug/adplug/mkj.h @@ -0,0 +1,50 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * mkj.h - MKJamz Player, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "player.h" + +class CmkjPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CmkjPlayer(Copl *newopl) + : CPlayer(newopl), songbuf(0) + { } + ~CmkjPlayer() + { if(songbuf) delete [] songbuf; } + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype() + { return std::string("MKJamz Audio File"); } + +private: + short maxchannel,maxnotes,*songbuf; + bool songend; + + struct { + short defined,songptr,octave,waveform,pstat,speed,delay; + } channel[9]; +}; diff --git a/plugins/adplug/adplug/msc.cpp b/plugins/adplug/adplug/msc.cpp new file mode 100644 index 00000000..3702adcf --- /dev/null +++ b/plugins/adplug/adplug/msc.cpp @@ -0,0 +1,313 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * msc.c - MSC Player by Lubomir Bulej (pallas@kadan.cz) + */ + +#include <stdio.h> +#include <string.h> + +#include "msc.h" +#include "debug.h" + +const unsigned char CmscPlayer::msc_signature [MSC_SIGN_LEN] = { + 'C', 'e', 'r', 'e', 's', ' ', '\x13', ' ', + 'M', 'S', 'C', 'p', 'l', 'a', 'y', ' ' }; + +/*** public methods *************************************/ + +CPlayer *CmscPlayer::factory (Copl * newopl) +{ + return new CmscPlayer (newopl); +} + +CmscPlayer::CmscPlayer(Copl * newopl) : CPlayer (newopl) +{ + desc = NULL; + msc_data = NULL; + raw_data = NULL; + nr_blocks = 0; +} + +CmscPlayer::~CmscPlayer() +{ + if (raw_data != NULL) + delete [] raw_data; + + if (msc_data != NULL) { + // free compressed blocks + for (int blk_num = 0; blk_num < nr_blocks; blk_num++) { + if (msc_data [blk_num].mb_data != NULL) + delete [] msc_data [blk_num].mb_data; + } + + delete [] msc_data; + } + + if (desc != NULL) + delete [] desc; +} + +bool CmscPlayer::load(const std::string & filename, const CFileProvider & fp) +{ + binistream * bf; + msc_header hdr; + + // open and validate the file + bf = fp.open (filename); + if (! bf) + return false; + + if (! load_header (bf, & hdr)) { + fp.close (bf); + return false; + } + + // get stuff from the header + version = hdr.mh_ver; + timer_div = hdr.mh_timer; + nr_blocks = hdr.mh_nr_blocks; + block_len = hdr.mh_block_len; + + if (! nr_blocks) { + fp.close (bf); + return false; + } + + // load compressed data blocks + msc_data = new msc_block [nr_blocks]; + raw_data = new u8 [block_len]; + + for (int blk_num = 0; blk_num < nr_blocks; blk_num++) { + msc_block blk; + + blk.mb_length = bf->readInt (2); + blk.mb_data = new u8 [blk.mb_length]; + for (int oct_num = 0; oct_num < blk.mb_length; oct_num++) { + blk.mb_data [oct_num] = bf->readInt (1); + } + + msc_data [blk_num] = blk; + } + + // clean up & initialize + fp.close (bf); + rewind (0); + + return true; +} + +bool CmscPlayer::update() +{ + // output data + while (! delay) { + u8 cmnd; + u8 data; + + // decode data + if (! decode_octet (& cmnd)) + return false; + + if (! decode_octet (& data)) + return false; + + // check for special commands + switch (cmnd) { + + // delay + case 0xff: + delay = 1 + (u8) (data - 1); + break; + + // play command & data + default: + opl->write (cmnd, data); + + } // command switch + } // play pass + + + // count delays + if (delay) + delay--; + + // advance player position + play_pos++; + return true; +} + +void CmscPlayer::rewind(int subsong) +{ + // reset state + dec_prefix = 0; + block_num = 0; + block_pos = 0; + play_pos = 0; + raw_pos = 0; + delay = 0; + + // init the OPL chip and go to OPL2 mode + opl->init(); + opl->write(1, 32); +} + +float CmscPlayer::getrefresh() +{ + // PC timer oscillator frequency / wait register + return 1193180 / (float) (timer_div ? timer_div : 0xffff); +} + +std::string CmscPlayer::gettype() +{ + char vstr [40]; + + sprintf(vstr, "AdLib MSCplay (version %d)", version); + return std::string (vstr); +} + +/*** private methods *************************************/ + +bool CmscPlayer::load_header(binistream * bf, msc_header * hdr) +{ + // check signature + bf->readString ((char *) hdr->mh_sign, sizeof (hdr->mh_sign)); + if (memcmp (msc_signature, hdr->mh_sign, MSC_SIGN_LEN) != 0) + return false; + + // check version + hdr->mh_ver = bf->readInt (2); + if (hdr->mh_ver != 0) + return false; + + bf->readString ((char *) hdr->mh_desc, sizeof (hdr->mh_desc)); + hdr->mh_timer = bf->readInt (2); + hdr->mh_nr_blocks = bf->readInt (2); + hdr->mh_block_len = bf->readInt (2); + return true; +} + +bool CmscPlayer::decode_octet(u8 * output) +{ + msc_block blk; // compressed data block + + if (block_num >= nr_blocks) + return false; + + blk = msc_data [block_num]; + while (1) { + u8 octet; // decoded octet + u8 len_corr; // length correction + + // advance to next block if necessary + if (block_pos >= blk.mb_length && dec_len == 0) { + block_num++; + if (block_num >= nr_blocks) + return false; + + blk = msc_data [block_num]; + block_pos = 0; + raw_pos = 0; + } + + // decode the compressed music data + switch (dec_prefix) { + + // decode prefix + case 155: + case 175: + octet = blk.mb_data [block_pos++]; + if (octet == 0) { + // invalid prefix, output original + octet = dec_prefix; + dec_prefix = 0; + break; + } + + // isolate length and distance + dec_len = (octet & 0x0F); + len_corr = 2; + + dec_dist = (octet & 0xF0) >> 4; + if (dec_prefix == 155) + dec_dist++; + + // next decode step for respective prefix type + dec_prefix++; + continue; + + + // check for extended length + case 156: + if (dec_len == 15) + dec_len += blk.mb_data [block_pos++]; + + // add length correction and go for copy mode + dec_len += len_corr; + dec_prefix = 255; + continue; + + + // get extended distance + case 176: + dec_dist += 17 + 16 * blk.mb_data [block_pos++]; + len_corr = 3; + + // check for extended length + dec_prefix = 156; + continue; + + + // prefix copy mode + case 255: + if((int)raw_pos >= dec_dist) + octet = raw_data [raw_pos - dec_dist]; + else { + AdPlug_LogWrite("error! read before raw_data buffer.\n"); + octet = 0; + } + + dec_len--; + if (dec_len == 0) { + // back to normal mode + dec_prefix = 0; + } + + break; + + + // normal mode + default: + octet = blk.mb_data [block_pos++]; + if (octet == 155 || octet == 175) { + // it's a prefix, restart + dec_prefix = octet; + continue; + } + } // prefix switch + + + // output the octet + if (output != NULL) + *output = octet; + + raw_data [raw_pos++] = octet; + break; + }; // decode pass + + return true; +} diff --git a/plugins/adplug/adplug/msc.h b/plugins/adplug/adplug/msc.h new file mode 100644 index 00000000..a42ec750 --- /dev/null +++ b/plugins/adplug/adplug/msc.h @@ -0,0 +1,87 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * msc.h - MSC Player by Lubomir Bulej (pallas@kadan.cz) + */ + +#include "player.h" + +#define MSC_SIGN_LEN 16 +#define MSC_DESC_LEN 64 + +class CmscPlayer: public CPlayer +{ + public: + static CPlayer * factory(Copl * newopl); + + CmscPlayer(Copl * newopl); + ~CmscPlayer(); + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype (); + + protected: + typedef unsigned char u8; + typedef unsigned short u16; + + struct msc_header { + u8 mh_sign [MSC_SIGN_LEN]; + u16 mh_ver; + u8 mh_desc [MSC_DESC_LEN]; + u16 mh_timer; + u16 mh_nr_blocks; + u16 mh_block_len; + }; + + struct msc_block { + u16 mb_length; + u8 * mb_data; + }; + + // file data + char * desc; // song desctiption + unsigned short version; // file version + unsigned short nr_blocks; // number of music blocks + unsigned short block_len; // maximal block length + unsigned short timer_div; // timer divisor + msc_block * msc_data; // compressed music data + + // decoder state + unsigned long block_num; // active block + unsigned long block_pos; // position in block + unsigned long raw_pos; // position in data buffer + u8 * raw_data; // decompression buffer + + u8 dec_prefix; // prefix / state + int dec_dist; // prefix distance + unsigned int dec_len; // prefix length + + // player state + unsigned char delay; // active delay + unsigned long play_pos; // player position + + private: + static const u8 msc_signature [MSC_SIGN_LEN]; + + bool load_header (binistream * bf, msc_header * hdr); + bool decode_octet (u8 * output); +}; diff --git a/plugins/adplug/adplug/mtk.cpp b/plugins/adplug/adplug/mtk.cpp new file mode 100644 index 00000000..410e00e0 --- /dev/null +++ b/plugins/adplug/adplug/mtk.cpp @@ -0,0 +1,141 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * mtk.cpp - MPU-401 Trakker Loader by Simon Peter (dn.tlp@gmx.net) + */ + +#include <string.h> +#include "mtk.h" + +/*** public methods **************************************/ + +CPlayer *CmtkLoader::factory(Copl *newopl) +{ + return new CmtkLoader(newopl); +} + +bool CmtkLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + struct { + char id[18]; + unsigned short crc,size; + } header; + struct mtkdata { + char songname[34],composername[34],instname[0x80][34]; + unsigned char insts[0x80][12],order[0x80],dummy,patterns[0x32][0x40][9]; + } *data; + unsigned char *cmp,*org; + unsigned int i; + unsigned long cmpsize,cmpptr=0,orgptr=0; + unsigned short ctrlbits=0,ctrlmask=0,cmd,cnt,offs; + + // read header + f->readString(header.id, 18); + header.crc = f->readInt(2); + header.size = f->readInt(2); + + // file validation section + if(strncmp(header.id,"mpu401tr\x92kk\xeer@data",18)) + { fp.close(f); return false; } + + // load section + cmpsize = fp.filesize(f) - 22; + cmp = new unsigned char[cmpsize]; + org = new unsigned char[header.size]; + for(i = 0; i < cmpsize; i++) cmp[i] = f->readInt(1); + fp.close(f); + + while(cmpptr < cmpsize) { // decompress + ctrlmask >>= 1; + if(!ctrlmask) { + ctrlbits = cmp[cmpptr] + (cmp[cmpptr + 1] << 8); + cmpptr += 2; + ctrlmask = 0x8000; + } + if(!(ctrlbits & ctrlmask)) { // uncompressed data + if(orgptr >= header.size) + goto err; + + org[orgptr] = cmp[cmpptr]; + orgptr++; cmpptr++; + continue; + } + + // compressed data + cmd = (cmp[cmpptr] >> 4) & 0x0f; + cnt = cmp[cmpptr] & 0x0f; + cmpptr++; + switch(cmd) { + case 0: + if(orgptr + cnt > header.size) goto err; + cnt += 3; + memset(&org[orgptr],cmp[cmpptr],cnt); + cmpptr++; orgptr += cnt; + break; + + case 1: + if(orgptr + cnt > header.size) goto err; + cnt += (cmp[cmpptr] << 4) + 19; + memset(&org[orgptr],cmp[++cmpptr],cnt); + cmpptr++; orgptr += cnt; + break; + + case 2: + if(orgptr + cnt > header.size) goto err; + offs = (cnt+3) + (cmp[cmpptr] << 4); + cnt = cmp[++cmpptr] + 16; cmpptr++; + memcpy(&org[orgptr],&org[orgptr - offs],cnt); + orgptr += cnt; + break; + + default: + if(orgptr + cmd > header.size) goto err; + offs = (cnt+3) + (cmp[cmpptr++] << 4); + memcpy(&org[orgptr],&org[orgptr-offs],cmd); + orgptr += cmd; + break; + } + } + delete [] cmp; + data = (struct mtkdata *) org; + + // convert to HSC replay data + memset(title,0,34); strncpy(title,data->songname+1,33); + memset(composer,0,34); strncpy(composer,data->composername+1,33); + memset(instname,0,0x80*34); + for(i=0;i<0x80;i++) + strncpy(instname[i],data->instname[i]+1,33); + memcpy(instr,data->insts,0x80 * 12); + memcpy(song,data->order,0x80); + memcpy(patterns,data->patterns,header.size-6084); + for (i=0;i<128;i++) { // correct instruments + instr[i][2] ^= (instr[i][2] & 0x40) << 1; + instr[i][3] ^= (instr[i][3] & 0x40) << 1; + instr[i][11] >>= 4; // make unsigned + } + + delete [] org; + rewind(0); + return true; + + err: + delete [] cmp; + delete [] org; + return false; +} diff --git a/plugins/adplug/adplug/mtk.h b/plugins/adplug/adplug/mtk.h new file mode 100644 index 00000000..175576a2 --- /dev/null +++ b/plugins/adplug/adplug/mtk.h @@ -0,0 +1,50 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * mtk.h - MPU-401 Trakker Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include "hsc.h" + +class CmtkLoader: public ChscPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CmtkLoader(Copl *newopl) + : ChscPlayer(newopl) + { + mtkmode = 1; + }; + + bool load(const std::string &filename, const CFileProvider &fp); + + std::string gettype() + { return std::string("MPU-401 Trakker"); }; + std::string gettitle() + { return std::string(title); }; + std::string getauthor() + { return std::string(composer); }; + unsigned int getinstruments() + { return 128; }; + std::string getinstrument(unsigned int n) + { return std::string(instname[n]); }; + + private: + char title[34],composer[34],instname[0x80][34]; +}; diff --git a/plugins/adplug/adplug/opl.h b/plugins/adplug/adplug/opl.h new file mode 100644 index 00000000..401bcb98 --- /dev/null +++ b/plugins/adplug/adplug/opl.h @@ -0,0 +1,69 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * opl.h - OPL base class, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_OPL +#define H_ADPLUG_OPL + +class Copl +{ + public: + typedef enum { + TYPE_OPL2, TYPE_OPL3, TYPE_DUAL_OPL2 + } ChipType; + + Copl() + : currChip(0), currType(TYPE_OPL2) + { + } + + virtual ~Copl() + { + } + + virtual void write(int reg, int val) = 0; // combined register select + data write + virtual void setchip(int n) // select OPL chip + { + if(n < 2) + currChip = n; + } + + virtual int getchip() // returns current OPL chip + { + return currChip; + } + + virtual void init(void) = 0; // reinitialize OPL chip(s) + + // return this OPL chip's type + ChipType gettype() + { + return currType; + } + + // Emulation only: fill buffer + virtual void update(short *buf, int samples) {} + + protected: + int currChip; // currently selected OPL chip number + ChipType currType; // this OPL chip's type +}; + +#endif diff --git a/plugins/adplug/adplug/player.cpp b/plugins/adplug/adplug/player.cpp new file mode 100644 index 00000000..9a0061af --- /dev/null +++ b/plugins/adplug/adplug/player.cpp @@ -0,0 +1,70 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * player.cpp - Replayer base class, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "player.h" +#include "adplug.h" +#include "silentopl.h" + +/***** CPlayer *****/ + +const unsigned short CPlayer::note_table[12] = + {363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647, 686}; + +const unsigned char CPlayer::op_table[9] = + {0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12}; + +CPlayer::CPlayer(Copl *newopl) + : opl(newopl), db(CAdPlug::database) +{ +} + +CPlayer::~CPlayer() +{ +} + +unsigned long CPlayer::songlength(int subsong) +{ + CSilentopl tempopl; + Copl *saveopl = opl; + float slength = 0.0f; + + // save original OPL from being overwritten + opl = &tempopl; + + // get song length + rewind(subsong); + while(update() && slength < 600000) // song length limit: 10 minutes + slength += 1000.0f / getrefresh(); + rewind(subsong); + + // restore original OPL and return + opl = saveopl; + return (unsigned long)slength; +} + +void CPlayer::seek(unsigned long ms) +{ + float pos = 0.0f; + + rewind(); + while(pos < ms && update()) // seek to new position + pos += 1000/getrefresh(); +} diff --git a/plugins/adplug/adplug/player.h b/plugins/adplug/adplug/player.h new file mode 100644 index 00000000..6b99c34b --- /dev/null +++ b/plugins/adplug/adplug/player.h @@ -0,0 +1,83 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * player.h - Replayer base class, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_PLAYER +#define H_ADPLUG_PLAYER + +#include <string> + +#include "fprovide.h" +#include "opl.h" +#include "database.h" + +class CPlayer +{ +public: + CPlayer(Copl *newopl); + virtual ~CPlayer(); + +/***** Operational methods *****/ + void seek(unsigned long ms); + + virtual bool load(const std::string &fn, // loads file + const CFileProvider &fp = CProvider_Filesystem()) = 0; + virtual bool update() = 0; // executes replay code for 1 tick + virtual void rewind(int subsong = -1) = 0; // rewinds to specified subsong + virtual float getrefresh() = 0; // returns needed timer refresh rate + +/***** Informational methods *****/ + unsigned long songlength(int subsong = -1); + + virtual std::string gettype() = 0; // returns file type + virtual std::string gettitle() // returns song title + { return std::string(); } + virtual std::string getauthor() // returns song author name + { return std::string(); } + virtual std::string getdesc() // returns song description + { return std::string(); } + virtual unsigned int getpatterns() // returns number of patterns + { return 0; } + virtual unsigned int getpattern() // returns currently playing pattern + { return 0; } + virtual unsigned int getorders() // returns size of orderlist + { return 0; } + virtual unsigned int getorder() // returns currently playing song position + { return 0; } + virtual unsigned int getrow() // returns currently playing row + { return 0; } + virtual unsigned int getspeed() // returns current song speed + { return 0; } + virtual unsigned int getsubsongs() // returns number of subsongs + { return 1; } + virtual unsigned int getinstruments() // returns number of instruments + { return 0; } + virtual std::string getinstrument(unsigned int n) // returns n-th instrument name + { return std::string(); } + +protected: + Copl *opl; // our OPL chip + CAdPlugDatabase *db; // AdPlug Database + + static const unsigned short note_table[12]; // standard adlib note table + static const unsigned char op_table[9]; // the 9 operators as expected by the OPL +}; + +#endif diff --git a/plugins/adplug/adplug/players.cpp b/plugins/adplug/adplug/players.cpp new file mode 100644 index 00000000..09c32b73 --- /dev/null +++ b/plugins/adplug/adplug/players.cpp @@ -0,0 +1,105 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * players.h - Players enumeration, by Simon Peter <dn.tlp@gmx.net> + */ + +#include <stdlib.h> +#include <string.h> + +#include "players.h" + +/***** CPlayerDesc *****/ + +CPlayerDesc::CPlayerDesc() + : factory(0), extensions(0), extlength(0) +{ +} + +CPlayerDesc::CPlayerDesc(const CPlayerDesc &pd) + : factory(pd.factory), filetype(pd.filetype), extlength(pd.extlength) +{ + if(pd.extensions) { + extensions = (char *)malloc(extlength); + memcpy(extensions, pd.extensions, extlength); + } else + extensions = 0; +} + +CPlayerDesc::CPlayerDesc(Factory f, const std::string &type, const char *ext) + : factory(f), filetype(type), extensions(0) +{ + const char *i = ext; + + // Determine length of passed extensions list + while(*i) i += strlen(i) + 1; + extlength = i - ext + 1; // length = difference between last and first char + 1 + + extensions = (char *)malloc(extlength); + memcpy(extensions, ext, extlength); +} + +CPlayerDesc::~CPlayerDesc() +{ + if(extensions) free(extensions); +} + +void CPlayerDesc::add_extension(const char *ext) +{ + unsigned long newlength = extlength + strlen(ext) + 1; + + extensions = (char *)realloc(extensions, newlength); + strcpy(extensions + extlength - 1, ext); + extensions[newlength - 1] = '\0'; + extlength = newlength; +} + +const char *CPlayerDesc::get_extension(unsigned int n) const +{ + const char *i = extensions; + unsigned int j; + + for(j = 0; j < n && (*i); j++, i += strlen(i) + 1) ; + return (*i != '\0' ? i : 0); +} + +/***** CPlayers *****/ + +const CPlayerDesc *CPlayers::lookup_filetype(const std::string &ftype) const +{ + const_iterator i; + + for(i = begin(); i != end(); i++) + if((*i)->filetype == ftype) + return *i; + + return 0; +} + +const CPlayerDesc *CPlayers::lookup_extension(const std::string &extension) const +{ + const_iterator i; + unsigned int j; + + for(i = begin(); i != end(); i++) + for(j = 0; (*i)->get_extension(j); j++) + if(!stricmp(extension.c_str(), (*i)->get_extension(j))) + return *i; + + return 0; +} diff --git a/plugins/adplug/adplug/players.h b/plugins/adplug/adplug/players.h new file mode 100644 index 00000000..9d686aef --- /dev/null +++ b/plugins/adplug/adplug/players.h @@ -0,0 +1,60 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * players.h - Players enumeration, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_PLAYERS +#define H_ADPLUG_PLAYERS + +#include <string> +#include <list> + +#include "opl.h" +#include "player.h" + +class CPlayerDesc +{ +public: + typedef CPlayer *(*Factory)(Copl *); + + Factory factory; + std::string filetype; + + CPlayerDesc(); + CPlayerDesc(const CPlayerDesc &pd); + CPlayerDesc(Factory f, const std::string &type, const char *ext); + + ~CPlayerDesc(); + + void add_extension(const char *ext); + const char *get_extension(unsigned int n) const; + +private: + char *extensions; + unsigned long extlength; +}; + +class CPlayers: public std::list<const CPlayerDesc *> +{ +public: + const CPlayerDesc *lookup_filetype(const std::string &ftype) const; + const CPlayerDesc *lookup_extension(const std::string &extension) const; +}; + +#endif diff --git a/plugins/adplug/adplug/protrack.cpp b/plugins/adplug/adplug/protrack.cpp new file mode 100644 index 00000000..86716ef8 --- /dev/null +++ b/plugins/adplug/adplug/protrack.cpp @@ -0,0 +1,820 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * protrack.cpp - Generic Protracker Player + * + * NOTES: + * This is a generic Protracker-based formats player. It offers all Protracker + * features, plus a good set of extensions to be compatible to other Protracker + * derivatives. It is derived from the former SA2 player. If you got a + * Protracker-like format, this is most certainly the player you want to use. + */ + +#include <string.h> +#include "protrack.h" +#include "debug.h" + +#define SPECIALARPLEN 256 // Standard length of special arpeggio lists +#define JUMPMARKER 0x80 // Orderlist jump marker + +// SA2 compatible adlib note table +const unsigned short CmodPlayer::sa2_notetable[12] = + {340,363,385,408,432,458,485,514,544,577,611,647}; + +// SA2 compatible vibrato rate table +const unsigned char CmodPlayer::vibratotab[32] = + {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}; + +/*** public methods *************************************/ + +CmodPlayer::CmodPlayer(Copl *newopl) + : CPlayer(newopl), inst(0), order(0), arplist(0), arpcmd(0), initspeed(6), + nop(0), activechan(0xffffffff), flags(Standard), curchip(opl->getchip()), + nrows(0), npats(0), nchans(0) +{ + realloc_order(128); + realloc_patterns(64, 64, 9); + realloc_instruments(250); + init_notetable(sa2_notetable); +} + +CmodPlayer::~CmodPlayer() +{ + dealloc(); +} + +bool CmodPlayer::update() +{ + unsigned char pattbreak=0, donote, pattnr, chan, oplchan, info1, + info2, info, pattern_delay; + unsigned short track; + unsigned long row; + + if(!speed) // song full stop + return !songend; + + // effect handling (timer dependant) + for(chan = 0; chan < nchans; chan++) { + oplchan = set_opl_chip(chan); + + if(arplist && arpcmd && inst[channel[chan].inst].arpstart) // special arpeggio + if(channel[chan].arpspdcnt) + channel[chan].arpspdcnt--; + else + if(arpcmd[channel[chan].arppos] != 255) { + switch(arpcmd[channel[chan].arppos]) { + case 252: channel[chan].vol1 = arplist[channel[chan].arppos]; // set volume + if(channel[chan].vol1 > 63) // ????? + channel[chan].vol1 = 63; + channel[chan].vol2 = channel[chan].vol1; + setvolume(chan); + break; + case 253: channel[chan].key = 0; setfreq(chan); break; // release sustaining note + case 254: channel[chan].arppos = arplist[channel[chan].arppos]; break; // arpeggio loop + default: if(arpcmd[channel[chan].arppos]) { + if(arpcmd[channel[chan].arppos] / 10) + opl->write(0xe3 + op_table[oplchan], arpcmd[channel[chan].arppos] / 10 - 1); + if(arpcmd[channel[chan].arppos] % 10) + opl->write(0xe0 + op_table[oplchan], (arpcmd[channel[chan].arppos] % 10) - 1); + if(arpcmd[channel[chan].arppos] < 10) // ????? + opl->write(0xe0 + op_table[oplchan], arpcmd[channel[chan].arppos] - 1); + } + } + if(arpcmd[channel[chan].arppos] != 252) { + if(arplist[channel[chan].arppos] <= 96) + setnote(chan,channel[chan].note + arplist[channel[chan].arppos]); + if(arplist[channel[chan].arppos] >= 100) + setnote(chan,arplist[channel[chan].arppos] - 100); + } else + setnote(chan,channel[chan].note); + setfreq(chan); + if(arpcmd[channel[chan].arppos] != 255) + channel[chan].arppos++; + channel[chan].arpspdcnt = inst[channel[chan].inst].arpspeed - 1; + } + + info1 = channel[chan].info1; + info2 = channel[chan].info2; + if(flags & Decimal) + info = channel[chan].info1 * 10 + channel[chan].info2; + else + info = (channel[chan].info1 << 4) + channel[chan].info2; + switch(channel[chan].fx) { + case 0: if(info) { // arpeggio + if(channel[chan].trigger < 2) + channel[chan].trigger++; + else + channel[chan].trigger = 0; + switch(channel[chan].trigger) { + case 0: setnote(chan,channel[chan].note); break; + case 1: setnote(chan,channel[chan].note + info1); break; + case 2: setnote(chan,channel[chan].note + info2); + } + setfreq(chan); + } + break; + case 1: slide_up(chan,info); setfreq(chan); break; // slide up + case 2: slide_down(chan,info); setfreq(chan); break; // slide down + case 3: tone_portamento(chan,channel[chan].portainfo); break; // tone portamento + case 4: vibrato(chan,channel[chan].vibinfo1,channel[chan].vibinfo2); break; // vibrato + case 5: // tone portamento & volume slide + case 6: if(channel[chan].fx == 5) // vibrato & volume slide + tone_portamento(chan,channel[chan].portainfo); + else + vibrato(chan,channel[chan].vibinfo1,channel[chan].vibinfo2); + case 10: if(del % 4) // SA2 volume slide + break; + if(info1) + vol_up(chan,info1); + else + vol_down(chan,info2); + setvolume(chan); + break; + case 14: if(info1 == 3) // retrig note + if(!(del % (info2+1))) + playnote(chan); + break; + case 16: if(del % 4) // AMD volume slide + break; + if(info1) + vol_up_alt(chan,info1); + else + vol_down_alt(chan,info2); + setvolume(chan); + break; + case 20: // RAD volume slide + if(info < 50) + vol_down_alt(chan,info); + else + vol_up_alt(chan,info - 50); + setvolume(chan); + break; + case 26: // volume slide + if(info1) + vol_up(chan,info1); + else + vol_down(chan,info2); + setvolume(chan); + break; + case 28: + if (info1) { + slide_up(chan,1); channel[chan].info1--; + } + if (info2) { + slide_down(chan,1); channel[chan].info2--; + } + setfreq(chan); + break; + } + } + + if(del) { // speed compensation + del--; + return !songend; + } + + // arrangement handling + if(!resolve_order()) return !songend; + pattnr = order[ord]; + + if(!rw) AdPlug_LogWrite("\nCmodPlayer::update(): Pattern: %d, Order: %d\n", pattnr, ord); + AdPlug_LogWrite("CmodPlayer::update():%3d|", rw); + + // play row + pattern_delay = 0; + row = rw; + for(chan = 0; chan < nchans; chan++) { + oplchan = set_opl_chip(chan); + + if(!(activechan >> (31 - chan)) & 1) { // channel active? + AdPlug_LogWrite("N/A|"); + continue; + } + if(!(track = trackord[pattnr][chan])) { // resolve track + AdPlug_LogWrite("------------|"); + continue; + } else + track--; + + AdPlug_LogWrite("%3d%3d%2X%2X%2X|", tracks[track][row].note, + tracks[track][row].inst, tracks[track][row].command, + tracks[track][row].param1, tracks[track][row].param2); + + donote = 0; + if(tracks[track][row].inst) { + channel[chan].inst = tracks[track][row].inst - 1; + if (!(flags & Faust)) { + channel[chan].vol1 = 63 - (inst[channel[chan].inst].data[10] & 63); + channel[chan].vol2 = 63 - (inst[channel[chan].inst].data[9] & 63); + setvolume(chan); + } + } + + if(tracks[track][row].note && tracks[track][row].command != 3) { // no tone portamento + channel[chan].note = tracks[track][row].note; + setnote(chan,tracks[track][row].note); + channel[chan].nextfreq = channel[chan].freq; + channel[chan].nextoct = channel[chan].oct; + channel[chan].arppos = inst[channel[chan].inst].arpstart; + channel[chan].arpspdcnt = 0; + if(tracks[track][row].note != 127) // handle key off + donote = 1; + } + channel[chan].fx = tracks[track][row].command; + channel[chan].info1 = tracks[track][row].param1; + channel[chan].info2 = tracks[track][row].param2; + + if(donote) + playnote(chan); + + // command handling (row dependant) + info1 = channel[chan].info1; + info2 = channel[chan].info2; + if(flags & Decimal) + info = channel[chan].info1 * 10 + channel[chan].info2; + else + info = (channel[chan].info1 << 4) + channel[chan].info2; + switch(channel[chan].fx) { + case 3: // tone portamento + if(tracks[track][row].note) { + if(tracks[track][row].note < 13) + channel[chan].nextfreq = notetable[tracks[track][row].note - 1]; + else + if(tracks[track][row].note % 12 > 0) + channel[chan].nextfreq = notetable[(tracks[track][row].note % 12) - 1]; + else + channel[chan].nextfreq = notetable[11]; + channel[chan].nextoct = (tracks[track][row].note - 1) / 12; + if(tracks[track][row].note == 127) { // handle key off + channel[chan].nextfreq = channel[chan].freq; + channel[chan].nextoct = channel[chan].oct; + } + } + if(info) // remember vars + channel[chan].portainfo = info; + break; + + case 4: // vibrato (remember vars) + if(info) { + channel[chan].vibinfo1 = info1; + channel[chan].vibinfo2 = info2; + } + break; + + case 7: tempo = info; break; // set tempo + + case 8: channel[chan].key = 0; setfreq(chan); break; // release sustaining note + + case 9: // set carrier/modulator volume + if(info1) + channel[chan].vol1 = info1 * 7; + else + channel[chan].vol2 = info2 * 7; + setvolume(chan); + break; + + case 11: // position jump + pattbreak = 1; rw = 0; if(info < ord) songend = 1; ord = info; break; + + case 12: // set volume + channel[chan].vol1 = info; + channel[chan].vol2 = info; + if(channel[chan].vol1 > 63) + channel[chan].vol1 = 63; + if(channel[chan].vol2 > 63) + channel[chan].vol2 = 63; + setvolume(chan); + break; + + case 13: // pattern break + if(!pattbreak) { pattbreak = 1; rw = info; ord++; } break; + + case 14: // extended command + switch(info1) { + case 0: // define cell-tremolo + if(info2) + regbd |= 128; + else + regbd &= 127; + opl->write(0xbd,regbd); + break; + + case 1: // define cell-vibrato + if(info2) + regbd |= 64; + else + regbd &= 191; + opl->write(0xbd,regbd); + break; + + case 4: // increase volume fine + vol_up_alt(chan,info2); + setvolume(chan); + break; + + case 5: // decrease volume fine + vol_down_alt(chan,info2); + setvolume(chan); + break; + + case 6: // manual slide up + slide_up(chan,info2); + setfreq(chan); + break; + + case 7: // manual slide down + slide_down(chan,info2); + setfreq(chan); + break; + + case 8: // pattern delay (rows) + pattern_delay = info2 * speed; + break; + } + break; + + case 15: // SA2 set speed + if(info <= 0x1f) + speed = info; + if(info >= 0x32) + tempo = info; + if(!info) + songend = 1; + break; + + case 17: // alternate set volume + channel[chan].vol1 = info; + if(channel[chan].vol1 > 63) + channel[chan].vol1 = 63; + if(inst[channel[chan].inst].data[0] & 1) { + channel[chan].vol2 = info; + if(channel[chan].vol2 > 63) + channel[chan].vol2 = 63; + } + + setvolume(chan); + break; + + case 18: // AMD set speed + if(info <= 31 && info > 0) + speed = info; + if(info > 31 || !info) + tempo = info; + break; + + case 19: // RAD/A2M set speed + speed = (info ? info : info + 1); + break; + + case 21: // set modulator volume + if(info <= 63) + channel[chan].vol2 = info; + else + channel[chan].vol2 = 63; + setvolume(chan); + break; + + case 22: // set carrier volume + if(info <= 63) + channel[chan].vol1 = info; + else + channel[chan].vol1 = 63; + setvolume(chan); + break; + + case 23: // fine frequency slide up + slide_up(chan,info); + setfreq(chan); + break; + + case 24: // fine frequency slide down + slide_down(chan,info); + setfreq(chan); + break; + + case 25: // set carrier/modulator waveform + if(info1 != 0x0f) + opl->write(0xe3 + op_table[oplchan],info1); + if(info2 != 0x0f) + opl->write(0xe0 + op_table[oplchan],info2); + break; + + case 27: // set chip tremolo/vibrato + if(info1) + regbd |= 128; + else + regbd &= 127; + if(info2) + regbd |= 64; + else + regbd &= 191; + opl->write(0xbd,regbd); + break; + + case 29: // pattern delay (frames) + pattern_delay = info; + break; + } + } + + // speed compensation + del = speed - 1 + pattern_delay; + + if(!pattbreak) { // next row (only if no manual advance) + rw++; + if(rw >= nrows) { + rw = 0; + ord++; + } + } + + resolve_order(); // so we can report songend right away + AdPlug_LogWrite("\n"); + return !songend; +} + +unsigned char CmodPlayer::set_opl_chip(unsigned char chan) + /* + * Sets OPL chip according to channel number. Channels 0-8 are on first chip, + * channels 9-17 are on second chip. Returns corresponding OPL channel + * number. + */ +{ + int newchip = chan < 9 ? 0 : 1; + + if(newchip != curchip) { + opl->setchip(newchip); + curchip = newchip; + } + + return chan % 9; +} + +bool CmodPlayer::resolve_order() + /* + * Resolves current orderlist entry, checking for jumps and loops. + * + * Returns true on correct processing, false if immediate recursive loop + * has been detected. + */ +{ + if(ord < length) { + while(order[ord] >= JUMPMARKER) { // jump to order + unsigned long neword = order[ord] - JUMPMARKER; + + if(neword <= ord) songend = 1; + if(neword == ord) return false; + ord = neword; + } + } else { + songend = 1; + ord = restartpos; + } + + return true; +} + +void CmodPlayer::rewind(int subsong) +{ + unsigned long i; + + // Reset playing variables + songend = del = ord = rw = regbd = 0; + tempo = bpm; speed = initspeed; + + // Reset channel data + memset(channel,0,sizeof(Channel)*nchans); + + // Compute number of patterns, if needed + if(!nop) + for(i=0;i<length;i++) + nop = (order[i] > nop ? order[i] : nop); + + opl->init(); // Reset OPL chip + opl->write(1, 32); // Go to ym3812 mode + + // Enable OPL3 extensions if flagged + if(flags & Opl3) { + opl->setchip(1); + opl->write(1, 32); + opl->write(5, 1); + opl->setchip(0); + } + + // Enable tremolo/vibrato depth if flagged + if(flags & Tremolo) regbd |= 128; + if(flags & Vibrato) regbd |= 64; + if(regbd) opl->write(0xbd, regbd); +} + +float CmodPlayer::getrefresh() +{ + return (float) (tempo / 2.5); +} + +void CmodPlayer::init_trackord() +{ + unsigned long i; + + for(i=0;i<npats*nchans;i++) + trackord[i / nchans][i % nchans] = i + 1; +} + +bool CmodPlayer::init_specialarp() +{ + arplist = new unsigned char[SPECIALARPLEN]; + arpcmd = new unsigned char[SPECIALARPLEN]; + + return true; +} + +void CmodPlayer::init_notetable(const unsigned short *newnotetable) +{ + memcpy(notetable, newnotetable, 12 * 2); +} + +bool CmodPlayer::realloc_order(unsigned long len) +{ + if(order) delete [] order; + order = new unsigned char[len]; + return true; +} + +bool CmodPlayer::realloc_patterns(unsigned long pats, unsigned long rows, unsigned long chans) +{ + unsigned long i; + + dealloc_patterns(); + + // set new number of tracks, rows and channels + npats = pats; nrows = rows; nchans = chans; + + // alloc new patterns + tracks = new Tracks *[pats * chans]; + for(i=0;i<pats*chans;i++) tracks[i] = new Tracks[rows]; + trackord = new unsigned short *[pats]; + for(i=0;i<pats;i++) trackord[i] = new unsigned short[chans]; + channel = new Channel[chans]; + + // initialize new patterns + for(i=0;i<pats*chans;i++) memset(tracks[i],0,sizeof(Tracks)*rows); + for(i=0;i<pats;i++) memset(trackord[i],0,chans*2); + + return true; +} + +void CmodPlayer::dealloc_patterns() +{ + unsigned long i; + + // dealloc everything previously allocated + if(npats && nrows && nchans) { + for(i=0;i<npats*nchans;i++) delete [] tracks[i]; + delete [] tracks; + for(i=0;i<npats;i++) delete [] trackord[i]; + delete [] trackord; + delete [] channel; + } +} + +bool CmodPlayer::realloc_instruments(unsigned long len) +{ + // dealloc previous instance, if any + if(inst) delete [] inst; + + inst = new Instrument[len]; + memset(inst,0,sizeof(Instrument)*len); // reset instruments + return true; +} + +void CmodPlayer::dealloc() +{ + if(inst) delete [] inst; + if(order) delete [] order; + if(arplist) delete [] arplist; + if(arpcmd) delete [] arpcmd; + dealloc_patterns(); +} + +/*** private methods *************************************/ + +void CmodPlayer::setvolume(unsigned char chan) +{ + unsigned char oplchan = set_opl_chip(chan); + + if(flags & Faust) + setvolume_alt(chan); + else { + opl->write(0x40 + op_table[oplchan], 63-channel[chan].vol2 + (inst[channel[chan].inst].data[9] & 192)); + opl->write(0x43 + op_table[oplchan], 63-channel[chan].vol1 + (inst[channel[chan].inst].data[10] & 192)); + } +} + +void CmodPlayer::setvolume_alt(unsigned char chan) +{ + unsigned char oplchan = set_opl_chip(chan); + unsigned char ivol2 = inst[channel[chan].inst].data[9] & 63; + unsigned char ivol1 = inst[channel[chan].inst].data[10] & 63; + + opl->write(0x40 + op_table[oplchan], (((63 - channel[chan].vol2 & 63) + ivol2) >> 1) + (inst[channel[chan].inst].data[9] & 192)); + opl->write(0x43 + op_table[oplchan], (((63 - channel[chan].vol1 & 63) + ivol1) >> 1) + (inst[channel[chan].inst].data[10] & 192)); +} + +void CmodPlayer::setfreq(unsigned char chan) +{ + unsigned char oplchan = set_opl_chip(chan); + + opl->write(0xa0 + oplchan, channel[chan].freq & 255); + if(channel[chan].key) + opl->write(0xb0 + oplchan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2) | 32); + else + opl->write(0xb0 + oplchan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2)); +} + +void CmodPlayer::playnote(unsigned char chan) +{ + unsigned char oplchan = set_opl_chip(chan); + unsigned char op = op_table[oplchan], insnr = channel[chan].inst; + + if(!(flags & NoKeyOn)) + opl->write(0xb0 + oplchan, 0); // stop old note + + // set instrument data + opl->write(0x20 + op, inst[insnr].data[1]); + opl->write(0x23 + op, inst[insnr].data[2]); + opl->write(0x60 + op, inst[insnr].data[3]); + opl->write(0x63 + op, inst[insnr].data[4]); + opl->write(0x80 + op, inst[insnr].data[5]); + opl->write(0x83 + op, inst[insnr].data[6]); + opl->write(0xe0 + op, inst[insnr].data[7]); + opl->write(0xe3 + op, inst[insnr].data[8]); + opl->write(0xc0 + oplchan, inst[insnr].data[0]); + opl->write(0xbd, inst[insnr].misc); // set misc. register + + // set frequency, volume & play + channel[chan].key = 1; + setfreq(chan); + + if (flags & Faust) { + channel[chan].vol2 = 63; + channel[chan].vol1 = 63; + } + setvolume(chan); +} + +void CmodPlayer::setnote(unsigned char chan, int note) +{ + if(note > 96) + if(note == 127) { // key off + channel[chan].key = 0; + setfreq(chan); + return; + } else + note = 96; + + if(note < 13) + channel[chan].freq = notetable[note - 1]; + else + if(note % 12 > 0) + channel[chan].freq = notetable[(note % 12) - 1]; + else + channel[chan].freq = notetable[11]; + channel[chan].oct = (note - 1) / 12; + channel[chan].freq += inst[channel[chan].inst].slide; // apply pre-slide +} + +void CmodPlayer::slide_down(unsigned char chan, int amount) +{ + channel[chan].freq -= amount; + if(channel[chan].freq <= 342) + if(channel[chan].oct) { + channel[chan].oct--; + channel[chan].freq <<= 1; + } else + channel[chan].freq = 342; +} + +void CmodPlayer::slide_up(unsigned char chan, int amount) +{ + channel[chan].freq += amount; + if(channel[chan].freq >= 686) + if(channel[chan].oct < 7) { + channel[chan].oct++; + channel[chan].freq >>= 1; + } else + channel[chan].freq = 686; +} + +void CmodPlayer::tone_portamento(unsigned char chan, unsigned char info) +{ + if(channel[chan].freq + (channel[chan].oct << 10) < channel[chan].nextfreq + + (channel[chan].nextoct << 10)) { + slide_up(chan,info); + if(channel[chan].freq + (channel[chan].oct << 10) > channel[chan].nextfreq + + (channel[chan].nextoct << 10)) { + channel[chan].freq = channel[chan].nextfreq; + channel[chan].oct = channel[chan].nextoct; + } + } + if(channel[chan].freq + (channel[chan].oct << 10) > channel[chan].nextfreq + + (channel[chan].nextoct << 10)) { + slide_down(chan,info); + if(channel[chan].freq + (channel[chan].oct << 10) < channel[chan].nextfreq + + (channel[chan].nextoct << 10)) { + channel[chan].freq = channel[chan].nextfreq; + channel[chan].oct = channel[chan].nextoct; + } + } + setfreq(chan); +} + +void CmodPlayer::vibrato(unsigned char chan, unsigned char speed, unsigned char depth) +{ + int i; + + if(!speed || !depth) + return; + + if(depth > 14) + depth = 14; + + for(i=0;i<speed;i++) { + channel[chan].trigger++; + while(channel[chan].trigger >= 64) + channel[chan].trigger -= 64; + if(channel[chan].trigger >= 16 && channel[chan].trigger < 48) + slide_down(chan,vibratotab[channel[chan].trigger - 16] / (16-depth)); + if(channel[chan].trigger < 16) + slide_up(chan,vibratotab[channel[chan].trigger + 16] / (16-depth)); + if(channel[chan].trigger >= 48) + slide_up(chan,vibratotab[channel[chan].trigger - 48] / (16-depth)); + } + setfreq(chan); +} + +void CmodPlayer::vol_up(unsigned char chan, int amount) +{ + if(channel[chan].vol1 + amount < 63) + channel[chan].vol1 += amount; + else + channel[chan].vol1 = 63; + + if(channel[chan].vol2 + amount < 63) + channel[chan].vol2 += amount; + else + channel[chan].vol2 = 63; +} + +void CmodPlayer::vol_down(unsigned char chan, int amount) +{ + if(channel[chan].vol1 - amount > 0) + channel[chan].vol1 -= amount; + else + channel[chan].vol1 = 0; + + if(channel[chan].vol2 - amount > 0) + channel[chan].vol2 -= amount; + else + channel[chan].vol2 = 0; +} + +void CmodPlayer::vol_up_alt(unsigned char chan, int amount) +{ + if(channel[chan].vol1 + amount < 63) + channel[chan].vol1 += amount; + else + channel[chan].vol1 = 63; + if(inst[channel[chan].inst].data[0] & 1) + if(channel[chan].vol2 + amount < 63) + channel[chan].vol2 += amount; + else + channel[chan].vol2 = 63; +} + +void CmodPlayer::vol_down_alt(unsigned char chan, int amount) +{ + if(channel[chan].vol1 - amount > 0) + channel[chan].vol1 -= amount; + else + channel[chan].vol1 = 0; + if(inst[channel[chan].inst].data[0] & 1) + if(channel[chan].vol2 - amount > 0) + channel[chan].vol2 -= amount; + else + channel[chan].vol2 = 0; +} diff --git a/plugins/adplug/adplug/protrack.h b/plugins/adplug/adplug/protrack.h new file mode 100644 index 00000000..8da9489e --- /dev/null +++ b/plugins/adplug/adplug/protrack.h @@ -0,0 +1,119 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * protrack.h - Generic Protracker Player by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_PROTRACK +#define H_PROTRACK + +#include "player.h" + +class CmodPlayer: public CPlayer +{ +public: + CmodPlayer(Copl *newopl); + virtual ~CmodPlayer(); + + bool update(); + void rewind(int subsong); + float getrefresh(); + + unsigned int getpatterns() + { return nop; } + unsigned int getpattern() + { return order[ord]; } + unsigned int getorders() + { return length; } + unsigned int getorder() + { return ord; } + unsigned int getrow() + { return rw; } + unsigned int getspeed() + { return speed; } + + protected: + enum Flags { + Standard = 0, + Decimal = 1 << 0, + Faust = 1 << 1, + NoKeyOn = 1 << 2, + Opl3 = 1 << 3, + Tremolo = 1 << 4, + Vibrato = 1 << 5, + Percussion = 1 << 6 + }; + + struct Instrument { + unsigned char data[11],arpstart,arpspeed,arppos,arpspdcnt,misc; + signed char slide; + } *inst; + + struct Tracks { + unsigned char note,command,inst,param2,param1; + } **tracks; + + unsigned char *order, *arplist, *arpcmd, initspeed; + unsigned short tempo, **trackord, bpm, nop; + unsigned long length, restartpos, activechan; + int flags, curchip; + + struct Channel { + unsigned short freq,nextfreq; + unsigned char oct,vol1,vol2,inst,fx,info1,info2,key,nextoct, + note,portainfo,vibinfo1,vibinfo2,arppos,arpspdcnt; + signed char trigger; + } *channel; + + void init_trackord(); + bool init_specialarp(); + void init_notetable(const unsigned short *newnotetable); + bool realloc_order(unsigned long len); + bool realloc_patterns(unsigned long pats, unsigned long rows, unsigned long chans); + bool realloc_instruments(unsigned long len); + + void dealloc(); + + private: + static const unsigned short sa2_notetable[12]; + static const unsigned char vibratotab[32]; + + unsigned char speed, del, songend, regbd; + unsigned short rows, notetable[12]; + unsigned long rw, ord, nrows, npats, nchans; + + void setvolume(unsigned char chan); + void setvolume_alt(unsigned char chan); + void setfreq(unsigned char chan); + void playnote(unsigned char chan); + void setnote(unsigned char chan, int note); + void slide_down(unsigned char chan, int amount); + void slide_up(unsigned char chan, int amount); + void tone_portamento(unsigned char chan, unsigned char info); + void vibrato(unsigned char chan, unsigned char speed, unsigned char depth); + void vol_up(unsigned char chan, int amount); + void vol_down(unsigned char chan, int amount); + void vol_up_alt(unsigned char chan, int amount); + void vol_down_alt(unsigned char chan, int amount); + + void dealloc_patterns(); + bool resolve_order(); + unsigned char set_opl_chip(unsigned char chan); +}; + +#endif diff --git a/plugins/adplug/adplug/psi.cpp b/plugins/adplug/adplug/psi.cpp new file mode 100644 index 00000000..00184526 --- /dev/null +++ b/plugins/adplug/adplug/psi.cpp @@ -0,0 +1,177 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] PSI player, by Riven the Mage <riven@ok.ru> + */ + +/* + - discovery - + + file(s) : 4BIDDEN.COM, PGRID.EXE + type : Forbidden Dreams BBStro + Power Grid BBStro + tune : by Friar Tuck [Shadow Faction/ICE] + player : by Psi [Future Crew] + comment : seems to me what 4bidden tune & player was ripped from pgrid + + file(s) : MYSTRUNE.COM + type : Mystical Runes BBStro + tune : by ? + player : by Psi [Future Crew] +*/ + +#include "psi.h" +#include "debug.h" + +const unsigned char CxadpsiPlayer::psi_adlib_registers[99] = +{ + 0x20, 0x23, 0x40, 0x43, 0x60, 0x63, 0x80, 0x83, 0xE0, 0xE3, 0xC0, + 0x21, 0x24, 0x41, 0x44, 0x61, 0x64, 0x81, 0x84, 0xE1, 0xE4, 0xC1, + 0x22, 0x25, 0x42, 0x45, 0x62, 0x65, 0x82, 0x85, 0xE2, 0xE5, 0xC2, + 0x28, 0x2B, 0x48, 0x4B, 0x68, 0x6B, 0x88, 0x8B, 0xE8, 0xEB, 0xC3, + 0x29, 0x2C, 0x49, 0x4C, 0x69, 0x6C, 0x89, 0x8C, 0xE9, 0xEC, 0xC4, + 0x2A, 0x2D, 0x4A, 0x4D, 0x6A, 0x6D, 0x8A, 0x8D, 0xEA, 0xED, 0xC5, + 0x30, 0x33, 0x50, 0x53, 0x70, 0x73, 0x90, 0x93, 0xF0, 0xF3, 0xC6, + 0x31, 0x34, 0x51, 0x54, 0x71, 0x74, 0x91, 0x94, 0xF1, 0xF4, 0xC7, + 0x32, 0x35, 0x52, 0x55, 0x72, 0x75, 0x92, 0x95, 0xF2, 0xF5, 0xC8 +}; + +const unsigned short CxadpsiPlayer::psi_notes[16] = +{ + 0x216B, 0x2181, 0x2198, 0x21B0, 0x21CA, 0x21E5, 0x2202, 0x2220, + 0x2241, 0x2263, 0x2287, 0x2364, + 0x0000, 0x0000, 0x0000, 0x0000 // by riven +}; + +CPlayer *CxadpsiPlayer::factory(Copl *newopl) +{ + return new CxadpsiPlayer(newopl); +} + +void CxadpsiPlayer::xadplayer_rewind(int subsong) +{ + opl_write(0x01, 0x20); + opl_write(0x08, 0x00); + opl_write(0xBD, 0x00); + + // get header + header.instr_ptr = (tune[1] << 8) + tune[0]; + header.seq_ptr = (tune[3] << 8) + tune[2]; + + // define instruments + psi.instr_table = &tune[header.instr_ptr]; + + for(int i=0; i<8; i++) + { + for(int j=0; j<11; j++) { + unsigned short inspos = (psi.instr_table[i * 2 + 1] << 8) + psi.instr_table[i * 2]; + + opl_write(psi_adlib_registers[i*11 + j],tune[inspos + j]); + } + + opl_write(0xA0+i, 0x00); + opl_write(0xB0+i, 0x00); + + psi.note_delay[i] = 1; + psi.note_curdelay[i] = 1; + psi.looping[i] = 0; + } + + // calculate sequence pointer + psi.seq_table = &tune[header.seq_ptr]; +} + +void CxadpsiPlayer::xadplayer_update() +{ + unsigned short ptr; + + for(int i=0; i<8; i++) + { + ptr = (psi.seq_table[(i<<1) * 2 + 1] << 8) + psi.seq_table[(i<<1) * 2]; + + psi.note_curdelay[i]--; + + if (!psi.note_curdelay[i]) + { + opl_write(0xA0+i, 0x00); + opl_write(0xB0+i, 0x00); + + unsigned char event = tune[ptr++]; +#ifdef DEBUG + AdPlug_LogWrite("channel %02X, event %02X:\n",i+1,event); +#endif + + // end of sequence ? + if (!event) + { + ptr = (psi.seq_table[(i<<1) * 2 + 3] << 8) + psi.seq_table[(i<<1) * 2 + 2]; + + event = tune[ptr++]; +#ifdef DEBUG + AdPlug_LogWrite(" channel %02X, event %02X:\n",i+1,event); +#endif + + // set sequence loop flag + psi.looping[i] = 1; + + // module loop ? + plr.looping = 1; + for(int j=0; j<8; j++) + plr.looping &= psi.looping[j]; + } + + // new note delay ? + if (event & 0x80) + { + psi.note_delay[i] = (event & 0x7F); + + event = tune[ptr++]; +#ifdef DEBUG + AdPlug_LogWrite(" channel %02X, event %02X:\n",i+1,event); +#endif + } + + psi.note_curdelay[i] = psi.note_delay[i]; + + // play note + unsigned short note = psi_notes[event & 0x0F]; + + opl_write(0xA0+i, note & 0xFF); + opl_write(0xB0+i, (note >> 8) + ((event >> 2) & 0xFC)); + + // save position + psi.seq_table[(i<<1) * 2] = ptr & 0xff; + psi.seq_table[(i<<1) * 2 + 1] = ptr >> 8; + } + } +} + +float CxadpsiPlayer::xadplayer_getrefresh() +{ + return 70.0f; +} + +std::string CxadpsiPlayer::xadplayer_gettype() +{ + return std::string("xad: psi player"); +} + +unsigned int CxadpsiPlayer::xadplayer_getinstruments() +{ + return 8; +} diff --git a/plugins/adplug/adplug/psi.h b/plugins/adplug/adplug/psi.h new file mode 100644 index 00000000..9714ed12 --- /dev/null +++ b/plugins/adplug/adplug/psi.h @@ -0,0 +1,64 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] PSI player, by Riven the Mage <riven@ok.ru> + */ + +#include "xad.h" + +class CxadpsiPlayer: public CxadPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CxadpsiPlayer(Copl *newopl): CxadPlayer(newopl) + { } + +protected: + struct psi_header + { + unsigned short instr_ptr; + unsigned short seq_ptr; + } header; + + struct + { + unsigned char *instr_table; + unsigned char *seq_table; + unsigned char note_delay[9]; + unsigned char note_curdelay[9]; + unsigned char looping[9]; + } psi; + // + bool xadplayer_load() + { + if(xad.fmt == PSI) + return true; + else + return false; + } + void xadplayer_rewind(int subsong); + void xadplayer_update(); + float xadplayer_getrefresh(); + std::string xadplayer_gettype(); + unsigned int xadplayer_getinstruments(); + +private: + static const unsigned char psi_adlib_registers[99]; + static const unsigned short psi_notes[16]; +}; diff --git a/plugins/adplug/adplug/rad.cpp b/plugins/adplug/adplug/rad.cpp new file mode 100644 index 00000000..0178a6c3 --- /dev/null +++ b/plugins/adplug/adplug/rad.cpp @@ -0,0 +1,125 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * rad.cpp - RAD Loader by Simon Peter <dn.tlp@gmx.net> + * + * BUGS: + * some volumes are dropped out + */ + +#include <string.h> +#include "rad.h" + +CPlayer *CradLoader::factory(Copl *newopl) +{ + return new CradLoader(newopl); +} + +bool CradLoader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + char id[16]; + unsigned char buf,ch,c,b,inp; + char bufstr[2] = "\0"; + unsigned int i,j; + unsigned short patofs[32]; + const unsigned char convfx[16] = {255,1,2,3,255,5,255,255,255,255,20,255,17,0xd,255,19}; + + // file validation section + f->readString(id, 16); version = f->readInt(1); + if(strncmp(id,"RAD by REALiTY!!",16) || version != 0x10) + { fp.close(f); return false; } + + // load section + radflags = f->readInt(1); + if(radflags & 128) { // description + memset(desc,0,80*22); + while((buf = f->readInt(1))) + if(buf == 1) + strcat(desc,"\n"); + else + if(buf >= 2 && buf <= 0x1f) + for(i=0;i<buf;i++) + strcat(desc," "); + else { + *bufstr = buf; + strcat(desc,bufstr); + } + } + while((buf = f->readInt(1))) { // instruments + buf--; + inst[buf].data[2] = f->readInt(1); inst[buf].data[1] = f->readInt(1); + inst[buf].data[10] = f->readInt(1); inst[buf].data[9] = f->readInt(1); + inst[buf].data[4] = f->readInt(1); inst[buf].data[3] = f->readInt(1); + inst[buf].data[6] = f->readInt(1); inst[buf].data[5] = f->readInt(1); + inst[buf].data[0] = f->readInt(1); + inst[buf].data[8] = f->readInt(1); inst[buf].data[7] = f->readInt(1); + } + length = f->readInt(1); + for(i = 0; i < length; i++) order[i] = f->readInt(1); // orderlist + for(i = 0; i < 32; i++) patofs[i] = f->readInt(2); // pattern offset table + init_trackord(); // patterns + for(i=0;i<32;i++) + if(patofs[i]) { + f->seek(patofs[i]); + do { + buf = f->readInt(1); b = buf & 127; + do { + ch = f->readInt(1); c = ch & 127; + inp = f->readInt(1); + tracks[i*9+c][b].note = inp & 127; + tracks[i*9+c][b].inst = (inp & 128) >> 3; + inp = f->readInt(1); + tracks[i*9+c][b].inst += inp >> 4; + tracks[i*9+c][b].command = inp & 15; + if(inp & 15) { + inp = f->readInt(1); + tracks[i*9+c][b].param1 = inp / 10; + tracks[i*9+c][b].param2 = inp % 10; + } + } while(!(ch & 128)); + } while(!(buf & 128)); + } else + memset(trackord[i],0,9*2); + fp.close(f); + + // convert replay data + for(i=0;i<32*9;i++) // convert patterns + for(j=0;j<64;j++) { + if(tracks[i][j].note == 15) + tracks[i][j].note = 127; + if(tracks[i][j].note > 16 && tracks[i][j].note < 127) + tracks[i][j].note -= 4 * (tracks[i][j].note >> 4); + if(tracks[i][j].note && tracks[i][j].note < 126) + tracks[i][j].note++; + tracks[i][j].command = convfx[tracks[i][j].command]; + } + restartpos = 0; initspeed = radflags & 31; + bpm = radflags & 64 ? 0 : 50; flags = Decimal; + + rewind(0); + return true; +} + +float CradLoader::getrefresh() +{ + if(tempo) + return (float) (tempo); + else + return 18.2f; +} diff --git a/plugins/adplug/adplug/rad.h b/plugins/adplug/adplug/rad.h new file mode 100644 index 00000000..07814b8a --- /dev/null +++ b/plugins/adplug/adplug/rad.h @@ -0,0 +1,44 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * rad.h - RAD Loader by Simon Peter <dn.tlp@gmx.net> + */ + +#include "protrack.h" + +class CradLoader: public CmodPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CradLoader(Copl *newopl) + : CmodPlayer(newopl) + { *desc = '\0'; }; + + bool load(const std::string &filename, const CFileProvider &fp); + float getrefresh(); + + std::string gettype() + { return std::string("Reality ADlib Tracker"); }; + std::string getdesc() + { return std::string(desc); }; + +private: + unsigned char version,radflags; + char desc[80*22]; +}; diff --git a/plugins/adplug/adplug/rat.cpp b/plugins/adplug/adplug/rat.cpp new file mode 100644 index 00000000..b20af250 --- /dev/null +++ b/plugins/adplug/adplug/rat.cpp @@ -0,0 +1,293 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] RAT player, by Riven the Mage <riven@ok.ru> + */ + +/* + - discovery - + + file(s) : PINA.EXE + type : Experimental Connection BBStro tune + tune : by (?)Ratt/GRIF + player : by (?)Ratt/GRIF + comment : there are bug in original replayer's adlib_init(): wrong frequency registers. +*/ + +#include <string.h> +#include "rat.h" +#include "debug.h" + +const unsigned char CxadratPlayer::rat_adlib_bases[18] = +{ + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12, + 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 +}; + +const unsigned short CxadratPlayer::rat_notes[16] = +{ + 0x157, 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287, + 0x000, 0x000, 0x000, 0x000 // by riven +}; + +CPlayer *CxadratPlayer::factory(Copl *newopl) +{ + return new CxadratPlayer(newopl); +} + +bool CxadratPlayer::xadplayer_load() +{ + if(xad.fmt != RAT) + return false; + + // load header + memcpy(&rat.hdr, &tune[0], sizeof(rat_header)); + + // is 'RAT'-signed ? + if (strncmp(rat.hdr.id,"RAT",3)) + return false; + + // is version 1.0 ? + if (rat.hdr.version != 0x10) + return false; + + // load order + rat.order = &tune[0x40]; + + // load instruments + rat.inst = (rat_instrument *)&tune[0x140]; + + // load pattern data + unsigned short patseg = (rat.hdr.patseg[1] << 8) + rat.hdr.patseg[0]; + unsigned char *event_ptr = &tune[patseg << 4]; + + for(int i=0;i<rat.hdr.numpat;i++) + for(int j=0;j<64;j++) + for(int k=0;k<rat.hdr.numchan;k++) + { + memcpy(&rat.tracks[i][j][k], event_ptr, sizeof(rat_event)); + + event_ptr += sizeof(rat_event); + } + + return true; +} + +void CxadratPlayer::xadplayer_rewind(int subsong) +{ + int i; + + rat.order_pos = rat.hdr.order_start; + rat.pattern_pos = 0; + rat.volume = rat.hdr.volume; + + plr.speed = rat.hdr.speed; + + // clear channel data + memset(&rat.channel, 0, sizeof(rat.channel[0])*9); + + // init OPL + opl_write(0x01, 0x20); + opl_write(0x08, 0x00); + opl_write(0xBD, 0x00); + + // set default frequencies + for(i=0;i<9;i++) + { + opl_write(0xA0+i, 0x00); + opl_write(0xA3+i, 0x00); + opl_write(0xB0+i, 0x00); + opl_write(0xB3+i, 0x00); + } + + // set default volumes + for(i=0;i<0x1F;i++) + opl_write(0x40+i, 0x3F); +} + +void CxadratPlayer::xadplayer_update() +{ + int i; + + rat_event event; + + // process events + for(i=0;i<rat.hdr.numchan;i++) + { + memcpy(&event,&rat.tracks[rat.order[rat.order_pos]][rat.pattern_pos][i],sizeof(rat_event)); +#ifdef DEBUG + AdPlug_LogWrite("order %02X, pattern %02X, row %02X, channel %02X, event %02X %02X %02X %02X %02X:\n", + rat.order_pos, rat.order[rat.order_pos], rat.pattern_pos, i, event.note, event.instrument, event.volume, event.fx, event.fxp + ); +#endif + + // is instrument ? + if (event.instrument != 0xFF) + { + rat.channel[i].instrument = event.instrument - 1; + rat.channel[i].volume = rat.inst[event.instrument - 1].volume; + } + + // is volume ? + if (event.volume != 0xFF) + rat.channel[i].volume = event.volume; + + // is note ? + if (event.note != 0xFF) + { + // mute channel + opl_write(0xB0+i, 0x00); + opl_write(0xA0+i, 0x00); + + // if note != 0xFE then play + if (event.note != 0xFE) + { + unsigned char ins = rat.channel[i].instrument; + + // synthesis/feedback + opl_write(0xC0+i, rat.inst[ins].connect); + + // controls + opl_write(0x20+rat_adlib_bases[i], rat.inst[ins].mod_ctrl); + opl_write(0x20+rat_adlib_bases[i+9], rat.inst[ins].car_ctrl); + + // volumes + opl_write(0x40+rat_adlib_bases[i], __rat_calc_volume(rat.inst[ins].mod_volume,rat.channel[i].volume,rat.volume)); + opl_write(0x40+rat_adlib_bases[i+9], __rat_calc_volume(rat.inst[ins].car_volume,rat.channel[i].volume,rat.volume)); + + // attack/decay + opl_write(0x60+rat_adlib_bases[i], rat.inst[ins].mod_AD); + opl_write(0x60+rat_adlib_bases[i+9], rat.inst[ins].car_AD); + + // sustain/release + opl_write(0x80+rat_adlib_bases[i], rat.inst[ins].mod_SR); + opl_write(0x80+rat_adlib_bases[i+9], rat.inst[ins].car_SR); + + // waveforms + opl_write(0xE0+rat_adlib_bases[i], rat.inst[ins].mod_wave); + opl_write(0xE0+rat_adlib_bases[i+9], rat.inst[ins].car_wave); + + // octave/frequency + unsigned short insfreq = (rat.inst[ins].freq[1] << 8) + rat.inst[ins].freq[0]; + unsigned short freq = insfreq * rat_notes[event.note & 0x0F] / 0x20AB; + + opl_write(0xA0+i, freq & 0xFF); + opl_write(0xB0+i, (freq >> 8) | ((event.note & 0xF0) >> 2) | 0x20); + } + } + + // is effect ? + if (event.fx != 0xFF) + { + rat.channel[i].fx = event.fx; + rat.channel[i].fxp = event.fxp; + } + } + + // next row + rat.pattern_pos++; + + // process effects + for(i=0;i<rat.hdr.numchan;i++) + { + unsigned char old_order_pos = rat.order_pos; + + switch (rat.channel[i].fx) + { + case 0x01: // 0x01: Set Speed + plr.speed = rat.channel[i].fxp; + break; + case 0x02: // 0x02: Position Jump + if (rat.channel[i].fxp < rat.hdr.order_end) + rat.order_pos = rat.channel[i].fxp; + else + rat.order_pos = 0; + + // jumpback ? + if (rat.order_pos <= old_order_pos) + plr.looping = 1; + + rat.pattern_pos = 0; + break; + case 0x03: // 0x03: Pattern Break (?) + rat.pattern_pos = 0x40; + break; + } + + rat.channel[i].fx = 0; + } + + // end of pattern ? + if (rat.pattern_pos >= 0x40) + { + rat.pattern_pos = 0; + + rat.order_pos++; + + // end of module ? + if (rat.order_pos == rat.hdr.order_end) + { + rat.order_pos = rat.hdr.order_loop; + + plr.looping = 1; + } + } +} + +float CxadratPlayer::xadplayer_getrefresh() +{ + return 60.0f; +} + +std::string CxadratPlayer::xadplayer_gettype() +{ + return (std::string("xad: rat player")); +} + +std::string CxadratPlayer::xadplayer_gettitle() +{ + return (std::string(rat.hdr.title,32)); +} + +unsigned int CxadratPlayer::xadplayer_getinstruments() +{ + return rat.hdr.numinst; +} + +/* -------- Internal Functions ---------------------------- */ + +unsigned char CxadratPlayer::__rat_calc_volume(unsigned char ivol, unsigned char cvol, unsigned char gvol) +{ +#ifdef DEBUG + AdPlug_LogWrite("volumes: instrument %02X, channel %02X, global %02X:\n", ivol, cvol, gvol); +#endif + unsigned short vol; + + vol = ivol; + vol &= 0x3F; + vol ^= 0x3F; + vol *= cvol; + vol >>= 6; + vol *= gvol; + vol >>= 6; + vol ^= 0x3F; + + vol |= ivol & 0xC0; + + return vol; +} diff --git a/plugins/adplug/adplug/rat.h b/plugins/adplug/adplug/rat.h new file mode 100644 index 00000000..cc9ddf71 --- /dev/null +++ b/plugins/adplug/adplug/rat.h @@ -0,0 +1,121 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * [xad] RAT player, by Riven the Mage <riven@ok.ru> + */ + +#include "xad.h" + +class CxadratPlayer: public CxadPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CxadratPlayer(Copl *newopl): CxadPlayer(newopl) + { } + +protected: + struct rat_header + { + char id[3]; + unsigned char version; + char title[32]; + unsigned char numchan; + unsigned char reserved_25; + unsigned char order_end; + unsigned char reserved_27; + unsigned char numinst; // ?: Number of Instruments + unsigned char reserved_29; + unsigned char numpat; // ?: Number of Patterns + unsigned char reserved_2B; + unsigned char order_start; + unsigned char reserved_2D; + unsigned char order_loop; + unsigned char reserved_2F; + unsigned char volume; + unsigned char speed; + unsigned char reserved_32[12]; + unsigned char patseg[2]; + }; + + struct rat_event + { + unsigned char note; + unsigned char instrument; + unsigned char volume; + unsigned char fx; + unsigned char fxp; + }; + + struct rat_instrument + { + unsigned char freq[2]; + unsigned char reserved_2[2]; + unsigned char mod_ctrl; + unsigned char car_ctrl; + unsigned char mod_volume; + unsigned char car_volume; + unsigned char mod_AD; + unsigned char car_AD; + unsigned char mod_SR; + unsigned char car_SR; + unsigned char mod_wave; + unsigned char car_wave; + unsigned char connect; + unsigned char reserved_F; + unsigned char volume; + unsigned char reserved_11[3]; + }; + + struct + { + rat_header hdr; + + unsigned char volume; + unsigned char order_pos; + unsigned char pattern_pos; + + unsigned char *order; + + rat_instrument *inst; + + rat_event tracks[256][64][9]; + + struct + { + unsigned char instrument; + unsigned char volume; + unsigned char fx; + unsigned char fxp; + } channel[9]; + } rat; + // + bool xadplayer_load(); + void xadplayer_rewind(int subsong); + void xadplayer_update(); + float xadplayer_getrefresh(); + std::string xadplayer_gettype(); + std::string xadplayer_gettitle(); + unsigned int xadplayer_getinstruments(); + // +private: + static const unsigned char rat_adlib_bases[18]; + static const unsigned short rat_notes[16]; + + unsigned char __rat_calc_volume(unsigned char ivol, unsigned char cvol, unsigned char gvol); +}; diff --git a/plugins/adplug/adplug/raw.cpp b/plugins/adplug/adplug/raw.cpp new file mode 100644 index 00000000..1672a1d0 --- /dev/null +++ b/plugins/adplug/adplug/raw.cpp @@ -0,0 +1,104 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * raw.c - RAW Player by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> +#include "raw.h" + +/*** public methods *************************************/ + +CPlayer *CrawPlayer::factory(Copl *newopl) +{ + return new CrawPlayer(newopl); +} + +bool CrawPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + char id[8]; + unsigned long i; + + // file validation section + f->readString(id, 8); + if(strncmp(id,"RAWADATA",8)) { fp.close (f); return false; } + + // load section + clock = f->readInt(2); // clock speed + length = (fp.filesize(f) - 10) / 2; + data = new Tdata [length]; + for(i = 0; i < length; i++) { + data[i].param = f->readInt(1); + data[i].command = f->readInt(1); + } + + fp.close(f); + rewind(0); + return true; +} + +bool CrawPlayer::update() +{ + bool setspeed; + + if(pos >= length) return false; + + if(del) { + del--; + return !songend; + } + + do { + setspeed = false; + switch(data[pos].command) { + case 0: del = data[pos].param - 1; break; + case 2: + if(!data[pos].param) { + pos++; + speed = data[pos].param + (data[pos].command << 8); + setspeed = true; + } else + opl->setchip(data[pos].param - 1); + break; + case 0xff: + if(data[pos].param == 0xff) { + rewind(0); // auto-rewind song + songend = true; + return !songend; + } + break; + default: + opl->write(data[pos].command,data[pos].param); + break; + } + } while(data[pos++].command || setspeed); + + return !songend; +} + +void CrawPlayer::rewind(int subsong) +{ + pos = del = 0; speed = clock; songend = false; + opl->init(); opl->write(1, 32); // go to 9 channel mode +} + +float CrawPlayer::getrefresh() +{ + return 1193180.0 / (speed ? speed : 0xffff); // timer oscillator speed / wait register = clock frequency +} diff --git a/plugins/adplug/adplug/raw.h b/plugins/adplug/adplug/raw.h new file mode 100644 index 00000000..e05f1fe7 --- /dev/null +++ b/plugins/adplug/adplug/raw.h @@ -0,0 +1,52 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * raw.h - RAW Player by Simon Peter <dn.tlp@gmx.net> + */ + +#include "player.h" + +class CrawPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CrawPlayer(Copl *newopl) + : CPlayer(newopl), data(0) + { }; + ~CrawPlayer() + { if(data) delete [] data; }; + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype() + { return std::string("RdosPlay RAW"); }; + +protected: + struct Tdata { + unsigned char param, command; + } *data; + + unsigned long pos, length; + unsigned short clock, speed; + unsigned char del; + bool songend; +}; diff --git a/plugins/adplug/adplug/realopl.cpp b/plugins/adplug/adplug/realopl.cpp new file mode 100644 index 00000000..e8dcda81 --- /dev/null +++ b/plugins/adplug/adplug/realopl.cpp @@ -0,0 +1,208 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * realopl.cpp - Real hardware OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifdef _MSC_VER // Microsoft Visual C++ +# include <conio.h> +# define INP _inp +# define OUTP _outp +#elif defined(__WATCOMC__) // Watcom C/C++ and OpenWatcom +# include <conio.h> +# define INP inp +# define OUTP outp +#elif defined(WIN32) && defined(__MSVCRT__) && defined(__MINGW32__) // MinGW32 +/* +int __cdecl _inp(unsigned short); +int __cdecl _outp(unsigned short, int); +# define INP _inp +# define OUTP _outp +*/ +# define INP inb +# define OUTP(reg, val) outb(val, reg) +#elif defined(DJGPP) // DJGPP +# include <pc.h> +# define INP inportb +# define OUTP outportb +#else // no support on other platforms +# define INP(reg) 0 +# define OUTP(reg, val) +#endif + +#include "realopl.h" + +#define SHORTDELAY 6 // short delay in I/O port-reads after OPL hardware output +#define LONGDELAY 35 // long delay in I/O port-reads after OPL hardware output + +const unsigned char CRealopl::op_table[9] = + {0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12}; + +#if defined(WIN32) && defined(__MINGW32__) +static __inline unsigned char inb(unsigned short int port) +{ + unsigned char _v; + + __asm__ __volatile__ ("inb %w1,%0":"=a" (_v):"Nd" (port)); + return _v; +} + +static __inline void outb(unsigned char value, unsigned short int port) +{ + __asm__ __volatile__ ("outb %b0,%w1": :"a" (value), "Nd" (port)); +} +#endif + +CRealopl::CRealopl(unsigned short initport) + : adlport(initport), hardvol(0), bequiet(false), nowrite(false) +{ + for(int i=0;i<22;i++) { + hardvols[0][i][0] = 0; + hardvols[0][i][1] = 0; + hardvols[1][i][0] = 0; + hardvols[1][i][1] = 0; + } + + currType = TYPE_OPL3; +} + +bool CRealopl::harddetect() +{ + unsigned char stat1, stat2, i; + unsigned short adp = (currChip == 0 ? adlport : adlport + 2); + + hardwrite(4,0x60); hardwrite(4,0x80); + stat1 = INP(adp); + hardwrite(2,0xff); hardwrite(4,0x21); + for(i=0;i<80;i++) // wait for adlib + INP(adp); + stat2 = INP(adp); + hardwrite(4,0x60); hardwrite(4,0x80); + + if(((stat1 & 0xe0) == 0) && ((stat2 & 0xe0) == 0xc0)) + return true; + else + return false; +} + +bool CRealopl::detect() +{ + unsigned char stat; + + setchip(0); + if(harddetect()) { + // is at least OPL2, check for OPL3 + currType = TYPE_OPL2; + + stat = INP(adlport); + if(stat & 6) { + // not OPL3, try dual-OPL2 + setchip(1); + if(harddetect()) + currType = TYPE_DUAL_OPL2; + } else + currType = TYPE_OPL3; + + setchip(0); + return true; + } else + return false; +} + +void CRealopl::setvolume(int volume) +{ + int i, j; + + hardvol = volume; + for(j = 0; j < 2; j++) + for(i = 0; i < 9; i++) { + hardwrite(0x43+op_table[i],((hardvols[j][op_table[i]+3][0] & 63) + volume) > 63 ? 63 : hardvols[j][op_table[i]+3][0] + volume); + if(hardvols[j][i][1] & 1) // modulator too? + hardwrite(0x40+op_table[i],((hardvols[j][op_table[i]][0] & 63) + volume) > 63 ? 63 : hardvols[j][op_table[i]][0] + volume); + } +} + +void CRealopl::setquiet(bool quiet) +{ + bequiet = quiet; + + if(quiet) { + oldvol = hardvol; + setvolume(63); + } else + setvolume(oldvol); +} + +void CRealopl::hardwrite(int reg, int val) +{ + int i; + unsigned short adp = (currChip == 0 ? adlport : adlport + 2); + + OUTP(adp,reg); // set register + for(i=0;i<SHORTDELAY;i++) // wait for adlib + INP(adp); + OUTP(adp+1,val); // set value + for(i=0;i<LONGDELAY;i++) // wait for adlib + INP(adp); +} + +void CRealopl::write(int reg, int val) +{ + int i; + + if(nowrite) + return; + + if(currType == TYPE_OPL2 && currChip > 0) + return; + + if(bequiet && (reg >= 0xb0 && reg <= 0xb8)) // filter all key-on commands + val &= ~32; + if(reg >= 0x40 && reg <= 0x55) // cache volumes + hardvols[currChip][reg-0x40][0] = val; + if(reg >= 0xc0 && reg <= 0xc8) + hardvols[currChip][reg-0xc0][1] = val; + if(hardvol) // reduce volume + for(i=0;i<9;i++) { + if(reg == 0x43 + op_table[i]) + val = ((val & 63) + hardvol) > 63 ? 63 : val + hardvol; + else + if((reg == 0x40 + op_table[i]) && (hardvols[currChip][i][1] & 1)) + val = ((val & 63) + hardvol) > 63 ? 63 : val + hardvol; + } + + hardwrite(reg,val); +} + +void CRealopl::init() +{ + int i, j; + + for(j = 0; j < 2; j++) { + setchip(j); + + for(i=0;i<9;i++) { // stop instruments + hardwrite(0xb0 + i,0); // key off + hardwrite(0x80 + op_table[i],0xff); // fastest release + } + + hardwrite(0xbd,0); // clear misc. register + } + + setchip(0); +} diff --git a/plugins/adplug/adplug/realopl.h b/plugins/adplug/adplug/realopl.h new file mode 100644 index 00000000..e675fd92 --- /dev/null +++ b/plugins/adplug/adplug/realopl.h @@ -0,0 +1,72 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * realopl.h - Real hardware OPL, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_REALOPL +#define H_ADPLUG_REALOPL + +#include "opl.h" + +#define DFL_ADLIBPORT 0x388 // default adlib baseport + +class CRealopl: public Copl +{ + public: + CRealopl(unsigned short initport = DFL_ADLIBPORT); // initport = OPL2 hardware baseport + + bool detect(); // returns true if adlib compatible board is found, else false + void setvolume(int volume); // set adlib master volume (0 - 63) 0 = loudest, 63 = softest + void setquiet(bool quiet = true); // sets the OPL2 quiet, while still writing to the registers + void setport(unsigned short port) // set new OPL2 hardware baseport + { + adlport = port; + } + void setnowrite(bool nw = true) // set hardware write status + { + nowrite = nw; + } + + int getvolume() // get adlib master volume + { + return hardvol; + } + + // template methods + void write(int reg, int val); + void init(); + void settype(ChipType type) + { + currType = type; + } + + protected: + void hardwrite(int reg, int val); // write to OPL2 hardware registers + bool harddetect(); // do real hardware detection + + static const unsigned char op_table[9]; // the 9 operators as expected by the OPL2 + + unsigned short adlport; // adlib hardware baseport + int hardvol, oldvol; // hardware master volume + bool bequiet; // quiet status cache + char hardvols[2][22][2]; // volume cache + bool nowrite; // don't write to hardware, if true +}; + +#endif diff --git a/plugins/adplug/adplug/rix.cpp b/plugins/adplug/adplug/rix.cpp new file mode 100644 index 00000000..02ebc8d5 --- /dev/null +++ b/plugins/adplug/adplug/rix.cpp @@ -0,0 +1,495 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * rix.cpp - Softstar RIX OPL Format Player by palxex <palxex.ys168.com> + * BSPAL <BSPAL.ys168.com> + */ + +#include <string.h> +#include "rix.h" +#include "debug.h" + +const unsigned char CrixPlayer::adflag[] = {0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1}; +const unsigned char CrixPlayer::reg_data[] = {0,1,2,3,4,5,8,9,10,11,12,13,16,17,18,19,20,21}; +const unsigned char CrixPlayer::ad_C0_offs[] = {0,1,2,0,1,2,3,4,5,3,4,5,6,7,8,6,7,8}; +const unsigned char CrixPlayer::modify[] = {0,3,1,4,2,5,6,9,7,10,8,11,12,15,13,16,14,17,12,\ + 15,16,0,14,0,17,0,13,0}; +const unsigned char CrixPlayer::bd_reg_data[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x08,0x04,0x02,0x01, + 0x00,0x01,0x01,0x03,0x0F,0x05,0x00,0x01,0x03,0x0F,0x00, + 0x00,0x00,0x01,0x00,0x00,0x01,0x01,0x0F,0x07,0x00,0x02, + 0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x0A, + 0x04,0x00,0x08,0x0C,0x0B,0x00,0x00,0x00,0x01,0x00,0x00, + 0x00,0x00,0x0D,0x04,0x00,0x06,0x0F,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x0C,0x00,0x0F,0x0B,0x00,0x08,0x05,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x0F,0x0B,0x00, + 0x07,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00, + 0x0F,0x0B,0x00,0x05,0x05,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0x00,0x0F,0x0B,0x00,0x07,0x05,0x00,0x00,0x00, + 0x00,0x00,0x00}; +unsigned char CrixPlayer::for40reg[] = {0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, + 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F}; +unsigned short CrixPlayer::mus_time = 0x4268; + +/*** public methods *************************************/ + +CPlayer *CrixPlayer::factory(Copl *newopl) +{ + return new CrixPlayer(newopl); +} + +CrixPlayer::CrixPlayer(Copl *newopl) + : CPlayer(newopl), flag_mkf(0), file_buffer(0), buf_addr(0) +{ +} + +CrixPlayer::~CrixPlayer() +{ + if(file_buffer) + delete [] file_buffer; +} + +bool CrixPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + unsigned long i=0; + + if(stricmp(filename.substr(filename.length()-4,4).c_str(),".mkf")==0) + { + flag_mkf=1; + f->seek(0); + int offset=f->readInt(4); + f->seek(offset); + } + if(f->readInt(2)!=0x55aa){ fp.close(f);return false; } + file_buffer = new unsigned char [fp.filesize(f) + 1]; + f->seek(0); + while(!f->eof()) + file_buffer[i++]=f->readInt(1); + length=i; + fp.close(f); + if(!flag_mkf) + buf_addr=file_buffer; + rewind(0); + return true; +} + +bool CrixPlayer::update() +{ + int_08h_entry(); + return !play_end; +} + +void CrixPlayer::rewind(int subsong) +{ + I = 0; T = 0; + mus_block = 0; + ins_block = 0; + rhythm = 0; + music_on = 0; + pause_flag = 0; + band = 0; + band_low = 0; + e0_reg_flag = 0; + bd_modify = 0; + sustain = 0; + play_end = 0; + pos = index = 0; + + memset(f_buffer, 0, sizeof(unsigned short) * 300); + memset(a0b0_data2, 0, sizeof(unsigned short) * 11); + memset(a0b0_data3, 0, 18); + memset(a0b0_data4, 0, 18); + memset(a0b0_data5, 0, 96); + memset(addrs_head, 0, 96); + memset(insbuf, 0, 28 * sizeof(unsigned short)); + memset(displace, 0, 11 * sizeof(unsigned short)); + memset(reg_bufs, 0, 18 * sizeof(ADDT)); + + if(flag_mkf) + { + unsigned int *buf_index=(unsigned int *)file_buffer; + int offset1=buf_index[subsong],offset2; + while((offset2=buf_index[++subsong])==offset1); + length=offset2-offset1+1; + buf_addr=file_buffer+offset1; + } + opl->init(); + opl->write(1,32); // go to OPL2 mode + set_new_int(); + data_initial(); +} + +unsigned int CrixPlayer::getsubsongs() +{ + if(flag_mkf) + { + unsigned int *buf_index=(unsigned int *)file_buffer; + int songs=buf_index[0]/4,i=0; + for(i=0;i<songs;i++) + if(buf_index[i+1]==buf_index[i]) + songs--; + return songs; + } + else + return 1; +} + +float CrixPlayer::getrefresh() +{ + return 70.0f; +} + +/*------------------Implemention----------------------------*/ +inline void CrixPlayer::set_new_int() +{ +// if(!ad_initial()) exit(1); + ad_initial(); +} +/*----------------------------------------------------------*/ +inline void CrixPlayer::Pause() +{ + register unsigned short i; + pause_flag = 1; + for(i=0;i<11;i++) + switch_ad_bd(i); +} +/*----------------------------------------------------------*/ +inline void CrixPlayer::ad_a0b0l_reg_(unsigned short index,unsigned short p2,unsigned short p3) +{ +// unsigned short i = p2+a0b0_data2[index]; + a0b0_data4[index] = p3; + a0b0_data3[index] = p2; +} +inline void CrixPlayer::data_initial() +{ + rhythm = buf_addr[2]; + mus_block = (buf_addr[0x0D]<<8)+buf_addr[0x0C]; + ins_block = (buf_addr[0x09]<<8)+buf_addr[0x08]; + I = mus_block+1; + if(rhythm != 0) + { + // ad_a0b0_reg(6); + // ad_a0b0_reg(7); + // ad_a0b0_reg(8); + ad_a0b0l_reg_(8,0x18,0); + ad_a0b0l_reg_(7,0x1F,0); + } + bd_modify = 0; + // ad_bd_reg(); + band = 0; music_on = 1; +} +/*----------------------------------------------------------*/ +inline unsigned short CrixPlayer::ad_initial() +{ + register unsigned short i,j,k = 0; + for(i=0;i<25;i++) + { + f_buffer[i*12]=(unsigned int)((i*24+10000)*0.27461678223+4)>>3; + for(int t=1;t<12;t++) + f_buffer[i*12+t]=f_buffer[i*12+t-1]*1.06; + } + for(i=0;i<8;i++) + for(j=0;j<12;j++) + { + a0b0_data5[k] = i; + addrs_head[k] = j; + k++; + } + //ad_bd_reg(); + //ad_08_reg(); + //for(i=0;i<9;i++) ad_a0b0_reg(i); + e0_reg_flag = 0x20; + //for(i=0;i<18;i++) ad_bop(0xE0+reg_data[i],0); + //ad_bop(1,e0_reg_flag); + return 1;//ad_test(); +} +/*----------------------------------------------------------*/ +inline void CrixPlayer::ad_bop(unsigned short reg,unsigned short value) +{ + if(reg == 2 || reg == 3) + AdPlug_LogWrite("switch OPL2/3 mode!\n"); + opl->write(reg & 0xff, value & 0xff); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::int_08h_entry() + { + unsigned short band_sus = 1; + while(band_sus) + { + if(sustain <= 0) + { + band_sus = rix_proc(); + if(band_sus) sustain += band_sus; + else + { + play_end=1; + break; + } + } + else + { + if(band_sus) sustain -= 14; /* aging */ + break; + } + } + } +/*--------------------------------------------------------------*/ +inline unsigned short CrixPlayer::rix_proc() +{ + unsigned char ctrl = 0; + if(music_on == 0||pause_flag == 1) return 0; + band = 0; + while(buf_addr[I] != 0x80 && I<length-1) + { + band_low = buf_addr[I-1]; + ctrl = buf_addr[I]; I+=2; + switch(ctrl&0xF0) + { + case 0x90: rix_get_ins(); rix_90_pro(ctrl&0x0F); break; + case 0xA0: rix_A0_pro(ctrl&0x0F,((unsigned short)band_low)<<6); break; + case 0xB0: rix_B0_pro(ctrl&0x0F,band_low); break; + case 0xC0: switch_ad_bd(ctrl&0x0F); + if(band_low != 0) rix_C0_pro(ctrl&0x0F,band_low); + break; + default: band = (ctrl<<8)+band_low; break; + } + if(band != 0) return band; + } + music_ctrl(); + I = mus_block+1; + band = 0; music_on = 1; + return 0; +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::rix_get_ins() +{ + int i; + unsigned char *baddr = (&buf_addr[ins_block])+(band_low<<6); + + for(i = 0; i < 28; i++) + insbuf[i] = (baddr[i * 2 + 1] << 8) + baddr[i * 2]; +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::rix_90_pro(unsigned short ctrl_l) +{ + if(rhythm == 0 || ctrl_l < 6) + { + ins_to_reg(modify[ctrl_l*2],insbuf,insbuf[26]); + ins_to_reg(modify[ctrl_l*2+1],insbuf+13,insbuf[27]); + return; + } + else if(ctrl_l > 6) + { + ins_to_reg(modify[ctrl_l*2+6],insbuf,insbuf[26]); + return; + } + else + { + ins_to_reg(12,insbuf,insbuf[26]); + ins_to_reg(15,insbuf+13,insbuf[27]); + return; + } +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::rix_A0_pro(unsigned short ctrl_l,unsigned short index) +{ + if(rhythm == 0 || ctrl_l <= 6) + { + prepare_a0b0(ctrl_l,index>0x3FFF?0x3FFF:index); + ad_a0b0l_reg(ctrl_l,a0b0_data3[ctrl_l],a0b0_data4[ctrl_l]); + } + else return; +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::prepare_a0b0(unsigned short index,unsigned short v) /* important !*/ +{ + short high = 0,low = 0; unsigned int res; + int res1 = (v-0x2000)*0x19; + if(res1 == (int)0xff) return; + low = res1/0x2000; + if(low < 0) + { + low = 0x18-low; high = (signed short)low<0?0xFFFF:0; + res = high; res<<=16; res+=low; + low = ((signed short)res)/(signed short)0xFFE7; + a0b0_data2[index] = low; + low = res; + res = low - 0x18; + high = (signed short)res%0x19; + low = (signed short)res/0x19; + if(high != 0) {low = 0x19; low = low-high;} + } + else + { + res = high = low; + low = (signed short)res/(signed short)0x19; + a0b0_data2[index] = low; + res = high; + low = (signed short)res%(signed short)0x19; + } + low = (signed short)low*(signed short)0x18; + displace[index] = low; +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_a0b0l_reg(unsigned short index,unsigned short p2,unsigned short p3) +{ + unsigned short data; unsigned short i = p2+a0b0_data2[index]; + a0b0_data4[index] = p3; + a0b0_data3[index] = p2; + i = ((signed short)i<=0x5F?i:0x5F); + i = ((signed short)i>=0?i:0); + data = f_buffer[addrs_head[i]+displace[index]/2]; + ad_bop(0xA0+index,data); + data = a0b0_data5[i]*4+(p3<1?0:0x20)+((data>>8)&3); + ad_bop(0xB0+index,data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::rix_B0_pro(unsigned short ctrl_l,unsigned short index) +{ + register int temp = 0; + if(rhythm == 0 || ctrl_l < 6) temp = modify[ctrl_l*2+1]; + else + { + temp = ctrl_l > 6?ctrl_l*2:ctrl_l*2+1; + temp = modify[temp+6]; + } + for40reg[temp] = index>0x7F?0x7F:index; + ad_40_reg(temp); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::rix_C0_pro(unsigned short ctrl_l,unsigned short index) +{ + register unsigned short i = index>=12?index-12:0; + if(ctrl_l < 6 || rhythm == 0) + { + ad_a0b0l_reg(ctrl_l,i,1); + return; + } + else + { + if(ctrl_l != 6) + { + if(ctrl_l == 8) + { + ad_a0b0l_reg(ctrl_l,i,0); + ad_a0b0l_reg(7,i+7,0); + } + } + else ad_a0b0l_reg(ctrl_l,i,0); + bd_modify |= bd_reg_data[ctrl_l]; + ad_bd_reg(); + return; + } +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::switch_ad_bd(unsigned short index) +{ + + if(rhythm == 0 || index < 6) ad_a0b0l_reg(index,a0b0_data3[index],0); + else + { + bd_modify &= (~bd_reg_data[index]), + ad_bd_reg(); + } +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ins_to_reg(unsigned short index,unsigned short* insb,unsigned short value) +{ + register unsigned short i; + for(i=0;i<13;i++) reg_bufs[index].v[i] = insb[i]; + reg_bufs[index].v[13] = value&3; + ad_bd_reg(),ad_08_reg(), + ad_40_reg(index),ad_C0_reg(index),ad_60_reg(index), + ad_80_reg(index),ad_20_reg(index),ad_E0_reg(index); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_E0_reg(unsigned short index) +{ + unsigned short data = e0_reg_flag == 0?0:(reg_bufs[index].v[13]&3); + ad_bop(0xE0+reg_data[index],data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_20_reg(unsigned short index) +{ + unsigned short data = (reg_bufs[index].v[9] < 1?0:0x80); + data += (reg_bufs[index].v[10] < 1?0:0x40); + data += (reg_bufs[index].v[5] < 1?0:0x20); + data += (reg_bufs[index].v[11] < 1?0:0x10); + data += (reg_bufs[index].v[1]&0x0F); + ad_bop(0x20+reg_data[index],data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_80_reg(unsigned short index) +{ + unsigned short data = (reg_bufs[index].v[7]&0x0F),temp = reg_bufs[index].v[4]; + data |= (temp << 4); + ad_bop(0x80+reg_data[index],data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_60_reg(unsigned short index) +{ + unsigned short data = reg_bufs[index].v[6]&0x0F,temp = reg_bufs[index].v[3]; + data |= (temp << 4); + ad_bop(0x60+reg_data[index],data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_C0_reg(unsigned short index) +{ + unsigned short data = reg_bufs[index].v[2]; + if(adflag[index] == 1) return; + data *= 2, + data |= (reg_bufs[index].v[12] < 1?1:0); + ad_bop(0xC0+ad_C0_offs[index],data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_40_reg(unsigned short index) +{ + unsigned int res = 0; + unsigned short data = 0,temp = reg_bufs[index].v[0]; + data = 0x3F - (0x3F & reg_bufs[index].v[8]), + data *= for40reg[index], + data *= 2, + data += 0x7F, + res = data; + data = res/0xFE, + data -= 0x3F, + data = -data, + data |= temp<<6; + ad_bop(0x40+reg_data[index],data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_bd_reg() +{ + unsigned short data = rhythm < 1? 0:0x20; + data |= bd_modify; + ad_bop(0xBD,data); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::ad_a0b0_reg(unsigned short index) +{ + ad_bop(0xA0+index,0); + ad_bop(0xB0+index,0); +} +/*--------------------------------------------------------------*/ +inline void CrixPlayer::music_ctrl() +{ + register int i; + for(i=0;i<11;i++) + switch_ad_bd(i); +} diff --git a/plugins/adplug/adplug/rix.h b/plugins/adplug/adplug/rix.h new file mode 100644 index 00000000..68e08c8b --- /dev/null +++ b/plugins/adplug/adplug/rix.h @@ -0,0 +1,112 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * rix.h - Softstar RIX OPL Format Player by palxex <palxex.ys168.com> + * BSPAL <BSPAL.ys168.com> + */ + +#include "player.h" + +class CrixPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + CrixPlayer(Copl *newopl); + ~CrixPlayer(); + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + unsigned int getsubsongs(); + + std::string gettype() + { return std::string("Softstar RIX OPL Music Format"); }; + + protected: + typedef struct { + unsigned char v[14]; + } ADDT; + + int flag_mkf; + unsigned char *file_buffer; + unsigned char *buf_addr; /* rix files' f_buffer */ + unsigned short f_buffer[300];//9C0h-C18h + unsigned short a0b0_data2[11]; + unsigned char a0b0_data3[18]; + unsigned char a0b0_data4[18]; + unsigned char a0b0_data5[96]; + unsigned char addrs_head[96]; + unsigned short insbuf[28]; + unsigned short displace[11]; + ADDT reg_bufs[18]; + unsigned long pos,length; + unsigned char index; + + static const unsigned char adflag[18]; + static const unsigned char reg_data[18]; + static const unsigned char ad_C0_offs[18]; + static const unsigned char modify[28]; + static const unsigned char bd_reg_data[124]; + static unsigned char for40reg[18]; + static unsigned short mus_time; + unsigned int I,T; + unsigned short mus_block; + unsigned short ins_block; + unsigned char rhythm; + unsigned char music_on; + unsigned char pause_flag; + unsigned short band; + unsigned char band_low; + unsigned short e0_reg_flag; + unsigned char bd_modify; + int sustain; + int play_end; + +#define ad_08_reg() ad_bop(8,0) /**/ + inline void ad_20_reg(unsigned short); /**/ + inline void ad_40_reg(unsigned short); /**/ + inline void ad_60_reg(unsigned short); /**/ + inline void ad_80_reg(unsigned short); /**/ + inline void ad_a0b0_reg(unsigned short); /**/ + inline void ad_a0b0l_reg(unsigned short,unsigned short,unsigned short); /**/ + inline void ad_a0b0l_reg_(unsigned short,unsigned short,unsigned short); /**/ + inline void ad_bd_reg(); /**/ + inline void ad_bop(unsigned short,unsigned short); /**/ + inline void ad_C0_reg(unsigned short); /**/ + inline void ad_E0_reg(unsigned short); /**/ + inline unsigned short ad_initial(); /**/ + inline unsigned short ad_test(); /**/ + inline void crc_trans(unsigned short,unsigned short); /**/ + inline void data_initial(); /* done */ + inline void init(); /**/ + inline void ins_to_reg(unsigned short,unsigned short*,unsigned short); /**/ + inline void int_08h_entry(); /**/ + inline void music_ctrl(); /**/ + inline void Pause(); /**/ + inline void prepare_a0b0(unsigned short,unsigned short); /**/ + inline void rix_90_pro(unsigned short); /**/ + inline void rix_A0_pro(unsigned short,unsigned short); /**/ + inline void rix_B0_pro(unsigned short,unsigned short); /**/ + inline void rix_C0_pro(unsigned short,unsigned short); /**/ + inline void rix_get_ins(); /**/ + inline unsigned short rix_proc(); /**/ + inline void set_new_int(); + inline void switch_ad_bd(unsigned short); /**/ +}; diff --git a/plugins/adplug/adplug/rol.cpp b/plugins/adplug/adplug/rol.cpp new file mode 100644 index 00000000..7507d79b --- /dev/null +++ b/plugins/adplug/adplug/rol.cpp @@ -0,0 +1,723 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * rol.h - ROL Player by OPLx <oplx@yahoo.com> + * + * Visit: http://tenacity.hispeed.com/aomit/oplx/ + */ +#include <algorithm> + +#include "rol.h" +#include "debug.h" + +int const CrolPlayer::kSizeofDataRecord = 30; +int const CrolPlayer::kMaxTickBeat = 60; +int const CrolPlayer::kSilenceNote = -12; +int const CrolPlayer::kNumMelodicVoices = 9; +int const CrolPlayer::kNumPercussiveVoices = 11; +int const CrolPlayer::kBassDrumChannel = 6; +int const CrolPlayer::kSnareDrumChannel = 7; +int const CrolPlayer::kTomtomChannel = 8; +int const CrolPlayer::kTomtomFreq = 2;//4; +int const CrolPlayer::kSnareDrumFreq = 2;//kTomtomFreq + 7; +float const CrolPlayer::kDefaultUpdateTme = 18.2f; +float const CrolPlayer::kPitchFactor = 400.0f; + +static const unsigned char drum_table[4] = {0x14, 0x12, 0x15, 0x11}; + +CrolPlayer::uint16 const CrolPlayer::kNoteTable[12] = +{ + 340, // C + 363, // C# + 385, // D + 408, // D# + 432, // E + 458, // F + 485, // F# + 514, // G + 544, // G# + 577, // A + 611, // A# + 647 // B +}; + +/*** public methods **************************************/ + +CPlayer *CrolPlayer::factory(Copl *newopl) +{ + return new CrolPlayer(newopl); +} +//--------------------------------------------------------- +CrolPlayer::CrolPlayer(Copl *newopl) +: CPlayer ( newopl ) + ,rol_header ( NULL ) + ,mNextTempoEvent ( 0 ) + ,mCurrTick ( 0 ) + ,mTimeOfLastNote ( 0 ) + ,mRefresh ( kDefaultUpdateTme ) + ,bdRegister ( 0 ) +{ + int n; + + memset(bxRegister, 0, sizeof(bxRegister) ); + memset(volumeCache, 0, sizeof(volumeCache) ); + memset(freqCache, 0, sizeof(freqCache) ); + + for(n=0; n<11; n++) + pitchCache[n]=1.0f; +} +//--------------------------------------------------------- +CrolPlayer::~CrolPlayer() +{ + if( rol_header != NULL ) + { + delete rol_header; + rol_header=NULL; + } +} +//--------------------------------------------------------- +bool CrolPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + + char *fn = new char[filename.length()+12]; + int i; + std::string bnk_filename; + + AdPlug_LogWrite("*** CrolPlayer::load(f, \"%s\") ***\n", filename.c_str()); + strcpy(fn,filename.data()); + for (i=strlen(fn)-1; i>=0; i--) + if (fn[i] == '/' || fn[i] == '\\') + break; + strcpy(fn+i+1,"standard.bnk"); + bnk_filename = fn; + delete [] fn; + AdPlug_LogWrite("bnk_filename = \"%s\"\n",bnk_filename.c_str()); + + rol_header = new SRolHeader; + memset( rol_header, 0, sizeof(SRolHeader) ); + + rol_header->version_major = f->readInt( 2 ); + rol_header->version_minor = f->readInt( 2 ); + + // Version check + if(rol_header->version_major != 0 || rol_header->version_minor != 4) { + AdPlug_LogWrite("Unsupported file version %d.%d or not a ROL file!\n", + rol_header->version_major, rol_header->version_minor); + AdPlug_LogWrite("--- CrolPlayer::load ---\n"); + fp.close(f); + return false; + } + + f->seek( 40, binio::Add ); + + rol_header->ticks_per_beat = f->readInt( 2 ); + rol_header->beats_per_measure = f->readInt( 2 ); + rol_header->edit_scale_y = f->readInt( 2 ); + rol_header->edit_scale_x = f->readInt( 2 ); + + f->seek( 1, binio::Add ); + + rol_header->mode = f->readInt(1); + + f->seek( 90+38+15, binio::Add ); + + rol_header->basic_tempo = f->readFloat( binio::Single ); + + load_tempo_events( f ); + + mTimeOfLastNote = 0; + + if( load_voice_data( f, bnk_filename, fp ) != true ) + { + AdPlug_LogWrite("CrolPlayer::load_voice_data(f) failed!\n"); + AdPlug_LogWrite("--- CrolPlayer::load ---\n"); + + fp.close( f ); + return false; + } + + fp.close( f ); + + rewind( 0 ); + AdPlug_LogWrite("--- CrolPlayer::load ---\n"); + return true; +} +//--------------------------------------------------------- +bool CrolPlayer::update() +{ + if( mNextTempoEvent < mTempoEvents.size() && + mTempoEvents[mNextTempoEvent].time == mCurrTick ) + { + SetRefresh( mTempoEvents[mNextTempoEvent].multiplier ); + ++mNextTempoEvent; + } + + TVoiceData::iterator curr = voice_data.begin(); + TVoiceData::iterator end = voice_data.end(); + int voice = 0; + + while( curr != end ) + { + UpdateVoice( voice, *curr ); + ++curr; + ++voice; + } + + ++mCurrTick; + + if( mCurrTick > mTimeOfLastNote ) + { + return false; + } + + return true; + //return ( mCurrTick > mTimeOfLastNote ) ? false : true; +} +//--------------------------------------------------------- +void CrolPlayer::rewind( int subsong ) +{ + TVoiceData::iterator curr = voice_data.begin(); + TVoiceData::iterator end = voice_data.end(); + + while( curr != end ) + { + CVoiceData &voice = *curr; + + voice.Reset(); + ++curr; + } + + memset(bxRegister, 0, sizeof(bxRegister) ); + memset(volumeCache, 0, sizeof(volumeCache) ); + + bdRegister = 0; + + opl->init(); // initialize to melodic by default + opl->write(1,0x20); // Enable waveform select (bit 5) + + if( rol_header->mode == 0 ) + { + opl->write( 0xbd, 0x20 ); // select rhythm mode (bit 5) + bdRegister = 0x20; + + SetFreq( kTomtomChannel, 24 ); + SetFreq( kSnareDrumChannel, 31 ); + } + + mNextTempoEvent = 0; + mCurrTick = 0; + + SetRefresh(1.0f); +} +//--------------------------------------------------------- +inline float fmin( int const a, int const b ) +{ + return static_cast<float>( a < b ? a : b ); +} +//--------------------------------------------------------- +void CrolPlayer::SetRefresh( float const multiplier ) +{ + float const tickBeat = fmin(kMaxTickBeat, rol_header->ticks_per_beat); + + mRefresh = (tickBeat*rol_header->basic_tempo*multiplier) / 60.0f; +} +//--------------------------------------------------------- +float CrolPlayer::getrefresh() +{ + return mRefresh; +} +//--------------------------------------------------------- +void CrolPlayer::UpdateVoice( int const voice, CVoiceData &voiceData ) +{ + TNoteEvents const &nEvents = voiceData.note_events; + + if( nEvents.empty() || voiceData.mEventStatus & CVoiceData::kES_NoteEnd ) + { + return; // no note data to process, don't bother doing anything. + } + + TInstrumentEvents &iEvents = voiceData.instrument_events; + TVolumeEvents &vEvents = voiceData.volume_events; + TPitchEvents &pEvents = voiceData.pitch_events; + + if( !(voiceData.mEventStatus & CVoiceData::kES_InstrEnd ) && + iEvents[voiceData.next_instrument_event].time == mCurrTick ) + { + if( voiceData.next_instrument_event < iEvents.size() ) + { + send_ins_data_to_chip( voice, iEvents[voiceData.next_instrument_event].ins_index ); + ++voiceData.next_instrument_event; + } + else + { + voiceData.mEventStatus |= CVoiceData::kES_InstrEnd; + } + } + + if( !(voiceData.mEventStatus & CVoiceData::kES_VolumeEnd ) && + vEvents[voiceData.next_volume_event].time == mCurrTick ) + { + SVolumeEvent const &volumeEvent = vEvents[voiceData.next_volume_event]; + + if( voiceData.next_volume_event < vEvents.size() ) + { + int const volume = (int)(63.0f*(1.0f - volumeEvent.multiplier)); + + SetVolume( voice, volume ); + + ++voiceData.next_volume_event; // move to next volume event + } + else + { + voiceData.mEventStatus |= CVoiceData::kES_VolumeEnd; + } + } + + if( voiceData.mForceNote || voiceData.current_note_duration > voiceData.mNoteDuration-1 ) + { + if( mCurrTick != 0 ) + { + ++voiceData.current_note; + } + + if( voiceData.current_note < nEvents.size() ) + { + SNoteEvent const ¬eEvent = nEvents[voiceData.current_note]; + + SetNote( voice, noteEvent.number ); + voiceData.current_note_duration = 0; + voiceData.mNoteDuration = noteEvent.duration; + voiceData.mForceNote = false; + } + else + { + SetNote( voice, kSilenceNote ); + voiceData.mEventStatus |= CVoiceData::kES_NoteEnd; + return; + } + } + + if( !(voiceData.mEventStatus & CVoiceData::kES_PitchEnd ) && + pEvents[voiceData.next_pitch_event].time == mCurrTick ) + { + if( voiceData.next_pitch_event < pEvents.size() ) + { + SetPitch(voice,pEvents[voiceData.next_pitch_event].variation); + ++voiceData.next_pitch_event; + } + else + { + voiceData.mEventStatus |= CVoiceData::kES_PitchEnd; + } + } + + ++voiceData.current_note_duration; +} +//--------------------------------------------------------- +void CrolPlayer::SetNote( int const voice, int const note ) +{ + if( voice < kBassDrumChannel || rol_header->mode ) + { + SetNoteMelodic( voice, note ); + } + else + { + SetNotePercussive( voice, note ); + } +} +//--------------------------------------------------------- +void CrolPlayer::SetNotePercussive( int const voice, int const note ) +{ + int const bit_pos = 4-voice+kBassDrumChannel; + + bdRegister &= ~( 1<<bit_pos ); + opl->write( 0xbd, bdRegister ); + + if( note != kSilenceNote ) + { + switch( voice ) + { + case kTomtomChannel: + SetFreq( kSnareDrumChannel, note+7 ); + case kBassDrumChannel: + SetFreq( voice, note ); + break; + } + + bdRegister |= 1<<bit_pos; + opl->write( 0xbd, bdRegister ); + } +} +//--------------------------------------------------------- +void CrolPlayer::SetNoteMelodic( int const voice, int const note ) +{ + opl->write( 0xb0+voice, bxRegister[voice] & ~0x20 ); + + if( note != kSilenceNote ) + { + SetFreq( voice, note, true ); + } +} +//--------------------------------------------------------- +void CrolPlayer::SetPitch(int const voice, real32 const variation) +{ + pitchCache[voice] = variation; + freqCache[voice] += (uint16)((((float)freqCache[voice])*(variation-1.0f)) / kPitchFactor); + + opl->write(0xa0+voice,freqCache[voice] & 0xff); +} +//--------------------------------------------------------- +void CrolPlayer::SetFreq( int const voice, int const note, bool const keyOn ) +{ + uint16 freq = kNoteTable[note%12] + ((note/12) << 10); + freq += (uint16)((((float)freq)*(pitchCache[voice]-1.0f))/kPitchFactor); + + freqCache[voice] = freq; + bxRegister[voice] = ((freq >> 8) & 0x1f); + + opl->write( 0xa0+voice, freq & 0xff ); + opl->write( 0xb0+voice, bxRegister[voice] | (keyOn ? 0x20 : 0x0) ); +} +//--------------------------------------------------------- +void CrolPlayer::SetVolume( int const voice, int const volume ) +{ + volumeCache[voice] = (volumeCache[voice] &0xc0) | volume; + + int const op_offset = ( voice < kSnareDrumChannel || rol_header->mode ) ? + op_table[voice]+3 : drum_table[voice-kSnareDrumChannel]; + + opl->write( 0x40+op_offset, volumeCache[voice] ); +} +//--------------------------------------------------------- +void CrolPlayer::send_ins_data_to_chip( int const voice, int const ins_index ) +{ + SRolInstrument &instrument = ins_list[ins_index].instrument; + + send_operator( voice, instrument.modulator, instrument.carrier ); +} +//--------------------------------------------------------- +void CrolPlayer::send_operator( int const voice, SOPL2Op const &modulator, SOPL2Op const &carrier ) +{ + if( voice < kSnareDrumChannel || rol_header->mode ) + { + int const op_offset = op_table[voice]; + + opl->write( 0x20+op_offset, modulator.ammulti ); + opl->write( 0x40+op_offset, modulator.ksltl ); + opl->write( 0x60+op_offset, modulator.ardr ); + opl->write( 0x80+op_offset, modulator.slrr ); + opl->write( 0xc0+voice , modulator.fbc ); + opl->write( 0xe0+op_offset, modulator.waveform ); + + volumeCache[voice] = (carrier.ksltl & 0xc0) | volumeCache[voice] & 0x3f; + + opl->write( 0x23+op_offset, carrier.ammulti ); + opl->write( 0x43+op_offset, volumeCache[voice] ); + opl->write( 0x63+op_offset, carrier.ardr ); + opl->write( 0x83+op_offset, carrier.slrr ); +// opl->write( 0xc3+voice , carrier.fbc ); <- don't bother writing this. + opl->write( 0xe3+op_offset, carrier.waveform ); + } + else + { + int const op_offset = drum_table[voice-kSnareDrumChannel]; + + volumeCache[voice] = (modulator.ksltl & 0xc0) | volumeCache[voice] & 0x3f; + + opl->write( 0x20+op_offset, modulator.ammulti ); + opl->write( 0x40+op_offset, volumeCache[voice] ); + opl->write( 0x60+op_offset, modulator.ardr ); + opl->write( 0x80+op_offset, modulator.slrr ); + opl->write( 0xc0+voice , modulator.fbc ); + opl->write( 0xe0+op_offset, modulator.waveform ); + } +} +//--------------------------------------------------------- +void CrolPlayer::load_tempo_events( binistream *f ) +{ + int16 const num_tempo_events = f->readInt( 2 ); + + mTempoEvents.reserve( num_tempo_events ); + + for(int i=0; i<num_tempo_events; ++i) + { + STempoEvent event; + + event.time = f->readInt( 2 ); + event.multiplier = f->readFloat( binio::Single ); + mTempoEvents.push_back( event ); + } +} +//--------------------------------------------------------- +bool CrolPlayer::load_voice_data( binistream *f, std::string const &bnk_filename, const CFileProvider &fp ) +{ + SBnkHeader bnk_header; + binistream *bnk_file = fp.open( bnk_filename.c_str() ); + + if( bnk_file ) + { + load_bnk_info( bnk_file, bnk_header ); + + int const numVoices = rol_header->mode ? kNumMelodicVoices : kNumPercussiveVoices; + + voice_data.reserve( numVoices ); + for(int i=0; i<numVoices; ++i) + { + CVoiceData voice; + + load_note_events( f, voice ); + load_instrument_events( f, voice, bnk_file, bnk_header ); + load_volume_events( f, voice ); + load_pitch_events( f, voice ); + + voice_data.push_back( voice ); + } + + fp.close(bnk_file); + + return true; + } + + return false; +} +//--------------------------------------------------------- +void CrolPlayer::load_note_events( binistream *f, CVoiceData &voice ) +{ + f->seek( 15, binio::Add ); + + int16 const time_of_last_note = f->readInt( 2 ); + + if( time_of_last_note != 0 ) + { + TNoteEvents ¬e_events = voice.note_events; + int16 total_duration = 0; + + do + { + SNoteEvent event; + + event.number = f->readInt( 2 ); + event.duration = f->readInt( 2 ); + + event.number += kSilenceNote; // adding -12 + + note_events.push_back( event ); + + total_duration += event.duration; + } while( total_duration < time_of_last_note ); + + if( time_of_last_note > mTimeOfLastNote ) + { + mTimeOfLastNote = time_of_last_note; + } + } + + f->seek( 15, binio::Add ); +} +//--------------------------------------------------------- +void CrolPlayer::load_instrument_events( binistream *f, CVoiceData &voice, + binistream *bnk_file, SBnkHeader const &bnk_header ) +{ + int16 const number_of_instrument_events = f->readInt( 2 ); + + TInstrumentEvents &instrument_events = voice.instrument_events; + + instrument_events.reserve( number_of_instrument_events ); + + for(int i=0; i<number_of_instrument_events; ++i) + { + SInstrumentEvent event; + event.time = f->readInt( 2 ); + f->readString( event.name, 9 ); + + std::string event_name = event.name; + event.ins_index = load_rol_instrument( bnk_file, bnk_header, event_name ); + + instrument_events.push_back( event ); + + f->seek( 1+2, binio::Add ); + } + + f->seek( 15, binio::Add ); +} +//--------------------------------------------------------- +void CrolPlayer::load_volume_events( binistream *f, CVoiceData &voice ) +{ + int16 const number_of_volume_events = f->readInt( 2 ); + + TVolumeEvents &volume_events = voice.volume_events; + + volume_events.reserve( number_of_volume_events ); + + for(int i=0; i<number_of_volume_events; ++i) + { + SVolumeEvent event; + event.time = f->readInt( 2 ); + event.multiplier = f->readFloat( binio::Single ); + + volume_events.push_back( event ); + } + + f->seek( 15, binio::Add ); +} +//--------------------------------------------------------- +void CrolPlayer::load_pitch_events( binistream *f, CVoiceData &voice ) +{ + int16 const number_of_pitch_events = f->readInt( 2 ); + + TPitchEvents &pitch_events = voice.pitch_events; + + pitch_events.reserve( number_of_pitch_events ); + + for(int i=0; i<number_of_pitch_events; ++i) + { + SPitchEvent event; + event.time = f->readInt( 2 ); + event.variation = f->readFloat( binio::Single ); + + pitch_events.push_back( event ); + } +} +//--------------------------------------------------------- +bool CrolPlayer::load_bnk_info( binistream *f, SBnkHeader &header ) +{ + header.version_major = f->readInt(1); + header.version_minor = f->readInt(1); + f->readString( header.signature, 6 ); + + header.number_of_list_entries_used = f->readInt( 2 ); + header.total_number_of_list_entries = f->readInt( 2 ); + + header.abs_offset_of_name_list = f->readInt( 4 ); + header.abs_offset_of_data = f->readInt( 4 ); + + f->seek( header.abs_offset_of_name_list, binio::Set ); + + TInstrumentNames &ins_name_list = header.ins_name_list; + ins_name_list.reserve( header.number_of_list_entries_used ); + + for(int i=0; i<header.number_of_list_entries_used; ++i) + { + SInstrumentName instrument; + + instrument.index = f->readInt( 2 ); + instrument.record_used = f->readInt(1); + f->readString( instrument.name, 9 ); + + // printf("%s = #%d\n", instrument.name, i ); + + ins_name_list.push_back( instrument ); + } + + //std::sort( ins_name_list.begin(), ins_name_list.end(), StringCompare() ); + + return true; +} +//--------------------------------------------------------- +int CrolPlayer::load_rol_instrument( binistream *f, SBnkHeader const &header, std::string &name ) +{ + TInstrumentNames const &ins_name_list = header.ins_name_list; + + int const ins_index = get_ins_index( name ); + + if( ins_index != -1 ) + { + return ins_index; + } + + typedef TInstrumentNames::const_iterator TInsIter; + typedef std::pair<TInsIter, TInsIter> TInsIterPair; + + TInsIterPair range = std::equal_range( ins_name_list.begin(), + ins_name_list.end(), + name, + StringCompare() ); + + if( range.first != range.second ) + { + int const seekOffs = header.abs_offset_of_data + (range.first->index*kSizeofDataRecord); + f->seek( seekOffs, binio::Set ); + } + + SUsedList usedIns; + usedIns.name = name; + + if( range.first != range.second ) + { + read_rol_instrument( f, usedIns.instrument ); + } + else + { + // set up default instrument data here + memset( &usedIns.instrument, 0, kSizeofDataRecord ); + } + ins_list.push_back( usedIns ); + + return ins_list.size()-1; +} +//--------------------------------------------------------- +int CrolPlayer::get_ins_index( std::string const &name ) const +{ + for(unsigned int i=0; i<ins_list.size(); ++i) + { + if( stricmp(ins_list[i].name.c_str(), name.c_str()) == 0 ) + { + return i; + } + } + + return -1; +} +//--------------------------------------------------------- +void CrolPlayer::read_rol_instrument( binistream *f, SRolInstrument &ins ) +{ + ins.mode = f->readInt(1); + ins.voice_number = f->readInt(1); + + read_fm_operator( f, ins.modulator ); + read_fm_operator( f, ins.carrier ); + + ins.modulator.waveform = f->readInt(1); + ins.carrier.waveform = f->readInt(1); +} +//--------------------------------------------------------- +void CrolPlayer::read_fm_operator( binistream *f, SOPL2Op &opl2_op ) +{ + SFMOperator fm_op; + + fm_op.key_scale_level = f->readInt(1); + fm_op.freq_multiplier = f->readInt(1); + fm_op.feed_back = f->readInt(1); + fm_op.attack_rate = f->readInt(1); + fm_op.sustain_level = f->readInt(1); + fm_op.sustaining_sound = f->readInt(1); + fm_op.decay_rate = f->readInt(1); + fm_op.release_rate = f->readInt(1); + fm_op.output_level = f->readInt(1); + fm_op.amplitude_vibrato = f->readInt(1); + fm_op.frequency_vibrato = f->readInt(1); + fm_op.envelope_scaling = f->readInt(1); + fm_op.fm_type = f->readInt(1); + + opl2_op.ammulti = fm_op.amplitude_vibrato << 7 | fm_op.frequency_vibrato << 6 | fm_op.sustaining_sound << 5 | fm_op.envelope_scaling << 4 | fm_op.freq_multiplier; + opl2_op.ksltl = fm_op.key_scale_level << 6 | fm_op.output_level; + opl2_op.ardr = fm_op.attack_rate << 4 | fm_op.decay_rate; + opl2_op.slrr = fm_op.sustain_level << 4 | fm_op.release_rate; + opl2_op.fbc = fm_op.feed_back << 1 | (fm_op.fm_type ^ 1); +} diff --git a/plugins/adplug/adplug/rol.h b/plugins/adplug/adplug/rol.h new file mode 100644 index 00000000..fdb9fabb --- /dev/null +++ b/plugins/adplug/adplug/rol.h @@ -0,0 +1,309 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * rol.h - ROL Player by OPLx <oplx@yahoo.com> + * + * Visit: http://tenacity.hispeed.com/aomit/oplx/ + */ +#ifndef H_ROLPLAYER +#define H_ROLPLAYER + +#include <vector> +#include <string> +#include <string.h> + +#include "player.h" + +class CrolPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CrolPlayer(Copl *newopl); + + ~CrolPlayer(); + + bool load (const std::string &filename, const CFileProvider &fp); + bool update (); + void rewind (int subsong); // rewinds to specified subsong + float getrefresh(); // returns needed timer refresh rate + + std::string gettype() { return std::string("Adlib Visual Composer"); } + +private: + typedef unsigned short uint16; + typedef signed short int16; +#ifdef __x86_64__ + typedef signed int int32; +#else + typedef signed long int int32; +#endif + typedef float real32; + + typedef struct + { + uint16 version_major; + uint16 version_minor; + char unused0[40]; + uint16 ticks_per_beat; + uint16 beats_per_measure; + uint16 edit_scale_y; + uint16 edit_scale_x; + char unused1; + char mode; + char unused2[90]; + char filler0[38]; + char filler1[15]; + real32 basic_tempo; + } SRolHeader; + + typedef struct + { + int16 time; + real32 multiplier; + } STempoEvent; + + typedef struct + { + int16 number; + int16 duration; + } SNoteEvent; + + typedef struct + { + int16 time; + char name[9]; + int16 ins_index; + } SInstrumentEvent; + + typedef struct + { + int16 time; + real32 multiplier; + } SVolumeEvent; + + typedef struct + { + int16 time; + real32 variation; + } SPitchEvent; + + typedef std::vector<SNoteEvent> TNoteEvents; + typedef std::vector<SInstrumentEvent> TInstrumentEvents; + typedef std::vector<SVolumeEvent> TVolumeEvents; + typedef std::vector<SPitchEvent> TPitchEvents; + +#define bit_pos( pos ) (1<<pos) + + class CVoiceData + { + public: + enum EEventStatus + { + kES_NoteEnd = bit_pos( 0 ), + kES_PitchEnd = bit_pos( 1 ), + kES_InstrEnd = bit_pos( 2 ), + kES_VolumeEnd = bit_pos( 3 ), + + kES_None = 0 + }; + + explicit CVoiceData() + : mForceNote ( true ) + ,mEventStatus ( kES_None ) + ,current_note ( 0 ) + ,current_note_duration( 0 ) + ,mNoteDuration ( 0 ) + ,next_instrument_event( 0 ) + ,next_volume_event ( 0 ) + ,next_pitch_event ( 0 ) + { + } + + void Reset() + { + mForceNote = true; + mEventStatus = kES_None; + current_note = 0; + current_note_duration = 0; + mNoteDuration = 0; + next_instrument_event = 0; + next_volume_event = 0; + next_pitch_event = 0; + } + + TNoteEvents note_events; + TInstrumentEvents instrument_events; + TVolumeEvents volume_events; + TPitchEvents pitch_events; + + bool mForceNote : 1; + int mEventStatus; + unsigned int current_note; + int current_note_duration; + int mNoteDuration; + unsigned int next_instrument_event; + unsigned int next_volume_event; + unsigned int next_pitch_event; + }; + + typedef struct + { + uint16 index; + char record_used; + char name[9]; + } SInstrumentName; + + typedef std::vector<SInstrumentName> TInstrumentNames; + + typedef struct + { + char version_major; + char version_minor; + char signature[6]; + uint16 number_of_list_entries_used; + uint16 total_number_of_list_entries; + int32 abs_offset_of_name_list; + int32 abs_offset_of_data; + + TInstrumentNames ins_name_list; + } SBnkHeader; + + typedef struct + { + unsigned char key_scale_level; + unsigned char freq_multiplier; + unsigned char feed_back; + unsigned char attack_rate; + unsigned char sustain_level; + unsigned char sustaining_sound; + unsigned char decay_rate; + unsigned char release_rate; + unsigned char output_level; + unsigned char amplitude_vibrato; + unsigned char frequency_vibrato; + unsigned char envelope_scaling; + unsigned char fm_type; + } SFMOperator; + + typedef struct + { + unsigned char ammulti; + unsigned char ksltl; + unsigned char ardr; + unsigned char slrr; + unsigned char fbc; + unsigned char waveform; + } SOPL2Op; + + typedef struct + { + char mode; + char voice_number; + SOPL2Op modulator; + SOPL2Op carrier; + } SRolInstrument; + + typedef struct + { + std::string name; + SRolInstrument instrument; + } SUsedList; + + void load_tempo_events ( binistream *f ); + bool load_voice_data ( binistream *f, std::string const &bnk_filename, const CFileProvider &fp ); + void load_note_events ( binistream *f, CVoiceData &voice ); + void load_instrument_events( binistream *f, CVoiceData &voice, + binistream *bnk_file, SBnkHeader const &bnk_header ); + void load_volume_events ( binistream *f, CVoiceData &voice ); + void load_pitch_events ( binistream *f, CVoiceData &voice ); + + bool load_bnk_info ( binistream *f, SBnkHeader &header ); + int load_rol_instrument ( binistream *f, SBnkHeader const &header, std::string &name ); + void read_rol_instrument ( binistream *f, SRolInstrument &ins ); + void read_fm_operator ( binistream *f, SOPL2Op &opl2_op ); + int get_ins_index( std::string const &name ) const; + + void UpdateVoice( int const voice, CVoiceData &voiceData ); + void SetNote( int const voice, int const note ); + void SetNoteMelodic( int const voice, int const note ); + void SetNotePercussive( int const voice, int const note ); + void SetFreq ( int const voice, int const note, bool const keyOn=false ); + void SetPitch ( int const voice, real32 const variation ); + void SetVolume ( int const voice, int const volume ); + void SetRefresh( float const multiplier ); + void send_ins_data_to_chip( int const voice, int const ins_index ); + void send_operator( int const voice, SOPL2Op const &modulator, SOPL2Op const &carrier ); + + class StringCompare + { + public: + bool operator()( SInstrumentName const &lhs, SInstrumentName const &rhs ) const + { + return keyLess(lhs.name, rhs.name); + } + + bool operator()( SInstrumentName const &lhs, std::string const &rhs ) const + { + return keyLess(lhs.name, rhs.c_str()); + } + + bool operator()( std::string const &lhs, SInstrumentName const &rhs ) const + { + return keyLess(lhs.c_str(), rhs.name); + } + private: + bool keyLess( const char *const lhs, const char *const rhs ) const + { + return stricmp(lhs, rhs) < 0; + } + }; + + typedef std::vector<CVoiceData> TVoiceData; + + SRolHeader *rol_header; + std::vector<STempoEvent> mTempoEvents; + TVoiceData voice_data; + std::vector<SUsedList> ins_list; + + unsigned int mNextTempoEvent; + int mCurrTick; + int mTimeOfLastNote; + float mRefresh; + unsigned char bdRegister; + unsigned char bxRegister[9]; + unsigned char volumeCache[11]; + uint16 freqCache[11]; + real32 pitchCache[11]; + + static int const kSizeofDataRecord; + static int const kMaxTickBeat; + static int const kSilenceNote; + static int const kNumMelodicVoices; + static int const kNumPercussiveVoices; + static int const kBassDrumChannel; + static int const kSnareDrumChannel; + static int const kTomtomChannel; + static int const kTomtomFreq; + static int const kSnareDrumFreq; + static float const kDefaultUpdateTme; + static float const kPitchFactor; + static uint16 const kNoteTable[12]; +}; + +#endif diff --git a/plugins/adplug/adplug/s3m.cpp b/plugins/adplug/adplug/s3m.cpp new file mode 100644 index 00000000..ddc2a3b9 --- /dev/null +++ b/plugins/adplug/adplug/s3m.cpp @@ -0,0 +1,536 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * s3m.c - S3M Player by Simon Peter <dn.tlp@gmx.net> + * + * BUGS: + * Extra Fine Slides (EEx, FEx) & Fine Vibrato (Uxy) are inaccurate + */ + +#include <string.h> +#include "s3m.h" + +const char Cs3mPlayer::chnresolv[] = // S3M -> adlib channel conversion + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,-1,-1,-1,-1,-1,-1,-1}; + +const unsigned short Cs3mPlayer::notetable[12] = // S3M adlib note table + {340,363,385,408,432,458,485,514,544,577,611,647}; + +const unsigned char Cs3mPlayer::vibratotab[32] = // vibrato rate table + {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}; + +/*** public methods *************************************/ + +CPlayer *Cs3mPlayer::factory(Copl *newopl) +{ + return new Cs3mPlayer(newopl); +} + +Cs3mPlayer::Cs3mPlayer(Copl *newopl): CPlayer(newopl) +{ + int i,j,k; + + memset(pattern,255,sizeof(pattern)); + memset(orders,255,sizeof(orders)); + + for(i=0;i<99;i++) // setup pattern + for(j=0;j<64;j++) + for(k=0;k<32;k++) { + pattern[i][j][k].instrument = 0; + pattern[i][j][k].info = 0; + } +} + +bool Cs3mPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + unsigned short insptr[99],pattptr[99]; + int i,row; + unsigned char bufval,bufval2; + unsigned short ppatlen; + s3mheader *checkhead; + bool adlibins=false; + + // file validation section + checkhead = new s3mheader; + load_header(f, checkhead); + if(checkhead->kennung != 0x1a || checkhead->typ != 16 + || checkhead->insnum > 99) { + delete checkhead; fp.close(f); return false; + } else + if(strncmp(checkhead->scrm,"SCRM",4)) { + delete checkhead; fp.close(f); return false; + } else { // is an adlib module? + f->seek(checkhead->ordnum, binio::Add); + for(i = 0; i < checkhead->insnum; i++) + insptr[i] = f->readInt(2); + for(i=0;i<checkhead->insnum;i++) { + f->seek(insptr[i]*16); + if(f->readInt(1) >= 2) { + adlibins = true; + break; + } + } + delete checkhead; + if(!adlibins) { fp.close(f); return false; } + } + + // load section + f->seek(0); // rewind for load + load_header(f, &header); // read header + + // security check + if(header.ordnum > 256 || header.insnum > 99 || header.patnum > 99) { + fp.close(f); + return false; + } + + for(i = 0; i < header.ordnum; i++) orders[i] = f->readInt(1); // read orders + for(i = 0; i < header.insnum; i++) insptr[i] = f->readInt(2); // instrument parapointers + for(i = 0; i < header.patnum; i++) pattptr[i] = f->readInt(2); // pattern parapointers + + for(i=0;i<header.insnum;i++) { // load instruments + f->seek(insptr[i]*16); + inst[i].type = f->readInt(1); + f->readString(inst[i].filename, 15); + inst[i].d00 = f->readInt(1); inst[i].d01 = f->readInt(1); + inst[i].d02 = f->readInt(1); inst[i].d03 = f->readInt(1); + inst[i].d04 = f->readInt(1); inst[i].d05 = f->readInt(1); + inst[i].d06 = f->readInt(1); inst[i].d07 = f->readInt(1); + inst[i].d08 = f->readInt(1); inst[i].d09 = f->readInt(1); + inst[i].d0a = f->readInt(1); inst[i].d0b = f->readInt(1); + inst[i].volume = f->readInt(1); inst[i].dsk = f->readInt(1); + f->ignore(2); + inst[i].c2spd = f->readInt(4); + f->ignore(12); + f->readString(inst[i].name, 28); + f->readString(inst[i].scri, 4); + } + + for(i=0;i<header.patnum;i++) { // depack patterns + f->seek(pattptr[i]*16); + ppatlen = f->readInt(2); + unsigned long pattpos = f->pos(); + for(row=0;(row<64) && (pattpos-pattptr[i]*16<=ppatlen);row++) + do { + bufval = f->readInt(1); + if(bufval & 32) { + bufval2 = f->readInt(1); + pattern[i][row][bufval & 31].note = bufval2 & 15; + pattern[i][row][bufval & 31].oct = (bufval2 & 240) >> 4; + pattern[i][row][bufval & 31].instrument = f->readInt(1); + } + if(bufval & 64) + pattern[i][row][bufval & 31].volume = f->readInt(1); + if(bufval & 128) { + pattern[i][row][bufval & 31].command = f->readInt(1); + pattern[i][row][bufval & 31].info = f->readInt(1); + } + } while(bufval); + } + + fp.close(f); + rewind(0); + return true; // done +} + +bool Cs3mPlayer::update() +{ + unsigned char pattbreak=0,donote; // remember vars + unsigned char pattnr,chan,row,info; // cache vars + signed char realchan; + + // effect handling (timer dependant) + for(realchan=0; realchan<9; realchan++) { + info = channel[realchan].info; // fill infobyte cache + switch(channel[realchan].fx) { + case 11: + case 12: if(channel[realchan].fx == 11) // dual command: H00 and Dxy + vibrato(realchan,channel[realchan].dualinfo); + else // dual command: G00 and Dxy + tone_portamento(realchan,channel[realchan].dualinfo); + case 4: if(info <= 0x0f) // volume slide down + if(channel[realchan].vol - info >= 0) + channel[realchan].vol -= info; + else + channel[realchan].vol = 0; + if((info & 0x0f) == 0) // volume slide up + if(channel[realchan].vol + (info >> 4) <= 63) + channel[realchan].vol += info >> 4; + else + channel[realchan].vol = 63; + setvolume(realchan); + break; + case 5: if(info == 0xf0 || info <= 0xe0) { // slide down + slide_down(realchan,info); + setfreq(realchan); + } + break; + case 6: if(info == 0xf0 || info <= 0xe0) { // slide up + slide_up(realchan,info); + setfreq(realchan); + } + break; + case 7: tone_portamento(realchan,channel[realchan].dualinfo); break; // tone portamento + case 8: vibrato(realchan,channel[realchan].dualinfo); break; // vibrato + case 10: channel[realchan].nextfreq = channel[realchan].freq; // arpeggio + channel[realchan].nextoct = channel[realchan].oct; + switch(channel[realchan].trigger) { + case 0: channel[realchan].freq = notetable[channel[realchan].note]; break; + case 1: if(channel[realchan].note + ((info & 0xf0) >> 4) < 12) + channel[realchan].freq = notetable[channel[realchan].note + ((info & 0xf0) >> 4)]; + else { + channel[realchan].freq = notetable[channel[realchan].note + ((info & 0xf0) >> 4) - 12]; + channel[realchan].oct++; + } + break; + case 2: if(channel[realchan].note + (info & 0x0f) < 12) + channel[realchan].freq = notetable[channel[realchan].note + (info & 0x0f)]; + else { + channel[realchan].freq = notetable[channel[realchan].note + (info & 0x0f) - 12]; + channel[realchan].oct++; + } + break; + } + if(channel[realchan].trigger < 2) + channel[realchan].trigger++; + else + channel[realchan].trigger = 0; + setfreq(realchan); + channel[realchan].freq = channel[realchan].nextfreq; + channel[realchan].oct = channel[realchan].nextoct; + break; + case 21: vibrato(realchan,(unsigned char) (info / 4)); break; // fine vibrato + } + } + + if(del) { // speed compensation + del--; + return !songend; + } + + // arrangement handling + pattnr = orders[ord]; + if(pattnr == 0xff || ord > header.ordnum) { // "--" end of song + songend = 1; // set end-flag + ord = 0; + pattnr = orders[ord]; + if(pattnr == 0xff) + return !songend; + } + if(pattnr == 0xfe) { // "++" skip marker + ord++; pattnr = orders[ord]; + } + + // play row + row = crow; // fill row cache + for(chan=0;chan<32;chan++) { + if(!(header.chanset[chan] & 128)) // resolve S3M -> AdLib channels + realchan = chnresolv[header.chanset[chan] & 127]; + else + realchan = -1; // channel disabled + if(realchan != -1) { // channel playable? + // set channel values + donote = 0; + if(pattern[pattnr][row][chan].note < 14) + // tone portamento + if(pattern[pattnr][row][chan].command == 7 || pattern[pattnr][row][chan].command == 12) { + channel[realchan].nextfreq = notetable[pattern[pattnr][row][chan].note]; + channel[realchan].nextoct = pattern[pattnr][row][chan].oct; + } else { // normal note + channel[realchan].note = pattern[pattnr][row][chan].note; + channel[realchan].freq = notetable[pattern[pattnr][row][chan].note]; + channel[realchan].oct = pattern[pattnr][row][chan].oct; + channel[realchan].key = 1; + donote = 1; + } + if(pattern[pattnr][row][chan].note == 14) { // key off (is 14 here, cause note is only first 4 bits) + channel[realchan].key = 0; + setfreq(realchan); + } + if((channel[realchan].fx != 8 && channel[realchan].fx != 11) && // vibrato begins + (pattern[pattnr][row][chan].command == 8 || pattern[pattnr][row][chan].command == 11)) { + channel[realchan].nextfreq = channel[realchan].freq; + channel[realchan].nextoct = channel[realchan].oct; + } + if(pattern[pattnr][row][chan].note >= 14) + if((channel[realchan].fx == 8 || channel[realchan].fx == 11) && // vibrato ends + (pattern[pattnr][row][chan].command != 8 && pattern[pattnr][row][chan].command != 11)) { + channel[realchan].freq = channel[realchan].nextfreq; + channel[realchan].oct = channel[realchan].nextoct; + setfreq(realchan); + } + if(pattern[pattnr][row][chan].instrument) { // set instrument + channel[realchan].inst = pattern[pattnr][row][chan].instrument - 1; + if(inst[channel[realchan].inst].volume < 64) + channel[realchan].vol = inst[channel[realchan].inst].volume; + else + channel[realchan].vol = 63; + if(pattern[pattnr][row][chan].command != 7) + donote = 1; + } + if(pattern[pattnr][row][chan].volume != 255) + if(pattern[pattnr][row][chan].volume < 64) // set volume + channel[realchan].vol = pattern[pattnr][row][chan].volume; + else + channel[realchan].vol = 63; + channel[realchan].fx = pattern[pattnr][row][chan].command; // set command + if(pattern[pattnr][row][chan].info) // set infobyte + channel[realchan].info = pattern[pattnr][row][chan].info; + + // some commands reset the infobyte memory + switch(channel[realchan].fx) { + case 1: + case 2: + case 3: + case 20: + channel[realchan].info = pattern[pattnr][row][chan].info; + break; + } + + // play note + if(donote) + playnote(realchan); + if(pattern[pattnr][row][chan].volume != 255) // set volume + setvolume(realchan); + + // command handling (row dependant) + info = channel[realchan].info; // fill infobyte cache + switch(channel[realchan].fx) { + case 1: speed = info; break; // set speed + case 2: if(info <= ord) songend = 1; ord = info; crow = 0; pattbreak = 1; break; // jump to order + case 3: if(!pattbreak) { crow = info; ord++; pattbreak = 1; } break; // pattern break + case 4: if(info > 0xf0) // fine volume down + if(channel[realchan].vol - (info & 0x0f) >= 0) + channel[realchan].vol -= info & 0x0f; + else + channel[realchan].vol = 0; + if((info & 0x0f) == 0x0f && info >= 0x1f) // fine volume up + if(channel[realchan].vol + ((info & 0xf0) >> 4) <= 63) + channel[realchan].vol += (info & 0xf0) >> 4; + else + channel[realchan].vol = 63; + setvolume(realchan); + break; + case 5: if(info > 0xf0) { // fine slide down + slide_down(realchan,(unsigned char) (info & 0x0f)); + setfreq(realchan); + } + if(info > 0xe0 && info < 0xf0) { // extra fine slide down + slide_down(realchan,(unsigned char) ((info & 0x0f) / 4)); + setfreq(realchan); + } + break; + case 6: if(info > 0xf0) { // fine slide up + slide_up(realchan,(unsigned char) (info & 0x0f)); + setfreq(realchan); + } + if(info > 0xe0 && info < 0xf0) { // extra fine slide up + slide_up(realchan,(unsigned char) ((info & 0x0f) / 4)); + setfreq(realchan); + } + break; + case 7: // tone portamento + case 8: if((channel[realchan].fx == 7 || // vibrato (remember info for dual commands) + channel[realchan].fx == 8) && pattern[pattnr][row][chan].info) + channel[realchan].dualinfo = info; + break; + case 10: channel[realchan].trigger = 0; break; // arpeggio (set trigger) + case 19: if(info == 0xb0) // set loop start + loopstart = row; + if(info > 0xb0 && info <= 0xbf) // pattern loop + if(!loopcnt) { + loopcnt = info & 0x0f; + crow = loopstart; + pattbreak = 1; + } else + if(--loopcnt > 0) { + crow = loopstart; + pattbreak = 1; + } + if((info & 0xf0) == 0xe0) // patterndelay + del = speed * (info & 0x0f) - 1; + break; + case 20: tempo = info; break; // set tempo + } + } + } + + if(!del) + del = speed - 1; // speed compensation + if(!pattbreak) { // next row (only if no manual advance) + crow++; + if(crow > 63) { + crow = 0; + ord++; + loopstart = 0; + } + } + + return !songend; // still playing +} + +void Cs3mPlayer::rewind(int subsong) +{ + // set basic variables + songend = 0; ord = 0; crow = 0; tempo = header.it; + speed = header.is; del = 0; loopstart = 0; loopcnt = 0; + + memset(channel,0,sizeof(channel)); + + opl->init(); // reset OPL chip + opl->write(1,32); // Go to ym3812 mode +} + +std::string Cs3mPlayer::gettype() +{ + char filever[5]; + + switch(header.cwtv) { // determine version number + case 0x1300: strcpy(filever,"3.00"); break; + case 0x1301: strcpy(filever,"3.01"); break; + case 0x1303: strcpy(filever,"3.03"); break; + case 0x1320: strcpy(filever,"3.20"); break; + default: strcpy(filever,"3.??"); + } + + return (std::string("Scream Tracker ") + filever); +} + +float Cs3mPlayer::getrefresh() +{ + return (float) (tempo / 2.5); +} + +/*** private methods *************************************/ + +void Cs3mPlayer::load_header(binistream *f, s3mheader *h) +{ + int i; + + f->readString(h->name, 28); + h->kennung = f->readInt(1); h->typ = f->readInt(1); + f->ignore(2); + h->ordnum = f->readInt(2); h->insnum = f->readInt(2); + h->patnum = f->readInt(2); h->flags = f->readInt(2); + h->cwtv = f->readInt(2); h->ffi = f->readInt(2); + f->readString(h->scrm, 4); + h->gv = f->readInt(1); h->is = f->readInt(1); h->it = f->readInt(1); + h->mv = f->readInt(1); h->uc = f->readInt(1); h->dp = f->readInt(1); + f->ignore(8); + h->special = f->readInt(2); + for(i = 0; i < 32; i++) h->chanset[i] = f->readInt(1); +} + +void Cs3mPlayer::setvolume(unsigned char chan) +{ + unsigned char op = op_table[chan], insnr = channel[chan].inst; + + opl->write(0x43 + op,(int)(63-((63-(inst[insnr].d03 & 63))/63.0)*channel[chan].vol) + (inst[insnr].d03 & 192)); + if(inst[insnr].d0a & 1) + opl->write(0x40 + op,(int)(63-((63-(inst[insnr].d02 & 63))/63.0)*channel[chan].vol) + (inst[insnr].d02 & 192)); +} + +void Cs3mPlayer::setfreq(unsigned char chan) +{ + opl->write(0xa0 + chan, channel[chan].freq & 255); + if(channel[chan].key) + opl->write(0xb0 + chan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2) | 32); + else + opl->write(0xb0 + chan, ((channel[chan].freq & 768) >> 8) + (channel[chan].oct << 2)); +} + +void Cs3mPlayer::playnote(unsigned char chan) +{ + unsigned char op = op_table[chan], insnr = channel[chan].inst; + + opl->write(0xb0 + chan, 0); // stop old note + + // set instrument data + opl->write(0x20 + op, inst[insnr].d00); + opl->write(0x23 + op, inst[insnr].d01); + opl->write(0x40 + op, inst[insnr].d02); + opl->write(0x43 + op, inst[insnr].d03); + opl->write(0x60 + op, inst[insnr].d04); + opl->write(0x63 + op, inst[insnr].d05); + opl->write(0x80 + op, inst[insnr].d06); + opl->write(0x83 + op, inst[insnr].d07); + opl->write(0xe0 + op, inst[insnr].d08); + opl->write(0xe3 + op, inst[insnr].d09); + opl->write(0xc0 + chan, inst[insnr].d0a); + + // set frequency & play + channel[chan].key = 1; + setfreq(chan); +} + +void Cs3mPlayer::slide_down(unsigned char chan, unsigned char amount) +{ + if(channel[chan].freq - amount > 340) + channel[chan].freq -= amount; + else + if(channel[chan].oct > 0) { + channel[chan].oct--; + channel[chan].freq = 684; + } else + channel[chan].freq = 340; +} + +void Cs3mPlayer::slide_up(unsigned char chan, unsigned char amount) +{ + if(channel[chan].freq + amount < 686) + channel[chan].freq += amount; + else + if(channel[chan].oct < 7) { + channel[chan].oct++; + channel[chan].freq = 341; + } else + channel[chan].freq = 686; +} + +void Cs3mPlayer::vibrato(unsigned char chan, unsigned char info) +{ + unsigned char i,speed,depth; + + speed = info >> 4; + depth = (info & 0x0f) / 2; + + for(i=0;i<speed;i++) { + channel[chan].trigger++; + while(channel[chan].trigger >= 64) + channel[chan].trigger -= 64; + if(channel[chan].trigger >= 16 && channel[chan].trigger < 48) + slide_down(chan,(unsigned char) (vibratotab[channel[chan].trigger - 16] / (16-depth))); + if(channel[chan].trigger < 16) + slide_up(chan,(unsigned char) (vibratotab[channel[chan].trigger + 16] / (16-depth))); + if(channel[chan].trigger >= 48) + slide_up(chan,(unsigned char) (vibratotab[channel[chan].trigger - 48] / (16-depth))); + } + setfreq(chan); +} + +void Cs3mPlayer::tone_portamento(unsigned char chan, unsigned char info) +{ + if(channel[chan].freq + (channel[chan].oct << 10) < channel[chan].nextfreq + + (channel[chan].nextoct << 10)) + slide_up(chan,info); + if(channel[chan].freq + (channel[chan].oct << 10) > channel[chan].nextfreq + + (channel[chan].nextoct << 10)) + slide_down(chan,info); + setfreq(chan); +} diff --git a/plugins/adplug/adplug/s3m.h b/plugins/adplug/adplug/s3m.h new file mode 100644 index 00000000..aefc492e --- /dev/null +++ b/plugins/adplug/adplug/s3m.h @@ -0,0 +1,107 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * s3m.h - AdLib S3M Player by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_S3M +#define H_ADPLUG_S3M + +#include "player.h" + +class Cs3mPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + Cs3mPlayer(Copl *newopl); + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype(); + std::string gettitle() + { return std::string(header.name); }; + + unsigned int getpatterns() + { return header.patnum; }; + unsigned int getpattern() + { return orders[ord]; }; + unsigned int getorders() + { return (header.ordnum-1); }; + unsigned int getorder() + { return ord; }; + unsigned int getrow() + { return crow; }; + unsigned int getspeed() + { return speed; }; + unsigned int getinstruments() + { return header.insnum; }; + std::string getinstrument(unsigned int n) + { return std::string(inst[n].name); }; + + protected: + struct s3mheader { + char name[28]; // song name + unsigned char kennung,typ,dummy[2]; + unsigned short ordnum,insnum,patnum,flags,cwtv,ffi; + char scrm[4]; + unsigned char gv,is,it,mv,uc,dp,dummy2[8]; + unsigned short special; + unsigned char chanset[32]; + }; + + struct s3minst { + unsigned char type; + char filename[15]; + unsigned char d00,d01,d02,d03,d04,d05,d06,d07,d08,d09,d0a,d0b,volume,dsk,dummy[2]; + unsigned long c2spd; + char dummy2[12], name[28],scri[4]; + } inst[99]; + + struct { + unsigned char note,oct,instrument,volume,command,info; + } pattern[99][64][32]; + + struct { + unsigned short freq,nextfreq; + unsigned char oct,vol,inst,fx,info,dualinfo,key,nextoct,trigger,note; + } channel[9]; + + s3mheader header; + unsigned char orders[256]; + unsigned char crow,ord,speed,tempo,del,songend,loopstart,loopcnt; + + private: + static const char chnresolv[]; + static const unsigned short notetable[12]; + static const unsigned char vibratotab[32]; + + void load_header(binistream *f, s3mheader *h); + void setvolume(unsigned char chan); + void setfreq(unsigned char chan); + void playnote(unsigned char chan); + void slide_down(unsigned char chan, unsigned char amount); + void slide_up(unsigned char chan, unsigned char amount); + void vibrato(unsigned char chan, unsigned char info); + void tone_portamento(unsigned char chan, unsigned char info); +}; + +#endif diff --git a/plugins/adplug/adplug/sa2.cpp b/plugins/adplug/adplug/sa2.cpp new file mode 100644 index 00000000..2e1248bc --- /dev/null +++ b/plugins/adplug/adplug/sa2.cpp @@ -0,0 +1,262 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * sa2.cpp - SAdT2 Loader by Simon Peter <dn.tlp@gmx.net> + * SAdT Loader by Mamiya <mamiya@users.sourceforge.net> + */ + +#include <string.h> +#include <stdio.h> + +#include "sa2.h" +#include "debug.h" + +CPlayer *Csa2Loader::factory(Copl *newopl) +{ + return new Csa2Loader(newopl); +} + +bool Csa2Loader::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + struct { + unsigned char data[11],arpstart,arpspeed,arppos,arpspdcnt; + } insts; + unsigned char buf; + int i,j, k, notedis = 0; + const unsigned char convfx[16] = {0,1,2,3,4,5,6,255,8,255,10,11,12,13,255,15}; + unsigned char sat_type; + enum SAT_TYPE { + HAS_ARPEGIOLIST = (1 << 7), + HAS_V7PATTERNS = (1 << 6), + HAS_ACTIVECHANNELS = (1 << 5), + HAS_TRACKORDER = (1 << 4), + HAS_ARPEGIO = (1 << 3), + HAS_OLDBPM = (1 << 2), + HAS_OLDPATTERNS = (1 << 1), + HAS_UNKNOWN127 = (1 << 0) + }; + + // read header + f->readString(header.sadt, 4); + header.version = f->readInt(1); + + // file validation section + if(strncmp(header.sadt,"SAdT",4)) { fp.close(f); return false; } + switch(header.version) { + case 1: + notedis = +0x18; + sat_type = HAS_UNKNOWN127 | HAS_OLDPATTERNS | HAS_OLDBPM; + break; + case 2: + notedis = +0x18; + sat_type = HAS_OLDPATTERNS | HAS_OLDBPM; + break; + case 3: + notedis = +0x0c; + sat_type = HAS_OLDPATTERNS | HAS_OLDBPM; + break; + case 4: + notedis = +0x0c; + sat_type = HAS_ARPEGIO | HAS_OLDPATTERNS | HAS_OLDBPM; + break; + case 5: + notedis = +0x0c; + sat_type = HAS_ARPEGIO | HAS_ARPEGIOLIST | HAS_OLDPATTERNS | HAS_OLDBPM; + break; + case 6: + sat_type = HAS_ARPEGIO | HAS_ARPEGIOLIST | HAS_OLDPATTERNS | HAS_OLDBPM; + break; + case 7: + sat_type = HAS_ARPEGIO | HAS_ARPEGIOLIST | HAS_V7PATTERNS; + break; + case 8: + sat_type = HAS_ARPEGIO | HAS_ARPEGIOLIST | HAS_TRACKORDER; + break; + case 9: + sat_type = HAS_ARPEGIO | HAS_ARPEGIOLIST | HAS_TRACKORDER | HAS_ACTIVECHANNELS; + break; + default: /* unknown */ + fp.close(f); + return false; + } + + // load section + // instruments + for(i = 0; i < 31; i++) { + if(sat_type & HAS_ARPEGIO) { + for(j = 0; j < 11; j++) insts.data[j] = f->readInt(1); + insts.arpstart = f->readInt(1); + insts.arpspeed = f->readInt(1); + insts.arppos = f->readInt(1); + insts.arpspdcnt = f->readInt(1); + inst[i].arpstart = insts.arpstart; + inst[i].arpspeed = insts.arpspeed; + inst[i].arppos = insts.arppos; + inst[i].arpspdcnt = insts.arpspdcnt; + } else { + for(j = 0; j < 11; j++) insts.data[j] = f->readInt(1); + inst[i].arpstart = 0; + inst[i].arpspeed = 0; + inst[i].arppos = 0; + inst[i].arpspdcnt = 0; + } + for(j=0;j<11;j++) + inst[i].data[j] = insts.data[j]; + inst[i].misc = 0; + inst[i].slide = 0; + } + + // instrument names + for(i = 0; i < 29; i++) f->readString(instname[i], 17); + + f->ignore(3); // dummy bytes + for(i = 0; i < 128; i++) order[i] = f->readInt(1); // pattern orders + if(sat_type & HAS_UNKNOWN127) f->ignore(127); + + // infos + nop = f->readInt(2); length = f->readInt(1); restartpos = f->readInt(1); + + // bpm + bpm = f->readInt(2); + if(sat_type & HAS_OLDBPM) { + bpm = bpm * 125 / 50; // cps -> bpm + } + + if(sat_type & HAS_ARPEGIOLIST) { + init_specialarp(); + for(i = 0; i < 256; i++) arplist[i] = f->readInt(1); // arpeggio list + for(i = 0; i < 256; i++) arpcmd[i] = f->readInt(1); // arpeggio commands + } + + for(i=0;i<64;i++) { // track orders + for(j=0;j<9;j++) { + if(sat_type & HAS_TRACKORDER) + trackord[i][j] = f->readInt(1); + else + { + trackord[i][j] = i * 9 + j; + } + } + } + + if(sat_type & HAS_ACTIVECHANNELS) + activechan = f->readInt(2) << 16; // active channels + + AdPlug_LogWrite("Csa2Loader::load(\"%s\"): sat_type = %x, nop = %d, " + "length = %d, restartpos = %d, activechan = %x, bpm = %d\n", + filename.c_str(), sat_type, nop, length, restartpos, activechan, bpm); + + // track data + if(sat_type & HAS_OLDPATTERNS) { + i = 0; + while(!f->ateof()) { + for(j=0;j<64;j++) { + for(k=0;k<9;k++) { + buf = f->readInt(1); + tracks[i+k][j].note = buf ? (buf + notedis) : 0; + tracks[i+k][j].inst = f->readInt(1); + tracks[i+k][j].command = convfx[f->readInt(1) & 0xf]; + tracks[i+k][j].param1 = f->readInt(1); + tracks[i+k][j].param2 = f->readInt(1); + } + } + i+=9; + } + } else + if(sat_type & HAS_V7PATTERNS) { + i = 0; + while(!f->ateof()) { + for(j=0;j<64;j++) { + for(k=0;k<9;k++) { + buf = f->readInt(1); + tracks[i+k][j].note = buf >> 1; + tracks[i+k][j].inst = (buf & 1) << 4; + buf = f->readInt(1); + tracks[i+k][j].inst += buf >> 4; + tracks[i+k][j].command = convfx[buf & 0x0f]; + buf = f->readInt(1); + tracks[i+k][j].param1 = buf >> 4; + tracks[i+k][j].param2 = buf & 0x0f; + } + } + i+=9; + } + } else { + i = 0; + while(!f->ateof()) { + for(j=0;j<64;j++) { + buf = f->readInt(1); + tracks[i][j].note = buf >> 1; + tracks[i][j].inst = (buf & 1) << 4; + buf = f->readInt(1); + tracks[i][j].inst += buf >> 4; + tracks[i][j].command = convfx[buf & 0x0f]; + buf = f->readInt(1); + tracks[i][j].param1 = buf >> 4; + tracks[i][j].param2 = buf & 0x0f; + } + i++; + } + } + fp.close(f); + + // fix instrument names + for(i=0;i<29;i++) + for(j=0;j<17;j++) + if(!instname[i][j]) + instname[i][j] = ' '; + + rewind(0); // rewind module + return true; +} + +std::string Csa2Loader::gettype() +{ + char tmpstr[40]; + + sprintf(tmpstr,"Surprise! Adlib Tracker 2 (version %d)",header.version); + return std::string(tmpstr); +} + +std::string Csa2Loader::gettitle() +{ + char bufinst[29*17],buf[18]; + int i,ptr; + + // parse instrument names for song name + memset(bufinst,'\0',29*17); + for(i=0;i<29;i++) { + buf[16] = ' '; buf[17] = '\0'; + memcpy(buf,instname[i]+1,16); + for(ptr=16;ptr>0;ptr--) + if(buf[ptr] == ' ') + buf[ptr] = '\0'; + else { + if(ptr<16) + buf[ptr+1] = ' '; + break; + } + strcat(bufinst,buf); + } + + if(strchr(bufinst,'"')) + return std::string(bufinst,strchr(bufinst,'"')-bufinst+1,strrchr(bufinst,'"')-strchr(bufinst,'"')-1); + else + return std::string(); +} diff --git a/plugins/adplug/adplug/sa2.h b/plugins/adplug/adplug/sa2.h new file mode 100644 index 00000000..ed904f0d --- /dev/null +++ b/plugins/adplug/adplug/sa2.h @@ -0,0 +1,55 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * sa2.h - SAdT2 Loader by Simon Peter <dn.tlp@gmx.net> + * SAdT Loader by Mamiya <mamiya@users.sourceforge.net> + */ + +#include "protrack.h" + +class Csa2Loader: public CmodPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + Csa2Loader(Copl *newopl) + : CmodPlayer(newopl) + { } + + bool load(const std::string &filename, const CFileProvider &fp); + + std::string gettype(); + std::string gettitle(); + unsigned int getinstruments() + { return 31; } + std::string getinstrument(unsigned int n) + { + if(n < 29) + return std::string(instname[n],1,16); + else + return std::string("-broken-"); + } + +private: + struct sa2header { + char sadt[4]; + unsigned char version; + } header; + + char instname[29][17]; +}; diff --git a/plugins/adplug/adplug/silentopl.h b/plugins/adplug/adplug/silentopl.h new file mode 100644 index 00000000..352754ab --- /dev/null +++ b/plugins/adplug/adplug/silentopl.h @@ -0,0 +1,29 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * silentopl.h - Silent OPL device, by Simon Peter (dn.tlp@gmx.net) + */ + +#include "opl.h" + +class CSilentopl: public Copl +{ +public: + void write(int reg, int val) {} + void init() {} +}; diff --git a/plugins/adplug/adplug/sng.cpp b/plugins/adplug/adplug/sng.cpp new file mode 100644 index 00000000..bf0fa699 --- /dev/null +++ b/plugins/adplug/adplug/sng.cpp @@ -0,0 +1,85 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2002 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * sng.cpp - SNG Player by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> +#include "sng.h" + +CPlayer *CsngPlayer::factory(Copl *newopl) +{ + return new CsngPlayer(newopl); +} + +bool CsngPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + int i; + + // load header + f->readString(header.id, 4); + header.length = f->readInt(2); header.start = f->readInt(2); + header.loop = f->readInt(2); header.delay = f->readInt(1); + header.compressed = f->readInt(1) ? true : false; + + // file validation section + if(strncmp(header.id,"ObsM",4)) { fp.close(f); return false; } + + // load section + header.length /= 2; header.start /= 2; header.loop /= 2; + data = new Sdata [header.length]; + for(i = 0; i < header.length; i++) { + data[i].val = f->readInt(1); + data[i].reg = f->readInt(1); + } + + rewind(0); + fp.close(f); + return true; +} + +bool CsngPlayer::update() +{ + if(header.compressed && del) { + del--; + return !songend; + } + + while(data[pos].reg) { + opl->write(data[pos].reg, data[pos].val); + pos++; + if(pos >= header.length) { + songend = true; + pos = header.loop; + } + } + + if(!header.compressed) + opl->write(data[pos].reg, data[pos].val); + + if(data[pos].val) del = data[pos].val - 1; pos++; + if(pos >= header.length) { songend = true; pos = header.loop; } + return !songend; +} + +void CsngPlayer::rewind(int subsong) +{ + pos = header.start; del = header.delay; songend = false; + opl->init(); opl->write(1,32); // go to OPL2 mode +} diff --git a/plugins/adplug/adplug/sng.h b/plugins/adplug/adplug/sng.h new file mode 100644 index 00000000..6eef98de --- /dev/null +++ b/plugins/adplug/adplug/sng.h @@ -0,0 +1,64 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2002 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * sng.h - SNG Player by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_SNGPLAYER +#define H_ADPLUG_SNGPLAYER + +#include "player.h" + +class CsngPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CsngPlayer(Copl *newopl) + : CPlayer(newopl), data(0) + { }; + ~CsngPlayer() + { if(data) delete [] data; }; + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh() + { return 70.0f; }; + + std::string gettype() + { return std::string("SNG File Format"); }; + +protected: + struct { + char id[4]; + unsigned short length,start,loop; + unsigned char delay; + bool compressed; + } header; + + struct Sdata { + unsigned char val,reg; + } *data; + + unsigned char del; + unsigned short pos; + bool songend; +}; + +#endif diff --git a/plugins/adplug/adplug/temuopl.cpp b/plugins/adplug/adplug/temuopl.cpp new file mode 100644 index 00000000..13691d78 --- /dev/null +++ b/plugins/adplug/adplug/temuopl.cpp @@ -0,0 +1,75 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * temuopl.cpp - Tatsuyuki Satoh's OPL2 emulator, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "temuopl.h" + +CTemuopl::CTemuopl(int rate, bool bit16, bool usestereo) + : use16bit(bit16), stereo(usestereo) +{ + opl = OPLCreate(OPL_TYPE_YM3812, 3579545, rate); +} + +CTemuopl::~CTemuopl() +{ + OPLDestroy(opl); +} + +void CTemuopl::update(short *buf, int samples) +{ + int i; + + if(use16bit) { + YM3812UpdateOne(opl,buf,samples); + + if(stereo) + for(i=samples-1;i>=0;i--) { + buf[i*2] = buf[i]; + buf[i*2+1] = buf[i]; + } + } else { + short *tempbuf = new short[stereo ? samples*2 : samples]; + int i; + + YM3812UpdateOne(opl,tempbuf,samples); + + if(stereo) + for(i=samples-1;i>=0;i--) { + tempbuf[i*2] = tempbuf[i]; + tempbuf[i*2+1] = tempbuf[i]; + } + + for(i=0;i<(stereo ? samples*2 : samples);i++) + ((char *)buf)[i] = (tempbuf[i] >> 8) ^ 0x80; + + delete [] tempbuf; + } +} + +void CTemuopl::write(int reg, int val) +{ + OPLWrite(opl,0,reg); + OPLWrite(opl,1,val); +} + +void CTemuopl::init() +{ + OPLResetChip(opl); +} diff --git a/plugins/adplug/adplug/temuopl.h b/plugins/adplug/adplug/temuopl.h new file mode 100644 index 00000000..564fe3d8 --- /dev/null +++ b/plugins/adplug/adplug/temuopl.h @@ -0,0 +1,47 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * temuopl.h - Tatsuyuki Satoh's OPL2 emulator, by Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_ADPLUG_TEMUOPL +#define H_ADPLUG_TEMUOPL + +#include "opl.h" +extern "C" { +#include "fmopl.h" +} + +class CTemuopl: public Copl +{ + public: + CTemuopl(int rate, bool bit16, bool usestereo); // rate = sample rate + virtual ~CTemuopl(); + + void update(short *buf, int samples); // fill buffer + + // template methods + void write(int reg, int val); + void init(); + + private: + bool use16bit,stereo; + FM_OPL *opl; // holds emulator data +}; + +#endif diff --git a/plugins/adplug/adplug/u6m.cpp b/plugins/adplug/adplug/u6m.cpp new file mode 100644 index 00000000..5c0a2ca2 --- /dev/null +++ b/plugins/adplug/adplug/u6m.cpp @@ -0,0 +1,934 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * u6m.cpp - Ultima 6 Music Player by Marc Winterrowd. + * This code extends the Adlib Winamp plug-in by Simon Peter <dn.tlp@gmx.net> + */ + +#include "u6m.h" + +// Makes security checks on output buffer before writing +#define SAVE_OUTPUT_ROOT(c, d, p) \ +if(p < d.size) \ + output_root(c, d.data, p); \ +else \ + return false; + +CPlayer *Cu6mPlayer::factory(Copl *newopl) +{ + return new Cu6mPlayer(newopl); +} + +bool Cu6mPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + // file validation section + // this section only checks a few *necessary* conditions + unsigned long filesize, decompressed_filesize; + binistream *f; + + f = fp.open(filename); if(!f) return false; + filesize = fp.filesize(f); + + if (filesize >= 6) + { + // check if the file has a valid pseudo-header + unsigned char pseudo_header[6]; + f->readString((char *)pseudo_header, 6); + decompressed_filesize = pseudo_header[0] + (pseudo_header[1] << 8); + + if (!( (pseudo_header[2]==0) && (pseudo_header[3]==0) && + (pseudo_header[4] + ((pseudo_header[5] & 0x1)<<8) == 0x100) && + (decompressed_filesize > (filesize-4)) )) + { + fp.close(f); + return(false); + } + } + else + { + fp.close(f); + return(false); + } + + // load section + song_data = new unsigned char[decompressed_filesize]; + unsigned char* compressed_song_data = new unsigned char[filesize-3]; + + f->seek(4); + f->readString((char *)compressed_song_data, filesize - 4); + fp.close(f); + + // attempt to decompress the song data + // if unsuccessful, deallocate song_data[] on the spot, and return(false) + data_block source, destination; + source.size = filesize-4; + source.data = compressed_song_data; + destination.size = decompressed_filesize; + destination.data = song_data; + + if (!lzw_decompress(source,destination)) + { + delete[] compressed_song_data; + delete[] song_data; + return(false); + } + + // deallocation section + delete[] compressed_song_data; + + rewind(0); + return (true); +} + + +bool Cu6mPlayer::update() +{ + if (!driver_active) + { + driver_active = true; + dec_clip(read_delay); + if (read_delay == 0) + { + command_loop(); + } + + // on all Adlib channels: freq slide/vibrato, mute factor slide + for (int i = 0; i < 9; i++) + { + if (channel_freq_signed_delta[i]!=0) + // frequency slide + mute factor slide + { + // freq slide + freq_slide(i); + + // mute factor slide + if (carrier_mf_signed_delta[i]!=0) + { + mf_slide(i); + } + } + else + // vibrato + mute factor slide + { + // vibrato + if ((vb_multiplier[i]!=0) && ((channel_freq[i].hi & 0x20)==0x20)) + { + vibrato(i); + } + + // mute factor slide + if (carrier_mf_signed_delta[i]!=0) + { + mf_slide(i); + } + } + } + + driver_active = false; + } + + return !songend; +} + + +void Cu6mPlayer::rewind(int subsong) +{ + played_ticks = 0; + songend = false; + + // set the driver's internal variables + byte_pair freq_word = {0,0}; + + driver_active = false; + song_pos = 0; + loop_position = 0; // position of the loop point + read_delay = 0; // delay (in timer ticks) before further song data is read + + for (int i = 0; i < 9; i++) + { + // frequency + channel_freq_signed_delta[i] = 0; + channel_freq[i] = freq_word; // Adlib freq settings for each channel + + // vibrato ("vb") + vb_current_value[i] = 0; + vb_double_amplitude[i] = 0; + vb_multiplier[i] = 0; + vb_direction_flag[i] = 0; + + // mute factor ("mf") == ~(volume) + carrier_mf[i] = 0; + carrier_mf_signed_delta[i] = 0; + carrier_mf_mod_delay_backup[i] = 0; + carrier_mf_mod_delay[i] = 0; + } + + while (!subsong_stack.empty()) // empty subsong stack + subsong_stack.pop(); + + opl->init(); + out_adlib(1,32); // go to OPL2 mode +} + + +float Cu6mPlayer::getrefresh() +{ + return ((float)60); // the Ultima 6 music driver expects to be called at 60 Hz +} + + +// ============================================================================================ +// +// +// Functions called by load() +// +// +// ============================================================================================ + + +// decompress from memory to memory +bool Cu6mPlayer::lzw_decompress(Cu6mPlayer::data_block source, Cu6mPlayer::data_block dest) +{ + bool end_marker_reached = false; + int codeword_size = 9; + long bits_read = 0; + int next_free_codeword = 0x102; + int dictionary_size = 0x200; + MyDict dictionary = MyDict(); + std::stack<unsigned char> root_stack; + + long bytes_written = 0; + + int cW; + int pW; + unsigned char C; + + while (!end_marker_reached) + { + cW = get_next_codeword(bits_read, source.data, codeword_size); + switch (cW) + { + // re-init the dictionary + case 0x100: + codeword_size = 9; + next_free_codeword = 0x102; + dictionary_size = 0x200; + dictionary.reset(); + cW = get_next_codeword(bits_read, source.data, codeword_size); + SAVE_OUTPUT_ROOT((unsigned char)cW, dest, bytes_written); + break; + // end of compressed file has been reached + case 0x101: + end_marker_reached = true; + break; + // (cW <> 0x100) && (cW <> 0x101) + default: + if (cW < next_free_codeword) // codeword is already in the dictionary + { + // create the string associated with cW (on the stack) + get_string(cW,dictionary,root_stack); + C = root_stack.top(); + // output the string represented by cW + while (!root_stack.empty()) + { + SAVE_OUTPUT_ROOT(root_stack.top(), dest, bytes_written); + root_stack.pop(); + } + // add pW+C to the dictionary + dictionary.add(C,pW); + + next_free_codeword++; + if (next_free_codeword >= dictionary_size) + { + if (codeword_size < max_codeword_length) + { + codeword_size += 1; + dictionary_size *= 2; + } + } + } + else // codeword is not yet defined + { + // create the string associated with pW (on the stack) + get_string(pW,dictionary,root_stack); + C = root_stack.top(); + // output the string represented by pW + while (!root_stack.empty()) + { + SAVE_OUTPUT_ROOT(root_stack.top(), dest, bytes_written); + root_stack.pop(); + } + // output the char C + SAVE_OUTPUT_ROOT(C, dest, bytes_written); + + // the new dictionary entry must correspond to cW + // if it doesn't, something is wrong with the lzw-compressed data. + if (cW != next_free_codeword) + { + /* printf("cW != next_free_codeword!\n"); + exit(-1); */ + return false; + } + // add pW+C to the dictionary + dictionary.add(C,pW); + + next_free_codeword++; + if (next_free_codeword >= dictionary_size) + { + if (codeword_size < max_codeword_length) + { + codeword_size += 1; + dictionary_size *= 2; + } + } + }; + break; + } + // shift roles - the current cW becomes the new pW + pW = cW; + } + + return(true); // indicate successful decompression +} + + +// -------------------- +// Additional functions +// -------------------- + + +// Read the next code word from the source buffer +int Cu6mPlayer::get_next_codeword (long& bits_read, unsigned char *source, int codeword_size) +{ + unsigned char b0,b1,b2; + int codeword; + + b0 = source[bits_read/8]; + b1 = source[bits_read/8+1]; + b2 = source[bits_read/8+2]; + + codeword = ((b2 << 16) + (b1 << 8) + b0); + codeword = codeword >> (bits_read % 8); + switch (codeword_size) + { + case 0x9: + codeword = codeword & 0x1ff; + break; + case 0xa: + codeword = codeword & 0x3ff; + break; + case 0xb: + codeword = codeword & 0x7ff; + break; + case 0xc: + codeword = codeword & 0xfff; + break; + default: + codeword = -1; // indicates that an error has occurred + break; + } + + bits_read += codeword_size; + return (codeword); +} + + +// output a root to memory +void Cu6mPlayer::output_root(unsigned char root, unsigned char *destination, long& position) +{ + destination[position] = root; + position++; +} + + +// output the string represented by a codeword +void Cu6mPlayer::get_string(int codeword, Cu6mPlayer::MyDict& dictionary, std::stack<unsigned char>& root_stack) +{ + unsigned char root; + int current_codeword; + + current_codeword = codeword; + + while (current_codeword > 0xff) + { + root = dictionary.get_root(current_codeword); + current_codeword = dictionary.get_codeword(current_codeword); + root_stack.push(root); + } + + // push the root at the leaf + root_stack.push((unsigned char)current_codeword); +} + + +// ============================================================================================ +// +// +// Functions called by update() +// +// +// ============================================================================================ + + +// This function reads the song data and executes the embedded commands. +void Cu6mPlayer::command_loop() +{ + unsigned char command_byte; // current command byte + int command_nibble_hi; // command byte, bits 4-7 + int command_nibble_lo; // command byte, bite 0-3 + bool repeat_loop = true; // + + do + { + // extract low and high command nibbles + command_byte = read_song_byte(); // implicitly increments song_pos + command_nibble_hi = command_byte >> 4; + command_nibble_lo = command_byte & 0xf; + + switch (command_nibble_hi) + { + case 0x0: command_0(command_nibble_lo); break; + case 0x1: command_1(command_nibble_lo); break; + case 0x2: command_2(command_nibble_lo); break; + case 0x3: command_3(command_nibble_lo); break; + case 0x4: command_4(command_nibble_lo); break; + case 0x5: command_5(command_nibble_lo); break; + case 0x6: command_6(command_nibble_lo); break; + case 0x7: command_7(command_nibble_lo); break; + case 0x8: + switch (command_nibble_lo) + { + case 1: command_81(); break; + case 2: command_82(); repeat_loop = false; break; + case 3: command_83(); break; + case 5: command_85(); break; + case 6: command_86(); break; + default: break; // maybe generate an error? + } + break; + case 0xE: command_E(); break; + case 0xF: command_F(); break; + default: break; // maybe generate an error? + } + + } while (repeat_loop); +} + + +// -------------------------------------------------------- +// The commands supported by the U6 music file format +// -------------------------------------------------------- + +// ---------------------------------------- +// Set octave and frequency, note off +// Format: 0c nn +// c = channel, nn = packed Adlib frequency +// ---------------------------------------- +void Cu6mPlayer::command_0(int channel) +{ + unsigned char freq_byte; + byte_pair freq_word; + + freq_byte = read_song_byte(); + freq_word = expand_freq_byte(freq_byte); + set_adlib_freq(channel,freq_word); +} + + +// --------------------------------------------------- +// Set octave and frequency, old note off, new note on +// Format: 1c nn +// c = channel, nn = packed Adlib frequency +// --------------------------------------------------- +void Cu6mPlayer::command_1(int channel) +{ + unsigned char freq_byte; + byte_pair freq_word; + + vb_direction_flag[channel] = 0; + vb_current_value[channel] = 0; + + freq_byte = read_song_byte(); + freq_word = expand_freq_byte(freq_byte); + set_adlib_freq(channel,freq_word); + + freq_word.hi = freq_word.hi | 0x20; // note on + set_adlib_freq(channel,freq_word); +} + + +// ---------------------------------------- +// Set octave and frequency, note on +// Format: 2c nn +// c = channel, nn = packed Adlib frequency +// ---------------------------------------- +void Cu6mPlayer::command_2(int channel) +{ + unsigned char freq_byte; + byte_pair freq_word; + + freq_byte = read_song_byte(); + freq_word = expand_freq_byte(freq_byte); + freq_word.hi = freq_word.hi | 0x20; // note on + set_adlib_freq(channel,freq_word); +} + + +// -------------------------------------- +// Set "carrier mute factor"==not(volume) +// Format: 3c nn +// c = channel, nn = mute factor +// -------------------------------------- +void Cu6mPlayer::command_3(int channel) +{ + unsigned char mf_byte; + + carrier_mf_signed_delta[channel] = 0; + mf_byte = read_song_byte(); + set_carrier_mf(channel,mf_byte); +} + + +// ---------------------------------------- +// set "modulator mute factor"==not(volume) +// Format: 4c nn +// c = channel, nn = mute factor +// ---------------------------------------- +void Cu6mPlayer::command_4(int channel) +{ + unsigned char mf_byte; + + mf_byte = read_song_byte(); + set_modulator_mf(channel,mf_byte); +} + + +// -------------------------------------------- +// Set portamento (pitch slide) +// Format: 5c nn +// c = channel, nn = signed channel pitch delta +// -------------------------------------------- +void Cu6mPlayer::command_5(int channel) +{ + channel_freq_signed_delta[channel] = read_signed_song_byte(); +} + + +// -------------------------------------------- +// Set vibrato paramters +// Format: 6c mn +// c = channel +// m = vibrato double amplitude +// n = vibrato multiplier +// -------------------------------------------- +void Cu6mPlayer::command_6(int channel) +{ + unsigned char vb_parameters; + + vb_parameters = read_song_byte(); + vb_double_amplitude[channel] = vb_parameters >> 4; // high nibble + vb_multiplier[channel] = vb_parameters & 0xF; // low nibble +} + + +// ---------------------------------------- +// Assign Adlib instrument to Adlib channel +// Format: 7c nn +// c = channel, nn = instrument number +// ---------------------------------------- +void Cu6mPlayer::command_7(int channel) +{ + int instrument_offset = instrument_offsets[read_song_byte()]; + out_adlib_opcell(channel, false, 0x20, *(song_data + instrument_offset+0)); + out_adlib_opcell(channel, false, 0x40, *(song_data + instrument_offset+1)); + out_adlib_opcell(channel, false, 0x60, *(song_data + instrument_offset+2)); + out_adlib_opcell(channel, false, 0x80, *(song_data + instrument_offset+3)); + out_adlib_opcell(channel, false, 0xE0, *(song_data + instrument_offset+4)); + out_adlib_opcell(channel, true, 0x20, *(song_data + instrument_offset+5)); + out_adlib_opcell(channel, true, 0x40, *(song_data + instrument_offset+6)); + out_adlib_opcell(channel, true, 0x60, *(song_data + instrument_offset+7)); + out_adlib_opcell(channel, true, 0x80, *(song_data + instrument_offset+8)); + out_adlib_opcell(channel, true, 0xE0, *(song_data + instrument_offset+9)); + out_adlib(0xC0+channel, *(song_data + instrument_offset+10)); +} + + +// ------------------------------------------- +// Branch to a new subsong +// Format: 81 nn aa bb +// nn == number of times to repeat the subsong +// aa == subsong offset (low byte) +// bb == subsong offset (high byte) +// ------------------------------------------- +void Cu6mPlayer::command_81() +{ + subsong_info new_ss_info; + + new_ss_info.subsong_repetitions = read_song_byte(); + new_ss_info.subsong_start = read_song_byte(); new_ss_info.subsong_start += read_song_byte() << 8; + new_ss_info.continue_pos = song_pos; + + subsong_stack.push(new_ss_info); + song_pos = new_ss_info.subsong_start; +} + + +// ------------------------------------------------------------ +// Stop interpreting commands for this timer tick +// Format: 82 nn +// nn == delay (in timer ticks) until further data will be read +// ------------------------------------------------------------ +void Cu6mPlayer::command_82() +{ + read_delay = read_song_byte(); +} + + +// ----------------------------- +// Adlib instrument data follows +// Format: 83 nn <11 bytes> +// nn == instrument number +// ----------------------------- +void Cu6mPlayer::command_83() +{ + unsigned char instrument_number = read_song_byte(); + instrument_offsets[instrument_number] = song_pos; + song_pos += 11; +} + + +// ---------------------------------------------- +// Set -1 mute factor slide (upward volume slide) +// Format: 85 cn +// c == channel +// n == slide delay +// ---------------------------------------------- +void Cu6mPlayer::command_85() +{ + unsigned char data_byte = read_song_byte(); + int channel = data_byte >> 4; // high nibble + unsigned char slide_delay = data_byte & 0xF; // low nibble + carrier_mf_signed_delta[channel] = +1; + carrier_mf_mod_delay[channel] = slide_delay + 1; + carrier_mf_mod_delay_backup[channel] = slide_delay + 1; +} + + +// ------------------------------------------------ +// Set +1 mute factor slide (downward volume slide) +// Format: 86 cn +// c == channel +// n == slide speed +// ------------------------------------------------ +void Cu6mPlayer::command_86() +{ + unsigned char data_byte = read_song_byte(); + int channel = data_byte >> 4; // high nibble + unsigned char slide_delay = data_byte & 0xF; // low nibble + carrier_mf_signed_delta[channel] = -1; + carrier_mf_mod_delay[channel] = slide_delay + 1; + carrier_mf_mod_delay_backup[channel] = slide_delay + 1; +} + + +// -------------- +// Set loop point +// Format: E? +// -------------- +void Cu6mPlayer::command_E() +{ + loop_position = song_pos; +} + + +// --------------------------- +// Return from current subsong +// Format: F? +// --------------------------- +void Cu6mPlayer::command_F() +{ + if (!subsong_stack.empty()) + { + subsong_info temp = subsong_stack.top(); + subsong_stack.pop(); + temp.subsong_repetitions--; + if (temp.subsong_repetitions==0) + { + song_pos = temp.continue_pos; + } + else + { + song_pos = temp.subsong_start; + subsong_stack.push(temp); + } + } + else + { + song_pos = loop_position; + songend = true; + } +} + + +// -------------------- +// Additional functions +// -------------------- + +// This function decrements its argument, without allowing it to become negative. +void Cu6mPlayer::dec_clip(int& param) +{ + param--; + if (param < 0) { param = 0; } +} + + +// Returns the byte at the current song position. +// Side effect: increments song_pos. +unsigned char Cu6mPlayer::read_song_byte() +{ + unsigned char song_byte; + song_byte = song_data[song_pos]; + song_pos++; + return(song_byte); +} + + +// Same as read_song_byte(), except that it returns a signed byte +signed char Cu6mPlayer::read_signed_song_byte() +{ + unsigned char song_byte; + int signed_value; + song_byte = *(song_data + song_pos); + song_pos++; + if (song_byte <= 127) + { + signed_value = song_byte; + } + else + { + signed_value = (int)song_byte - 0x100; + } + return((signed char)signed_value); +} + + +Cu6mPlayer::byte_pair Cu6mPlayer::expand_freq_byte(unsigned char freq_byte) +{ + const byte_pair freq_table[24] = + { + {0x00,0x00}, {0x58,0x01}, {0x82,0x01}, {0xB0,0x01}, + {0xCC,0x01}, {0x03,0x02}, {0x41,0x02}, {0x86,0x02}, + {0x00,0x00}, {0x6A,0x01}, {0x96,0x01}, {0xC7,0x01}, + {0xE4,0x01}, {0x1E,0x02}, {0x5F,0x02}, {0xA8,0x02}, + {0x00,0x00}, {0x47,0x01}, {0x6E,0x01}, {0x9A,0x01}, + {0xB5,0x01}, {0xE9,0x01}, {0x24,0x02}, {0x66,0x02} + }; + + int packed_freq; + int octave; + byte_pair freq_word; + + packed_freq = freq_byte & 0x1F; + octave = freq_byte >> 5; + + // range check (not present in the original U6 music driver) + if (packed_freq >= 24) { packed_freq = 0; } + + freq_word.hi = freq_table[packed_freq].hi + (octave << 2); + freq_word.lo = freq_table[packed_freq].lo; + + return(freq_word); +} + + +void Cu6mPlayer::set_adlib_freq(int channel,Cu6mPlayer::byte_pair freq_word) +{ + out_adlib(0xA0+channel,freq_word.lo); + out_adlib(0xB0+channel,freq_word.hi); + // update the Adlib register backups + channel_freq[channel] = freq_word; +} + + +// this function sets the Adlib frequency, but does not update the register backups +void Cu6mPlayer::set_adlib_freq_no_update(int channel,Cu6mPlayer::byte_pair freq_word) +{ + out_adlib(0xA0+channel,freq_word.lo); + out_adlib(0xB0+channel,freq_word.hi); +} + + +void Cu6mPlayer::set_carrier_mf(int channel,unsigned char mute_factor) +{ + out_adlib_opcell(channel,true,0x40,mute_factor); + carrier_mf[channel] = mute_factor; +} + + +void Cu6mPlayer::set_modulator_mf(int channel,unsigned char mute_factor) +{ + out_adlib_opcell(channel,false,0x40,mute_factor); +} + + +void Cu6mPlayer::freq_slide(int channel) +{ + byte_pair freq = channel_freq[channel]; + + long freq_word = freq.lo + (freq.hi << 8) + channel_freq_signed_delta[channel]; + if (freq_word < 0) { freq_word += 0x10000; } + if (freq_word > 0xFFFF) { freq_word -= 0x10000; } + + freq.lo = freq_word & 0xFF; + freq.hi = (freq_word >> 8) & 0xFF; + set_adlib_freq(channel,freq); +} + + +void Cu6mPlayer::vibrato(int channel) +{ + byte_pair freq; + + if (vb_current_value[channel] >= vb_double_amplitude[channel]) + { vb_direction_flag[channel] = 1; } + else if (vb_current_value[channel] <= 0) + { vb_direction_flag[channel] = 0; } + + if (vb_direction_flag[channel]==0) + { vb_current_value[channel]++; } + else + { vb_current_value[channel]--; } + + long freq_word = channel_freq[channel].lo + (channel_freq[channel].hi << 8); + freq_word += (vb_current_value[channel] - (vb_double_amplitude[channel] >> 1)) + * vb_multiplier[channel]; + if (freq_word < 0) { freq_word += 0x10000; } + if (freq_word > 0xFFFF) { freq_word -= 0x10000; } + + freq.lo = freq_word & 0xFF; + freq.hi = (freq_word >> 8) & 0xFF; + set_adlib_freq_no_update(channel,freq); +} + + +void Cu6mPlayer::mf_slide(int channel) +{ + carrier_mf_mod_delay[channel]--; + if (carrier_mf_mod_delay[channel]==0) + { + carrier_mf_mod_delay[channel] = carrier_mf_mod_delay_backup[channel]; + int current_mf = carrier_mf[channel] + carrier_mf_signed_delta[channel]; + if (current_mf > 0x3F) + { + current_mf = 0x3F; + carrier_mf_signed_delta[channel] = 0; + } + else if (current_mf < 0) + { + current_mf = 0; + carrier_mf_signed_delta[channel] = 0; + } + + set_carrier_mf(channel,(unsigned char)current_mf); + } +} + + +void Cu6mPlayer::out_adlib(unsigned char adlib_register, unsigned char adlib_data) +{ + opl->write(adlib_register,adlib_data); +} + + +void Cu6mPlayer::out_adlib_opcell(int channel, bool carrier, unsigned char adlib_register, unsigned char out_byte) +{ + const unsigned char adlib_channel_to_carrier_offset[9] = + {0x03,0x04,0x05,0x0B,0x0C,0x0D,0x13,0x14,0x15}; + const unsigned char adlib_channel_to_modulator_offset[9] = + {0x00,0x01,0x02,0x08,0x09,0x0A,0x10,0x11,0x12}; + + if (carrier) + { + out_adlib(adlib_register+adlib_channel_to_carrier_offset[channel],out_byte); + } + else + { + out_adlib(adlib_register+adlib_channel_to_modulator_offset[channel],out_byte); + } +} + + +// ============================================================================================ +// +// +// The Dictionary +// +// +// ============================================================================================ + + +Cu6mPlayer::MyDict::MyDict() +{ + dict_size = default_dict_size; + dictionary = new dict_entry[dict_size-0x100]; // don't allocate space for the roots + contains = 0x102; +} + + +Cu6mPlayer::MyDict::MyDict(int max_size) +{ + dict_size = max_size; + dictionary = new dict_entry[dict_size-0x100]; // don't allocate space for the roots + contains = 0x102; +} + + +Cu6mPlayer::MyDict::~MyDict() +{ + delete [] dictionary; +} + +// re-initializes the dictionary +void Cu6mPlayer::MyDict::reset() +{ + contains = 0x102; +} + + +// Note: If the dictionary is already full, this function does nothing. +void Cu6mPlayer::MyDict::add(unsigned char root, int codeword) +{ + if (contains < dict_size) + { + dictionary[contains-0x100].root = root; + dictionary[contains-0x100].codeword = codeword; + contains++; + } +} + + +unsigned char Cu6mPlayer::MyDict::get_root(int codeword) +{ + return (dictionary[codeword-0x100].root); +} + + +int Cu6mPlayer::MyDict::get_codeword(int codeword) +{ + return (dictionary[codeword-0x100].codeword); +} diff --git a/plugins/adplug/adplug/u6m.h b/plugins/adplug/adplug/u6m.h new file mode 100644 index 00000000..30e7f3b5 --- /dev/null +++ b/plugins/adplug/adplug/u6m.h @@ -0,0 +1,168 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2006 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * u6m.h - Ultima 6 Music Player by Marc Winterrowd. + * This code extends the Adlib Winamp plug-in by Simon Peter <dn.tlp@gmx.net> + */ + +#include <stack> + +#include "player.h" + +#define default_dict_size 4096 // because maximum codeword size == 12 bits +#define max_codeword_length 12 // maximum codeword length in bits + +class Cu6mPlayer: public CPlayer +{ + public: + static CPlayer *factory(Copl *newopl); + + Cu6mPlayer(Copl *newopl) : CPlayer(newopl), song_data(0) + { + }; + + + ~Cu6mPlayer() + { + if(song_data) delete[] song_data; + }; + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype() + { + return std::string("Ultima 6 Music"); + }; + + protected: + + struct byte_pair + { + unsigned char lo; + unsigned char hi; + }; + + struct subsong_info // information about a subsong + { + int continue_pos; + int subsong_repetitions; + int subsong_start; + }; + + struct dict_entry // dictionary entry + { + unsigned char root; + int codeword; + }; + + struct data_block // + { + long size; + unsigned char *data; + }; + + class MyDict + { + private: + // The actual number of dictionary entries allocated + // is (dictionary_size-256), because there are 256 roots + // that do not need to be stored. + int contains; // number of entries currently in the dictionary + int dict_size; // max number of entries that will fit into the dictionary + dict_entry* dictionary; + + public: + MyDict(); // use dictionary size of 4096 + MyDict(int); // let the caller specify a dictionary size + ~MyDict(); + void reset(); // re-initializes the dictionary + void add(unsigned char, int); + unsigned char get_root(int); + int get_codeword(int); + }; + + + // class variables + long played_ticks; + + unsigned char* song_data; // the uncompressed .m file (the "song") + bool driver_active; // flag to prevent reentrancy + bool songend; // indicates song end + int song_pos; // current offset within the song + int loop_position; // position of the loop point + int read_delay; // delay (in timer ticks) before further song data is read + std::stack<subsong_info> subsong_stack; + + int instrument_offsets[9]; // offsets of the adlib instrument data + // vibrato ("vb") + unsigned char vb_current_value[9]; + unsigned char vb_double_amplitude[9]; + unsigned char vb_multiplier[9]; + unsigned char vb_direction_flag[9]; + // mute factor ("mf") = not(volume) + unsigned char carrier_mf[9]; + signed char carrier_mf_signed_delta[9]; + unsigned char carrier_mf_mod_delay_backup[9]; + unsigned char carrier_mf_mod_delay[9]; + // frequency + byte_pair channel_freq[9]; // adlib freq settings for each channel + signed char channel_freq_signed_delta[9]; + + // protected functions used by update() + void command_loop(); + unsigned char read_song_byte(); + signed char read_signed_song_byte(); + void dec_clip(int&); + byte_pair expand_freq_byte(unsigned char); + void set_adlib_freq(int channel,byte_pair freq_word); + void set_adlib_freq_no_update(int channel,byte_pair freq_word); + void set_carrier_mf(int channel,unsigned char mute_factor); + void set_modulator_mf(int channel,unsigned char mute_factor); + void freq_slide(int channel); + void vibrato(int channel); + void mf_slide(int channel); + + void command_0(int channel); + void command_1(int channel); + void command_2(int channel); + void command_3(int channel); + void command_4(int channel); + void command_5(int channel); + void command_6(int channel); + void command_7(int channel); + void command_81(); + void command_82(); + void command_83(); + void command_85(); + void command_86(); + void command_E(); + void command_F(); + + void out_adlib(unsigned char adlib_register, unsigned char adlib_data); + void out_adlib_opcell(int channel, bool carrier, unsigned char adlib_register, unsigned char out_byte); + + // protected functions used by load() + bool lzw_decompress(data_block source, data_block dest); + int get_next_codeword (long& bits_read, unsigned char *source, int codeword_size); + void output_root(unsigned char root, unsigned char *destination, long& position); + void get_string(int codeword, MyDict& dictionary, std::stack<unsigned char>& root_stack); +}; + diff --git a/plugins/adplug/adplug/xad.cpp b/plugins/adplug/adplug/xad.cpp new file mode 100644 index 00000000..81c7c0a7 --- /dev/null +++ b/plugins/adplug/adplug/xad.cpp @@ -0,0 +1,140 @@ +/* + Adplug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + xad.cpp - XAD shell player by Riven the Mage <riven@ok.ru> +*/ + +#include "xad.h" +#include "debug.h" + +/* -------- Public Methods -------------------------------- */ + +CxadPlayer::CxadPlayer(Copl * newopl) : CPlayer(newopl) +{ + tune = 0; +} + +CxadPlayer::~CxadPlayer() +{ + if (tune) + delete [] tune; +} + +bool CxadPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + bool ret = false; + + // load header + xad.id = f->readInt(4); + f->readString(xad.title, 36); + f->readString(xad.author, 36); + xad.fmt = f->readInt(2); + xad.speed = f->readInt(1); + xad.reserved_a = f->readInt(1); + + // 'XAD!' - signed ? + if(xad.id != 0x21444158) { fp.close(f); return false; } + + // get file size + tune_size = fp.filesize(f) - 80; + + // load() + tune = new unsigned char [tune_size]; + f->readString((char *)tune, tune_size); + fp.close(f); + + ret = xadplayer_load(); + + if (ret) + rewind(0); + + return ret; +} + +void CxadPlayer::rewind(int subsong) +{ + opl->init(); + + plr.speed = xad.speed; + plr.speed_counter = 1; + plr.playing = 1; + plr.looping = 0; + + // rewind() + xadplayer_rewind(subsong); + +#ifdef DEBUG + AdPlug_LogWrite("-----------\n"); +#endif +} + +bool CxadPlayer::update() +{ + if (--plr.speed_counter) + goto update_end; + + plr.speed_counter = plr.speed; + + // update() + xadplayer_update(); + +update_end: + return (plr.playing && (!plr.looping)); +} + +float CxadPlayer::getrefresh() +{ + return xadplayer_getrefresh(); +} + +std::string CxadPlayer::gettype() +{ + return xadplayer_gettype(); +} + +std::string CxadPlayer::gettitle() +{ + return xadplayer_gettitle(); +} + +std::string CxadPlayer::getauthor() +{ + return xadplayer_getauthor(); +} + +std::string CxadPlayer::getinstrument(unsigned int i) +{ + return xadplayer_getinstrument(i); +} + +unsigned int CxadPlayer::getinstruments() +{ + return xadplayer_getinstruments(); +} + +/* -------- Protected Methods ------------------------------- */ + +void CxadPlayer::opl_write(int reg, int val) +{ + adlib[reg] = val; +#ifdef DEBUG + AdPlug_LogWrite("[ %02X ] = %02X\n",reg,val); +#endif + opl->write(reg,val); +} diff --git a/plugins/adplug/adplug/xad.h b/plugins/adplug/adplug/xad.h new file mode 100644 index 00000000..16872558 --- /dev/null +++ b/plugins/adplug/adplug/xad.h @@ -0,0 +1,97 @@ +/* + AdPlug - Replayer for many OPL2/OPL3 audio file formats. + Copyright (C) 1999 - 2003 Simon Peter <dn.tlp@gmx.net>, et al. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + xad.h - XAD shell player by Riven the Mage <riven@ok.ru> +*/ + +#ifndef H_ADPLUG_XAD +#define H_ADPLUG_XAD + +#include "player.h" + +class CxadPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl); + + CxadPlayer(Copl * newopl); + ~CxadPlayer(); + + bool load(const std::string &filename, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype(); + std::string gettitle(); + std::string getauthor(); + std::string getinstrument(unsigned int i); + unsigned int getinstruments(); + +protected: + virtual void xadplayer_rewind(int subsong) = 0; + virtual bool xadplayer_load() = 0; + virtual void xadplayer_update() = 0; + virtual float xadplayer_getrefresh() = 0; + virtual std::string xadplayer_gettype() = 0; + virtual std::string xadplayer_gettitle() + { + return std::string(xad.title); + } + virtual std::string xadplayer_getauthor() + { + return std::string(xad.author); + } + virtual std::string xadplayer_getinstrument(unsigned int i) + { + return std::string(""); + } + virtual unsigned int xadplayer_getinstruments() + { + return 0; + } + + enum { HYP=1, PSI, FLASH, BMF, RAT, HYBRID }; + + struct xad_header + { + unsigned long id; + char title[36]; + char author[36]; + unsigned short fmt; + unsigned char speed; + unsigned char reserved_a; + } xad; + + unsigned char * tune; + unsigned long tune_size; + + struct + { + int playing; + int looping; + unsigned char speed; + unsigned char speed_counter; + } plr; + + unsigned char adlib[256]; + + void opl_write(int reg, int val); +}; + +#endif diff --git a/plugins/adplug/adplug/xsm.cpp b/plugins/adplug/adplug/xsm.cpp new file mode 100644 index 00000000..3c97de04 --- /dev/null +++ b/plugins/adplug/adplug/xsm.cpp @@ -0,0 +1,117 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * xsm.cpp - eXtra Simple Music Player, by Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> + +#include "xsm.h" + +CxsmPlayer::CxsmPlayer(Copl *newopl) + : CPlayer(newopl), music(0) +{ +} + +CxsmPlayer::~CxsmPlayer() +{ + if(music) delete [] music; +} + +bool CxsmPlayer::load(const std::string &filename, const CFileProvider &fp) +{ + binistream *f = fp.open(filename); if(!f) return false; + char id[6]; + int i, j; + + // check if header matches + f->readString(id, 6); songlen = f->readInt(2); + if(strncmp(id, "ofTAZ!", 6) || songlen > 3200) { fp.close(f); return false; } + + // read and set instruments + for(i = 0; i < 9; i++) { + opl->write(0x20 + op_table[i], f->readInt(1)); + opl->write(0x23 + op_table[i], f->readInt(1)); + opl->write(0x40 + op_table[i], f->readInt(1)); + opl->write(0x43 + op_table[i], f->readInt(1)); + opl->write(0x60 + op_table[i], f->readInt(1)); + opl->write(0x63 + op_table[i], f->readInt(1)); + opl->write(0x80 + op_table[i], f->readInt(1)); + opl->write(0x83 + op_table[i], f->readInt(1)); + opl->write(0xe0 + op_table[i], f->readInt(1)); + opl->write(0xe3 + op_table[i], f->readInt(1)); + opl->write(0xc0 + op_table[i], f->readInt(1)); + f->ignore(5); + } + + // read song data + music = new char [songlen * 9]; + for(i = 0; i < 9; i++) + for(j = 0; j < songlen; j++) + music[j * 9 + i] = f->readInt(1); + + // success + fp.close(f); + rewind(0); + return true; +} + +bool CxsmPlayer::update() +{ + int c; + + if(notenum >= songlen) { + songend = true; + notenum = last = 0; + } + + for(c = 0; c < 9; c++) + if(music[notenum * 9 + c] != music[last * 9 + c]) + opl->write(0xb0 + c, 0); + + for(c = 0; c < 9; c++) { + if(music[notenum * 9 + c]) + play_note(c, music[notenum * 9 + c] % 12, music[notenum * 9 + c] / 12); + else + play_note(c, 0, 0); + } + + last = notenum; + notenum++; + return !songend; +} + +void CxsmPlayer::rewind(int subsong) +{ + notenum = last = 0; + songend = false; +} + +float CxsmPlayer::getrefresh() +{ + return 5.0f; +} + +void CxsmPlayer::play_note(int c, int note, int octv) +{ + int freq = note_table[note]; + + if(!note && !octv) freq = 0; + opl->write(0xa0 + c, freq & 0xff); + opl->write(0xb0 + c, (freq / 0xff) | 32 | (octv * 4)); +} diff --git a/plugins/adplug/adplug/xsm.h b/plugins/adplug/adplug/xsm.h new file mode 100644 index 00000000..15129606 --- /dev/null +++ b/plugins/adplug/adplug/xsm.h @@ -0,0 +1,46 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2003 Simon Peter, <dn.tlp@gmx.net>, et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * xsm.h - eXtra Simple Music Player, by Simon Peter <dn.tlp@gmx.net> + */ + +#include "player.h" + +class CxsmPlayer: public CPlayer +{ +public: + static CPlayer *factory(Copl *newopl) { return new CxsmPlayer(newopl); } + + CxsmPlayer(Copl *newopl); + ~CxsmPlayer(); + + bool load(const std::string &fn, const CFileProvider &fp); + bool update(); + void rewind(int subsong); + float getrefresh(); + + std::string gettype() { return std::string("eXtra Simple Music"); } + +private: + unsigned short songlen; + char *music; + unsigned int last, notenum; + bool songend; + + void play_note(int c, int note, int octv); +}; diff --git a/plugins/adplug/libbinio/COPYING b/plugins/adplug/libbinio/COPYING new file mode 100644 index 00000000..cf9b6b99 --- /dev/null +++ b/plugins/adplug/libbinio/COPYING @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. +^L + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. +^L + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. +^L + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. +^L + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. +^L + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. +^L + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. +^L + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS +^L + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/plugins/adplug/libbinio/binfile.cpp b/plugins/adplug/libbinio/binfile.cpp new file mode 100644 index 00000000..446f899f --- /dev/null +++ b/plugins/adplug/libbinio/binfile.cpp @@ -0,0 +1,247 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binfile.h - Binary file I/O + * Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#include <stdio.h> +#include <errno.h> + +#include "binfile.h" + +/***** binfbase *****/ + +binfbase::binfbase() + : f(NULL) +{ +} + +binfbase::~binfbase() +{ + if(f != NULL) close(); +} + +void binfbase::close() +{ + if(f != NULL) { + if(fclose(f) == EOF) err |= Fatal; else f = NULL; + } else + err |= NotOpen; +} + +void binfbase::seek(long pos, Offset offs) +{ + int error; + + if(f == NULL) { err |= NotOpen; return; } + + switch(offs) { + case Set: error = fseek(f, pos, SEEK_SET); break; + case Add: error = fseek(f, pos, SEEK_CUR); break; + case End: error = fseek(f, pos, SEEK_END); break; + } + + if(error == -1) err |= Fatal; +} + +long binfbase::pos() +{ + long pos; + + if(f == NULL) { err |= NotOpen; return 0; } + + pos = ftell(f); + + if(pos == -1) { + err |= Fatal; + return 0; + } else + return pos; +} + +/***** binifstream *****/ + +binifstream::binifstream() +{ +} + +binifstream::binifstream(const char *filename, const Mode mode) +{ + open(filename, mode); +} + +#if BINIO_ENABLE_STRING +binifstream::binifstream(const std::string &filename, const Mode mode) +{ + open(filename, mode); +} +#endif + +binifstream::~binifstream() +{ +} + +void binifstream::open(const char *filename, const Mode mode) +{ + f = fopen(filename, "rb"); + + if(f == NULL) + switch(errno) { + case ENOENT: err |= NotFound; break; + case EACCES: err |= Denied; break; + default: err |= NotOpen; break; + } +} + +#if BINIO_ENABLE_STRING +void binifstream::open(const std::string &filename, const Mode mode) +{ + open(filename.c_str(), mode); +} +#endif + +binifstream::Byte binifstream::getByte() +{ + int read; + + if(f != NULL) { + read = fgetc(f); + if(read == EOF) err |= Eof; + return (Byte)read; + } else { + err |= NotOpen; + return 0; + } +} + +/***** binofstream *****/ + +binofstream::binofstream() +{ +} + +binofstream::binofstream(const char *filename, const Mode mode) +{ + open(filename, mode); +} + +#if BINIO_ENABLE_STRING +binofstream::binofstream(const std::string &filename, const Mode mode) +{ + open(filename, mode); +} +#endif + +binofstream::~binofstream() +{ +} + +void binofstream::open(const char *filename, const Mode mode) +{ + char *modestr = "wb"; + + // Check if append mode is desired + if(mode & Append) modestr = "ab"; + + f = fopen(filename, modestr); + + if(f == NULL) + switch(errno) { + case EEXIST: + case EACCES: + case EROFS: + err |= Denied; + break; + case ENOENT: err |= NotFound; break; + default: err |= NotOpen; break; + } +} + +#if BINIO_ENABLE_STRING +void binofstream::open(const std::string &filename, const Mode mode) +{ + open(filename.c_str(), mode); +} +#endif + +void binofstream::putByte(Byte b) +{ + if(f == NULL) { err |= NotOpen; return; } + + if(fputc(b, f) == EOF) + err |= Fatal; +} + +/***** binfstream *****/ + +binfstream::binfstream() +{ +} + +binfstream::binfstream(const char *filename, const Mode mode) +{ + open(filename, mode); +} + +#if BINIO_ENABLE_STRING +binfstream::binfstream(const std::string &filename, const Mode mode) +{ + open(filename, mode); +} +#endif + +binfstream::~binfstream() +{ +} + +void binfstream::open(const char *filename, const Mode mode) +{ + char *modestr = "w+b"; // Create & at beginning + int ferror = 0; + + // Apply desired mode + if(mode & NoCreate) { + if(!(mode & Append)) + modestr[0] = 'r'; // NoCreate & at beginning + } else + if(mode & Append) // Create & append + modestr[0] = 'a'; + + f = fopen(filename, modestr); + + // NoCreate & append (emulated -- not possible with standard C fopen()) + if(f != NULL && (mode & Append) && (mode & NoCreate)) + ferror = fseek(f, 0, SEEK_END); + + if(f == NULL || ferror == -1) { + switch(errno) { + case EEXIST: + case EACCES: + case EROFS: + err |= Denied; + break; + case ENOENT: err |= NotFound; break; + default: err |= NotOpen; break; + } + } +} + +#if BINIO_ENABLE_STRING +void binfstream::open(const std::string &filename, const Mode mode) +{ + open(filename.c_str(), mode); +} +#endif diff --git a/plugins/adplug/libbinio/binfile.h b/plugins/adplug/libbinio/binfile.h new file mode 100644 index 00000000..ad24baee --- /dev/null +++ b/plugins/adplug/libbinio/binfile.h @@ -0,0 +1,110 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binfile.h - Binary file I/O + * Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_BINIO_BINFILE +#define H_BINIO_BINFILE + +#include <stdio.h> + +#include "binio.h" + +class binfbase: virtual public binio +{ +public: + typedef enum { + Append = 1 << 0, + NoCreate = 1 << 1 + } ModeFlags; + + typedef int Mode; + + binfbase(); + virtual ~binfbase(); + + virtual void open(const char *filename, const Mode mode) = 0; +#if BINIO_ENABLE_STRING + virtual void open(const std::string &filename, const Mode mode) = 0; +#endif + void close(); + + virtual void seek(long pos, Offset offs = Set); + virtual long pos(); + +protected: + FILE *f; +}; + +class binifstream: public binistream, virtual public binfbase +{ +public: + binifstream(); + binifstream(const char *filename, const Mode mode = NoCreate); +#if BINIO_ENABLE_STRING + binifstream(const std::string &filename, const Mode mode = NoCreate); +#endif + + virtual ~binifstream(); + + virtual void open(const char *filename, const Mode mode = NoCreate); +#if BINIO_ENABLE_STRING + virtual void open(const std::string &filename, const Mode mode = NoCreate); +#endif + +protected: + virtual Byte getByte(); +}; + +class binofstream: public binostream, virtual public binfbase +{ +public: + binofstream(); + binofstream(const char *filename, const Mode mode = 0); +#if BINIO_ENABLE_STRING + binofstream(const std::string &filename, const Mode mode = 0); +#endif + + virtual ~binofstream(); + + virtual void open(const char *filename, const Mode mode = 0); +#if BINIO_ENABLE_STRING + virtual void open(const std::string &filename, const Mode mode = 0); +#endif + +protected: + virtual void putByte(Byte b); +}; + +class binfstream: public binifstream, public binofstream +{ +public: + binfstream(); + binfstream(const char *filename, const Mode mode = 0); +#if BINIO_ENABLE_STRING + binfstream(const std::string &filename, const Mode mode = 0); +#endif + + virtual ~binfstream(); + + virtual void open(const char *filename, const Mode mode = 0); +#if BINIO_ENABLE_STRING + virtual void open(const std::string &filename, const Mode mode = 0); +#endif +}; + +#endif diff --git a/plugins/adplug/libbinio/binio.cpp b/plugins/adplug/libbinio/binio.cpp new file mode 100644 index 00000000..75489788 --- /dev/null +++ b/plugins/adplug/libbinio/binio.cpp @@ -0,0 +1,640 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binio.cpp - Binary stream I/O classes + * Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#include <string.h> + +#include "binio.h" + +#if BINIO_WITH_MATH + +#include <math.h> + +#ifdef __QNXNTO__ + #define pow std::powf +#endif // __QNXNTO__ + +// If 'math.h' doesn't define HUGE_VAL, we try to use HUGE instead. +#ifndef HUGE_VAL +# define HUGE_VAL HUGE +#endif + +#endif + +/***** Defines *****/ + +#if BINIO_ENABLE_STRING +// String buffer size for std::string readString() method +#define STRINGBUFSIZE 256 +#endif + +/***** binio *****/ + +const binio::Flags binio::system_flags = binio::detect_system_flags(); + +const binio::Flags binio::detect_system_flags() +{ + Flags f = 0; + + // Endian test + union { + int word; + Byte byte; + } endian_test; + + endian_test.word = 1; + if(endian_test.byte != 1) f |= BigEndian; + + // IEEE-754 floating-point test + float fl = 6.5; + Byte *dat = (Byte *)&fl; + + if(sizeof(float) == 4 && sizeof(double) == 8) + if(f & BigEndian) { + if(dat[0] == 0x40 && dat[1] == 0xD0 && !dat[2] && !dat[3]) + f |= FloatIEEE; + } else + if(dat[3] == 0x40 && dat[2] == 0xD0 && !dat[1] && !dat[0]) + f |= FloatIEEE; + + return f; +} + +binio::binio() + : my_flags(system_flags), err(NoError) +{ +} + +binio::~binio() +{ +} + +void binio::setFlag(Flag f, bool set) +{ + if(set) + my_flags |= f; + else + my_flags &= !f; +} + +bool binio::getFlag(Flag f) +{ + return (my_flags & f ? true : false); +} + +binio::Error binio::error() +{ + Error e = err; + + err = NoError; + return e; +} + +bool binio::eof() +{ + return (err & Eof ? true : false); +} + +/***** binistream *****/ + +binistream::binistream() +{ +} + +binistream::~binistream() +{ +} + +binistream::Int binistream::readInt(unsigned int size) +{ + unsigned int i; + Int val = 0, in; + + // Check if 'size' doesn't exceed our system's biggest type. + if(size > sizeof(Int)) { + err |= Unsupported; + return 0; + } + + for(i = 0; i < size; i++) { + in = getByte(); + if(getFlag(BigEndian)) + val <<= 8; + else + in <<= i * 8; + val |= in; + } + + return val; +} + +binistream::Float binistream::readFloat(FType ft) +{ + if(getFlag(FloatIEEE)) { // Read IEEE-754 floating-point value + unsigned int i, size; + Byte in[8]; + bool swap; + + // Determine appropriate size for given type. + switch(ft) { + case Single: size = 4; break; // 32 bits + case Double: size = 8; break; // 64 bits + } + + // Determine byte ordering, depending on what we do next + if(system_flags & FloatIEEE) + swap = getFlag(BigEndian) ^ (system_flags & BigEndian); + else + swap = !getFlag(BigEndian); + + // Read the float byte by byte, converting endianess + for(i = 0; i < size; i++) + if(swap) + in[size - i - 1] = getByte(); + else + in[i] = getByte(); + + if(system_flags & FloatIEEE) { + // Compatible system, let the hardware do the conversion + switch(ft) { + case Single: return *(float *)in; + case Double: return *(double *)in; + } + } else { // Incompatible system, convert manually + switch(ft) { + case Single: return ieee_single2float(in); + case Double: return ieee_double2float(in); + } + } + } + + // User tried to read a (yet) unsupported floating-point type. Bail out. + err |= Unsupported; return 0.0; +} + +binistream::Float binistream::ieee_single2float(Byte *data) +{ + signed int sign = data[0] >> 7 ? -1 : 1; + unsigned int exp = ((data[0] << 1) & 0xff) | ((data[1] >> 7) & 1), + fracthi7 = data[1] & 0x7f; + Float fract = fracthi7 * 65536.0 + data[2] * 256.0 + data[3]; + + // Signed and unsigned zero + if(!exp && !fracthi7 && !data[2] && !data[3]) return sign * 0.0; + + // Signed and unsigned infinity (maybe unsupported on non-IEEE systems) + if(exp == 255) + if(!fracthi7 && !data[2] && !data[3]) { +#ifdef HUGE_VAL + if(sign == -1) return -HUGE_VAL; else return HUGE_VAL; +#else + err |= Unsupported; + if(sign == -1) return -1.0; else return 1.0; +#endif + } else { // Not a number (maybe unsupported on non-IEEE systems) +#ifdef NAN + return NAN; +#else + err |= Unsupported; return 0.0; +#endif + } + + if(!exp) // Unnormalized float values + return sign * pow(2, -126) * fract * pow(2, -23); + else // Normalized float values + return sign * pow(2, exp - 127) * (fract * pow(2, -23) + 1); + + err |= Fatal; return 0.0; +} + +binistream::Float binistream::ieee_double2float(Byte *data) +{ + signed int sign = data[0] >> 7 ? -1 : 1; + unsigned int exp = ((unsigned int)(data[0] & 0x7f) << 4) | (data[1] >> 4), + fracthi4 = data[1] & 0xf; + Float fract = fracthi4 * pow(2, 48) + data[2] * pow(2, 40) + data[3] * + pow(2, 32) + data[4] * pow(2, 24) + data[5] * pow(2, 16) + data[6] * + pow(2, 8) + data[7]; + + // Signed and unsigned zero + if(!exp && !fracthi4 && !data[2] && !data[3] && !data[4] && !data[5] && + !data[6] && !data[7]) return sign * 0.0; + + // Signed and unsigned infinity (maybe unsupported on non-IEEE systems) + if(exp == 2047) + if(!fracthi4 && !data[2] && !data[3] && !data[4] && !data[5] && !data[6] && + !data[7]) { +#ifdef HUGE_VAL + if(sign == -1) return -HUGE_VAL; else return HUGE_VAL; +#else + err |= Unsupported; + if(sign == -1) return -1.0; else return 1.0; +#endif + } else { // Not a number (maybe unsupported on non-IEEE systems) +#ifdef NAN + return NAN; +#else + err |= Unsupported; return 0.0; +#endif + } + + if(!exp) // Unnormalized float values + return sign * pow(2, -1022) * fract * pow(2, -52); + else // Normalized float values + return sign * pow(2, exp - 1023) * (fract * pow(2, -52) + 1); + + err |= Fatal; return 0.0; +} + +#if !BINIO_WITH_MATH +binio::Float binio::pow(Float base, signed int exp) +/* Our own, stripped-down version of pow() for not having to depend on 'math.h'. + * This one handles float values for the base and an integer exponent, both + * positive and negative. + */ +{ + int i; + Float val = base; + + if(!exp) return 1.0; + + for(i = 1; i < (exp < 0 ? -exp : exp); i++) + val *= base; + + if(exp < 0) val = 1.0 / val; + + return val; +} +#endif + +unsigned long binistream::readString(char *str, unsigned long maxlen) +{ + unsigned long i; + + for(i = 0; i < maxlen; i++) { + str[i] = (char)getByte(); + if(err) { str[i] = '\0'; return i; } + } + + return maxlen; +} + +unsigned long binistream::readString(char *str, unsigned long maxlen, + const char delim) +{ + unsigned long i; + + for(i = 0; i < maxlen; i++) { + str[i] = (char)getByte(); + if(str[i] == delim || err) { str[i] = '\0'; return i; } + } + + str[maxlen] = '\0'; + return maxlen; +} + +#if BINIO_ENABLE_STRING +std::string binistream::readString(const char delim) +{ + char buf[STRINGBUFSIZE + 1]; + std::string tempstr; + unsigned long read; + + do { + read = readString(buf, STRINGBUFSIZE, delim); + tempstr.append(buf, read); + } while(read == STRINGBUFSIZE); + + return tempstr; +} +#endif + +binistream::Int binistream::peekInt(unsigned int size) +{ + Int val = readInt(size); + if(!err) seek(-(long)size, Add); + return val; +} + +binistream::Float binistream::peekFloat(FType ft) +{ + Float val = readFloat(ft); + + if(!err) + switch(ft) { + case Single: seek(-4, Add); break; + case Double: seek(-8, Add); break; + } + + return val; +} + +bool binistream::ateof() +{ + Error olderr = err; // Save current error state + bool eof_then; + + peekInt(1); + eof_then = eof(); // Get error state of next byte + err = olderr; // Restore original error state + return eof_then; +} + +void binistream::ignore(unsigned long amount) +{ + unsigned long i; + + for(i = 0; i < amount; i++) + getByte(); +} + +/***** binostream *****/ + +binostream::binostream() +{ +} + +binostream::~binostream() +{ +} + +void binostream::writeInt(Int val, unsigned int size) +{ + unsigned int i; + + // Check if 'size' doesn't exceed our system's biggest type. + if(size > sizeof(Int)) { err |= Unsupported; return; } + + for(i = 0; i < size; i++) { + if(getFlag(BigEndian)) + putByte((val >> (size - i - 1) * 8) & 0xff); + else { + putByte(val & 0xff); + val >>= 8; + } + } +} + +void binostream::writeFloat(Float f, FType ft) +{ + if(getFlag(FloatIEEE)) { // Write IEEE-754 floating-point value + unsigned int i, size; + Byte *out; + bool swap; + + if(system_flags & FloatIEEE) { + // compatible system, let the hardware do the conversion + float outf = f; + double outd = f; + + // Hardware could be big or little endian, convert appropriately + swap = getFlag(BigEndian) ^ (system_flags & BigEndian); + + // Determine appropriate size for given type and convert by hardware + switch(ft) { + case Single: size = 4; out = (Byte *)&outf; break; // 32 bits + case Double: size = 8; out = (Byte *)&outd; break; // 64 bits + } + } else { +#if BINIO_WITH_MATH + // incompatible system, do the conversion manually + Byte buf[8]; + + // Our own value is always big endian, just check whether we have to + // convert for a different stream format. + swap = !getFlag(BigEndian); + + // Convert system's float to requested IEEE-754 float + switch(ft) { + case Single: size = 4; float2ieee_single(f, buf); break; + case Double: size = 8; float2ieee_double(f, buf); break; + } + + out = buf; // Make the value ready for writing +#else + // No necessary support routines to do the conversion, bail out! + err |= Unsupported; return; +#endif + } + + // Write the float byte by byte, converting endianess + if(swap) out += size - 1; + for(i = 0; i < size; i++) { + putByte(*out); + if(swap) out--; else out++; + } + + return; // We're done. + } + + // User tried to write an unsupported floating-point type. Bail out. + err |= Unsupported; +} + +#ifdef BINIO_WITH_MATH + +/* + * Single and double floating-point to IEEE-754 equivalent conversion functions + * courtesy of Ken Turkowski. + * + * Copyright (C) 1989-1991 Ken Turkowski. <turk@computer.org> + * + * All rights reserved. + * + * Warranty Information + * Even though I have reviewed this software, I make no warranty + * or representation, either express or implied, with respect to this + * software, its quality, accuracy, merchantability, or fitness for a + * particular purpose. As a result, this software is provided "as is," + * and you, its user, are assuming the entire risk as to its quality + * and accuracy. + * + * This code may be used and freely distributed as long as it includes + * this copyright notice and the above warranty information. + */ + +/**************************************************************** + * The following two routines make up for deficiencies in many + * compilers to convert properly between unsigned integers and + * floating-point. Some compilers which have this bug are the + * THINK_C compiler for the Macintosh and the C compiler for the + * Silicon Graphics MIPS-based Iris. + ****************************************************************/ + +#ifdef applec /* The Apple C compiler works */ +# define FloatToUnsigned(f) ((unsigned long)(f)) +#else +# define FloatToUnsigned(f) ((unsigned long)(((long)((f) - 2147483648.0)) + 2147483647L + 1)) +#endif + +#define SEXP_MAX 255 +#define SEXP_OFFSET 127 +#define SEXP_SIZE 8 +#define SEXP_POSITION (32-SEXP_SIZE-1) + +void binostream::float2ieee_single(Float num, Byte *bytes) +{ + long sign; + register long bits; + + if (num < 0) { /* Can't distinguish a negative zero */ + sign = 0x80000000; + num *= -1; + } else { + sign = 0; + } + + if (num == 0) { + bits = 0; + } else { + Float fMant; + int expon; + + fMant = frexp(num, &expon); + + if ((expon > (SEXP_MAX-SEXP_OFFSET+1)) || !(fMant < 1)) { + /* NaN's and infinities fail second test */ + bits = sign | 0x7F800000; /* +/- infinity */ + } + + else { + long mantissa; + + if (expon < -(SEXP_OFFSET-2)) { /* Smaller than normalized */ + int shift = (SEXP_POSITION+1) + (SEXP_OFFSET-2) + expon; + if (shift < 0) { /* Way too small: flush to zero */ + bits = sign; + } + else { /* Nonzero denormalized number */ + mantissa = (long)(fMant * (1L << shift)); + bits = sign | mantissa; + } + } + + else { /* Normalized number */ + mantissa = (long)floor(fMant * (1L << (SEXP_POSITION+1))); + mantissa -= (1L << SEXP_POSITION); /* Hide MSB */ + bits = sign | ((long)((expon + SEXP_OFFSET - 1)) << SEXP_POSITION) | mantissa; + } + } + } + + bytes[0] = bits >> 24; /* Copy to byte string */ + bytes[1] = bits >> 16; + bytes[2] = bits >> 8; + bytes[3] = bits; +} + +#define DEXP_MAX 2047 +#define DEXP_OFFSET 1023 +#define DEXP_SIZE 11 +#define DEXP_POSITION (32-DEXP_SIZE-1) + +void binostream::float2ieee_double(Float num, Byte *bytes) +{ + long sign; + long first, second; + + if (num < 0) { /* Can't distinguish a negative zero */ + sign = 0x80000000; + num *= -1; + } else { + sign = 0; + } + + if (num == 0) { + first = 0; + second = 0; + } else { + Float fMant, fsMant; + int expon; + + fMant = frexp(num, &expon); + + if ((expon > (DEXP_MAX-DEXP_OFFSET+1)) || !(fMant < 1)) { + /* NaN's and infinities fail second test */ + first = sign | 0x7FF00000; /* +/- infinity */ + second = 0; + } + + else { + long mantissa; + + if (expon < -(DEXP_OFFSET-2)) { /* Smaller than normalized */ + int shift = (DEXP_POSITION+1) + (DEXP_OFFSET-2) + expon; + if (shift < 0) { /* Too small for something in the MS word */ + first = sign; + shift += 32; + if (shift < 0) { /* Way too small: flush to zero */ + second = 0; + } + else { /* Pretty small demorn */ + second = FloatToUnsigned(floor(ldexp(fMant, shift))); + } + } + else { /* Nonzero denormalized number */ + fsMant = ldexp(fMant, shift); + mantissa = (long)floor(fsMant); + first = sign | mantissa; + second = FloatToUnsigned(floor(ldexp(fsMant - mantissa, 32))); + } + } + + else { /* Normalized number */ + fsMant = ldexp(fMant, DEXP_POSITION+1); + mantissa = (long)floor(fsMant); + mantissa -= (1L << DEXP_POSITION); /* Hide MSB */ + fsMant -= (1L << DEXP_POSITION); + first = sign | ((long)((expon + DEXP_OFFSET - 1)) << DEXP_POSITION) | mantissa; + second = FloatToUnsigned(floor(ldexp(fsMant - mantissa, 32))); + } + } + } + + bytes[0] = first >> 24; + bytes[1] = first >> 16; + bytes[2] = first >> 8; + bytes[3] = first; + bytes[4] = second >> 24; + bytes[5] = second >> 16; + bytes[6] = second >> 8; + bytes[7] = second; +} + +#endif // BINIO_WITH_MATH + +unsigned long binostream::writeString(const char *str, unsigned long amount) +{ + unsigned int i; + + if(!amount) amount = strlen(str); + + for(i = 0; i < amount; i++) { + putByte(str[i]); + if(err) return i; + } + + return amount; +} + +#if BINIO_ENABLE_STRING +unsigned long binostream::writeString(const std::string &str) +{ + return writeString(str.c_str()); +} +#endif diff --git a/plugins/adplug/libbinio/binio.h b/plugins/adplug/libbinio/binio.h new file mode 100644 index 00000000..31bcfac6 --- /dev/null +++ b/plugins/adplug/libbinio/binio.h @@ -0,0 +1,175 @@ +/* -*-C++-*- + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binio.h - Binary stream I/O classes + * Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_BINIO_BINIO +#define H_BINIO_BINIO + +/***** Configuration *****/ + +// BINIO_ENABLE_STRING - Build std::string supporting methods +// +// Set to 1 to build std::string supporting methods. You need the STL to +// do this. +#define BINIO_ENABLE_STRING 1 + +// BINIO_ENABLE_IOSTREAM - Build iostream wrapper classes +// +// Set to 1 to build the iostream wrapper classes. You need the standard +// C++ library to do this. +#define BINIO_ENABLE_IOSTREAM 1 + +// BINIO_ISO_STDLIB - Build with ISO C++ standard library compliance +// +// Set to 1 to build for the ISO standard C++ library (i.e. namespaces, STL and +// templatized iostream). Set to 0 to build for the traditional C++ library. +#define BINIO_ISO_STDLIB 1 + +// BINIO_WITH_MATH - Build with 'math.h' dependency to allow float conversions +// +// Set to 1 to also build routines that depend on the 'math.h' standard C header +// file (this sometimes also implies a 'libm' or 'libmath' dependency). These +// routines are needed in order to write IEEE-754 floating-point numbers on a +// system that doesn't support this format natively. For only reading these +// numbers, however, these routines are not needed. If set to 0, writing +// IEEE-754 numbers on an incompatible system will be disabled. +#define BINIO_WITH_MATH 1 + +/***** Implementation *****/ + +// Disable annoying multiple inheritance compiler warning on MSVC6 +#ifdef _MSC_VER +# pragma warning(disable: 4250) +#endif + +#if BINIO_ENABLE_STRING +#include <string> +#endif + +class binio +{ +public: + typedef enum { + BigEndian = 1 << 0, + FloatIEEE = 1 << 1 + } Flag; + + typedef enum { + NoError = 0, + Fatal = 1 << 0, + Unsupported = 1 << 1, + NotOpen = 1 << 2, + Denied = 1 << 3, + NotFound = 1 << 4, + Eof = 1 << 5 + } ErrorCode; + + typedef enum { Set, Add, End } Offset; + typedef enum { Single, Double } FType; + typedef int Error; + + binio(); + virtual ~binio(); + + void setFlag(Flag f, bool set = true); + bool getFlag(Flag f); + + Error error(); + bool eof(); + + virtual void seek(long, Offset = Set) = 0; + virtual long pos() = 0; + +protected: + typedef long long Int; + typedef long double Float; + typedef unsigned char Byte; // has to be unsigned! + + typedef int Flags; + + Flags my_flags; + static const Flags system_flags; + Error err; + + // Some math.h emulation functions... +#if !BINIO_WITH_MATH + Float pow(Float base, signed int exp); + Float ldexp(Float x, signed int exp) { return x * pow(2, exp); } +#endif + +private: + static const Flags detect_system_flags(); +}; + +class binistream: virtual public binio +{ +public: + binistream(); + virtual ~binistream(); + + Int readInt(unsigned int size); + Float readFloat(FType ft); + unsigned long readString(char *str, unsigned long amount); + unsigned long readString(char *str, unsigned long maxlen, const char delim); +#if BINIO_ENABLE_STRING + std::string readString(const char delim = '\0'); +#endif + + Int peekInt(unsigned int size); + Float peekFloat(FType ft); + + bool ateof(); + void ignore(unsigned long amount = 1); + +protected: + virtual Byte getByte() = 0; + +private: + Float ieee_single2float(Byte *data); + Float ieee_double2float(Byte *data); +}; + +class binostream: virtual public binio +{ +public: + binostream(); + virtual ~binostream(); + + void writeInt(Int val, unsigned int size); + void writeFloat(Float f, FType ft); + unsigned long writeString(const char *str, unsigned long amount = 0); +#if BINIO_ENABLE_STRING + unsigned long writeString(const std::string &str); +#endif + +protected: + virtual void putByte(Byte) = 0; + +private: + void float2ieee_single(Float f, Byte *data); + void float2ieee_double(Float f, Byte *data); +}; + +class binstream: public binistream, public binostream +{ +public: + binstream(); + virtual ~binstream(); +}; + +#endif diff --git a/plugins/adplug/libbinio/binstr.cpp b/plugins/adplug/libbinio/binstr.cpp new file mode 100644 index 00000000..0e3a9f86 --- /dev/null +++ b/plugins/adplug/libbinio/binstr.cpp @@ -0,0 +1,114 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binstr.cpp - Binary I/O on standard C strings in memory + * Copyright (C) 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#include "binstr.h" + +/***** binsbase *****/ + +binsbase::binsbase(void *str, unsigned long len) + : data((Byte *)str), spos((Byte *)str), length(len) +{ +} + +binsbase::~binsbase() +{ +} + +void binsbase::seek(long p, Offset offs) +{ + switch(offs) { + case Set: spos = data + p; break; + case Add: spos += p; break; + case End: spos = data + length - 1 + p; break; + } + + // Seek before start of data + if(spos < data) { + err |= Eof; + spos = data; + return; + } + + // Seek after end of data + if(spos - data >= length) { + err |= Eof; + spos = data + length - 1; + } +} + +long binsbase::pos() +{ + return (long)(spos - data); +} + +/***** binisstream *****/ + +binisstream::binisstream(void *str, unsigned long len) + : binsbase(str, len) +{ +} + +binisstream::~binisstream() +{ +} + +binisstream::Byte binisstream::getByte() +{ + Byte in = 0; + + if(spos - data >= length) + err |= Eof; + else { + in = *spos; + spos++; + } + + return in; +} + +/***** binosstream *****/ + +binosstream::binosstream(void *str, unsigned long len) + : binsbase(str, len) +{ +} + +binosstream::~binosstream() +{ +} + +void binosstream::putByte(Byte b) +{ + *spos = b; + spos++; + + if(spos - data >= length) + spos = data + length - 1; +} + +/***** binsstream *****/ + +binsstream::binsstream(void *str, unsigned long len) + : binsbase(str, len), binisstream(str, len), binosstream(str, len) +{ +} + +binsstream::~binsstream() +{ +} diff --git a/plugins/adplug/libbinio/binstr.h b/plugins/adplug/libbinio/binstr.h new file mode 100644 index 00000000..33e4b12c --- /dev/null +++ b/plugins/adplug/libbinio/binstr.h @@ -0,0 +1,66 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binstr.h - Binary I/O on standard C strings in memory + * Copyright (C) 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_BINIO_BINSTR +#define H_BINIO_BINSTR + +#include "binio.h" + +class binsbase: virtual public binio +{ +public: + binsbase(void *str, unsigned long len); + virtual ~binsbase(); + + virtual void seek(long p, Offset offs = Set); + virtual long pos(); + +protected: + Byte *data, *spos; + long length; +}; + +class binisstream: public binistream, virtual public binsbase +{ +public: + binisstream(void *str, unsigned long len); + virtual ~binisstream(); + +protected: + virtual Byte getByte(); +}; + +class binosstream: public binostream, virtual public binsbase +{ +public: + binosstream(void *str, unsigned long len); + virtual ~binosstream(); + +protected: + virtual void putByte(Byte b); +}; + +class binsstream: public binisstream, public binosstream +{ +public: + binsstream(void *str, unsigned long len); + virtual ~binsstream(); +}; + +#endif diff --git a/plugins/adplug/libbinio/binwrap.cpp b/plugins/adplug/libbinio/binwrap.cpp new file mode 100644 index 00000000..740ee982 --- /dev/null +++ b/plugins/adplug/libbinio/binwrap.cpp @@ -0,0 +1,132 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binwrap.cpp - Binary I/O wrapper, using standard iostreams library + * Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#include <stdio.h> +#include "binwrap.h" + +#if BINIO_ENABLE_IOSTREAM + +/***** biniwstream *****/ + +biniwstream::biniwstream(istream *istr) + : in(istr) +{ +} + +biniwstream::~biniwstream() +{ +} + +void biniwstream::seek(long pos, Offset offs) +{ + if(!in) { err = NotOpen; return; } + + switch(offs) { + case Set: in->seekg(pos, ios::beg); break; + case Add: in->seekg(pos, ios::cur); break; + case End: in->seekg(pos, ios::end); break; + } +} + +biniwstream::Byte biniwstream::getByte() +{ + if(!in) { err = NotOpen; return 0; } + + int i = in->get(); + if(i == EOF) err |= Eof; + return (Byte)i; +} + +long biniwstream::pos() +{ + if(!in) { err = NotOpen; return 0; } + return (long)in->tellg(); +} + +/***** binowstream *****/ + +binowstream::binowstream(ostream *ostr) + : out(ostr) +{ +} + +binowstream::~binowstream() +{ +} + +void binowstream::seek(long pos, Offset offs) +{ + if(!out) { err = NotOpen; return; } + + switch(offs) { + case Set: out->seekp(pos, ios::beg); break; + case Add: out->seekp(pos, ios::cur); break; + case End: out->seekp(pos, ios::end); break; + } +} + +void binowstream::putByte(binio::Byte b) +{ + if(!out) { err = NotOpen; return; } + out->put((char)b); +} + +long binowstream::pos() +{ + if(!out) { err = NotOpen; return 0; } + return (long)out->tellp(); +} + +/***** binwstream *****/ + +binwstream::binwstream(iostream *str) + : biniwstream(str), binowstream(str), io(str) +{ +} + +binwstream::~binwstream() +{ +} + +void binwstream::seek(long pos, Offset offs) +{ + biniwstream::seek(pos, offs); + binowstream::seek(pos, offs); +} + +long binwstream::pos() +{ + if(!io) { err = NotOpen; return 0; } + return (long)io->tellg(); +} + +binwstream::Byte binwstream::getByte() +{ + Byte in = biniwstream::getByte(); + binowstream::seek(biniwstream::pos(), Set); // sync stream position + return in; +} + +void binwstream::putByte(Byte b) +{ + binowstream::putByte(b); + biniwstream::seek(binowstream::pos(), Set); // sync stream position +} + +#endif diff --git a/plugins/adplug/libbinio/binwrap.h b/plugins/adplug/libbinio/binwrap.h new file mode 100644 index 00000000..74b009f6 --- /dev/null +++ b/plugins/adplug/libbinio/binwrap.h @@ -0,0 +1,92 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * binwrap.h - Binary I/O wrapper, using standard iostreams library + * Copyright (C) 2002, 2003 Simon Peter <dn.tlp@gmx.net> + */ + +#ifndef H_BINIO_BINWRAP +#define H_BINIO_BINWRAP + +#include "binio.h" + +#if BINIO_ENABLE_IOSTREAM + +#if BINIO_ISO_STDLIB +#include <iostream> + +using std::iostream; +using std::ostream; +using std::istream; +using std::ios; +using std::streambuf; +#else + +#include <iostream.h> + +#endif + +class biniwstream: public binistream +{ +public: + biniwstream(istream *istr); + virtual ~biniwstream(); + + virtual void seek(long pos, Offset offs = Set); + virtual long pos(); + +protected: + virtual Byte getByte(); + +private: + istream *in; +}; + +class binowstream: public binostream +{ +public: + binowstream(ostream *ostr); + virtual ~binowstream(); + + virtual void seek(long pos, Offset offs = Set); + virtual long pos(); + +protected: + virtual void putByte(Byte b); + +private: + ostream *out; +}; + +class binwstream: public biniwstream, public binowstream +{ +public: + binwstream(iostream *str); + virtual ~binwstream(); + + virtual void seek(long pos, Offset offs = Set); + virtual long pos(); + +protected: + virtual Byte getByte(); + virtual void putByte(Byte b); + +private: + iostream *io; +}; + +#endif + +#endif diff --git a/plugins/adplug/plugin.c b/plugins/adplug/plugin.c new file mode 100644 index 00000000..b16254bb --- /dev/null +++ b/plugins/adplug/plugin.c @@ -0,0 +1,68 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +// this is a decoder plugin skeleton +// use to create new decoder plugins + +#include "../../deadbeef.h" + +extern const char *adplug_exts[]; +extern const char *adplug_filetypes[]; + +int +adplug_init (DB_playItem_t *it); +void +adplug_free (void); +int +adplug_read_int16 (char *bytes, int size); +int +adplug_seek_sample (int sample); +int +adplug_seek (float time); +DB_playItem_t * +adplug_insert (DB_playItem_t *after, const char *fname); +int +adplug_start (void); +int +adplug_stop (void); + +// define plugin interface +DB_decoder_t adplug_plugin = { + DB_PLUGIN_SET_API_VERSION + .plugin.version_major = 0, + .plugin.version_minor = 1, + .plugin.type = DB_PLUGIN_DECODER, + .plugin.name = "Adplug player", + .plugin.descr = "Adplug player (ADLIB OPL2/OPL3 emulator)", + .plugin.author = "Alexey Yakovenko", + .plugin.email = "waker@users.sourceforge.net", + .plugin.website = "http://deadbeef.sf.net", + .plugin.start = adplug_start, + .plugin.stop = adplug_stop, + .init = adplug_init, + .free = adplug_free, + .read_int16 = adplug_read_int16, + .seek = adplug_seek, + .seek_sample = adplug_seek_sample, + .insert = adplug_insert, + .exts = adplug_exts, + .id = "adplug", + .filetypes = adplug_filetypes +}; + diff --git a/plugins/alsa/Makefile.am b/plugins/alsa/Makefile.am new file mode 100644 index 00000000..265c9b50 --- /dev/null +++ b/plugins/alsa/Makefile.am @@ -0,0 +1,9 @@ +if HAVE_ALSA +alsadir = $(libdir)/$(PACKAGE) +pkglib_LTLIBRARIES = alsa.la +alsa_la_SOURCES = alsa.c +alsa_la_LDFLAGS = -module +alsa_la_LIBADD = $(LDADD) $(ALSA_DEPS_LIBS) + +AM_CFLAGS = $(CFLAGS) -std=c99 $(ALSA_DEPS_CFLAGS) +endif diff --git a/palsa.c b/plugins/alsa/alsa.c index e214ce5a..22c3e9b3 100644 --- a/palsa.c +++ b/plugins/alsa/alsa.c @@ -20,19 +20,13 @@ #include <stdint.h> #include <unistd.h> #include <sys/prctl.h> -#ifdef HAVE_CONFIG_H -# include <config.h> -#endif -#include "palsa.h" -#include "threading.h" -#include "streamer.h" -#include "conf.h" -#include "volume.h" -#include "messagepump.h" #include "deadbeef.h" -//#define trace(...) { fprintf(stderr, __VA_ARGS__); } -#define trace(fmt,...) +#define trace(...) { fprintf(stderr, __VA_ARGS__); } +//#define trace(fmt,...) + +static DB_output_t plugin; +DB_functions_t *deadbeef; static snd_pcm_t *audio; static int bufsize = -1; @@ -49,7 +43,49 @@ static void palsa_callback (char *stream, int len); static void -palsa_thread (uintptr_t context); +palsa_thread (void *context); + +static int +palsa_init (void); + +static int +palsa_free (void); + +static int +palsa_change_rate (int rate); + +static int +palsa_play (void); + +static int +palsa_stop (void); + +static int +palsa_isstopped (void); + +static int +palsa_ispaused (void); + +static int +palsa_pause (void); + +static int +palsa_unpause (void); + +static int +palsa_get_rate (void); + +static int +palsa_get_bps (void); + +static int +palsa_get_channels (void); + +static int +palsa_get_endianness (void); + +static void +palsa_enum_soundcards (void (*callback)(const char *name, const char *desc, void*), void *userdata); static int palsa_set_hw_params (int samplerate) { @@ -129,7 +165,7 @@ palsa_set_hw_params (int samplerate) { trace ("Unable to get buffer size for playback: %s\n", snd_strerror(err)); goto error; } - trace ("alsa buffer size: %d frames\n", size); + trace ("alsa buffer size: %d frames\n", (int)size); bufsize = size; if ((err = snd_pcm_hw_params (audio, hw_params)) < 0) { @@ -149,8 +185,8 @@ palsa_init (void) { int err; // get and cache conf variables - strcpy (conf_alsa_soundcard, conf_get_str ("alsa_soundcard", "default")); - conf_alsa_resample = conf_get_int ("alsa.resample", 0); + strcpy (conf_alsa_soundcard, deadbeef->conf_get_str ("alsa_soundcard", "default")); + conf_alsa_resample = deadbeef->conf_get_int ("alsa.resample", 0); trace ("alsa_soundcard: %s\n", conf_alsa_soundcard); trace ("alsa.resample: %d\n", conf_alsa_resample); @@ -163,7 +199,7 @@ palsa_init (void) { return -1; } - mutex = mutex_create (); + mutex = deadbeef->mutex_create (); if (palsa_set_hw_params (alsa_rate) < 0) { goto open_error; @@ -191,7 +227,7 @@ palsa_init (void) { snd_strerror (err)); goto open_error; } - trace ("alsa period size: %d frames\n", av); + trace ("alsa period size: %d frames\n", (int)av); if ((err = snd_pcm_sw_params_set_start_threshold (audio, sw_params, 0U)) < 0) { trace ("cannot set start mode (%s)\n", @@ -219,7 +255,7 @@ palsa_init (void) { snd_pcm_start (audio); alsa_terminate = 0; - alsa_tid = thread_start (palsa_thread, 0); + alsa_tid = deadbeef->thread_start (palsa_thread, NULL); return 0; @@ -244,13 +280,13 @@ palsa_change_rate (int rate) { return rate; } trace ("trying to change samplerate to: %d\n", rate); - mutex_lock (mutex); + deadbeef->mutex_lock (mutex); snd_pcm_drop (audio); int ret = palsa_set_hw_params (rate); if (state != 0) { snd_pcm_start (audio); } - mutex_unlock (mutex); + deadbeef->mutex_unlock (mutex); if (ret < 0) { return -1; } @@ -258,19 +294,21 @@ palsa_change_rate (int rate) { return alsa_rate; } -void +int palsa_free (void) { trace ("palsa_free\n"); if (audio && !alsa_terminate) { alsa_terminate = 1; - thread_join (alsa_tid); + printf ("waiting for alsa thread to finish\n"); + deadbeef->thread_join (alsa_tid); alsa_tid = 0; snd_pcm_close(audio); audio = NULL; - mutex_free (mutex); + deadbeef->mutex_free (mutex); state = 0; alsa_terminate = 0; } + return 0; } static void @@ -322,15 +360,15 @@ palsa_stop (void) { return 0; } state = 0; - if (conf_get_int ("alsa.freeonstop", 0)) { + if (deadbeef->conf_get_int ("alsa.freeonstop", 0)) { palsa_free (); } else { - mutex_lock (mutex); + deadbeef->mutex_lock (mutex); snd_pcm_drop (audio); - mutex_unlock (mutex); + deadbeef->mutex_unlock (mutex); } - streamer_reset (1); + deadbeef->streamer_reset (1); return 0; } @@ -368,11 +406,33 @@ palsa_unpause (void) { int palsa_get_rate (void) { + if (!audio) { + palsa_init (); + } return alsa_rate; } +int +palsa_get_bps (void) { + return 16; +} + +int +palsa_get_channels (void) { + return 2; +} + +static int +palsa_get_endianness (void) { +#if WORDS_BIGENDIAN + return 1; +#else + return 0; +#endif +} + static void -palsa_thread (uintptr_t context) { +palsa_thread (void *context) { prctl (PR_SET_NAME, "deadbeef-alsa", 0, 0, 0, 0); int err; for (;;) { @@ -384,21 +444,21 @@ palsa_thread (uintptr_t context) { continue; } - mutex_lock (mutex); + deadbeef->mutex_lock (mutex); /* wait till the interface is ready for data, or 1 second has elapsed. */ if ((err = snd_pcm_wait (audio, 500)) < 0 && state == 1) { if (err == -ESTRPIPE) { trace ("alsa: trying to recover from suspend...\n"); - messagepump_push (M_REINIT_SOUND, 0, 0, 0); - mutex_unlock (mutex); + deadbeef->sendmessage (M_REINIT_SOUND, 0, 0, 0); + deadbeef->mutex_unlock (mutex); break; } else { trace ("alsa: trying to recover from xrun...\n"); snd_pcm_prepare (audio); - mutex_unlock (mutex); + deadbeef->mutex_unlock (mutex); continue; } } @@ -408,11 +468,11 @@ palsa_thread (uintptr_t context) { snd_pcm_sframes_t frames_to_deliver; if ((frames_to_deliver = snd_pcm_avail_update (audio)) < 0) { if (frames_to_deliver == -EPIPE) { - mutex_unlock (mutex); + deadbeef->mutex_unlock (mutex); trace ("an xrun occured\n"); continue; } else { - mutex_unlock (mutex); + deadbeef->mutex_unlock (mutex); trace ("unknown ALSA avail update return value (%d)\n", (int)frames_to_deliver); continue; @@ -429,18 +489,18 @@ palsa_thread (uintptr_t context) { snd_pcm_prepare (audio); snd_pcm_start (audio); } - mutex_unlock (mutex); + deadbeef->mutex_unlock (mutex); usleep (1000); // this must be here to prevent mutex deadlock } } static void palsa_callback (char *stream, int len) { - if (!streamer_ok_to_read (len)) { + if (!deadbeef->streamer_ok_to_read (len)) { memset (stream, 0, len); return; } - int bytesread = streamer_read (stream, len); + int bytesread = deadbeef->streamer_read (stream, len); // FIXME: move volume control to streamer_read for copy optimization #if 0 @@ -474,7 +534,7 @@ palsa_callback (char *stream, int len) { ); #else - int16_t ivolume = volume_get_amp () * 1000; + int16_t ivolume = deadbeef->volume_get_amp () * 1000; for (int i = 0; i < bytesread/2; i++) { ((int16_t*)stream)[i] = (int16_t)(((int32_t)(((int16_t*)stream)[i])) * ivolume / 1000); } @@ -484,14 +544,15 @@ palsa_callback (char *stream, int len) { } } -void -palsa_configchanged (void) { - int alsa_resample = conf_get_int ("alsa.resample", 0); - const char *alsa_soundcard = conf_get_str ("alsa_soundcard", "default"); +static int +palsa_configchanged (DB_event_t *ev, uintptr_t data) { + int alsa_resample = deadbeef->conf_get_int ("alsa.resample", 0); + const char *alsa_soundcard = deadbeef->conf_get_str ("alsa_soundcard", "default"); if (alsa_resample != conf_alsa_resample || strcmp (alsa_soundcard, conf_alsa_soundcard)) { - messagepump_push (M_REINIT_SOUND, 0, 0, 0); + deadbeef->sendmessage (M_REINIT_SOUND, 0, 0, 0); } + return 0; } // derived from alsa-utils/aplay.c @@ -522,3 +583,55 @@ palsa_enum_soundcards (void (*callback)(const char *name, const char *desc, void } snd_device_name_free_hint(hints); } + +int +palsa_get_state (void) { + return state; +} + +int +alsa_start (void) { + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_CONFIGCHANGED, DB_CALLBACK (palsa_configchanged), 0); + return 0; +} + +int +alsa_stop (void) { + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_CONFIGCHANGED, DB_CALLBACK (palsa_configchanged), 0); + return 0; +} + +DB_plugin_t * +alsa_load (DB_functions_t *api) { + deadbeef = api; + return DB_PLUGIN (&plugin); +} + +// define plugin interface +static DB_output_t plugin = { + DB_PLUGIN_SET_API_VERSION + .plugin.version_major = 0, + .plugin.version_minor = 1, + .plugin.nostop = 1, + .plugin.type = DB_PLUGIN_OUTPUT, + .plugin.name = "ALSA output plugin", + .plugin.descr = "plays sound through linux standard alsa library", + .plugin.author = "Alexey Yakovenko", + .plugin.email = "waker@users.sourceforge.net", + .plugin.website = "http://deadbeef.sf.net", + .plugin.start = alsa_start, + .plugin.stop = alsa_stop, + .init = palsa_init, + .free = palsa_free, + .change_rate = palsa_change_rate, + .play = palsa_play, + .stop = palsa_stop, + .pause = palsa_pause, + .unpause = palsa_unpause, + .state = palsa_get_state, + .samplerate = palsa_get_rate, + .bitspersample = palsa_get_bps, + .channels = palsa_get_channels, + .endianness = palsa_get_endianness, + .enum_soundcards = palsa_enum_soundcards, +}; diff --git a/plugins/cdda/cdda.c b/plugins/cdda/cdda.c index 4effd077..63b3edde 100644 --- a/plugins/cdda/cdda.c +++ b/plugins/cdda/cdda.c @@ -15,6 +15,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* screwed/maintained by Alexey Yakovenko <waker@users.sourceforge.net> */ + #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -44,11 +47,13 @@ static unsigned int tail_len; static int current_sector; static unsigned int current_sample = 0; static uintptr_t mutex; +static intptr_t cddb_tid; + +#define DEFAULT_SERVER "freedb.org" +#define DEFAULT_PORT 888 +#define DEFAULT_USE_CDDB 1 +#define DEFAULT_PROTOCOL 1 -static int use_cddb = 1; -static char server[1024] = "freedb.org"; -static int port = 888; -static int proto_cddb = 1; struct cddb_thread_params { @@ -72,15 +77,6 @@ trim (char* s) } static int -read_config () -{ - use_cddb = deadbeef->conf_get_int ("cdda.freedb.enable", 1); - strncpy (server, deadbeef->conf_get_str ("cdda.freedb.host", "freedb.org"), sizeof (server)-1); - port = deadbeef->conf_get_int ("cdda.freedb.port", 888); - proto_cddb = deadbeef->conf_get_int ("cdda.protocol", 1); // 1 is cddb, 0 is http -} - -static int cda_init (DB_playItem_t *it) { // trace ("CDA: initing %s\n", it->fname); @@ -239,10 +235,10 @@ resolve_disc (CdIo_t *cdio) conn = cddb_new(); - cddb_set_server_name (conn, server); - cddb_set_server_port (conn, port); + cddb_set_server_name (conn, deadbeef->conf_get_str ("cdda.freedb.host", DEFAULT_SERVER)); + cddb_set_server_port (conn, deadbeef->conf_get_int ("cdda.freedb.port", DEFAULT_PORT)); - if (!proto_cddb) + if (!deadbeef->conf_get_int ("cdda.protocol", DEFAULT_PROTOCOL)) { cddb_http_enable (conn); if (deadbeef->conf_get_int ("network.proxy", 0)) @@ -298,7 +294,7 @@ insert_single_track (CdIo_t* cdio, DB_playItem_t *after, const char* file, int t } static void -cddb_thread (uintptr_t items_i) +cddb_thread (void *items_i) { struct cddb_thread_params *params = (struct cddb_thread_params*)items_i; DB_playItem_t **items = params->items; @@ -346,6 +342,7 @@ cddb_thread (uintptr_t items_i) cddb_disc_destroy (disc); deadbeef->mutex_unlock (mutex); free (params); + cddb_tid = 0; } static DB_playItem_t * @@ -398,7 +395,12 @@ cda_insert (DB_playItem_t *after, const char *fname) { p->items[i] = res; } trace ("cdda: querying freedb...\n"); - deadbeef->thread_start (cddb_thread, (uintptr_t)p); //will destroy cdio + if (deadbeef->conf_get_int ("cdda.freedb.enable", DEFAULT_USE_CDDB)) { + if (cddb_tid) { + deadbeef->thread_join (cddb_tid); + } + cddb_tid = deadbeef->thread_start (cddb_thread, p); //will destroy cdio + } } else { @@ -412,16 +414,29 @@ cda_insert (DB_playItem_t *after, const char *fname) { static int cda_start (void) { mutex = deadbeef->mutex_create (); + return 0; } static int cda_stop (void) { + if (cddb_tid) { + fprintf (stderr, "cdda: waiting cddb query to end\n"); + deadbeef->thread_join (cddb_tid); + } deadbeef->mutex_free (mutex); + return 0; } static const char *exts[] = { "cda", "nrg", NULL }; static const char *filetypes[] = { "cdda", NULL }; +static const char settings_dlg[] = + "property \"Use CDDB/FreeDB\" checkbox cdda.freedb.enable 1;\n" + "property \"CDDB url (e.g. 'freedb.org')\" entry cdda.freedb.host freedb.org;\n" + "property \"CDDB port number (e.g. '888')\" entry cdda.freedb.port 888;\n" + "property \"Prefer CDDB protocol over HTTP\" checkbox cdda.protocol 1;" +; + // define plugin interface static DB_decoder_t plugin = { DB_PLUGIN_SET_API_VERSION @@ -430,11 +445,12 @@ static DB_decoder_t plugin = { .plugin.type = DB_PLUGIN_DECODER, .plugin.name = "Audio CD player", .plugin.descr = "using libcdio, includes .nrg image support", - .plugin.author = "Viktor Semykin", - .plugin.email = "thesame.ml@gmail.com", + .plugin.author = "Viktor Semykin, Alexey Yakovenko", + .plugin.email = "thesame.ml@gmail.com, waker@users.sourceforge.net", .plugin.website = "http://deadbeef.sf.net", .plugin.start = cda_start, .plugin.stop = cda_stop, + .plugin.configdialog = settings_dlg, .init = cda_init, .free = cda_free, .read_int16 = cda_read_int16, @@ -449,7 +465,7 @@ static DB_decoder_t plugin = { DB_plugin_t * cdda_load (DB_functions_t *api) { deadbeef = api; - read_config(); +// read_config(); return DB_PLUGIN (&plugin); } diff --git a/plugins/ffap/ffap.c b/plugins/ffap/ffap.c index 9850f9d8..e04ca40d 100644 --- a/plugins/ffap/ffap.c +++ b/plugins/ffap/ffap.c @@ -42,11 +42,12 @@ //#define trace(...) { fprintf(stderr, __VA_ARGS__); } #define trace(fmt,...) +#define likely(x) __builtin_expect((x),1) +#define unlikely(x) __builtin_expect((x),0) + static DB_decoder_t plugin; static DB_functions_t *deadbeef; -//static float timestart; -//static float timeend; static int startsample; static int endsample; @@ -256,7 +257,7 @@ typedef struct APEContext { const uint8_t *ptr; ///< current position in frame data const uint8_t *last_ptr; - uint8_t packet_data[PACKET_BUFFER_SIZE]; + uint8_t *packet_data; // must be PACKET_BUFFER_SIZE int packet_remaining; // number of bytes in packet_data int packet_sizeleft; // number of bytes left unread for current ape frame int samplestoskip; @@ -603,15 +604,20 @@ static int ape_read_packet(DB_FILE *fp, APEContext *ape_ctx) else nblocks = ape->blocksperframe; -// if (PACKET_MAX_SIZE < ape->frames[ape->currentframe].size + extra_size) { -// return -1; -// } -// packet_sizeleft = ape->frames[ape->currentframe].size + extra_size; - AV_WL32(ape->packet_data , nblocks); AV_WL32(ape->packet_data + 4, ape->frames[ape->currentframe].skip); // packet_sizeleft -= 8; +// update bitrate + int bitrate = -1; + if (nblocks != 0 && ape->frames[ape->currentframe].size != 0) { + float sec = (float)nblocks / ape->samplerate; + bitrate = ape->frames[ape->currentframe].size / sec * 8; + } + if (bitrate > 0) { + deadbeef->streamer_set_bitrate (bitrate/1000); + } + int sz = PACKET_BUFFER_SIZE-8; sz = min (sz, ape->frames[ape->currentframe].size); // fprintf (stderr, "readsize: %d, packetsize: %d\n", sz, ape->frames[ape->currentframe].size); @@ -627,6 +633,10 @@ static int ape_read_packet(DB_FILE *fp, APEContext *ape_ctx) static void ape_free_ctx (APEContext *ape_ctx) { int i; + if (ape_ctx->packet_data) { + free (ape_ctx->packet_data); + ape_ctx->packet_data = NULL; + } if (ape_ctx->frames) { free (ape_ctx->frames); ape_ctx->frames = NULL; @@ -649,21 +659,6 @@ ffap_free (void) ape_free_ctx (&ape_ctx); } -#if 0 -static int ape_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) -{ - AVStream *st = s->streams[stream_index]; - APEContext *ape = s->priv_data; - int index = av_index_search_timestamp(st, timestamp, flags); - - if (index < 0) - return -1; - - ape->currentframe = index; - return 0; -} -#endif - static DB_FILE *fp; static int @@ -711,17 +706,19 @@ ffap_init(DB_playItem_t *it) if (it->endsample > 0) { startsample = it->startsample; endsample = it->endsample; -// timestart = it->timestart; -// timeend = it->timeend; plugin.seek_sample (0); //trace ("start: %d/%f, end: %d/%f\n", startsample, timestart, endsample, timeend); } else { - //timestart = 0; - //timeend = it->duration; startsample = 0; endsample = ape_ctx.totalsamples-1; } + + ape_ctx.packet_data = malloc (PACKET_BUFFER_SIZE); + if (!ape_ctx.packet_data) { + fprintf (stderr, "ffap: failed to allocate memory for packet data\n"); + return -1; + } return 0; } @@ -919,7 +916,14 @@ static inline int ape_decode_value(APEContext * ctx, APERice *rice) overflow |= range_decode_bits(ctx, 16); } - base = range_decode_culfreq(ctx, pivot); +// base = range_decode_culfreq(ctx, pivot); + range_dec_normalize(ctx); + ctx->rc.help = ctx->rc.range / pivot; + if (unlikely (ctx->rc.help == 0)) { + ctx->error = 1; + return 0; + } + base = ctx->rc.low / ctx->rc.help; range_decode_update(ctx, 1, base); x = base + overflow * pivot; @@ -1507,8 +1511,6 @@ ape_decode_frame(APEContext *s, void *data, int *data_size) } if (s->packet_remaining < PACKET_BUFFER_SIZE) { -// assert (packet_sizeleft >= 0 && packet_remaining >= 0); -// if (packet_sizeleft == 0 && packet_remaining == 0) { if (s->samples == 0) { if (s->currentframe == s->totalframes) { return -1; @@ -1527,6 +1529,7 @@ ape_decode_frame(APEContext *s, void *data, int *data_size) s->ptr = s->last_ptr = s->packet_data; nblocks = s->samples = bytestream_get_be32(&s->ptr); + //fprintf (stderr, "s->samples=%d (1)\n", s->samples); n = bytestream_get_be32(&s->ptr); if(n < 0 || n > 3){ @@ -1536,6 +1539,7 @@ ape_decode_frame(APEContext *s, void *data, int *data_size) s->ptr += n; s->currentframeblocks = nblocks; + //buf += 4; if (s->samples <= 0) { *data_size = 0; @@ -1555,10 +1559,6 @@ ape_decode_frame(APEContext *s, void *data, int *data_size) sz = sz&~3; uint8_t *p = s->packet_data + s->packet_remaining; int r = deadbeef->fread (p, 1, sz, fp); - //if (r != s) { - // fprintf (stderr, "unexpected eof while reading ape frame\n"); - // return -1; - //} bswap_buf((uint32_t*)p, (const uint32_t*)p, r >> 2); s->packet_sizeleft -= r; s->packet_remaining += r; @@ -1664,7 +1664,7 @@ ffap_insert (DB_playItem_t *after, const char *fname) { it->filetype = "APE"; deadbeef->pl_set_item_duration (it, duration); - int v2err = deadbeef->junk_read_id3v2 (it, fp); + /*int v2err = */deadbeef->junk_read_id3v2 (it, fp); int v1err = deadbeef->junk_read_id3v1 (it, fp); if (v1err >= 0) { deadbeef->fseek (fp, -128, SEEK_END); @@ -1672,7 +1672,7 @@ ffap_insert (DB_playItem_t *after, const char *fname) { else { deadbeef->fseek (fp, 0, SEEK_END); } - int apeerr = deadbeef->junk_read_ape (it, fp); + /*int apeerr = */deadbeef->junk_read_ape (it, fp); deadbeef->fclose (fp); @@ -1779,7 +1779,7 @@ static DB_decoder_t plugin = { .plugin.version_minor = 1, .plugin.type = DB_PLUGIN_DECODER, .plugin.name = "Monkey's Audio (APE) decoder", - .plugin.descr = "Derived from ffmpeg code by Benjamin Zores and rockbox code by Dave Chapman", + .plugin.descr = "APE player based on code from libavc and rockbox", .plugin.author = "Alexey Yakovenko", .plugin.email = "waker@users.sourceforge.net", .plugin.website = "http://deadbeef.sf.net", diff --git a/plugins/ffmpeg/Makefile.am b/plugins/ffmpeg/Makefile.am new file mode 100644 index 00000000..0793990f --- /dev/null +++ b/plugins/ffmpeg/Makefile.am @@ -0,0 +1,9 @@ +if HAVE_FFMPEG +ffmpegdir = $(libdir)/$(PACKAGE) +pkglib_LTLIBRARIES = ffmpeg.la +ffmpeg_la_SOURCES = ffmpeg.c +ffmpeg_la_LDFLAGS = -module + +ffmpeg_la_LIBADD = $(LDADD) $(FFMPEG_DEPS_LIBS) +AM_CFLAGS = $(CFLAGS) -std=c99 ${FFMPEG_DEPS_CFLAGS} +endif diff --git a/plugins/ffmpeg/ffmpeg.c b/plugins/ffmpeg/ffmpeg.c new file mode 100644 index 00000000..fcbdff78 --- /dev/null +++ b/plugins/ffmpeg/ffmpeg.c @@ -0,0 +1,593 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#include "../../deadbeef.h" +#include <libavformat/avformat.h> +#include <libavcodec/avcodec.h> +#include <libavutil/avutil.h> +#include <libavutil/avstring.h> + +#define trace(...) { fprintf(stderr, __VA_ARGS__); } +//#define trace(fmt,...) + +#define min(x,y) ((x)<(y)?(x):(y)) +#define max(x,y) ((x)>(y)?(x):(y)) + +static DB_decoder_t plugin; +static DB_functions_t *deadbeef; + +static const char * exts[] = { "m4a", "mpc", "mp+", "mpp", "wma", "shn", "aa3", "oma", "ac3", "vqf", NULL }; + +enum { + FT_M4A = 0, + FT_MUSEPACK = 1, + FT_WMA = 2, + FT_SHORTEN = 3, + FT_ATRAC3 = 4, + FT_VQF = 5, + FT_UNKNOWN = 6 +}; + +static const char *filetypes[] = { "M4A", "MusePack", "WMA", "Shorten", "atrac3", "VQF", "FFMPEG", NULL }; + +#define FF_PROTOCOL_NAME "deadbeef" + +static AVCodec *codec; +static AVCodecContext *ctx; +static AVFormatContext *fctx; +static AVPacket pkt; +static int stream_id; + +static int left_in_packet; +static int have_packet; + +static char *buffer; // must be AVCODEC_MAX_AUDIO_FRAME_SIZE +static int left_in_buffer; + +static int startsample; +static int endsample; +static int currentsample; + +static int +ffmpeg_init (DB_playItem_t *it) { + // prepare to decode the track + // return -1 on failure + + int ret; + int l = strlen (it->fname); + char *uri = alloca (l + sizeof (FF_PROTOCOL_NAME) + 1); + int i; + + // construct uri + memcpy (uri, FF_PROTOCOL_NAME, sizeof (FF_PROTOCOL_NAME)-1); + memcpy (uri + sizeof (FF_PROTOCOL_NAME)-1, ":", 1); + memcpy (uri + sizeof (FF_PROTOCOL_NAME), it->fname, l); + uri[sizeof (FF_PROTOCOL_NAME) + l] = 0; + trace ("ffmpeg: uri: %s\n", uri); + + // open file + if ((ret = av_open_input_file(&fctx, uri, NULL, 0, NULL)) < 0) { + trace ("fctx is %p, ret %d/%s", fctx, ret, strerror(-ret)); + return -1; + } + + stream_id = -1; + for (i = 0; i < fctx->nb_streams; i++) + { + ctx = fctx->streams[i]->codec; + if (ctx->codec_type == CODEC_TYPE_AUDIO) + { + av_find_stream_info(fctx); + codec = avcodec_find_decoder(ctx->codec_id); + if (codec != NULL) { + stream_id = i; + break; + } + } + } + + if (codec == NULL) + { + trace ("ffmpeg can't decode %s\n", it->fname); + av_close_input_file(fctx); + return -1; + } + trace ("ffmpeg can decode %s\n", it->fname); + trace ("ffmpeg: codec=%s, stream=%d\n", codec->name, i); + + if (avcodec_open (ctx, codec) < 0) { + trace ("ffmpeg: avcodec_open failed\n"); + av_close_input_file(fctx); + return -1; + } + + int bps = av_get_bits_per_sample_format (ctx->sample_fmt); + int samplerate = ctx->sample_rate; + float duration = fctx->duration / (float)AV_TIME_BASE; + trace ("ffmpeg: bits per sample is %d\n", bps); + trace ("ffmpeg: samplerate is %d\n", samplerate); + trace ("ffmpeg: duration is %lld/%fsec\n", fctx->duration, duration); + + int totalsamples = fctx->duration * samplerate / AV_TIME_BASE; + left_in_packet = 0; + left_in_buffer = 0; + + memset (&pkt, 0, sizeof (pkt)); + have_packet = 0; + + buffer = malloc (AVCODEC_MAX_AUDIO_FRAME_SIZE); + if (!buffer) { + fprintf (stderr, "ffmpeg: failed to allocate buffer memory\n"); + return -1; + } + + + // fill in mandatory plugin fields + plugin.info.readpos = 0; + plugin.info.bps = bps; + plugin.info.channels = ctx->channels; + plugin.info.samplerate = samplerate; + + // subtrack info + currentsample = 0; + if (it->endsample > 0) { + startsample = it->startsample; + endsample = it->endsample; + plugin.seek_sample (0); + } + else { + startsample = 0; + endsample = totalsamples - 1; + } + return 0; +} + +static void +ffmpeg_free (void) { + if (buffer) { + free (buffer); + buffer = NULL; + } + // free everything allocated in _init and _read_int16 + if (have_packet) { + av_free_packet (&pkt); + have_packet = 0; + } + left_in_buffer = 0; + left_in_packet = 0; + stream_id = -1; + + if (fctx) { + av_close_input_file(fctx); + fctx = NULL; + } + if (ctx) { + avcodec_close (ctx); + ctx = NULL; + } + + codec = NULL; +} + +static int +ffmpeg_read_int16 (char *bytes, int size) { + // try decode `size' bytes + // return number of decoded bytes + // return 0 on EOF + + if (currentsample + size / (2 * plugin.info.channels) > endsample) { + size = (endsample - currentsample + 1) * 2 * plugin.info.channels; + if (size <= 0) { + return 0; + } + } + + int initsize = size; + + int encsize = 0; + int decsize = 0; + + while (size > 0) { + + if (left_in_buffer > 0) { + int sz = min (size, left_in_buffer); + memcpy (bytes, buffer, sz); + if (sz != left_in_buffer) { + memmove (buffer, buffer+sz, left_in_buffer-sz); + } + left_in_buffer -= sz; + size -= sz; + bytes += sz; + } + + while (left_in_packet > 0 && size > 0) { + int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE; + int len; + //trace ("in: out_size=%d(%d), size=%d\n", out_size, AVCODEC_MAX_AUDIO_FRAME_SIZE, size); +#if (LIBAVCODEC_VERSION_MAJOR <= 52) && (LIBAVCODEC_VERSION_MINOR <= 25) + len = avcodec_decode_audio2(ctx, (int16_t *)buffer, &out_size, pkt.data, pkt.size); +#else + len = avcodec_decode_audio3(ctx, (int16_t *)buffer, &out_size, &pkt); +#endif + //trace ("out: out_size=%d, len=%d\n", out_size, len); + if (len <= 0) { + break; + } + encsize += len; + decsize += out_size; + left_in_packet -= len; + left_in_buffer = out_size; + } + if (size == 0) { + break; + } + + // read next packet + if (have_packet) { + av_free_packet (&pkt); + have_packet = 0; + } + int errcount = 0; + for (;;) { + int ret; + if ((ret = av_read_frame(fctx, &pkt)) < 0) { + trace ("ffmpeg: error %d\n", ret); + if (ret == AVERROR_EOF || ret == -1) { + ret = -1; + break; + } + else { + if (++errcount > 4) { + trace ("ffmpeg: too many errors in a row (last is %d); interrupting stream\n", ret); + ret = -1; + break; + } + else { + continue; + } + } + } + else { + errcount = 0; + } + if (ret == -1) { + break; + } + //trace ("idx:%d, stream:%d\n", pkt.stream_index, stream_id); + if (pkt.stream_index != stream_id) { + av_free_packet (&pkt); + continue; + } + //trace ("got packet: size=%d\n", pkt.size); + have_packet = 1; + left_in_packet = pkt.size; + + if (pkt.duration > 0) { + AVRational *time_base = &fctx->streams[stream_id]->time_base; + float sec = (float)pkt.duration * time_base->num / time_base->den; + int bitrate = pkt.size/sec; + if (bitrate > 0) { + // FIXME: seems like duration translation is wrong + deadbeef->streamer_set_bitrate (bitrate / 100); + } + } + + break; + } + if (!have_packet) { + break; + } + } + + currentsample += (initsize-size) / (2 * plugin.info.channels); + plugin.info.readpos = (float)currentsample / plugin.info.samplerate; + + return initsize-size; +} + +static int +ffmpeg_seek_sample (int sample) { + // seek to specified sample (frame) + // return 0 on success + // return -1 on failure + if (have_packet) { + av_free_packet (&pkt); + have_packet = 0; + } + sample += startsample; + int64_t tm = (int64_t)sample/ plugin.info.samplerate * AV_TIME_BASE; + trace ("ffmpeg: seek to sample: %d, t: %d\n", sample, (int)tm); + left_in_packet = 0; + left_in_buffer = 0; + if (av_seek_frame(fctx, -1, tm, AVSEEK_FLAG_ANY) < 0) { + trace ("ffmpeg: seek error\n"); + return -1; + } + + // update readpos + currentsample = sample; + plugin.info.readpos = (float)(sample-startsample) / plugin.info.samplerate; + return 0; +} + +static int +ffmpeg_seek (float time) { + // seek to specified time in seconds + // return 0 on success + // return -1 on failure + return ffmpeg_seek_sample (time * plugin.info.samplerate); +} + +static DB_playItem_t * +ffmpeg_insert (DB_playItem_t *after, const char *fname) { + // read information from the track + // load/process cuesheet if exists + // insert track into playlist + // return track pointer on success + // return NULL on failure + + AVCodec *codec = NULL; + AVCodecContext *ctx = NULL; + AVFormatContext *fctx = NULL; + int ret; + int l = strlen (fname); + char *uri = alloca (l + sizeof (FF_PROTOCOL_NAME) + 1); + int i; + + // construct uri + memcpy (uri, FF_PROTOCOL_NAME, sizeof (FF_PROTOCOL_NAME)-1); + memcpy (uri + sizeof (FF_PROTOCOL_NAME)-1, ":", 1); + memcpy (uri + sizeof (FF_PROTOCOL_NAME), fname, l); + uri[sizeof (FF_PROTOCOL_NAME) + l] = 0; + trace ("ffmpeg: uri: %s\n", uri); + + // open file + if ((ret = av_open_input_file(&fctx, uri, NULL, 0, NULL)) < 0) { + trace ("fctx is %p, ret %d/%s", fctx, ret, strerror(-ret)); + return NULL; + } + + av_find_stream_info(fctx); + for (i = 0; i < fctx->nb_streams; i++) + { + ctx = fctx->streams[i]->codec; + if (ctx->codec_type == CODEC_TYPE_AUDIO) + { + codec = avcodec_find_decoder(ctx->codec_id); + if (codec != NULL) + break; + } + } +// AVStream *stream = fctx->streams[i]; + + if (codec == NULL) + { + trace ("ffmpeg can't decode %s\n", fname); + av_close_input_file(fctx); + return NULL; + } + trace ("ffmpeg can decode %s\n", fname); + trace ("ffmpeg: codec=%s, stream=%d\n", codec->name, i); + + if (avcodec_open (ctx, codec) < 0) { + trace ("ffmpeg: avcodec_open failed\n"); + av_close_input_file(fctx); + return NULL; + } + + int bps = av_get_bits_per_sample_format (ctx->sample_fmt); + int samplerate = ctx->sample_rate; + float duration = fctx->duration / (float)AV_TIME_BASE; +// float duration = stream->duration * stream->time_base.num / (float)stream->time_base.den; + trace ("ffmpeg: bits per sample is %d\n", bps); + trace ("ffmpeg: samplerate is %d\n", samplerate); + trace ("ffmpeg: duration is %f\n", duration); + + int totalsamples = fctx->duration * samplerate / AV_TIME_BASE; + + // find filetype + const char *filetype; + const char *ext = fname + strlen(fname) - 1; + while (ext > fname && *ext != '.') { + ext--; + } + if (*ext == '.') { + ext++; + } + + if (!strcasecmp (ext, "m4a")) { + filetype = filetypes[FT_M4A]; + } + else if (!strcasecmp (ext, "mpc") || !strcasecmp (ext, "mp+") || !strcasecmp (ext, "mpp")) { + filetype = filetypes[FT_MUSEPACK]; + } + else if (!strcasecmp (ext, "wma")) { + filetype = filetypes[FT_WMA]; + } + else if (!strcasecmp (ext, "shn")) { + filetype = filetypes[FT_SHORTEN]; + } + else if (!strcasecmp (ext, "aa3") || !strcasecmp (ext, "oma") || !strcasecmp (ext, "ac3")) { + filetype = filetypes[FT_ATRAC3]; + } + else if (!strcasecmp (ext, "vqf")) { + filetype = filetypes[FT_VQF]; + } + else { + filetype = filetypes[FT_UNKNOWN]; + } + + // external cuesheet + DB_playItem_t *cue = deadbeef->pl_insert_cue (after, fname, &plugin, filetype, totalsamples, samplerate); + if (cue) { + // cuesheet loaded + av_close_input_file(fctx); + return cue; + } + DB_playItem_t *it = deadbeef->pl_item_alloc (); + it->decoder = &plugin; + it->fname = strdup (fname); + it->filetype = filetype; + + deadbeef->pl_set_item_duration (it, duration); + + // add metainfo +#if LIBAVFORMAT_VERSION_INT < (53<<16) + if (!strlen (fctx->title)) { + // title is empty, this call will set track title to filename without extension + deadbeef->pl_add_meta (it, "title", NULL); + } + else { + deadbeef->pl_add_meta (it, "title", fctx->title); + } + deadbeef->pl_add_meta (it, "artist", fctx->author); + deadbeef->pl_add_meta (it, "album", fctx->album); + deadbeef->pl_add_meta (it, "year", fctx->album); + deadbeef->pl_add_meta (it, "copyright", fctx->copyright); + deadbeef->pl_add_meta (it, "comment", fctx->comment); + deadbeef->pl_add_meta (it, "genre", fctx->genre); + + char tmp[10]; + snprintf (tmp, sizeof (tmp), "%d", fctx->year); + deadbeef->pl_add_meta (it, "year", tmp); + snprintf (tmp, sizeof (tmp), "%d", fctx->track); + deadbeef->pl_add_meta (it, "track", tmp); +#else +// read using other means? +// av_metadata_get? +#endif + // free decoder + av_close_input_file(fctx); + + // now the track is ready, insert into playlist + after = deadbeef->pl_insert_item (after, it); + return after; +} + +// vfs wrapper for ffmpeg +static int +ffmpeg_vfs_open(URLContext *h, const char *filename, int flags) +{ + DB_FILE *f; + av_strstart(filename, FF_PROTOCOL_NAME ":", &filename); + if (flags & URL_WRONLY) { + return -ENOENT; + } else { + f = deadbeef->fopen (filename); + } + + if (f == NULL) + return -ENOENT; + + h->priv_data = f; + return 0; +} + +static int +ffmpeg_vfs_read(URLContext *h, unsigned char *buf, int size) +{ + int res = deadbeef->fread (buf, 1, size, h->priv_data); + return res; +} + +static int +ffmpeg_vfs_write(URLContext *h, unsigned char *buf, int size) +{ + return -1; +} + +static int64_t +ffmpeg_vfs_seek(URLContext *h, int64_t pos, int whence) +{ + DB_FILE *f = h->priv_data; + + if (whence == AVSEEK_SIZE) { + return f->vfs->streaming ? -1 : deadbeef->fgetlength (h->priv_data); + } + int ret = deadbeef->fseek (h->priv_data, pos, whence); + return ret; +} + +static int +ffmpeg_vfs_close(URLContext *h) +{ + trace ("ffmpeg_vfs_close\n"); + deadbeef->fclose (h->priv_data); + return 0; +} + +static URLProtocol vfswrapper = { + .name = FF_PROTOCOL_NAME, + .url_open = ffmpeg_vfs_open, + .url_read = ffmpeg_vfs_read, + .url_write = ffmpeg_vfs_write, + .url_seek = ffmpeg_vfs_seek, + .url_close = ffmpeg_vfs_close, +}; + +static int +ffmpeg_start (void) { + // do one-time plugin initialization here + // e.g. starting threads for background processing, subscribing to events, etc + // return 0 on success + // return -1 on failure + avcodec_init (); + av_register_all (); + av_register_protocol (&vfswrapper); + return 0; +} +static int +ffmpeg_stop (void) { + // undo everything done in _start here + // return 0 on success + // return -1 on failure + trace ("ffmpeg stop\n"); + return 0; +} + +// define plugin interface +static DB_decoder_t plugin = { + DB_PLUGIN_SET_API_VERSION + .plugin.version_major = 0, + .plugin.version_minor = 1, + .plugin.type = DB_PLUGIN_DECODER, + .plugin.name = "FFMPEG audio player", + .plugin.descr = "decodes audio formats using FFMPEG libavcodec", + .plugin.author = "Alexey Yakovenko", + .plugin.email = "waker@users.sourceforge.net", + .plugin.website = "http://deadbeef.sf.net", + .plugin.start = ffmpeg_start, + .plugin.stop = ffmpeg_stop, + .init = ffmpeg_init, + .free = ffmpeg_free, + .read_int16 = ffmpeg_read_int16, +// .read_float32 = ffmpeg_read_float32, + .seek = ffmpeg_seek, + .seek_sample = ffmpeg_seek_sample, + .insert = ffmpeg_insert, + .exts = exts, + .id = "ffmpeg", + .filetypes = filetypes +}; + +DB_plugin_t * +ffmpeg_load (DB_functions_t *api) { + deadbeef = api; + return DB_PLUGIN (&plugin); +} + diff --git a/plugins/flac/flac.c b/plugins/flac/flac.c index 11f8cfe3..4b9f1950 100644 --- a/plugins/flac/flac.c +++ b/plugins/flac/flac.c @@ -48,6 +48,7 @@ typedef struct { int channels; int totalsamples; int bps; + int bytesread; } cue_cb_data_t; static cue_cb_data_t flac_callbacks; @@ -56,6 +57,7 @@ static cue_cb_data_t flac_callbacks; FLAC__StreamDecoderReadStatus flac_read_cb (const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) { cue_cb_data_t *cb = (cue_cb_data_t *)client_data; size_t r = deadbeef->fread (buffer, 1, *bytes, cb->file); + cb->bytesread += r; *bytes = r; if (r == 0) { return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; @@ -91,9 +93,19 @@ FLAC__bool flac_eof_cb (const FLAC__StreamDecoder *decoder, void *client_data) { static FLAC__StreamDecoderWriteStatus cflac_write_callback (const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const inputbuffer[], void *client_data) { + cue_cb_data_t *cb = (cue_cb_data_t *)client_data; if (frame->header.blocksize == 0) { return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } + int bitrate = -1; + float sec = ((float)frame->header.blocksize / frame->header.sample_rate); + if (cb->bytesread != 0 && sec != 0) { + bitrate = cb->bytesread / sec * 8; + } + cb->bytesread = 0; + if (bitrate > 0) { + deadbeef->streamer_set_bitrate (bitrate/1000); + } int readbytes = frame->header.blocksize * plugin.info.channels * plugin.info.bps / 8; int bufsize = BUFFERSIZE-remaining; int bufsamples = bufsize / (plugin.info.channels * plugin.info.bps / 8); @@ -138,17 +150,19 @@ static int cflac_init_stop_decoding; static void cflac_init_error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) { - fprintf(stderr, "cflac: got error callback: %s\n", FLAC__StreamDecoderErrorStatusString[status]); if (status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC) { + fprintf(stderr, "cflac: got error callback: %s\n", FLAC__StreamDecoderErrorStatusString[status]); cflac_init_stop_decoding = 1; } } static int cflac_init (DB_playItem_t *it) { + trace ("cflac_init %s\n", it->fname); memset (&flac_callbacks, 0, sizeof (flac_callbacks)); flac_callbacks.file = deadbeef->fopen (it->fname); if (!flac_callbacks.file) { + trace ("cflac_init failed to open file\n"); return -1; } int skip = deadbeef->junk_get_leading_size (flac_callbacks.file); @@ -158,10 +172,12 @@ cflac_init (DB_playItem_t *it) { char sign[4]; if (deadbeef->fread (sign, 1, 4, flac_callbacks.file) != 4) { plugin.free (); + trace ("cflac_init failed to read signature\n"); return -1; } if (strncmp (sign, "fLaC", 4)) { plugin.free (); + trace ("cflac_init bad signature\n"); return -1; } deadbeef->fseek (flac_callbacks.file, -4, SEEK_CUR); @@ -176,11 +192,13 @@ cflac_init (DB_playItem_t *it) { status = FLAC__stream_decoder_init_stream (decoder, flac_read_cb, flac_seek_cb, flac_tell_cb, flac_lenght_cb, flac_eof_cb, cflac_write_callback, cflac_metadata_callback, cflac_error_callback, &flac_callbacks); if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { plugin.free (); + trace ("cflac_init bad decoder status\n"); return -1; } //plugin.info.samplerate = -1; if (!FLAC__stream_decoder_process_until_end_of_metadata (decoder)) { plugin.free (); + trace ("cflac_init metadata failed\n"); return -1; } plugin.info.samplerate = flac_callbacks.samplerate; @@ -189,6 +207,7 @@ cflac_init (DB_playItem_t *it) { plugin.info.readpos = 0; if (plugin.info.samplerate == -1) { // not a FLAC stream plugin.free (); + trace ("cflac_init not a flac stream\n"); return -1; } buffer = malloc (BUFFERSIZE); @@ -197,6 +216,7 @@ cflac_init (DB_playItem_t *it) { endsample = it->endsample; if (plugin.seek_sample (0) < 0) { plugin.free (); + trace ("cflac_init failed to seek to sample 0\n"); return -1; } trace ("flac(cue): startsample=%d, endsample=%d, totalsamples=%d, currentsample=%d\n", startsample, endsample, flac_callbacks.totalsamples, currentsample); @@ -234,6 +254,7 @@ cflac_read_int16 (char *bytes, int size) { } } int initsize = size; + int nbytes_in = 0; do { if (remaining) { int s = size * 2; diff --git a/plugins/gtkui/Makefile.am b/plugins/gtkui/Makefile.am new file mode 100644 index 00000000..504984e0 --- /dev/null +++ b/plugins/gtkui/Makefile.am @@ -0,0 +1,17 @@ +if HAVE_GTK +gtkuidir = $(libdir)/$(PACKAGE) +pkglib_LTLIBRARIES = gtkui.la +gtkui_la_SOURCES = gtkui.c gtkui.h\ + callbacks.c interface.c support.c callbacks.h interface.h support.h\ + gtkplaylist.c gtkplaylist.h\ + drawing.h gdkdrawing.c\ + progress.c progress.h\ + search.c search.h\ + fileman.c\ + parser.c parser.h + +gtkui_la_LDFLAGS = -module + +gtkui_la_LIBADD = $(LDADD) $(GTKUI_DEPS_LIBS) +AM_CFLAGS = -std=c99 $(GTKUI_DEPS_CFLAGS) +endif diff --git a/callbacks.c b/plugins/gtkui/callbacks.c index 1e7104fe..8d82b8c4 100644 --- a/callbacks.c +++ b/plugins/gtkui/callbacks.c @@ -32,30 +32,29 @@ #include "interface.h" #include "support.h" -#include "common.h" - -#include "playlist.h" #include "gtkplaylist.h" -#include "messagepump.h" -#include "codec.h" -#include "playback.h" #include "search.h" -#include "streamer.h" #include "progress.h" -#include "volume.h" #include "session.h" -#include "conf.h" +#include "gtkui.h" +#include "parser.h" -#include "plugins.h" +#define SELECTED(it) (deadbeef->pl_is_selected(it)) +#define SELECT(it, sel) (deadbeef->pl_set_selected(it,sel)) +#define VSELECT(it, sel) {deadbeef->pl_set_selected(it,sel);gtk_pl_redraw_item_everywhere (it);} +#define PL_NEXT(it, iter) (deadbeef->pl_get_next(it, iter)) +GtkWidget *formatwin = NULL; +gtkplaylist_t *last_playlist; extern GtkWidget *mainwin; extern gtkplaylist_t main_playlist; extern gtkplaylist_t search_playlist; +extern DB_functions_t *deadbeef; // defined in gtkui.c gboolean playlist_tooltip_handler (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer unused) { - playItem_t *item = gtkpl_get_for_idx (&main_playlist, main_playlist.scrollpos + y / rowheight); + DB_playItem_t *item = deadbeef->pl_get_for_idx (main_playlist.scrollpos + y / rowheight); if (item && item->fname) { gtk_tooltip_set_text (tooltip, item->fname); return TRUE; @@ -63,6 +62,16 @@ playlist_tooltip_handler (GtkWidget *widget, gint x, gint y, gboolean keyboard_m return FALSE; } +static int +main_get_count (void) { + return deadbeef->pl_getcount (); +} + +static int +search_get_count (void) { + return search_count; +} + void main_playlist_init (GtkWidget *widget) { // init playlist control structure, and put it into widget user-data @@ -72,20 +81,18 @@ main_playlist_init (GtkWidget *widget) { main_playlist.header = lookup_widget (mainwin, "header"); main_playlist.scrollbar = lookup_widget (mainwin, "playscroll"); main_playlist.hscrollbar = lookup_widget (mainwin, "playhscroll"); - main_playlist.pcurr = &playlist_current_ptr; - main_playlist.pcount = &pl_count; +// main_playlist.pcurr = &playlist_current_ptr; +// main_playlist.pcount = &pl_count; + main_playlist.get_count = main_get_count; main_playlist.iterator = PL_MAIN; - main_playlist.multisel = 1; +// main_playlist.multisel = 1; main_playlist.scrollpos = 0; main_playlist.hscrollpos = 0; - main_playlist.row = -1; +// main_playlist.row = -1; main_playlist.clicktime = -1; main_playlist.nvisiblerows = 0; -// FIXME: on 1st run, copy colwidths to new columns -// main_playlist.colwidths = session_get_main_colwidths_ptr (); - - DB_conf_item_t *col = conf_find ("playlist.column.", NULL); + DB_conf_item_t *col = deadbeef->conf_find ("playlist.column.", NULL); if (!col) { // create default set of columns gtkpl_column_append (&main_playlist, gtkpl_column_alloc ("Playing", 50, DB_COLUMN_PLAYING, NULL, 0)); @@ -97,7 +104,7 @@ main_playlist_init (GtkWidget *widget) { else { while (col) { gtkpl_append_column_from_textdef (&main_playlist, col->value); - col = conf_find ("playlist.column.", col); + col = deadbeef->conf_find ("playlist.column.", col); } } @@ -108,7 +115,7 @@ main_playlist_init (GtkWidget *widget) { // FIXME: filepath should be in properties dialog, while tooltip should be // used to show text that doesn't fit in column width - if (conf_get_int ("playlist.showpathtooltip", 0)) { + if (deadbeef->conf_get_int ("playlist.showpathtooltip", 0)) { GValue value = {0, }; g_value_init (&value, G_TYPE_BOOLEAN); g_value_set_boolean (&value, TRUE); @@ -129,20 +136,19 @@ search_playlist_init (GtkWidget *widget) { search_playlist.hscrollbar = lookup_widget (searchwin, "searchhscroll"); assert (search_playlist.header); assert (search_playlist.scrollbar); - // main_playlist.pcurr = &search_current; - search_playlist.pcount = &search_count; - search_playlist.multisel = 0; +// main_playlist.pcurr = &search_current; +// search_playlist.pcount = &search_count; + search_playlist.get_count = search_get_count; +// search_playlist.multisel = 0; search_playlist.iterator = PL_SEARCH; search_playlist.scrollpos = 0; search_playlist.hscrollpos = 0; - search_playlist.row = -1; +// search_playlist.row = -1; search_playlist.clicktime = -1; search_playlist.nvisiblerows = 0; -// FIXME: port to new columns -// search_playlist.colwidths = session_get_search_colwidths_ptr (); // create default set of columns - DB_conf_item_t *col = conf_find ("search.column.", NULL); + DB_conf_item_t *col = deadbeef->conf_find ("search.column.", NULL); if (!col) { gtkpl_column_append (&search_playlist, gtkpl_column_alloc ("Artist / Album", 150, DB_COLUMN_ARTIST_ALBUM, NULL, 0)); gtkpl_column_append (&search_playlist, gtkpl_column_alloc ("Track â„–", 50, DB_COLUMN_TRACK, NULL, 1)); @@ -152,7 +158,7 @@ search_playlist_init (GtkWidget *widget) { else { while (col) { gtkpl_append_column_from_textdef (&search_playlist, col->value); - col = conf_find ("search.column.", col); + col = deadbeef->conf_find ("search.column.", col); } } gtk_object_set_data (GTK_OBJECT (search_playlist.playlist), "ps", &search_playlist); @@ -184,6 +190,95 @@ on_playlist_button_press_event (GtkWidget *widget, if (event->button == 1) { gtkpl_mouse1_pressed (ps, event->state, event->x, event->y, event->time); } + else if (event->button == 3) { + // get item under cursor + int y = event->y / rowheight + ps->scrollpos; + if (y < 0 || y >= ps->get_count ()) { + y = -1; + } + DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (y, ps->iterator); + if (!it) { + // clicked empty space -- deselect everything and show insensitive menu + it = deadbeef->pl_get_first (ps->iterator); + while (it) { + SELECT (it, 0); + it = PL_NEXT (it, ps->iterator); + } + playlist_refresh (); + // no menu + } + else { + if (!SELECTED (it)) { + // item is unselected -- reset selection and select this + DB_playItem_t *it2 = deadbeef->pl_get_first (ps->iterator); + while (it2) { + SELECT (it2, 0); + it2 = PL_NEXT (it2, ps->iterator); + } + SELECT (it, 1); + playlist_refresh (); + } + { + int inqueue = deadbeef->pl_playqueue_test (it); + GtkWidget *playlist_menu; + GtkWidget *add_to_playback_queue1; + GtkWidget *remove_from_playback_queue1; + GtkWidget *separator9; + GtkWidget *remove2; + GtkWidget *separator8; + GtkWidget *properties1; + + playlist_menu = gtk_menu_new (); + add_to_playback_queue1 = gtk_menu_item_new_with_mnemonic ("Add to playback queue"); + gtk_widget_show (add_to_playback_queue1); + gtk_container_add (GTK_CONTAINER (playlist_menu), add_to_playback_queue1); + gtk_object_set_data (GTK_OBJECT (add_to_playback_queue1), "ps", ps); + + remove_from_playback_queue1 = gtk_menu_item_new_with_mnemonic ("Remove from playback queue"); + if (inqueue == -1) { + gtk_widget_set_sensitive (remove_from_playback_queue1, FALSE); + } + gtk_widget_show (remove_from_playback_queue1); + gtk_container_add (GTK_CONTAINER (playlist_menu), remove_from_playback_queue1); + gtk_object_set_data (GTK_OBJECT (remove_from_playback_queue1), "ps", ps); + + separator9 = gtk_separator_menu_item_new (); + gtk_widget_show (separator9); + gtk_container_add (GTK_CONTAINER (playlist_menu), separator9); + gtk_widget_set_sensitive (separator9, FALSE); + + remove2 = gtk_menu_item_new_with_mnemonic ("Remove"); + gtk_widget_show (remove2); + gtk_container_add (GTK_CONTAINER (playlist_menu), remove2); + gtk_object_set_data (GTK_OBJECT (remove2), "ps", ps); + + separator8 = gtk_separator_menu_item_new (); + gtk_widget_show (separator8); + gtk_container_add (GTK_CONTAINER (playlist_menu), separator8); + gtk_widget_set_sensitive (separator8, FALSE); + + properties1 = gtk_menu_item_new_with_mnemonic ("Properties"); + gtk_widget_set_sensitive (properties1, FALSE); + gtk_widget_show (properties1); + gtk_container_add (GTK_CONTAINER (playlist_menu), properties1); + gtk_object_set_data (GTK_OBJECT (properties1), "ps", ps); + + g_signal_connect ((gpointer) add_to_playback_queue1, "activate", + G_CALLBACK (on_add_to_playback_queue1_activate), + NULL); + g_signal_connect ((gpointer) remove_from_playback_queue1, "activate", + G_CALLBACK (on_remove_from_playback_queue1_activate), + NULL); + g_signal_connect ((gpointer) remove2, "activate", + G_CALLBACK (on_remove2_activate), + NULL); + g_signal_connect ((gpointer) properties1, "activate", + G_CALLBACK (on_properties1_activate), + NULL); + gtk_menu_popup (GTK_MENU (playlist_menu), NULL, NULL, NULL, widget, 0, gtk_get_current_event_time()); + } + } + } return FALSE; } @@ -233,7 +328,7 @@ file_filter_func (const GtkFileFilterInfo *filter_info, gpointer data) { return FALSE; } p++; - DB_decoder_t **codecs = plug_get_decoder_list (); + DB_decoder_t **codecs = deadbeef->plug_get_decoder_list (); for (int i = 0; codecs[i]; i++) { if (codecs[i]->exts && codecs[i]->insert) { const char **exts = codecs[i]->exts; @@ -266,29 +361,6 @@ set_file_filter (GtkWidget *dlg, const char *name) { gtk_file_filter_set_name (flt, name); gtk_file_filter_add_custom (flt, GTK_FILE_FILTER_FILENAME, file_filter_func, NULL, NULL); -#if 0 - DB_decoder_t **codecs = plug_get_decoder_list (); - for (int i = 0; codecs[i]; i++) { - if (codecs[i]->exts && codecs[i]->insert) { - const char **exts = codecs[i]->exts; - if (exts) { - for (int e = 0; exts[e]; e++) { - char filter[20]; - snprintf (filter, 20, "*.%s", exts[e]); - gtk_file_filter_add_pattern (flt, filter); - char *p; - for (p = filter; *p; p++) { - *p = toupper (*p); - } - gtk_file_filter_add_pattern (flt, filter); - } - } - } - } - gtk_file_filter_add_pattern (flt, "*.pls"); - gtk_file_filter_add_pattern (flt, "*.m3u"); -#endif - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), flt); gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dlg), flt); flt = gtk_file_filter_new (); @@ -307,21 +379,21 @@ on_open_activate (GtkMenuItem *menuitem, gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE); // restore folder - gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), conf_get_str ("filechooser.lastdir", "")); + gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), deadbeef->conf_get_str ("filechooser.lastdir", "")); int response = gtk_dialog_run (GTK_DIALOG (dlg)); // store folder gchar *folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dlg)); if (folder) { - conf_set_str ("filechooser.lastdir", folder); + deadbeef->conf_set_str ("filechooser.lastdir", folder); g_free (folder); } if (response == GTK_RESPONSE_OK) { - pl_free (); + deadbeef->pl_free (); GSList *lst = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dlg)); gtk_widget_destroy (dlg); if (lst) { - messagepump_push (M_OPENFILES, (uintptr_t)lst, 0, 0); + gtkui_open_files (lst); } } else { @@ -341,12 +413,12 @@ on_add_files_activate (GtkMenuItem *menuitem, gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE); // restore folder - gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), conf_get_str ("filechooser.lastdir", "")); + gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), deadbeef->conf_get_str ("filechooser.lastdir", "")); int response = gtk_dialog_run (GTK_DIALOG (dlg)); // store folder gchar *folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dlg)); if (folder) { - conf_set_str ("filechooser.lastdir", folder); + deadbeef->conf_set_str ("filechooser.lastdir", folder); g_free (folder); } if (response == GTK_RESPONSE_OK) @@ -354,7 +426,7 @@ on_add_files_activate (GtkMenuItem *menuitem, GSList *lst = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dlg)); gtk_widget_destroy (dlg); if (lst) { - messagepump_push (M_ADDFILES, (uintptr_t)lst, 0, 0); + gtkui_add_files (lst); } } else { @@ -372,12 +444,12 @@ on_add_folders_activate (GtkMenuItem *menuitem, gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), TRUE); // restore folder - gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), conf_get_str ("filechooser.lastdir", "")); + gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), deadbeef->conf_get_str ("filechooser.lastdir", "")); int response = gtk_dialog_run (GTK_DIALOG (dlg)); // store folder gchar *folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dlg)); if (folder) { - conf_set_str ("filechooser.lastdir", folder); + deadbeef->conf_set_str ("filechooser.lastdir", folder); g_free (folder); } if (response == GTK_RESPONSE_OK) @@ -386,7 +458,7 @@ on_add_folders_activate (GtkMenuItem *menuitem, GSList *lst = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dlg)); gtk_widget_destroy (dlg); if (lst) { - messagepump_push (M_ADDDIRS, (uintptr_t)lst, 0, 0); + gtkui_add_dirs (lst); } } else { @@ -408,7 +480,7 @@ on_quit_activate (GtkMenuItem *menuitem, gpointer user_data) { progress_abort (); - messagepump_push (M_TERMINATE, 0, 0, 0); + deadbeef->sendmessage (M_TERMINATE, 0, 0, 0); } @@ -416,7 +488,7 @@ void on_clear1_activate (GtkMenuItem *menuitem, gpointer user_data) { - pl_free (); + deadbeef->pl_free (); gtkplaylist_t *ps = &main_playlist; GtkWidget *widget = ps->playlist; gtkpl_setup_scrollbar (ps); @@ -430,7 +502,7 @@ void on_select_all1_activate (GtkMenuItem *menuitem, gpointer user_data) { - pl_select_all (); + deadbeef->pl_select_all (); gtkplaylist_t *ps = &main_playlist; GtkWidget *widget = ps->playlist; gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); @@ -444,11 +516,12 @@ on_remove1_activate (GtkMenuItem *menuitem, { gtkplaylist_t *ps = &main_playlist; GtkWidget *widget = ps->playlist; - ps->row = pl_delete_selected (); - if (ps->row != -1) { - playItem_t *it = pl_get_for_idx (ps->row); + int row = deadbeef->pl_delete_selected (); + deadbeef->pl_set_cursor (PL_MAIN, row); + if (row != -1) { + DB_playItem_t *it = deadbeef->pl_get_for_idx (row); if (it) { - it->selected = 1; + deadbeef->pl_set_selected (it, 1); } } gtkpl_setup_scrollbar (ps); @@ -464,7 +537,7 @@ on_crop1_activate (GtkMenuItem *menuitem, { gtkplaylist_t *ps = &main_playlist; GtkWidget *widget = ps->playlist; - pl_crop_selected (); + deadbeef->pl_crop_selected (); gtkpl_setup_scrollbar (ps); gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); gtkpl_expose (ps, 0, 0, widget->allocation.width, widget->allocation.height); @@ -489,7 +562,7 @@ void on_stopbtn_clicked (GtkButton *button, gpointer user_data) { - messagepump_push (M_STOPSONG, 0, 0, 0); + deadbeef->sendmessage (M_STOPSONG, 0, 0, 0); } @@ -497,7 +570,7 @@ void on_playbtn_clicked (GtkButton *button, gpointer user_data) { - messagepump_push (M_PLAYSONG, 0, 0, 0); + deadbeef->sendmessage (M_PLAYSONG, 0, 0, 0); } @@ -505,7 +578,7 @@ void on_pausebtn_clicked (GtkButton *button, gpointer user_data) { - messagepump_push (M_PAUSESONG, 0, 0, 0); + deadbeef->sendmessage (M_PAUSESONG, 0, 0, 0); } @@ -513,7 +586,7 @@ void on_prevbtn_clicked (GtkButton *button, gpointer user_data) { - messagepump_push (M_PREVSONG, 0, 0, 0); + deadbeef->sendmessage (M_PREVSONG, 0, 0, 0); } @@ -521,7 +594,7 @@ void on_nextbtn_clicked (GtkButton *button, gpointer user_data) { - messagepump_push (M_NEXTSONG, 0, 0, 0); + deadbeef->sendmessage (M_NEXTSONG, 0, 0, 0); } @@ -529,7 +602,7 @@ void on_playrand_clicked (GtkButton *button, gpointer user_data) { - messagepump_push (M_PLAYRANDOM, 0, 0, 0); + deadbeef->sendmessage (M_PLAYRANDOM, 0, 0, 0); } @@ -538,7 +611,14 @@ on_mainwin_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { - gtkpl_keypress (&main_playlist, event->keyval, event->state); + + if (event->keyval == GDK_n) { + // button for that one is not in toolbar anymore, so handle it manually + deadbeef->sendmessage (M_PLAYRANDOM, 0, 0, 0); + } + else { + gtkpl_keypress (&main_playlist, event->keyval, event->state); + } return FALSE; } @@ -598,15 +678,16 @@ on_playlist_drag_data_get (GtkWidget *widget, case TARGET_SAMEWIDGET: { // format as "STRING" consisting of array of pointers - int nsel = pl_getselcount (); + int nsel = deadbeef->pl_getselcount (); if (!nsel) { break; // something wrong happened } uint32_t *ptr = malloc (nsel * sizeof (uint32_t)); int idx = 0; int i = 0; - for (playItem_t *it = playlist_head[PL_MAIN]; it; it = it->next[PL_MAIN], idx++) { - if (it->selected) { + DB_playItem_t *it = deadbeef->pl_get_first (PL_MAIN); + for (; it; it = deadbeef->pl_get_next (it, PL_MAIN), idx++) { + if (deadbeef->pl_is_selected (it)) { ptr[i] = idx; i++; } @@ -634,14 +715,22 @@ on_playlist_drag_data_received (GtkWidget *widget, GTKPL_PROLOGUE; gchar *ptr=(char*)data->data; if (target_type == 0) { // uris - fprintf (stderr, "calling gtkpl_handle_fm_drag_drop\n"); -// if (!strncmp(ptr,"file:///",8)) { - gtkpl_handle_fm_drag_drop (ps, y, ptr, data->length); -// } + // this happens when dropped from file manager + char *mem = malloc (data->length+1); + memcpy (mem, ptr, data->length); + mem[data->length] = 0; + // we don't pass control structure, but there's only one drag-drop view currently + gtkui_receive_fm_drop (mem, data->length, y); } else if (target_type == 1) { uint32_t *d= (uint32_t *)ptr; - gtkpl_handle_drag_drop (ps, y, d, data->length/4); + int length = data->length/4; + int drop_row = y / rowheight + ps->scrollpos; + DB_playItem_t *drop_before = deadbeef->pl_get_for_idx_and_iter (drop_row, ps->iterator); + while (drop_before && SELECTED (drop_before)) { + drop_before = PL_NEXT(drop_before, ps->iterator); + } + deadbeef->pl_move_items (ps->iterator, drop_before, d, length); } gtk_drag_finish (drag_context, TRUE, FALSE, time); } @@ -676,69 +765,10 @@ on_playlist_drag_leave (GtkWidget *widget, } void -on_voice1_clicked (GtkButton *button, - gpointer user_data) -{ - codec_lock (); - if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { - str_playing_song.decoder->mutevoice (0, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); - } - codec_unlock (); -} - - -void -on_voice2_clicked (GtkButton *button, - gpointer user_data) -{ - codec_lock (); - if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { - str_playing_song.decoder->mutevoice (1, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); - } - codec_unlock (); -} - - -void -on_voice3_clicked (GtkButton *button, - gpointer user_data) -{ - codec_lock (); - if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { - str_playing_song.decoder->mutevoice (2, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); - } - codec_unlock (); -} - - -void -on_voice4_clicked (GtkButton *button, - gpointer user_data) -{ - codec_lock (); - if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { - str_playing_song.decoder->mutevoice (3, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); - } - codec_unlock (); -} - - -void -on_voice5_clicked (GtkButton *button, - gpointer user_data) -{ - codec_lock (); - if (str_playing_song.decoder && str_playing_song.decoder->mutevoice) { - str_playing_song.decoder->mutevoice (4, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) ? 0 : 1); - } - codec_unlock (); -} - -void on_order_linear_activate (GtkMenuItem *menuitem, gpointer user_data) { - conf_set_int ("playback.order", 0); + deadbeef->conf_set_int ("playback.order", 0); } @@ -746,7 +776,7 @@ void on_order_shuffle_activate (GtkMenuItem *menuitem, gpointer user_data) { - conf_set_int ("playback.order", 1); + deadbeef->conf_set_int ("playback.order", 1); } @@ -754,7 +784,7 @@ void on_order_random_activate (GtkMenuItem *menuitem, gpointer user_data) { - conf_set_int ("playback.order", 2); + deadbeef->conf_set_int ("playback.order", 2); } @@ -762,7 +792,7 @@ void on_loop_all_activate (GtkMenuItem *menuitem, gpointer user_data) { - conf_set_int ("playback.loop", 0); + deadbeef->conf_set_int ("playback.loop", 0); } @@ -770,7 +800,7 @@ void on_loop_single_activate (GtkMenuItem *menuitem, gpointer user_data) { - conf_set_int ("playback.loop", 2); + deadbeef->conf_set_int ("playback.loop", 2); } @@ -778,7 +808,7 @@ void on_loop_disable_activate (GtkMenuItem *menuitem, gpointer user_data) { - conf_set_int ("playback.loop", 1); + deadbeef->conf_set_int ("playback.loop", 1); } void @@ -825,8 +855,7 @@ on_playlist_load_activate (GtkMenuItem *menuitem, gchar *fname = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg)); gtk_widget_destroy (dlg); if (fname) { - int res = pl_load (fname); - printf ("load result: %d\n", res); + int res = deadbeef->pl_load (fname); g_free (fname); gtkplaylist_t *ps = &main_playlist; gtkpl_setup_scrollbar (ps); @@ -858,8 +887,7 @@ save_playlist_as (void) { gtk_widget_destroy (dlg); if (fname) { - int res = pl_save (fname); - printf ("save as res: %d\n", res); + int res = deadbeef->pl_save (fname); if (res >= 0 && strlen (fname) < 1024) { strcpy (last_playlist_save_name, fname); } @@ -879,8 +907,7 @@ on_playlist_save_activate (GtkMenuItem *menuitem, save_playlist_as (); } else { - int res = pl_save (last_playlist_save_name); - printf ("save res: %d\n", res); + int res = deadbeef->pl_save (last_playlist_save_name); } } @@ -956,7 +983,8 @@ seekbar_draw (GtkWidget *widget) { if (!cr) { return; } - if (!str_playing_song.decoder || str_playing_song._duration < 0) { + DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); + if (!trk->decoder || deadbeef->pl_get_item_duration (trk) < 0) { clearlooks_rounded_rectangle (cr, 2, widget->allocation.height/2-4, widget->allocation.width-4, 8, 4, 0xff); theme_set_cairo_source_rgb (cr, COLO_SEEKBAR_FRONT); cairo_stroke (cr); @@ -975,8 +1003,8 @@ seekbar_draw (GtkWidget *widget) { pos = x; } else { - if (str_playing_song.decoder && str_playing_song._duration > 0) { - pos = streamer_get_playpos () / str_playing_song._duration; + if (trk->decoder && deadbeef->pl_get_item_duration (trk) > 0) { + pos = deadbeef->streamer_get_playpos () / deadbeef->pl_get_item_duration (trk); pos *= widget->allocation.width; } } @@ -1047,7 +1075,7 @@ on_seekbar_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { - if (p_isstopped ()) { + if (deadbeef->get_output ()->state () == OUTPUT_STATE_STOPPED) { return FALSE; } seekbar_moving = 1; @@ -1066,12 +1094,12 @@ on_seekbar_button_release_event (GtkWidget *widget, seekbar_moving = 0; seekbar_draw (widget); seekbar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height); - float time = event->x * str_playing_song._duration / (widget->allocation.width); + DB_playItem_t *trk = deadbeef->streamer_get_playing_track (); + float time = event->x * deadbeef->pl_get_item_duration (trk) / (widget->allocation.width); if (time < 0) { time = 0; } - streamer_set_seek (time); -// messagepump_push (M_SONGSEEK, 0, time * 1000, 0); + deadbeef->streamer_seek (time); return FALSE; } @@ -1090,9 +1118,9 @@ volumebar_draw (GtkWidget *widget) { if (!cr) { return; } - float range = -volume_get_min_db (); + float range = -deadbeef->volume_get_min_db (); int n = widget->allocation.width / 4; - float vol = (range + volume_get_db ()) / range * n; + float vol = (range + deadbeef->volume_get_db ()) / range * n; float h = 16; for (int i = 0; i < n; i++) { float iy = (float)i + 3; @@ -1144,7 +1172,7 @@ on_volumebar_motion_notify_event (GtkWidget *widget, gpointer user_data) { if (event->state & GDK_BUTTON1_MASK) { - float range = -volume_get_min_db (); + float range = -deadbeef->volume_get_min_db (); float volume = event->x / widget->allocation.width * range - range; if (volume > 0) { volume = 0; @@ -1152,7 +1180,7 @@ on_volumebar_motion_notify_event (GtkWidget *widget, if (volume < -range) { volume = -range; } - volume_set_db (volume); + deadbeef->volume_set_db (volume); volumebar_draw (widget); volumebar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height); } @@ -1164,7 +1192,7 @@ on_volumebar_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { - float range = -volume_get_min_db (); + float range = -deadbeef->volume_get_min_db (); float volume = event->x / widget->allocation.width * range - range; if (volume < -range) { volume = -range; @@ -1172,7 +1200,7 @@ on_volumebar_button_press_event (GtkWidget *widget, if (volume > 0) { volume = 0; } - volume_set_db (volume); + deadbeef->volume_set_db (volume); volumebar_draw (widget); volumebar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height); return FALSE; @@ -1199,12 +1227,12 @@ on_mainwin_delete_event (GtkWidget *widget, GdkEvent *event, gpointer user_data) { - int conf_close_send_to_tray = conf_get_int ("close_send_to_tray", 0); + int conf_close_send_to_tray = deadbeef->conf_get_int ("close_send_to_tray", 0); if (conf_close_send_to_tray) { gtk_widget_hide (widget); } else { - messagepump_push (M_TERMINATE, 0, 0, 0); + deadbeef->sendmessage (M_TERMINATE, 0, 0, 0); } return TRUE; } @@ -1217,8 +1245,8 @@ on_volumebar_scroll_event (GtkWidget *widget, GdkEventScroll *event, gpointer user_data) { - float range = -volume_get_min_db (); - float vol = volume_get_db (); + float range = -deadbeef->volume_get_min_db (); + float vol = deadbeef->volume_get_db (); if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_RIGHT) { vol += 1; } @@ -1231,7 +1259,7 @@ on_volumebar_scroll_event (GtkWidget *widget, else if (vol < -range) { vol = -range; } - volume_set_db (vol); + deadbeef->volume_set_db (vol); GtkWidget *volumebar = lookup_widget (mainwin, "volumebar"); volumebar_draw (volumebar); volumebar_expose (volumebar, 0, 0, volumebar->allocation.width, volumebar->allocation.height); @@ -1245,7 +1273,21 @@ on_mainwin_configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer user_data) { - session_capture_window_attrs ((uintptr_t)widget); +#if GTK_CHECK_VERSION(2,2,0) + GdkWindowState window_state = gdk_window_get_state (GDK_WINDOW (mainwin->window)); +#else + GdkWindowState window_state = gdk_window_get_state (G_OBJECT (mainwin)); +#endif + if (!(window_state & GDK_WINDOW_STATE_MAXIMIZED) && GTK_WIDGET_VISIBLE (mainwin)) { + int x, y; + int w, h; + gtk_window_get_position (GTK_WINDOW (mainwin), &x, &y); + gtk_window_get_size (GTK_WINDOW (mainwin), &w, &h); + deadbeef->conf_set_int ("mainwin.geometry.x", x); + deadbeef->conf_set_int ("mainwin.geometry.y", y); + deadbeef->conf_set_int ("mainwin.geometry.w", w); + deadbeef->conf_set_int ("mainwin.geometry.h", h); + } return FALSE; } @@ -1254,7 +1296,7 @@ void on_scroll_follows_playback_activate (GtkMenuItem *menuitem, gpointer user_data) { - conf_set_int ("playlist.scroll.followplayback", gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem))); + deadbeef->conf_set_int ("playlist.scroll.followplayback", gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem))); } @@ -1371,7 +1413,7 @@ void on_add_audio_cd_activate (GtkMenuItem *menuitem, gpointer user_data) { - pl_add_file ("all.cda", NULL, NULL); + deadbeef->pl_add_file ("all.cda", NULL, NULL); playlist_refresh (); } @@ -1389,7 +1431,7 @@ gtk_enum_sound_callback (const char *name, const char *desc, void *userdata) { GtkComboBox *combobox = GTK_COMBO_BOX (userdata); gtk_combo_box_append_text (combobox, desc); - if (!strcmp (conf_get_str ("alsa_soundcard", "default"), name)) { + if (!strcmp (deadbeef->conf_get_str ("alsa_soundcard", "default"), name)) { gtk_combo_box_set_active (combobox, num_alsa_devices); } @@ -1399,50 +1441,103 @@ gtk_enum_sound_callback (const char *name, const char *desc, void *userdata) { } void -on_preferences_activate (GtkMenuItem *menuitem, - gpointer user_data) -{ - GtkWidget *w = prefwin = create_prefwin (); - gtk_window_set_transient_for (GTK_WINDOW (w), GTK_WINDOW (mainwin)); - - // alsa_soundcard +on_plugin_active_toggled (GtkCellRendererToggle *cell_renderer, gchar *path, GtkTreeModel *model) { + GtkTreePath *p = gtk_tree_path_new_from_string (path); + if (p) { + int *indices = gtk_tree_path_get_indices (p); + //gtk_tree_path_free (p); // wtf?? gtk crashes on this + if (indices) { + DB_plugin_t **plugins = deadbeef->plug_get_list (); + DB_plugin_t *plug = plugins[*indices]; + gboolean state; + GtkTreeIter iter; + gtk_tree_model_get_iter (model, &iter, p); + gtk_tree_model_get (model, &iter, 0, &state, -1); + if (!deadbeef->plug_activate (plug, !state)) { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, !state, -1); + } + } + g_free (indices); + } +} - const char *s = conf_get_str ("alsa_soundcard", "default"); +void +preferences_fill_soundcards (void) { + GtkWidget *w = prefwin; + if (!prefwin) { + return; + } + const char *s = deadbeef->conf_get_str ("alsa_soundcard", "default"); GtkComboBox *combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_soundcard")); + GtkTreeModel *mdl = gtk_combo_box_get_model (combobox); + gtk_list_store_clear (GTK_LIST_STORE (mdl)); + gtk_combo_box_append_text (combobox, "Default Audio Device"); if (!strcmp (s, "default")) { gtk_combo_box_set_active (combobox, 0); } num_alsa_devices = 1; strcpy (alsa_device_names[0], "default"); - palsa_enum_soundcards (gtk_enum_sound_callback, combobox); + if (deadbeef->get_output ()->enum_soundcards) { + deadbeef->get_output ()->enum_soundcards (gtk_enum_sound_callback, combobox); + gtk_widget_set_sensitive (GTK_WIDGET (combobox), TRUE); + } + else { + gtk_widget_set_sensitive (GTK_WIDGET (combobox), FALSE); + } +} + +void +on_preferences_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + GtkWidget *w = prefwin = create_prefwin (); + gtk_window_set_transient_for (GTK_WINDOW (w), GTK_WINDOW (mainwin)); + + GtkComboBox *combobox = NULL;; + + // output plugin selection + const char *outplugname = deadbeef->conf_get_str ("output_plugin", "ALSA output plugin"); + combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_output_plugin")); + + DB_output_t **out_plugs = deadbeef->plug_get_output_list (); + for (int i = 0; out_plugs[i]; i++) { + gtk_combo_box_append_text (combobox, out_plugs[i]->plugin.name); + if (!strcmp (outplugname, out_plugs[i]->plugin.name)) { + gtk_combo_box_set_active (combobox, i); + } + } + + // soundcard (output device) selection + preferences_fill_soundcards (); + // alsa resampling - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_alsa_resampling")), conf_get_int ("alsa.resample", 0)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_alsa_resampling")), deadbeef->conf_get_int ("alsa.resample", 0)); // alsa freeonstop - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_alsa_freewhenstopped")), conf_get_int ("alsa.freeonstop", 0)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_alsa_freewhenstopped")), deadbeef->conf_get_int ("alsa.freeonstop", 0)); // src_quality combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_src_quality")); - gtk_combo_box_set_active (combobox, conf_get_int ("src_quality", 2)); + gtk_combo_box_set_active (combobox, deadbeef->conf_get_int ("src_quality", 2)); // replaygain_mode combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_replaygain_mode")); - gtk_combo_box_set_active (combobox, conf_get_int ("replaygain_mode", 0)); + gtk_combo_box_set_active (combobox, deadbeef->conf_get_int ("replaygain_mode", 0)); // replaygain_scale - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_replaygain_scale")), conf_get_int ("replaygain_scale", 1)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_replaygain_scale")), deadbeef->conf_get_int ("replaygain_scale", 1)); // close_send_to_tray - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_close_send_to_tray")), conf_get_int ("close_send_to_tray", 0)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_close_send_to_tray")), deadbeef->conf_get_int ("close_send_to_tray", 0)); // network - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_network_enableproxy")), conf_get_int ("network.proxy", 0)); - gtk_entry_set_text (GTK_ENTRY (lookup_widget (w, "pref_network_proxyaddress")), conf_get_str ("network.proxy.address", "")); - gtk_entry_set_text (GTK_ENTRY (lookup_widget (w, "pref_network_proxyport")), conf_get_str ("network.proxy.port", "8080")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (w, "pref_network_enableproxy")), deadbeef->conf_get_int ("network.proxy", 0)); + gtk_entry_set_text (GTK_ENTRY (lookup_widget (w, "pref_network_proxyaddress")), deadbeef->conf_get_str ("network.proxy.address", "")); + gtk_entry_set_text (GTK_ENTRY (lookup_widget (w, "pref_network_proxyport")), deadbeef->conf_get_str ("network.proxy.port", "8080")); combobox = GTK_COMBO_BOX (lookup_widget (w, "pref_network_proxytype")); - const char *type = conf_get_str ("network.proxy.type", "HTTP"); + const char *type = deadbeef->conf_get_str ("network.proxy.type", "HTTP"); if (!strcasecmp (type, "HTTP")) { gtk_combo_box_set_active (combobox, 0); } @@ -1464,16 +1559,22 @@ on_preferences_activate (GtkMenuItem *menuitem, // list of plugins GtkTreeView *tree = GTK_TREE_VIEW (lookup_widget (w, "pref_pluginlist")); - GtkListStore *store = gtk_list_store_new (1, G_TYPE_STRING);//GTK_LIST_STORE (gtk_tree_view_get_model (tree)); - GtkCellRenderer *rend = gtk_cell_renderer_text_new (); - GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes ("Title", rend, "text", 0, NULL); - gtk_tree_view_append_column (tree, col); - DB_plugin_t **plugins = plug_get_list (); + GtkListStore *store = gtk_list_store_new (3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_BOOLEAN); + GtkCellRenderer *rend_toggle = gtk_cell_renderer_toggle_new (); + GtkCellRenderer *rend_text = gtk_cell_renderer_text_new (); + g_signal_connect ((gpointer)rend_toggle, "toggled", + G_CALLBACK (on_plugin_active_toggled), + store); + GtkTreeViewColumn *col1 = gtk_tree_view_column_new_with_attributes ("Active", rend_toggle, "active", 0, "activatable", 2, NULL); + GtkTreeViewColumn *col2 = gtk_tree_view_column_new_with_attributes ("Title", rend_text, "text", 1, NULL); + gtk_tree_view_append_column (tree, col1); + gtk_tree_view_append_column (tree, col2); + DB_plugin_t **plugins = deadbeef->plug_get_list (); int i; for (i = 0; plugins[i]; i++) { GtkTreeIter it; gtk_list_store_append (store, &it); - gtk_list_store_set (store, &it, 0, plugins[i]->name, -1); + gtk_list_store_set (store, &it, 0, plugins[i]->inactive ? FALSE : TRUE, 1, plugins[i]->name, 2, plugins[i]->nostop ? FALSE : TRUE, -1); } gtk_tree_view_set_model (tree, GTK_TREE_MODEL (store)); @@ -1487,8 +1588,42 @@ on_pref_soundcard_changed (GtkComboBox *combobox, { int active = gtk_combo_box_get_active (combobox); if (active >= 0 && active < num_alsa_devices) { - conf_set_str ("alsa_soundcard", alsa_device_names[active]); - messagepump_push (M_CONFIGCHANGED, 0, 0, 0); + const char *soundcard = deadbeef->conf_get_str ("alsa_soundcard", "default"); + if (strcmp (soundcard, alsa_device_names[active])) { + deadbeef->conf_set_str ("alsa_soundcard", alsa_device_names[active]); + deadbeef->sendmessage (M_CONFIGCHANGED, 0, 0, 0); + } + } +} + +void +on_pref_output_plugin_changed (GtkComboBox *combobox, + gpointer user_data) +{ + const char *outplugname = deadbeef->conf_get_str ("output_plugin", "ALSA output plugin"); + int active = gtk_combo_box_get_active (combobox); + + DB_output_t **out_plugs = deadbeef->plug_get_output_list (); + DB_output_t *prev = NULL; + DB_output_t *new = NULL; + + for (int i = 0; out_plugs[i]; i++) { + if (!strcmp (out_plugs[i]->plugin.name, outplugname)) { + prev = out_plugs[i]; + } + if (i == active) { + new = out_plugs[i]; + } + } + + if (!new) { + fprintf (stderr, "failed to find output plugin selected in preferences window\n"); + } + else { + if (prev != new) { + deadbeef->conf_set_str ("output_plugin", new->plugin.name); + deadbeef->sendmessage (M_REINIT_SOUND, 0, 0, 0); + } } } @@ -1497,8 +1632,8 @@ on_pref_alsa_resampling_clicked (GtkButton *button, gpointer user_data) { int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); - conf_set_int ("alsa.resample", active); - messagepump_push (M_CONFIGCHANGED, 0, 0, 0); + deadbeef->conf_set_int ("alsa.resample", active); + deadbeef->sendmessage (M_CONFIGCHANGED, 0, 0, 0); } @@ -1507,8 +1642,8 @@ on_pref_src_quality_changed (GtkComboBox *combobox, gpointer user_data) { int active = gtk_combo_box_get_active (combobox); - conf_set_int ("src_quality", active == -1 ? 2 : active); - messagepump_push (M_CONFIGCHANGED, 0, 0, 0); + deadbeef->conf_set_int ("src_quality", active == -1 ? 2 : active); + deadbeef->sendmessage (M_CONFIGCHANGED, 0, 0, 0); } @@ -1517,8 +1652,8 @@ on_pref_replaygain_mode_changed (GtkComboBox *combobox, gpointer user_data) { int active = gtk_combo_box_get_active (combobox); - conf_set_int ("replaygain_mode", active == -1 ? 0 : active); - messagepump_push (M_CONFIGCHANGED, 0, 0, 0); + deadbeef->conf_set_int ("replaygain_mode", active == -1 ? 0 : active); + deadbeef->sendmessage (M_CONFIGCHANGED, 0, 0, 0); } void @@ -1526,8 +1661,8 @@ on_pref_replaygain_scale_clicked (GtkButton *button, gpointer user_data) { int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); - conf_set_int ("replaygain_scale", active); - messagepump_push (M_CONFIGCHANGED, 0, 0, 0); + deadbeef->conf_set_int ("replaygain_scale", active); + deadbeef->sendmessage (M_CONFIGCHANGED, 0, 0, 0); } @@ -1536,15 +1671,8 @@ on_pref_close_send_to_tray_clicked (GtkButton *button, gpointer user_data) { int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); - conf_set_int ("close_send_to_tray", active); - messagepump_push (M_CONFIGCHANGED, 0, 0, 0); -} - - -void -on_pref_plugin_configure_activate (GtkButton *button, - gpointer user_data) -{ + deadbeef->conf_set_int ("close_send_to_tray", active); + deadbeef->sendmessage (M_CONFIGCHANGED, 0, 0, 0); } void @@ -1559,8 +1687,9 @@ on_pref_pluginlist_cursor_changed (GtkTreeView *treeview, return; } int *indices = gtk_tree_path_get_indices (path); - DB_plugin_t **plugins = plug_get_list (); + DB_plugin_t **plugins = deadbeef->plug_get_list (); DB_plugin_t *p = plugins[*indices]; + g_free (indices); assert (p); GtkWidget *w = prefwin;//GTK_WIDGET (gtk_widget_get_parent_window (GTK_WIDGET (treeview))); assert (w); @@ -1574,13 +1703,42 @@ on_pref_pluginlist_cursor_changed (GtkTreeView *treeview, gtk_entry_set_text (e, p->website ? p->website : ""); } +gboolean +on_prefwin_delete_event (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + prefwin = NULL; + return FALSE; +} + +void +pl_add_column (const char *title, int width, int id, const char *format, int align_right) +{ + gtkplaylist_t *ps = last_playlist; + + gtkpl_column_append (ps, gtkpl_column_alloc (title, width, id, format, align_right)); + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); + + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + + gtkpl_column_rewrite_config (ps); +} void on_artist_activate (GtkMenuItem *menuitem, gpointer user_data) { - + GtkWidget *parent = GTK_WIDGET (menuitem); + do + { + parent = gtk_widget_get_parent (parent); + printf ("parent: %x\n", parent); + } while (parent); + pl_add_column ("Artist", 100, DB_COLUMN_ARTIST, NULL, 0); } @@ -1588,7 +1746,7 @@ void on_album_activate (GtkMenuItem *menuitem, gpointer user_data) { - + pl_add_column ("Album", 100, DB_COLUMN_ALBUM, NULL, 0); } @@ -1596,7 +1754,7 @@ void on_tracknum_activate (GtkMenuItem *menuitem, gpointer user_data) { - + pl_add_column ("Track â„–", 50, DB_COLUMN_TRACK, NULL, 0); } @@ -1604,7 +1762,7 @@ void on_duration_activate (GtkMenuItem *menuitem, gpointer user_data) { - + pl_add_column ("Duration", 50, DB_COLUMN_DURATION, NULL, 0); } @@ -1612,7 +1770,7 @@ void on_playing_activate (GtkMenuItem *menuitem, gpointer user_data) { - + pl_add_column ("Playing", 50, DB_COLUMN_PLAYING, NULL, 0); } @@ -1620,7 +1778,7 @@ void on_title_activate (GtkMenuItem *menuitem, gpointer user_data) { - + pl_add_column ("Title", 150, DB_COLUMN_TITLE, NULL, 0); } @@ -1628,7 +1786,9 @@ void on_custom_activate (GtkMenuItem *menuitem, gpointer user_data) { - + if (!formatwin) + formatwin = create_inputformat (); + gtk_widget_show (formatwin); } @@ -1636,7 +1796,20 @@ void on_remove_column_activate (GtkMenuItem *menuitem, gpointer user_data) { + gtkplaylist_t *ps = last_playlist; + if (!ps->active_column) + return; + + gtkpl_column_remove (ps, ps->active_column); + + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); + + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + + gtkpl_column_rewrite_config (ps); } @@ -1645,14 +1818,14 @@ on_pref_alsa_freewhenstopped_clicked (GtkButton *button, gpointer user_data) { int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); - conf_set_int ("alsa.freeonstop", active); + deadbeef->conf_set_int ("alsa.freeonstop", active); } void on_pref_network_proxyaddress_changed (GtkEditable *editable, gpointer user_data) { - conf_set_str ("network.proxy.address", gtk_entry_get_text (GTK_ENTRY (editable))); + deadbeef->conf_set_str ("network.proxy.address", gtk_entry_get_text (GTK_ENTRY (editable))); } @@ -1660,7 +1833,7 @@ void on_pref_network_enableproxy_clicked (GtkButton *button, gpointer user_data) { - conf_set_int ("network.proxy", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))); + deadbeef->conf_set_int ("network.proxy", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))); } @@ -1668,7 +1841,7 @@ void on_pref_network_proxyport_changed (GtkEditable *editable, gpointer user_data) { - conf_set_int ("network.proxy.port", atoi (gtk_entry_get_text (GTK_ENTRY (editable)))); + deadbeef->conf_set_int ("network.proxy.port", atoi (gtk_entry_get_text (GTK_ENTRY (editable)))); } @@ -1680,25 +1853,25 @@ on_pref_network_proxytype_changed (GtkComboBox *combobox, int active = gtk_combo_box_get_active (combobox); switch (active) { case 0: - conf_set_str ("network.proxy.type", "HTTP"); + deadbeef->conf_set_str ("network.proxy.type", "HTTP"); break; case 1: - conf_set_str ("network.proxy.type", "HTTP_1_0"); + deadbeef->conf_set_str ("network.proxy.type", "HTTP_1_0"); break; case 2: - conf_set_str ("network.proxy.type", "SOCKS4"); + deadbeef->conf_set_str ("network.proxy.type", "SOCKS4"); break; case 3: - conf_set_str ("network.proxy.type", "SOCKS5"); + deadbeef->conf_set_str ("network.proxy.type", "SOCKS5"); break; case 4: - conf_set_str ("network.proxy.type", "SOCKS4A"); + deadbeef->conf_set_str ("network.proxy.type", "SOCKS4A"); break; case 5: - conf_set_str ("network.proxy.type", "SOCKS5_HOSTNAME"); + deadbeef->conf_set_str ("network.proxy.type", "SOCKS5_HOSTNAME"); break; default: - conf_set_str ("network.proxy.type", "HTTP"); + deadbeef->conf_set_str ("network.proxy.type", "HTTP"); break; } } @@ -1743,7 +1916,7 @@ on_addlocation_entry_activate (GtkEntry *entry, { const char *text = gtk_entry_get_text (entry); if (text) { - pl_add_file (text, NULL, NULL); + deadbeef->pl_add_file (text, NULL, NULL); playlist_refresh (); } add_location_destroy (); @@ -1758,7 +1931,7 @@ on_addlocation_ok_clicked (GtkButton *button, if (entry) { const char *text = gtk_entry_get_text (entry); if (text) { - pl_add_file (text, NULL, NULL); + deadbeef->pl_add_file (text, NULL, NULL); playlist_refresh (); } } @@ -1778,7 +1951,336 @@ on_addlocation_key_press_event (GtkWidget *widget, } +void +on_prop_entry_changed(GtkEditable *editable, gpointer user_data) { + const char *key = g_object_get_data (G_OBJECT (editable), "key"); + if (key) { + deadbeef->conf_set_str (key, gtk_entry_get_text (GTK_ENTRY (editable))); + } +} + +void +on_prop_checkbox_clicked (GtkButton *button, gpointer user_data) { + const char *key = g_object_get_data (G_OBJECT (button), "key"); + if (key) { + deadbeef->conf_set_int (key, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))); + } +} + +void +on_prop_browse_file (GtkButton *button, gpointer user_data) { + GtkWidget *dlg = gtk_file_chooser_dialog_new ("Open file...", GTK_WINDOW (mainwin), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL); + + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dlg), FALSE); + // restore folder + gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dlg), deadbeef->conf_get_str ("filechooser.lastdir", "")); + int response = gtk_dialog_run (GTK_DIALOG (dlg)); + // store folder + gchar *folder = gtk_file_chooser_get_current_folder_uri (GTK_FILE_CHOOSER (dlg)); + if (folder) { + deadbeef->conf_set_str ("filechooser.lastdir", folder); + g_free (folder); + } + if (response == GTK_RESPONSE_OK) { + gchar *file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg)); + gtk_widget_destroy (dlg); + if (file) { + GtkWidget *entry = GTK_WIDGET (user_data); + gtk_entry_set_text (GTK_ENTRY (entry), file); + g_free (file); + } + } + else { + gtk_widget_destroy (dlg); + } +} + +gboolean +on_plug_prefwin_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { + if (event->keyval == GDK_Escape) { + gtk_widget_destroy (widget); + } + return FALSE; +} + +void +plugin_configure (GtkWidget *parentwin, DB_plugin_t *p) { + // create window + GtkWidget *win = gtk_window_new (GTK_WINDOW_TOPLEVEL); +// gtk_widget_set_events (win, GDK_KEY_PRESS_MASK); + gtk_widget_set_size_request (win, 300, -1); + g_signal_connect ((gpointer) win, "key_press_event", G_CALLBACK (on_plug_prefwin_key_press_event), NULL); + char title[200]; + snprintf (title, sizeof (title), "Setup %s", p->name); + gtk_window_set_title (GTK_WINDOW (win), title); + gtk_window_set_modal (GTK_WINDOW (win), TRUE); + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parentwin)); + GtkWidget *tbl; + tbl = gtk_table_new (1, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (tbl), 3); + gtk_table_set_col_spacings (GTK_TABLE (tbl), 3); + gtk_container_add (GTK_CONTAINER (win), tbl); + + int nrows = 0; + // parse script + char token[MAX_TOKEN]; + const char *script = p->configdialog; + parser_line = 1; + while (script = gettoken (script, token)) { + if (strcmp (token, "property")) { + fprintf (stderr, "invalid token while loading plugin %s config dialog: %s at line %d\n", p->name, token, parser_line); + break; + } + char labeltext[MAX_TOKEN]; + script = gettoken_warn_eof (script, labeltext); + if (!script) { + break; + } + char type[MAX_TOKEN]; + script = gettoken_warn_eof (script, type); + if (!script) { + break; + } + char key[MAX_TOKEN]; + script = gettoken_warn_eof (script, key); + if (!script) { + break; + } + char def[MAX_TOKEN]; + script = gettoken_warn_eof (script, def); + if (!script) { + break; + } + script = gettoken_warn_eof (script, token); + if (!script) { + break; + } + if (strcmp (token, ";")) { + fprintf (stderr, "expected `;' while loading plugin %s config dialog: %s at line %d\n", p->name, token, parser_line); + break; + } + + // add to dialog + nrows++; + gtk_table_resize (GTK_TABLE (tbl), nrows, 2); + GtkWidget *label; + GtkWidget *prop; + GtkWidget *cont = NULL; + label = gtk_label_new (labeltext); + if (!strcmp (type, "entry") || !strcmp (type, "password")) { + prop = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (prop), deadbeef->conf_get_str (key, def)); + g_signal_connect ((gpointer) prop, "changed", + G_CALLBACK (on_prop_entry_changed), + NULL); + } + else if (!strcmp (type, "checkbox")) { + prop = gtk_check_button_new (); + int val = deadbeef->conf_get_int (key, atoi (def)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (prop), val); + g_signal_connect ((gpointer) prop, "clicked", + G_CALLBACK (on_prop_checkbox_clicked), + NULL); + } + else if (!strcmp (type, "file")) { + cont = gtk_hbox_new (FALSE, FALSE); + prop = gtk_entry_new (); + gtk_editable_set_editable (GTK_EDITABLE (prop), FALSE); + g_signal_connect ((gpointer) prop, "changed", + G_CALLBACK (on_prop_entry_changed), + NULL); + gtk_entry_set_text (GTK_ENTRY (prop), deadbeef->conf_get_str (key, def)); + gtk_box_pack_start (GTK_BOX (cont), prop, TRUE, TRUE, 0); + GtkWidget *btn = gtk_button_new_with_label ("…"); + gtk_box_pack_start (GTK_BOX (cont), btn, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (btn), "clicked", G_CALLBACK (on_prop_browse_file), prop); + } + if (!strcmp (type, "password")) { + gtk_entry_set_visibility (GTK_ENTRY (prop), FALSE); + } + if (!cont) { + cont = prop; + } + if (label && prop) { + char *keydup = strdup (key); + g_object_set_data_full (G_OBJECT (prop), "key", keydup, (GDestroyNotify)free); + gtk_table_attach (GTK_TABLE (tbl), label, 0, 1, nrows-1, nrows, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions)0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_table_attach (GTK_TABLE (tbl), cont, 1, 2, nrows-1, nrows, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions)0, 0, 0); + } + } + + + gtk_widget_show_all (win); +} + +void +on_configure_plugin_clicked (GtkButton *button, + gpointer user_data) +{ + GtkWidget *w = prefwin; + GtkTreeView *treeview = GTK_TREE_VIEW (lookup_widget (w, "pref_pluginlist")); + GtkTreePath *path; + GtkTreeViewColumn *col; + gtk_tree_view_get_cursor (treeview, &path, &col); + if (!path || !col) { + // reset + return; + } + int *indices = gtk_tree_path_get_indices (path); + DB_plugin_t **plugins = deadbeef->plug_get_list (); + DB_plugin_t *p = plugins[*indices]; + if (p->configdialog) { + plugin_configure (prefwin, p); + } +} + + +gboolean +on_mainwin_window_state_event (GtkWidget *widget, + GdkEventWindowState *event, + gpointer user_data) +{ + // based on pidgin maximization handler +#if GTK_CHECK_VERSION(2,2,0) + if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) { + if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { + deadbeef->conf_set_int ("mainwin.geometry.maximized", 1); + } + else { + deadbeef->conf_set_int ("mainwin.geometry.maximized", 0); + } + } +#else + GdkWindowState new_window_state = gdk_window_get_state(G_OBJECT(widget)); + + if (new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { + deadbeef->conf_set_int ("mainwin.geometry.maximized", 1); + } + else { + deadbeef->conf_set_int ("mainwin.geometry.maximized", 0); + } +#endif + return FALSE; +} + + +void +on_toggle_status_bar_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + GtkWidget *sb = lookup_widget (mainwin, "statusbar"); + if (sb) { + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem))) { + deadbeef->conf_set_int ("gtkui.statusbar.visible", 0); + gtk_widget_hide (sb); + } + else { + deadbeef->conf_set_int ("gtkui.statusbar.visible", 1); + gtk_widget_show (sb); + } + } +} + +void +on_toggle_column_headers_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + GtkWidget *header = lookup_widget (mainwin, "header"); + if (header) { + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem))) { + deadbeef->conf_set_int ("gtkui.headers.visible", 0); + gtk_widget_hide (header); + } + else { + deadbeef->conf_set_int ("gtkui.headers.visible", 1); + gtk_widget_show (header); + } + } +} + +void +on_stop_after_current_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + deadbeef->conf_set_int ("playlist.stop_after_current", gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem))); +} + + +void +on_add_to_playback_queue1_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + GtkWidget *widget = GTK_WIDGET (menuitem); + GTKPL_PROLOGUE; + DB_playItem_t *it = deadbeef->pl_get_first (ps->iterator); + while (it) { + if (SELECTED (it)) { + deadbeef->pl_playqueue_push (it); + } + it = PL_NEXT (it, ps->iterator); + } + playlist_refresh (); +} +void +on_remove_from_playback_queue1_activate + (GtkMenuItem *menuitem, + gpointer user_data) +{ + GtkWidget *widget = GTK_WIDGET (menuitem); + GTKPL_PROLOGUE; + DB_playItem_t *it = deadbeef->pl_get_first (ps->iterator); + while (it) { + if (SELECTED (it)) { + deadbeef->pl_playqueue_remove (it); + } + it = PL_NEXT (it, ps->iterator); + } + playlist_refresh (); +} + + +void +on_remove2_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ +} + +void +on_properties1_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + +} + + + +void +on_format_cancel_clicked (GtkButton *button, + gpointer user_data) +{ + gtk_widget_hide (formatwin); +} + + +void +on_format_ok_clicked (GtkButton *button, + gpointer user_data) +{ + const gchar *title = gtk_entry_get_text (GTK_ENTRY (lookup_widget (formatwin, "titleentry"))); + const gchar *format = gtk_entry_get_text (GTK_ENTRY (lookup_widget (formatwin, "formatentry"))); + pl_add_column (title, 100, -1, format, 0); + gtk_widget_hide (formatwin); +} + + +void +on_cursor_follows_playback_activate (GtkMenuItem *menuitem, + gpointer user_data) +{ + deadbeef->conf_set_int ("playlist.scroll.cursorfollowplayback", gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menuitem))); +} diff --git a/callbacks.h b/plugins/gtkui/callbacks.h index d2e3f61a..188cfe8a 100644 --- a/callbacks.h +++ b/plugins/gtkui/callbacks.h @@ -692,3 +692,70 @@ on_addlocation_ok_clicked (GtkButton *button, void on_addlocation_entry_activate (GtkEntry *entry, gpointer user_data); + +void +on_configure_plugin_clicked (GtkButton *button, + gpointer user_data); + +gboolean +on_mainwin_window_state_event (GtkWidget *widget, + GdkEventWindowState *event, + gpointer user_data); + +void +on_pref_output_plugin_changed (GtkComboBox *combobox, + gpointer user_data); + +void +on_toggle_status_bar_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_toggle_menu_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_toggle_column_headers_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_stop_after_current1_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_stop_after_current_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_add_to_playback_queue1_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_remove_from_playback_queue1_activate + (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_remove2_activate (GtkMenuItem *menuitem, + gpointer user_data); + +void +on_properties1_activate (GtkMenuItem *menuitem, + gpointer user_data); + +gboolean +on_prefwin_delete_event (GtkWidget *widget, + GdkEvent *event, + gpointer user_data); + +void +on_format_cancel_clicked (GtkButton *button, + gpointer user_data); + +void +on_format_ok_clicked (GtkButton *button, + gpointer user_data); + +void +on_cursor_follows_playback_activate (GtkMenuItem *menuitem, + gpointer user_data); diff --git a/deadbeef.glade b/plugins/gtkui/deadbeef.glade index b48af591..e017976b 100644 --- a/deadbeef.glade +++ b/plugins/gtkui/deadbeef.glade @@ -25,6 +25,7 @@ <signal name="key_press_event" handler="on_mainwin_key_press_event" last_modification_time="Thu, 30 Jul 2009 21:14:26 GMT"/> <signal name="delete_event" handler="on_mainwin_delete_event" last_modification_time="Thu, 13 Aug 2009 20:35:55 GMT"/> <signal name="configure_event" handler="on_mainwin_configure_event" last_modification_time="Sun, 23 Aug 2009 15:26:53 GMT"/> + <signal name="window_state_event" handler="on_mainwin_window_state_event" last_modification_time="Wed, 09 Dec 2009 19:39:55 GMT"/> <child> <widget class="GtkVBox" id="vbox1"> @@ -56,7 +57,7 @@ <accelerator key="O" modifiers="GDK_CONTROL_MASK" signal="activate"/> <child internal-child="image"> - <widget class="GtkImage" id="image120"> + <widget class="GtkImage" id="image290"> <property name="visible">True</property> <property name="stock">gtk-open</property> <property name="icon_size">1</property> @@ -83,7 +84,7 @@ <signal name="activate" handler="on_add_files_activate" last_modification_time="Sat, 04 Jul 2009 13:04:01 GMT"/> <child internal-child="image"> - <widget class="GtkImage" id="image121"> + <widget class="GtkImage" id="image291"> <property name="visible">True</property> <property name="stock">gtk-add</property> <property name="icon_size">1</property> @@ -104,7 +105,7 @@ <signal name="activate" handler="on_add_folders_activate" last_modification_time="Sun, 06 Sep 2009 17:51:40 GMT"/> <child internal-child="image"> - <widget class="GtkImage" id="image122"> + <widget class="GtkImage" id="image292"> <property name="visible">True</property> <property name="stock">gtk-add</property> <property name="icon_size">1</property> @@ -125,7 +126,7 @@ <signal name="activate" handler="on_add_audio_cd_activate" last_modification_time="Sat, 10 Oct 2009 15:29:22 GMT"/> <child internal-child="image"> - <widget class="GtkImage" id="image123"> + <widget class="GtkImage" id="image293"> <property name="visible">True</property> <property name="stock">gtk-add</property> <property name="icon_size">1</property> @@ -154,6 +155,39 @@ </child> <child> + <widget class="GtkMenuItem" id="playlist_load"> + <property name="visible">True</property> + <property name="label" translatable="yes">Load playlist</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_playlist_load_activate" last_modification_time="Sun, 09 Aug 2009 19:04:52 GMT"/> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="playlist_save"> + <property name="visible">True</property> + <property name="label" translatable="yes">Save playlist</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_playlist_save_activate" last_modification_time="Sun, 09 Aug 2009 19:04:52 GMT"/> + </widget> + </child> + + <child> + <widget class="GtkMenuItem" id="playlist_save_as"> + <property name="visible">True</property> + <property name="label" translatable="yes">Save playlist as</property> + <property name="use_underline">True</property> + <signal name="activate" handler="on_playlist_save_as_activate" last_modification_time="Sun, 09 Aug 2009 19:04:52 GMT"/> + </widget> + </child> + + <child> + <widget class="GtkSeparatorMenuItem" id="separator8"> + <property name="visible">True</property> + </widget> + </child> + + <child> <widget class="GtkImageMenuItem" id="quit"> <property name="visible">True</property> <property name="label" translatable="yes">_Quit</property> @@ -162,7 +196,7 @@ <accelerator key="Q" modifiers="GDK_CONTROL_MASK" signal="activate"/> <child internal-child="image"> - <widget class="GtkImage" id="image124"> + <widget class="GtkImage" id="image294"> <property name="visible">True</property> <property name="stock">gtk-quit</property> <property name="icon_size">1</property> @@ -182,7 +216,7 @@ <child> <widget class="GtkMenuItem" id="edit1"> <property name="visible">True</property> - <property name="label" translatable="yes">Edit</property> + <property name="label" translatable="yes">_Edit</property> <property name="use_underline">True</property> <child> @@ -196,7 +230,7 @@ <signal name="activate" handler="on_clear1_activate" last_modification_time="Sun, 06 Sep 2009 18:30:03 GMT"/> <child internal-child="image"> - <widget class="GtkImage" id="image125"> + <widget class="GtkImage" id="image295"> <property name="visible">True</property> <property name="stock">gtk-clear</property> <property name="icon_size">1</property> @@ -237,7 +271,7 @@ <accelerator key="Delete" modifiers="0" signal="activate"/> <child internal-child="image"> - <widget class="GtkImage" id="image126"> + <widget class="GtkImage" id="image296"> <property name="visible">True</property> <property name="stock">gtk-remove</property> <property name="icon_size">1</property> @@ -293,40 +327,46 @@ </child> <child> - <widget class="GtkMenuItem" id="playlist1"> + <widget class="GtkMenuItem" id="view1"> <property name="visible">True</property> - <property name="label" translatable="yes">Playlist</property> + <property name="label" translatable="yes">_View</property> <property name="use_underline">True</property> <child> - <widget class="GtkMenu" id="playlist1_menu"> + <widget class="GtkMenu" id="view1_menu"> <child> - <widget class="GtkMenuItem" id="playlist_load"> + <widget class="GtkCheckMenuItem" id="view_status_bar"> <property name="visible">True</property> - <property name="label" translatable="yes">Load</property> + <property name="label" translatable="yes">Status bar</property> <property name="use_underline">True</property> - <signal name="activate" handler="on_playlist_load_activate" last_modification_time="Sun, 09 Aug 2009 19:04:52 GMT"/> + <property name="active">False</property> + <signal name="activate" handler="on_toggle_status_bar_activate" last_modification_time="Sun, 13 Dec 2009 17:23:50 GMT"/> </widget> </child> <child> - <widget class="GtkMenuItem" id="playlist_save"> + <widget class="GtkCheckMenuItem" id="view_headers"> <property name="visible">True</property> - <property name="label" translatable="yes">Save</property> + <property name="label" translatable="yes">Column headers</property> <property name="use_underline">True</property> - <signal name="activate" handler="on_playlist_save_activate" last_modification_time="Sun, 09 Aug 2009 19:04:52 GMT"/> + <property name="active">False</property> + <signal name="activate" handler="on_toggle_column_headers_activate" last_modification_time="Sun, 13 Dec 2009 17:24:47 GMT"/> </widget> </child> + </widget> + </child> + </widget> + </child> - <child> - <widget class="GtkMenuItem" id="playlist_save_as"> - <property name="visible">True</property> - <property name="label" translatable="yes">Save As</property> - <property name="use_underline">True</property> - <signal name="activate" handler="on_playlist_save_as_activate" last_modification_time="Sun, 09 Aug 2009 19:04:52 GMT"/> - </widget> - </child> + <child> + <widget class="GtkMenuItem" id="playback1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Playback</property> + <property name="use_underline">True</property> + + <child> + <widget class="GtkMenu" id="playback1_menu"> <child> <widget class="GtkMenuItem" id="order1"> @@ -427,6 +467,27 @@ <signal name="activate" handler="on_scroll_follows_playback_activate" last_modification_time="Wed, 02 Sep 2009 17:46:43 GMT"/> </widget> </child> + + <child> + <widget class="GtkCheckMenuItem" id="cursor_follows_playback"> + <property name="visible">True</property> + <property name="label" translatable="yes">Cursor follows playback</property> + <property name="use_underline">True</property> + <property name="active">False</property> + <signal name="activate" handler="on_cursor_follows_playback_activate" last_modification_time="Sat, 26 Dec 2009 16:48:26 GMT"/> + </widget> + </child> + + <child> + <widget class="GtkCheckMenuItem" id="stop_after_current"> + <property name="visible">True</property> + <property name="label" translatable="yes">Stop after current</property> + <property name="use_underline">True</property> + <property name="active">False</property> + <signal name="activate" handler="on_stop_after_current_activate" last_modification_time="Sun, 20 Dec 2009 13:32:19 GMT"/> + <accelerator key="M" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> </widget> </child> </widget> @@ -458,7 +519,7 @@ <signal name="activate" handler="on_help1_activate" last_modification_time="Tue, 08 Sep 2009 17:32:06 GMT"/> <child internal-child="image"> - <widget class="GtkImage" id="image127"> + <widget class="GtkImage" id="image297"> <property name="visible">True</property> <property name="stock">gtk-help</property> <property name="icon_size">1</property> @@ -715,7 +776,7 @@ <widget class="GtkDrawingArea" id="header"> <property name="height_request">24</property> <property name="visible">True</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <signal name="expose_event" handler="on_header_expose_event" last_modification_time="Thu, 06 Aug 2009 14:54:29 GMT"/> <signal name="configure_event" handler="on_header_configure_event" last_modification_time="Thu, 06 Aug 2009 14:54:33 GMT"/> <signal name="realize" handler="on_header_realize" last_modification_time="Thu, 06 Aug 2009 14:54:41 GMT"/> @@ -840,7 +901,7 @@ <property name="events">GDK_KEY_PRESS_MASK</property> <property name="title" translatable="yes">Search</property> <property name="type">GTK_WINDOW_TOPLEVEL</property> - <property name="window_position">GTK_WIN_POS_CENTER_ALWAYS</property> + <property name="window_position">GTK_WIN_POS_NONE</property> <property name="modal">False</property> <property name="resizable">True</property> <property name="destroy_with_parent">False</property> @@ -925,7 +986,7 @@ <widget class="GtkDrawingArea" id="searchheader"> <property name="height_request">24</property> <property name="visible">True</property> - <property name="events">GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="events">GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <signal name="button_press_event" handler="on_header_button_press_event" last_modification_time="Sat, 08 Aug 2009 22:38:34 GMT"/> <signal name="button_release_event" handler="on_header_button_release_event" last_modification_time="Sat, 08 Aug 2009 22:38:38 GMT"/> <signal name="configure_event" handler="on_header_configure_event" last_modification_time="Sat, 08 Aug 2009 22:38:42 GMT"/> @@ -1340,6 +1401,7 @@ <property name="focus_on_map">True</property> <property name="urgency_hint">False</property> <signal name="key_press_event" handler="on_prefwin_key_press_event" last_modification_time="Sat, 07 Nov 2009 15:47:40 GMT"/> + <signal name="delete_event" handler="on_prefwin_delete_event" last_modification_time="Tue, 22 Dec 2009 20:16:22 GMT"/> <child> <widget class="GtkNotebook" id="notebook2"> @@ -1355,16 +1417,16 @@ <widget class="GtkTable" id="table3"> <property name="border_width">3</property> <property name="visible">True</property> - <property name="n_rows">6</property> + <property name="n_rows">7</property> <property name="n_columns">2</property> <property name="homogeneous">False</property> <property name="row_spacing">0</property> <property name="column_spacing">3</property> <child> - <widget class="GtkLabel" id="label4"> + <widget class="GtkLabel" id="label15"> <property name="visible">True</property> - <property name="label" translatable="yes">Output device</property> + <property name="label" translatable="yes">Release ALSA while stopped</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> @@ -1382,17 +1444,17 @@ <packing> <property name="left_attach">0</property> <property name="right_attach">1</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> + <property name="top_attach">6</property> + <property name="bottom_attach">7</property> <property name="x_options">fill</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label5"> + <widget class="GtkLabel" id="label9"> <property name="visible">True</property> - <property name="label" translatable="yes">Software ALSA resampling</property> + <property name="label" translatable="yes">Replaygain peak scale</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> @@ -1410,17 +1472,17 @@ <packing> <property name="left_attach">0</property> <property name="right_attach">1</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> <property name="x_options">fill</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label6"> + <widget class="GtkLabel" id="label8"> <property name="visible">True</property> - <property name="label" translatable="yes">SRC quality (libsamplerate)</property> + <property name="label" translatable="yes">Replaygain mode</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> @@ -1438,17 +1500,17 @@ <packing> <property name="left_attach">0</property> <property name="right_attach">1</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> <property name="x_options">fill</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label8"> + <widget class="GtkLabel" id="label6"> <property name="visible">True</property> - <property name="label" translatable="yes">Replaygain mode</property> + <property name="label" translatable="yes">SRC quality (libsamplerate)</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> @@ -1474,9 +1536,9 @@ </child> <child> - <widget class="GtkLabel" id="label9"> + <widget class="GtkLabel" id="label5"> <property name="visible">True</property> - <property name="label" translatable="yes">Replaygain peak scale</property> + <property name="label" translatable="yes">Software ALSA resampling</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> @@ -1494,17 +1556,17 @@ <packing> <property name="left_attach">0</property> <property name="right_attach">1</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">fill</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label15"> + <widget class="GtkLabel" id="label4"> <property name="visible">True</property> - <property name="label" translatable="yes">Release ALSA while stopped</property> + <property name="label" translatable="yes">Output device</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> @@ -1522,37 +1584,59 @@ <packing> <property name="left_attach">0</property> <property name="right_attach">1</property> - <property name="top_attach">5</property> - <property name="bottom_attach">6</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">fill</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkCheckButton" id="pref_alsa_resampling"> + <widget class="GtkLabel" id="label23"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes"></property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="label" translatable="yes">Output plugin</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkComboBox" id="pref_soundcard"> + <property name="visible">True</property> + <property name="add_tearoffs">False</property> <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - <signal name="clicked" handler="on_pref_alsa_resampling_clicked" last_modification_time="Sat, 24 Oct 2009 16:50:34 GMT"/> + <signal name="changed" handler="on_pref_soundcard_changed" last_modification_time="Sat, 07 Nov 2009 14:12:28 GMT"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> <property name="top_attach">1</property> <property name="bottom_attach">2</property> - <property name="y_options"></property> + <property name="y_options">fill</property> </packing> </child> <child> - <widget class="GtkCheckButton" id="pref_replaygain_scale"> + <widget class="GtkCheckButton" id="pref_alsa_resampling"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="label" translatable="yes"></property> @@ -1562,19 +1646,19 @@ <property name="active">False</property> <property name="inconsistent">False</property> <property name="draw_indicator">True</property> - <signal name="clicked" handler="on_pref_replaygain_scale_clicked" last_modification_time="Sat, 10 Oct 2009 18:52:10 GMT"/> + <signal name="clicked" handler="on_pref_alsa_resampling_clicked" last_modification_time="Sat, 24 Oct 2009 16:50:34 GMT"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">4</property> - <property name="bottom_attach">5</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkCheckButton" id="pref_alsa_freewhenstopped"> + <widget class="GtkCheckButton" id="pref_replaygain_scale"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="label" translatable="yes"></property> @@ -1584,7 +1668,7 @@ <property name="active">False</property> <property name="inconsistent">False</property> <property name="draw_indicator">True</property> - <signal name="clicked" handler="on_pref_alsa_freewhenstopped_clicked" last_modification_time="Sat, 07 Nov 2009 11:37:02 GMT"/> + <signal name="clicked" handler="on_pref_replaygain_scale_clicked" last_modification_time="Sat, 10 Oct 2009 18:52:10 GMT"/> </widget> <packing> <property name="left_attach">1</property> @@ -1596,18 +1680,24 @@ </child> <child> - <widget class="GtkComboBox" id="pref_soundcard"> + <widget class="GtkCheckButton" id="pref_alsa_freewhenstopped"> <property name="visible">True</property> - <property name="add_tearoffs">False</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes"></property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> <property name="focus_on_click">True</property> - <signal name="changed" handler="on_pref_soundcard_changed" last_modification_time="Sat, 07 Nov 2009 14:12:28 GMT"/> + <property name="active">False</property> + <property name="inconsistent">False</property> + <property name="draw_indicator">True</property> + <signal name="clicked" handler="on_pref_alsa_freewhenstopped_clicked" last_modification_time="Sat, 07 Nov 2009 11:37:02 GMT"/> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - <property name="y_options">fill</property> + <property name="top_attach">6</property> + <property name="bottom_attach">7</property> + <property name="y_options"></property> </packing> </child> @@ -1626,8 +1716,8 @@ linear</property> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> <property name="y_options">fill</property> </packing> </child> @@ -1645,8 +1735,25 @@ Album</property> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">3</property> - <property name="bottom_attach">4</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="y_options">fill</property> + </packing> + </child> + + <child> + <widget class="GtkComboBox" id="pref_output_plugin"> + <property name="visible">True</property> + <property name="add_tearoffs">False</property> + <property name="focus_on_click">True</property> + <signal name="changed" handler="on_pref_output_plugin_changed" last_modification_time="Fri, 11 Dec 2009 21:05:28 GMT"/> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> <property name="y_options">fill</property> </packing> </child> @@ -2012,11 +2119,13 @@ SOCKS5_HOSTNAME</property> <child> <widget class="GtkHPaned" id="hpaned1"> + <property name="border_width">3</property> <property name="visible">True</property> <property name="can_focus">True</property> <child> <widget class="GtkScrolledWindow" id="scrolledwindow2"> + <property name="border_width">3</property> <property name="width_request">280</property> <property name="visible">True</property> <property name="can_focus">True</property> @@ -2030,7 +2139,7 @@ SOCKS5_HOSTNAME</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="headers_visible">True</property> - <property name="rules_hint">False</property> + <property name="rules_hint">True</property> <property name="reorderable">False</property> <property name="enable_search">True</property> <property name="fixed_height_mode">False</property> @@ -2048,9 +2157,10 @@ SOCKS5_HOSTNAME</property> <child> <widget class="GtkTable" id="table5"> + <property name="border_width">3</property> <property name="width_request">400</property> <property name="visible">True</property> - <property name="n_rows">4</property> + <property name="n_rows">5</property> <property name="n_columns">2</property> <property name="homogeneous">False</property> <property name="row_spacing">0</property> @@ -2251,6 +2361,27 @@ SOCKS5_HOSTNAME</property> <property name="y_options"></property> </packing> </child> + + <child> + <widget class="GtkButton" id="configure_plugin"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Configure</property> + <property name="use_underline">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <signal name="clicked" handler="on_configure_plugin_clicked" last_modification_time="Tue, 01 Dec 2009 16:54:36 GMT"/> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="y_padding">3</property> + <property name="x_options"></property> + <property name="y_options"></property> + </packing> + </child> </widget> <packing> <property name="shrink">True</property> @@ -2487,4 +2618,212 @@ SOCKS5_HOSTNAME</property> </child> </widget> +<widget class="GtkWindow" id="inputformat"> + <property name="visible">True</property> + <property name="title" translatable="yes">Column Format</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_CENTER_ALWAYS</property> + <property name="modal">True</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="focus_on_map">True</property> + <property name="urgency_hint">False</property> + + <child> + <widget class="GtkVBox" id="vbox8"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkHBox" id="hbox10"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkLabel" id="label26"> + <property name="visible">True</property> + <property name="label" translatable="yes">Title:</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="titleentry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes">Custom</property> + <property name="has_frame">True</property> + <property name="invisible_char">â—</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkHBox" id="hbox9"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkLabel" id="format"> + <property name="visible">True</property> + <property name="label" translatable="yes">Format:</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="formatentry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">â—</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label25"> + <property name="visible">True</property> + <property name="label" translatable="yes">Format fields: +%a - artist +%t - title +%b - album +%n - track +%l - duration</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkHButtonBox" id="hbuttonbox1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property> + <property name="spacing">0</property> + + <child> + <widget class="GtkButton" id="button2"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <signal name="clicked" handler="on_format_cancel_clicked" last_modification_time="Wed, 23 Dec 2009 21:28:57 GMT"/> + </widget> + </child> + + <child> + <widget class="GtkButton" id="button3"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-ok</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <signal name="clicked" handler="on_format_ok_clicked" last_modification_time="Wed, 23 Dec 2009 21:28:38 GMT"/> + </widget> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + </child> +</widget> + </glade-interface> diff --git a/deadbeef.gladep b/plugins/gtkui/deadbeef.gladep index 22ea860c..6cfb1d88 100644 --- a/deadbeef.gladep +++ b/plugins/gtkui/deadbeef.gladep @@ -7,4 +7,6 @@ <source_directory></source_directory> <gnome_support>FALSE</gnome_support> <gettext_support>FALSE</gettext_support> + <output_main_file>FALSE</output_main_file> + <output_build_files>FALSE</output_build_files> </glade-project> diff --git a/drawing.h b/plugins/gtkui/drawing.h index 23c350b6..23c350b6 100644 --- a/drawing.h +++ b/plugins/gtkui/drawing.h diff --git a/plugins/gtkui/fileman.c b/plugins/gtkui/fileman.c new file mode 100644 index 00000000..d181fa1f --- /dev/null +++ b/plugins/gtkui/fileman.c @@ -0,0 +1,67 @@ +#include "../../deadbeef.h" +#include <gtk/gtk.h> +#include <stdlib.h> +#include "gtkui.h" +#include "gtkplaylist.h" + +static void +add_dirs_worker (void *data) { + GSList *lst = (GSList *)data; + gtkpl_add_dirs (&main_playlist, lst); +} + +void +gtkui_add_dirs (GSList *lst) { + deadbeef->thread_start (add_dirs_worker, lst); +} + +static void +add_files_worker (void *data) { + GSList *lst = (GSList *)data; + gtkpl_add_files (&main_playlist, lst); +} + +void +gtkui_add_files (struct _GSList *lst) { + deadbeef->thread_start (add_files_worker, lst); +} + +static void +open_files_worker (void *data) { + GSList *lst = (GSList *)data; + gtkpl_add_files (&main_playlist, lst); + gtkpl_set_cursor (PL_MAIN, 0); + deadbeef->sendmessage (M_PLAYSONG, 0, 0, 0); +} + +void +gtkui_open_files (struct _GSList *lst) { + deadbeef->pl_free (); + deadbeef->thread_start (open_files_worker, lst); +} + +struct fmdrop_data { + char *mem; + int length; + int drop_y; +}; + +static void +fmdrop_worker (void *ctx) { + struct fmdrop_data *data = (struct fmdrop_data *)ctx; + gtkpl_add_fm_dropped_files (&main_playlist, data->mem, data->length, data->drop_y); + free (data); +} + +void +gtkui_receive_fm_drop (char *mem, int length, int drop_y) { + struct fmdrop_data *data = malloc (sizeof (struct fmdrop_data)); + if (!data) { + fprintf (stderr, "gtkui_receive_fm_drop: malloc failed\n"); + return; + } + data->mem = mem; + data->length = length; + data->drop_y = drop_y; + deadbeef->thread_start (fmdrop_worker, data); +} diff --git a/gdkdrawing.c b/plugins/gtkui/gdkdrawing.c index 621ee92e..621ee92e 100644 --- a/gdkdrawing.c +++ b/plugins/gtkui/gdkdrawing.c diff --git a/gtkplaylist.c b/plugins/gtkui/gtkplaylist.c index a32e5456..bf551e5a 100644 --- a/gtkplaylist.c +++ b/plugins/gtkui/gtkplaylist.c @@ -34,22 +34,28 @@ #include "callbacks.h" #include "interface.h" #include "support.h" -#include "playlist.h" -#include "playback.h" -#include "codec.h" -#include "common.h" -#include "messagepump.h" -#include "streamer.h" #include "search.h" #include "progress.h" #include "drawing.h" #include "session.h" #include "deadbeef.h" -#include "conf.h" -#include "timeline.h" +#include "parser.h" -//#define trace(...) { fprintf(stderr, __VA_ARGS__); } -#define trace(fmt,...) +#define min(x,y) ((x)<(y)?(x):(y)) +#define max(x,y) ((x)>(y)?(x):(y)) + +#define PL_HEAD(iter) (deadbeef->pl_get_first(iter)) +#define PL_TAIL(iter) (deadbeef->pl_get_last(iter)) +#define PL_NEXT(it, iter) (deadbeef->pl_get_next(it, iter)) +#define PL_PREV(it, iter) (deadbeef->pl_get_prev(it, iter)) +#define SELECTED(it) (deadbeef->pl_is_selected(it)) +#define SELECT(it, sel) (deadbeef->pl_set_selected(it,sel)) +#define VSELECT(it, sel) {deadbeef->pl_set_selected(it,sel);gtk_pl_redraw_item_everywhere (it);} + +extern DB_functions_t *deadbeef; // defined in gtkui.c + +#define trace(...) { fprintf(stderr, __VA_ARGS__); } +//#define trace(fmt,...) // debug function for gdk_draw_drawable static inline void @@ -63,6 +69,10 @@ extern GtkWidget *mainwin; extern GtkStatusIcon *trayicon; extern gtkplaylist_t main_playlist; +extern gtkplaylist_t *last_playlist; + +static GtkWidget *theme_treeview; + // orange on dark color scheme float colo_dark_orange[COLO_COUNT][3] = { { 0x7f/255.f, 0x7f/255.f, 0x7f/255.f }, // cursor @@ -122,6 +132,7 @@ static int header_dragpt[2]; #define COLHDR_ANIM_TIME 0.2f +#if 0 typedef struct { int c1; int c2; @@ -190,6 +201,7 @@ colhdr_anim_swap (gtkplaylist_t *pl, int c1, int c2, int x1, int x2) { timeline_init (colhdr_anim.timeline, COLHDR_ANIM_TIME, 100, colhdr_anim_cb, &colhdr_anim); timeline_start (colhdr_anim.timeline); } +#endif // that must be called before gtk_init void @@ -201,14 +213,21 @@ gtkpl_init (void) { buffering16_pixbuf = draw_load_pixbuf ("buffering_16.png"); rowheight = draw_get_font_size () + 12; memcpy (colo_current, colo_white_blue, sizeof (colo_current)); + theme_treeview = gtk_tree_view_new (); + gtk_widget_show (theme_treeview); + GtkWidget *vbox1 = lookup_widget (mainwin, "vbox1"); + gtk_box_pack_start (GTK_BOX (vbox1), theme_treeview, FALSE, FALSE, 0); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (theme_treeview), TRUE); } void gtkpl_free (gtkplaylist_t *pl) { +#if 0 if (colhdr_anim.timeline) { timeline_free (colhdr_anim.timeline, 1); colhdr_anim.timeline = 0; } +#endif while (pl->columns) { gtkpl_column_t *next = pl->columns->next; gtkpl_column_free (pl->columns); @@ -235,16 +254,18 @@ void gtkpl_setup_scrollbar (gtkplaylist_t *ps) { GtkWidget *playlist = ps->playlist; int h = playlist->allocation.height / rowheight; - int size = (*ps->pcount); + int cnt = ps->get_count (); + int size = cnt; if (h >= size) { size = 0; } GtkWidget *scroll = ps->scrollbar; - if (ps->row >= (*ps->pcount)) { - ps->row = (*ps->pcount) - 1; + int row = deadbeef->pl_get_cursor (ps->iterator); + if (row >= cnt) { + row = cnt - 1; } - if (ps->scrollpos > (*ps->pcount)-ps->nvisiblerows+1) { - int n = (*ps->pcount) - ps->nvisiblerows + 1; + if (ps->scrollpos > cnt-ps->nvisiblerows+1) { + int n = cnt - ps->nvisiblerows + 1; ps->scrollpos = max (0, n); gtk_range_set_value (GTK_RANGE (scroll), ps->scrollpos); } @@ -263,11 +284,14 @@ gtkpl_setup_hscrollbar (gtkplaylist_t *ps) { GtkWidget *playlist = ps->playlist; int w = playlist->allocation.width; int size = 0; - int i; gtkpl_column_t *c; for (c = ps->columns; c; c = c->next) { size += c->width; } + ps->totalwidth = size; + if (ps->totalwidth < ps->playlist->allocation.width) { + ps->totalwidth = ps->playlist->allocation.width; + } if (w >= size) { size = 0; } @@ -288,7 +312,7 @@ gtkpl_setup_hscrollbar (gtkplaylist_t *ps) { } void -gtkpl_redraw_pl_row_novis (gtkplaylist_t *ps, int row, playItem_t *it) { +gtkpl_redraw_pl_row_novis (gtkplaylist_t *ps, int row, DB_playItem_t *it) { draw_begin ((uintptr_t)ps->backbuf); gtkpl_draw_pl_row_back (ps, row, it); if (it) { @@ -298,7 +322,7 @@ gtkpl_redraw_pl_row_novis (gtkplaylist_t *ps, int row, playItem_t *it) { } void -gtkpl_redraw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) { +gtkpl_redraw_pl_row (gtkplaylist_t *ps, int row, DB_playItem_t *it) { if (row < ps->scrollpos || row >= ps->scrollpos+ps->nvisiblerows) { return; } @@ -314,101 +338,49 @@ gtkpl_redraw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) { } void -gtkpl_draw_pl_row_back (gtkplaylist_t *ps, int row, playItem_t *it) { +gtkpl_draw_pl_row_back (gtkplaylist_t *ps, int row, DB_playItem_t *it) { // draw background - float w; - int start, end; - int startx, endx; - int width, height; - draw_get_canvas_size ((uintptr_t)ps->backbuf, &width, &height); - w = width; - if (it && ((it->selected && ps->multisel) || (row == ps->row && !ps->multisel))) { - if (row % 2) { - theme_set_fg_color (COLO_PLAYLIST_SEL_EVEN); - } - else { - theme_set_fg_color (COLO_PLAYLIST_SEL_ODD); - } - draw_rect (0, row * rowheight - ps->scrollpos * rowheight, width, rowheight, 1); + GtkWidget *treeview = theme_treeview; + if (treeview->style->depth == -1) { + return; // drawing was called too early } - else { - if (row % 2) { - theme_set_fg_color (COLO_PLAYLIST_EVEN); - } - else { - theme_set_fg_color (COLO_PLAYLIST_ODD); - } - draw_rect (0, row * rowheight - ps->scrollpos * rowheight, width, rowheight, 1); - } - if (row == ps->row) { - theme_set_fg_color (COLO_PLAYLIST_CURSOR); - draw_rect (0, row * rowheight - ps->scrollpos * rowheight, width, rowheight-1, 0); + GTK_OBJECT_FLAGS (treeview) |= GTK_HAS_FOCUS; + int x = -ps->hscrollpos; + int w = ps->totalwidth; + gtk_paint_flat_box (treeview->style, ps->backbuf, (it && SELECTED(it)) ? GTK_STATE_SELECTED : GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, treeview, (row & 1) ? "cell_even_ruled" : "cell_odd_ruled", x, row * rowheight - ps->scrollpos * rowheight, w, rowheight); + if (row == deadbeef->pl_get_cursor (ps->iterator)) { + // not all gtk engines/themes render focus rectangle in treeviews + // but we want it anyway + gdk_draw_rectangle (ps->backbuf, treeview->style->fg_gc[GTK_STATE_NORMAL], FALSE, x, row * rowheight - ps->scrollpos * rowheight, w-1, rowheight-1); + // gtkstyle focus drawing, for reference +// gtk_paint_focus (treeview->style, ps->backbuf, (it && SELECTED(it)) ? GTK_STATE_SELECTED : GTK_STATE_NORMAL, NULL, treeview, "treeview", x, row * rowheight - ps->scrollpos * rowheight, w, rowheight); } } void -gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) { +gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, DB_playItem_t *it) { if (row-ps->scrollpos >= ps->nvisiblerows || row-ps->scrollpos < 0) { // fprintf (stderr, "WARNING: attempt to draw row outside of screen bounds (%d)\n", row-ps->scrollpos); return; } int width, height; draw_get_canvas_size ((uintptr_t)ps->backbuf, &width, &height); - if (it && ((it->selected && ps->multisel) || (row == ps->row && !ps->multisel))) { - if (row % 2) { - theme_set_bg_color (COLO_PLAYLIST_SEL_EVEN); - } - else { - theme_set_bg_color (COLO_PLAYLIST_SEL_ODD); - } - theme_set_fg_color (COLO_PLAYLIST_SEL_TEXT); + if (it && SELECTED (it)) { + GdkColor *clr = &theme_treeview->style->fg[GTK_STATE_SELECTED]; + float rgb[3] = { clr->red/65535.f, clr->green/65535.f, clr->blue/65535.f }; + draw_set_fg_color (rgb); } else { - if (row % 2) { - theme_set_bg_color (COLO_PLAYLIST_EVEN); - } - else { - theme_set_bg_color (COLO_PLAYLIST_ODD); - } - theme_set_fg_color (COLO_PLAYLIST_TEXT); - } - // draw as columns - char dur[50]; - pl_format_title (it, dur, sizeof (dur), "%l"); - - const char *artist = pl_find_meta (it, "artist"); - if (!artist) { - artist = "?"; - } - const char *album = pl_find_meta (it, "album"); - if (!album) { - album = "?"; - } - const char *track = pl_find_meta (it, "track"); - if (!track) { - track = ""; + GdkColor *clr = &theme_treeview->style->fg[GTK_STATE_NORMAL]; + float rgb[3] = { clr->red/65535.f, clr->green/65535.f, clr->blue/65535.f }; + draw_set_fg_color (rgb); } - const char *title = pl_find_meta (it, "title"); - if (!title) { - title = "?"; - } - char artistalbum[1024]; - pl_format_title (it, artistalbum, sizeof (artistalbum), "%a - %b"); -#if 0 - const char *columns[pl_ncolumns] = { - "", - artistalbum, - track, - title, - dur - }; -#endif int x = -ps->hscrollpos; gtkpl_column_t *c; for (c = ps->columns; c; c = c->next) { - if (it == playlist_current_ptr && c->id == DB_COLUMN_PLAYING/* && !p_isstopped ()*/) { - int paused = p_ispaused (); - int buffering = !streamer_ok_to_read (-1); + if (it == deadbeef->pl_getcurrent () && c->id == DB_COLUMN_PLAYING) { + int paused = deadbeef->get_output ()->state () == OUTPUT_STATE_PAUSED; + int buffering = !deadbeef->streamer_ok_to_read (-1); uintptr_t pixbuf; if (paused) { pixbuf = pause16_pixbuf; @@ -422,41 +394,14 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) { draw_pixbuf ((uintptr_t)ps->backbuf, pixbuf, x + c->width/2 - 8 - ps->hscrollpos, (row - ps->scrollpos) * rowheight + rowheight/2 - 8, 0, 0, 16, 16); } else { - char fmt_text[1024]; - const char *text = NULL; - if (c->id != -1) { - switch (c->id) { - case DB_COLUMN_ARTIST_ALBUM: - text = artistalbum; - break; - case DB_COLUMN_ARTIST: - text = artist; - break; - case DB_COLUMN_ALBUM: - text = album; - break; - case DB_COLUMN_TITLE: - text = title; - break; - case DB_COLUMN_DURATION: - text = dur; - break; - case DB_COLUMN_TRACK: - text = track; - break; - } - } - else if (c->format) { - pl_format_title (it, fmt_text, sizeof (fmt_text), c->format); - text = fmt_text; + char text[1024]; + deadbeef->pl_format_title (it, text, sizeof (text), c->id, c->format); + + if (c->align_right) { + draw_text (x+5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 1, text); } - if (text) { - if (c->align_right) { - draw_text_with_colors (x+5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 1, text); - } - else { - draw_text_with_colors (x + 5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 0, text); - } + else { + draw_text (x + 5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 0, text); } } x += c->width; @@ -466,7 +411,6 @@ gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) { void gtkpl_draw_playlist (gtkplaylist_t *ps, int x, int y, int w, int h) { - GtkWidget *widget = ps->playlist; if (!ps->backbuf) { return; } @@ -476,22 +420,23 @@ gtkpl_draw_playlist (gtkplaylist_t *ps, int x, int y, int w, int h) { int row2; int row2_full; row1 = max (0, y / rowheight + ps->scrollpos); - row2 = min ((*ps->pcount), (y+h) / rowheight + ps->scrollpos + 1); + int cnt = ps->get_count (); + row2 = min (cnt, (y+h) / rowheight + ps->scrollpos + 1); row2_full = (y+h) / rowheight + ps->scrollpos + 1; // draw background - playItem_t *it = gtkpl_get_for_idx (ps, ps->scrollpos); - playItem_t *it_copy = it; + DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (ps->scrollpos, ps->iterator); + DB_playItem_t *it_copy = it; for (row = row1; row < row2_full; row++) { gtkpl_draw_pl_row_back (ps, row, it); if (it) { - it = it->next[ps->iterator]; + it = PL_NEXT (it, ps->iterator); } } it = it_copy; int idx = 0; for (row = row1; row < row2; row++, idx++) { gtkpl_draw_pl_row (ps, row, it); - it = it->next[ps->iterator]; + it = PL_NEXT (it, ps->iterator); } draw_end (); @@ -542,21 +487,16 @@ gtkpl_expose_header (gtkplaylist_t *ps, int x, int y, int w, int h) { void gtkpl_select_single (gtkplaylist_t *ps, int sel) { - if (!ps->multisel) { - return; - } int idx=0; - GtkWidget *widget = ps->playlist; - for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + DB_playItem_t *it = PL_HEAD (ps->iterator); + for (; it; it = PL_NEXT (it, ps->iterator), idx++) { if (idx == sel) { - if (!it->selected) { - it->selected = 1; - gtkpl_redraw_pl_row (ps, idx, it); + if (!SELECTED (it)) { + VSELECT (it, 1); } } - else if (it->selected) { - it->selected = 0; - gtkpl_redraw_pl_row (ps, idx, it); + else if (SELECTED (it)) { + VSELECT (it, 0); } } } @@ -565,12 +505,12 @@ gtkpl_select_single (gtkplaylist_t *ps, int sel) { // {{{ [+] if clicked unselected item: // unselect all // select clicked item -// ps->row = clicked +// deadbeef->pl_get_cursor (ps->iterator) = clicked // redraw // start 'area selection' mode // }}} // {{{ [+] if clicked selected item: -// ps->row = clicked +// deadbeef->pl_get_cursor (ps->iterator) = clicked // redraw // wait until next release or motion event, whichever is 1st // if release is 1st: @@ -590,16 +530,16 @@ static int shift_sel_anchor = -1; void gtkpl_mouse1_pressed (gtkplaylist_t *ps, int state, int ex, int ey, double time) { // cursor must be set here, but selection must be handled in keyrelease - if ((*ps->pcount) == 0) { + int cnt = ps->get_count (); + if (cnt == 0) { return; } - GtkWidget *widget = ps->playlist; // remember mouse coords for doubleclick detection ps->lastpos[0] = ex; ps->lastpos[1] = ey; // select item int y = ey/rowheight + ps->scrollpos; - if (y < 0 || y >= (*ps->pcount)) { + if (y < 0 || y >= ps->get_count ()) { y = -1; } @@ -607,21 +547,21 @@ gtkpl_mouse1_pressed (gtkplaylist_t *ps, int state, int ex, int ey, double time) && fabs(ps->lastpos[0] - ex) < 3 && fabs(ps->lastpos[1] - ey) < 3) { // doubleclick - play this item - if (ps->row != -1) { - playItem_t *it = gtkpl_get_for_idx (ps, ps->row); - it->selected = 1; - int r = pl_get_idx_of (it); - int prev = main_playlist.row; + if (deadbeef->pl_get_cursor (ps->iterator) != -1) { + DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (deadbeef->pl_get_cursor (ps->iterator), ps->iterator); +// SELECT (it, 1); + int r = deadbeef->pl_get_idx_of (it); + int prev = deadbeef->pl_get_cursor (ps->iterator); if (prev != r) { - main_playlist.row = r; + deadbeef->pl_set_cursor (ps->iterator, r); if (prev != -1) { - gtkpl_redraw_pl_row (&main_playlist, prev, pl_get_for_idx (prev)); + gtkpl_redraw_pl_row (&main_playlist, prev, deadbeef->pl_get_for_idx (prev)); } if (r != -1) { gtkpl_redraw_pl_row (&main_playlist, r, it); } } - messagepump_push (M_PLAYSONGNUM, 0, r, 0); + deadbeef->sendmessage (M_PLAYSONGNUM, 0, r, 0); return; } @@ -634,74 +574,64 @@ gtkpl_mouse1_pressed (gtkplaylist_t *ps, int state, int ex, int ey, double time) int sel = y; if (y == -1) { - y = (*ps->pcount) - 1; + y = ps->get_count () - 1; } - int prev = ps->row; - ps->row = y; - shift_sel_anchor = ps->row; + int prev = deadbeef->pl_get_cursor (ps->iterator); + deadbeef->pl_set_cursor (ps->iterator, y); + shift_sel_anchor = deadbeef->pl_get_cursor (ps->iterator); // handle multiple selection - if (ps->multisel) { - if (!(state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK))) - { - playItem_t *it = gtkpl_get_for_idx (ps, sel); - if (!it || !it->selected) { - // reset selection, and set it to single item - gtkpl_select_single (ps, sel); - areaselect = 1; - areaselect_x = ex; - areaselect_y = ey; - areaselect_dx = -1; - areaselect_dy = -1; - shift_sel_anchor = ps->row; - } - else { - dragwait = 1; - gtkpl_redraw_pl_row (ps, prev, gtkpl_get_for_idx (ps, prev)); - if (ps->row != prev) { - gtkpl_redraw_pl_row (ps, ps->row, gtkpl_get_for_idx (ps, ps->row)); - } + if (!(state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK))) + { + DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (sel, ps->iterator); + if (!it || !SELECTED (it)) { + // reset selection, and set it to single item + gtkpl_select_single (ps, sel); + areaselect = 1; + areaselect_x = ex; + areaselect_y = ey; + areaselect_dx = -1; + areaselect_dy = -1; + shift_sel_anchor = deadbeef->pl_get_cursor (ps->iterator); + } + else { + dragwait = 1; + gtkpl_redraw_pl_row (ps, prev, gtkpl_get_for_idx (ps, prev)); + if (deadbeef->pl_get_cursor (ps->iterator) != prev) { + gtkpl_redraw_pl_row (ps, deadbeef->pl_get_cursor (ps->iterator), gtkpl_get_for_idx (ps, deadbeef->pl_get_cursor (ps->iterator))); } } - else if (state & GDK_CONTROL_MASK) { - // toggle selection - if (y != -1) { - playItem_t *it = gtkpl_get_for_idx (ps, y); - if (it) { - it->selected = 1 - it->selected; - gtkpl_redraw_pl_row (ps, y, it); - } + } + else if (state & GDK_CONTROL_MASK) { + // toggle selection + if (y != -1) { + DB_playItem_t *it = gtkpl_get_for_idx (ps, y); + if (it) { + VSELECT (it, 1 - SELECTED (it)); } } - else if (state & GDK_SHIFT_MASK) { - // select range - int start = min (prev, ps->row); - int end = max (prev, ps->row); - int idx = 0; - for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { - if (idx >= start && idx <= end) { - if (!it->selected) { - it->selected = 1; - gtkpl_redraw_pl_row (ps, idx, it); - } + } + else if (state & GDK_SHIFT_MASK) { + // select range + int start = min (prev, deadbeef->pl_get_cursor (ps->iterator)); + int end = max (prev, deadbeef->pl_get_cursor (ps->iterator)); + int idx = 0; + for (DB_playItem_t *it = PL_HEAD (ps->iterator); it; it = PL_NEXT (it, ps->iterator), idx++) { + if (idx >= start && idx <= end) { + if (!SELECTED (it)) { + VSELECT (it, 1); } - else { - if (it->selected) { - it->selected = 0; - gtkpl_redraw_pl_row (ps, idx, it); - } + } + else { + if (SELECTED (it)) { + VSELECT (it, 0); } } } - if (ps->row != -1 && sel == -1) { - gtkpl_redraw_pl_row (ps, ps->row, gtkpl_get_for_idx (ps, ps->row)); - } } - else { - if (ps->row != -1) { - gtkpl_redraw_pl_row (ps, ps->row, gtkpl_get_for_idx (ps, ps->row)); - } + if (deadbeef->pl_get_cursor (ps->iterator) != -1 && sel == -1) { + gtkpl_redraw_pl_row (ps, deadbeef->pl_get_cursor (ps->iterator), gtkpl_get_for_idx (ps, deadbeef->pl_get_cursor (ps->iterator))); } - if (prev != -1 && prev != ps->row) { + if (prev != -1 && prev != deadbeef->pl_get_cursor (ps->iterator)) { gtkpl_redraw_pl_row (ps, prev, gtkpl_get_for_idx (ps, prev)); } @@ -781,11 +711,10 @@ gtkpl_scroll_playlist_cb (gpointer data) { playlist_scroll_active = 0; return FALSE; } - if (sc >= *ps->pcount) { + if (sc >= ps->get_count ()) { playlist_scroll_active = 0; return FALSE; } - GDK_THREADS_ENTER (); gtk_range_set_value (GTK_RANGE (ps->scrollbar), sc); if (playlist_scroll_mode == 0) { GdkEventMotion ev; @@ -795,7 +724,6 @@ gtkpl_scroll_playlist_cb (gpointer data) { else if (playlist_scroll_mode == 1) { gtkpl_track_dragdrop (ps, playlist_scroll_pointer_y); } - GDK_THREADS_LEAVE (); scroll_sleep_time -= 0.1; if (scroll_sleep_time < 0.05) { scroll_sleep_time = 0.05; @@ -819,23 +747,20 @@ gtkpl_mousemove (gtkplaylist_t *ps, GdkEventMotion *event) { } } else if (areaselect) { - GtkWidget *widget = ps->playlist; int y = event->y/rowheight + ps->scrollpos; //if (y != shift_sel_anchor) { int start = min (y, shift_sel_anchor); int end = max (y, shift_sel_anchor); int idx=0; - for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + for (DB_playItem_t *it = PL_HEAD(ps->iterator); it; it = PL_NEXT(it, ps->iterator), idx++) { if (idx >= start && idx <= end) { - if (!it->selected) { - it->selected = 1; - gtkpl_redraw_pl_row (ps, idx, it); + if (!SELECTED (it)) { + VSELECT (it, 1); } } - else if (it->selected) { - it->selected = 0; - gtkpl_redraw_pl_row (ps, idx, it); + else if (SELECTED (it)) { + VSELECT (it, 0); } } } @@ -876,7 +801,7 @@ gtkpl_handle_scroll_event (gtkplaylist_t *ps, int direction) { GtkWidget *range = ps->scrollbar;; GtkWidget *playlist = ps->playlist; int h = playlist->allocation.height / rowheight; - int size = (*ps->pcount); + int size = ps->get_count (); if (h >= size) { size = 0; } @@ -884,7 +809,6 @@ gtkpl_handle_scroll_event (gtkplaylist_t *ps, int direction) { return; } // pass event to scrollbar - GtkAdjustment* adj = gtk_range_get_adjustment (GTK_RANGE (range)); int newscroll = gtk_range_get_value (GTK_RANGE (range)); if (direction == GDK_SCROLL_UP) { newscroll -= 2; @@ -942,26 +866,16 @@ gtkpl_hscroll (gtkplaylist_t *ps, int newscroll) { } void -gtkpl_randomsong (void) { - p_stop (); - pl_randomsong (); -} - -void -gtkpl_playsongnum (int idx) { - p_stop (); - streamer_set_nextsong (idx, 1); -} - -void gtkpl_songchanged (gtkplaylist_t *ps, int from, int to) { if (!dragwait && to != -1) { - GtkWidget *widget = ps->playlist; - if (conf_get_int ("playlist.scroll.followplayback", 0)) { + if (deadbeef->conf_get_int ("playlist.scroll.followplayback", 0)) { if (to < ps->scrollpos || to >= ps->scrollpos + ps->nvisiblefullrows) { gtk_range_set_value (GTK_RANGE (ps->scrollbar), to - ps->nvisiblerows/2); } } + if (deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 1)) { + gtkpl_set_cursor (PL_MAIN, to); + } } if (from >= 0) { @@ -974,155 +888,83 @@ gtkpl_songchanged (gtkplaylist_t *ps, int from, int to) { void gtkpl_keypress (gtkplaylist_t *ps, int keyval, int state) { - GtkWidget *widget = ps->playlist; GtkWidget *range = ps->scrollbar; - int prev = ps->row; + int prev = deadbeef->pl_get_cursor (ps->iterator); int newscroll = ps->scrollpos; -// C-f is now handled by gtk -// if ((keyval == GDK_F || keyval == GDK_f) && (state & GDK_CONTROL_MASK)) { -// search_start (); -// } -// else -// if ((keyval == GDK_A || keyval == GDK_a) && (state & GDK_CONTROL_MASK)) { -// // select all -// pl_select_all (); -// gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); -// draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, 0, 0, 0, 0, widget->allocation.width, widget->allocation.height); -// return; -// } -// else if ((keyval == GDK_P || keyval == GDK_p) && (state & GDK_CONTROL_MASK)) { -// messagepump_push (M_PAUSESONG, 0, 0, 0); -// } -// else -// if (keyval == GDK_Return && ps->row != -1) { -// messagepump_push (M_PLAYSONGNUM, 0, ps->row, 0); -// return; -// } -// else -// if (keyval == GDK_Delete) { -// pl_delete_selected (); -// playlist_refresh (); -// return; -// } -// else - if (keyval == GDK_Down && ps->row < (*ps->pcount) - 1) { - ps->row++; - if (ps->row > ps->scrollpos + widget->allocation.height / rowheight - 1) { - newscroll = ps->row - widget->allocation.height / rowheight + 1; + if (keyval == GDK_Down) { + int cursor = deadbeef->pl_get_cursor (ps->iterator); + if (cursor < ps->get_count () - 1) { + gtkpl_set_cursor (ps->iterator, cursor+1); } } - else if (keyval == GDK_r) { - extern int replaygain; - replaygain = 1-replaygain; - fprintf (stderr, "replaygain=%d\n", replaygain); - } - else if (keyval == GDK_t) { - extern int replaygain_scale; - replaygain_scale = 1-replaygain_scale; - fprintf (stderr, "replaygain_scale=%d\n", replaygain_scale); - } - else if (keyval == GDK_Up && ps->row > 0) { - ps->row--; - if (ps->row < ps->scrollpos) { - newscroll = ps->row; + else if (keyval == GDK_Up) { + int cursor = deadbeef->pl_get_cursor (ps->iterator); + if (cursor > 0) { + gtkpl_set_cursor (ps->iterator, cursor-1); } } - else if (keyval == GDK_Page_Down && ps->row < (*ps->pcount) - 1) { - ps->row += 10; - if (ps->row >= (*ps->pcount)) { - ps->row = (*ps->pcount) - 1; - } - if (ps->row > ps->scrollpos + widget->allocation.height / rowheight - 1) { - newscroll = ps->row - widget->allocation.height / rowheight + 1; + else if (keyval == GDK_Page_Down) { + int cursor = deadbeef->pl_get_cursor (ps->iterator); + if (cursor < ps->get_count () - 1) { + cursor += 10; + if (cursor >= ps->get_count ()) { + cursor = ps->get_count () - 1; + } + gtkpl_set_cursor (ps->iterator, cursor); } } - else if (keyval == GDK_Page_Up && ps->row > 0) { - ps->row -= 10; - if (ps->row < 0) { - ps->row = 0; - } - if (ps->row < ps->scrollpos) { - newscroll = ps->row; + else if (keyval == GDK_Page_Up) { + int cursor = deadbeef->pl_get_cursor (ps->iterator); + if (cursor > 0) { + cursor -= 10; + if (cursor < 0) { + cursor = 0; + } + gtkpl_set_cursor (ps->iterator, cursor); } } - else if (keyval == GDK_End && ps->row != (*ps->pcount) - 1) { - ps->row = (*ps->pcount) - 1; - if (ps->row > ps->scrollpos + widget->allocation.height / rowheight - 1) { - newscroll = ps->row - widget->allocation.height / rowheight + 1; + else if (keyval == GDK_End) { + int cursor = deadbeef->pl_get_cursor (ps->iterator); + if (cursor != ps->get_count () - 1) { + gtkpl_set_cursor (ps->iterator, ps->get_count () - 1); } } - else if (keyval == GDK_Home && ps->row != 0) { - ps->row = 0; - if (ps->row < ps->scrollpos) { - newscroll = ps->row; + else if (keyval == GDK_Home) { + int cursor = deadbeef->pl_get_cursor (ps->iterator); + if (cursor != 0) { + gtkpl_set_cursor (ps->iterator, 0); } } if (state & GDK_SHIFT_MASK) { - // select all between shift_sel_anchor and ps->row - if (prev != ps->row) { - int minvis = ps->scrollpos; - int maxvis = ps->scrollpos + ps->nvisiblerows-1; - int start = min (ps->row, shift_sel_anchor); - int end = max (ps->row, shift_sel_anchor); + // select all between shift_sel_anchor and deadbeef->pl_get_cursor (ps->iterator) + if (prev != deadbeef->pl_get_cursor (ps->iterator)) { + int start = min (deadbeef->pl_get_cursor (ps->iterator), shift_sel_anchor); + int end = max (deadbeef->pl_get_cursor (ps->iterator), shift_sel_anchor); int idx=0; - for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + for (DB_playItem_t *it = PL_HEAD (ps->iterator); it; it = PL_NEXT(it,ps->iterator), idx++) { if (idx >= start && idx <= end) { - it->selected = 1; - if (idx >= minvis && idx <= maxvis) { - if (newscroll == ps->scrollpos) { - gtkpl_redraw_pl_row (ps, idx, it); - } - else { - gtkpl_redraw_pl_row_novis (ps, idx, it); - } - } + VSELECT (it, 1); } - else if (it->selected) + else if (SELECTED (it)) { - it->selected = 0; - if (idx >= minvis && idx <= maxvis) { - if (newscroll == ps->scrollpos) { - gtkpl_redraw_pl_row (ps, idx, it); - } - else { - gtkpl_redraw_pl_row_novis (ps, idx, it); - } - } + VSELECT (it, 0); } } } } else { // reset selection, set new single cursor/selection - if (prev != ps->row) { - int minvis = ps->scrollpos; - int maxvis = ps->scrollpos + ps->nvisiblerows-1; - shift_sel_anchor = ps->row; + if (prev != deadbeef->pl_get_cursor (ps->iterator)) { + shift_sel_anchor = deadbeef->pl_get_cursor (ps->iterator); int idx=0; - for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { - if (idx == ps->row) { - if (!it->selected) { - it->selected = 1; - if (idx >= minvis && idx <= maxvis) { - if (newscroll == ps->scrollpos) { - gtkpl_redraw_pl_row (ps, idx, it); - } - else { - gtkpl_redraw_pl_row_novis (ps, idx, it); - } - } + for (DB_playItem_t *it = PL_HEAD (ps->iterator); it; it = PL_NEXT(it, ps->iterator), idx++) { + if (idx == deadbeef->pl_get_cursor (ps->iterator)) { + if (!SELECTED (it)) { + VSELECT (it, 1); } } - else if (it->selected) { - it->selected = 0; - if (idx >= minvis && idx <= maxvis) { - if (newscroll == ps->scrollpos) { - gtkpl_redraw_pl_row (ps, idx, it); - } - else { - gtkpl_redraw_pl_row_novis (ps, idx, it); - } - } + else if (SELECTED (it)) { + VSELECT (it, 0); } } } @@ -1183,71 +1025,6 @@ gtkpl_track_dragdrop (gtkplaylist_t *ps, int y) { } void -gtkpl_handle_drag_drop (gtkplaylist_t *ps, int drop_y, uint32_t *d, int length) { - int drop_row = drop_y / rowheight + ps->scrollpos; - playItem_t *drop_before = gtkpl_get_for_idx (ps, drop_row); - while (drop_before && drop_before->selected) { - drop_before = drop_before->next[ps->iterator]; - } - // unlink items from playlist, and link together - playItem_t *head = NULL; - playItem_t *tail = NULL; - int processed = 0; - int idx = 0; - playItem_t *next = NULL; - for (playItem_t *it = playlist_head[ps->iterator]; it && processed < length; it = next, idx++) { - next = it->next[ps->iterator]; - if (idx == d[processed]) { - if (it->prev[ps->iterator]) { - it->prev[ps->iterator]->next[ps->iterator] = it->next[ps->iterator]; - } - else { - playlist_head[ps->iterator] = it->next[ps->iterator]; - } - if (it->next[ps->iterator]) { - it->next[ps->iterator]->prev[ps->iterator] = it->prev[ps->iterator]; - } - else { - playlist_tail[ps->iterator] = it->prev[ps->iterator]; - } - if (tail) { - tail->next[ps->iterator] = it; - it->prev[ps->iterator] = tail; - tail = it; - } - else { - head = tail = it; - it->prev[ps->iterator] = it->next[ps->iterator] = NULL; - } - processed++; - } - } - // find insertion point - playItem_t *drop_after = NULL; - if (drop_before) { - drop_after = drop_before->prev[ps->iterator]; - } - else { - drop_after = playlist_tail[ps->iterator]; - } - // insert in between - head->prev[ps->iterator] = drop_after; - if (drop_after) { - drop_after->next[ps->iterator] = head; - } - else { - playlist_head[ps->iterator] = head; - } - tail->next[ps->iterator] = drop_before; - if (drop_before) { - drop_before->prev[ps->iterator] = tail; - } - else { - playlist_tail[ps->iterator] = tail; - } -} - -void on_playlist_drag_end (GtkWidget *widget, GdkDragContext *drag_context, gpointer user_data) @@ -1302,39 +1079,47 @@ strcopy_special (char *dest, const char *src, int len) { *dest = 0; } +static gboolean +set_progress_text_idle (gpointer data) { + const char *text = (const char *)data; + progress_settext (text); + return FALSE; +} + int -gtkpl_add_file_info_cb (playItem_t *it, void *data) { +gtkpl_add_file_info_cb (DB_playItem_t *it, void *data) { if (progress_is_aborted ()) { return -1; } - GDK_THREADS_ENTER(); - progress_settext (it->fname); - GDK_THREADS_LEAVE(); -#if 0 - GtkEntry *e = (GtkEntry *)data; - GDK_THREADS_ENTER(); - gtk_entry_set_text (GTK_ENTRY (e), it->fname); - GDK_THREADS_LEAVE(); - usleep (100); - countdown = 10; -#endif + g_idle_add (set_progress_text_idle, it->fname); return 0; } +static gboolean +progress_show_idle (gpointer data) { + progress_show (); + return FALSE; +} + +static gboolean +progress_hide_idle (gpointer data) { + progress_hide (); + playlist_refresh (); + return FALSE; +} + void gtkpl_add_fm_dropped_files (gtkplaylist_t *ps, char *ptr, int length, int drop_y) { - GDK_THREADS_ENTER(); - progress_show (); - GDK_THREADS_LEAVE(); + g_idle_add (progress_show_idle, NULL); int drop_row = drop_y / rowheight + ps->scrollpos; - playItem_t *drop_before = gtkpl_get_for_idx (ps, drop_row); - playItem_t *after = NULL; + DB_playItem_t *drop_before = deadbeef->pl_get_for_idx_and_iter (drop_row, ps->iterator); + DB_playItem_t *after = NULL; if (drop_before) { - after = drop_before->prev[ps->iterator]; + after = PL_PREV (drop_before, ps->iterator); } else { - after = playlist_tail[ps->iterator]; + after = PL_TAIL (ps->iterator); } const uint8_t *p = (const uint8_t*)ptr; while (*p) { @@ -1348,9 +1133,9 @@ gtkpl_add_fm_dropped_files (gtkplaylist_t *ps, char *ptr, int length, int drop_y //strncpy (fname, p, pe - p); //fname[pe - p] = 0; int abort = 0; - playItem_t *inserted = pl_insert_dir (after, fname, &abort, gtkpl_add_file_info_cb, NULL); + DB_playItem_t *inserted = deadbeef->pl_insert_dir (after, fname, &abort, gtkpl_add_file_info_cb, NULL); if (!inserted && !abort) { - inserted = pl_insert_file (after, fname, &abort, gtkpl_add_file_info_cb, NULL); + inserted = deadbeef->pl_insert_file (after, fname, &abort, gtkpl_add_file_info_cb, NULL); } if (inserted) { after = inserted; @@ -1364,20 +1149,7 @@ gtkpl_add_fm_dropped_files (gtkplaylist_t *ps, char *ptr, int length, int drop_y } free (ptr); - GDK_THREADS_ENTER(); - progress_hide (); - playlist_refresh (); - GDK_THREADS_LEAVE(); -} - -void -gtkpl_handle_fm_drag_drop (gtkplaylist_t *ps, int drop_y, void *ptr, int length) { - // this happens when dropped from file manager - char *mem = malloc (length+1); - memcpy (mem, ptr, length); - mem[length] = 0; - // we don't pass control structure, but there's only one drag-drop view currently - messagepump_push (M_FMDRAGDROP, (uintptr_t)mem, length, drop_y); + g_idle_add (progress_hide_idle, NULL); } void @@ -1399,6 +1171,7 @@ gtkpl_header_draw (gtkplaylist_t *ps) { for (c = ps->columns; c; c = c->next, idx++) { w = c->width; int xx = x; +#if 0 if (colhdr_anim.anim_active) { if (idx == colhdr_anim.c2) { xx = colhdr_anim.ax1; @@ -1407,16 +1180,29 @@ gtkpl_header_draw (gtkplaylist_t *ps) { xx = colhdr_anim.ax2; } } +#endif if (header_dragging < 0 || idx != header_dragging) { if (xx >= widget->allocation.width) { continue; } + int arrow_sz = 10; if (w > 0) { gtk_paint_vline (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, NULL, NULL, NULL, 2, h-4, xx+w - 2); GdkColor *gdkfg = &widget->style->fg[0]; float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff}; draw_set_fg_color (fg); - draw_text (xx + 5, h/2-draw_get_font_size()/2, c->width-10, 0, c->title); + int w = c->width-10; + if (c->sort_order) { + w -= arrow_sz; + if (w < 0) { + w = 0; + } + } + draw_text (xx + 5, h/2-draw_get_font_size()/2, w, 0, c->title); + } + if (c->sort_order != 0) { + int dir = c->sort_order == 1 ? GTK_ARROW_DOWN : GTK_ARROW_UP; + gtk_paint_arrow (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, widget, NULL, dir, TRUE, xx + c->width-arrow_sz-5, widget->allocation.height/2-arrow_sz/2, arrow_sz, arrow_sz); } } else { @@ -1430,6 +1216,7 @@ gtkpl_header_draw (gtkplaylist_t *ps) { for (c = ps->columns; c; c = c->next, idx++) { w = c->width; if (idx == header_dragging) { +#if 0 if (colhdr_anim.anim_active) { if (idx == colhdr_anim.c2) { x = colhdr_anim.ax1; @@ -1438,6 +1225,7 @@ gtkpl_header_draw (gtkplaylist_t *ps) { x = colhdr_anim.ax2; } } +#endif // draw empty slot if (x < widget->allocation.width) { gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_ACTIVE, GTK_SHADOW_ETCHED_IN, NULL, widget, "button", x, 0, w, h); @@ -1500,8 +1288,9 @@ on_header_realize (GtkWidget *widget, cursor_drag = gdk_cursor_new (GDK_FLEUR); } -float last_header_motion_ev = -1; -int prev_header_x = -1; +static float last_header_motion_ev = -1; //is it subject to remove? +static int prev_header_x = -1; +static int header_prepare = 0; gboolean on_header_motion_notify_event (GtkWidget *widget, @@ -1509,12 +1298,30 @@ on_header_motion_notify_event (GtkWidget *widget, gpointer user_data) { GTKPL_PROLOGUE; - if (header_dragging >= 0) { + int ev_x, ev_y; + GdkModifierType ev_state; + + if (event->is_hint) + gdk_window_get_pointer (event->window, &ev_x, &ev_y, &ev_state); + else + { + ev_x = event->x; + ev_y = event->y; + ev_state = event->state; + } + + + if ((ev_state & GDK_BUTTON1_MASK) && header_prepare) { + if (gtk_drag_check_threshold (widget, ev_x, prev_header_x, 0, 0)) { + header_prepare = 0; + } + } + if (!header_prepare && header_dragging >= 0) { gdk_window_set_cursor (widget->window, cursor_drag); gtkpl_column_t *c; int i; for (i = 0, c = ps->columns; i < header_dragging && c; c = c->next, i++); - c->movepos = event->x - header_dragpt[0]; + c->movepos = ev_x - header_dragpt[0]; // find closest column to the left int inspos = -1; @@ -1533,8 +1340,6 @@ on_header_motion_notify_event (GtkWidget *widget, x += cc->width; } if (inspos >= 0 && inspos != header_dragging) { - int c1 = inspos; - int c2 = header_dragging; // remove c from list if (c == ps->columns) { ps->columns = c->next; @@ -1580,7 +1385,7 @@ on_header_motion_notify_event (GtkWidget *widget, } else if (header_sizing >= 0) { last_header_motion_ev = event->time; - prev_header_x = event->x; + prev_header_x = ev_x; gdk_window_set_cursor (widget->window, cursor_sz); // get column start pos int x = -ps->hscrollpos; @@ -1590,7 +1395,7 @@ on_header_motion_notify_event (GtkWidget *widget, x += c->width; } - int newx = event->x > x + MIN_COLUMN_WIDTH ? event->x : x + MIN_COLUMN_WIDTH; + int newx = ev_x > x + MIN_COLUMN_WIDTH ? ev_x : x + MIN_COLUMN_WIDTH; c->width = newx - x; gtkpl_setup_hscrollbar (ps); gtkpl_header_draw (ps); @@ -1605,7 +1410,7 @@ on_header_motion_notify_event (GtkWidget *widget, for (c = ps->columns; c; c = c->next) { int w = c->width; if (w > 0) { // ignore collapsed columns (hack for search window) - if (event->x >= x + w - 2 && event->x <= x + w) { + if (ev_x >= x + w - 2 && ev_x <= x + w) { gdk_window_set_cursor (widget->window, cursor_sz); break; } @@ -1622,6 +1427,20 @@ on_header_motion_notify_event (GtkWidget *widget, return FALSE; } +gtkpl_column_t* +gtkpl_get_column_for_click (gtkplaylist_t *pl, int click_x) { + int x = -pl->hscrollpos; + gtkpl_column_t *c; + for (c = pl->columns; c; c = c->next) { + int w = c->width; + if (click_x >= x && click_x < x + w) { + return c; + } + x += w; + } + return NULL; +} + gboolean on_header_button_press_event (GtkWidget *widget, GdkEventButton *event, @@ -1645,14 +1464,23 @@ on_header_button_press_event (GtkWidget *widget, break; } else if (event->x > x + 2 && event->x < x + w - 2) { + // prepare to drag or sort header_dragpt[0] = event->x - x; + header_prepare = 1; header_dragging = i; header_sizing = -1; + prev_header_x = event->x; break; } x += w; } } + else if (event->button == 3) { + ps->active_column = gtkpl_get_column_for_click (ps, event->x); + GtkWidget *menu = create_headermenu (); + last_playlist = ps; + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, widget, 3, gtk_get_current_event_time()); + } prev_header_x = -1; last_header_motion_ev = -1; return FALSE; @@ -1665,110 +1493,110 @@ on_header_button_release_event (GtkWidget *widget, { GTKPL_PROLOGUE; if (event->button == 1) { - header_sizing = -1; - int x = 0; - gtkpl_column_t *c; - for (c = ps->columns; c; c = c->next) { - int w = c->width; - if (event->x >= x + w - 2 && event->x <= x + w) { - gdk_window_set_cursor (widget->window, cursor_sz); - break; - } - else { - gdk_window_set_cursor (widget->window, NULL); - } - x += w; - } - if (header_dragging >= 0) { + if (header_prepare) { + header_sizing = -1; header_dragging = -1; - gtkpl_setup_hscrollbar (ps); + header_prepare = 0; + // sort + gtkpl_column_t *c; + int i = 0; + int x = -ps->hscrollpos; + int sorted = 0; + for (c = ps->columns; c; c = c->next, i++) { + int w = c->width; + if (event->x > x + 2 && event->x < x + w - 2) { + if (!c->sort_order) { + c->sort_order = 1; + } + else if (c->sort_order == 1) { + c->sort_order = 2; + } + else if (c->sort_order == 2) { + c->sort_order = 1; + } + deadbeef->pl_sort (ps == &main_playlist ? PL_MAIN : PL_SEARCH, c->id, c->format, c->sort_order-1); + sorted = 1; + } + else { + c->sort_order = 0; + } + x += w; + } + playlist_refresh (); gtkpl_header_draw (ps); gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); - gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); - gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); - gtkpl_column_rewrite_config (ps); + } + else { + header_sizing = -1; + int x = 0; + gtkpl_column_t *c; + for (c = ps->columns; c; c = c->next) { + int w = c->width; + if (event->x >= x + w - 2 && event->x <= x + w) { + gdk_window_set_cursor (widget->window, cursor_sz); + break; + } + else { + gdk_window_set_cursor (widget->window, NULL); + } + x += w; + } + if (header_dragging >= 0) { + header_dragging = -1; + gtkpl_setup_hscrollbar (ps); + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_column_rewrite_config (ps); + } } } -// NOTE: disabled for 0.3.0 release -// else if (event->button == 3) { -// GtkWidget *menu = create_headermenu (); -// gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, widget, 0, gtk_get_current_event_time()); -// } return FALSE; } void gtkpl_add_dir (gtkplaylist_t *ps, char *folder) { - GDK_THREADS_ENTER(); - progress_show (); - GDK_THREADS_LEAVE(); - pl_add_dir (folder, gtkpl_add_file_info_cb, NULL); + g_idle_add (progress_show_idle, NULL); + deadbeef->pl_add_dir (folder, gtkpl_add_file_info_cb, NULL); g_free (folder); - GDK_THREADS_ENTER(); - progress_hide (); - playlist_refresh (); - GDK_THREADS_LEAVE(); + g_idle_add (progress_hide_idle, NULL); } static void gtkpl_adddir_cb (gpointer data, gpointer userdata) { - pl_add_dir (data, gtkpl_add_file_info_cb, userdata); + deadbeef->pl_add_dir (data, gtkpl_add_file_info_cb, userdata); g_free (data); } void gtkpl_add_dirs (gtkplaylist_t *ps, GSList *lst) { - GDK_THREADS_ENTER(); - progress_show (); - GDK_THREADS_LEAVE(); + g_idle_add (progress_show_idle, NULL); g_slist_foreach(lst, gtkpl_adddir_cb, NULL); g_slist_free (lst); - GDK_THREADS_ENTER(); - progress_hide (); - playlist_refresh (); - GDK_THREADS_LEAVE(); + g_idle_add (progress_hide_idle, NULL); } static void gtkpl_addfile_cb (gpointer data, gpointer userdata) { - pl_add_file (data, gtkpl_add_file_info_cb, userdata); + deadbeef->pl_add_file (data, gtkpl_add_file_info_cb, userdata); g_free (data); } void gtkpl_add_files (gtkplaylist_t *ps, GSList *lst) { - GDK_THREADS_ENTER(); - progress_show (); - GDK_THREADS_LEAVE(); + g_idle_add (progress_show_idle, NULL); g_slist_foreach(lst, gtkpl_addfile_cb, NULL); g_slist_free (lst); - GDK_THREADS_ENTER(); - progress_hide (); - playlist_refresh (); - GDK_THREADS_LEAVE(); -} - -void -gtkpl_playsong (gtkplaylist_t *ps) { - if (p_ispaused ()) { - p_unpause (); - } - else if (ps->row != -1) { - p_stop (); - streamer_set_nextsong (ps->row, 1); - } - else { - p_stop (); - streamer_set_nextsong (0, 1); - } + g_idle_add (progress_hide_idle, NULL); } int -gtkpl_get_idx_of (gtkplaylist_t *ps, playItem_t *it) { - playItem_t *c = playlist_head[ps->iterator]; +gtkpl_get_idx_of (gtkplaylist_t *ps, DB_playItem_t *it) { + DB_playItem_t *c = PL_HEAD(ps->iterator); int idx = 0; while (c && c != it) { - c = c->next[ps->iterator]; + c = PL_NEXT(c, ps->iterator);; idx++; } if (!c) { @@ -1777,13 +1605,13 @@ gtkpl_get_idx_of (gtkplaylist_t *ps, playItem_t *it) { return idx; } -playItem_t * +DB_playItem_t * gtkpl_get_for_idx (gtkplaylist_t *ps, int idx) { - playItem_t *it = playlist_head[ps->iterator]; + DB_playItem_t *it = PL_HEAD(ps->iterator); while (idx--) { if (!it) return NULL; - it = it->next[ps->iterator]; + it = PL_NEXT(it, ps->iterator); } return it; } @@ -1862,122 +1690,47 @@ gtkpl_column_remove (gtkplaylist_t *pl, gtkpl_column_t *c) { void gtkpl_append_column_from_textdef (gtkplaylist_t *pl, const uint8_t *def) { // syntax: "title" "format" id width alignright - char title[128]; - char format[128]; + char token[MAX_TOKEN]; + const char *p = def; + char title[MAX_TOKEN]; int id; + char fmt[MAX_TOKEN]; int width; - int align_right; - // title - if (*def != '"') { - return; - } - def++; - if (*def == 0) { - return; - } - const uint8_t *e = def; - e++; - while (*e && *e != '"') { - e++; - } - if (*e == 0) { - return; - } - memcpy (title, def, e-def); - title[e-def] = 0; - // skip whitespace - def = e; - def++; - while (*def && *def <= ' ') { - def++; - } - if (*def == 0) { - return; - } - // format - if (*def != '"') { - return; - } - def++; - if (*def == 0) { - return; - } - e = def; - while (*e && *e != '"') { - e++; - } - if (*e == 0) { - return; - } - memcpy (format, def, e-def); - format[e-def] = 0; - // skip whitespace - def = e; - def++; - while (*def && *def <= ' ') { - def++; - } - if (*def == 0) { + int align; + + parser_init (); + + p = gettoken_warn_eof (p, token); + if (!p) { return; } - // id - e = def; - while (*e && (isdigit (*e) || *e == '-')) { - e++; - } - if (*e == 0) { + strcpy (title, token); + + p = gettoken_warn_eof (p, token); + if (!p) { return; } - { - char s[e-def+1]; - memcpy (s, def, e-def); - s[e-def] = 0; - id = atoi (s); - } - // skip whitespace - def = e; - def++; - while (*def && *def <= ' ') { - def++; - } - if (*def == 0) { + strcpy (fmt, token); + + p = gettoken_warn_eof (p, token); + if (!p) { return; } - // width - e = def; - while (*e && isdigit (*e)) { - e++; - } - if (*e == 0) { + id = atoi (token); + + p = gettoken_warn_eof (p, token); + if (!p) { return; } - { - char s[e-def+1]; - memcpy (s, def, e-def); - s[e-def] = 0; - width = atoi (s); - } - // skip whitespace - def = e; - def++; - while (*def && *def <= ' ') { - def++; - } - if (*def == 0) { + width = atoi (token); + + p = gettoken_warn_eof (p, token); + if (!p) { return; } - // align_right - e = def; - while (*e && isdigit (*e)) { - e++; - } - { - char s[e-def+1]; - memcpy (s, def, e-def); - s[e-def] = 0; - align_right = atoi (s); - } - gtkpl_column_append (pl, gtkpl_column_alloc (title, width, id, format[0] ? format : NULL, align_right)); + align = atoi (token); + + gtkpl_column_append (pl, gtkpl_column_alloc (title, width, id, fmt, align)); } void @@ -1986,22 +1739,19 @@ gtkpl_column_update_config (gtkplaylist_t *pl, gtkpl_column_t *c, int idx) { char value[128]; snprintf (key, sizeof (key), "%s.column.%d", pl->title, idx); snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", c->title, c->format ? c->format : "", c->id, c->width, c->align_right); - conf_set_str (key, value); + deadbeef->conf_set_str (key, value); } void gtkpl_column_rewrite_config (gtkplaylist_t *pl) { char key[128]; - char value[128]; snprintf (key, sizeof (key), "%s.column.", pl->title); - conf_remove_items (key); + deadbeef->conf_remove_items (key); gtkpl_column_t *c; int i = 0; for (c = pl->columns; c; c = c->next, i++) { - snprintf (key, sizeof (key), "%s.column.%d", pl->title, i); - snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", c->title, c->format ? c->format : "", c->id, c->width, c->align_right); - conf_set_str (key, value); + gtkpl_column_update_config (pl, c, i); } } @@ -2015,22 +1765,35 @@ set_tray_tooltip (const char *text) { } void -gtkpl_current_track_changed (playItem_t *it) { +gtkpl_current_track_changed (DB_playItem_t *it) { char str[600]; - char dname[512]; - pl_format_item_display_name (it, dname, 512); - snprintf (str, 600, "DeaDBeeF - %s", dname); + if (it) { + char dname[512]; + deadbeef->pl_format_item_display_name (it, dname, 512); + snprintf (str, sizeof (str), "DeaDBeeF - %s", dname); + } + else { + strcpy (str, "DeaDBeeF"); + } gtk_window_set_title (GTK_WINDOW (mainwin), str); set_tray_tooltip (str); } -void -gtkpl_songchanged_wrapper (int from, int to) { - GDK_THREADS_ENTER (); +struct fromto_t { + int from; + int to; +}; + +static gboolean +update_win_title_idle (gpointer data) { + struct fromto_t *ft = (struct fromto_t *)data; + int from = ft->from; + int to = ft->to; + free (ft); // update window title if (from >= 0 || to >= 0) { if (to >= 0) { - playItem_t *it = pl_get_for_idx (to); + DB_playItem_t *it = deadbeef->pl_get_for_idx (to); if (it) { // it might have been deleted after event was sent gtkpl_current_track_changed (it); } @@ -2042,5 +1805,121 @@ gtkpl_songchanged_wrapper (int from, int to) { } // update playlist view gtkpl_songchanged (&main_playlist, from, to); - GDK_THREADS_LEAVE (); + return FALSE; +} + +static gboolean +redraw_seekbar_cb (gpointer nothing) { + void seekbar_draw (GtkWidget *widget); + void seekbar_expose (GtkWidget *widget, int x, int y, int w, int h); + GtkWidget *widget = lookup_widget (mainwin, "seekbar"); + seekbar_draw (widget); + seekbar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height); + return FALSE; +} + +void +redraw_queued_tracks (gtkplaylist_t *pl) { + DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (pl->scrollpos, pl->iterator); + int i = pl->scrollpos; + while (it && i < pl->scrollpos + pl->nvisiblerows) { + if (deadbeef->pl_playqueue_test (it) != -1) { + gtkpl_redraw_pl_row (pl, i, it); + } + it = PL_NEXT (it, pl->iterator); + i++; + } +} + +static gboolean +redraw_queued_tracks_cb (gpointer nothing) { + redraw_queued_tracks (&main_playlist); + redraw_queued_tracks (&search_playlist); + return FALSE; +} + +void +gtkpl_songchanged_wrapper (int from, int to) { + struct fromto_t *ft = malloc (sizeof (struct fromto_t)); + ft->from = from; + ft->to = to; + g_idle_add (update_win_title_idle, ft); + if (ft->to == -1) { + // redraw seekbar + g_idle_add (redraw_seekbar_cb, NULL); + } + g_idle_add (redraw_queued_tracks_cb, NULL); +} + +void +gtk_pl_redraw_item_everywhere (DB_playItem_t *it) { + gtkplaylist_t *pl = &search_playlist; + int idx = gtkpl_get_idx_of (pl, it); + int minvis = pl->scrollpos; + int maxvis = pl->scrollpos + pl->nvisiblerows-1; + if (idx >= minvis && idx <= maxvis) { + gtkpl_redraw_pl_row (pl, idx, it); + } + pl = &main_playlist; + idx = gtkpl_get_idx_of (pl, it); + minvis = pl->scrollpos; + maxvis = pl->scrollpos + pl->nvisiblerows-1; + if (idx >= minvis && idx <= maxvis) { + gtkpl_redraw_pl_row (pl, idx, it); + } +} + +struct set_cursor_t { + int iter; + int cursor; + int prev; + gtkplaylist_t *pl; +}; + +static gboolean +gtkpl_set_cursor_cb (gpointer data) { + struct set_cursor_t *sc = (struct set_cursor_t *)data; + deadbeef->pl_set_cursor (sc->iter, sc->cursor); + gtkpl_select_single (sc->pl, sc->cursor); + DB_playItem_t *it; + int minvis = sc->pl->scrollpos; + int maxvis = sc->pl->scrollpos + sc->pl->nvisiblerows-1; + if (sc->prev >= minvis && sc->prev <= maxvis) { + it = deadbeef->pl_get_for_idx_and_iter (sc->prev, sc->iter); + gtkpl_redraw_pl_row (sc->pl, sc->prev, it); + } + if (sc->cursor >= minvis && sc->cursor <= maxvis) { + it = deadbeef->pl_get_for_idx_and_iter (sc->cursor, sc->iter); + gtkpl_redraw_pl_row (sc->pl, sc->cursor, it); + } + + int newscroll = sc->pl->scrollpos; + if (sc->cursor < sc->pl->scrollpos) { + newscroll = sc->cursor; + } + else if (sc->cursor >= sc->pl->scrollpos + sc->pl->nvisiblefullrows) { + newscroll = sc->cursor - sc->pl->nvisiblefullrows + 1; + if (newscroll < 0) { + newscroll = 0; + } + } + if (sc->pl->scrollpos != newscroll) { + GtkWidget *range = sc->pl->scrollbar; + gtk_range_set_value (GTK_RANGE (range), newscroll); + } + + free (data); + return FALSE; +} + +void +gtkpl_set_cursor (int iter, int cursor) { + gtkplaylist_t *pl = (iter == PL_MAIN) ? &main_playlist : &search_playlist; + int prev = deadbeef->pl_get_cursor (iter); + struct set_cursor_t *data = malloc (sizeof (struct set_cursor_t)); + data->prev = prev; + data->iter = iter; + data->cursor = cursor; + data->pl = pl; + g_idle_add (gtkpl_set_cursor_cb, data); } diff --git a/gtkplaylist.h b/plugins/gtkui/gtkplaylist.h index 2e827839..ebf27f99 100644 --- a/gtkplaylist.h +++ b/plugins/gtkui/gtkplaylist.h @@ -21,7 +21,7 @@ #include <gtk/gtk.h> #include <stdint.h> #include <assert.h> -#include "playlist.h" +#include "../../deadbeef.h" // drag and drop targets enum { @@ -57,7 +57,7 @@ typedef struct gtkpl_column_s { int movepos; // valid only while `moving' is 1 struct gtkpl_column_s *next; unsigned align_right : 1; -// unsigned moving : 1; + unsigned sort_order : 2; // 0=none, 1=asc, 2=desc } gtkpl_column_t; // structure of this kind must be set as user data for playlist, header and scrollbar widgets @@ -69,27 +69,27 @@ typedef struct { GtkWidget *header; GtkWidget *scrollbar; GtkWidget *hscrollbar; + int totalwidth; // width of playlist, including invisible part GdkPixmap *backbuf; GdkPixmap *backbuf_header; const char *title; // unique id, used for config writing, etc // parameters - playItem_t **pcurr; // pointer to current item - int *pcount; // pointer to count of items in list - int iterator; // index into next array of playItem_t struct + int (*get_count)(void); // function pointer to get number of tracks + int iterator; // index into next array of DB_playItem_t struct int lastpos[2]; // last mouse position (for playlist widget) - int multisel; // if it uses multiple selection // current state int scrollpos; int hscrollpos; - int row; double clicktime; // for doubleclick detection int nvisiblerows; int nvisiblefullrows; -// int *colwidths;//[pl_ncolumns]; // current column widths -// int ncolumns; gtkpl_column_t *columns; + gtkpl_column_t *active_column; } gtkplaylist_t; +extern gtkplaylist_t main_playlist; +extern gtkplaylist_t search_playlist; + #define GTKPL_PROLOGUE \ gtkplaylist_t *ps = (gtkplaylist_t *)gtk_object_get_data (GTK_OBJECT (widget), "ps"); assert (ps); @@ -103,19 +103,19 @@ void gtkpl_free (gtkplaylist_t *pl); void -gtkpl_redraw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it); +gtkpl_redraw_pl_row (gtkplaylist_t *ps, int row, DB_playItem_t *it); void -gtkpl_redraw_pl_row_novis (gtkplaylist_t *ps, int row, playItem_t *it); +gtkpl_redraw_pl_row_novis (gtkplaylist_t *ps, int row, DB_playItem_t *it); void gtkpl_setup_scrollbar (gtkplaylist_t *ps); void -gtkpl_draw_pl_row_back (gtkplaylist_t *ps, int row, playItem_t *it); +gtkpl_draw_pl_row_back (gtkplaylist_t *ps, int row, DB_playItem_t *it); void -gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it); +gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, DB_playItem_t *it); void gtkpl_draw_playlist (gtkplaylist_t *ps, int x, int y, int w, int h); @@ -151,15 +151,6 @@ void gtkpl_track_dragdrop (gtkplaylist_t *ps, int y); void -gtkpl_handle_drag_drop (gtkplaylist_t *ps, int drop_y, uint32_t *d, int length); - -void -gtkpl_handle_fm_drag_drop (gtkplaylist_t *ps, int drop_y, void *ptr, int length); - -void -gtkpl_add_fm_dropped_files (gtkplaylist_t *ps, char *ptr, int length, int drop_y); - -void gtkpl_select_single (gtkplaylist_t *ps, int sel); void @@ -178,14 +169,14 @@ void gtkpl_configure (gtkplaylist_t *ps); int -gtkpl_get_idx_of (gtkplaylist_t *ps, playItem_t *it); +gtkpl_get_idx_of (gtkplaylist_t *ps, DB_playItem_t *it); -playItem_t * +DB_playItem_t * gtkpl_get_for_idx (gtkplaylist_t *ps, int idx); -// this functions take value from passed playlist, that's why it's here -void -gtkpl_playsong (gtkplaylist_t *ps); +//// this functions take value from passed playlist, that's why it's here +//void +//gtkpl_playsong (gtkplaylist_t *ps); void gtkpl_songchanged (gtkplaylist_t *ps, int from, int to); @@ -256,6 +247,15 @@ void gtkpl_songchanged_wrapper (int from, int to); void -gtkpl_current_track_changed (playItem_t *it); +gtkpl_current_track_changed (DB_playItem_t *it); + +void +gtk_pl_redraw_item_everywhere (DB_playItem_t *it); + +void +gtkpl_set_cursor (int iter, int cursor); + +gtkpl_column_t* +gtkpl_get_column_for_click (gtkplaylist_t *pl, int click_x); #endif // __GTKPLAYLIST_H diff --git a/plugins/gtkui/gtkui.c b/plugins/gtkui/gtkui.c new file mode 100644 index 00000000..85c819e7 --- /dev/null +++ b/plugins/gtkui/gtkui.c @@ -0,0 +1,518 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#include "../../deadbeef.h" +#include <gtk/gtk.h> +#include <string.h> +#include <stdlib.h> +#include "gtkplaylist.h" +#include "search.h" +#include "progress.h" +#include "interface.h" +#include "callbacks.h" +#include "support.h" + +//#define trace(...) { fprintf(stderr, __VA_ARGS__); } +#define trace(fmt,...) + +static DB_gui_t plugin; +DB_functions_t *deadbeef; + +static intptr_t gtk_tid; + +// main widgets +GtkWidget *mainwin; +GtkWidget *searchwin; +GtkStatusIcon *trayicon; +GtkWidget *traymenu; + +// playlist configuration structures +gtkplaylist_t main_playlist; +gtkplaylist_t search_playlist; + +// update status bar and window title +static int sb_context_id = -1; +static char sb_text[512]; +static float last_songpos = -1; + +static gboolean +update_songinfo (gpointer ctx) { + char sbtext_new[512] = "-"; + float songpos = last_songpos; + + float pl_totaltime = deadbeef->pl_get_totaltime (); + int daystotal = (int)pl_totaltime / (3600*24); + int hourtotal = ((int)pl_totaltime / 3600) % 24; + int mintotal = ((int)pl_totaltime/60) % 60; + int sectotal = ((int)pl_totaltime) % 60; + + char totaltime_str[512] = ""; + if (daystotal == 0) + snprintf (totaltime_str, sizeof (totaltime_str), "%d:%02d:%02d", hourtotal, mintotal, sectotal); + + else if (daystotal == 1) + snprintf (totaltime_str, sizeof (totaltime_str), "1 day %d:%02d:%02d", hourtotal, mintotal, sectotal); + + else + snprintf (totaltime_str, sizeof (totaltime_str), "%d days %d:%02d:%02d", daystotal, hourtotal, mintotal, sectotal); + + + + DB_playItem_t *track = deadbeef->streamer_get_playing_track (); + float duration = deadbeef->pl_get_item_duration (track); + + if (deadbeef->get_output ()->state () == OUTPUT_STATE_STOPPED) { + snprintf (sbtext_new, sizeof (sbtext_new), "Stopped | %d tracks | %s total playtime", deadbeef->pl_getcount (), totaltime_str); + songpos = 0; + } + else if (track->decoder) { +// codec_lock (); + DB_decoder_t *c = track->decoder; + float playpos = deadbeef->streamer_get_playpos (); + int minpos = playpos / 60; + int secpos = playpos - minpos * 60; + int mindur = duration / 60; + int secdur = duration - mindur * 60; + + const char *mode = c->info.channels == 1 ? "Mono" : "Stereo"; + int samplerate = c->info.samplerate; + int bitspersample = c->info.bps; + songpos = playpos; +// codec_unlock (); + + char t[100]; + if (duration >= 0) { + snprintf (t, sizeof (t), "%d:%02d", mindur, secdur); + } + else { + strcpy (t, "-:--"); + } + + char sbitrate[20] = ""; +#if 1 + int bitrate = deadbeef->streamer_get_apx_bitrate (); + if (bitrate > 0) { + snprintf (sbitrate, sizeof (sbitrate), "| %d kbps ", bitrate); + } +#endif + const char *spaused = deadbeef->get_output ()->state () == OUTPUT_STATE_PAUSED ? "Paused | " : ""; + snprintf (sbtext_new, sizeof (sbtext_new), "%s%s %s| %dHz | %d bit | %s | %d:%02d / %s | %d tracks | %s total playtime", spaused, track->filetype ? track->filetype:"-", sbitrate, samplerate, bitspersample, mode, minpos, secpos, t, deadbeef->pl_getcount (), totaltime_str); + } + + if (strcmp (sbtext_new, sb_text)) { + strcpy (sb_text, sbtext_new); + + // form statusline + // FIXME: don't update if window is not visible + GtkStatusbar *sb = GTK_STATUSBAR (lookup_widget (mainwin, "statusbar")); + if (sb_context_id == -1) { + sb_context_id = gtk_statusbar_get_context_id (sb, "msg"); + } + + gtk_statusbar_pop (sb, sb_context_id); + gtk_statusbar_push (sb, sb_context_id, sb_text); + } + + if (songpos != last_songpos) { + void seekbar_draw (GtkWidget *widget); + void seekbar_expose (GtkWidget *widget, int x, int y, int w, int h); + if (mainwin) { + GtkWidget *widget = lookup_widget (mainwin, "seekbar"); + // translate volume to seekbar pixels + songpos /= duration; + songpos *= widget->allocation.width; + if (songpos != last_songpos) { + seekbar_draw (widget); + seekbar_expose (widget, 0, 0, widget->allocation.width, widget->allocation.height); + last_songpos = songpos; + } + } + } + return FALSE; +} + +gboolean +on_trayicon_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + gpointer user_data) +{ + float vol = deadbeef->volume_get_db (); + if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_RIGHT) { + vol += 1; + } + else if (event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_LEFT) { + vol -= 1; + } + if (vol > 0) { + vol = 0; + } + else if (vol < -60) { + vol = -60; + } + deadbeef->volume_set_db (vol); + GtkWidget *volumebar = lookup_widget (mainwin, "volumebar"); + volumebar_draw (volumebar); + volumebar_expose (volumebar, 0, 0, volumebar->allocation.width, volumebar->allocation.height); + return FALSE; +} + +#if GTK_MINOR_VERSION<=14 + +gboolean +on_trayicon_activate (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + if (GTK_WIDGET_VISIBLE (mainwin)) { + gtk_widget_hide (mainwin); + } + else { + int x = deadbeef->conf_get_int ("mainwin.geometry.x", 40); + int y = deadbeef->conf_get_int ("mainwin.geometry.y", 40); + int w = deadbeef->conf_get_int ("mainwin.geometry.w", 500); + int h = deadbeef->conf_get_int ("mainwin.geometry.h", 300); + gtk_widget_show (mainwin); + gtk_window_move (mainwin, x, y); + gtk_window_resize (mainwin, w, h); + if (deadbeef->conf_get_int ("mainwin.geometry.maximized", 0)) { + gtk_window_maximize (GTK_WINDOW (mainwin)); + } + gtk_window_present (GTK_WINDOW (mainwin)); + } + return FALSE; +} + +#else + +gboolean +on_trayicon_button_press_event (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + if (event->button == 1) { + if (GTK_WIDGET_VISIBLE (mainwin)) { + gtk_widget_hide (mainwin); + } + else { + gtk_widget_show (mainwin); + int x = deadbeef->conf_get_int ("mainwin.geometry.x", 40); + int y = deadbeef->conf_get_int ("mainwin.geometry.y", 40); + int w = deadbeef->conf_get_int ("mainwin.geometry.w", 500); + int h = deadbeef->conf_get_int ("mainwin.geometry.h", 300); + gtk_window_move (GTK_WINDOW (mainwin), x, y); + gtk_window_resize (GTK_WINDOW (mainwin), w, h); + if (deadbeef->conf_get_int ("mainwin.geometry.maximized", 0)) { + gtk_window_maximize (GTK_WINDOW (mainwin)); + } + gtk_window_present (GTK_WINDOW (mainwin)); + } + } + else if (event->button == 2) { + deadbeef->sendmessage (M_PAUSESONG, 0, 0, 0); + } + return FALSE; +} +#endif + +gboolean +on_trayicon_popup_menu (GtkWidget *widget, + guint button, + guint time, + gpointer user_data) +{ + gtk_menu_popup (GTK_MENU (traymenu), NULL, NULL, gtk_status_icon_position_menu, trayicon, button, time); + return FALSE; +} + +static gboolean +activate_cb (gpointer nothing) { + gtk_widget_show (mainwin); + gtk_window_present (GTK_WINDOW (mainwin)); + return FALSE; +} + +static int +gtkui_on_activate (DB_event_t *ev, uintptr_t data) { + g_idle_add (activate_cb, NULL); + return 0; +} + +static int +gtkui_on_songchanged (DB_event_trackchange_t *ev, uintptr_t data) { + gtkpl_songchanged_wrapper (ev->from, ev->to); + return 0; +} + +struct trackinfo_t { + int index; + DB_playItem_t *track; +}; + +static gboolean +trackinfochanged_cb (gpointer data) { + struct trackinfo_t *ti = (struct trackinfo_t *)data; + gtkpl_redraw_pl_row (&main_playlist, ti->index, ti->track); + if (ti->track == deadbeef->pl_getcurrent ()) { + gtkpl_current_track_changed (ti->track); + } + free (ti); + return FALSE; +} + +static int +gtkui_on_trackinfochanged (DB_event_track_t *ev, uintptr_t data) { + struct trackinfo_t *ti = malloc (sizeof (struct trackinfo_t)); + ti->index = ev->index; + ti->track = ev->track; + g_idle_add (trackinfochanged_cb, ti); + return 0; +} + +static gboolean +paused_cb (gpointer nothing) { + DB_playItem_t *curr = deadbeef->pl_getcurrent (); + if (curr) { + int idx = deadbeef->pl_get_idx_of (curr); + gtkpl_redraw_pl_row (&main_playlist, idx, curr); + } + return FALSE; +} + +static int +gtkui_on_paused (DB_event_state_t *ev, uintptr_t data) { + g_idle_add (paused_cb, NULL); +} + +static gboolean +playlistchanged_cb (gpointer none) { + playlist_refresh (); + search_refresh (); + return FALSE; +} + +static int +gtkui_on_playlistchanged (DB_event_t *ev, uintptr_t data) { + g_idle_add (playlistchanged_cb, NULL); +} + +static int +gtkui_on_frameupdate (DB_event_t *ev, uintptr_t data) { + g_idle_add (update_songinfo, NULL); +} + +static int +gtkui_on_volumechanged (DB_event_t *ev, uintptr_t data) { + void volumebar_notify_changed (void); // FIXME: do it properly + volumebar_notify_changed (); + return 0; +} + +static int +gtkui_on_configchanged (DB_event_t *ev, uintptr_t data) { + // order and looping + const char *w; + + // order + const char *orderwidgets[3] = { "order_linear", "order_shuffle", "order_random" }; + w = orderwidgets[deadbeef->conf_get_int ("playback.order", PLAYBACK_ORDER_LINEAR)]; + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE); + + // looping + const char *loopingwidgets[3] = { "loop_all", "loop_disable", "loop_single" }; + w = loopingwidgets[deadbeef->conf_get_int ("playback.loop", PLAYBACK_MODE_LOOP_ALL)]; + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, w)), TRUE); + + // scroll follows playback + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "scroll_follows_playback")), deadbeef->conf_get_int ("playlist.scroll.followplayback", 0) ? TRUE : FALSE); + + // cursor follows playback + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "cursor_follows_playback")), deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 0) ? TRUE : FALSE); + + // stop after current + int stop_after_current = deadbeef->conf_get_int ("playlist.stop_after_current", 0); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (lookup_widget (mainwin, "stop_after_current")), stop_after_current ? TRUE : FALSE); + return 0; +} + +static gboolean +outputchanged_cb (gpointer nothing) { + void preferences_fill_soundcards (void); + preferences_fill_soundcards (); + return FALSE; +} + +static int +gtkui_on_outputchanged (DB_event_t *ev, uintptr_t nothing) { + g_idle_add (outputchanged_cb, NULL); + return 0; +} + + +void +gtkui_thread (void *ctx) { + // let's start some gtk + g_thread_init (NULL); + add_pixmap_directory (PREFIX "/share/deadbeef/pixmaps"); + gdk_threads_init (); + gdk_threads_enter (); + gtk_set_locale (); + int argc = 1; + char **argv = alloca (sizeof (char *)); + argv[0] = "deadbeef"; + gtk_init (&argc, (char ***)&argv); + + // system tray icon + traymenu = create_traymenu (); + GdkPixbuf *trayicon_pixbuf = create_pixbuf ("play_24.png"); + trayicon = gtk_status_icon_new_from_pixbuf (trayicon_pixbuf); + set_tray_tooltip ("DeaDBeeF"); + //gtk_status_icon_set_title (GTK_STATUS_ICON (trayicon), "DeaDBeeF"); +#if GTK_MINOR_VERSION <= 14 + g_signal_connect ((gpointer)trayicon, "activate", G_CALLBACK (on_trayicon_activate), NULL); +#else + g_signal_connect ((gpointer)trayicon, "scroll_event", G_CALLBACK (on_trayicon_scroll_event), NULL); + g_signal_connect ((gpointer)trayicon, "button_press_event", G_CALLBACK (on_trayicon_button_press_event), NULL); +#endif + g_signal_connect ((gpointer)trayicon, "popup_menu", G_CALLBACK (on_trayicon_popup_menu), NULL); + + mainwin = create_mainwin (); + gtkpl_init (); + + GdkPixbuf *mainwin_icon_pixbuf; + mainwin_icon_pixbuf = create_pixbuf ("play_24.png"); + if (mainwin_icon_pixbuf) + { + gtk_window_set_icon (GTK_WINDOW (mainwin), mainwin_icon_pixbuf); + gdk_pixbuf_unref (mainwin_icon_pixbuf); + } + { + int x = deadbeef->conf_get_int ("mainwin.geometry.x", 40); + int y = deadbeef->conf_get_int ("mainwin.geometry.y", 40); + int w = deadbeef->conf_get_int ("mainwin.geometry.w", 500); + int h = deadbeef->conf_get_int ("mainwin.geometry.h", 300); + gtk_window_move (GTK_WINDOW (mainwin), x, y); + gtk_window_resize (GTK_WINDOW (mainwin), w, h); + if (deadbeef->conf_get_int ("mainwin.geometry.maximized", 0)) { + gtk_window_maximize (GTK_WINDOW (mainwin)); + } + } + + gtkui_on_configchanged (NULL, 0); + + // visibility of statusbar and headers + GtkWidget *header_mi = lookup_widget (mainwin, "view_headers"); + GtkWidget *sb_mi = lookup_widget (mainwin, "view_status_bar"); + GtkWidget *header = lookup_widget (mainwin, "header"); + GtkWidget *sb = lookup_widget (mainwin, "statusbar"); + if (deadbeef->conf_get_int ("gtkui.headers.visible", 1)) { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (header_mi), TRUE); + } + else { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (header_mi), FALSE); + gtk_widget_hide (header); + } + if (deadbeef->conf_get_int ("gtkui.statusbar.visible", 1)) { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (sb_mi), TRUE); + } + else { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (sb_mi), FALSE); + gtk_widget_hide (sb); + } + + searchwin = create_searchwin (); + gtk_window_set_transient_for (GTK_WINDOW (searchwin), GTK_WINDOW (mainwin)); + extern void main_playlist_init (GtkWidget *widget); + main_playlist_init (lookup_widget (mainwin, "playlist")); + extern void search_playlist_init (GtkWidget *widget); + search_playlist_init (lookup_widget (searchwin, "searchlist")); + + progress_init (); + gtk_widget_show (mainwin); + + gtk_main (); + gdk_threads_leave (); +} + +static int +gtkui_start (void) { + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_ACTIVATE, DB_CALLBACK (gtkui_on_activate), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGCHANGED, DB_CALLBACK (gtkui_on_songchanged), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_TRACKINFOCHANGED, DB_CALLBACK (gtkui_on_trackinfochanged), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_PAUSED, DB_CALLBACK (gtkui_on_paused), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_PLAYLISTCHANGED, DB_CALLBACK (gtkui_on_playlistchanged), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_FRAMEUPDATE, DB_CALLBACK (gtkui_on_frameupdate), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_VOLUMECHANGED, DB_CALLBACK (gtkui_on_volumechanged), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_CONFIGCHANGED, DB_CALLBACK (gtkui_on_configchanged), 0); + deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_OUTPUTCHANGED, DB_CALLBACK (gtkui_on_outputchanged), 0); + // gtk must be running in separate thread + gtk_tid = deadbeef->thread_start (gtkui_thread, NULL); + + return 0; +} + +static gboolean +quit_gtk_cb (gpointer nothing) { + gtk_main_quit (); + return FALSE; +} + +static int +gtkui_stop (void) { + trace ("unsubscribing events\n"); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_ACTIVATE, DB_CALLBACK (gtkui_on_activate), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGCHANGED, DB_CALLBACK (gtkui_on_songchanged), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_TRACKINFOCHANGED, DB_CALLBACK (gtkui_on_trackinfochanged), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_PAUSED, DB_CALLBACK (gtkui_on_paused), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_PLAYLISTCHANGED, DB_CALLBACK (gtkui_on_playlistchanged), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_FRAMEUPDATE, DB_CALLBACK (gtkui_on_frameupdate), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_VOLUMECHANGED, DB_CALLBACK (gtkui_on_volumechanged), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_CONFIGCHANGED, DB_CALLBACK (gtkui_on_configchanged), 0); + deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_OUTPUTCHANGED, DB_CALLBACK (gtkui_on_outputchanged), 0); + trace ("quitting gtk\n"); + g_idle_add (quit_gtk_cb, NULL); + trace ("waiting for gtk thread to finish\n"); + deadbeef->thread_join (gtk_tid); + trace ("gtk thread finished\n"); + gtk_tid = 0; + gtkpl_free (&main_playlist); + gtkpl_free (&search_playlist); + trace ("gtkui_stop completed\n"); + return 0; +} + +DB_plugin_t * +gtkui_load (DB_functions_t *api) { + deadbeef = api; + return DB_PLUGIN (&plugin); +} + +// define plugin interface +static DB_gui_t plugin = { + DB_PLUGIN_SET_API_VERSION + .plugin.version_major = 0, + .plugin.version_minor = 1, + .plugin.nostop = 1, + .plugin.type = DB_PLUGIN_MISC, + .plugin.name = "Standard GTK2 user interface", + .plugin.descr = "Default DeaDBeeF GUI", + .plugin.author = "Alexey Yakovenko", + .plugin.email = "waker@users.sourceforge.net", + .plugin.website = "http://deadbeef.sf.net", + .plugin.start = gtkui_start, + .plugin.stop = gtkui_stop +}; diff --git a/plugins/gtkui/gtkui.h b/plugins/gtkui/gtkui.h new file mode 100644 index 00000000..ae88ecff --- /dev/null +++ b/plugins/gtkui/gtkui.h @@ -0,0 +1,20 @@ +#ifndef __GTKUI_H +#define __GTKUI_H + +extern DB_functions_t *deadbeef; + +struct _GSList; + +void +gtkui_add_dirs (struct _GSList *lst); + +void +gtkui_add_files (struct _GSList *lst); + +void +gtkui_open_files (struct _GSList *lst); + +void +gtkui_receive_fm_drop (char *mem, int length, int drop_y); + +#endif diff --git a/interface.c b/plugins/gtkui/interface.c index 5c674a58..cfd805a4 100644 --- a/interface.c +++ b/plugins/gtkui/interface.c @@ -35,36 +35,41 @@ create_mainwin (void) GtkWidget *menuitem1; GtkWidget *menuitem1_menu; GtkWidget *open; - GtkWidget *image120; + GtkWidget *image290; GtkWidget *separator2; GtkWidget *add_files; - GtkWidget *image121; + GtkWidget *image291; GtkWidget *add_folders; - GtkWidget *image122; + GtkWidget *image292; GtkWidget *add_audio_cd; - GtkWidget *image123; + GtkWidget *image293; GtkWidget *add_location1; GtkWidget *separatormenuitem1; + GtkWidget *playlist_load; + GtkWidget *playlist_save; + GtkWidget *playlist_save_as; + GtkWidget *separator8; GtkWidget *quit; - GtkWidget *image124; + GtkWidget *image294; GtkWidget *edit1; GtkWidget *edit1_menu; GtkWidget *clear1; - GtkWidget *image125; + GtkWidget *image295; GtkWidget *select_all1; GtkWidget *selection1; GtkWidget *selection1_menu; GtkWidget *remove1; - GtkWidget *image126; + GtkWidget *image296; GtkWidget *crop1; GtkWidget *find1; GtkWidget *separator5; GtkWidget *preferences; - GtkWidget *playlist1; - GtkWidget *playlist1_menu; - GtkWidget *playlist_load; - GtkWidget *playlist_save; - GtkWidget *playlist_save_as; + GtkWidget *view1; + GtkWidget *view1_menu; + GtkWidget *view_status_bar; + GtkWidget *view_headers; + GtkWidget *playback1; + GtkWidget *playback1_menu; GtkWidget *order1; GtkWidget *order1_menu; GSList *order_linear_group = NULL; @@ -78,11 +83,13 @@ create_mainwin (void) GtkWidget *loop_single; GtkWidget *loop_disable; GtkWidget *scroll_follows_playback; + GtkWidget *cursor_follows_playback; + GtkWidget *stop_after_current; GtkWidget *menuitem4; GtkWidget *menuitem4_menu; GtkWidget *about1; GtkWidget *help1; - GtkWidget *image127; + GtkWidget *image297; GtkWidget *hbox2; GtkWidget *hbox3; GtkWidget *stopbtn; @@ -137,9 +144,9 @@ create_mainwin (void) GDK_O, (GdkModifierType) GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); - image120 = gtk_image_new_from_stock ("gtk-open", GTK_ICON_SIZE_MENU); - gtk_widget_show (image120); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (open), image120); + image290 = gtk_image_new_from_stock ("gtk-open", GTK_ICON_SIZE_MENU); + gtk_widget_show (image290); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (open), image290); separator2 = gtk_separator_menu_item_new (); gtk_widget_show (separator2); @@ -150,25 +157,25 @@ create_mainwin (void) gtk_widget_show (add_files); gtk_container_add (GTK_CONTAINER (menuitem1_menu), add_files); - image121 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU); - gtk_widget_show (image121); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_files), image121); + image291 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU); + gtk_widget_show (image291); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_files), image291); add_folders = gtk_image_menu_item_new_with_mnemonic ("Add folder(s)"); gtk_widget_show (add_folders); gtk_container_add (GTK_CONTAINER (menuitem1_menu), add_folders); - image122 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU); - gtk_widget_show (image122); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_folders), image122); + image292 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU); + gtk_widget_show (image292); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_folders), image292); add_audio_cd = gtk_image_menu_item_new_with_mnemonic ("Add Audio CD"); gtk_widget_show (add_audio_cd); gtk_container_add (GTK_CONTAINER (menuitem1_menu), add_audio_cd); - image123 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU); - gtk_widget_show (image123); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_audio_cd), image123); + image293 = gtk_image_new_from_stock ("gtk-add", GTK_ICON_SIZE_MENU); + gtk_widget_show (image293); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (add_audio_cd), image293); add_location1 = gtk_menu_item_new_with_mnemonic ("Add location"); gtk_widget_show (add_location1); @@ -179,6 +186,23 @@ create_mainwin (void) gtk_container_add (GTK_CONTAINER (menuitem1_menu), separatormenuitem1); gtk_widget_set_sensitive (separatormenuitem1, FALSE); + playlist_load = gtk_menu_item_new_with_mnemonic ("Load playlist"); + gtk_widget_show (playlist_load); + gtk_container_add (GTK_CONTAINER (menuitem1_menu), playlist_load); + + playlist_save = gtk_menu_item_new_with_mnemonic ("Save playlist"); + gtk_widget_show (playlist_save); + gtk_container_add (GTK_CONTAINER (menuitem1_menu), playlist_save); + + playlist_save_as = gtk_menu_item_new_with_mnemonic ("Save playlist as"); + gtk_widget_show (playlist_save_as); + gtk_container_add (GTK_CONTAINER (menuitem1_menu), playlist_save_as); + + separator8 = gtk_separator_menu_item_new (); + gtk_widget_show (separator8); + gtk_container_add (GTK_CONTAINER (menuitem1_menu), separator8); + gtk_widget_set_sensitive (separator8, FALSE); + quit = gtk_image_menu_item_new_with_mnemonic ("_Quit"); gtk_widget_show (quit); gtk_container_add (GTK_CONTAINER (menuitem1_menu), quit); @@ -186,11 +210,11 @@ create_mainwin (void) GDK_Q, (GdkModifierType) GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); - image124 = gtk_image_new_from_stock ("gtk-quit", GTK_ICON_SIZE_MENU); - gtk_widget_show (image124); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (quit), image124); + image294 = gtk_image_new_from_stock ("gtk-quit", GTK_ICON_SIZE_MENU); + gtk_widget_show (image294); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (quit), image294); - edit1 = gtk_menu_item_new_with_mnemonic ("Edit"); + edit1 = gtk_menu_item_new_with_mnemonic ("_Edit"); gtk_widget_show (edit1); gtk_container_add (GTK_CONTAINER (menubar1), edit1); @@ -201,9 +225,9 @@ create_mainwin (void) gtk_widget_show (clear1); gtk_container_add (GTK_CONTAINER (edit1_menu), clear1); - image125 = gtk_image_new_from_stock ("gtk-clear", GTK_ICON_SIZE_MENU); - gtk_widget_show (image125); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (clear1), image125); + image295 = gtk_image_new_from_stock ("gtk-clear", GTK_ICON_SIZE_MENU); + gtk_widget_show (image295); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (clear1), image295); select_all1 = gtk_menu_item_new_with_mnemonic ("Select all"); gtk_widget_show (select_all1); @@ -226,9 +250,9 @@ create_mainwin (void) GDK_Delete, (GdkModifierType) 0, GTK_ACCEL_VISIBLE); - image126 = gtk_image_new_from_stock ("gtk-remove", GTK_ICON_SIZE_MENU); - gtk_widget_show (image126); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (remove1), image126); + image296 = gtk_image_new_from_stock ("gtk-remove", GTK_ICON_SIZE_MENU); + gtk_widget_show (image296); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (remove1), image296); crop1 = gtk_menu_item_new_with_mnemonic ("Crop"); gtk_widget_show (crop1); @@ -250,28 +274,31 @@ create_mainwin (void) gtk_widget_show (preferences); gtk_container_add (GTK_CONTAINER (edit1_menu), preferences); - playlist1 = gtk_menu_item_new_with_mnemonic ("Playlist"); - gtk_widget_show (playlist1); - gtk_container_add (GTK_CONTAINER (menubar1), playlist1); + view1 = gtk_menu_item_new_with_mnemonic ("_View"); + gtk_widget_show (view1); + gtk_container_add (GTK_CONTAINER (menubar1), view1); - playlist1_menu = gtk_menu_new (); - gtk_menu_item_set_submenu (GTK_MENU_ITEM (playlist1), playlist1_menu); + view1_menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (view1), view1_menu); - playlist_load = gtk_menu_item_new_with_mnemonic ("Load"); - gtk_widget_show (playlist_load); - gtk_container_add (GTK_CONTAINER (playlist1_menu), playlist_load); + view_status_bar = gtk_check_menu_item_new_with_mnemonic ("Status bar"); + gtk_widget_show (view_status_bar); + gtk_container_add (GTK_CONTAINER (view1_menu), view_status_bar); - playlist_save = gtk_menu_item_new_with_mnemonic ("Save"); - gtk_widget_show (playlist_save); - gtk_container_add (GTK_CONTAINER (playlist1_menu), playlist_save); + view_headers = gtk_check_menu_item_new_with_mnemonic ("Column headers"); + gtk_widget_show (view_headers); + gtk_container_add (GTK_CONTAINER (view1_menu), view_headers); - playlist_save_as = gtk_menu_item_new_with_mnemonic ("Save As"); - gtk_widget_show (playlist_save_as); - gtk_container_add (GTK_CONTAINER (playlist1_menu), playlist_save_as); + playback1 = gtk_menu_item_new_with_mnemonic ("_Playback"); + gtk_widget_show (playback1); + gtk_container_add (GTK_CONTAINER (menubar1), playback1); + + playback1_menu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (playback1), playback1_menu); order1 = gtk_menu_item_new_with_mnemonic ("Order"); gtk_widget_show (order1); - gtk_container_add (GTK_CONTAINER (playlist1_menu), order1); + gtk_container_add (GTK_CONTAINER (playback1_menu), order1); order1_menu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (order1), order1_menu); @@ -296,7 +323,7 @@ create_mainwin (void) looping1 = gtk_menu_item_new_with_mnemonic ("Looping"); gtk_widget_show (looping1); - gtk_container_add (GTK_CONTAINER (playlist1_menu), looping1); + gtk_container_add (GTK_CONTAINER (playback1_menu), looping1); looping1_menu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (looping1), looping1_menu); @@ -321,9 +348,20 @@ create_mainwin (void) scroll_follows_playback = gtk_check_menu_item_new_with_mnemonic ("Scroll follows playback"); gtk_widget_show (scroll_follows_playback); - gtk_container_add (GTK_CONTAINER (playlist1_menu), scroll_follows_playback); + gtk_container_add (GTK_CONTAINER (playback1_menu), scroll_follows_playback); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (scroll_follows_playback), TRUE); + cursor_follows_playback = gtk_check_menu_item_new_with_mnemonic ("Cursor follows playback"); + gtk_widget_show (cursor_follows_playback); + gtk_container_add (GTK_CONTAINER (playback1_menu), cursor_follows_playback); + + stop_after_current = gtk_check_menu_item_new_with_mnemonic ("Stop after current"); + gtk_widget_show (stop_after_current); + gtk_container_add (GTK_CONTAINER (playback1_menu), stop_after_current); + gtk_widget_add_accelerator (stop_after_current, "activate", accel_group, + GDK_M, (GdkModifierType) GDK_CONTROL_MASK, + GTK_ACCEL_VISIBLE); + menuitem4 = gtk_menu_item_new_with_mnemonic ("_Help"); gtk_widget_show (menuitem4); gtk_container_add (GTK_CONTAINER (menubar1), menuitem4); @@ -339,9 +377,9 @@ create_mainwin (void) gtk_widget_show (help1); gtk_container_add (GTK_CONTAINER (menuitem4_menu), help1); - image127 = gtk_image_new_from_stock ("gtk-help", GTK_ICON_SIZE_MENU); - gtk_widget_show (image127); - gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (help1), image127); + image297 = gtk_image_new_from_stock ("gtk-help", GTK_ICON_SIZE_MENU); + gtk_widget_show (image297); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (help1), image297); hbox2 = gtk_hbox_new (FALSE, 0); gtk_widget_show (hbox2); @@ -457,7 +495,7 @@ create_mainwin (void) gtk_widget_show (header); gtk_box_pack_start (GTK_BOX (vbox3), header, FALSE, TRUE, 0); gtk_widget_set_size_request (header, -1, 24); - gtk_widget_set_events (header, GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + gtk_widget_set_events (header, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); playlist = gtk_drawing_area_new (); gtk_widget_show (playlist); @@ -489,6 +527,9 @@ create_mainwin (void) g_signal_connect ((gpointer) mainwin, "configure_event", G_CALLBACK (on_mainwin_configure_event), NULL); + g_signal_connect ((gpointer) mainwin, "window_state_event", + G_CALLBACK (on_mainwin_window_state_event), + NULL); g_signal_connect ((gpointer) open, "activate", G_CALLBACK (on_open_activate), NULL); @@ -504,6 +545,15 @@ create_mainwin (void) g_signal_connect ((gpointer) add_location1, "activate", G_CALLBACK (on_add_location_activate), NULL); + g_signal_connect ((gpointer) playlist_load, "activate", + G_CALLBACK (on_playlist_load_activate), + NULL); + g_signal_connect ((gpointer) playlist_save, "activate", + G_CALLBACK (on_playlist_save_activate), + NULL); + g_signal_connect ((gpointer) playlist_save_as, "activate", + G_CALLBACK (on_playlist_save_as_activate), + NULL); g_signal_connect ((gpointer) quit, "activate", G_CALLBACK (on_quit_activate), NULL); @@ -525,14 +575,11 @@ create_mainwin (void) g_signal_connect ((gpointer) preferences, "activate", G_CALLBACK (on_preferences_activate), NULL); - g_signal_connect ((gpointer) playlist_load, "activate", - G_CALLBACK (on_playlist_load_activate), - NULL); - g_signal_connect ((gpointer) playlist_save, "activate", - G_CALLBACK (on_playlist_save_activate), + g_signal_connect ((gpointer) view_status_bar, "activate", + G_CALLBACK (on_toggle_status_bar_activate), NULL); - g_signal_connect ((gpointer) playlist_save_as, "activate", - G_CALLBACK (on_playlist_save_as_activate), + g_signal_connect ((gpointer) view_headers, "activate", + G_CALLBACK (on_toggle_column_headers_activate), NULL); g_signal_connect ((gpointer) order_linear, "activate", G_CALLBACK (on_order_linear_activate), @@ -555,6 +602,12 @@ create_mainwin (void) g_signal_connect ((gpointer) scroll_follows_playback, "activate", G_CALLBACK (on_scroll_follows_playback_activate), NULL); + g_signal_connect ((gpointer) cursor_follows_playback, "activate", + G_CALLBACK (on_cursor_follows_playback_activate), + NULL); + g_signal_connect ((gpointer) stop_after_current, "activate", + G_CALLBACK (on_stop_after_current_activate), + NULL); g_signal_connect ((gpointer) about1, "activate", G_CALLBACK (on_about1_activate), NULL); @@ -689,36 +742,41 @@ create_mainwin (void) GLADE_HOOKUP_OBJECT (mainwin, menuitem1, "menuitem1"); GLADE_HOOKUP_OBJECT (mainwin, menuitem1_menu, "menuitem1_menu"); GLADE_HOOKUP_OBJECT (mainwin, open, "open"); - GLADE_HOOKUP_OBJECT (mainwin, image120, "image120"); + GLADE_HOOKUP_OBJECT (mainwin, image290, "image290"); GLADE_HOOKUP_OBJECT (mainwin, separator2, "separator2"); GLADE_HOOKUP_OBJECT (mainwin, add_files, "add_files"); - GLADE_HOOKUP_OBJECT (mainwin, image121, "image121"); + GLADE_HOOKUP_OBJECT (mainwin, image291, "image291"); GLADE_HOOKUP_OBJECT (mainwin, add_folders, "add_folders"); - GLADE_HOOKUP_OBJECT (mainwin, image122, "image122"); + GLADE_HOOKUP_OBJECT (mainwin, image292, "image292"); GLADE_HOOKUP_OBJECT (mainwin, add_audio_cd, "add_audio_cd"); - GLADE_HOOKUP_OBJECT (mainwin, image123, "image123"); + GLADE_HOOKUP_OBJECT (mainwin, image293, "image293"); GLADE_HOOKUP_OBJECT (mainwin, add_location1, "add_location1"); GLADE_HOOKUP_OBJECT (mainwin, separatormenuitem1, "separatormenuitem1"); + GLADE_HOOKUP_OBJECT (mainwin, playlist_load, "playlist_load"); + GLADE_HOOKUP_OBJECT (mainwin, playlist_save, "playlist_save"); + GLADE_HOOKUP_OBJECT (mainwin, playlist_save_as, "playlist_save_as"); + GLADE_HOOKUP_OBJECT (mainwin, separator8, "separator8"); GLADE_HOOKUP_OBJECT (mainwin, quit, "quit"); - GLADE_HOOKUP_OBJECT (mainwin, image124, "image124"); + GLADE_HOOKUP_OBJECT (mainwin, image294, "image294"); GLADE_HOOKUP_OBJECT (mainwin, edit1, "edit1"); GLADE_HOOKUP_OBJECT (mainwin, edit1_menu, "edit1_menu"); GLADE_HOOKUP_OBJECT (mainwin, clear1, "clear1"); - GLADE_HOOKUP_OBJECT (mainwin, image125, "image125"); + GLADE_HOOKUP_OBJECT (mainwin, image295, "image295"); GLADE_HOOKUP_OBJECT (mainwin, select_all1, "select_all1"); GLADE_HOOKUP_OBJECT (mainwin, selection1, "selection1"); GLADE_HOOKUP_OBJECT (mainwin, selection1_menu, "selection1_menu"); GLADE_HOOKUP_OBJECT (mainwin, remove1, "remove1"); - GLADE_HOOKUP_OBJECT (mainwin, image126, "image126"); + GLADE_HOOKUP_OBJECT (mainwin, image296, "image296"); GLADE_HOOKUP_OBJECT (mainwin, crop1, "crop1"); GLADE_HOOKUP_OBJECT (mainwin, find1, "find1"); GLADE_HOOKUP_OBJECT (mainwin, separator5, "separator5"); GLADE_HOOKUP_OBJECT (mainwin, preferences, "preferences"); - GLADE_HOOKUP_OBJECT (mainwin, playlist1, "playlist1"); - GLADE_HOOKUP_OBJECT (mainwin, playlist1_menu, "playlist1_menu"); - GLADE_HOOKUP_OBJECT (mainwin, playlist_load, "playlist_load"); - GLADE_HOOKUP_OBJECT (mainwin, playlist_save, "playlist_save"); - GLADE_HOOKUP_OBJECT (mainwin, playlist_save_as, "playlist_save_as"); + GLADE_HOOKUP_OBJECT (mainwin, view1, "view1"); + GLADE_HOOKUP_OBJECT (mainwin, view1_menu, "view1_menu"); + GLADE_HOOKUP_OBJECT (mainwin, view_status_bar, "view_status_bar"); + GLADE_HOOKUP_OBJECT (mainwin, view_headers, "view_headers"); + GLADE_HOOKUP_OBJECT (mainwin, playback1, "playback1"); + GLADE_HOOKUP_OBJECT (mainwin, playback1_menu, "playback1_menu"); GLADE_HOOKUP_OBJECT (mainwin, order1, "order1"); GLADE_HOOKUP_OBJECT (mainwin, order1_menu, "order1_menu"); GLADE_HOOKUP_OBJECT (mainwin, order_linear, "order_linear"); @@ -730,11 +788,13 @@ create_mainwin (void) GLADE_HOOKUP_OBJECT (mainwin, loop_single, "loop_single"); GLADE_HOOKUP_OBJECT (mainwin, loop_disable, "loop_disable"); GLADE_HOOKUP_OBJECT (mainwin, scroll_follows_playback, "scroll_follows_playback"); + GLADE_HOOKUP_OBJECT (mainwin, cursor_follows_playback, "cursor_follows_playback"); + GLADE_HOOKUP_OBJECT (mainwin, stop_after_current, "stop_after_current"); GLADE_HOOKUP_OBJECT (mainwin, menuitem4, "menuitem4"); GLADE_HOOKUP_OBJECT (mainwin, menuitem4_menu, "menuitem4_menu"); GLADE_HOOKUP_OBJECT (mainwin, about1, "about1"); GLADE_HOOKUP_OBJECT (mainwin, help1, "help1"); - GLADE_HOOKUP_OBJECT (mainwin, image127, "image127"); + GLADE_HOOKUP_OBJECT (mainwin, image297, "image297"); GLADE_HOOKUP_OBJECT (mainwin, hbox2, "hbox2"); GLADE_HOOKUP_OBJECT (mainwin, hbox3, "hbox3"); GLADE_HOOKUP_OBJECT (mainwin, stopbtn, "stopbtn"); @@ -782,7 +842,6 @@ create_searchwin (void) gtk_widget_set_size_request (searchwin, 600, 150); gtk_widget_set_events (searchwin, GDK_KEY_PRESS_MASK); gtk_window_set_title (GTK_WINDOW (searchwin), "Search"); - gtk_window_set_position (GTK_WINDOW (searchwin), GTK_WIN_POS_CENTER_ALWAYS); gtk_window_set_skip_taskbar_hint (GTK_WINDOW (searchwin), TRUE); gtk_window_set_skip_pager_hint (GTK_WINDOW (searchwin), TRUE); @@ -821,7 +880,7 @@ create_searchwin (void) gtk_widget_show (searchheader); gtk_box_pack_start (GTK_BOX (vbox5), searchheader, FALSE, TRUE, 0); gtk_widget_set_size_request (searchheader, -1, 24); - gtk_widget_set_events (searchheader, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + gtk_widget_set_events (searchheader, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); searchlist = gtk_drawing_area_new (); gtk_widget_show (searchlist); @@ -1152,18 +1211,20 @@ create_prefwin (void) GtkWidget *prefwin; GtkWidget *notebook2; GtkWidget *table3; - GtkWidget *label4; - GtkWidget *label5; - GtkWidget *label6; - GtkWidget *label8; - GtkWidget *label9; GtkWidget *label15; + GtkWidget *label9; + GtkWidget *label8; + GtkWidget *label6; + GtkWidget *label5; + GtkWidget *label4; + GtkWidget *label23; + GtkWidget *pref_soundcard; GtkWidget *pref_alsa_resampling; GtkWidget *pref_replaygain_scale; GtkWidget *pref_alsa_freewhenstopped; - GtkWidget *pref_soundcard; GtkWidget *pref_src_quality; GtkWidget *pref_replaygain_mode; + GtkWidget *pref_output_plugin; GtkWidget *Sound; GtkWidget *table4; GtkWidget *label7; @@ -1191,6 +1252,7 @@ create_prefwin (void) GtkWidget *pref_plugin_author; GtkWidget *pref_plugin_email; GtkWidget *pref_plugin_website; + GtkWidget *configure_plugin; GtkWidget *label3; prefwin = gtk_window_new (GTK_WINDOW_TOPLEVEL); @@ -1202,81 +1264,88 @@ create_prefwin (void) gtk_widget_show (notebook2); gtk_container_add (GTK_CONTAINER (prefwin), notebook2); - table3 = gtk_table_new (6, 2, FALSE); + table3 = gtk_table_new (7, 2, FALSE); gtk_widget_show (table3); gtk_container_add (GTK_CONTAINER (notebook2), table3); gtk_container_set_border_width (GTK_CONTAINER (table3), 3); gtk_table_set_col_spacings (GTK_TABLE (table3), 3); - label4 = gtk_label_new ("Output device"); - gtk_widget_show (label4); - gtk_table_attach (GTK_TABLE (table3), label4, 0, 1, 0, 1, + label15 = gtk_label_new ("Release ALSA while stopped"); + gtk_widget_show (label15); + gtk_table_attach (GTK_TABLE (table3), label15, 0, 1, 6, 7, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); - gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5); + gtk_misc_set_alignment (GTK_MISC (label15), 0, 0.5); - label5 = gtk_label_new ("Software ALSA resampling"); - gtk_widget_show (label5); - gtk_table_attach (GTK_TABLE (table3), label5, 0, 1, 1, 2, + label9 = gtk_label_new ("Replaygain peak scale"); + gtk_widget_show (label9); + gtk_table_attach (GTK_TABLE (table3), label9, 0, 1, 5, 6, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); - gtk_misc_set_alignment (GTK_MISC (label5), 0, 0.5); + gtk_misc_set_alignment (GTK_MISC (label9), 0, 0.5); + + label8 = gtk_label_new ("Replaygain mode"); + gtk_widget_show (label8); + gtk_table_attach (GTK_TABLE (table3), label8, 0, 1, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label8), 0, 0.5); label6 = gtk_label_new ("SRC quality (libsamplerate)"); gtk_widget_show (label6); - gtk_table_attach (GTK_TABLE (table3), label6, 0, 1, 2, 3, + gtk_table_attach (GTK_TABLE (table3), label6, 0, 1, 3, 4, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); gtk_misc_set_alignment (GTK_MISC (label6), 0, 0.5); - label8 = gtk_label_new ("Replaygain mode"); - gtk_widget_show (label8); - gtk_table_attach (GTK_TABLE (table3), label8, 0, 1, 3, 4, + label5 = gtk_label_new ("Software ALSA resampling"); + gtk_widget_show (label5); + gtk_table_attach (GTK_TABLE (table3), label5, 0, 1, 2, 3, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); - gtk_misc_set_alignment (GTK_MISC (label8), 0, 0.5); + gtk_misc_set_alignment (GTK_MISC (label5), 0, 0.5); - label9 = gtk_label_new ("Replaygain peak scale"); - gtk_widget_show (label9); - gtk_table_attach (GTK_TABLE (table3), label9, 0, 1, 4, 5, + label4 = gtk_label_new ("Output device"); + gtk_widget_show (label4); + gtk_table_attach (GTK_TABLE (table3), label4, 0, 1, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); - gtk_misc_set_alignment (GTK_MISC (label9), 0, 0.5); + gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5); - label15 = gtk_label_new ("Release ALSA while stopped"); - gtk_widget_show (label15); - gtk_table_attach (GTK_TABLE (table3), label15, 0, 1, 5, 6, + label23 = gtk_label_new ("Output plugin"); + gtk_widget_show (label23); + gtk_table_attach (GTK_TABLE (table3), label23, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 0); - gtk_misc_set_alignment (GTK_MISC (label15), 0, 0.5); + gtk_misc_set_alignment (GTK_MISC (label23), 0, 0.5); + + pref_soundcard = gtk_combo_box_new_text (); + gtk_widget_show (pref_soundcard); + gtk_table_attach (GTK_TABLE (table3), pref_soundcard, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); pref_alsa_resampling = gtk_check_button_new_with_mnemonic (""); gtk_widget_show (pref_alsa_resampling); - gtk_table_attach (GTK_TABLE (table3), pref_alsa_resampling, 1, 2, 1, 2, + gtk_table_attach (GTK_TABLE (table3), pref_alsa_resampling, 1, 2, 2, 3, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); pref_replaygain_scale = gtk_check_button_new_with_mnemonic (""); gtk_widget_show (pref_replaygain_scale); - gtk_table_attach (GTK_TABLE (table3), pref_replaygain_scale, 1, 2, 4, 5, + gtk_table_attach (GTK_TABLE (table3), pref_replaygain_scale, 1, 2, 5, 6, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); pref_alsa_freewhenstopped = gtk_check_button_new_with_mnemonic (""); gtk_widget_show (pref_alsa_freewhenstopped); - gtk_table_attach (GTK_TABLE (table3), pref_alsa_freewhenstopped, 1, 2, 5, 6, + gtk_table_attach (GTK_TABLE (table3), pref_alsa_freewhenstopped, 1, 2, 6, 7, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0), 0, 0); - pref_soundcard = gtk_combo_box_new_text (); - gtk_widget_show (pref_soundcard); - gtk_table_attach (GTK_TABLE (table3), pref_soundcard, 1, 2, 0, 1, - (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), - (GtkAttachOptions) (GTK_FILL), 0, 0); - pref_src_quality = gtk_combo_box_new_text (); gtk_widget_show (pref_src_quality); - gtk_table_attach (GTK_TABLE (table3), pref_src_quality, 1, 2, 2, 3, + gtk_table_attach (GTK_TABLE (table3), pref_src_quality, 1, 2, 3, 4, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (GTK_FILL), 0, 0); gtk_combo_box_append_text (GTK_COMBO_BOX (pref_src_quality), "sinc_best_quality"); @@ -1287,13 +1356,19 @@ create_prefwin (void) pref_replaygain_mode = gtk_combo_box_new_text (); gtk_widget_show (pref_replaygain_mode); - gtk_table_attach (GTK_TABLE (table3), pref_replaygain_mode, 1, 2, 3, 4, + gtk_table_attach (GTK_TABLE (table3), pref_replaygain_mode, 1, 2, 4, 5, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (GTK_FILL), 0, 0); gtk_combo_box_append_text (GTK_COMBO_BOX (pref_replaygain_mode), "Disable"); gtk_combo_box_append_text (GTK_COMBO_BOX (pref_replaygain_mode), "Track"); gtk_combo_box_append_text (GTK_COMBO_BOX (pref_replaygain_mode), "Album"); + pref_output_plugin = gtk_combo_box_new_text (); + gtk_widget_show (pref_output_plugin); + gtk_table_attach (GTK_TABLE (table3), pref_output_plugin, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + Sound = gtk_label_new ("Sound"); gtk_widget_show (Sound); gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook2), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook2), 0), Sound); @@ -1394,22 +1469,26 @@ create_prefwin (void) hpaned1 = gtk_hpaned_new (); gtk_widget_show (hpaned1); gtk_container_add (GTK_CONTAINER (notebook2), hpaned1); + gtk_container_set_border_width (GTK_CONTAINER (hpaned1), 3); scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL); gtk_widget_show (scrolledwindow2); gtk_paned_pack1 (GTK_PANED (hpaned1), scrolledwindow2, FALSE, TRUE); gtk_widget_set_size_request (scrolledwindow2, 280, -1); + gtk_container_set_border_width (GTK_CONTAINER (scrolledwindow2), 3); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow2), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow2), GTK_SHADOW_IN); pref_pluginlist = gtk_tree_view_new (); gtk_widget_show (pref_pluginlist); gtk_container_add (GTK_CONTAINER (scrolledwindow2), pref_pluginlist); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (pref_pluginlist), TRUE); - table5 = gtk_table_new (4, 2, FALSE); + table5 = gtk_table_new (5, 2, FALSE); gtk_widget_show (table5); gtk_paned_pack2 (GTK_PANED (hpaned1), table5, TRUE, TRUE); gtk_widget_set_size_request (table5, 400, -1); + gtk_container_set_border_width (GTK_CONTAINER (table5), 3); label11 = gtk_label_new ("Description"); gtk_widget_show (label11); @@ -1471,6 +1550,12 @@ create_prefwin (void) gtk_editable_set_editable (GTK_EDITABLE (pref_plugin_website), FALSE); gtk_entry_set_invisible_char (GTK_ENTRY (pref_plugin_website), 9679); + configure_plugin = gtk_button_new_with_mnemonic ("Configure"); + gtk_widget_show (configure_plugin); + gtk_table_attach (GTK_TABLE (table5), configure_plugin, 0, 2, 4, 5, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 3); + label3 = gtk_label_new ("Plugins"); gtk_widget_show (label3); gtk_notebook_set_tab_label (GTK_NOTEBOOK (notebook2), gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook2), 3), label3); @@ -1478,6 +1563,12 @@ create_prefwin (void) g_signal_connect ((gpointer) prefwin, "key_press_event", G_CALLBACK (on_prefwin_key_press_event), NULL); + g_signal_connect ((gpointer) prefwin, "delete_event", + G_CALLBACK (on_prefwin_delete_event), + NULL); + g_signal_connect ((gpointer) pref_soundcard, "changed", + G_CALLBACK (on_pref_soundcard_changed), + NULL); g_signal_connect ((gpointer) pref_alsa_resampling, "clicked", G_CALLBACK (on_pref_alsa_resampling_clicked), NULL); @@ -1487,15 +1578,15 @@ create_prefwin (void) g_signal_connect ((gpointer) pref_alsa_freewhenstopped, "clicked", G_CALLBACK (on_pref_alsa_freewhenstopped_clicked), NULL); - g_signal_connect ((gpointer) pref_soundcard, "changed", - G_CALLBACK (on_pref_soundcard_changed), - NULL); g_signal_connect ((gpointer) pref_src_quality, "changed", G_CALLBACK (on_pref_src_quality_changed), NULL); g_signal_connect ((gpointer) pref_replaygain_mode, "changed", G_CALLBACK (on_pref_replaygain_mode_changed), NULL); + g_signal_connect ((gpointer) pref_output_plugin, "changed", + G_CALLBACK (on_pref_output_plugin_changed), + NULL); g_signal_connect ((gpointer) pref_close_send_to_tray, "clicked", G_CALLBACK (on_pref_close_send_to_tray_clicked), NULL); @@ -1514,23 +1605,28 @@ create_prefwin (void) g_signal_connect ((gpointer) pref_pluginlist, "cursor_changed", G_CALLBACK (on_pref_pluginlist_cursor_changed), NULL); + g_signal_connect ((gpointer) configure_plugin, "clicked", + G_CALLBACK (on_configure_plugin_clicked), + NULL); /* Store pointers to all widgets, for use by lookup_widget(). */ GLADE_HOOKUP_OBJECT_NO_REF (prefwin, prefwin, "prefwin"); GLADE_HOOKUP_OBJECT (prefwin, notebook2, "notebook2"); GLADE_HOOKUP_OBJECT (prefwin, table3, "table3"); - GLADE_HOOKUP_OBJECT (prefwin, label4, "label4"); - GLADE_HOOKUP_OBJECT (prefwin, label5, "label5"); - GLADE_HOOKUP_OBJECT (prefwin, label6, "label6"); - GLADE_HOOKUP_OBJECT (prefwin, label8, "label8"); - GLADE_HOOKUP_OBJECT (prefwin, label9, "label9"); GLADE_HOOKUP_OBJECT (prefwin, label15, "label15"); + GLADE_HOOKUP_OBJECT (prefwin, label9, "label9"); + GLADE_HOOKUP_OBJECT (prefwin, label8, "label8"); + GLADE_HOOKUP_OBJECT (prefwin, label6, "label6"); + GLADE_HOOKUP_OBJECT (prefwin, label5, "label5"); + GLADE_HOOKUP_OBJECT (prefwin, label4, "label4"); + GLADE_HOOKUP_OBJECT (prefwin, label23, "label23"); + GLADE_HOOKUP_OBJECT (prefwin, pref_soundcard, "pref_soundcard"); GLADE_HOOKUP_OBJECT (prefwin, pref_alsa_resampling, "pref_alsa_resampling"); GLADE_HOOKUP_OBJECT (prefwin, pref_replaygain_scale, "pref_replaygain_scale"); GLADE_HOOKUP_OBJECT (prefwin, pref_alsa_freewhenstopped, "pref_alsa_freewhenstopped"); - GLADE_HOOKUP_OBJECT (prefwin, pref_soundcard, "pref_soundcard"); GLADE_HOOKUP_OBJECT (prefwin, pref_src_quality, "pref_src_quality"); GLADE_HOOKUP_OBJECT (prefwin, pref_replaygain_mode, "pref_replaygain_mode"); + GLADE_HOOKUP_OBJECT (prefwin, pref_output_plugin, "pref_output_plugin"); GLADE_HOOKUP_OBJECT (prefwin, Sound, "Sound"); GLADE_HOOKUP_OBJECT (prefwin, table4, "table4"); GLADE_HOOKUP_OBJECT (prefwin, label7, "label7"); @@ -1558,6 +1654,7 @@ create_prefwin (void) GLADE_HOOKUP_OBJECT (prefwin, pref_plugin_author, "pref_plugin_author"); GLADE_HOOKUP_OBJECT (prefwin, pref_plugin_email, "pref_plugin_email"); GLADE_HOOKUP_OBJECT (prefwin, pref_plugin_website, "pref_plugin_website"); + GLADE_HOOKUP_OBJECT (prefwin, configure_plugin, "configure_plugin"); GLADE_HOOKUP_OBJECT (prefwin, label3, "label3"); return prefwin; @@ -1728,3 +1825,98 @@ create_addlocation (void) return addlocation; } +GtkWidget* +create_inputformat (void) +{ + GtkWidget *inputformat; + GtkWidget *vbox8; + GtkWidget *hbox10; + GtkWidget *label26; + GtkWidget *titleentry; + GtkWidget *hbox9; + GtkWidget *format; + GtkWidget *formatentry; + GtkWidget *label25; + GtkWidget *hbuttonbox1; + GtkWidget *button2; + GtkWidget *button3; + + inputformat = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (inputformat), "Column Format"); + gtk_window_set_position (GTK_WINDOW (inputformat), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_modal (GTK_WINDOW (inputformat), TRUE); + gtk_window_set_type_hint (GTK_WINDOW (inputformat), GDK_WINDOW_TYPE_HINT_DIALOG); + + vbox8 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox8); + gtk_container_add (GTK_CONTAINER (inputformat), vbox8); + + hbox10 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox10); + gtk_box_pack_start (GTK_BOX (vbox8), hbox10, FALSE, FALSE, 0); + + label26 = gtk_label_new ("Title:"); + gtk_widget_show (label26); + gtk_box_pack_start (GTK_BOX (hbox10), label26, FALSE, FALSE, 0); + + titleentry = gtk_entry_new (); + gtk_widget_show (titleentry); + gtk_box_pack_start (GTK_BOX (hbox10), titleentry, TRUE, TRUE, 0); + gtk_entry_set_text (GTK_ENTRY (titleentry), "Custom"); + gtk_entry_set_invisible_char (GTK_ENTRY (titleentry), 9679); + + hbox9 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox9); + gtk_box_pack_start (GTK_BOX (vbox8), hbox9, FALSE, FALSE, 0); + + format = gtk_label_new ("Format:"); + gtk_widget_show (format); + gtk_box_pack_start (GTK_BOX (hbox9), format, FALSE, FALSE, 0); + + formatentry = gtk_entry_new (); + gtk_widget_show (formatentry); + gtk_box_pack_start (GTK_BOX (hbox9), formatentry, TRUE, TRUE, 0); + gtk_entry_set_invisible_char (GTK_ENTRY (formatentry), 9679); + + label25 = gtk_label_new ("Format fields:\n%a - artist\n%t - title\n%b - album\n%n - track\n%l - duration"); + gtk_widget_show (label25); + gtk_box_pack_start (GTK_BOX (vbox8), label25, TRUE, TRUE, 0); + + hbuttonbox1 = gtk_hbutton_box_new (); + gtk_widget_show (hbuttonbox1); + gtk_box_pack_start (GTK_BOX (vbox8), hbuttonbox1, FALSE, FALSE, 0); + + button2 = gtk_button_new_from_stock ("gtk-cancel"); + gtk_widget_show (button2); + gtk_container_add (GTK_CONTAINER (hbuttonbox1), button2); + GTK_WIDGET_SET_FLAGS (button2, GTK_CAN_DEFAULT); + + button3 = gtk_button_new_from_stock ("gtk-ok"); + gtk_widget_show (button3); + gtk_container_add (GTK_CONTAINER (hbuttonbox1), button3); + GTK_WIDGET_SET_FLAGS (button3, GTK_CAN_DEFAULT); + + g_signal_connect ((gpointer) button2, "clicked", + G_CALLBACK (on_format_cancel_clicked), + NULL); + g_signal_connect ((gpointer) button3, "clicked", + G_CALLBACK (on_format_ok_clicked), + NULL); + + /* Store pointers to all widgets, for use by lookup_widget(). */ + GLADE_HOOKUP_OBJECT_NO_REF (inputformat, inputformat, "inputformat"); + GLADE_HOOKUP_OBJECT (inputformat, vbox8, "vbox8"); + GLADE_HOOKUP_OBJECT (inputformat, hbox10, "hbox10"); + GLADE_HOOKUP_OBJECT (inputformat, label26, "label26"); + GLADE_HOOKUP_OBJECT (inputformat, titleentry, "titleentry"); + GLADE_HOOKUP_OBJECT (inputformat, hbox9, "hbox9"); + GLADE_HOOKUP_OBJECT (inputformat, format, "format"); + GLADE_HOOKUP_OBJECT (inputformat, formatentry, "formatentry"); + GLADE_HOOKUP_OBJECT (inputformat, label25, "label25"); + GLADE_HOOKUP_OBJECT (inputformat, hbuttonbox1, "hbuttonbox1"); + GLADE_HOOKUP_OBJECT (inputformat, button2, "button2"); + GLADE_HOOKUP_OBJECT (inputformat, button3, "button3"); + + return inputformat; +} + diff --git a/interface.h b/plugins/gtkui/interface.h index f9b470ff..431e5e12 100644 --- a/interface.h +++ b/plugins/gtkui/interface.h @@ -10,3 +10,4 @@ GtkWidget* create_helpwindow (void); GtkWidget* create_prefwin (void); GtkWidget* create_headermenu (void); GtkWidget* create_addlocation (void); +GtkWidget* create_inputformat (void); diff --git a/plugins/gtkui/parser.c b/plugins/gtkui/parser.c new file mode 100644 index 00000000..4cbd2da4 --- /dev/null +++ b/plugins/gtkui/parser.c @@ -0,0 +1,107 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include "parser.h" + +// very basic parser, ripped from psynth, optimized, and extended to support +// quoted strings and extra special chars +int parser_line; + +void +parser_init (void) { + parser_line = 1; +} + +const char * +skipws (const char *p) { + while (*p <= ' ' && *p) { + if (*p == '\n') { + parser_line++; + } + p++; + } + if (!*p) { + return NULL; + } + return p; +} + +const char * +gettoken (const char *p, char *tok) { + const char *c; + assert (p); + assert (tok); + int n = MAX_TOKEN-1; + char specialchars[] = "{}();"; + if (!(p = skipws (p))) { + return NULL; + } + if (*p == '"') { + p++; + c = p; + while (n > 0 && *c && *c != '"') { + if (*c == '\n') { + parser_line++; + } + *tok++ = *c++; + n--; + } + if (*c) { + c++; + } + *tok = 0; + return c; + } + if (strchr (specialchars, *p)) { + *tok = *p; + tok[1] = 0; + return p+1; + } + c = p; + while (n > 0 && *c > ' ' && !strchr (specialchars, *c)) { + *tok++ = *c++; + n--; + } + *tok = 0; + return c; +} + +const char * +gettoken_warn_eof (const char *p, char *tok) { + p = gettoken (p, tok); + if (!p) { + fprintf (stderr, "parser: unexpected eof at line %d", parser_line); + } + return p; +} + +const char * +gettoken_err_eof (const char *p, char *tok) { + p = gettoken (p, tok); + if (!p) { + fprintf (stderr, "parser: unexpected eof at line %d", parser_line); + exit (-1); + } + return p; +} + + diff --git a/palsa.h b/plugins/gtkui/parser.h index 2896702a..61955bcf 100644 --- a/palsa.h +++ b/plugins/gtkui/parser.h @@ -15,44 +15,25 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef __PALSA_H -#define __PALSA_H +#ifndef __PARSER_H +#define __PARSER_H -int -palsa_init (void); +#define MAX_TOKEN 256 +extern int parser_line; void -palsa_free (void); +parser_init (void); -int -palsa_change_rate (int rate); +const char * +skipws (const char *p); -int -palsa_play (void); +const char * +gettoken (const char *p, char *tok); -int -palsa_stop (void); +const char * +gettoken_warn_eof (const char *p, char *tok); -int -palsa_isstopped (void); - -int -palsa_ispaused (void); - -int -palsa_pause (void); - -int -palsa_unpause (void); - -int -palsa_get_rate (void); - -void -palsa_configchanged (void); - -void -palsa_enum_soundcards (void (*callback)(const char *name, const char *desc, void*), void *userdata); - -#endif // __PALSA_H +const char * +gettoken_err_eof (const char *p, char *tok); +#endif diff --git a/progress.c b/plugins/gtkui/progress.c index 78acf7c4..78acf7c4 100644 --- a/progress.c +++ b/plugins/gtkui/progress.c diff --git a/progress.h b/plugins/gtkui/progress.h index e4de0e8e..e4de0e8e 100644 --- a/progress.h +++ b/plugins/gtkui/progress.h diff --git a/search.c b/plugins/gtkui/search.c index 1ce99edd..55b5ad3f 100644 --- a/search.c +++ b/plugins/gtkui/search.c @@ -29,13 +29,24 @@ #include "interface.h" #include "support.h" -#include "common.h" #include "search.h" #include "gtkplaylist.h" -#include "messagepump.h" -#include "utf8.h" #include "deadbeef.h" +#define min(x,y) ((x)<(y)?(x):(y)) +#define max(x,y) ((x)>(y)?(x):(y)) + +#define PL_HEAD(iter) (deadbeef->pl_get_first(iter)) +#define PL_TAIL(iter) (deadbeef->pl_get_last(iter)) +#define PL_NEXT(it, iter) (deadbeef->pl_get_next(it, iter)) +#define PL_PREV(it, iter) (deadbeef->pl_get_prev(it, iter)) +#define SELECTED(it) (deadbeef->pl_is_selected(it)) +#define SELECT(it, sel) (deadbeef->pl_set_selected(it,sel)) + +extern DB_functions_t *deadbeef; // defined in gtkui.c +//#define trace(...) { fprintf(stderr, __VA_ARGS__); } +#define trace(fmt,...) + extern GtkWidget *searchwin; struct playItem_s *search_current = NULL; int search_count = 0; @@ -59,30 +70,7 @@ on_searchentry_changed (GtkEditable *editable, // with search_head const gchar *text = gtk_entry_get_text (GTK_ENTRY (editable)); - - playlist_head[PL_SEARCH] = NULL; - playlist_tail[PL_SEARCH] = NULL; - search_count = 0; - if (*text) { - for (playItem_t *it = playlist_head[PL_MAIN]; it; it = it->next[PL_MAIN]) { - for (metaInfo_t *m = it->meta; m; m = m->next) { -// if (strcasestr (m->value, text)) { - if (utfcasestr (m->value, text)) { - // add to list - it->next[PL_SEARCH] = NULL; - if (playlist_tail[PL_SEARCH]) { - playlist_tail[PL_SEARCH]->next[PL_SEARCH] = it; - playlist_tail[PL_SEARCH] = it; - } - else { - playlist_head[PL_SEARCH] = playlist_tail[PL_SEARCH] = it; - } - search_count++; - break; - } - } - } - } + search_count = deadbeef->pl_process_search (text); extern gtkplaylist_t search_playlist; gtkplaylist_t *ps = &search_playlist; @@ -90,12 +78,20 @@ on_searchentry_changed (GtkEditable *editable, //memset (ps->fmtcache, 0, sizeof (int16_t) * 3 * pl_ncolumns * ps->nvisiblerows); gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + + // redraw main playlist to be in sync selection-wise + ps = &main_playlist; + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); } void search_refresh (void) { - if (searchwin) { - on_searchentry_changed (GTK_EDITABLE (lookup_widget (searchwin, "searchentry")), NULL); + if (searchwin && GTK_WIDGET_VISIBLE (searchwin)) { + gtkplaylist_t *ps = &search_playlist; + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + //on_searchentry_changed (GTK_EDITABLE (lookup_widget (searchwin, "searchentry")), NULL); } } @@ -165,12 +161,16 @@ on_searchwin_key_press_event (GtkWidget *widget, extern gtkplaylist_t search_playlist; gtkplaylist_t *ps = &search_playlist; if (search_count > 0) { - playItem_t *it = gtkpl_get_for_idx (ps, max (ps->row, 0)); + int row = deadbeef->pl_get_cursor (ps->iterator); + DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (max (row, 0), ps->iterator); if (it) { - messagepump_push (M_PLAYSONGNUM, 0, pl_get_idx_of (it), 0); + deadbeef->sendmessage (M_PLAYSONGNUM, 0, deadbeef->pl_get_idx_of (it), 0); } } } + else { + gtkpl_keypress (&search_playlist, event->keyval, event->state); + } return FALSE; } diff --git a/search.h b/plugins/gtkui/search.h index 32d45e07..32d45e07 100644 --- a/search.h +++ b/plugins/gtkui/search.h diff --git a/support.c b/plugins/gtkui/support.c index 7dc3c78c..7dc3c78c 100644 --- a/support.c +++ b/plugins/gtkui/support.c diff --git a/support.h b/plugins/gtkui/support.h index 2dea079c..2dea079c 100644 --- a/support.h +++ b/plugins/gtkui/support.h diff --git a/plugins/hotkeys/hotkeys.c b/plugins/hotkeys/hotkeys.c index 415e055c..7d292f10 100644 --- a/plugins/hotkeys/hotkeys.c +++ b/plugins/hotkeys/hotkeys.c @@ -33,11 +33,11 @@ static intptr_t loop_tid; #define MAX_COMMAND_COUNT 256 typedef struct { - char *name; + const char *name; KeySym keysym; } xkey_t; -#define KEY( kname, kcode ) { .name=kname, .keysym=kcode }, +#define KEY(kname, kcode) { .name=kname, .keysym=kcode }, static const xkey_t keys[] = { #include "keysyms.inc" @@ -61,27 +61,27 @@ typedef struct { } known_command_t; static int -get_keycode( Display *disp, const char* name ) { +get_keycode (Display *disp, const char* name) { static int first_kk, last_kk; static KeySym* syms; static int ks_per_kk; static int first_time = 1; - int i, j, ks; + int i, ks; - if ( first_time ) + if (first_time) { - XDisplayKeycodes( disp, &first_kk, &last_kk ); + XDisplayKeycodes (disp, &first_kk, &last_kk); - syms = XGetKeyboardMapping( disp, first_kk, last_kk - first_kk, &ks_per_kk ); + syms = XGetKeyboardMapping (disp, first_kk, last_kk - first_kk, &ks_per_kk); first_time = 0; } - for ( i = 0; i < last_kk-first_kk; i++ ) + for (i = 0; i < last_kk-first_kk; i++) { - KeySym sym = *(syms + i*ks_per_kk); - for ( ks = 0; ks < sizeof( keys ) / sizeof( xkey_t ); ks++ ) + KeySym sym = * (syms + i*ks_per_kk); + for (ks = 0; ks < sizeof (keys) / sizeof (xkey_t); ks++) { - if ( (keys[ ks ].keysym == sym) && ( 0 == strcmp(name, keys[ ks ].name) ) ) + if ( (keys[ ks ].keysym == sym) && (0 == strcmp (name, keys[ ks ].name))) { return i+first_kk; } @@ -91,81 +91,92 @@ get_keycode( Display *disp, const char* name ) { } static char* -trim(char* s) +trim (char* s) { char *h, *t; - for ( h = s; *h == ' ' || *h == '\t'; h++ ); - for ( t = s + strlen(s); *t == ' ' || *t == '\t'; t-- ); - *(t+1) = 0; + for (h = s; *h == ' ' || *h == '\t'; h++); + for (t = s + strlen (s); *t == ' ' || *t == '\t'; t--); + * (t+1) = 0; return h; } static void -cmd_seek_fwd() { - deadbeef->playback_set_pos( deadbeef->playback_get_pos() + 5 ); +cmd_seek_fwd () { + deadbeef->playback_set_pos (deadbeef->playback_get_pos () + 5); } static void -cmd_seek_back() { - deadbeef->playback_set_pos( deadbeef->playback_get_pos() - 5 ); +cmd_seek_back () { + deadbeef->playback_set_pos (deadbeef->playback_get_pos () - 5); } static void -cmd_volume_up() { - deadbeef->volume_set_db( deadbeef->volume_get_db() + 2 ); +cmd_volume_up () { + deadbeef->volume_set_db (deadbeef->volume_get_db () + 2); } static void -cmd_volume_down() { - deadbeef->volume_set_db( deadbeef->volume_get_db() - 2 ); +cmd_volume_down () { + deadbeef->volume_set_db (deadbeef->volume_get_db () - 2); +} + +static void +cmd_stop_after_current () { + int var = deadbeef->conf_get_int ("playlist.stop_after_current", 0); + var = 1 - var; + deadbeef->conf_set_int ("playlist.stop_after_current", var); + deadbeef->sendmessage (M_CONFIGCHANGED, 0, 0, 0); } static command_func_t -get_command( const char* command ) +get_command (const char* command) { - if ( 0 == strcasecmp( command, "toggle_pause" ) ) + if (!strcasecmp (command, "toggle_pause")) return deadbeef->playback_pause; - if ( 0 == strcasecmp( command, "play" ) ) + else if (!strcasecmp (command, "play")) return deadbeef->playback_play; - if ( 0 == strcasecmp( command, "prev" ) ) + else if (!strcasecmp (command, "prev")) return deadbeef->playback_prev; - if ( 0 == strcasecmp( command, "next" ) ) + else if (!strcasecmp (command, "next")) return deadbeef->playback_next; - if ( 0 == strcasecmp( command, "stop" ) ) + else if (!strcasecmp (command, "stop")) return deadbeef->playback_stop; - if ( 0 == strcasecmp( command, "play_random" ) ) + else if (!strcasecmp (command, "play_random")) return deadbeef->playback_random; - if ( 0 == strcasecmp( command, "seek_fwd" ) ) + else if (!strcasecmp (command, "seek_fwd")) return cmd_seek_fwd; - if ( 0 == strcasecmp( command, "seek_back" ) ) + else if (!strcasecmp (command, "seek_back")) return cmd_seek_back; - if ( 0 == strcasecmp( command, "volume_up" ) ) + else if (!strcasecmp (command, "volume_up")) return cmd_volume_up; - if ( 0 == strcasecmp( command, "volume_down" ) ) + else if (!strcasecmp (command, "volume_down")) return cmd_volume_down; + else if (!strcasecmp (command, "toggle_stop_after_current")) + return cmd_stop_after_current; + return NULL; } static int -read_config( Display *disp ) +read_config (Display *disp) { DB_conf_item_t *item = deadbeef->conf_find ("hotkeys.", NULL); while (item) { // fprintf (stderr, "hotkeys: adding %s %s\n", item->key, item->value); - if ( command_count == MAX_COMMAND_COUNT ) + if (command_count == MAX_COMMAND_COUNT) { - fprintf( stderr, "hotkeys: maximum number (%d) of commands exceeded\n", MAX_COMMAND_COUNT ); + fprintf (stderr, "hotkeys: maximum number (%d) of commands exceeded\n", MAX_COMMAND_COUNT); break; } @@ -177,16 +188,15 @@ read_config( Display *disp ) char param[l+1]; memcpy (param, item->value, l+1); - char* colon = strchr( param, ':' ); - if ( !colon ) + char* colon = strchr (param, ':'); + if (!colon) { - fprintf( stderr, "hotkeys: bad config option %s %s\n", item->key, item->value); + fprintf (stderr, "hotkeys: bad config option %s %s\n", item->key, item->value); continue; } char* command = colon+1; *colon = 0; - int modifier = 0; char* key = NULL; int done = 0; @@ -194,22 +204,22 @@ read_config( Display *disp ) char* space = param - 1; do { p = space+1; - space = strchr( p, ' ' ); - if ( space ) + space = strchr (p, ' '); + if (space) *space = 0; else done = 1; - if ( 0 == strcasecmp( p, "Ctrl" ) ) + if (0 == strcasecmp (p, "Ctrl")) cmd_entry->modifier |= ControlMask; - else if ( 0 == strcasecmp( p, "Alt" ) ) + else if (0 == strcasecmp (p, "Alt")) cmd_entry->modifier |= Mod1Mask; - else if ( 0 == strcasecmp( p, "Shift" ) ) + else if (0 == strcasecmp (p, "Shift")) cmd_entry->modifier |= ShiftMask; - else if ( 0 == strcasecmp( p, "Super" ) ) { + else if (0 == strcasecmp (p, "Super")) { cmd_entry->modifier |= Mod2Mask; } @@ -223,26 +233,26 @@ read_config( Display *disp ) } else { // lookup name table - cmd_entry->keycode = get_keycode( disp, p ); + cmd_entry->keycode = get_keycode (disp, p); } - if ( !cmd_entry->keycode ) + if (!cmd_entry->keycode) { - fprintf( stderr, "hotkeys: Unknown key: <%s> while parsing %s %s\n", key, item->key, item->value ); + fprintf (stderr, "hotkeys: Unknown key: <%s> while parsing %s %s\n", key, item->key, item->value); break; } } - } while ( !done ); + } while (!done); if (done) { - if ( cmd_entry->keycode == 0 ) { - fprintf( stderr, "hotkeys: Key not found while parsing %s %s\n", item->key, item->value); + if (cmd_entry->keycode == 0) { + fprintf (stderr, "hotkeys: Key not found while parsing %s %s\n", item->key, item->value); } else { command = trim (command); - cmd_entry->func = get_command( command ); - if ( !cmd_entry->func ) + cmd_entry->func = get_command (command); + if (!cmd_entry->func) { - fprintf( stderr, "hotkeys: Unknown command <%s> while parsing %s %s\n", command, item->key, item->value); + fprintf (stderr, "hotkeys: Unknown command <%s> while parsing %s %s\n", command, item->key, item->value); } else { command_count++; @@ -260,56 +270,56 @@ hotkeys_load (DB_functions_t *api) { } static void -cleanup() { +cleanup () { command_count = 0; - XCloseDisplay( disp ); + XCloseDisplay (disp); } static void -hotkeys_event_loop( uintptr_t unused ) { +hotkeys_event_loop (void *unused) { int i; while (!finished) { XEvent event; - while ( XPending( disp ) ) + while (XPending (disp)) { - XNextEvent( disp, &event ); + XNextEvent (disp, &event); - if ( event.xkey.type == KeyPress ) + if (event.xkey.type == KeyPress) { - for ( i = 0; i < command_count; i++ ) + for (i = 0; i < command_count; i++) if ( (event.xkey.keycode == commands[ i ].keycode) && - ((event.xkey.state & commands[ i ].modifier) == commands[ i ].modifier) ) + ( (event.xkey.state & commands[ i ].modifier) == commands[ i ].modifier)) { - commands[i].func(); + commands[i].func (); break; } } } - usleep( 200 * 1000 ); + usleep (200 * 1000); } } static int x_err_handler (Display *d, XErrorEvent *evt) { char buffer[1024]; - XGetErrorText(d, evt->error_code, buffer, sizeof (buffer)); - fprintf( stderr, "hotkeys: xlib error: %s\n", buffer); + XGetErrorText (d, evt->error_code, buffer, sizeof (buffer)); + fprintf (stderr, "hotkeys: xlib error: %s\n", buffer); } static int hotkeys_start (void) { finished = 0; loop_tid = 0; - disp = XOpenDisplay( NULL ); - if ( !disp ) + disp = XOpenDisplay (NULL); + if (!disp) { - fprintf( stderr, "hotkeys: could not open display\n" ); + fprintf (stderr, "hotkeys: could not open display\n"); return -1; } - XSetErrorHandler( x_err_handler ); + XSetErrorHandler (x_err_handler); - read_config( disp ); + read_config (disp); int i; // need to grab it here to prevent gdk_x_error from being called while we're // doing it on other thread @@ -318,10 +328,10 @@ hotkeys_start (void) { } XSync (disp, 0); if (command_count > 0) { - loop_tid = deadbeef->thread_start( hotkeys_event_loop, 0 ); + loop_tid = deadbeef->thread_start (hotkeys_event_loop, 0); } else { - cleanup(); + cleanup (); } } @@ -329,8 +339,8 @@ static int hotkeys_stop (void) { if (loop_tid) { finished = 1; - deadbeef->thread_join( loop_tid ); - cleanup(); + deadbeef->thread_join (loop_tid); + cleanup (); } } diff --git a/plugins/lastfm/lastfm.c b/plugins/lastfm/lastfm.c index 776e2bbe..9864ed02 100644 --- a/plugins/lastfm/lastfm.c +++ b/plugins/lastfm/lastfm.c @@ -34,7 +34,8 @@ static DB_misc_t plugin; static DB_functions_t *deadbeef; #define LFM_CLIENTID "ddb" -#define SCROBBLER_URL_V1 "http://post.audioscrobbler.com" +#define SCROBBLER_URL_LFM "http://post.audioscrobbler.com" +#define SCROBBLER_URL_LIBRE "http://turtle.libre.fm" static char lfm_user[100]; static char lfm_pass[100]; @@ -47,7 +48,6 @@ static uintptr_t lfm_mutex; static uintptr_t lfm_cond; static int lfm_stopthread; static intptr_t lfm_tid; -static int lfm_abort; DB_plugin_t * lastfm_load (DB_functions_t *api) { @@ -65,9 +65,24 @@ static char lfm_nowplaying[2048]; // packet for nowplaying, or "" //static char lfm_subm_queue[LFM_SUBMISSION_QUEUE_SIZE][2048]; static DB_playItem_t *lfm_subm_queue[LFM_SUBMISSION_QUEUE_SIZE]; +static void +lfm_update_auth (void) { + const char *user = deadbeef->conf_get_str ("lastfm.login", ""); + const char *pass = deadbeef->conf_get_str ("lastfm.password", ""); + if (strcmp (user, lfm_user) || strcmp (pass, lfm_pass)) { + strcpy (lfm_user, user); + strcpy (lfm_pass, pass); + lfm_sess[0] = 0; + } +} + static size_t lastfm_curl_res (void *ptr, size_t size, size_t nmemb, void *stream) { + if (lfm_stopthread) { + trace ("lfm: lastfm_curl_res: aborting current request\n"); + return 0; + } int len = size * nmemb; if (lfm_reply_sz + len >= MAX_REPLY) { trace ("reply is too large. stopping.\n"); @@ -85,7 +100,7 @@ lastfm_curl_res (void *ptr, size_t size, size_t nmemb, void *stream) static int lfm_curl_control (void *stream, double dltotal, double dlnow, double ultotal, double ulnow) { trace ("lfm_curl_control\n"); - if (lfm_abort) { + if (lfm_stopthread) { trace ("lfm: aborting current request\n"); return -1; } @@ -100,7 +115,6 @@ curl_req_send (const char *req, const char *post) { trace ("lastfm: failed to init curl\n"); return -1; } - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(curl, CURLOPT_URL, req); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, lastfm_curl_res); memset(lfm_err, 0, sizeof(lfm_err)); @@ -162,6 +176,7 @@ curl_req_cleanup (void) { static int auth (void) { + lfm_update_auth (); if (lfm_sess[0]) { return 0; } @@ -176,10 +191,11 @@ auth (void) { deadbeef->md5 (sig, token, strlen (token)); deadbeef->md5_to_str (token, sig); + const char *scrobbler_url = deadbeef->conf_get_str ("lastfm.scrobbler_url", SCROBBLER_URL_LFM); #if LFM_TESTMODE - snprintf (req, sizeof (req), "%s/?hs=true&p=1.2.1&c=tst&v=1.0&u=%s&t=%d&a=%s", SCROBBLER_URL_V1, lfm_user, timestamp, token); + snprintf (req, sizeof (req), "%s/?hs=true&p=1.2.1&c=tst&v=1.0&u=%s&t=%d&a=%s", scrobbler_url, lfm_user, timestamp, token); #else - snprintf (req, sizeof (req), "%s/?hs=true&p=1.2.1&c=%s&v=%d.%d&u=%s&t=%d&a=%s", SCROBBLER_URL_V1, LFM_CLIENTID, plugin.plugin.version_major, plugin.plugin.version_minor, lfm_user, timestamp, token); + snprintf (req, sizeof (req), "%s/?hs=true&p=1.2.1&c=%s&v=%d.%d&u=%s&t=%d&a=%s", scrobbler_url, LFM_CLIENTID, plugin.plugin.version_major, plugin.plugin.version_minor, lfm_user, timestamp, token); #endif // handshake int status = curl_req_send (req, NULL); @@ -445,9 +461,9 @@ lfm_format_uri (int subm, DB_playItem_t *song, char *out, int outl) { } static int -lastfm_songstarted (DB_event_song_t *ev, uintptr_t data) { +lastfm_songstarted (DB_event_track_t *ev, uintptr_t data) { deadbeef->mutex_lock (lfm_mutex); - if (lfm_format_uri (-1, ev->song, lfm_nowplaying, sizeof (lfm_nowplaying)) < 0) { + if (lfm_format_uri (-1, ev->track, lfm_nowplaying, sizeof (lfm_nowplaying)) < 0) { lfm_nowplaying[0] = 0; } // trace ("%s\n", lfm_nowplaying); @@ -460,28 +476,28 @@ lastfm_songstarted (DB_event_song_t *ev, uintptr_t data) { } static int -lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) { +lastfm_songfinished (DB_event_track_t *ev, uintptr_t data) { trace ("lfm songfinished\n"); #if !LFM_IGNORE_RULES // check submission rules // duration must be >= 30 sec - if (deadbeef->pl_get_item_duration (ev->song) < 30) { - trace ("song duration is %f seconds. not eligible for submission\n", ev->song->duration); + if (deadbeef->pl_get_item_duration (ev->track) < 30) { + trace ("track duration is %f seconds. not eligible for submission\n", deadbeef->pl_get_item_duration (ev->track)); return 0; } // must be played for >=240sec of half the total time - if (ev->song->playtime < 240 && ev->song->playtime < deadbeef->pl_get_item_duration (ev->song)/2) { - trace ("song playtime=%f seconds. not eligible for submission\n", ev->song->playtime); + if (ev->track->playtime < 240 && ev->track->playtime < deadbeef->pl_get_item_duration (ev->track)/2) { + trace ("track playtime=%f seconds. not eligible for submission\n", ev->track->playtime); return 0; } #endif - if (!deadbeef->pl_find_meta (ev->song, "artist") - || !deadbeef->pl_find_meta (ev->song, "title") -// || !deadbeef->pl_find_meta (ev->song, "album") + if (!deadbeef->pl_find_meta (ev->track, "artist") + || !deadbeef->pl_find_meta (ev->track, "title") +// || !deadbeef->pl_find_meta (ev->track, "album") ) { - trace ("lfm: not enough metadata for submission, artist=%s, title=%s, album=%s\n", deadbeef->pl_find_meta (ev->song, "artist"), deadbeef->pl_find_meta (ev->song, "title"), deadbeef->pl_find_meta (ev->song, "album")); + trace ("lfm: not enough metadata for submission, artist=%s, title=%s, album=%s\n", deadbeef->pl_find_meta (ev->track, "artist"), deadbeef->pl_find_meta (ev->track, "title"), deadbeef->pl_find_meta (ev->track, "album")); return 0; } deadbeef->mutex_lock (lfm_mutex); @@ -490,7 +506,7 @@ lastfm_songfinished (DB_event_song_t *ev, uintptr_t data) { if (!lfm_subm_queue[i]) { trace ("lfm: song is now in queue for submission\n"); lfm_subm_queue[i] = deadbeef->pl_item_alloc (); - deadbeef->pl_item_copy (lfm_subm_queue[i], ev->song); + deadbeef->pl_item_copy (lfm_subm_queue[i], ev->track); break; } } @@ -512,6 +528,7 @@ lfm_send_nowplaying (void) { snprintf (s, sizeof (s), "s=%s&", lfm_sess); int l = strlen (lfm_nowplaying); strcpy (lfm_nowplaying+l, s); + trace ("content:\n%s\n", lfm_nowplaying); #if !LFM_NOSEND for (int attempts = 2; attempts > 0; attempts--) { int status = curl_req_send (lfm_nowplaying_url, lfm_nowplaying); @@ -625,24 +642,29 @@ lfm_send_submissions (void) { } static void -lfm_thread (uintptr_t ctx) { +lfm_thread (void *ctx) { //trace ("lfm_thread started\n"); for (;;) { - deadbeef->cond_wait (lfm_cond, lfm_mutex); - trace ("cond signalled!\n"); if (lfm_stopthread) { deadbeef->mutex_unlock (lfm_mutex); - deadbeef->cond_signal (lfm_cond); trace ("lfm_thread end\n"); return; } + trace ("lfm wating for cond...\n"); + deadbeef->cond_wait (lfm_cond, lfm_mutex); + if (lfm_stopthread) { + deadbeef->mutex_unlock (lfm_mutex); + trace ("lfm_thread end[2]\n"); + return; + } + trace ("cond signalled!\n"); deadbeef->mutex_unlock (lfm_mutex); + trace ("lfm sending nowplaying...\n"); // try to send nowplaying if (lfm_nowplaying[0]) { lfm_send_nowplaying (); } - lfm_send_submissions (); } } @@ -738,20 +760,14 @@ auth_v2 (void) { static int lastfm_start (void) { - // load login/pass + lfm_sess[0] = 0; lfm_mutex = 0; lfm_cond = 9; lfm_tid = 0; - lfm_abort = 0; - strcpy (lfm_user, deadbeef->conf_get_str ("lastfm.login", "")); - strcpy (lfm_pass, deadbeef->conf_get_str ("lastfm.password", "")); - if (!lfm_user[0] || !lfm_pass[0]) { - return 0; - } lfm_stopthread = 0; lfm_mutex = deadbeef->mutex_create (); lfm_cond = deadbeef->cond_create (); - lfm_tid = deadbeef->thread_start (lfm_thread, 0); + lfm_tid = deadbeef->thread_start (lfm_thread, NULL); // subscribe to frameupdate event deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGSTARTED, DB_CALLBACK (lastfm_songstarted), 0); deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_SONGFINISHED, DB_CALLBACK (lastfm_songfinished), 0); @@ -763,11 +779,13 @@ static int lastfm_stop (void) { trace ("lastfm_stop\n"); if (lfm_mutex) { - lfm_abort = 1; + lfm_stopthread = 1; deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGSTARTED, DB_CALLBACK (lastfm_songstarted), 0); deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_SONGFINISHED, DB_CALLBACK (lastfm_songfinished), 0); - lfm_stopthread = 1; + + trace ("lfm_stop signalling cond\n"); deadbeef->cond_signal (lfm_cond); + trace ("waiting for thread to finish\n"); deadbeef->thread_join (lfm_tid); lfm_tid = 0; deadbeef->cond_free (lfm_cond); @@ -776,6 +794,12 @@ lastfm_stop (void) { return 0; } +static const char settings_dlg[] = + "property Username entry lastfm.login \"\";\n" + "property Password password lastfm.password \"\";" + "property \"Scrobble URL\" entry lastfm.scrobbler_url \""SCROBBLER_URL_LFM"\";" +; + // define plugin interface static DB_misc_t plugin = { DB_PLUGIN_SET_API_VERSION @@ -788,5 +812,6 @@ static DB_misc_t plugin = { .plugin.email = "waker@users.sourceforge.net", .plugin.website = "http://deadbeef.sf.net", .plugin.start = lastfm_start, - .plugin.stop = lastfm_stop + .plugin.stop = lastfm_stop, + .plugin.configdialog = settings_dlg }; diff --git a/plugins/mpgmad/mpgmad.c b/plugins/mpgmad/mpgmad.c index 567c8405..abee585b 100644 --- a/plugins/mpgmad/mpgmad.c +++ b/plugins/mpgmad/mpgmad.c @@ -792,7 +792,7 @@ cmp3_stream_frame (void) { // synthesize single frame mad_synth_frame(&synth,&frame); buffer.decode_remaining = synth.pcm.length; - deadbeef->playback_update_bitrate (frame.header.bitrate/1000); + deadbeef->streamer_set_bitrate (frame.header.bitrate/1000); break; } return eof; diff --git a/plugins/nullout/Makefile.am b/plugins/nullout/Makefile.am new file mode 100644 index 00000000..da914823 --- /dev/null +++ b/plugins/nullout/Makefile.am @@ -0,0 +1,5 @@ +nulloutdir = $(libdir)/$(PACKAGE) +pkglib_LTLIBRARIES = nullout.la +nullout_la_SOURCES = nullout.c +nullout_la_LDFLAGS = -module +AM_CFLAGS = $(CFLAGS) -std=c99 diff --git a/plugins/nullout/nullout.c b/plugins/nullout/nullout.c new file mode 100644 index 00000000..1e08121b --- /dev/null +++ b/plugins/nullout/nullout.c @@ -0,0 +1,242 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <stdint.h> +#include <unistd.h> +#include <sys/prctl.h> +#include <stdio.h> +#include <string.h> +#include "deadbeef.h" + +//#define trace(...) { fprintf(stderr, __VA_ARGS__); } +#define trace(fmt,...) + +static DB_output_t plugin; +DB_functions_t *deadbeef; + +static intptr_t null_tid; +static int null_terminate; +static int null_rate; +static int state; + +static void +pnull_callback (char *stream, int len); + +static void +pnull_thread (void *context); + +static int +pnull_init (void); + +static int +pnull_free (void); + +static int +pnull_change_rate (int rate); + +static int +pnull_play (void); + +static int +pnull_stop (void); + +static int +pnull_pause (void); + +static int +pnull_unpause (void); + +static int +pnull_get_rate (void); + +static int +pnull_get_bps (void); + +static int +pnull_get_channels (void); + +static int +pnull_get_endianness (void); + +int +pnull_init (void) { + trace ("pnull_init\n"); + state = OUTPUT_STATE_STOPPED; + null_rate = 44100; + null_terminate = 0; + null_tid = deadbeef->thread_start (pnull_thread, NULL); + return 0; +} + +int +pnull_change_rate (int rate) { + null_rate = rate; + return null_rate; +} + +int +pnull_free (void) { + trace ("pnull_free\n"); + if (!null_terminate) { + if (null_tid) { + null_terminate = 1; + deadbeef->thread_join (null_tid); + } + null_tid = 0; + state = OUTPUT_STATE_STOPPED; + null_terminate = 0; + } + return 0; +} + +int +pnull_play (void) { + if (!null_tid) { + pnull_init (); + } + state = OUTPUT_STATE_PLAYING; + return 0; +} + +int +pnull_stop (void) { + state = OUTPUT_STATE_STOPPED; + deadbeef->streamer_reset (1); + return 0; +} + +int +pnull_pause (void) { + if (state == OUTPUT_STATE_STOPPED) { + return -1; + } + // set pause state + state = OUTPUT_STATE_PAUSED; + return 0; +} + +int +pnull_unpause (void) { + // unset pause state + if (state == OUTPUT_STATE_PAUSED) { + state = OUTPUT_STATE_PLAYING; + } + return 0; +} + +int +pnull_get_rate (void) { + return null_rate; +} + +int +pnull_get_bps (void) { + return 16; +} + +int +pnull_get_channels (void) { + return 2; +} + +static int +pnull_get_endianness (void) { +#if WORDS_BIGENDIAN + return 1; +#else + return 0; +#endif +} + +static void +pnull_thread (void *context) { + prctl (PR_SET_NAME, "deadbeef-null", 0, 0, 0, 0); + for (;;) { + if (null_terminate) { + break; + } + if (state != OUTPUT_STATE_PLAYING) { + usleep (10000); + continue; + } + + char buf[4096]; + pnull_callback (buf, 1024); + } +} + +static void +pnull_callback (char *stream, int len) { + if (!deadbeef->streamer_ok_to_read (len)) { + memset (stream, 0, len); + return; + } + int bytesread = deadbeef->streamer_read (stream, len); + + if (bytesread < len) { + memset (stream + bytesread, 0, len-bytesread); + } +} + +int +pnull_get_state (void) { + return state; +} + +int +null_start (void) { + return 0; +} + +int +null_stop (void) { + return 0; +} + +DB_plugin_t * +nullout_load (DB_functions_t *api) { + deadbeef = api; + return DB_PLUGIN (&plugin); +} + +// define plugin interface +static DB_output_t plugin = { + DB_PLUGIN_SET_API_VERSION + .plugin.version_major = 0, + .plugin.version_minor = 1, + .plugin.nostop = 1, + .plugin.type = DB_PLUGIN_OUTPUT, + .plugin.name = "null output plugin", + .plugin.descr = "doesn't play anything", + .plugin.author = "Alexey Yakovenko", + .plugin.email = "waker@users.sourceforge.net", + .plugin.website = "http://deadbeef.sf.net", + .plugin.start = null_start, + .plugin.stop = null_stop, + .init = pnull_init, + .free = pnull_free, + .change_rate = pnull_change_rate, + .play = pnull_play, + .stop = pnull_stop, + .pause = pnull_pause, + .unpause = pnull_unpause, + .state = pnull_get_state, + .samplerate = pnull_get_rate, + .bitspersample = pnull_get_bps, + .channels = pnull_get_channels, + .endianness = pnull_get_endianness, +}; diff --git a/plugins/sndfile/sndfile.c b/plugins/sndfile/sndfile.c index 46e3a99f..dfb956de 100644 --- a/plugins/sndfile/sndfile.c +++ b/plugins/sndfile/sndfile.c @@ -23,26 +23,75 @@ #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) -#define trace(...) { fprintf(stderr, __VA_ARGS__); } -//#define trace(fmt,...) +//#define trace(...) { fprintf(stderr, __VA_ARGS__); } +#define trace(fmt,...) static DB_decoder_t plugin; static DB_functions_t *deadbeef; typedef struct { SNDFILE *ctx; + DB_FILE *file; int startsample; int endsample; int currentsample; + int bitrate; } sndfilectx_t; static sndfilectx_t sfctx; + +// vfs wrapper for sf +static sf_count_t +sf_vfs_get_filelen (void *user_data) { + sndfilectx_t *ctx = user_data; + return deadbeef->fgetlength (ctx->file); +} + +static sf_count_t +sf_vfs_read (void *ptr, sf_count_t count, void *user_data) { + sndfilectx_t *ctx = user_data; + return deadbeef->fread (ptr, 1, count, ctx->file); +} + +static sf_count_t +sf_vfs_write (const void *ptr, sf_count_t count, void *user_data) { + return -1; +} + +static sf_count_t +sf_vfs_seek (sf_count_t offset, int whence, void *user_data) { + sndfilectx_t *ctx = user_data; + return deadbeef->fseek (ctx->file, offset, whence); +} + +static sf_count_t +sf_vfs_tell (void *user_data) { + sndfilectx_t *ctx = user_data; + return deadbeef->ftell (ctx->file); +} + +static SF_VIRTUAL_IO vfs = { + .get_filelen = sf_vfs_get_filelen, + .seek = sf_vfs_seek, + .read = sf_vfs_read, + .write = sf_vfs_write, + .tell = sf_vfs_tell +}; + static int sndfile_init (DB_playItem_t *it) { memset (&sfctx, 0, sizeof (sfctx)); SF_INFO inf; - sfctx.ctx = sf_open (it->fname, SFM_READ, &inf); + DB_FILE *fp = deadbeef->fopen (it->fname); + if (!fp) { + trace ("sndfile: failed to open %s\n", it->fname); + return -1; + } + int fsize = deadbeef->fgetlength (fp); + + sfctx.file = fp; + sfctx.ctx = sf_open_virtual (&vfs, SFM_READ, &inf, &sfctx); if (!sfctx.ctx) { return -1; } @@ -62,6 +111,15 @@ sndfile_init (DB_playItem_t *it) { sfctx.startsample = 0; sfctx.endsample = inf.frames-1; } + // hack bitrate + float sec = (float)(sfctx.endsample-sfctx.startsample) / inf.samplerate; + if (sec != 0) { + sfctx.bitrate = fsize / sec * 8 / 1000; + } + else { + sfctx.bitrate = -1; + } + return 0; } @@ -70,6 +128,9 @@ sndfile_free (void) { if (sfctx.ctx) { sf_close (sfctx.ctx); } + if (sfctx.file) { + deadbeef->fclose (sfctx.file); + } memset (&sfctx, 0, sizeof (sfctx)); } @@ -86,6 +147,9 @@ sndfile_read_int16 (char *bytes, int size) { sfctx.currentsample += n; size = n * 2 * plugin.info.channels; plugin.info.readpos = (float)(sfctx.currentsample-sfctx.startsample)/plugin.info.samplerate; + if (sfctx.bitrate > 0) { + deadbeef->streamer_set_bitrate (sfctx.bitrate); + } return size; } @@ -102,6 +166,9 @@ sndfile_read_float32 (char *bytes, int size) { sfctx.currentsample += n; size = n * 4 * plugin.info.channels; plugin.info.readpos = (float)(sfctx.currentsample-sfctx.startsample)/plugin.info.samplerate; + if (sfctx.bitrate > 0) { + deadbeef->streamer_set_bitrate (sfctx.bitrate); + } return size; } @@ -120,14 +187,22 @@ sndfile_seek (float sec) { static DB_playItem_t * sndfile_insert (DB_playItem_t *after, const char *fname) { SF_INFO inf; - SNDFILE *sf = sf_open (fname, SFM_READ, &inf); - if (!sf) { + sndfilectx_t sfctx; + sfctx.file = deadbeef->fopen (fname); + if (!sfctx.file) { + trace ("sndfile: failed to open %s\n", fname); + return NULL; + } + sfctx.ctx = sf_open_virtual (&vfs, SFM_READ, &inf, &sfctx); + if (!sfctx.ctx) { trace ("sndfile: sf_open failed"); return NULL; } int totalsamples = inf.frames; int samplerate = inf.samplerate; - sf_close (sf); + sf_close (sfctx.ctx); + deadbeef->fclose (sfctx.file); + float duration = (float)totalsamples / samplerate; DB_playItem_t *cue_after = deadbeef->pl_insert_cue (after, fname, &plugin, "sndfile", totalsamples, samplerate); if (cue_after) { diff --git a/plugins/vfs_curl/vfs_curl.c b/plugins/vfs_curl/vfs_curl.c index 034220e2..b4ea90fa 100644 --- a/plugins/vfs_curl/vfs_curl.c +++ b/plugins/vfs_curl/vfs_curl.c @@ -57,6 +57,8 @@ typedef struct { char *content_name; char *content_genre; uint8_t status; +// int icy_metaint; +// int wait_meta; // flags (bitfields to save some space) unsigned seektoend : 1; // indicates that next tell must return length unsigned gotheader : 1; // tells that all headers (including ICY) were processed (to start reading body) @@ -137,10 +139,30 @@ http_curl_write (void *ptr, size_t size, size_t nmemb, void *stream) { deadbeef->mutex_unlock (fp->mutex); break; } +#if 0 + if (fp->wait_meta == 0) { + char sz; + memcpy (&sz, ptr, 1); + printf ("reading %d bytes of metadata, seekpos:%d!\n", (int)sz*4, fp->pos); + ptr += 16 * sz; + avail -= 16 * sz + 1; + printf ("avail=%d!\n", avail); + fp->wait_meta = fp->icy_metaint; + } +#endif int sz = BUFFER_SIZE/2 - fp->remaining; // number of bytes free in buffer // don't allow to fill more than half -- used for seeking backwards + if (sz > 5000) { // wait until there are at least 5k bytes free int cp = min (avail, sz); +#if 0 + if (fp->wait_meta - cp <= 0) { + printf ("cp=%d->%d\n", cp, fp->wait_meta); + cp = fp->wait_meta; + } + fp->wait_meta -= cp; +#endif + int writepos = (fp->pos + fp->remaining) & BUFFER_MASK; // copy 1st portion (before end of buffer int part1 = BUFFER_SIZE - writepos; @@ -252,8 +274,6 @@ http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream) assert (stream); HTTP_FILE *fp = (HTTP_FILE *)stream; const uint8_t c_type_str[] ="Content-Type:"; - const uint8_t icy_name_str[] ="icy-name:"; - const uint8_t icy_genre_str[] ="icy-genre:"; const uint8_t *p = ptr; const uint8_t *end = p + size*nmemb; uint8_t key[256]; @@ -282,6 +302,11 @@ http_content_header_handler (void *ptr, size_t size, size_t nmemb, void *stream) } fp->content_genre = strdup (value); } +// else if (!strcasecmp (key, "icy-metaint")) { +// //printf ("icy-metaint: %d\n", atoi (value)); +// fp->icy_metaint = atoi (value); +// fp->wait_meta = fp->icy_metaint; +// } } if (!fp->icyheader) { fp->gotsomeheader = 1; @@ -307,7 +332,7 @@ http_curl_control (void *stream, double dltotal, double dlnow, double ultotal, d } static void -http_thread_func (uintptr_t ctx) { +http_thread_func (void *ctx) { HTTP_FILE *fp = (HTTP_FILE *)ctx; CURL *curl; curl = curl_easy_init (); @@ -338,6 +363,7 @@ http_thread_func (uintptr_t ctx) { trace ("vfs_curl: started loading data\n"); for (;;) { +// struct curl_slist *headers = NULL; curl_easy_reset (curl); curl_easy_setopt (curl, CURLOPT_URL, fp->url); curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1); @@ -354,6 +380,8 @@ http_thread_func (uintptr_t ctx) { // enable up to 10 redirects curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt (curl, CURLOPT_MAXREDIRS, 10); +// headers = curl_slist_append (headers, "Icy-Metadata:1"); +// curl_easy_setopt (curl, CURLOPT_HTTPHEADER, headers); if (fp->pos > 0) { curl_easy_setopt (curl, CURLOPT_RESUME_FROM, fp->pos); } @@ -401,6 +429,7 @@ http_thread_func (uintptr_t ctx) { fp->status = STATUS_INITIAL; trace ("seeking to %d\n", fp->pos); deadbeef->mutex_unlock (fp->mutex); +// curl_slist_free_all (headers); } curl_easy_cleanup (curl); @@ -413,7 +442,7 @@ http_thread_func (uintptr_t ctx) { static void http_start_streamer (HTTP_FILE *fp) { fp->mutex = deadbeef->mutex_create (); - fp->tid = deadbeef->thread_start (http_thread_func, (uintptr_t)fp); + fp->tid = deadbeef->thread_start (http_thread_func, fp); } static DB_FILE * diff --git a/plugins/vorbis/vorbis.c b/plugins/vorbis/vorbis.c index 5679f852..0b6507b4 100644 --- a/plugins/vorbis/vorbis.c +++ b/plugins/vorbis/vorbis.c @@ -280,6 +280,7 @@ cvorbis_read (char *bytes, int size) { } plugin.info.readpos = (float)(ov_pcm_tell(&vorbis_file)-startsample)/vi->rate; trace ("cvorbis_read got %d bytes, readpos %f, currentsample %d, ret %d\n", initsize-size, plugin.info.readpos, currentsample, ret); + deadbeef->streamer_set_bitrate (ov_bitrate_instant (&vorbis_file)/1000); return initsize - size; } @@ -370,8 +371,8 @@ cvorbis_insert (DB_playItem_t *after, const char *fname) { } static int -vorbis_trackdeleted (DB_event_song_t *ev, uintptr_t data) { - if (ev->song == ptrack) { +vorbis_trackdeleted (DB_event_track_t *ev, uintptr_t data) { + if (ev->track == ptrack) { ptrack = NULL; } return 0; @@ -380,11 +381,13 @@ vorbis_trackdeleted (DB_event_song_t *ev, uintptr_t data) { static int vorbis_start (void) { deadbeef->ev_subscribe (DB_PLUGIN (&plugin), DB_EV_TRACKDELETED, DB_CALLBACK (vorbis_trackdeleted), 0); + return 0; } static int vorbis_stop (void) { deadbeef->ev_unsubscribe (DB_PLUGIN (&plugin), DB_EV_TRACKDELETED, DB_CALLBACK (vorbis_trackdeleted), 0); + return 0; } static const char * exts[] = { "ogg", NULL }; diff --git a/plugins/vtx/Makefile.am b/plugins/vtx/Makefile.am new file mode 100644 index 00000000..abfa8c98 --- /dev/null +++ b/plugins/vtx/Makefile.am @@ -0,0 +1,6 @@ +vtxdir = $(libdir)/$(PACKAGE) +pkglib_LTLIBRARIES = vtx.la +vtx_la_SOURCES = vtx.c ay8912.c ayemu_8912.h ayemu.h ayemu_vtxfile.h lh5dec.c vtxfile.c +vtx_la_LDFLAGS = -module + +AM_CFLAGS = $(CFLAGS) -std=c99 diff --git a/plugins/vtx/ay8912.c b/plugins/vtx/ay8912.c new file mode 100644 index 00000000..9ac72245 --- /dev/null +++ b/plugins/vtx/ay8912.c @@ -0,0 +1,498 @@ +/* AY/YM emulator implementation. */ + +#include "ayemu.h" + +#define debuglog stderr; + +char *ayemu_err; + +static const char VERSION[] = "libayemu 0.9"; + +const int MAGIC1 = 0xcdef; /* for check ayemu_t structure inited */ + +enum { +/* Max amplitude value for stereo signal for avoiding for possible + folowwing SSRC for clipping */ + AYEMU_MAX_AMP = 24575, + AYEMU_DEFAULT_CHIP_FREQ = 1773400 +}; + +/* sound chip volume envelops (will calculated by gen_env()) */ +static int bEnvGenInit = 0; +static int Envelope [16][128]; + + +/* AY volume table (c) by V_Soft and Lion 17 */ +static int Lion17_AY_table [16] = + { 0, 513, 828, 1239, 1923, 3238, 4926, 9110, + 10344, 17876, 24682, 30442, 38844, 47270, 56402, 65535}; + +/* YM volume table (c) by V_Soft and Lion 17 */ +static int Lion17_YM_table [32] = + { 0, 0, 190, 286, 375, 470, 560, 664, + 866, 1130, 1515, 1803, 2253, 2848, 3351, 3862, + 4844, 6058, 7290, 8559, 10474, 12878, 15297, 17787, + 21500, 26172, 30866, 35676, 42664, 50986, 58842, 65535}; + +/* AY volume table (c) by Hacker KAY */ +static int KAY_AY_table [16] = + { 0, 836, 1212, 1773, 2619, 3875, 5397, 8823, + 10392, 16706, 23339, 29292, 36969, 46421, 55195, 65535}; + +/* YM volume table (c) by Hacker KAY */ +static int KAY_YM_table [32] = + { 0, 0, 248, 450, 670, 826, 1010, 1239, + 1552, 1919, 2314, 2626, 3131, 3778, 4407, 5031, + 5968, 7161, 8415, 9622, 11421, 13689, 15957, 18280, + 21759, 26148, 30523, 34879, 41434, 49404, 57492, 65535}; + +/* default equlaizer (layout) settings for AY and YM, 7 stereo types */ +static const int default_layout [2][7][6] = { + { + /* A_l, A_r, B_l, B_r, C_l, C_r */ + + /* for AY */ + {100, 100, 100, 100, 100, 100}, // _MONO + {100, 33, 70, 70, 33, 100}, // _ABC + {100, 33, 33, 100, 70, 70}, // _ACB + {70, 70, 100, 33, 33, 100}, // _BAC + {33, 100, 100, 33, 70, 70}, // _BCA + {70, 70, 33, 100, 100, 33}, // _CAB + {33, 100, 70, 70, 100, 33}}, // _CBA + { + /* for YM */ + {100, 100, 100, 100, 100, 100}, // _MONO + {100, 5, 70, 70, 5, 100}, // _ABC + {100, 5, 5, 100, 70, 70}, // _ACB + {70, 70, 100, 5, 5, 100}, // _BAC + {5, 100, 100, 5, 70, 70}, // _BCA + {70, 70, 5, 100, 100, 5}, // _CAB + {5, 100, 70, 70, 100, 5}} // _CBA +}; + + +static int check_magic(ayemu_ay_t *ay) +{ + if (ay->magic == MAGIC1) + return 1; + fprintf(stderr, "libayemu: passed pointer %p to uninitialized ayemu_ay_t structure\n", ay); + return 0; +} + + +/* make chip hardware envelop tables. + Will execute once before first use. */ +static void gen_env() +{ + int env; + int pos; + int hold; + int dir; + int vol; + + for (env = 0; env < 16; env++) { + hold = 0; + dir = (env & 4)? 1 : -1; + vol = (env & 4)? -1 : 32; + for (pos = 0; pos < 128; pos++) { + if (!hold) { + vol += dir; + if (vol < 0 || vol >= 32) { + if ( env & 8 ) { + if ( env & 2 ) dir = -dir; + vol = (dir > 0 )? 0:31; + if ( env & 1 ) { + hold = 1; + vol = ( dir > 0 )? 31:0; + } + } else { + vol = 0; + hold = 1; + } + } + } + Envelope[env][pos] = vol; + } + } + bEnvGenInit = 1; +} + + +/** + * \retval ayemu_init none. + * +*/ +void ayemu_init(ayemu_ay_t *ay) +{ + ay->default_chip_flag = 1; + ay->ChipFreq = AYEMU_DEFAULT_CHIP_FREQ; + ay->default_stereo_flag = 1; + ay->default_sound_format_flag = 1; + ay->dirty = 1; + ay->magic = MAGIC1; + + ayemu_reset(ay); +} + +/** Reset AY/YM chip. + * + * \arg \c ay - pointer to ayemu_ay_t structure. + * \return none. + */ +void ayemu_reset(ayemu_ay_t *ay) +{ + if (!check_magic(ay)) return; + + ay->cnt_a = ay->cnt_b = ay->cnt_c = ay->cnt_n = ay->cnt_e = 0; + ay->bit_a = ay->bit_b = ay->bit_c = ay->bit_n = 0; + ay->env_pos = ay->EnvNum = 0; + ay->Cur_Seed = 0xffff; +} + + +static void set_table_ay (ayemu_ay_t *ay, int tbl[16]) +{ + int n; + for (n = 0; n < 32; n++) + ay->table[n] = tbl[n/2]; + ay->type = AYEMU_AY; +} + +static void set_table_ym (ayemu_ay_t *ay, int tbl[32]) +{ + int n; + for (n = 0; n < 32; n++) + ay->table[n] = tbl[n]; + ay->type = AYEMU_YM; +} + + +/** Set chip type. */ +int ayemu_set_chip_type(ayemu_ay_t *ay, ayemu_chip_t type, int *custom_table) +{ +if (!check_magic(ay)) + return 0; + + if (!(type == AYEMU_AY_CUSTOM || type == AYEMU_YM_CUSTOM) && custom_table != NULL) { + ayemu_err = "For non-custom chip type 'custom_table' param must be NULL"; + return 0; + } + + switch(type) { + case AYEMU_AY: + case AYEMU_AY_LION17: + set_table_ay(ay, Lion17_AY_table); + break; + case AYEMU_YM: + case AYEMU_YM_LION17: + set_table_ym(ay, Lion17_YM_table); + break; + case AYEMU_AY_KAY: + set_table_ay(ay, KAY_AY_table); + break; + case AYEMU_YM_KAY: + set_table_ym(ay, KAY_YM_table); + break; + case AYEMU_AY_CUSTOM: + set_table_ay(ay, custom_table); + break; + case AYEMU_YM_CUSTOM: + set_table_ym(ay, custom_table); + break; + default: + ayemu_err = "Incorrect chip type"; + return 0; + } + + ay->default_chip_flag = 0; + ay->dirty = 1; + return 1; +} + + +/** Set chip frequency. */ +void ayemu_set_chip_freq(ayemu_ay_t *ay, int chipfreq) +{ + if (!check_magic(ay)) return; + + ay->ChipFreq = chipfreq; + ay->dirty = 1; +} + +/*! Set output sound format + * \arg \c ay - pointer to ayemu_t structure + * \arg \c freq - sound freq (44100 for example) + * \arg \c chans - number of channels (1-mono, 2-stereo) + * \arg \c bits - now supported only 16 and 8. + * \retval \b 1 on success, \b 0 if error occure + */ +int ayemu_set_sound_format (ayemu_ay_t *ay, int freq, int chans, int bits) +{ + if (!check_magic(ay)) + return 0; + + if (!(bits == 16 || bits == 8)) { + ayemu_err = "Incorrect bits value"; + return 0; + } + else if (!(chans == 1 || chans == 2)) { + ayemu_err = "Incorrect number of channels"; + return 0; + } + else if (freq < 50) { + ayemu_err = "Incorrect output sound freq"; + return 0; + } + else { + ay->sndfmt.freq = freq; + ay->sndfmt.channels = chans; + ay->sndfmt.bpc = bits; + } + + ay->default_sound_format_flag = 0; + ay->dirty = 1; + return 1; +} + + +/*! Set amplitude factor for each of channels (A,B anc C, tone and noise). + * Factor's value must be from (-100) to 100. + * \arg ay - pointer to ayemu_t structure + * \arg stereo_type - type of stereo + * \arg custom_eq - NULL or pointer to custom table of mixer layout. + * \retval 1 if OK, 0 if error occures. + */ +int ayemu_set_stereo(ayemu_ay_t *ay, ayemu_stereo_t stereo_type, int *custom_eq) +{ + int i; + int chip; + + if (!check_magic(ay)) + return 0; + + if (stereo_type != AYEMU_STEREO_CUSTOM && custom_eq != NULL) { + ayemu_err = "Stereo type not custom, 'custom_eq' parametr must be NULL"; + return 0; + } + + chip = (ay->type == AYEMU_AY)? 0 : 1; + + switch(stereo_type) { + case AYEMU_MONO: + case AYEMU_ABC: + case AYEMU_ACB: + case AYEMU_BAC: + case AYEMU_BCA: + case AYEMU_CAB: + case AYEMU_CBA: + for (i = 0 ; i < 6 ; i++) + ay->eq[i] = default_layout[chip][stereo_type][i]; + break; + case AYEMU_STEREO_CUSTOM: + for (i = 0 ; i < 6 ; i++) + ay->eq[i] = custom_eq[i]; + break; + default: + ayemu_err = "Incorrect stereo type"; + return 0; + } + + ay->default_stereo_flag = 0; + ay->dirty = 1; + return 1; +} + + +#if 0 +#define WARN_IF_REGISTER_GREAT_THAN(r,m) \ +if (*(regs + r) > m) \ + fprintf(stderr, "ayemu_set_regs: warning: possible bad register data- R%d > %d\n", r, m) +#else +#define WARN_IF_REGISTER_GREAT_THAN(r,m) +#endif + + +/** Assign values for AY registers. + * + * You must pass array of char [14] to this function + */ +void ayemu_set_regs(ayemu_ay_t *ay, ayemu_ay_reg_frame_t regs) +{ + if (!check_magic(ay)) return; + + WARN_IF_REGISTER_GREAT_THAN(1,15); + WARN_IF_REGISTER_GREAT_THAN(3,15); + WARN_IF_REGISTER_GREAT_THAN(5,15); + WARN_IF_REGISTER_GREAT_THAN(8,31); + WARN_IF_REGISTER_GREAT_THAN(9,31); + WARN_IF_REGISTER_GREAT_THAN(10,31); + + ay->regs.tone_a = regs[0] + ((regs[1]&0x0f) << 8); + ay->regs.tone_b = regs[2] + ((regs[3]&0x0f) << 8); + ay->regs.tone_c = regs[4] + ((regs[5]&0x0f) << 8); + + ay->regs.noise = regs[6] & 0x1f; + + ay->regs.R7_tone_a = ! (regs[7] & 0x01); + ay->regs.R7_tone_b = ! (regs[7] & 0x02); + ay->regs.R7_tone_c = ! (regs[7] & 0x04); + + ay->regs.R7_noise_a = ! (regs[7] & 0x08); + ay->regs.R7_noise_b = ! (regs[7] & 0x10); + ay->regs.R7_noise_c = ! (regs[7] & 0x20); + + ay->regs.vol_a = regs[8] & 0x0f; + ay->regs.vol_b = regs[9] & 0x0f; + ay->regs.vol_c = regs[10] & 0x0f; + ay->regs.env_a = regs[8] & 0x10; + ay->regs.env_b = regs[9] & 0x10; + ay->regs.env_c = regs[10] & 0x10; + ay->regs.env_freq = regs[11] + (regs[12] << 8); + + if (regs[13] != 0xff) { /* R13 = 255 means continue curent envelop */ + ay->regs.env_style = regs[13] & 0x0f; + ay->env_pos = ay->cnt_e = 0; + } +} + + +static void prepare_generation(ayemu_ay_t *ay) +{ + int vol, max_l, max_r; + + if (!ay->dirty) return; + + if (!bEnvGenInit) gen_env (); + + if (ay->default_chip_flag) ayemu_set_chip_type(ay, AYEMU_AY, NULL); + + if (ay->default_stereo_flag) ayemu_set_stereo(ay, AYEMU_ABC, NULL); + + if (ay->default_sound_format_flag) ayemu_set_sound_format(ay, 44100, 2, 16); + + ay->ChipTacts_per_outcount = ay->ChipFreq / ay->sndfmt.freq / 8; + + { /* GenVols */ + int n, m; + int vol; + for (n = 0; n < 32; n++) { + vol = ay->table[n]; + for (m=0; m < 6; m++) + ay->vols[m][n] = (int) (((double) vol * ay->eq[m]) / 100); + } + } + + /* ÄÉÎÁÍÉÞÅÓËÁÑ ÎÁÓÔÒÏÊËÁ ÇÌÏÂÁÌØÎÏÇÏ ËÏÜÆÆÉÃÉÅÎÔÁ ÕÓÉÌÅÎÉÑ + ÐÏÄÒÁÚÕÍÅ×ÁÅÔÓÑ, ÞÔÏ × vols [x][31] ÌÅÖÉÔ ÓÁÍÁÑ ÂÏÌØÛÁÑ ÇÒÏÍËÏÓÔØ + TODO: óÄÅÌÁÔØ ÐÒÏ×ÅÒËÕ ÎÁ ÜÔÏ ;-) + */ + max_l = ay->vols[0][31] + ay->vols[2][31] + ay->vols[3][31]; + max_r = ay->vols[1][31] + ay->vols[3][31] + ay->vols[5][31]; + vol = (max_l > max_r) ? max_l : max_r; // =157283 on all defaults + ay->Amp_Global = ay->ChipTacts_per_outcount *vol / AYEMU_MAX_AMP; + + ay->dirty = 0; +} + + +/*! Generate sound. + * Fill sound buffer with current register data + * Return value: pointer to next data in output sound buffer + * \retval \b 1 if OK, \b 0 if error occures. + */ +void *ayemu_gen_sound(ayemu_ay_t *ay, void *buff, size_t sound_bufsize) +{ + int mix_l, mix_r; + int tmpvol; + int m; + int snd_numcount; + unsigned char *sound_buf = buff; + + if (!check_magic(ay)) + return 0; + + prepare_generation(ay); + + snd_numcount = sound_bufsize / (ay->sndfmt.channels * (ay->sndfmt.bpc >> 3)); + while (snd_numcount-- > 0) { + mix_l = mix_r = 0; + + for (m = 0 ; m < ay->ChipTacts_per_outcount ; m++) { + if (++ay->cnt_a >= ay->regs.tone_a) { + ay->cnt_a = 0; + ay->bit_a = ! ay->bit_a; + } + if (++ay->cnt_b >= ay->regs.tone_b) { + ay->cnt_b = 0; + ay->bit_b = ! ay->bit_b; + } + if (++ay->cnt_c >= ay->regs.tone_c) { + ay->cnt_c = 0; + ay->bit_c = ! ay->bit_c; + } + + /* GenNoise (c) Hacker KAY & Sergey Bulba */ + if (++ay->cnt_n >= (ay->regs.noise * 2)) { + ay->cnt_n = 0; + ay->Cur_Seed = (ay->Cur_Seed * 2 + 1) ^ \ + (((ay->Cur_Seed >> 16) ^ (ay->Cur_Seed >> 13)) & 1); + ay->bit_n = ((ay->Cur_Seed >> 16) & 1); + } + + if (++ay->cnt_e >= ay->regs.env_freq) { + ay->cnt_e = 0; + if (++ay->env_pos > 127) + ay->env_pos = 64; + } + +#define ENVVOL Envelope [ay->regs.env_style][ay->env_pos] + + if ((ay->bit_a | !ay->regs.R7_tone_a) & (ay->bit_n | !ay->regs.R7_noise_a)) { + tmpvol = (ay->regs.env_a)? ENVVOL : ay->regs.vol_a * 2 + 1; + mix_l += ay->vols[0][tmpvol]; + mix_r += ay->vols[1][tmpvol]; + } + + if ((ay->bit_b | !ay->regs.R7_tone_b) & (ay->bit_n | !ay->regs.R7_noise_b)) { + tmpvol =(ay->regs.env_b)? ENVVOL : ay->regs.vol_b * 2 + 1; + mix_l += ay->vols[2][tmpvol]; + mix_r += ay->vols[3][tmpvol]; + } + + if ((ay->bit_c | !ay->regs.R7_tone_c) & (ay->bit_n | !ay->regs.R7_noise_c)) { + tmpvol = (ay->regs.env_c)? ENVVOL : ay->regs.vol_c * 2 + 1; + mix_l += ay->vols[4][tmpvol]; + mix_r += ay->vols[5][tmpvol]; + } + } /* end for (m=0; ...) */ + + mix_l /= ay->Amp_Global; + mix_r /= ay->Amp_Global; + + if (ay->sndfmt.bpc == 8) { + mix_l = (mix_l >> 8) | 128; /* 8 bit sound */ + mix_r = (mix_r >> 8) | 128; + *sound_buf++ = mix_l; + if (ay->sndfmt.channels != 1) + *sound_buf++ = mix_r; + } else { + *sound_buf++ = mix_l & 0x00FF; /* 16 bit sound */ + *sound_buf++ = (mix_l >> 8); + if (ay->sndfmt.channels != 1) { + *sound_buf++ = mix_r & 0x00FF; + *sound_buf++ = (mix_r >> 8); + } + } + } + return sound_buf; +} + +/** Free all data allocated by emulator + * + * For now it do nothing. + */ +void ayemu_free (ayemu_ay_t *ay) +{ + /* nothing to do here */ + return; +} diff --git a/plugins/vtx/ayemu.h b/plugins/vtx/ayemu.h new file mode 100644 index 00000000..637cea91 --- /dev/null +++ b/plugins/vtx/ayemu.h @@ -0,0 +1,84 @@ +/* + ayemu - AY/YM sound chip emulator and music file loader + Copyright (C) 2003-2004 Sashnov Alexander + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Alexander Sashnov + sashnov@ngs.ru +*/ + +/*! + * \mainpage libayemu library + * + * Homepage on sourceforge.net: http://sourceforge.net/projects/libayemu + * + * This library will help you to add AY/YM music in your own game, demos, etc. + * + * For example of how to use it see playvtx and xmms-vtx packages. + * + * Python wrapper and example comming soon. + * + */ + +#ifndef _AYEMU_H +#define _AYEMU_H + +#ifdef __cplusplus +# define BEGIN_C_DECLS extern "C" { +# define END_C_DECLS } +#else /* !__cplusplus */ +# define BEGIN_C_DECLS +# define END_C_DECLS +#endif /* __cplusplus */ + +/* Make sure the correct platform symbols are defined */ +#if !defined(WIN32) && defined(_WIN32) +#define WIN32 +#endif /* Windows */ + +/* Some compilers use a special export keyword */ +#ifndef DECLSPEC +# ifdef __BEOS__ +# if defined(__GNUC__) +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC __declspec(export) +# endif +# else +# ifdef WIN32 +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# define DECLSPEC +# endif +# endif +#endif + +#define EXTERN extern DECLSPEC + + +/* include other library headers */ +#include "ayemu_8912.h" +#include "ayemu_vtxfile.h" + +#endif diff --git a/plugins/vtx/ayemu_8912.h b/plugins/vtx/ayemu_8912.h new file mode 100644 index 00000000..e8e50e5a --- /dev/null +++ b/plugins/vtx/ayemu_8912.h @@ -0,0 +1,156 @@ +/** + * AY/YM emulator include file + */ + +#ifndef _AYEMU_ay8912_h +#define _AYEMU_ay8912_h + +#include <stddef.h> + +BEGIN_C_DECLS + +// TODO: ayemu_ay_t - hide all private data, allocate it in init function. + +typedef unsigned char ayemu_ay_reg_frame_t[14]; + + + +/** Types of stereo. + The codes of stereo types used for generage sound. */ +typedef enum +{ + AYEMU_MONO = 0, + AYEMU_ABC, + AYEMU_ACB, + AYEMU_BAC, + AYEMU_BCA, + AYEMU_CAB, + AYEMU_CBA, + AYEMU_STEREO_CUSTOM = 255 +} ayemu_stereo_t; + +/** Sound chip type. + Constant for identify used chip for emulation */ +typedef enum { + AYEMU_AY, /**< default AY chip (lion17 for now) */ + AYEMU_YM, /**< default YM chip (lion17 for now) */ + AYEMU_AY_LION17, /**< emulate AY with Lion17 table */ + AYEMU_YM_LION17, /**< emulate YM with Lion17 table */ + AYEMU_AY_KAY, /**< emulate AY with HACKER KAY table */ + AYEMU_YM_KAY, /**< emulate YM with HACKER KAY table */ + AYEMU_AY_LOG, /**< emulate AY with logariphmic table */ + AYEMU_YM_LOG, /**< emulate YM with logariphmic table */ + AYEMU_AY_CUSTOM, /**< use AY with custom table. */ + AYEMU_YM_CUSTOM /**< use YM with custom table. */ +} ayemu_chip_t; + +/** parsed by #ayemu_set_regs() AY registers data \internal */ +typedef struct +{ + int tone_a; /**< R0, R1 */ + int tone_b; /**< R2, R3 */ + int tone_c; /**< R4, R5 */ + int noise; /**< R6 */ + int R7_tone_a; /**< R7 bit 0 */ + int R7_tone_b; /**< R7 bit 1 */ + int R7_tone_c; /**< R7 bit 2 */ + int R7_noise_a; /**< R7 bit 3 */ + int R7_noise_b; /**< R7 bit 4 */ + int R7_noise_c; /**< R7 bit 5 */ + int vol_a; /**< R8 bits 3-0 */ + int vol_b; /**< R9 bits 3-0 */ + int vol_c; /**< R10 bits 3-0 */ + int env_a; /**< R8 bit 4 */ + int env_b; /**< R9 bit 4 */ + int env_c; /**< R10 bit 4 */ + int env_freq; /**< R11, R12 */ + int env_style; /**< R13 */ +} +ayemu_regdata_t; + + +/** Output sound format \internal */ +typedef struct +{ + int freq; /**< sound freq */ + int channels; /**< channels (1-mono, 2-stereo) */ + int bpc; /**< bits (8 or 16) */ +} +ayemu_sndfmt_t; + +/** + * \defgroup libayemu Functions for emulate AY/YM chip + */ +/*@{*/ + +/** Data structure for sound chip emulation \internal + * + */ +typedef struct +{ + /* emulator settings */ + int table[32]; /**< table of volumes for chip */ + ayemu_chip_t type; /**< general chip type (\b AYEMU_AY or \b AYEMU_YM) */ + int ChipFreq; /**< chip emulator frequency */ + int eq[6]; /**< volumes for channels. + Array contains 6 elements: + A left, A right, B left, B right, C left and C right; + range -100...100 */ + ayemu_regdata_t regs; /**< parsed registers data */ + ayemu_sndfmt_t sndfmt; /**< output sound format */ + + /* flags */ + int magic; /**< structure initialized flag */ + int default_chip_flag; /**< =1 after init, resets in #ayemu_set_chip_type() */ + int default_stereo_flag; /**< =1 after init, resets in #ayemu_set_stereo() */ + int default_sound_format_flag; /**< =1 after init, resets in #ayemu_set_sound_format() */ + int dirty; /**< dirty flag. Sets if any emulator properties changed */ + + int bit_a; /**< state of channel A generator */ + int bit_b; /**< state of channel B generator */ + int bit_c; /**< state of channel C generator */ + int bit_n; /**< current generator state */ + int cnt_a; /**< back counter of A */ + int cnt_b; /**< back counter of B */ + int cnt_c; /**< back counter of C */ + int cnt_n; /**< back counter of noise generator */ + int cnt_e; /**< back counter of envelop generator */ + int ChipTacts_per_outcount; /**< chip's counts per one sound signal count */ + int Amp_Global; /**< scale factor for amplitude */ + int vols[6][32]; /**< stereo type (channel volumes) and chip table. + This cache calculated by #table and #eq */ + int EnvNum; /**< number of current envilopment (0...15) */ + int env_pos; /**< current position in envelop (0...127) */ + int Cur_Seed; /**< random numbers counter */ +} +ayemu_ay_t; + +EXTERN void +ayemu_init(ayemu_ay_t *ay); + +EXTERN void +ayemu_reset(ayemu_ay_t *ay); + +EXTERN int +ayemu_set_chip_type(ayemu_ay_t *ay, ayemu_chip_t chip, int *custom_table); + +EXTERN void +ayemu_set_chip_freq(ayemu_ay_t *ay, int chipfreq); + +EXTERN int +ayemu_set_stereo(ayemu_ay_t *ay, ayemu_stereo_t stereo, int *custom_eq); + +EXTERN int +ayemu_set_sound_format (ayemu_ay_t *ay, int freq, int chans, int bits); + +EXTERN void +ayemu_set_regs (ayemu_ay_t *ay, ayemu_ay_reg_frame_t regs); + +EXTERN void* +ayemu_gen_sound (ayemu_ay_t *ay, void *buf, size_t bufsize); + +/*@}*/ + +END_C_DECLS + +#endif diff --git a/plugins/vtx/ayemu_vtxfile.h b/plugins/vtx/ayemu_vtxfile.h new file mode 100644 index 00000000..3856b45d --- /dev/null +++ b/plugins/vtx/ayemu_vtxfile.h @@ -0,0 +1,73 @@ +#ifndef _AYEMU_vtxfile_h +#define _AYEMU_vtxfile_h + +#include <stdio.h> +#include <stdint.h> + +#include "ayemu_8912.h" + +BEGIN_C_DECLS + +/** + * \defgroup vtxfile Functions for extract data from vtx files + */ +/*@{*/ + +/** structure for VTX file format handler + * \internal + * It stores VTX file header and current state + * (open file pointer, extracted register data, etc). + */ +typedef struct +{ + ayemu_chip_t chiptype; /**< Type of sound chip */ + int stereo; /**< Type of stereo: ABC, BCA,... */ + int loop; /**< song loop */ + int chipFreq; /**< AY chip freq (1773400 for ZX) */ + int playerFreq; /**< 50 Hz for ZX, 60 Hz for yamaha */ + int year; /**< year song composed */ + char *title; /**< song title */ + char *author; /**< song author */ + char *from; /**< song from */ + char *tracker; /**< tracker */ + char *comment; /**< comment */ + int regdata_size; /**< size of unpacked data (need for unpack). */ + unsigned char *regdata; /**< unpacked song data */ + size_t frames; /**< number of AY data frames */ +} ayemu_vtx_t; + + +/*! Load only header (song info) from vtx file. + * Purpose: read tags in play list. + */ +EXTERN ayemu_vtx_t * ayemu_vtx_header(const char *buf, size_t size); + +/*! Load song (header and unpack data). + * + */ +EXTERN ayemu_vtx_t * ayemu_vtx_load(const char *buf, size_t size); + +EXTERN void ayemu_vtx_getframe(const ayemu_vtx_t *vtx, size_t frame_n, + ayemu_ay_reg_frame_t regs); + +/** Free all allocaded data and structure itself. + * You must call this for any ayemu_vtx_t pointer allocated by this lib + * by functions ayemu_vtx_header() and ayemu_vtx_load(). + */ +EXTERN void ayemu_vtx_free(ayemu_vtx_t *vtx); + +/*! Load song header from file. + * Helper (non-all platform) function + */ +EXTERN ayemu_vtx_t * ayemu_vtx_header_from_file(const char *filename); + +/*! Load song header and data from file. + * Helper (non-all platform) function + */ +EXTERN ayemu_vtx_t * ayemu_vtx_load_from_file(const char *filename); + +/*@}*/ + +END_C_DECLS + +#endif diff --git a/plugins/vtx/lh5dec.c b/plugins/vtx/lh5dec.c new file mode 100644 index 00000000..1b2456bb --- /dev/null +++ b/plugins/vtx/lh5dec.c @@ -0,0 +1,304 @@ +/* extractiong lh5 module + (c) Haruhiko Okumura + (m) Roman Scherbakov +*/ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> /* memmove */ +#include <limits.h> + +static unsigned short bitbuf; + +#define BITBUFSIZ (CHAR_BIT * sizeof bitbuf) + +#define DICBIT 13 /* 12(-lh4-) or 13(-lh5-) */ +#define DICSIZ (1L << DICBIT) +#define MATCHBIT 8 /* bits for MAXMATCH - THRESHOLD */ +#define MAXMATCH 256 /* formerly F (not more than unsigned char_MAX + 1) */ +#define THRESHOLD 3 /* choose optimal value */ +#define NC (UCHAR_MAX + MAXMATCH + 2 - THRESHOLD) +#define CBIT 9 /* $\lfloor \log_2 NC \rfloor + 1$ */ +#define CODE_BIT 16 /* codeword length */ +#define NP (DICBIT + 1) +#define NT (CODE_BIT + 3) +#define PBIT 4 /* smallest integer such that (1U << PBIT) > NP */ +#define TBIT 5 /* smallest integer such that (1U << TBIT) > NT */ +#if NT > NP +#define NPT NT +#else +#define NPT NP +#endif + +static unsigned long origsize, compsize; +static const unsigned char *in_buf; +static unsigned char *out_buf; + +static unsigned short subbitbuf; +static int bitcount; + +static unsigned short left[2 * NC - 1], right[2 * NC - 1]; +static unsigned char c_len[NC], pt_len[NPT]; +static unsigned short blocksize; + +static unsigned short c_table[4096], pt_table[256]; + +static int j; /* remaining bytes to copy */ + + +static void error(char *msg) +{ + fprintf(stderr, "libayemu: lh5dec.c: %s\n", msg); + exit(EXIT_FAILURE); +} + +static void fillbuf(int n) /* Shift bitbuf n bits left, read n bits */ +{ + bitbuf <<= n; + while (n > bitcount) { + bitbuf |= subbitbuf << (n -= bitcount); + if (compsize != 0) { + compsize--; subbitbuf = *in_buf++; + } else subbitbuf = 0; + bitcount = CHAR_BIT; + } + bitbuf |= subbitbuf >> (bitcount -= n); +} + +static unsigned short getbits(int n) +{ + unsigned short x; + + x = bitbuf >> (BITBUFSIZ - n); fillbuf(n); + return x; +} + +// make table for decoding + +static void make_table(int nchar, unsigned char bitlen[], int tablebits, unsigned short table[]) +{ + unsigned short count[17], weight[17], start[18], *p; + unsigned short i, k, len, ch, jutbits, avail, nextcode, mask; + + for (i = 1; i <= 16; i++) count[i] = 0; + for (i = 0; i < nchar; i++) count[bitlen[i]]++; + + start[1] = 0; + for (i = 1; i <= 16; i++) + start[i + 1] = start[i] + (count[i] << (16 - i)); + if (start[17] != (unsigned short)(1U << 16)) error("Bad table"); + + jutbits = 16 - tablebits; + for (i = 1; i <= tablebits; i++) { + start[i] >>= jutbits; + weight[i] = 1U << (tablebits - i); + } + while (i <= 16) weight[i++] = 1U << (16 - i); + + i = start[tablebits + 1] >> jutbits; + if (i != (unsigned short)(1U << 16)) { + k = 1U << tablebits; + while (i != k) table[i++] = 0; + } + + avail = nchar; + mask = 1U << (15 - tablebits); + for (ch = 0; ch < nchar; ch++) { + if ((len = bitlen[ch]) == 0) continue; + nextcode = start[len] + weight[len]; + if (len <= tablebits) { + for (i = start[len]; i < nextcode; i++) table[i] = ch; + } else { + k = start[len]; + p = &table[k >> jutbits]; + i = len - tablebits; + while (i != 0) { + if (*p == 0) { + right[avail] = left[avail] = 0; + *p = avail++; + } + if (k & mask) p = &right[*p]; + else p = &left[*p]; + k <<= 1; i--; + } + *p = ch; + } + start[len] = nextcode; + } +} + +// static Huffman + +static void read_pt_len(int nn, int nbit, int i_special) +{ + int i, c, n; + unsigned short mask; + + n = getbits(nbit); + if (n == 0) { + c = getbits(nbit); + for (i = 0; i < nn; i++) pt_len[i] = 0; + for (i = 0; i < 256; i++) pt_table[i] = c; + } else { + i = 0; + while (i < n) { + c = bitbuf >> (BITBUFSIZ - 3); + if (c == 7) { + mask = 1U << (BITBUFSIZ - 1 - 3); + while (mask & bitbuf) { mask >>= 1; c++; } + } + fillbuf((c < 7) ? 3 : c - 3); + pt_len[i++] = c; + if (i == i_special) { + c = getbits(2); + while (--c >= 0) pt_len[i++] = 0; + } + } + while (i < nn) pt_len[i++] = 0; + make_table(nn, pt_len, 8, pt_table); + } +} + +static void read_c_len(void) +{ + int i, c, n; + unsigned short mask; + + n = getbits(CBIT); + if (n == 0) { + c = getbits(CBIT); + for (i = 0; i < NC; i++) c_len[i] = 0; + for (i = 0; i < 4096; i++) c_table[i] = c; + } else { + i = 0; + while (i < n) { + c = pt_table[bitbuf >> (BITBUFSIZ - 8)]; + if (c >= NT) { + mask = 1U << (BITBUFSIZ - 1 - 8); + do { + if (bitbuf & mask) c = right[c]; + else c = left [c]; + mask >>= 1; + } while (c >= NT); + } + fillbuf(pt_len[c]); + if (c <= 2) { + if (c == 0) c = 1; + else if (c == 1) c = getbits(4) + 3; + else c = getbits(CBIT) + 20; + while (--c >= 0) c_len[i++] = 0; + } else c_len[i++] = c - 2; + } + while (i < NC) c_len[i++] = 0; + make_table(NC, c_len, 12, c_table); + } +} + + +static unsigned short decode_c(void) +{ + unsigned short j, mask; + + if (blocksize == 0) { + blocksize = getbits(16); + read_pt_len(NT, TBIT, 3); + read_c_len(); + read_pt_len(NP, PBIT, -1); + } + blocksize--; + j = c_table[bitbuf >> (BITBUFSIZ - 12)]; + if (j >= NC) { + mask = 1U << (BITBUFSIZ - 1 - 12); + do { + if (bitbuf & mask) j = right[j]; + else j = left [j]; + mask >>= 1; + } while (j >= NC); + } + fillbuf(c_len[j]); + return j; +} + + +static unsigned short decode_p(void) +{ + unsigned short j, mask; + + j = pt_table[bitbuf >> (BITBUFSIZ - 8)]; + if (j >= NP) { + mask = 1U << (BITBUFSIZ - 1 - 8); + do { + if (bitbuf & mask) j = right[j]; + else j = left [j]; + mask >>= 1; + } while (j >= NP); + } + fillbuf(pt_len[j]); + if (j != 0) j = (1U << (j - 1)) + getbits(j - 1); + return j; +} + + +static void decode(unsigned short count, unsigned char buffer[]) +{ + static unsigned short i; + unsigned short r, c; + + r = 0; + while (--j >= 0) { + buffer[r] = buffer[i]; + i = (i + 1) & (DICSIZ - 1); + if (++r == count) return; + } + for ( ; ; ) { + c = decode_c(); + if (c <= UCHAR_MAX) { + buffer[r] = c & UCHAR_MAX; + if (++r == count) return; + } else { + j = c - (UCHAR_MAX + 1 - THRESHOLD); + i = (r - decode_p() - 1) & (DICSIZ - 1); + while (--j >= 0) { + buffer[r] = buffer[i]; + i = (i + 1) & (DICSIZ - 1); + if (++r == count) return; + } + } + } +} + +void lh5_decode(const unsigned char *inp, unsigned char *outp, unsigned long original_size, unsigned long packed_size) +{ + unsigned short n; + unsigned char *buffer; + + compsize = packed_size; + origsize = original_size; + in_buf = inp; + out_buf = outp; + +#if 0 + fprintf(stderr, "DEBUG: compsize = %ld, origsize = %ld, first 8 bytes of packed data:\n", packed_size, original_size); + fprintf(stderr, " %02x %02x %02x %02x %02x %02x %02x %02x \n", + *(inp), *(inp+1),*(inp+2),*(inp+3), + *(inp+4),*(inp+5),*(inp+6),*(inp+7)); +#endif + + buffer = (unsigned char *) malloc(DICSIZ); + if (!buffer) error ("Out of memory"); + + bitbuf = 0; subbitbuf = 0; bitcount = 0; + fillbuf(BITBUFSIZ); + blocksize = 0; + j = 0; + + while (origsize != 0) { + n = (origsize > DICSIZ) ? DICSIZ : (unsigned short)origsize; + decode(n, buffer); + memmove(out_buf, buffer, n); + out_buf += n; + origsize -= n; + } + + if (buffer) free (buffer); + buffer = NULL; +} diff --git a/plugins/vtx/vtx.c b/plugins/vtx/vtx.c new file mode 100644 index 00000000..836ded0b --- /dev/null +++ b/plugins/vtx/vtx.c @@ -0,0 +1,308 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 Alexey Yakovenko + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#include <stdlib.h> +#include <string.h> +#include "../../deadbeef.h" +#include "ayemu.h" + +#define min(x,y) ((x)<(y)?(x):(y)) +#define max(x,y) ((x)>(y)?(x):(y)) + +//#define trace(...) { fprintf (stderr, __VA_ARGS__); } +#define trace(fmt,...) + +static DB_decoder_t plugin; +static DB_functions_t *deadbeef; + +static const char * exts[] = { "vtx", NULL }; +static const char *filetypes[] = { "VTX", NULL }; + +static ayemu_vtx_t *decoder; +static ayemu_ay_t ay; + +#define AY_FRAME_SIZE 14 + +static char regs[AY_FRAME_SIZE]; +static int vtx_pos; +static int left; +static int rate; +static int currentsample; +static int16_t soundbuffer[]; + +static int +vtx_init (DB_playItem_t *it) { + // prepare to decode the track + // return -1 on failure + + size_t sz = 0; + char *buf = NULL; + + DB_FILE *fp = deadbeef->fopen (it->fname); + if (!fp) { + trace ("vtx: failed to open file %s\n", it->fname); + return -1; + } + + sz = deadbeef->fgetlength (fp); + if (sz <= 0) { + trace ("vtx: bad file size\n"); + return -1; + } + + buf = malloc (sz); + if (!buf) { + trace ("vtx: out of memory\n"); + return -1; + } + if (deadbeef->fread (buf, 1, sz, fp) != sz) { + trace ("vtx: read failed\n"); + free (buf); + return -1; + } + + decoder = ayemu_vtx_load (buf, sz); + if (!decoder) { + trace ("vtx: ayemu_vtx_load failed\n"); + free (buf); + return -1; + } + trace ("vtx: data=%p, size=%d\n", decoder->regdata, decoder->regdata_size); + + free (buf); + + ayemu_init (&ay); + ayemu_set_chip_type (&ay, decoder->chiptype, NULL); + ayemu_set_chip_freq(&ay, decoder->chipFreq); + ayemu_set_stereo (&ay, decoder->stereo, NULL); + + int samplerate = deadbeef->get_output ()->samplerate (); + + ayemu_set_sound_format (&ay, samplerate, deadbeef->get_output ()->channels (), deadbeef->get_output ()->bitspersample ()); + + left = 0; + rate = deadbeef->get_output ()->channels () * deadbeef->get_output ()->bitspersample () / 8; + vtx_pos = 0; + memset (regs, 0, sizeof (regs)); + plugin.info.bps = deadbeef->get_output ()->bitspersample (); + plugin.info.channels = deadbeef->get_output ()->channels (); + plugin.info.samplerate = samplerate; + plugin.info.readpos = 0; + return 0; +} + +static void +vtx_free (void) { + // free everything allocated in _init + if (decoder) { + ayemu_vtx_free (decoder); + decoder = NULL; + } + ayemu_reset (&ay); +} + +/** Get next 14-bytes frame of AY register data. + * + * Return value: 1 on success, 0 on eof + */ +static int +ayemu_vtx_get_next_frame (ayemu_vtx_t *vtx, char *regs) +{ + int numframes = vtx->regdata_size / AY_FRAME_SIZE; + if (vtx_pos++ >= numframes) { + return 0; + } + else { + int n; + char *p = vtx->regdata + vtx_pos; + for(n = 0 ; n < AY_FRAME_SIZE ; n++, p += numframes) { + regs[n] = *p; + } + return 1; + } +} + +static int +vtx_read_int16 (char *bytes, int size) { + // try decode `size' bytes + // return number of decoded bytes + // return 0 on EOF + int initsize = size; + int donow = 0; + + while (size > 0) { + if (left > 0) { + donow = (size > left) ? left : size; + left -= donow; + bytes = ayemu_gen_sound (&ay, (char *)bytes, donow); + size -= donow; + } + else { + if ((ayemu_vtx_get_next_frame (decoder, regs) == 0)) { + break; // eof + } + else { + // number of samples it current frame + left = plugin.info.samplerate / decoder->playerFreq; + // mul by rate to get number of bytes; + left *= rate; + ayemu_set_regs (&ay, regs); + donow = 0; + } + } + } + currentsample += (initsize - size) / 4; + plugin.info.readpos = (float)currentsample / plugin.info.samplerate; + return initsize - size; +} + +static int +vtx_seek_sample (int sample) { + // seek to specified sample (frame) + // return 0 on success + // return -1 on failure + + // get frame + int num_frames = decoder->regdata_size / AY_FRAME_SIZE; + int samples_per_frame = plugin.info.samplerate / decoder->playerFreq; + + // start of frame + vtx_pos = sample / samples_per_frame; + if (vtx_pos >= num_frames) { + return -1; // eof + } + // copy register data + int n; + char *p = decoder->regdata + vtx_pos; + for(n = 0 ; n < AY_FRAME_SIZE ; n++, p += num_frames) { + regs[n] = *p; + } + // set number of bytes left in frame + left = plugin.info.samplerate / decoder->playerFreq - (sample % samples_per_frame); + // mul by rate to get number of bytes + left *= rate; + currentsample = sample; + plugin.info.readpos = (float)currentsample / plugin.info.samplerate; + + return 0; +} + +static int +vtx_seek (float time) { + // seek to specified time in seconds + // return 0 on success + // return -1 on failure + return vtx_seek_sample (time * plugin.info.samplerate); +} + +static DB_playItem_t * +vtx_insert (DB_playItem_t *after, const char *fname) { + // read information from the track + // load/process cuesheet if exists + // insert track into playlist + // return track pointer on success + // return NULL on failure + + trace ("vtx_insert %s\n"); + // load header + DB_FILE *fp = deadbeef->fopen (fname); + if (!fp) { + trace ("vtx: failed to open file\n"); + return NULL; + } + char buf[0x4000]; + size_t sz; + sz = deadbeef->fread (buf, 1, sizeof (buf), fp); + deadbeef->fclose (fp); + if (sz <= 0) { + trace ("vtx: failed to read header data from file\n"); + return NULL; + } + ayemu_vtx_t *hdr = ayemu_vtx_header (buf, sz); + if (!hdr) { + trace ("vtx: failed to read header\n"); + return NULL; + } + trace ("vtx: datasize: %d\n", hdr->regdata_size); + + DB_playItem_t *it = deadbeef->pl_item_alloc (); + + it->decoder = &plugin; + it->fname = strdup (fname); + it->filetype = filetypes[0]; + + int numframes = hdr->regdata_size / AY_FRAME_SIZE; +// int totalsamples = numframes * hdr->playerFreq; + trace ("vtx: numframes=%d, playerFreq=%d\n", numframes, hdr->playerFreq); + deadbeef->pl_set_item_duration (it, (float)numframes / hdr->playerFreq); + + // add metadata + deadbeef->pl_add_meta (it, "title", hdr->title); + deadbeef->pl_add_meta (it, "artist", hdr->author); + deadbeef->pl_add_meta (it, "album", hdr->from); + + ayemu_vtx_free (hdr); + after = deadbeef->pl_insert_item (after, it); + return after; +} + +static int +vtx_start (void) { + // do one-time plugin initialization here + // e.g. starting threads for background processing, subscribing to events, etc + // return 0 on success + // return -1 on failure + return 0; +} +static int +vtx_stop (void) { + // undo everything done in _start here + // return 0 on success + // return -1 on failure + return 0; +} +// define plugin interface +static DB_decoder_t plugin = { + DB_PLUGIN_SET_API_VERSION + .plugin.version_major = 0, + .plugin.version_minor = 1, + .plugin.type = DB_PLUGIN_DECODER, + .plugin.name = "VTX decoder", + .plugin.descr = "AY8910/12 chip emulator and vtx file player", + .plugin.author = "Alexey Yakovenko", + .plugin.email = "waker@users.sourceforge.net", + .plugin.website = "http://deadbeef.sf.net", + .plugin.start = vtx_start, + .plugin.stop = vtx_stop, + .init = vtx_init, + .free = vtx_free, + .read_int16 = vtx_read_int16, +// .read_float32 = vtx_read_float32, + .seek = vtx_seek, + .seek_sample = vtx_seek_sample, + .insert = vtx_insert, + .exts = exts, + .id = "vtx", + .filetypes = filetypes +}; + +DB_plugin_t * +vtx_load (DB_functions_t *api) { + deadbeef = api; + return DB_PLUGIN (&plugin); +} diff --git a/plugins/vtx/vtxfile.c b/plugins/vtx/vtxfile.c new file mode 100644 index 00000000..9dbeb9f7 --- /dev/null +++ b/plugins/vtx/vtxfile.c @@ -0,0 +1,283 @@ +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> +#include <unistd.h> + +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "ayemu.h" + +#define AYEMU_VTX_STRING_MAX 254 + +typedef const char * data_ptr_t; + + +/* LHA5 decoder, defined in lh5dec.c */ +extern void lh5_decode(unsigned const char *inp, + unsigned char *outp, + unsigned long original_size, + unsigned long packed_size); + +/* Read 8-bit integer from file. + */ +static data_ptr_t read_byte(data_ptr_t pos, int *p) +{ + const unsigned char *data = (const unsigned char*)pos; + *p = *data++; + return (data_ptr_t)data; +} + +/* Read 16-bit little-endian 1234 integer from file. + */ +static data_ptr_t read_word16(data_ptr_t pos, int *p) +{ + const unsigned char *data = (const unsigned char*)pos; + *p = *data++; + *p += *data++ << 8; + return (data_ptr_t)data; +} + +/* read 32-bit integer from file. + */ +static data_ptr_t read_word32(data_ptr_t pos, int *p) +{ + const unsigned char *data = (const unsigned char*)pos; + *p = *data++; + *p += *data++ << 8; + *p += *data++ << 16; + *p += *data++ << 24; + return (data_ptr_t)data; +} + +/* read_string: max 254 chars len (+1 for null terminator). + */ +static data_ptr_t read_string(data_ptr_t pos, char **res) +{ + int len; + + if (pos == NULL) + return NULL; + + len = strlen(pos); + + if (len > AYEMU_VTX_STRING_MAX) { + fprintf(stderr, "Error: string len more than %d (=%d)\n", AYEMU_VTX_STRING_MAX, len); + return NULL; + } + + *res = calloc(1, len + 1); + + strcpy(*res, pos); + + return pos + len + 1; +} + +static data_ptr_t read_header(data_ptr_t buf, ayemu_vtx_t **target, size_t size) +{ + char hdr[3]; + ayemu_vtx_t *vtx; + + hdr[0] = tolower(*buf++); + hdr[1] = tolower(*buf++); + hdr[2] = '\0'; + + if (size < 20) { + fprintf(stderr, "ayemu_vtx_open: file size < 20 bytes - it is impossible\n"); + return NULL; + } + + vtx = (ayemu_vtx_t*) calloc(1, sizeof(ayemu_vtx_t)); + + if (strncmp(hdr, "ay", 2) == 0) + vtx->chiptype = AYEMU_AY; + else if (strncmp (hdr, "ym", 2) == 0) + vtx->chiptype = AYEMU_YM; + else { + fprintf (stderr, "File is _not_ VORTEX format!\n" + "It not begins with 'ay' or 'ym' string.\n"); + goto clean_and_ret_null; + } + + buf = read_byte(buf, &vtx->stereo); + buf = read_word16(buf, &vtx->loop); + buf = read_word32(buf, &vtx->chipFreq); + buf = read_byte(buf, &vtx->playerFreq); + buf = read_word16(buf, &vtx->year); + buf = read_word32(buf, &vtx->regdata_size); + + buf = read_string(buf, &vtx->title); + buf = read_string(buf, &vtx->author); + buf = read_string(buf, &vtx->from); + buf = read_string(buf, &vtx->tracker); + buf = read_string(buf, &vtx->comment); + + *target = vtx; + return buf; + + clean_and_ret_null: + + ayemu_vtx_free(vtx); + return NULL; +} + + +ayemu_vtx_t * ayemu_vtx_header(data_ptr_t buf, size_t size) +{ + ayemu_vtx_t *vtx; + + const char *data; + + data = read_header(buf, &vtx, size); + + return vtx; +} + + +ayemu_vtx_t * ayemu_vtx_load(data_ptr_t buf, size_t size) +{ + ayemu_vtx_t *vtx; + + const char *data = read_header(buf, &vtx, size); + + if (! data) { + fprintf(stderr, "ayemu_vtx_load: Cannot parse file header\n"); + return NULL; + } + + // unpack data + size -= (buf - data); + + if ((vtx->regdata = (unsigned char *) malloc (vtx->regdata_size)) == NULL) { + fprintf (stderr, "ayemu_vtx_load_data: Can allocate %d bytes" + " for unpack register data\n", vtx->regdata_size); + return NULL; + } + + lh5_decode ((unsigned char*)data, vtx->regdata, vtx->regdata_size, size); + + vtx->frames = vtx->regdata_size / 14; + + return vtx; +} + + +/** Get specified data frame by number. + * + */ +void ayemu_vtx_getframe(const ayemu_vtx_t *vtx, size_t frame_n, ayemu_ay_reg_frame_t regs) +{ + int n; + + if (frame_n >= vtx->frames) + return; + + // calculate begin of data + unsigned char *p = vtx->regdata + frame_n; + + // step is data size / 14 + for(n = 0 ; n < 14 ; n++) { + regs[n] = *p; + p += vtx->frames; + } +} + +/** Free all allocaded resources. + * + */ +void ayemu_vtx_free(ayemu_vtx_t *vtx) +{ +#define FREE_PTR(x) if (x) { free(x); x = NULL; } + + FREE_PTR(vtx->title); + FREE_PTR(vtx->author); + FREE_PTR(vtx->from); + FREE_PTR(vtx->tracker); + FREE_PTR(vtx->comment); + FREE_PTR(vtx->regdata); +} + + + + +ayemu_vtx_t * ayemu_vtx_header_from_file(const char *filename) +{ + ayemu_vtx_t *ret; + size_t size; + const size_t page_size = (size_t) sysconf (_SC_PAGESIZE); + int fd; + struct stat st; + + // printf("Page size is %d\n", page_size); + + if (stat(filename, &st) != 0) { + fprintf(stderr, "Can't stat file %s: %s\n", filename, strerror(errno)); + return NULL; + } + size = st.st_size; + + fd = open(filename, O_RDONLY, 0); + if (fd == 0) { + fprintf(stderr, "Can't open file %s: %s\n", filename, strerror(errno)); + return NULL; + } + + size_t data_len = (size / page_size + 1) * page_size; + + char *data = mmap(NULL, data_len, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == (void*)(-1)) { + fprintf(stderr, "Can't mmap file %s: %s\n", filename, strerror(errno)); + return NULL; + } + + ret = ayemu_vtx_header(data, size); + + if (munmap(data, data_len) != 0) { + fprintf(stderr, "Can't munmmap file %s: %s\n", filename, strerror(errno)); + } + + return ret; +} + + +ayemu_vtx_t * ayemu_vtx_load_from_file(const char *filename) +{ + size_t size; + const size_t page_size = (size_t) sysconf (_SC_PAGESIZE); + int fd; + struct stat st; + ayemu_vtx_t *ret; + + // printf("Page size is %d\n", page_size); + + if (stat(filename, &st) != 0) { + fprintf(stderr, "Can't stat file %s: %s\n", filename, strerror(errno)); + return NULL; + } + size = st.st_size; + + fd = open(filename, O_RDONLY, 0); + if (fd == 0) { + fprintf(stderr, "Can't open file %s: %s\n", filename, strerror(errno)); + return NULL; + } + + size_t data_len = (size / page_size + 1) * page_size; + + char *data = mmap(NULL, data_len, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == (void*)(-1)) { + fprintf(stderr, "Can't mmap file %s: %s\n", filename, strerror(errno)); + return NULL; + } + + ret = ayemu_vtx_load(data, size); + + if (munmap(data, data_len) != 0) { + fprintf(stderr, "Can't munmmap file %s: %s\n", filename, strerror(errno)); + } + + return ret; +} diff --git a/plugins/wavpack/wavpack.c b/plugins/wavpack/wavpack.c index cdb26776..44aec9ea 100644 --- a/plugins/wavpack/wavpack.c +++ b/plugins/wavpack/wavpack.c @@ -160,6 +160,9 @@ wv_read_int16 (char *bytes, int size) { n--; } plugin.info.readpos = (float)(WavpackGetSampleIndex (wvctx.ctx)-wvctx.startsample)/WavpackGetSampleRate (wvctx.ctx); + + deadbeef->streamer_set_bitrate (WavpackGetInstantBitrate (wvctx.ctx) / 1000); + return size; } @@ -188,6 +191,7 @@ wv_read_float32 (char *bytes, int size) { n--; } plugin.info.readpos = (float)(WavpackGetSampleIndex (wvctx.ctx)-wvctx.startsample)/WavpackGetSampleRate (wvctx.ctx); + deadbeef->streamer_set_bitrate (WavpackGetInstantBitrate (wvctx.ctx) / 1000); return size; } @@ -27,11 +27,4 @@ session_save (const char *fname); int session_load (const char *fname); -void -session_capture_window_attrs (uintptr_t window); - -void -session_restore_window_attrs (uintptr_t window); - - #endif // __SESSION_H @@ -78,7 +78,8 @@ static int badsong = -1; static float seekpos = -1; static float playpos = 0; // play position of current song -static float avg_bitrate = -1; // avg bitrate of current song +static int avg_bitrate = -1; // avg bitrate of current song +static int last_bitrate = -1; // last bitrate of current song static int prevtrack_samplerate = -1; @@ -98,9 +99,17 @@ streamer_get_streaming_track (void) { return orig_streaming_song; } +playItem_t * +streamer_get_playing_track (void) { + return &str_playing_song; +} + // playlist must call that whenever item was removed void streamer_song_removed_notify (playItem_t *it) { + if (!mutex) { + return; // streamer is not running + } plug_trigger_event (DB_EV_TRACKDELETED, (uintptr_t)it); if (it == orig_playing_song) { orig_playing_song = NULL; @@ -124,10 +133,9 @@ streamer_set_current (playItem_t *it) { to = it ? pl_get_idx_of (it) : -1; if (!orig_playing_song || p_isstopped ()) { playlist_current_ptr = it; + //trace ("from=%d, to=%d\n", from, to); + //messagepump_push (M_SONGCHANGED, 0, from, to); } - trace ("from=%d, to=%d\n", from, to); - trace ("sending songchanged\n"); - messagepump_push (M_SONGCHANGED, 0, from, to); trace ("streamer_set_current %p, buns=%d\n", it); if(str_streaming_song.decoder) { str_streaming_song.decoder->free (); @@ -212,14 +220,16 @@ streamer_get_playpos (void) { return playpos; } -float -streamer_get_bitrate (void) { - return avg_bitrate; +void +streamer_set_bitrate (int bitrate) { + if (bytes_until_next_song <= 0) { // prevent next track from resetting current playback bitrate + last_bitrate = bitrate; + } } -void -streamer_update_bitrate (float bitrate) { - avg_bitrate = bitrate; +int +streamer_get_apx_bitrate (void) { + return avg_bitrate; } void @@ -257,7 +267,7 @@ static int streamer_read_async (char *bytes, int size); void -streamer_thread (uintptr_t ctx) { +streamer_thread (void *ctx) { prctl (PR_SET_NAME, "deadbeef-stream", 0, 0, 0, 0); codecleft = 0; @@ -279,6 +289,16 @@ streamer_thread (uintptr_t ctx) { continue; } playItem_t *try = pl_get_for_idx (sng); + if (!try) { // track is not in playlist + trace ("track #%d is not in playlist; stopping playback\n", sng); + p_stop (); + pl_item_free (&str_playing_song); + pl_item_free (&str_streaming_song); + orig_playing_song = NULL; + orig_streaming_song = NULL; + messagepump_push (M_SONGCHANGED, 0, -1, -1); + continue; + } int ret = streamer_set_current (try); if (ret < 0) { trace ("failed to play track %s, skipping...\n", try->fname); @@ -297,6 +317,8 @@ streamer_thread (uintptr_t ctx) { p_stop (); } else if (pstate == 1) { + last_bitrate = -1; + avg_bitrate = -1; p_play (); } else if (pstate == 2) { @@ -313,7 +335,6 @@ streamer_thread (uintptr_t ctx) { trace ("sending songfinished to plugins [1]\n"); plug_trigger_event (DB_EV_SONGFINISHED, 0); } -// messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (orig_playing_song), -1); streamer_set_current (NULL); pl_item_free (&str_playing_song); orig_playing_song = NULL; @@ -330,7 +351,6 @@ streamer_thread (uintptr_t ctx) { // means last song was deleted during final drain nextsong = -1; p_stop (); -// messagepump_push (M_SONGCHANGED, 0, pl_get_idx_of (playlist_current_ptr), -1); streamer_set_current (NULL); continue; } @@ -350,6 +370,8 @@ streamer_thread (uintptr_t ctx) { pl_item_free (&str_playing_song); // copy streaming into playing pl_item_copy (&str_playing_song, &str_streaming_song); + last_bitrate = -1; + avg_bitrate = -1; orig_playing_song = orig_streaming_song; if (orig_playing_song) { orig_playing_song->played = 1; @@ -362,10 +384,9 @@ streamer_thread (uintptr_t ctx) { trace ("sending songstarted to plugins\n"); plug_trigger_event (DB_EV_SONGSTARTED, 0); playpos = 0; - avg_bitrate = -1; // change samplerate if (prevtrack_samplerate != str_playing_song.decoder->info.samplerate) { - palsa_change_rate (str_playing_song.decoder->info.samplerate); + plug_get_output ()->change_rate (str_playing_song.decoder->info.samplerate); prevtrack_samplerate = str_playing_song.decoder->info.samplerate; } } @@ -376,6 +397,7 @@ streamer_thread (uintptr_t ctx) { seekpos = -1; if (orig_playing_song != orig_streaming_song) { + trace ("streamer already switched to next track\n"); // restart playing from new position if(str_streaming_song.decoder) { str_streaming_song.decoder->free (); @@ -384,6 +406,14 @@ streamer_thread (uintptr_t ctx) { orig_streaming_song = orig_playing_song; pl_item_copy (&str_streaming_song, orig_streaming_song); bytes_until_next_song = -1; + int ret = str_streaming_song.decoder->init (DB_PLAYITEM (orig_streaming_song)); + if (ret < 0) { + trace ("failed to restart prev track on seek, trying to jump to next track\n"); + pl_nextsong (0); + trace ("pl_nextsong switched to track %d\n", nextsong); + usleep (50000); + continue; + } } streamer_buffering = 1; @@ -401,6 +431,8 @@ streamer_thread (uintptr_t ctx) { if (str_playing_song.decoder->seek (pos) >= 0) { playpos = str_playing_song.decoder->info.readpos; } + last_bitrate = -1; + avg_bitrate = -1; streamer_unlock(); } } @@ -434,6 +466,7 @@ streamer_thread (uintptr_t ctx) { alloc_time -= ms; if (alloc_time > 0) { usleep (alloc_time * 1000); +// usleep (1000); } // trace ("fill: %d/%d\n", streambuffer_fill, STREAM_BUFFER_SIZE); } @@ -461,7 +494,7 @@ streamer_init (void) { if (!src) { return -1; } - streamer_tid = thread_start (streamer_thread, 0); + streamer_tid = thread_start (streamer_thread, NULL); return 0; } @@ -470,7 +503,9 @@ streamer_free (void) { streaming_terminate = 1; thread_join (streamer_tid); mutex_free (decodemutex); + decodemutex = 0; mutex_free (mutex); + mutex = 0; } void @@ -757,7 +792,12 @@ streamer_read_async (char *bytes, int size) { if (bytes_until_next_song < 0) { trace ("finished streaming song, queueing next\n"); bytes_until_next_song = streambuffer_fill; - pl_nextsong (0); + if (conf_get_int ("playlist.stop_after_current", 0)) { + streamer_set_nextsong (-2, 1); + } + else { + pl_nextsong (0); + } } break; } @@ -787,27 +827,6 @@ streamer_read (char *bytes, int size) { memcpy (bytes, streambuffer, sz); memmove (streambuffer, streambuffer+sz, streambuffer_fill-sz); streambuffer_fill -= sz; -#if 0 - int cp = sz; - int readpos = streambuffer_pos & STREAM_BUFFER_MASK; - int part1 = STREAM_BUFFER_SIZE-readpos; - part1 = min (part1, cp); - if (part1 > 0) { - memcpy (bytes, streambuffer+readpos, part1); - streambuffer_pos += part1; - streambuffer_fill -= part1; - cp -= part1; - bytes += part1; - } - if (cp > 0) { - memcpy (bytes, streambuffer, cp); - streambuffer_pos += cp; - streambuffer_fill -= cp; - bytes += cp; - } - assert (streambuffer_fill>=0); - streambuffer_pos &= STREAM_BUFFER_MASK; -#endif playpos += (float)sz/p_get_rate ()/4.f; str_playing_song.playtime += (float)sz/p_get_rate ()/4.f; if (playlist_current_ptr) { @@ -824,6 +843,32 @@ streamer_read (char *bytes, int size) { } } streamer_unlock (); + + // approximate bitrate + if (last_bitrate != -1) { + if (avg_bitrate == -1) { + avg_bitrate = last_bitrate; + } + else { + if (avg_bitrate < last_bitrate) { + avg_bitrate += 5; + if (avg_bitrate > last_bitrate) { + avg_bitrate = last_bitrate; + } + } + else if (avg_bitrate > last_bitrate) { + avg_bitrate -= 5; + if (avg_bitrate < last_bitrate) { + avg_bitrate = last_bitrate; + } + } + } +// printf ("apx bitrate: %d (last %d)\n", avg_bitrate, last_bitrate); + } + else { + avg_bitrate = -1; + } + #if 0 struct timeval tm2; gettimeofday (&tm2, NULL); @@ -887,3 +932,23 @@ streamer_configchanged (void) { mutex_unlock (decodemutex); } } + +void +streamer_play_current_track (void) { + if (p_ispaused ()) { + // unpause currently paused track + p_unpause (); + plug_trigger_event_paused (0); + } + else if (playlist_current_row[PL_MAIN] != -1) { + // play currently selected track + p_stop (); + streamer_set_nextsong (playlist_current_row[PL_MAIN], 1); + } + else { + // restart currently playing track + p_stop (); + streamer_set_nextsong (0, 1); + } +} + @@ -60,12 +60,6 @@ streamer_ok_to_read (int len); float streamer_get_playpos (void); -float -streamer_get_bitrate (void); - -void -streamer_update_bitrate (float bitrate); - int streamer_is_buffering (void); @@ -75,7 +69,19 @@ streamer_song_removed_notify (playItem_t *it); playItem_t * streamer_get_streaming_track (void); +playItem_t * +streamer_get_playing_track (void); + void streamer_configchanged (void); +void +streamer_play_current_track (void); + +void +streamer_set_bitrate (int bitrate); + +int +streamer_get_apx_bitrate (void); + #endif // __STREAMER_H diff --git a/threading.h b/threading.h index 2e986b59..51a72f74 100644 --- a/threading.h +++ b/threading.h @@ -21,7 +21,7 @@ #include <stdint.h> intptr_t -thread_start (void (*fn)(uintptr_t ctx), uintptr_t ctx); +thread_start (void (*fn)(void *ctx), void *ctx); int thread_join (intptr_t tid); diff --git a/threading_pthread.c b/threading_pthread.c index 942ddbfe..3ef22b26 100644 --- a/threading_pthread.c +++ b/threading_pthread.c @@ -21,7 +21,7 @@ #include "threading.h" intptr_t -thread_start (void (*fn)(uintptr_t ctx), uintptr_t ctx) { +thread_start (void (*fn)(void *ctx), void *ctx) { pthread_t tid; pthread_attr_t attr; int s = pthread_attr_init (&attr); @@ -68,7 +68,7 @@ timeline_stop (timeline_t *tl, int wait) { } void -timeline_thread_func (uintptr_t ctx) { +timeline_thread_func (void *ctx) { printf ("timeline thread started\n"); timeline_t *tl = (timeline_t *)ctx; @@ -113,7 +113,7 @@ timeline_start (timeline_t *tl) { tl->stop = 0; tl->destroy = 0; if (!tl->tid) { - tl->tid = thread_start (timeline_thread_func, (uintptr_t)tl); + tl->tid = thread_start (timeline_thread_func, tl); } else { printf ("reusing existing thread\n"); @@ -21,8 +21,8 @@ #include "vfs.h" #include "plugins.h" -#define trace(...) { fprintf(stderr, __VA_ARGS__); } -//#define trace(fmt,...) +//#define trace(...) { fprintf(stderr, __VA_ARGS__); } +#define trace(fmt,...) DB_FILE * vfs_fopen (const char *fname) { diff --git a/web/index.html b/web/index.html index 46eb041d..2863a8ee 100644 --- a/web/index.html +++ b/web/index.html @@ -403,7 +403,7 @@ implemented basic session management, window size/position, volume, playmode are <p>official jabber conference for russian speaking users is here: deadbeef-ru@conference.jabber.ru</p> <p>email: <a href="mailto:waker@users.sourceforge.net">Alexey Yakovenko</a></p> <h1 id='screenshots'>Screenshots</h1> -<img src="https://sourceforge.net/dbimage.php?id=228167" alt="main window, version 0.1.0"/> +<img src="screenshot02.png" alt="main window, version 0.1.0"/> <hr/> <p><a href="http://sourceforge.net/">Project Web Hosted by<img src="http://sflogo.sourceforge.net/sflogo.php?group_id=272657&type=3" alt="SourceForge.net"/></a></p> |