use an external command to play midi music on windows
this works around the SDL_mixer known issue that on windows, controlling the music volume for midi playback always changes the entire process volume, so also affects effect sounds. unfortunately, Mix_SetMusicCMD doesn't work on windows either. so we just detect midi file playback and launch a trivial midi player in an external process with a fixed volume.
This commit is contained in:
parent
4bb6e4d572
commit
9e11518abb
6 changed files with 549 additions and 28 deletions
|
@ -1265,10 +1265,19 @@ if(WIN32 AND ENABLE_NSIS AND MAKENSIS_FOUND)
|
|||
add_custom_target(nsis ALL DEPENDS Stratagus-${STRATAGUS_VERSION}${MAKENSIS_SUFFIX})
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
add_executable(midiplayer WIN32 "src/sound/win32/midiplayer.c")
|
||||
set_target_properties(midiplayer PROPERTIES LINK_FLAGS "/SUBSYSTEM:CONSOLE")
|
||||
set_target_properties(midiplayer PROPERTIES OUTPUT_NAME "stratagus-midiplayer")
|
||||
endif()
|
||||
|
||||
########### install files ###############
|
||||
|
||||
install(TARGETS stratagus DESTINATION ${GAMEDIR})
|
||||
install(TARGETS png2stratagus DESTINATION ${BINDIR})
|
||||
if (WIN32)
|
||||
install(TARGETS midiplayer DESTINATION ${GAMEDIR})
|
||||
endif()
|
||||
|
||||
if(ENABLE_DOC AND DOXYGEN_FOUND)
|
||||
install(FILES doc/stratagus.6 DESTINATION ${MANDIR})
|
||||
|
|
|
@ -88,8 +88,6 @@ extern bool IsEffectsEnabled();
|
|||
/// Set the music finished callback
|
||||
void SetMusicFinishedCallback(void (*callback)());
|
||||
/// Play a music file
|
||||
extern int PlayMusic(Mix_Music *sample);
|
||||
/// Play a music file
|
||||
extern int PlayMusic(const std::string &file);
|
||||
/// Stop music playing
|
||||
extern void StopMusic();
|
||||
|
|
|
@ -60,6 +60,119 @@ static bool EffectsEnabled = true;
|
|||
static double VolumeScale = 1.0;
|
||||
static int MusicVolume = 0;
|
||||
|
||||
static void (*MusicFinishedCallback)();
|
||||
|
||||
#ifdef USE_WIN32
|
||||
static volatile bool threadWaiting = false;
|
||||
static std::string externalFile;
|
||||
static HANDLE hWaitingThread;
|
||||
static PROCESS_INFORMATION pi;
|
||||
|
||||
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
if (threadWaiting) {
|
||||
MusicFinishedCallback();
|
||||
threadWaiting = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void killPlayingProcess() {
|
||||
if (threadWaiting) {
|
||||
threadWaiting = false;
|
||||
TerminateProcess(pi.hProcess, 0);
|
||||
WaitForSingleObject(hWaitingThread, INFINITE);
|
||||
threadWaiting = false;
|
||||
} else {
|
||||
TerminateProcess(pi.hProcess, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool External_Play(const std::string &file) {
|
||||
if (threadWaiting && file == externalFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string midi = ".mid";
|
||||
auto it = midi.begin();
|
||||
if (file.size() > midi.size() &&
|
||||
std::all_of(std::next(file.begin(), file.size() - midi.size()), file.end(), [&it](const char & c) { return c == ::tolower(*(it++)); })) {
|
||||
// midi file, use external player, since windows vista+ does not allow midi volume control independent of process volume
|
||||
std::string full_filename = LibraryFileName(file.c_str());
|
||||
|
||||
static const char* midiplayerExe = "stratagus-midiplayer.exe";
|
||||
static const int midiplayerExeSz = strlen(midiplayerExe);
|
||||
|
||||
// set up a job so our children die with us
|
||||
static bool firstRun = true;
|
||||
static HANDLE hJob;
|
||||
if (firstRun) {
|
||||
hJob = CreateJobObject(NULL, NULL);
|
||||
JOBOBJECT_BASIC_LIMIT_INFORMATION limitInfo;
|
||||
ZeroMemory(&limitInfo, sizeof(limitInfo));
|
||||
limitInfo.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
SetInformationJobObject(hJob, JobObjectBasicLimitInformation, &limitInfo, sizeof(limitInfo));
|
||||
AssignProcessToJobObject(hJob, GetCurrentProcess());
|
||||
firstRun = false;
|
||||
}
|
||||
|
||||
int sz = midiplayerExeSz + 2 + 3 + 2 + full_filename.size() + 1; // exe + 2 spaces + 3 volume + 2 quotes + filename + nullbyte
|
||||
char *cmdline = new char[sz];
|
||||
snprintf(cmdline, sz, "%s %3d \"%s\"", midiplayerExe, std::min(MusicVolume, 255), full_filename.c_str());
|
||||
DebugPrint("Using external command to play midi on windows: %s\n" _C_ cmdline);
|
||||
killPlayingProcess();
|
||||
STARTUPINFO si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
bool result = true;
|
||||
if (CreateProcess(NULL, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
|
||||
AssignProcessToJobObject(hJob, pi.hProcess);
|
||||
externalFile = file;
|
||||
hWaitingThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, NULL);
|
||||
threadWaiting = true;
|
||||
} else {
|
||||
result = false;
|
||||
DebugPrint("CreateProcess failed (%d).\n" _C_ GetLastError());
|
||||
}
|
||||
delete[] cmdline;
|
||||
return result;
|
||||
}
|
||||
killPlayingProcess();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool External_IsPlaying() {
|
||||
return threadWaiting;
|
||||
}
|
||||
|
||||
static bool External_Stop() {
|
||||
if (External_IsPlaying()) {
|
||||
killPlayingProcess();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool External_Volume(int volume, int oldVolume) {
|
||||
if (External_IsPlaying() && externalFile.size() > 0) {
|
||||
if (oldVolume != volume) {
|
||||
External_Stop();
|
||||
External_Play(externalFile);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
#define External_Play(file) false
|
||||
#define External_IsPlaying() false
|
||||
#define External_Stop()
|
||||
#define External_Volume(volume) false
|
||||
#endif
|
||||
|
||||
extern volatile bool MusicFinished;
|
||||
|
||||
/// Channels for sound effects and unit speech
|
||||
|
@ -334,30 +447,10 @@ bool IsEffectsEnabled()
|
|||
*/
|
||||
void SetMusicFinishedCallback(void (*callback)())
|
||||
{
|
||||
MusicFinishedCallback = callback;
|
||||
Mix_HookMusicFinished(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
** Play a music file.
|
||||
**
|
||||
** @param sample Music sample.
|
||||
**
|
||||
** @return 0 if music is playing, -1 if not.
|
||||
*/
|
||||
int PlayMusic(Mix_Music *sample)
|
||||
{
|
||||
if (sample) {
|
||||
Mix_VolumeMusic(MusicVolume);
|
||||
MusicFinished = false;
|
||||
Mix_PlayMusic(sample, 0);
|
||||
Mix_VolumeMusic(MusicVolume / 4.0);
|
||||
return 0;
|
||||
} else {
|
||||
DebugPrint("Could not play sample\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** Play a music file.
|
||||
**
|
||||
|
@ -371,8 +464,13 @@ int PlayMusic(const std::string &file)
|
|||
return -1;
|
||||
}
|
||||
DebugPrint("play music %s\n" _C_ file.c_str());
|
||||
Mix_Music *music = LoadMusic(file);
|
||||
|
||||
if (External_Play(file)) {
|
||||
MusicFinished = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Mix_Music *music = LoadMusic(file);
|
||||
if (music) {
|
||||
MusicFinished = false;
|
||||
Mix_FadeInMusic(music, 0, 200);
|
||||
|
@ -388,6 +486,9 @@ int PlayMusic(const std::string &file)
|
|||
*/
|
||||
void StopMusic()
|
||||
{
|
||||
if (External_Stop()) {
|
||||
return;
|
||||
}
|
||||
Mix_FadeOutMusic(200);
|
||||
}
|
||||
|
||||
|
@ -400,7 +501,11 @@ void SetMusicVolume(int volume)
|
|||
{
|
||||
// due to left-right separation, sound effect volume is effectively halfed,
|
||||
// so we adjust the music
|
||||
int oldVolume = MusicVolume;
|
||||
MusicVolume = volume;
|
||||
if (External_Volume(volume, oldVolume)) {
|
||||
return;
|
||||
}
|
||||
Mix_VolumeMusic(volume / 4.0);
|
||||
}
|
||||
|
||||
|
|
411
src/sound/win32/midiplayer.c
Normal file
411
src/sound/win32/midiplayer.c
Normal file
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* mididemo.c
|
||||
*
|
||||
* Created on: Dec 21, 2011
|
||||
* Author: David J. Rager
|
||||
* Email: djrager@fourthwoods.com
|
||||
*
|
||||
* This code is hereby released into the public domain per the Creative Commons
|
||||
* Public Domain dedication.
|
||||
*
|
||||
* http://http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MAX_BUFFER_SIZE (512 * 12)
|
||||
HANDLE event;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct _mid_header {
|
||||
unsigned int id; // identifier "MThd"
|
||||
unsigned int size; // always 6 in big-endian format
|
||||
unsigned short format; // big-endian format
|
||||
unsigned short tracks; // number of tracks, big-endian
|
||||
unsigned short ticks; // number of ticks per quarter note, big-endian
|
||||
};
|
||||
|
||||
struct _mid_track {
|
||||
unsigned int id; // identifier "MTrk"
|
||||
unsigned int length; // track length, big-endian
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
struct trk {
|
||||
struct _mid_track* track;
|
||||
unsigned char* buf;
|
||||
unsigned char last_event;
|
||||
unsigned int absolute_time;
|
||||
};
|
||||
|
||||
struct evt {
|
||||
unsigned int absolute_time;
|
||||
unsigned char* data;
|
||||
unsigned char event;
|
||||
};
|
||||
|
||||
static unsigned char* load_file(const unsigned char* filename, unsigned int* len)
|
||||
{
|
||||
unsigned char* buf;
|
||||
unsigned int ret;
|
||||
FILE* f = fopen((char*)filename, "rb");
|
||||
if(f == NULL)
|
||||
return 0;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
*len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
buf = (unsigned char*)malloc(*len);
|
||||
if(buf == 0)
|
||||
{
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = fread(buf, 1, *len, f);
|
||||
fclose(f);
|
||||
|
||||
if(ret != *len)
|
||||
{
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static unsigned long read_var_long(unsigned char* buf, unsigned int* bytesread)
|
||||
{
|
||||
unsigned long var = 0;
|
||||
unsigned char c;
|
||||
|
||||
*bytesread = 0;
|
||||
|
||||
do
|
||||
{
|
||||
c = buf[(*bytesread)++];
|
||||
var = (var << 7) + (c & 0x7f);
|
||||
}
|
||||
while(c & 0x80);
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
static unsigned short swap_bytes_short(unsigned short in)
|
||||
{
|
||||
return ((in << 8) | (in >> 8));
|
||||
}
|
||||
|
||||
static unsigned long swap_bytes_long(unsigned long in)
|
||||
{
|
||||
unsigned short *p;
|
||||
p = (unsigned short*)∈
|
||||
|
||||
return ( (((unsigned long)swap_bytes_short(p[0])) << 16) |
|
||||
(unsigned long)swap_bytes_short(p[1]));
|
||||
}
|
||||
|
||||
static struct evt get_next_event(const struct trk* track)
|
||||
{
|
||||
unsigned char* buf;
|
||||
struct evt e;
|
||||
unsigned int bytesread;
|
||||
unsigned int time;
|
||||
|
||||
buf = track->buf;
|
||||
|
||||
time = read_var_long(buf, &bytesread);
|
||||
buf += bytesread;
|
||||
|
||||
e.absolute_time = track->absolute_time + time;
|
||||
e.data = buf;
|
||||
e.event = *e.data;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static int is_track_end(const struct evt* e)
|
||||
{
|
||||
if(e->event == 0xff) // meta-event?
|
||||
if(*(e->data + 1) == 0x2f) // track end?
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void CALLBACK example9_callback(HMIDIOUT out, UINT msg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case MOM_DONE:
|
||||
SetEvent(event);
|
||||
break;
|
||||
case MOM_POSITIONCB:
|
||||
case MOM_OPEN:
|
||||
case MOM_CLOSE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int get_buffer(struct trk* tracks, unsigned int ntracks, unsigned int* out, unsigned int* outlen)
|
||||
{
|
||||
MIDIEVENT e, *p;
|
||||
unsigned int streamlen = 0;
|
||||
unsigned int i;
|
||||
static unsigned int current_time = 0;
|
||||
|
||||
if(tracks == NULL || out == NULL || outlen == NULL)
|
||||
return 0;
|
||||
|
||||
*outlen = 0;
|
||||
|
||||
while(TRUE)
|
||||
{
|
||||
unsigned int time = (unsigned int)-1;
|
||||
unsigned int idx = -1;
|
||||
struct evt evt;
|
||||
unsigned char c;
|
||||
|
||||
if(((streamlen + 3) * sizeof(unsigned int)) >= MAX_BUFFER_SIZE)
|
||||
break;
|
||||
|
||||
// get the next event
|
||||
for(i = 0; i < ntracks; i++)
|
||||
{
|
||||
evt = get_next_event(&tracks[i]);
|
||||
if(!(is_track_end(&evt)) && (evt.absolute_time < time))
|
||||
{
|
||||
time = evt.absolute_time;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
// if idx == -1 then all the tracks have been read up to the end of track mark
|
||||
if(idx == -1)
|
||||
break; // we're done
|
||||
|
||||
e.dwStreamID = 0; // always 0
|
||||
|
||||
evt = get_next_event(&tracks[idx]);
|
||||
|
||||
tracks[idx].absolute_time = evt.absolute_time;
|
||||
e.dwDeltaTime = tracks[idx].absolute_time - current_time;
|
||||
current_time = tracks[idx].absolute_time;
|
||||
|
||||
if(!(evt.event & 0x80)) // running mode
|
||||
{
|
||||
unsigned char last = tracks[idx].last_event;
|
||||
c = *evt.data++; // get the first data byte
|
||||
e.dwEvent = ((unsigned long)MEVT_SHORTMSG << 24) |
|
||||
((unsigned long)last) |
|
||||
((unsigned long)c << 8);
|
||||
if(!((last & 0xf0) == 0xc0 || (last & 0xf0) == 0xd0))
|
||||
{
|
||||
c = *evt.data++; // get the second data byte
|
||||
e.dwEvent |= ((unsigned long)c << 16);
|
||||
}
|
||||
|
||||
p = (MIDIEVENT*)&out[streamlen];
|
||||
*p = e;
|
||||
|
||||
streamlen += 3;
|
||||
|
||||
tracks[idx].buf = evt.data;
|
||||
}
|
||||
else if(evt.event == 0xff) // meta-event
|
||||
{
|
||||
evt.data++; // skip the event byte
|
||||
unsigned char meta = *evt.data++; // read the meta-event byte
|
||||
unsigned int len;
|
||||
|
||||
switch(meta)
|
||||
{
|
||||
case 0x51: // only care about tempo events
|
||||
{
|
||||
unsigned char a, b, c;
|
||||
len = *evt.data++; // get the length byte, should be 3
|
||||
a = *evt.data++;
|
||||
b = *evt.data++;
|
||||
c = *evt.data++;
|
||||
|
||||
e.dwEvent = ((unsigned long)MEVT_TEMPO << 24) |
|
||||
((unsigned long)a << 16) |
|
||||
((unsigned long)b << 8) |
|
||||
((unsigned long)c << 0);
|
||||
|
||||
p = (MIDIEVENT*)&out[streamlen];
|
||||
*p = e;
|
||||
|
||||
streamlen += 3;
|
||||
}
|
||||
break;
|
||||
default: // skip all other meta events
|
||||
len = *evt.data++; // get the length byte
|
||||
evt.data += len;
|
||||
break;
|
||||
}
|
||||
|
||||
tracks[idx].buf = evt.data;
|
||||
}
|
||||
else if((evt.event & 0xf0) != 0xf0) // normal command
|
||||
{
|
||||
tracks[idx].last_event = evt.event;
|
||||
evt.data++; // skip the event byte
|
||||
c = *evt.data++; // get the first data byte
|
||||
e.dwEvent = ((unsigned long)MEVT_SHORTMSG << 24) |
|
||||
((unsigned long)evt.event << 0) |
|
||||
((unsigned long)c << 8);
|
||||
if(!((evt.event & 0xf0) == 0xc0 || (evt.event & 0xf0) == 0xd0))
|
||||
{
|
||||
c = *evt.data++; // get the second data byte
|
||||
e.dwEvent |= ((unsigned long)c << 16);
|
||||
}
|
||||
|
||||
p = (MIDIEVENT*)&out[streamlen];
|
||||
*p = e;
|
||||
|
||||
streamlen += 3;
|
||||
|
||||
tracks[idx].buf = evt.data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*outlen = streamlen * sizeof(unsigned int);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned int example9(char* filename, int volume)
|
||||
{
|
||||
unsigned char* midibuf = NULL;
|
||||
unsigned int midilen = 0;
|
||||
|
||||
struct _mid_header* hdr = NULL;
|
||||
|
||||
unsigned int i;
|
||||
|
||||
unsigned short ntracks = 0;
|
||||
struct trk* tracks = NULL;
|
||||
|
||||
unsigned int streambufsize = MAX_BUFFER_SIZE;
|
||||
unsigned int* streambuf = NULL;
|
||||
unsigned int streamlen = 0;
|
||||
|
||||
HMIDISTRM out;
|
||||
MIDIPROPTIMEDIV prop;
|
||||
MIDIHDR mhdr;
|
||||
unsigned int device = 0;
|
||||
|
||||
midibuf = load_file((unsigned char*)filename, &midilen);
|
||||
if(midibuf == NULL)
|
||||
{
|
||||
printf("could not open %s\n", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
hdr = (struct _mid_header*)midibuf;
|
||||
midibuf += sizeof(struct _mid_header);
|
||||
ntracks = swap_bytes_short(hdr->tracks);
|
||||
|
||||
tracks = (struct trk*)malloc(ntracks * sizeof(struct trk));
|
||||
if(tracks == NULL)
|
||||
goto error1;
|
||||
|
||||
for(i = 0; i < ntracks; i++)
|
||||
{
|
||||
tracks[i].track = (struct _mid_track*)midibuf;
|
||||
tracks[i].buf = midibuf + sizeof(struct _mid_track);
|
||||
tracks[i].absolute_time = 0;
|
||||
tracks[i].last_event = 0;
|
||||
|
||||
midibuf += sizeof(struct _mid_track) + swap_bytes_long(tracks[i].track->length);
|
||||
}
|
||||
|
||||
streambuf = (unsigned int*)malloc(sizeof(unsigned int) * streambufsize);
|
||||
if(streambuf == NULL)
|
||||
goto error2;
|
||||
|
||||
memset(streambuf, 0, sizeof(unsigned int) * streambufsize);
|
||||
|
||||
if ((event = CreateEvent(0, FALSE, FALSE, 0)) == NULL)
|
||||
goto error3;
|
||||
|
||||
if (midiStreamOpen(&out, &device, 1, (DWORD)example9_callback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
|
||||
goto error4;
|
||||
|
||||
prop.cbStruct = sizeof(MIDIPROPTIMEDIV);
|
||||
prop.dwTimeDiv = swap_bytes_short(hdr->ticks);
|
||||
if(midiStreamProperty(out, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV) != MMSYSERR_NOERROR)
|
||||
goto error5;
|
||||
|
||||
mhdr.lpData = (char*)streambuf;
|
||||
mhdr.dwBufferLength = mhdr.dwBytesRecorded = streambufsize;
|
||||
mhdr.dwFlags = 0;
|
||||
|
||||
if(midiOutPrepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
|
||||
goto error5;
|
||||
|
||||
if(midiStreamRestart(out) != MMSYSERR_NOERROR)
|
||||
goto error6;
|
||||
|
||||
if (midiOutSetVolume((HMIDIOUT)out, (DWORD)((volume & 0xFF) << 4)) != MMSYSERR_NOERROR)
|
||||
goto error6;
|
||||
|
||||
printf("buffering...\n");
|
||||
get_buffer(tracks, ntracks, streambuf, &streamlen);
|
||||
while(streamlen > 0)
|
||||
{
|
||||
mhdr.dwBytesRecorded = streamlen;
|
||||
|
||||
if(midiStreamOut(out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
|
||||
goto error7;
|
||||
|
||||
WaitForSingleObject(event, INFINITE);
|
||||
|
||||
printf("buffering...\n");
|
||||
get_buffer(tracks, ntracks, streambuf, &streamlen);
|
||||
}
|
||||
printf("done.\n");
|
||||
|
||||
error7:
|
||||
midiOutReset((HMIDIOUT)out);
|
||||
|
||||
error6:
|
||||
midiOutUnprepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR));
|
||||
|
||||
error5:
|
||||
midiStreamClose(out);
|
||||
|
||||
error4:
|
||||
CloseHandle(event);
|
||||
|
||||
error3:
|
||||
free(streambuf);
|
||||
|
||||
error2:
|
||||
free(tracks);
|
||||
|
||||
error1:
|
||||
free(hdr);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if(argc != 3) {
|
||||
return printf("Usage: %s <volume 0-255> <filename.mid>\n", argv[0]);
|
||||
}
|
||||
|
||||
example9(argv[2], atoi(argv[1]));
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -361,10 +361,7 @@ int PlayMovie(const std::string &name)
|
|||
}
|
||||
|
||||
StopMusic();
|
||||
Mix_Music *sample = LoadMusic(filename);
|
||||
if (sample) {
|
||||
PlayMusic(sample);
|
||||
}
|
||||
PlayMusic(filename);
|
||||
|
||||
EventCallback callbacks;
|
||||
|
||||
|
|
|
@ -215,6 +215,7 @@ Section "${NAME}"
|
|||
|
||||
SetOutPath $INSTDIR
|
||||
File "${EXE}"
|
||||
File midiplayer.exe
|
||||
File *.dll
|
||||
WriteRegStr HKLM "${REGKEY}" "DisplayName" "${NAME}"
|
||||
WriteRegStr HKLM "${REGKEY}" "UninstallString" "$\"$INSTDIR\${UNINSTALL}$\""
|
||||
|
|
Loading…
Add table
Reference in a new issue