Play wolf music

This commit is contained in:
Cong
2021-06-08 00:11:31 +10:00
parent 41b28f301f
commit bcdfc59e95
21 changed files with 2790 additions and 300 deletions

32
doc/license-mame.txt Normal file
View File

@@ -0,0 +1,32 @@
Copyright (c) 1997-2005, Nicola Salmoria and the MAME team
All rights reserved.
Redistribution and use of this code or any derivative works are permitted
provided that the following conditions are met:
* Redistributions may not be sold, nor may they be used in a commercial
product or activity.
* Redistributions that are modified from the original source must include the
complete source code, including the source code for all components used by a
binary built from the modified sources. However, as a special exception, the
source code 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.
* Redistributions must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@@ -74,7 +74,7 @@ static void CampaignIntroTerminate(GameLoopData *data)
static void CampaignIntroOnEnter(GameLoopData *data)
{
UNUSED(data);
MusicPlay(&gSoundDevice, MUSIC_BRIEFING, NULL, NULL);
MusicPlayGeneral(&gSoundDevice.music, MUSIC_BRIEFING);
}
static void CampaignIntroOnExit(GameLoopData *data)
{
@@ -435,7 +435,7 @@ static void MissionSummaryOnEnter(GameLoopData *data)
{
MissionSummaryData *mData = data->Data;
MusicPlay(&gSoundDevice, MUSIC_BRIEFING, NULL, NULL);
MusicPlayGeneral(&gSoundDevice.music, MUSIC_BRIEFING);
if (mData->completed && CanLevelSelect(mData->c->Entry.Mode))
{

View File

@@ -1,4 +1,7 @@
set(CW_SOURCES audio.c cwolfmap.c expand.c vswap.c)
set(CWHEADERS AUDIOWL1.H audiowl6.h audio.h cwolfmap.h expand.h vswap.h)
set(CW_SOURCES audio.c cwolfmap.c expand.c vswap.c mame/fmopl.c)
set(CWHEADERS AUDIOWL1.H audiowl6.h audio.h cwolfmap.h expand.h vswap.h mame/fmopl.h)
add_library(cwolfmap STATIC ${CW_SOURCES} ${CWHEADERS})
if(NOT WIN32)
target_link_libraries(cwolfmap m)
endif()

View File

@@ -4,8 +4,71 @@
#include <stdlib.h>
#include "audiowl6.h"
#include "mame/fmopl.h"
#define PATH_MAX 4096
static int volume = 20;
const int oplChip = 0;
#define OPL_CHANNELS 9
#define MUSIC_RATE 700
#define SAMPLES_PER_MUSIC_TICK (MUSIC_SAMPLE_RATE / MUSIC_RATE)
#pragma pack(push, 1)
typedef struct
{
uint8_t mChar, cChar, mScale, cScale, mAttack, cAttack, mSus, cSus, mWave,
cWave, nConn,
// These are only for Muse - these bytes are really unused
voice, mode;
uint8_t unused[3];
} AlInstrument;
#pragma pack(pop)
static const AlInstrument ChannelRelease = {
0, 0, 0x3F, 0x3F, 0xFF, 0xFF, 0xF, 0xF, 0, 0, 0,
0, 0, {0, 0, 0}};
#define alOut(n, b) YM3812Write(oplChip, n, b, &volume)
// Register addresses
// Operator stuff
#define alChar 0x20
#define alScale 0x40
#define alAttack 0x60
#define alSus 0x80
#define alWave 0xe0
// Channel stuff
#define alFreqL 0xa0
#define alFreqH 0xb0
#define alFeedCon 0xc0
// Global stuff
#define alEffects 0xbd
static void AlSetChanInst(const AlInstrument *inst, unsigned int chan)
{
static const uint8_t chanOps[OPL_CHANNELS] = {0, 1, 2, 8, 9,
0xA, 0x10, 0x11, 0x12};
uint8_t c, m;
m = chanOps[chan]; // modulator cell for channel
c = m + 3; // carrier cell for channel
alOut(m + alChar, inst->mChar);
alOut(m + alScale, inst->mScale);
alOut(m + alAttack, inst->mAttack);
alOut(m + alSus, inst->mSus);
alOut(m + alWave, inst->mWave);
alOut(c + alChar, inst->cChar);
alOut(c + alScale, inst->cScale);
alOut(c + alAttack, inst->cAttack);
alOut(c + alSus, inst->cSus);
alOut(c + alWave, inst->cWave);
alOut(chan + alFreqL, 0);
alOut(chan + alFreqH, 0);
alOut(chan + alFeedCon, 0);
}
int CWAudioLoadHead(CWAudioHead *head, const char *path)
{
@@ -26,10 +89,22 @@ int CWAudioLoadHead(CWAudioHead *head, const char *path)
head->nOffsets)
{
err = -1;
fprintf(stderr, "Failed to read audio head");
fprintf(stderr, "Failed to read audio head\n");
goto bail;
}
// Init adlib
if (YM3812Init(1, 3579545, MUSIC_SAMPLE_RATE))
{
fprintf(stderr, "Unable to create virtual OPL\n");
goto bail;
}
for (int i = 1; i < 0xf6; i++)
{
YM3812Write(oplChip, i, 0, &volume);
}
YM3812Write(oplChip, 1, 0x20, &volume); // Set WSE=1
bail:
if (f)
{
@@ -75,6 +150,7 @@ bail:
void CWAudioFree(CWAudio *audio)
{
CWAudioHeadFree(&audio->head);
YM3812Shutdown();
free(audio->data);
}
@@ -96,7 +172,7 @@ bail:
return err;
}
int CWAudioGetMusic(
int CWAudioGetMusicRaw(
const CWAudio *audio, const int i, const char **data, size_t *len)
{
int err = 0;
@@ -108,8 +184,112 @@ int CWAudioGetMusic(
err = -1;
goto bail;
}
if (*len == 88)
{
*len = 0;
goto bail;
}
*data = &audio->data[off];
bail:
return err;
}
int CWAudioGetMusic(
const CWAudio *audio, const int idx, char **data, size_t *len)
{
*data = NULL;
*len = 0;
const char *rawData;
size_t rawLen;
int err = CWAudioGetMusicRaw(audio, idx, &rawData, &rawLen);
if (err != 0)
{
goto bail;
}
if (rawLen == 0)
{
goto bail;
}
for (int i = 0; i < OPL_CHANNELS; i++)
{
AlSetChanInst(&ChannelRelease, i);
}
// Measure length of music
const uint16_t *sqHack = (const uint16_t *)rawData;
int sqHackLen;
if (*sqHack == 0)
{
// LumpLength?
sqHackLen = (int)rawLen;
}
else
{
sqHackLen = *sqHack++;
}
const uint16_t *sqHackPtr = sqHack;
int sqHackTime = 0;
int alTimeCount;
for (alTimeCount = 0; sqHackLen > 0; alTimeCount++)
{
do
{
if (sqHackTime > alTimeCount)
break;
sqHackTime = alTimeCount + *(sqHackPtr + 1);
sqHackPtr += 2;
sqHackLen -= 4;
} while (sqHackLen > 0);
}
// Decode music
// 2 bytes per sample (16-bit audio fmt)
*len = alTimeCount * SAMPLES_PER_MUSIC_TICK * MUSIC_AUDIO_CHANNELS * 2;
*data = malloc(*len);
int16_t *stream16 = (int16_t *)*data;
sqHack = (const uint16_t *)rawData;
if (*sqHack == 0)
{
// LumpLength?
sqHackLen = (int)rawLen;
}
else
{
sqHackLen = *sqHack++;
}
sqHackPtr = sqHack;
sqHackTime = 0;
for (alTimeCount = 0; sqHackLen > 0; alTimeCount++)
{
do
{
if (sqHackTime > alTimeCount)
break;
sqHackTime = alTimeCount + *(sqHackPtr + 1);
alOut(
*(const uint8_t *)sqHackPtr,
*(((const uint8_t *)sqHackPtr) + 1));
sqHackPtr += 2;
sqHackLen -= 4;
} while (sqHackLen > 0);
const int numreadysamples = SAMPLES_PER_MUSIC_TICK;
YM3812UpdateOne(oplChip, stream16, numreadysamples);
stream16 += numreadysamples * MUSIC_AUDIO_CHANNELS;
}
return err;
bail:
if (err != 0)
{
free(*data);
*data = NULL;
}
return err;
}

View File

@@ -1,6 +1,8 @@
#include <stdint.h>
#include <string.h>
#include <SDL_audio.h>
typedef struct
{
uint32_t *offsets;
@@ -15,6 +17,10 @@ typedef struct
char *data;
} CWAudio;
#define MUSIC_SAMPLE_RATE 44100
#define MUSIC_AUDIO_FMT AUDIO_S16
#define MUSIC_AUDIO_CHANNELS 2
int CWAudioLoadHead(CWAudioHead *head, const char *path);
void CWAudioHeadFree(CWAudioHead *head);
@@ -25,5 +31,7 @@ void CWAudioFree(CWAudio *audio);
// http://www.vgmpf.com/Wiki/index.php?title=IMF
int CWAudioGetAdlibSound(
const CWAudio *audio, const int i, const char **data, size_t *len);
int CWAudioGetMusic(
int CWAudioGetMusicRaw(
const CWAudio *audio, const int i, const char **data, size_t *len);
int CWAudioGetMusic(
const CWAudio *audio, const int i, char **data, size_t *len);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
#ifndef __FMOPL_H_
#define __FMOPL_H_
/* select output bits size of output : 8 or 16 */
#define OPL_SAMPLE_BITS 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 */
typedef int BOOL;
#endif
#if (OPL_SAMPLE_BITS==16)
typedef INT16 OPLSAMPLE;
#endif
#if (OPL_SAMPLE_BITS==8)
typedef INT8 OPLSAMPLE;
#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);
int YM3812Init(int num, int clock, int rate);
void YM3812Shutdown(void);
void YM3812ResetChip(int which);
int YM3812Write(int which, int a, int v, const int *volume);
unsigned char YM3812Read(int which, int a);
void YM3812Mute(int which,int channel,BOOL mute);
int YM3812TimerOver(int which, int c);
void YM3812UpdateOne(int which, INT16 *buffer, int length);
void YM3812SetTimerHandler(int which, OPL_TIMERHANDLER TimerHandler, int channelOffset);
void YM3812SetIRQHandler(int which, OPL_IRQHANDLER IRQHandler, int param);
void YM3812SetUpdateHandler(int which, OPL_UPDATEHANDLER UpdateHandler, int param);
#endif /* __FMOPL_H_ */

View File

@@ -0,0 +1,32 @@
Copyright (c) 1997-2005, Nicola Salmoria and the MAME team
All rights reserved.
Redistribution and use of this code or any derivative works are permitted
provided that the following conditions are met:
* Redistributions may not be sold, nor may they be used in a commercial
product or activity.
* Redistributions that are modified from the original source must include the
complete source code, including the source code for all components used by a
binary built from the modified sources. However, as a special exception, the
source code 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.
* Redistributions must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@@ -171,13 +171,13 @@ void EventPoll(
{
case SDL_WINDOWEVENT_FOCUS_GAINED:
regainedFocus = true;
MusicSetPlaying(&gSoundDevice, true);
MusicSetPlaying(&gSoundDevice.music, true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (!gCampaign.IsClient &&
!ConfigGetBool(&gConfig, "StartServer"))
{
MusicSetPlaying(&gSoundDevice, false);
MusicSetPlaying(&gSoundDevice.music, false);
handlers->HasLostFocus = true;
}
// Reset input handlers

View File

@@ -427,7 +427,11 @@ static void ConvertMission(
CArrayPushBack(&dest->Weapons, &wc);
}
}
strcpy(dest->Song, src->song);
if (strlen(src->song) > 0)
{
dest->Music.Type = MUSIC_SRC_DYNAMIC;
CSTRDUP(dest->Music.Data.Filename, src->song);
}
const color_t maskAlt =
RangeToColor(abs(src->altRange) % COLORRANGE_COUNT);

View File

@@ -135,12 +135,13 @@ int MapNewLoadArchive(const char *filename, CampaignSetting *c)
}
if (hasCustomAmmo)
{
PickupClassesLoadAmmo(&gPickupClasses.CustomClasses, &gAmmo.CustomAmmo);
PickupClassesLoadAmmo(
&gPickupClasses.CustomClasses, &gAmmo.CustomAmmo);
}
if (hasCustomGuns)
{
PickupClassesLoadGuns(
&gPickupClasses.CustomClasses, &gWeaponClasses.CustomGuns);
&gPickupClasses.CustomClasses, &gWeaponClasses.CustomGuns);
}
PickupClassesLoadKeys(&gPickupClasses.KeyClasses);
@@ -361,8 +362,12 @@ static json_t *SaveMissions(CArray *a)
json_insert_pair_into_object(
node, "Weapons", SaveWeapons(&mission->Weapons));
json_insert_pair_into_object(
node, "Song", json_new_string(mission->Song));
if (mission->Music.Type == MUSIC_SRC_DYNAMIC &&
strlen(mission->Music.Data.Filename) > 0)
{
json_insert_pair_into_object(
node, "Song", json_new_string(mission->Music.Data.Filename));
}
switch (mission->Type)
{
@@ -411,8 +416,8 @@ static json_t *SaveMissions(CArray *a)
AddBoolPair(node, "ExitEnabled", mission->u.Interior.ExitEnabled);
json_insert_pair_into_object(
node, "Doors", SaveDoors(mission->u.Interior.Doors));
json_insert_pair_into_object(
node, "Pillars", SavePillars(mission->u.Interior.Pillars));
json_insert_pair_into_object(
node, "Pillars", SavePillars(mission->u.Interior.Pillars));
break;
default:
CASSERT(false, "unknown map type");

View File

@@ -292,7 +292,8 @@ void LoadMissions(CArray *missions, json_t *missionsNode, int version)
LoadInt(&m.EnemyDensity, child, "EnemyDensity");
LoadWeapons(
&m.Weapons, json_find_first_label(child, "Weapons")->child);
strcpy(m.Song, json_find_first_label(child, "Song")->child->text);
m.Music.Type = MUSIC_SRC_DYNAMIC;
LoadStr(&m.Music.Data.Filename, child, "Song");
switch (m.Type)
{
case MAPTYPE_CLASSIC:
@@ -309,7 +310,9 @@ void LoadMissions(CArray *missions, json_t *missionsNode, int version)
LoadDoors(
&m.u.Classic.Doors,
json_find_first_label(child, "Doors")->child);
LoadPillars(&m.u.Classic.Pillars, json_find_first_label(child, "Pillars")->child);
LoadPillars(
&m.u.Classic.Pillars,
json_find_first_label(child, "Pillars")->child);
break;
case MAPTYPE_STATIC:
if (!MissionStaticTryLoadJSON(
@@ -353,7 +356,9 @@ void LoadMissions(CArray *missions, json_t *missionsNode, int version)
LoadDoors(
&m.u.Interior.Doors,
json_find_first_label(child, "Doors")->child);
LoadPillars(&m.u.Interior.Pillars, json_find_first_label(child, "Pillars")->child);
LoadPillars(
&m.u.Interior.Pillars,
json_find_first_label(child, "Pillars")->child);
break;
default:
assert(0 && "unknown map type");

View File

@@ -125,35 +125,107 @@ static const char *GetSound(const CWMapType type, const int i)
}
}
static const char *musicW[] = {
"corner", // 0
"dungeon", // 1
"warmarch", // 2
"getthem", // 3
"headache", // 4
"hitlerwaltz", // 5
"introcw3", // 6
"nazi_nor", // 7
"nazi_omi", // 8
"pow", // 9
"salute", // 10
"searchn", // 11
"suspense", // 12
"victors", // 13
"wonderin", // 14
"funkyou", // 15
"endlevel", // 16
"goingaft", // 17
"pregnant", // 18
"ultimate", // 19
"nazi_rap", // 20
"zerohour", // 21
"twelfth", // 22
"roster", // 23
"urahero", // 24
"vicmarch", // 25
"pacman", // 26
typedef enum
{
CORNER_MUS, // 0
DUNGEON_MUS, // 1
WARMARCH_MUS, // 2
GETTHEM_MUS, // 3
HEADACHE_MUS, // 4
HITLWLTZ_MUS, // 5
INTROCW3_MUS, // 6
NAZI_NOR_MUS, // 7
NAZI_OMI_MUS, // 8
POW_MUS, // 9
SALUTE_MUS, // 10
SEARCHN_MUS, // 11
SUSPENSE_MUS, // 12
VICTORS_MUS, // 13
WONDERIN_MUS, // 14
FUNKYOU_MUS, // 15
ENDLEVEL_MUS, // 16
GOINGAFT_MUS, // 17
PREGNANT_MUS, // 18
ULTIMATE_MUS, // 19
NAZI_RAP_MUS, // 20
ZEROHOUR_MUS, // 21
TWELFTH_MUS, // 22
ROSTER_MUS, // 23
URAHERO_MUS, // 24
VICMARCH_MUS, // 25
PACMAN_MUS, // 26
LASTMUSIC
} MusicWolf;
static const int songsWolf[] = {
//
// Episode One
//
GETTHEM_MUS, SEARCHN_MUS, POW_MUS, SUSPENSE_MUS, GETTHEM_MUS, SEARCHN_MUS,
POW_MUS, SUSPENSE_MUS,
WARMARCH_MUS, // Boss level
CORNER_MUS, // Secret level
//
// Episode Two
//
NAZI_OMI_MUS, PREGNANT_MUS, GOINGAFT_MUS, HEADACHE_MUS, NAZI_OMI_MUS,
PREGNANT_MUS, HEADACHE_MUS, GOINGAFT_MUS,
WARMARCH_MUS, // Boss level
DUNGEON_MUS, // Secret level
//
// Episode Three
//
INTROCW3_MUS, NAZI_RAP_MUS, TWELFTH_MUS, ZEROHOUR_MUS, INTROCW3_MUS,
NAZI_RAP_MUS, TWELFTH_MUS, ZEROHOUR_MUS,
ULTIMATE_MUS, // Boss level
PACMAN_MUS, // Secret level
//
// Episode Four
//
GETTHEM_MUS, SEARCHN_MUS, POW_MUS, SUSPENSE_MUS, GETTHEM_MUS, SEARCHN_MUS,
POW_MUS, SUSPENSE_MUS,
WARMARCH_MUS, // Boss level
CORNER_MUS, // Secret level
//
// Episode Five
//
NAZI_OMI_MUS, PREGNANT_MUS, GOINGAFT_MUS, HEADACHE_MUS, NAZI_OMI_MUS,
PREGNANT_MUS, HEADACHE_MUS, GOINGAFT_MUS,
WARMARCH_MUS, // Boss level
DUNGEON_MUS, // Secret level
//
// Episode Six
//
INTROCW3_MUS, NAZI_RAP_MUS, TWELFTH_MUS, ZEROHOUR_MUS, INTROCW3_MUS,
NAZI_RAP_MUS, TWELFTH_MUS, ZEROHOUR_MUS,
ULTIMATE_MUS, // Boss level
FUNKYOU_MUS // Secret level
};
static Mix_Chunk *LoadMusic(const CWolfMap *map, const int i)
{
// TODO: spear music
char *data;
size_t len;
const int err = CWAudioGetMusic(&map->audio, songsWolf[i], &data, &len);
if (err != 0)
{
goto bail;
}
return Mix_QuickLoad_RAW((Uint8 *)data, (Uint32)len);
bail:
return NULL;
}
int MapWolfScan(const char *filename, char **title, int *numMissions)
{
@@ -384,7 +456,8 @@ static void LoadMission(
CArrayPushBack(&m.Weapons, &wc);
wc = StrWeaponClass("Knife");
CArrayPushBack(&m.Weapons, &wc);
// TODO: song
m.Music.Type = MUSIC_SRC_CHUNK;
m.Music.Data.Chunk = LoadMusic(map, missionIndex);
MissionStaticInit(&m.u.Static);
m.u.Static.TileClasses = hashmap_copy(tileClasses, TileClassCopyHashMap);

View File

@@ -187,7 +187,20 @@ void MissionCopy(Mission *dst, const Mission *src)
dst->EnemyDensity = src->EnemyDensity;
CArrayCopy(&dst->Weapons, &src->Weapons);
memcpy(dst->Song, src->Song, sizeof dst->Song);
dst->Music = src->Music;
switch (src->Music.Type)
{
case MUSIC_SRC_DYNAMIC:
CSTRDUP(dst->Music.Data.Filename, src->Music.Data.Filename);
break;
case MUSIC_SRC_CHUNK:
// TODO: can't copy music chunks, only used by editor anyway
dst->Music.Data.Chunk = NULL;
break;
default:
CASSERT(false, "unsupported music type");
break;
}
memcpy(&dst->u, &src->u, sizeof dst->u);
switch (src->Type)
@@ -247,6 +260,17 @@ void MissionTerminate(Mission *m)
CASSERT(false, "unknown map type");
break;
}
switch (m->Music.Type)
{
case MUSIC_SRC_DYNAMIC:
CFREE(m->Music.Data.Filename);
break;
case MUSIC_SRC_CHUNK:
Mix_FreeChunk(m->Music.Data.Chunk);
break;
default:
break;
}
memset(m, 0, sizeof *m);
}
@@ -462,9 +486,22 @@ void MissionBegin(struct MissionOptions *m, const NGameBegin gb)
{
m->HasBegun = true;
m->state = MISSION_STATE_PLAY;
MusicPlay(
&gSoundDevice, MUSIC_GAME, gCampaign.Entry.Path, m->missionData->Song);
const char *musicErrorMsg = MusicGetErrorMessage(&gSoundDevice);
switch (m->missionData->Music.Type)
{
case MUSIC_SRC_DYNAMIC:
MusicPlayFile(
&gSoundDevice.music, MUSIC_GAME, gCampaign.Entry.Path,
m->missionData->Music.Data.Filename);
break;
case MUSIC_SRC_CHUNK:
MusicPlayChunk(
&gSoundDevice.music, MUSIC_GAME, m->missionData->Music.Data.Chunk);
break;
default:
CASSERT(false, "unsupported music type");
break;
}
const char *musicErrorMsg = MusicGetErrorMessage(&gSoundDevice.music);
if (strlen(musicErrorMsg) > 0)
{
// Display music error message for 2 seconds

View File

@@ -137,7 +137,14 @@ typedef struct
int EnemyDensity;
CArray Weapons; // of WeaponClass *
char Song[CDOGS_PATH_MAX];
struct
{
MusicSourceType Type;
union {
char *Filename;
Mix_Chunk *Chunk;
} Data;
} Music;
union {
// Classic

View File

@@ -1,58 +1,115 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (c) 2013-2016, 2019, 2021 Cong Xu
All rights reserved.
Copyright (c) 2013-2016, 2019, 2021 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "music.h"
#include <string.h>
#include <SDL.h>
#ifdef __EMSCRIPTEN__
#include <SDL2/SDL_mixer.h>
#else
#include <SDL_mixer.h>
#endif
#include <tinydir/tinydir.h>
#include "config.h"
#include "gamedata.h"
#include "log.h"
#include "sounds.h"
static void LoadMusic(CArray *tracks, const char *path);
void MusicPlayerInit(MusicPlayer *mp)
{
memset(mp, 0, sizeof *mp);
// Load music
LoadMusic(&mp->generalTracks[MUSIC_MENU], "music/menu");
LoadMusic(&mp->generalTracks[MUSIC_BRIEFING], "music/briefing");
LoadMusic(&mp->generalTracks[MUSIC_GAME], "music/game");
}
static void LoadMusic(CArray *tracks, const char *path)
{
CArrayInit(tracks, sizeof(Mix_Music *));
tinydir_dir dir;
char buf[CDOGS_PATH_MAX];
GetDataFilePath(buf, path);
if (tinydir_open(&dir, buf) == -1)
{
LOG(LM_MAIN, LL_ERROR, "Cannot open music dir %s: %s", buf,
strerror(errno));
return;
}
for (; dir.has_next; tinydir_next(&dir))
{
Mix_Music *m;
tinydir_file file;
if (tinydir_readfile(&dir, &file) == -1)
{
goto bail;
}
if (!file.is_reg)
{
continue;
}
m = MusicLoad(file.path);
if (m == NULL)
{
continue;
}
CArrayPushBack(tracks, &m);
}
bail:
tinydir_close(&dir);
}
static void UnloadMusic(CArray *tracks);
void MusicPlayerTerminate(MusicPlayer *mp)
{
for (MusicType type = MUSIC_MENU; type < MUSIC_COUNT; type++)
{
UnloadMusic(&mp->generalTracks[type]);
}
}
static void UnloadMusic(CArray *tracks)
{
CA_FOREACH(Mix_Music *, m, *tracks)
Mix_FreeMusic(*m);
CA_FOREACH_END()
CArrayTerminate(tracks);
}
Mix_Music *MusicLoad(const char *path)
{
// Only load music from known extensions
const char *ext = strrchr(path, '.');
if (ext == NULL || !(
strcmp(ext, ".it") == 0 || strcmp(ext, ".IT") == 0 ||
strcmp(ext, ".mod") == 0 || strcmp(ext, ".MOD") == 0 ||
strcmp(ext, ".ogg") == 0 || strcmp(ext, ".OGG") == 0 ||
strcmp(ext, ".s3m") == 0 || strcmp(ext, ".S3M") == 0 ||
strcmp(ext, ".xm") == 0 || strcmp(ext, ".XM") == 0))
if (ext == NULL ||
!(strcmp(ext, ".it") == 0 || strcmp(ext, ".IT") == 0 ||
strcmp(ext, ".mod") == 0 || strcmp(ext, ".MOD") == 0 ||
strcmp(ext, ".ogg") == 0 || strcmp(ext, ".OGG") == 0 ||
strcmp(ext, ".s3m") == 0 || strcmp(ext, ".S3M") == 0 ||
strcmp(ext, ".xm") == 0 || strcmp(ext, ".XM") == 0))
{
return NULL;
}
@@ -60,29 +117,19 @@ Mix_Music *MusicLoad(const char *path)
return Mix_LoadMUS(path);
}
static bool PlayMusic(SoundDevice *device)
static void PlayMusic(MusicPlayer *mp)
{
if (device->music == NULL)
{
strcpy(device->musicErrorMessage, SDL_GetError());
return false;
}
MusicResume(device);
MusicResume(mp);
if (ConfigGetInt(&gConfig, "Sound.MusicVolume") == 0)
{
MusicPause(device);
MusicPause(mp);
}
device->musicErrorMessage[0] = '\0';
return true;
}
static bool Play(SoundDevice *device, const char *path)
static bool Play(MusicPlayer *mp, const char *path)
{
if (!device->isInitialised)
if (!mp->isInitialised)
{
return true;
}
@@ -93,19 +140,42 @@ static bool Play(SoundDevice *device, const char *path)
return false;
}
device->music = MusicLoad(path);
return PlayMusic(device);
mp->type = MUSIC_SRC_DYNAMIC;
mp->u.dynamic = MusicLoad(path);
if (mp->u.dynamic == NULL)
{
strcpy(mp->errorMessage, SDL_GetError());
return false;
}
mp->errorMessage[0] = '\0';
PlayMusic(mp);
return true;
}
void MusicPlay(
SoundDevice *device, const MusicType type,
const char *missionPath, const char *music)
void MusicPlayGeneral(MusicPlayer *mp, const MusicType type)
{
// Play a tune
// Start by trying to play a mission specific song,
// otherwise pick one from the general collection...
MusicStop(device);
device->musicIsDynamic = false;
mp->type = MUSIC_SRC_GENERAL;
CArray *tracks = &mp->generalTracks[type];
if (tracks->size == 0)
{
return;
}
mp->u.general = *(Mix_Music **)CArrayGet(tracks, 0);
// Shuffle tracks
if (tracks->size > 1)
{
while (mp->u.general == *(Mix_Music **)CArrayGet(tracks, 0))
{
CArrayShuffle(tracks);
}
}
PlayMusic(mp);
}
void MusicPlayFile(
MusicPlayer *mp, const MusicType type, const char *missionPath,
const char *music)
{
MusicStop(mp);
bool played = false;
if (music != NULL && strlen(music) != 0)
{
@@ -115,91 +185,133 @@ void MusicPlay(
GetDataFilePath(buf, missionPath);
strcat(buf, "/");
strcat(buf, music);
played = Play(device, buf);
played = Play(mp, buf);
if (!played)
{
char buf2[CDOGS_PATH_MAX];
GetDataFilePath(buf2, missionPath);
PathGetDirname(buf, buf2);
strcat(buf, music);
played = Play(device, buf);
}
if (played)
{
device->musicIsDynamic = true;
played = Play(mp, buf);
}
}
if (!played)
{
CArray *tracks = &device->musicTracks[type];
if (tracks->size == 0)
MusicPlayGeneral(mp, type);
}
}
void MusicPlayChunk(MusicPlayer *mp, const MusicType type, Mix_Chunk *chunk)
{
MusicStop(mp);
bool played = false;
if (chunk != NULL)
{
mp->type = MUSIC_SRC_CHUNK;
mp->u.chunk.chunk = chunk;
mp->u.chunk.channel = Mix_PlayChannel(-1, chunk, -1);
played = true;
}
if (!played)
{
MusicPlayGeneral(mp, type);
}
}
void MusicStop(MusicPlayer *mp)
{
switch (mp->type)
{
case MUSIC_SRC_GENERAL:
Mix_HaltMusic();
mp->u.general = NULL;
break;
case MUSIC_SRC_DYNAMIC:
Mix_HaltMusic();
if (mp->u.dynamic != NULL)
{
Mix_FreeMusic(mp->u.dynamic);
}
mp->u.dynamic = NULL;
break;
case MUSIC_SRC_CHUNK:
if (mp->u.chunk.chunk != NULL)
{
Mix_HaltChannel(mp->u.chunk.channel);
}
mp->u.chunk.chunk = NULL;
break;
}
}
void MusicPause(MusicPlayer *mp)
{
switch (mp->type)
{
case MUSIC_SRC_GENERAL:
case MUSIC_SRC_DYNAMIC:
if (Mix_PlayingMusic())
{
Mix_PauseMusic();
}
break;
case MUSIC_SRC_CHUNK:
Mix_Pause(mp->u.chunk.channel);
break;
}
}
void MusicResume(MusicPlayer *mp)
{
switch (mp->type)
{
case MUSIC_SRC_GENERAL:
if (mp->u.general == NULL)
{
return;
}
device->music = *(Mix_Music **)CArrayGet(tracks, 0);
// Shuffle tracks
if (tracks->size > 1)
if (Mix_PausedMusic())
{
while (device->music == *(Mix_Music **)CArrayGet(tracks, 0))
{
CArrayShuffle(tracks);
}
Mix_ResumeMusic();
}
PlayMusic(device);
}
}
void MusicStop(SoundDevice *device)
{
if (device->music != NULL)
{
Mix_HaltMusic();
if (device->musicIsDynamic)
else if (!Mix_PlayingMusic())
{
Mix_FreeMusic(device->music);
Mix_PlayMusic(mp->u.general, -1);
}
device->music = NULL;
break;
case MUSIC_SRC_DYNAMIC:
if (mp->u.dynamic == NULL)
{
return;
}
if (Mix_PausedMusic())
{
Mix_ResumeMusic();
}
else if (!Mix_PlayingMusic())
{
Mix_PlayMusic(mp->u.dynamic, -1);
}
break;
case MUSIC_SRC_CHUNK:
Mix_Resume(mp->u.chunk.channel);
break;
}
}
void MusicPause(SoundDevice *s)
{
UNUSED(s);
if (Mix_PlayingMusic())
{
Mix_PauseMusic();
}
}
void MusicResume(SoundDevice *device)
{
if (device->music == NULL)
{
return;
}
if (Mix_PausedMusic())
{
Mix_ResumeMusic();
}
else if (!Mix_PlayingMusic())
{
Mix_PlayMusic(device->music, -1);
}
}
void MusicSetPlaying(SoundDevice *device, int isPlaying)
void MusicSetPlaying(MusicPlayer *mp, const bool isPlaying)
{
if (isPlaying)
{
MusicResume(device);
MusicResume(mp);
}
else
{
MusicPause(device);
MusicPause(mp);
}
}
const char *MusicGetErrorMessage(SoundDevice *device)
const char *MusicGetErrorMessage(const MusicPlayer *mp)
{
return device->musicErrorMessage;
return mp->errorMessage;
}

View File

@@ -1,62 +1,104 @@
/*
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (C) 1995 Ronny Wester
Copyright (C) 2003 Jeremy Chin
Copyright (C) 2003-2007 Lucas Martin-King
C-Dogs SDL
A port of the legendary (and fun) action/arcade cdogs.
Copyright (C) 1995 Ronny Wester
Copyright (C) 2003 Jeremy Chin
Copyright (C) 2003-2007 Lucas Martin-King
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 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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This file incorporates work covered by the following copyright and
permission notice:
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013, 2016-2017, 2019 Cong Xu
All rights reserved.
Copyright (c) 2013, 2016-2017, 2019, 2021 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "sounds.h"
#ifdef __EMSCRIPTEN__
#include <SDL2/SDL_mixer.h>
#else
#include <SDL_mixer.h>
#endif
#include <stdbool.h>
#include "c_array.h"
typedef enum
{
MUSIC_MENU,
MUSIC_BRIEFING,
MUSIC_GAME,
MUSIC_COUNT
} MusicType;
typedef enum
{
MUSIC_SRC_GENERAL,
MUSIC_SRC_DYNAMIC,
MUSIC_SRC_CHUNK
} MusicSourceType;
typedef struct
{
bool isInitialised;
MusicSourceType type;
union {
Mix_Music *general;
Mix_Music *dynamic;
struct
{
Mix_Chunk *chunk;
int channel;
} chunk;
} u;
CArray generalTracks[MUSIC_COUNT]; // of Mix_Music *
char errorMessage[128];
} MusicPlayer;
void MusicPlayerInit(MusicPlayer *mp);
void MusicPlayerTerminate(MusicPlayer *mp);
Mix_Music *MusicLoad(const char *path);
void MusicPlay(
SoundDevice *device, const MusicType type,
const char *missionPath, const char *music);
void MusicStop(SoundDevice *device);
void MusicPause(SoundDevice *s);
void MusicResume(SoundDevice *device);
void MusicSetPlaying(SoundDevice *device, int isPlaying);
const char *MusicGetErrorMessage(SoundDevice *device);
void MusicPlayGeneral(MusicPlayer *mp, const MusicType type);
void MusicPlayFile(
MusicPlayer *mp, const MusicType type, const char *missionPath,
const char *music);
void MusicPlayChunk(MusicPlayer *mp, const MusicType type, Mix_Chunk *chunk);
void MusicStop(MusicPlayer *mp);
void MusicPause(MusicPlayer *mp);
void MusicResume(MusicPlayer *mp);
void MusicSetPlaying(MusicPlayer *mp, const bool isPlaying);
const char *MusicGetErrorMessage(const MusicPlayer *mp);

View File

@@ -182,7 +182,6 @@ void SoundAdd(map_t sounds, const char *name, SoundData *sound)
}
}
static void SoundLoadMusic(CArray *tracks, const char *path);
void SoundInitialize(SoundDevice *device, const char *path)
{
memset(device, 0, sizeof *device);
@@ -193,11 +192,7 @@ void SoundInitialize(SoundDevice *device, const char *path)
char buf[CDOGS_PATH_MAX];
GetDataFilePath(buf, path);
SoundLoadDir(device->sounds, buf, NULL);
// Load music
SoundLoadMusic(&device->musicTracks[MUSIC_MENU], "music/menu");
SoundLoadMusic(&device->musicTracks[MUSIC_BRIEFING], "music/briefing");
SoundLoadMusic(&device->musicTracks[MUSIC_GAME], "music/game");
MusicPlayerInit(&device->music);
}
void SoundLoadDir(map_t sounds, const char *path, const char *prefix)
{
@@ -242,43 +237,6 @@ void SoundLoadDir(map_t sounds, const char *path, const char *prefix)
}
}
bail:
tinydir_close(&dir);
}
static void SoundLoadMusic(CArray *tracks, const char *path)
{
CArrayInit(tracks, sizeof(Mix_Music *));
tinydir_dir dir;
char buf[CDOGS_PATH_MAX];
GetDataFilePath(buf, path);
if (tinydir_open(&dir, buf) == -1)
{
LOG(LM_MAIN, LL_ERROR, "Cannot open music dir %s: %s", buf,
strerror(errno));
return;
}
for (; dir.has_next; tinydir_next(&dir))
{
Mix_Music *m;
tinydir_file file;
if (tinydir_readfile(&dir, &file) == -1)
{
goto bail;
}
if (!file.is_reg)
{
continue;
}
m = MusicLoad(file.path);
if (m == NULL)
{
continue;
}
CArrayPushBack(tracks, &m);
}
bail:
tinydir_close(&dir);
}
@@ -296,7 +254,7 @@ static void SoundClose(SoundDevice *s, const bool waitForSoundsComplete)
while (Mix_Playing(-1) > 0 && SDL_GetTicks() - waitStart < 1000)
;
// Don't stop the music unless we're reopening
MusicStop(s);
MusicStop(&s->music);
}
while (Mix_Init(0))
{
@@ -308,6 +266,7 @@ static void SoundClose(SoundDevice *s, const bool waitForSoundsComplete)
void SoundReconfigure(SoundDevice *s)
{
s->isInitialised = false;
s->music.isInitialised = false;
if (Mix_AllocateChannels(s->channels) != s->channels)
{
@@ -319,16 +278,10 @@ void SoundReconfigure(SoundDevice *s)
Mix_Volume(-1, sVol);
const int mVol = ConfigGetInt(&gConfig, "Sound.MusicVolume");
Mix_VolumeMusic(mVol);
if (mVol > 0)
{
MusicResume(s);
}
else
{
MusicPause(s);
}
MusicSetPlaying(&s->music, mVol > 0);
s->isInitialised = true;
s->music.isInitialised = true;
}
void SoundReopen(SoundDevice *s)
@@ -348,7 +301,6 @@ void SoundClear(map_t sounds)
{
hashmap_clear(sounds, SoundDataTerminate);
}
static void SoundUnloadMusic(CArray *tracks);
void SoundTerminate(SoundDevice *device, const bool waitForSoundsComplete)
{
SoundClose(device, waitForSoundsComplete);
@@ -356,10 +308,7 @@ void SoundTerminate(SoundDevice *device, const bool waitForSoundsComplete)
hashmap_destroy(device->sounds, SoundDataTerminate);
hashmap_destroy(device->customSounds, SoundDataTerminate);
for (MusicType type = MUSIC_MENU; type < MUSIC_COUNT; type++)
{
SoundUnloadMusic(&device->musicTracks[type]);
}
MusicPlayerTerminate(&device->music);
}
static void SoundDataTerminate(any_t data)
{
@@ -381,13 +330,6 @@ static void SoundDataTerminate(any_t data)
}
CFREE(s);
}
static void SoundUnloadMusic(CArray *tracks)
{
CA_FOREACH(Mix_Music *, m, *tracks)
Mix_FreeMusic(*m);
CA_FOREACH_END()
CArrayTerminate(tracks);
}
#define OUT_OF_SIGHT_DISTANCE_PLUS 100
static int GetChannel(SoundDevice *s, Mix_Chunk *data);

View File

@@ -22,7 +22,7 @@
This file incorporates work covered by the following copyright and
permission notice:
Copyright (c) 2013-2017, 2019-2020 Cong Xu
Copyright (c) 2013-2017, 2019-2021 Cong Xu
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -61,6 +61,7 @@
#include "c_hashmap/hashmap.h"
#include "defs.h"
#include "mathc/mathc.h"
#include "music.h"
#include "sys_config.h"
#include "utils.h"
#include "vector.h"
@@ -88,21 +89,10 @@ typedef struct
} u;
} SoundData;
typedef enum
{
MUSIC_MENU,
MUSIC_BRIEFING,
MUSIC_GAME,
MUSIC_COUNT
} MusicType;
typedef struct
{
int isInitialised;
Mix_Music *music;
bool musicIsDynamic;
CArray musicTracks[MUSIC_COUNT]; // of Mix_Music *
char musicErrorMessage[128];
MusicPlayer music;
bool isInitialised;
int channels;
// Two sets of ears for 4-player split screen

View File

@@ -153,11 +153,22 @@ static char *MissionGetSong(UIObject *o, void *data)
{
UNUSED(o);
Campaign *co = data;
if (!CampaignGetCurrentMission(co))
const Mission *m = CampaignGetCurrentMission(co);
if (!m || m->Music.Type != MUSIC_SRC_DYNAMIC)
{
return NULL;
}
return CampaignGetCurrentMission(co)->Song;
return m->Music.Data.Filename;
}
static char **MissionGetSongSrc(void *data)
{
Campaign *co = data;
const Mission *m = CampaignGetCurrentMission(co);
if (!m || m->Music.Type != MUSIC_SRC_DYNAMIC)
{
return NULL;
}
return &m->Music.Data.Filename;
}
static const char *MissionGetWidthStr(UIObject *o, void *data)
{
@@ -1037,9 +1048,9 @@ static UIObject *CreateMissionObjs(Campaign *co)
o = UIObjectCreate(
UITYPE_TEXTBOX, YC_MISSIONTITLE, svec2i(0, Y_ABS), svec2i(319, th));
o->u.Textbox.TextLinkFunc = MissionGetSong;
o->u.Textbox.MaxLen = sizeof((Mission *)0)->Song - 1;
o->u.Textbox.TextSourceFunc = MissionGetSongSrc;
o->Data = co;
CSTRDUP(o->u.Textbox.Hint, "(Mission song)");
CSTRDUP(o->u.Textbox.Hint, "(Mission song - enter filename without extension)");
o->Id2 = XC_MUSICFILE;
o->Flags = UI_SELECT_ONLY;
o->CheckVisible = MissionCheckVisible;

View File

@@ -148,7 +148,7 @@ static void MainMenuOnEnter(GameLoopData *data)
return;
}
MusicPlay(&gSoundDevice, MUSIC_MENU, NULL, NULL);
MusicPlayGeneral(&gSoundDevice.music, MUSIC_MENU);
// Reset config - could have been set to other values by server
ConfigResetChanged(&gConfig);
CampaignSettingTerminateAll(&gCampaign.Setting);