Vendor the Shockolate engine and build it via a Docker image with every
build / build (push) Failing after 5s
build / build (push) Failing after 5s
dependency (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont) prebuilt, so compiling the engine needs no network access - just the image and the engine source. Add res/assets/extract_assets.sh to pull the game's data files out of a purchased GOG installer, and a Makefile that assembles a runnable dist/ from the two. Also add `make package`, which builds a redistributable tarball that omits the proprietary game assets (shipping res/GET_ASSETS.txt instead) plus license information for both the MIT tooling and the GPLv3 engine, and a Gitea Actions workflow that builds and publishes it to dl.ladkau.de on every push.
This commit is contained in:
@@ -0,0 +1,889 @@
|
||||
#include <SDL.h>
|
||||
|
||||
#include "Xmi.h"
|
||||
#include "MusicDevice.h"
|
||||
#include "Prefs.h"
|
||||
|
||||
unsigned int NumTracks;
|
||||
char ChannelThread[16]; // 16 device channels
|
||||
int NumUsedChannels; // number of in-use device channels
|
||||
|
||||
MIDI_EVENT **TrackEvents;
|
||||
short *TrackTiming;
|
||||
unsigned short *TrackUsedChannels;
|
||||
|
||||
MIDI_EVENT *ThreadEventList[NUM_THREADS];
|
||||
int ThreadTiming[NUM_THREADS];
|
||||
char ThreadChannelRemap[16 * NUM_THREADS];
|
||||
SDL_atomic_t DeviceChannelVolume[16]; // only msb: 0-127
|
||||
SDL_atomic_t ThreadPlaying[NUM_THREADS];
|
||||
SDL_atomic_t ThreadCommand[NUM_THREADS];
|
||||
|
||||
MusicDevice *MusicDev;
|
||||
static SDL_mutex *MyMutex;
|
||||
|
||||
void MusicCallback(void *userdata, Uint8 *stream, int len) {
|
||||
MusicDevice *dev;
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
dev = *(MusicDevice **)userdata;
|
||||
if (!dev || !dev->isOpen) {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_memset(stream, 0, (size_t)len); // in case we don't get anything
|
||||
dev->generate(dev, (short *)((void *)stream), len / (int)(2 * sizeof(short)));
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
void FreeXMI(void) {
|
||||
unsigned int track;
|
||||
MIDI_EVENT *event, *next;
|
||||
|
||||
for (track = 0; track < NumTracks; track++) {
|
||||
event = TrackEvents[track];
|
||||
while (event) {
|
||||
next = event->next;
|
||||
if (event->buffer)
|
||||
free(event->buffer);
|
||||
free(event);
|
||||
event = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (TrackEvents) {
|
||||
free(TrackEvents);
|
||||
TrackEvents = 0;
|
||||
}
|
||||
if (TrackTiming) {
|
||||
free(TrackTiming);
|
||||
TrackTiming = 0;
|
||||
}
|
||||
if (TrackUsedChannels) {
|
||||
free(TrackUsedChannels);
|
||||
TrackUsedChannels = 0;
|
||||
}
|
||||
|
||||
NumTracks = 0;
|
||||
}
|
||||
|
||||
MIDI_EVENT *NewMIDIEvent(MIDI_EVENT **eventlist, MIDI_EVENT *curevent, int time) {
|
||||
if (*eventlist == 0) {
|
||||
*eventlist = curevent = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
curevent->next = 0;
|
||||
|
||||
if (time < 0)
|
||||
curevent->time = 0;
|
||||
else
|
||||
curevent->time = time;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
if (time < 0) {
|
||||
MIDI_EVENT *event = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
event->next = *eventlist;
|
||||
*eventlist = curevent = event;
|
||||
|
||||
curevent->time = 0;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
if (curevent->time > time)
|
||||
curevent = *eventlist;
|
||||
|
||||
while (curevent->next) {
|
||||
if (curevent->next->time > time) {
|
||||
MIDI_EVENT *event = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
event->next = curevent->next;
|
||||
curevent->next = event;
|
||||
curevent = event;
|
||||
|
||||
curevent->time = time;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
curevent = curevent->next;
|
||||
}
|
||||
|
||||
curevent->next = (MIDI_EVENT *)malloc(sizeof(MIDI_EVENT));
|
||||
|
||||
curevent = curevent->next;
|
||||
curevent->next = 0;
|
||||
|
||||
curevent->time = time;
|
||||
curevent->buffer = 0;
|
||||
curevent->len = 0;
|
||||
|
||||
return curevent;
|
||||
}
|
||||
|
||||
int ReadXMI(const char *filename) {
|
||||
FILE *f;
|
||||
int size, start, begin, pos, time, end, tempo, tempo_set;
|
||||
unsigned int i, count, len, chunk_len, quant, status, delta, b0, b1, b2, b3;
|
||||
unsigned char *data, *p;
|
||||
short ppqn;
|
||||
unsigned short used_channels;
|
||||
MIDI_EVENT *eventlist, *curevent, *prev;
|
||||
char buf[32];
|
||||
MusicMode mode = Music_GeneralMidi;
|
||||
|
||||
INFO("Reading XMI %s", filename);
|
||||
|
||||
extern FILE *fopen_caseless(const char *path, const char *mode); // see caseless.c
|
||||
f = fopen_caseless(filename, "rb");
|
||||
if (f == 0) {
|
||||
ERROR("Could not read XMI");
|
||||
return 0;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
data = (unsigned char *)malloc(size);
|
||||
if (fread(data, size, 1, f) != 1) {
|
||||
free(data);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
p = data;
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (memcmp(buf, "FORM", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // is not an xmi
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
start = p - data;
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (!memcmp(buf, "XMID", 4))
|
||||
NumTracks = 1; // XMI doesn't have XDIR, so there's only one track
|
||||
else if (memcmp(buf, "XDIR", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // invalid XMI format
|
||||
else {
|
||||
NumTracks = 0;
|
||||
|
||||
for (i = 4; i < len; i++) {
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
chunk_len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
i += 8;
|
||||
|
||||
if (memcmp(buf, "INFO", 4)) {
|
||||
p += ((chunk_len + 1) & ~1);
|
||||
i += ((chunk_len + 1) & ~1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chunk_len < 2)
|
||||
break;
|
||||
|
||||
b0 = *p++;
|
||||
b1 = *p++;
|
||||
NumTracks = b0 | (b1 << 8);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (NumTracks == 0) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // xmi must have at least one track
|
||||
|
||||
p = data + start + ((len + 1) & ~1);
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (memcmp(buf, "CAT ", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // invalid XMI format
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
if (memcmp(buf, "XMID", 4)) {
|
||||
free(data);
|
||||
return 0;
|
||||
} // invalid XMI format
|
||||
}
|
||||
|
||||
INFO("NumTracks: %i", NumTracks);
|
||||
|
||||
TrackEvents = (MIDI_EVENT **)malloc(NumTracks * sizeof(MIDI_EVENT *));
|
||||
TrackTiming = (short *)malloc(NumTracks * sizeof(short));
|
||||
TrackUsedChannels = (unsigned short *)malloc(NumTracks * sizeof(unsigned short));
|
||||
|
||||
for (i = 0; i < NumTracks; i++)
|
||||
TrackEvents[i] = 0;
|
||||
|
||||
count = 0;
|
||||
|
||||
while (p - data < size && count != NumTracks) {
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
|
||||
if (!memcmp(buf, "FORM", 4)) {
|
||||
p += 4;
|
||||
|
||||
memcpy(buf, p, 4);
|
||||
p += 4;
|
||||
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
b0 = *p++;
|
||||
len = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
|
||||
}
|
||||
|
||||
if (memcmp(buf, "EVNT", 4)) {
|
||||
p += ((len + 1) & ~1);
|
||||
continue;
|
||||
}
|
||||
|
||||
eventlist = 0;
|
||||
curevent = 0;
|
||||
time = 0;
|
||||
end = 0;
|
||||
tempo = 500000;
|
||||
tempo_set = 0;
|
||||
status = 0;
|
||||
used_channels = 0;
|
||||
|
||||
begin = p - data;
|
||||
|
||||
while (!end && p - data < size) {
|
||||
quant = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b0 = *p++;
|
||||
if (b0 & 0x80) {
|
||||
p--;
|
||||
break;
|
||||
}
|
||||
quant += b0;
|
||||
}
|
||||
time += quant * 3;
|
||||
|
||||
status = *p++;
|
||||
switch (status >> 4) {
|
||||
case 0x9: // note on
|
||||
used_channels |= (1 << (status & 15));
|
||||
b0 = *p++;
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = b0;
|
||||
curevent->data[1] = *p++;
|
||||
delta = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b1 = *p++;
|
||||
delta <<= 7;
|
||||
delta |= b1 & 0x7F;
|
||||
if (!(b1 & 0x80))
|
||||
break;
|
||||
}
|
||||
prev = curevent;
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time + delta * 3);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = b0;
|
||||
curevent->data[1] = 0;
|
||||
curevent = prev;
|
||||
break;
|
||||
|
||||
case 0x8:
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
case 0xE: // note off, aftertouch, controller, pitch wheel
|
||||
used_channels |= (1 << (status & 15));
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = *p++;
|
||||
curevent->data[1] = *p++;
|
||||
break;
|
||||
|
||||
case 0xC:
|
||||
case 0xD: // program change, pressure
|
||||
used_channels |= (1 << (status & 15));
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
curevent->data[0] = *p++;
|
||||
break;
|
||||
|
||||
case 0xF: // sysex
|
||||
if (status == 0xFF) {
|
||||
pos = p - data;
|
||||
b0 = *p++;
|
||||
if (b0 == 0x2F)
|
||||
end = 1;
|
||||
else if (b0 == 0x51 && !tempo_set) {
|
||||
p++;
|
||||
b3 = *p++;
|
||||
b2 = *p++;
|
||||
b1 = *p++;
|
||||
tempo = (b1 | (b2 << 8) | (b3 << 16)) * 3;
|
||||
tempo_set = 1;
|
||||
} else if (b0 == 0x51 && tempo_set) {
|
||||
quant = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b1 = *p++;
|
||||
quant <<= 7;
|
||||
quant |= b1 & 0x7F;
|
||||
if (!(b1 & 0x80))
|
||||
break;
|
||||
}
|
||||
p += quant;
|
||||
break;
|
||||
}
|
||||
p = data + pos;
|
||||
}
|
||||
curevent = NewMIDIEvent(&eventlist, curevent, time);
|
||||
curevent->status = status;
|
||||
if (status == 0xFF)
|
||||
curevent->data[0] = *p++;
|
||||
quant = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b0 = *p++;
|
||||
quant <<= 7;
|
||||
quant |= b0 & 0x7F;
|
||||
if (!(b0 & 0x80))
|
||||
break;
|
||||
}
|
||||
curevent->len = quant;
|
||||
if (curevent->len) {
|
||||
curevent->buffer = (unsigned char *)malloc(curevent->len);
|
||||
memcpy(curevent->buffer, p, curevent->len);
|
||||
p += curevent->len;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ppqn = (tempo * 3) / 25000;
|
||||
if (!ppqn)
|
||||
break; // unable to convert data
|
||||
|
||||
TrackEvents[count] = eventlist;
|
||||
TrackTiming[count] = ppqn;
|
||||
TrackUsedChannels[count] = used_channels;
|
||||
count++;
|
||||
|
||||
p = data + begin + ((len + 1) & ~1);
|
||||
}
|
||||
|
||||
if (count != NumTracks) // failed to extract all tracks from XMI
|
||||
{
|
||||
free(data);
|
||||
NumTracks = count;
|
||||
FreeXMI();
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(data);
|
||||
|
||||
// Setup a sound bank for res/sound/sblaster, or res/sound/genmidi
|
||||
if (strstr(filename, "sblaster") != NULL)
|
||||
mode = Music_SoundBlaster;
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
MusicDev->setupMode(MusicDev, mode);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
switch (mode) {
|
||||
case Music_GeneralMidi:
|
||||
INFO("Set General MIDI mode");
|
||||
break;
|
||||
case Music_SoundBlaster:
|
||||
INFO("Set Sound Blaster mode");
|
||||
break;
|
||||
}
|
||||
|
||||
return 1; // success
|
||||
}
|
||||
|
||||
int MyThread(void *arg) {
|
||||
int i;
|
||||
|
||||
MIDI_EVENT *event[NUM_THREADS];
|
||||
int ppqn[NUM_THREADS];
|
||||
double Ippqn[NUM_THREADS];
|
||||
int tempo[NUM_THREADS];
|
||||
double tick[NUM_THREADS];
|
||||
double last_tick[NUM_THREADS];
|
||||
double last_time[NUM_THREADS];
|
||||
unsigned int start[NUM_THREADS];
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
event[i] = 0;
|
||||
ppqn[i] = 1;
|
||||
Ippqn[i] = 1;
|
||||
tempo[i] = 0x07A120;
|
||||
tick[i] = 1;
|
||||
last_tick[i] = 0;
|
||||
last_time[i] = 0;
|
||||
start[i] = 0;
|
||||
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 0);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_READY);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int delay = 1;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
if (event[i] && SDL_AtomicGet(&ThreadCommand[i]) != THREAD_STOPTRACK) {
|
||||
double aim = last_time[i] + (event[i]->time - last_tick[i]) * tick[i];
|
||||
double diff = aim - ((SDL_GetTicks() - start[i]) * 1000.0);
|
||||
|
||||
if (diff > 0) {
|
||||
if (diff < 1200.0)
|
||||
delay = 0;
|
||||
continue;
|
||||
}
|
||||
delay = 0;
|
||||
|
||||
last_tick[i] = event[i]->time;
|
||||
last_time[i] = aim;
|
||||
|
||||
if (event[i]->status == 0xFF && event[i]->data[0] == 0x51) // tempo change
|
||||
{
|
||||
tempo[i] = (event[i]->buffer[0] << 16) | (event[i]->buffer[1] << 8) | event[i]->buffer[2];
|
||||
tick[i] = tempo[i] * Ippqn[i];
|
||||
} else if (event[i]->status >= 0x80 && event[i]->status < 0xF0) {
|
||||
int channel = (event[i]->status & 15);
|
||||
|
||||
if (channel != 9)
|
||||
channel = ThreadChannelRemap[channel + 16 * i]; // remap channel, except 9 (percussion)
|
||||
|
||||
uint8_t p1 = event[i]->data[0];
|
||||
uint8_t p2 = event[i]->data[1];
|
||||
|
||||
if ((event[i]->status & ~15) == 0xB0 && event[i]->data[0] == 0x07) {
|
||||
// set volume msb
|
||||
// store in array in case global volume changes later
|
||||
SDL_AtomicSet(&DeviceChannelVolume[channel], p2); // 0-127
|
||||
// send volume change to device
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && MusicDev->isOpen) {
|
||||
// scale new volume according to global music volume
|
||||
extern uchar curr_vol_lev; // 0-100
|
||||
const int scaledVolume = ((int)p2 * (int)curr_vol_lev) / 100;
|
||||
MusicDev->sendControllerChange(MusicDev, channel, p1, scaledVolume);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
} else {
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && MusicDev->isOpen) {
|
||||
switch (event[i]->status & ~15) {
|
||||
case 0x80:
|
||||
MusicDev->sendNoteOff(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0x90:
|
||||
MusicDev->sendNoteOn(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0xA0:
|
||||
MusicDev->sendNoteAfterTouch(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0xB0:
|
||||
MusicDev->sendControllerChange(MusicDev, channel, p1, p2);
|
||||
break;
|
||||
case 0xC0:
|
||||
MusicDev->sendProgramChange(MusicDev, channel, p1);
|
||||
break;
|
||||
case 0xD0:
|
||||
MusicDev->sendChannelAfterTouch(MusicDev, channel, p1);
|
||||
break;
|
||||
case 0xE0:
|
||||
MusicDev->sendPitchBendML(MusicDev, channel, p2, p1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
}
|
||||
|
||||
event[i] = event[i]->next;
|
||||
if (event[i] == 0)
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_STOPTRACK);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_STOPTRACK) {
|
||||
int channel;
|
||||
|
||||
delay = 0;
|
||||
|
||||
event[i] = 0;
|
||||
last_tick[i] = 0;
|
||||
last_time[i] = 0;
|
||||
start[i] = SDL_GetTicks();
|
||||
|
||||
// here we should turn off all notes for these channels plus 9 (percussion)
|
||||
for (channel = 0; channel < 16; channel++)
|
||||
if (ChannelThread[channel] == i) {
|
||||
ChannelThread[channel] = -1;
|
||||
NumUsedChannels--;
|
||||
SDL_AtomicSet(&DeviceChannelVolume[channel], 0);
|
||||
}
|
||||
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 0);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_READY);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_PLAYTRACK) {
|
||||
delay = 0;
|
||||
|
||||
event[i] = ThreadEventList[i];
|
||||
ppqn[i] = ThreadTiming[i];
|
||||
Ippqn[i] = 1.0 / ppqn[i];
|
||||
tempo[i] = 0x07A120;
|
||||
tick[i] = tempo[i] * Ippqn[i];
|
||||
last_tick[i] = 0;
|
||||
last_time[i] = 0;
|
||||
start[i] = SDL_GetTicks();
|
||||
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 1);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_READY);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_EXIT)
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_Delay(delay);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GetTrackNumChannels(unsigned int track) {
|
||||
int num = 0, channel;
|
||||
|
||||
// count channels used by track (could be zero if only percussion channel (9) is used)
|
||||
for (channel = 0; channel < 16; channel++)
|
||||
if (channel != 9 && (TrackUsedChannels[track] & (1 << channel)))
|
||||
num++;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
void StartTrack(int thread, unsigned int track) {
|
||||
int num, trackChannel, deviceChannel;
|
||||
char channel_remap[16];
|
||||
|
||||
if (track >= NumTracks)
|
||||
return;
|
||||
|
||||
num = GetTrackNumChannels(track);
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[thread]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
|
||||
// check if enough device channels free; 16 channels available except one (percussion)
|
||||
if (NumUsedChannels + num <= 16 - 1) {
|
||||
NumUsedChannels += num;
|
||||
|
||||
memset(channel_remap, 0, 16);
|
||||
|
||||
// assign channels used by track to device channels that are currently free
|
||||
for (trackChannel = 0; trackChannel < 16; trackChannel++) {
|
||||
// only map used, non-percussion channels
|
||||
if (trackChannel != 9 && (TrackUsedChannels[track] & (1 << trackChannel))) {
|
||||
// find first unassigned device channel
|
||||
for (deviceChannel = 0; deviceChannel < 16; deviceChannel++) {
|
||||
if (deviceChannel != 9 && ChannelThread[deviceChannel] == -1)
|
||||
break;
|
||||
}
|
||||
channel_remap[trackChannel] = deviceChannel;
|
||||
ChannelThread[deviceChannel] = thread;
|
||||
// default to full volume
|
||||
SDL_AtomicSet(&DeviceChannelVolume[deviceChannel], 127);
|
||||
}
|
||||
}
|
||||
|
||||
ThreadEventList[thread] = TrackEvents[track];
|
||||
ThreadTiming[thread] = TrackTiming[track];
|
||||
memcpy(ThreadChannelRemap + 16 * thread, channel_remap, 16);
|
||||
|
||||
SDL_AtomicSet(&ThreadCommand[thread], THREAD_PLAYTRACK);
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[thread]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
void StopTrack(int i) {
|
||||
if (!SDL_AtomicGet(&ThreadPlaying[i]))
|
||||
return;
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[i]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_STOPTRACK);
|
||||
|
||||
while (SDL_AtomicGet(&ThreadCommand[i]) != THREAD_READY)
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
void StopTheMusic(void) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++)
|
||||
StopTrack(i);
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
MusicDev->reset(MusicDev);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
int IsPlaying(int i) { return SDL_AtomicGet(&ThreadPlaying[i]); }
|
||||
|
||||
void InitReadXMI(void) {
|
||||
int channel, i;
|
||||
SDL_Thread *thread;
|
||||
|
||||
InitDecXMI();
|
||||
|
||||
MyMutex = SDL_CreateMutex();
|
||||
|
||||
for (channel = 0; channel < 16; channel++) {
|
||||
ChannelThread[channel] = -1;
|
||||
SDL_AtomicSet(&DeviceChannelVolume[channel], 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
SDL_AtomicSet(&ThreadPlaying[i], 0);
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_INIT);
|
||||
}
|
||||
|
||||
thread = SDL_CreateThread(MyThread, "MyThread", NULL);
|
||||
SDL_DetachThread(thread); // thread will go away on its own upon completion
|
||||
|
||||
i = 0;
|
||||
while (SDL_AtomicGet(&ThreadCommand[i]) == THREAD_INIT)
|
||||
SDL_Delay(1);
|
||||
|
||||
atexit(ShutdownReadXMI);
|
||||
}
|
||||
|
||||
void InitDecXMI(void) {
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
WARN("InitDecXMI(): *****WARNING***** Creating new music device, but one already exists!");
|
||||
}
|
||||
|
||||
// Start the Midi device
|
||||
MusicDevice *musicdev = NULL;
|
||||
int musicrate = 48000;
|
||||
|
||||
switch (gShockPrefs.soMidiBackend) {
|
||||
case OPT_SEQ_ADLMIDI: // adlmidi
|
||||
{
|
||||
INFO("Creating ADLMIDI device");
|
||||
musicdev = CreateMusicDevice(Music_AdlMidi);
|
||||
} break;
|
||||
case OPT_SEQ_NativeMI: // native midi
|
||||
{
|
||||
INFO("Creating native MIDI device");
|
||||
musicdev = CreateMusicDevice(Music_Native);
|
||||
} break;
|
||||
#ifdef USE_FLUIDSYNTH
|
||||
case OPT_SEQ_FluidSyn: // fluidsynth
|
||||
{
|
||||
INFO("Creating FluidSynth MIDI device");
|
||||
musicdev = CreateMusicDevice(Music_FluidSynth);
|
||||
} break;
|
||||
#endif
|
||||
}
|
||||
|
||||
// init chosen music device
|
||||
INFO("Opening MIDI device using output %d", gShockPrefs.soMidiOutput);
|
||||
if (musicdev && musicdev->init(musicdev, gShockPrefs.soMidiOutput, musicrate) != 0) {
|
||||
musicdev->destroy(musicdev);
|
||||
musicdev = NULL;
|
||||
}
|
||||
|
||||
// fallback to dummy
|
||||
if (!musicdev) {
|
||||
WARN("Using dummy MIDI driver");
|
||||
musicdev = CreateMusicDevice(Music_None);
|
||||
if (musicdev) {
|
||||
musicdev->init(musicdev, gShockPrefs.soMidiOutput, musicrate);
|
||||
}
|
||||
}
|
||||
|
||||
// force prefs to align with music device output
|
||||
if (musicdev) {
|
||||
gShockPrefs.soMidiOutput = musicdev->outputIndex;
|
||||
}
|
||||
|
||||
MusicDev = musicdev;
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
void ReloadDecXMI(void) {
|
||||
int i;
|
||||
|
||||
// determine whether a device type change is being requested, by comparing the
|
||||
// current device type (if any) with current preferences setting
|
||||
short deviceTypeMatch = 0;
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
switch (MusicDev->deviceType) {
|
||||
case Music_None:
|
||||
deviceTypeMatch = 0;
|
||||
break;
|
||||
case Music_AdlMidi:
|
||||
deviceTypeMatch = (gShockPrefs.soMidiBackend == 0);
|
||||
break;
|
||||
case Music_Native:
|
||||
deviceTypeMatch = (gShockPrefs.soMidiBackend == 1);
|
||||
break;
|
||||
#ifdef USE_FLUIDSYNTH
|
||||
case Music_FluidSynth:
|
||||
deviceTypeMatch = (gShockPrefs.soMidiBackend == 2);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// only destroy the device if it exists and any of the following apply:
|
||||
// - it hasn't been opened yet
|
||||
// - device type change requested
|
||||
// - device output change requested
|
||||
// this is needed to protect against reload spam generated by the UI slider
|
||||
if (MusicDev && (!MusicDev->isOpen || !deviceTypeMatch || MusicDev->outputIndex != gShockPrefs.soMidiOutput)) {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
INFO("Closing MIDI driver due to reload");
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
StopTrack(i);
|
||||
}
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
MusicDev->destroy(MusicDev);
|
||||
MusicDev = NULL;
|
||||
}
|
||||
|
||||
// only init music device if it doesn't still exist
|
||||
if (!MusicDev) {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
InitDecXMI();
|
||||
} else {
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownReadXMI(void) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
StopTrack(i);
|
||||
// don't set THREAD_EXIT yet, as this seems to cause deadlocks
|
||||
}
|
||||
SDL_Delay(50);
|
||||
for (i = 0; i < NUM_THREADS; i++) {
|
||||
SDL_AtomicSet(&ThreadCommand[i], THREAD_EXIT);
|
||||
}
|
||||
SDL_Delay(50); // wait a bit for thread to hopefully exit
|
||||
|
||||
INFO("Closing MIDI driver due to shutdown");
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
MusicDev->destroy(MusicDev);
|
||||
MusicDev = NULL;
|
||||
} else {
|
||||
WARN("ShutdownReadXMI(): Shutdown request received, but no music device exists!");
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
|
||||
FreeXMI();
|
||||
|
||||
SDL_DestroyMutex(MyMutex);
|
||||
}
|
||||
|
||||
unsigned int GetOutputCountXMI(void) {
|
||||
unsigned int outputCount = 0;
|
||||
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev) {
|
||||
outputCount = MusicDev->getOutputCount(MusicDev);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
|
||||
return outputCount;
|
||||
}
|
||||
|
||||
void GetOutputNameXMI(const unsigned int outputIndex, char *buffer, const unsigned int bufferSize) {
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && buffer && bufferSize >= 1) {
|
||||
MusicDev->getOutputName(MusicDev, outputIndex, buffer, bufferSize);
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
|
||||
void UpdateVolumeXMI(void) {
|
||||
// global volume has been changed
|
||||
extern uchar curr_vol_lev; // 0-100
|
||||
INFO("UpdateVolumeXMI(): Global music volume change to %d percent", curr_vol_lev);
|
||||
|
||||
// tell the music driver
|
||||
SDL_LockMutex(MyMutex);
|
||||
if (MusicDev && MusicDev->isOpen) {
|
||||
// send volume change controller (#7) for all channels
|
||||
for (int i = 0; i <= 15; ++i) {
|
||||
// skip unused device channels
|
||||
if (i != 9 && ChannelThread[i] == -1)
|
||||
continue;
|
||||
// scale new volume according to global music volume
|
||||
const int scaledVolume = ((int)SDL_AtomicGet(&DeviceChannelVolume[i]) * (int)curr_vol_lev) / 100;
|
||||
MusicDev->sendControllerChange(MusicDev, i, 7, scaledVolume);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(MyMutex);
|
||||
}
|
||||
Reference in New Issue
Block a user