From 81adf62374422b593aa9e17f5cc01f2e454cfd72 Mon Sep 17 00:00:00 2001 From: Ozkan Sezer Date: Thu, 4 Feb 2021 23:28:00 +0300 Subject: [PATCH] Updates for tracker music: - New console command music_jump: Jump to given order in music, like Unreal's music change. Only for module (tracker) music. - stream layer: Store the loop setting. - umx reader: Replaced questionable byte-swap. - libxmp backend: Handled the loop setting through libxmp apis. - mikmod backend: Enabled in-module loops. Respect global loop setting. More compatible reader callback structure. --- Quake/bgmusic.c | 31 +++++++++++++++++--------- Quake/snd_codec.c | 25 ++++++++++++++------- Quake/snd_codec.h | 11 +++++---- Quake/snd_codeci.h | 2 ++ Quake/snd_flac.c | 4 ++-- Quake/snd_mikmod.c | 53 +++++++++++++++++++++++++++++--------------- Quake/snd_mp3.c | 1 + Quake/snd_mpg123.c | 5 +++-- Quake/snd_opus.c | 1 + Quake/snd_umx.c | 46 +++++++++++++++++--------------------- Quake/snd_vorbis.c | 1 + Quake/snd_wave.c | 5 +---- Quake/snd_xmp.c | 13 +++++++---- Quakespasm-Music.txt | 3 +++ 14 files changed, 122 insertions(+), 79 deletions(-) diff --git a/Quake/bgmusic.c b/Quake/bgmusic.c index 65df3ca9..03dba8ba 100644 --- a/Quake/bgmusic.c +++ b/Quake/bgmusic.c @@ -76,14 +76,11 @@ static snd_stream_t *bgmstream = NULL; static void BGM_Play_f (void) { - if (Cmd_Argc() == 2) - { + if (Cmd_Argc() == 2) { BGM_Play (Cmd_Argv(1)); } - else - { + else { Con_Printf ("music \n"); - return; } } @@ -99,16 +96,17 @@ static void BGM_Resume_f (void) static void BGM_Loop_f (void) { - if (Cmd_Argc() == 2) - { + if (Cmd_Argc() == 2) { if (q_strcasecmp(Cmd_Argv(1), "0") == 0 || q_strcasecmp(Cmd_Argv(1),"off") == 0) bgmloop = false; else if (q_strcasecmp(Cmd_Argv(1), "1") == 0 || - q_strcasecmp(Cmd_Argv(1),"on") == 0) + q_strcasecmp(Cmd_Argv(1),"on") == 0) bgmloop = true; else if (q_strcasecmp(Cmd_Argv(1),"toggle") == 0) bgmloop = !bgmloop; + + if (bgmstream) bgmstream->loop = bgmloop; } if (bgmloop) @@ -122,6 +120,16 @@ static void BGM_Stop_f (void) BGM_Stop(); } +static void BGM_Jump_f (void) +{ + if (Cmd_Argc() != 2) { + Con_Printf ("music_jump \n"); + } + else if (bgmstream) { + S_CodecJumpToOrder(bgmstream, atoi(Cmd_Argv(1))); + } +} + qboolean BGM_Init (void) { music_handler_t *handlers = NULL; @@ -133,6 +141,7 @@ qboolean BGM_Init (void) Cmd_AddCommand("music_resume", BGM_Resume_f); Cmd_AddCommand("music_loop", BGM_Loop_f); Cmd_AddCommand("music_stop", BGM_Stop_f); + Cmd_AddCommand("music_jump", BGM_Jump_f); if (COM_CheckParm("-noextmusic") != 0) no_extmusic = true; @@ -206,7 +215,7 @@ static void BGM_Play_noext (const char *filename, unsigned int allowed_types) /* not supported in quake */ break; case BGM_STREAMER: - bgmstream = S_CodecOpenStreamType(tmp, handler->type); + bgmstream = S_CodecOpenStreamType(tmp, handler->type, bgmloop); if (bgmstream) return; /* success */ break; @@ -264,7 +273,7 @@ void BGM_Play (const char *filename) /* not supported in quake */ break; case BGM_STREAMER: - bgmstream = S_CodecOpenStreamType(tmp, handler->type); + bgmstream = S_CodecOpenStreamType(tmp, handler->type, bgmloop); if (bgmstream) return; /* success */ break; @@ -329,7 +338,7 @@ void BGM_PlayCDtrack (byte track, qboolean looping) { q_snprintf(tmp, sizeof(tmp), "%s/track%02d.%s", MUSIC_DIRNAME, (int)track, ext); - bgmstream = S_CodecOpenStreamType(tmp, type); + bgmstream = S_CodecOpenStreamType(tmp, type, bgmloop); if (! bgmstream) Con_Printf("Couldn't handle music file %s\n", tmp); } diff --git a/Quake/snd_codec.c b/Quake/snd_codec.c index dc37fb66..a38c28cb 100644 --- a/Quake/snd_codec.c +++ b/Quake/snd_codec.c @@ -117,7 +117,7 @@ void S_CodecShutdown (void) S_CodecOpenStream ================= */ -snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type) +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type, qboolean loop) { snd_codec_t *codec; snd_stream_t *stream; @@ -140,7 +140,7 @@ snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type) Con_Printf("Unknown type for %s\n", filename); return NULL; } - stream = S_CodecUtilOpen(filename, codec); + stream = S_CodecUtilOpen(filename, codec, loop); if (stream) { if (codec->codec_open(stream)) stream->status = STREAM_PLAY; @@ -149,7 +149,7 @@ snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type) return stream; } -snd_stream_t *S_CodecOpenStreamExt (const char *filename) +snd_stream_t *S_CodecOpenStreamExt (const char *filename, qboolean loop) { snd_codec_t *codec; snd_stream_t *stream; @@ -174,7 +174,7 @@ snd_stream_t *S_CodecOpenStreamExt (const char *filename) Con_Printf("Unknown extension for %s\n", filename); return NULL; } - stream = S_CodecUtilOpen(filename, codec); + stream = S_CodecUtilOpen(filename, codec, loop); if (stream) { if (codec->codec_open(stream)) stream->status = STREAM_PLAY; @@ -183,7 +183,7 @@ snd_stream_t *S_CodecOpenStreamExt (const char *filename) return stream; } -snd_stream_t *S_CodecOpenStreamAny (const char *filename) +snd_stream_t *S_CodecOpenStreamAny (const char *filename, qboolean loop) { snd_codec_t *codec; snd_stream_t *stream; @@ -198,7 +198,7 @@ snd_stream_t *S_CodecOpenStreamAny (const char *filename) while (codec) { q_snprintf(tmp, sizeof(tmp), "%s.%s", filename, codec->ext); - stream = S_CodecUtilOpen(tmp, codec); + stream = S_CodecUtilOpen(tmp, codec, loop); if (stream) { if (codec->codec_open(stream)) { stream->status = STREAM_PLAY; @@ -225,7 +225,7 @@ snd_stream_t *S_CodecOpenStreamAny (const char *filename) Con_Printf("Unknown extension for %s\n", filename); return NULL; } - stream = S_CodecUtilOpen(filename, codec); + stream = S_CodecUtilOpen(filename, codec, loop); if (stream) { if (codec->codec_open(stream)) stream->status = STREAM_PLAY; @@ -261,6 +261,14 @@ int S_CodecRewindStream (snd_stream_t *stream) return stream->codec->codec_rewind(stream); } +int S_CodecJumpToOrder (snd_stream_t *stream, int to) +{ + if (stream->codec->codec_jump) { + return stream->codec->codec_jump(stream, to); + } + return -1; +} + int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) { return stream->codec->codec_read(stream, bytes, buffer); @@ -268,7 +276,7 @@ int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) /* Util functions (used by codecs) */ -snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec, qboolean loop) { snd_stream_t *stream; FILE *handle; @@ -287,6 +295,7 @@ snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) /* Allocate a stream, Z_Malloc zeroes its content */ stream = (snd_stream_t *) Z_Malloc(sizeof(snd_stream_t)); stream->codec = codec; + stream->loop = loop; stream->fh.file = handle; stream->fh.start = ftell(handle); stream->fh.pos = 0; diff --git a/Quake/snd_codec.h b/Quake/snd_codec.h index 657b0b2a..fcc9f864 100644 --- a/Quake/snd_codec.h +++ b/Quake/snd_codec.h @@ -54,6 +54,7 @@ typedef struct snd_stream_s snd_info_t info; stream_status_t status; snd_codec_t *codec; /* codec handling this stream */ + qboolean loop; void *priv; /* data private to the codec. */ } snd_stream_t; @@ -64,22 +65,24 @@ void S_CodecShutdown (void); /* Callers of the following S_CodecOpenStream* functions * are reponsible for attaching any path to the filename */ -snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type); +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type, + qboolean loop); /* Decides according to the required type. */ -snd_stream_t *S_CodecOpenStreamAny (const char *filename); +snd_stream_t *S_CodecOpenStreamAny (const char *filename, qboolean loop); /* Decides according to file extension. if the * name has no extension, try all available. */ -snd_stream_t *S_CodecOpenStreamExt (const char *filename); +snd_stream_t *S_CodecOpenStreamExt (const char *filename, qboolean loop); /* Decides according to file extension. the name * MUST have an extension. */ void S_CodecCloseStream (snd_stream_t *stream); int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer); int S_CodecRewindStream (snd_stream_t *stream); +int S_CodecJumpToOrder (snd_stream_t *stream, int to); -snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec); +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec, qboolean loop); void S_CodecUtilClose(snd_stream_t **stream); diff --git a/Quake/snd_codeci.h b/Quake/snd_codeci.h index feedb61c..4df70315 100644 --- a/Quake/snd_codeci.h +++ b/Quake/snd_codeci.h @@ -32,6 +32,7 @@ typedef void (*CODEC_SHUTDOWN)(void); typedef qboolean (*CODEC_OPEN)(snd_stream_t *stream); typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); typedef int (*CODEC_REWIND)(snd_stream_t *stream); +typedef int (*CODEC_JUMP)(snd_stream_t *stream, int order); typedef void (*CODEC_CLOSE)(snd_stream_t *stream); struct snd_codec_s @@ -44,6 +45,7 @@ struct snd_codec_s CODEC_OPEN codec_open; CODEC_READ codec_read; CODEC_REWIND codec_rewind; + CODEC_JUMP codec_jump; CODEC_CLOSE codec_close; snd_codec_t *next; }; diff --git a/Quake/snd_flac.c b/Quake/snd_flac.c index b5417b32..838404b3 100644 --- a/Quake/snd_flac.c +++ b/Quake/snd_flac.c @@ -356,8 +356,7 @@ static void S_FLAC_CodecCloseStream (snd_stream_t *stream) FLAC__stream_decoder_finish (ff->decoder); FLAC__stream_decoder_delete (ff->decoder); - if (ff->buffer) - free(ff->buffer); + if (ff->buffer) free(ff->buffer); Z_Free(ff); S_CodecUtilClose(&stream); @@ -382,6 +381,7 @@ snd_codec_t flac_codec = S_FLAC_CodecOpenStream, S_FLAC_CodecReadStream, S_FLAC_CodecRewindStream, + NULL, /* jump */ S_FLAC_CodecCloseStream, NULL }; diff --git a/Quake/snd_mikmod.c b/Quake/snd_mikmod.c index 2327181c..bc0859b2 100644 --- a/Quake/snd_mikmod.c +++ b/Quake/snd_mikmod.c @@ -43,12 +43,15 @@ #endif typedef struct _mik_priv { -/* struct MREADER in libmikmod <= 3.2.0-beta2 - * doesn't have iobase members. adding them here - * so that if we compile against 3.2.0-beta2, we - * can still run OK against 3.2.0b3 and newer. */ - struct MREADER reader; + /* MREADER core members in libmikmod2/3: */ + int (*Seek)(struct MREADER*, long, int); + long (*Tell)(struct MREADER*); + BOOL (*Read)(struct MREADER*, void*, size_t); + int (*Get)(struct MREADER*); + BOOL (*Eof)(struct MREADER*); + /* no iobase members in libmikmod <= 3.2.0-beta2 */ long iobase, prev_iobase; + fshandle_t *fh; MODULE *module; } mik_priv_t; @@ -132,11 +135,11 @@ static qboolean S_MIKMOD_CodecOpenStream (snd_stream_t *stream) stream->priv = Z_Malloc(sizeof(mik_priv_t)); priv = (mik_priv_t *) stream->priv; - priv->reader.Seek = MIK_Seek; - priv->reader.Tell = MIK_Tell; - priv->reader.Read = MIK_Read; - priv->reader.Get = MIK_Get; - priv->reader.Eof = MIK_Eof; + priv->Seek = MIK_Seek; + priv->Tell = MIK_Tell; + priv->Read = MIK_Read; + priv->Get = MIK_Get; + priv->Eof = MIK_Eof; priv->fh = &stream->fh; priv->module = Player_LoadGeneric((MREADER *)stream->priv, 64, 0); @@ -147,13 +150,16 @@ static qboolean S_MIKMOD_CodecOpenStream (snd_stream_t *stream) return false; } - /* keep default values of fadeout (0: don't fade out volume during when last - * position of the module is being played), extspd (1: do process Protracker - * extended speed effect), panflag (1: do process panning effects), wrap (0: - * don't wrap to restart position when module is finished) are OK with us as - * set internally by libmikmod::Player_Init(). */ - /* just change the loop setting to 0, i.e. don't process in-module loops: */ - priv->module->loop = 0; + /* default values of module options set by Player_Init(): + * fadeout (0): don't fade out volume during when last position of the + * module is being played, + * extspd (1): process Protracker extended speed effect, + * panflag (1): process panning effects, + * wrap (0): don't wrap to restart position when module is finished, + * loop (1): process all in-module loops -- possible backward loops + * would make the module to loop endlessly. + */ + priv->module->wrap = stream->loop; Player_Start(priv->module); stream->info.rate = md_mixfreq; @@ -169,6 +175,10 @@ static int S_MIKMOD_CodecReadStream (snd_stream_t *stream, int bytes, void *buff { if (!Player_Active()) return 0; + + /* handle possible loop setting change: */ + ((mik_priv_t *)stream->priv)->module->wrap = stream->loop; + return (int) VC_WriteBytes((SBYTE *)buffer, bytes); } @@ -180,9 +190,15 @@ static void S_MIKMOD_CodecCloseStream (snd_stream_t *stream) S_CodecUtilClose(&stream); } +static int S_MIKMOD_CodecJumpToOrder (snd_stream_t *stream, int to) +{ + Player_SetPosition ((UWORD)to); + return 0; +} + static int S_MIKMOD_CodecRewindStream (snd_stream_t *stream) { - Player_SetPosition (0); + Player_SetPosition (0); /* FIXME: WRONG: THIS IS NOT A TIME SEEK */ return 0; } @@ -196,6 +212,7 @@ snd_codec_t mikmod_codec = S_MIKMOD_CodecOpenStream, S_MIKMOD_CodecReadStream, S_MIKMOD_CodecRewindStream, + S_MIKMOD_CodecJumpToOrder, S_MIKMOD_CodecCloseStream, NULL }; diff --git a/Quake/snd_mp3.c b/Quake/snd_mp3.c index 6d5a354f..b3bf9a14 100644 --- a/Quake/snd_mp3.c +++ b/Quake/snd_mp3.c @@ -452,6 +452,7 @@ snd_codec_t mp3_codec = S_MP3_CodecOpenStream, S_MP3_CodecReadStream, S_MP3_CodecRewindStream, + NULL, /* jump */ S_MP3_CodecCloseStream, NULL }; diff --git a/Quake/snd_mpg123.c b/Quake/snd_mpg123.c index 3eaf07a3..74508f23 100644 --- a/Quake/snd_mpg123.c +++ b/Quake/snd_mpg123.c @@ -49,7 +49,7 @@ static ssize_t mp3_read (void *f, void *buf, size_t size) } static off_t mp3_seek (void *f, off_t offset, int whence) { - if (f == NULL) return (-1); + if (f == NULL) return -1; if (FS_fseek((fshandle_t *)f, (long) offset, whence) < 0) return (off_t)-1; return (off_t) FS_ftell((fshandle_t *)f); @@ -200,7 +200,7 @@ static int S_MP3_CodecRewindStream (snd_stream_t *stream) { mp3_priv_t *priv = (mp3_priv_t *) stream->priv; off_t res = mpg123_seek(priv->handle, 0, SEEK_SET); - if (res >= 0) return (0); + if (res >= 0) return 0; return res; } @@ -214,6 +214,7 @@ snd_codec_t mp3_codec = S_MP3_CodecOpenStream, S_MP3_CodecReadStream, S_MP3_CodecRewindStream, + NULL, /* jump */ S_MP3_CodecCloseStream, NULL }; diff --git a/Quake/snd_opus.c b/Quake/snd_opus.c index a2148e9f..ea5e4f24 100644 --- a/Quake/snd_opus.c +++ b/Quake/snd_opus.c @@ -202,6 +202,7 @@ snd_codec_t opus_codec = S_OPUS_CodecOpenStream, S_OPUS_CodecReadStream, S_OPUS_CodecRewindStream, + NULL, /* jump */ S_OPUS_CodecCloseStream, NULL }; diff --git a/Quake/snd_umx.c b/Quake/snd_umx.c index 6cda61f1..6cf255f2 100644 --- a/Quake/snd_umx.c +++ b/Quake/snd_umx.c @@ -2,12 +2,7 @@ * Unreal UMX container support. * UPKG parsing partially based on Unreal Media Ripper (UMR) v0.3 * by Andy Ward , with additional updates - * by O. Sezer - see git repo at https://github.com/sezero/umr/ - * - * The cheaper way, i.e. linear search of music object like libxmp - * and libmodplug does, is possible. With this however we're using - * the embedded offset, size and object type directly from the umx - * file, and I feel safer with it. + * by O. Sezer - see git repo at https://github.com/sezero/umr.git * * Copyright (C) 2013 O. Sezer * @@ -63,10 +58,9 @@ struct upkg_hdr { uint32_t guid[4]; int32_t generation_count; #define UPKG_HDR_SIZE 64 /* 64 bytes up until here */ - /*struct _genhist *gen;*/ + struct _genhist *gen; }; -/*COMPILE_TIME_ASSERT(upkg_hdr, offsetof(struct upkg_hdr, gen) == UPKG_HDR_SIZE);*/ -COMPILE_TIME_ASSERT(upkg_hdr, sizeof(struct upkg_hdr) == UPKG_HDR_SIZE); +COMPILE_TIME_ASSERT(upkg_hdr, offsetof(struct upkg_hdr, gen) == UPKG_HDR_SIZE); #define UMUSIC_IT 0 #define UMUSIC_S3M 1 @@ -274,21 +268,21 @@ static int probe_umx (fshandle_t *f, const struct upkg_hdr *hdr, return t; } -static int32_t probe_header (void *header) +static int32_t probe_header (fshandle_t *f, struct upkg_hdr *hdr) { - struct upkg_hdr *hdr; - unsigned char *p; - uint32_t *swp; - int i; - + if (FS_fread(hdr, 1, UPKG_HDR_SIZE, f) < UPKG_HDR_SIZE) + return -1; /* byte swap the header - all members are 32 bit LE values */ - p = (unsigned char *) header; - swp = (uint32_t *) header; - for (i = 0; i < UPKG_HDR_SIZE/4; i++, p += 4) { - swp[i] = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); - } + hdr->tag = (uint32_t) LittleLong(hdr->tag); + hdr->file_version = LittleLong(hdr->file_version); + hdr->pkg_flags = (uint32_t) LittleLong(hdr->pkg_flags); + hdr->name_count = LittleLong(hdr->name_count); + hdr->name_offset = LittleLong(hdr->name_offset); + hdr->export_count = LittleLong(hdr->export_count); + hdr->export_offset = LittleLong(hdr->export_offset); + hdr->import_count = LittleLong(hdr->import_count); + hdr->import_offset = LittleLong(hdr->import_offset); - hdr = (struct upkg_hdr *) header; if (hdr->tag != UPKG_HDR_TAG) { Con_DPrintf("Unknown header tag 0x%x\n", hdr->tag); return -1; @@ -325,14 +319,13 @@ static int32_t probe_header (void *header) static int process_upkg (fshandle_t *f, int32_t *ofs, int32_t *objsize) { - char header[UPKG_HDR_SIZE]; + struct upkg_hdr header; - if (FS_fread(header, 1, UPKG_HDR_SIZE, f) < UPKG_HDR_SIZE) - return -1; - if (probe_header(header) < 0) + memset(&header, 0, sizeof(header)); + if (probe_header(f, &header) < 0) return -1; - return probe_umx(f, (struct upkg_hdr *)header, ofs, objsize); + return probe_umx(f, &header, ofs, objsize); } static qboolean S_UMX_CodecInitialize (void) @@ -399,6 +392,7 @@ snd_codec_t umx_codec = S_UMX_CodecOpenStream, S_UMX_CodecReadStream, S_UMX_CodecRewindStream, + NULL, /* jump */ S_UMX_CodecCloseStream, NULL }; diff --git a/Quake/snd_vorbis.c b/Quake/snd_vorbis.c index 16985dc0..7ea02bd9 100644 --- a/Quake/snd_vorbis.c +++ b/Quake/snd_vorbis.c @@ -196,6 +196,7 @@ snd_codec_t vorbis_codec = S_VORBIS_CodecOpenStream, S_VORBIS_CodecReadStream, S_VORBIS_CodecRewindStream, + NULL, /* jump */ S_VORBIS_CodecCloseStream, NULL }; diff --git a/Quake/snd_wave.c b/Quake/snd_wave.c index b31011a2..584c4de4 100644 --- a/Quake/snd_wave.c +++ b/Quake/snd_wave.c @@ -37,9 +37,7 @@ FGetLittleLong static int FGetLittleLong (FILE *f) { int v; - fread(&v, 1, sizeof(v), f); - return LittleLong(v); } @@ -51,9 +49,7 @@ FGetLittleShort static short FGetLittleShort(FILE *f) { short v; - fread(&v, 1, sizeof(v), f); - return LittleShort(v); } @@ -268,6 +264,7 @@ snd_codec_t wav_codec = S_WAV_CodecOpenStream, S_WAV_CodecReadStream, S_WAV_CodecRewindStream, + NULL, /* jump */ S_WAV_CodecCloseStream, NULL }; diff --git a/Quake/snd_xmp.c b/Quake/snd_xmp.c index 0ca1189a..7734fd3d 100644 --- a/Quake/snd_xmp.c +++ b/Quake/snd_xmp.c @@ -1,6 +1,6 @@ /* tracker music (module file) decoding support using libxmp >= v4.2.0 * https://sourceforge.net/projects/xmp/ - * https://github.com/cmatsuoka/libxmp.git + * https://github.com/libxmp/libxmp.git * * Copyright (C) 2016 O.Sezer * @@ -105,7 +105,7 @@ static int S_XMP_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) * is partial, the rest of the buffer will be zero-filled. * the last param is the max number that the current sequence * of song will be looped, or 0 to disable loop checking. */ - r = xmp_play_buffer((xmp_context)stream->priv, buffer, bytes, 1); + r = xmp_play_buffer((xmp_context)stream->priv, buffer, bytes, !stream->loop); if (r == 0) { return bytes; } @@ -125,12 +125,16 @@ static void S_XMP_CodecCloseStream (snd_stream_t *stream) S_CodecUtilClose(&stream); } +static int S_XMP_CodecJumpToOrder (snd_stream_t *stream, int to) +{ + return xmp_set_position((xmp_context)stream->priv, to); +} + static int S_XMP_CodecRewindStream (snd_stream_t *stream) { int ret = xmp_seek_time((xmp_context)stream->priv, 0); if (ret < 0) return ret; - /* reset internal state */ - xmp_play_buffer((xmp_context)stream->priv, NULL, 0, 0); + xmp_play_buffer((xmp_context)stream->priv, NULL, 0, 0); /* reset internal state */ return 0; } @@ -144,6 +148,7 @@ snd_codec_t xmp_codec = S_XMP_CodecOpenStream, S_XMP_CodecReadStream, S_XMP_CodecRewindStream, + S_XMP_CodecJumpToOrder, S_XMP_CodecCloseStream, NULL }; diff --git a/Quakespasm-Music.txt b/Quakespasm-Music.txt index 5f4a717b..e188d8de 100644 --- a/Quakespasm-Music.txt +++ b/Quakespasm-Music.txt @@ -59,6 +59,9 @@ New console commands: - music_loop 0 Makes the background music to play once and then stop +- music_jump + Jump to a given order in music (only for module (tracker) music) + New cvars: ------------------------- - bgm_extmusic (0 or 1): Disable or enable playback of external music