Implemented DosBeep in doscalls and a simple audio mixer in lib2ine.

This commit is contained in:
Ryan C. Gordon committed 2018-07-10 01:33:11 -04:00
1 parent 8c5d43f997
commit 490702cc45
7 files changed
+324 -7

No files matched your search

+2
View File
@@ -96,8 +96,10 @@ target_link_libraries(viocalls ncursesw)
# !!! FIXME: clean this up/
if(LX_LEGACY)
target_link_libraries(2ine "${CMAKE_CURRENT_SOURCE_DIR}/libSDL2-2.0.so.0")
target_link_libraries(pmwin "${CMAKE_CURRENT_SOURCE_DIR}/libSDL2-2.0.so.0")
else()
target_link_libraries(2ine "SDL2")
target_link_libraries(pmwin "SDL2")
endif()
+156 -7
View File
@@ -23,8 +23,10 @@
#include <pthread.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <assert.h>
#include "lib2ine.h"
#include "SDL.h"
extern char **environ;
@@ -640,6 +642,157 @@ static void prepOs2Drives(void)
#endif
}
typedef struct LxAudioGeneratorInfo
{
LxAudioGeneratorFn fn;
void *data;
struct LxAudioGeneratorInfo *next;
} LxAudioGeneratorInfo;
static LxAudioGeneratorInfo *audio_generators = NULL;
static SDL_AudioDeviceID sdl_audio_device = 0;
static SDL_AudioSpec sdl_audio_spec;
static Uint32 no_audio_generators_timeout = 0;
static int SDLCALL closeAudioDeviceThread(void *arg)
{
const SDL_AudioDeviceID dev = (SDL_AudioDeviceID) (size_t) arg;
//printf("2ine: Closing idle audio device %u.\n", (uint) dev);
SDL_CloseAudioDevice(dev);
return 0;
}
static void closeAudioDeviceFromAnotherThread(void)
{
FIXME("Make this atomic");
const SDL_AudioDeviceID dev = sdl_audio_device;
if (!sdl_audio_device) {
return;
}
sdl_audio_device = 0;
SDL_Thread *thread = SDL_CreateThread(closeAudioDeviceThread, "2ine_close_audio_device", (void *) (size_t) dev);
if (thread) {
SDL_DetachThread(thread);
} else {
sdl_audio_device = dev; // oh well.
}
}
static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len)
{
memset(stream, '\0', len);
LxAudioGeneratorInfo *generator = audio_generators;
if (!generator) {
if (!no_audio_generators_timeout) {
no_audio_generators_timeout = SDL_GetTicks() + 5000;
} else if (SDL_TICKS_PASSED(SDL_GetTicks(), no_audio_generators_timeout)) {
closeAudioDeviceFromAnotherThread();
}
return;
}
no_audio_generators_timeout = 0;
const int samples = len / sizeof (float);
LxAudioGeneratorInfo *prev = NULL;
while (generator) {
LxAudioGeneratorInfo *next = generator->next;
if (generator->fn(generator->data, (float *) stream, samples, sdl_audio_spec.freq)) {
prev = generator;
} else {
// remove this generator, it's done.
if (prev) {
prev->next = next;
} else {
assert(generator == audio_generators);
audio_generators = next;
}
free(generator);
}
generator = next;
}
}
static int registerAudioGenerator_lib2ine(LxAudioGeneratorFn fn, void *data, const int singleton)
{
if (!sdl_audio_device) {
SDL_Init(SDL_INIT_AUDIO);
SDL_AudioSpec spec;
SDL_zero(spec);
spec.freq = 48000;
spec.format = AUDIO_F32SYS;
spec.channels = 1;
spec.samples = 1024;
spec.callback = audioCallback;
sdl_audio_device = SDL_OpenAudioDevice(NULL, 0, &spec, &sdl_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (!sdl_audio_device) {
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return 0;
}
no_audio_generators_timeout = 0;
SDL_PauseAudioDevice(sdl_audio_device, 0);
}
LxAudioGeneratorInfo *info = (LxAudioGeneratorInfo *) malloc(sizeof (LxAudioGeneratorInfo *));
if (!info) {
return 0;
}
info->fn = fn;
info->data = data;
SDL_LockAudioDevice(sdl_audio_device);
if (singleton) {
for (LxAudioGeneratorInfo *i = audio_generators; i != NULL; i = i->next) {
if (i->fn == fn) {
SDL_UnlockAudioDevice(sdl_audio_device);
free(info);
return 2; // already added.
}
}
}
no_audio_generators_timeout = 0;
info->next = audio_generators;
audio_generators = info;
SDL_UnlockAudioDevice(sdl_audio_device);
return 1;
} // registerAudioGenerator_lib2ine
static void lib2ine_shutdown(void)
{
if (sdl_audio_device) {
// let the audio callback run about two more times, to let any last queued things fully render.
SDL_Delay(((sdl_audio_spec.samples * 1000) / sdl_audio_spec.freq) * 2);
SDL_CloseAudioDevice(sdl_audio_device);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
sdl_audio_device = 0;
}
LxAudioGeneratorInfo *generator = audio_generators;
audio_generators = NULL;
while (generator) {
LxAudioGeneratorInfo *next = generator->next;
free(generator);
generator = next;
}
for (int i = 0; i < (sizeof (GLoaderState.disks) / sizeof (GLoaderState.disks[0])); i++) {
free(GLoaderState.disks[i]);
GLoaderState.disks[i] = NULL;
}
free(GLoaderState.pib.pib_pchenv);
GLoaderState.pib.pib_pchenv = NULL;
memset(&GLoaderState, '\0', sizeof (GLoaderState));
pthread_key_delete(tlskey);
}
LX_NATIVE_CONSTRUCTOR(lib2ine)
{
if (pthread_key_create(&tlskey, NULL) != 0) {
@@ -662,6 +815,8 @@ LX_NATIVE_CONSTRUCTOR(lib2ine)
GLoaderState.loadModule = loadModule_lib2ine;
GLoaderState.makeUnixPath = makeUnixPath_lib2ine;
GLoaderState.terminate = terminate_lib2ine;
GLoaderState.registerAudioGenerator = registerAudioGenerator_lib2ine;
GLoaderState.lib2ine_shutdown = lib2ine_shutdown;
cfgLoadFiles();
prepOs2Drives();
@@ -689,13 +844,7 @@ LX_NATIVE_CONSTRUCTOR(lib2ine)
LX_NATIVE_DESTRUCTOR(lib2ine)
{
for (int i = 0; i < (sizeof (GLoaderState.disks) / sizeof (GLoaderState.disks[0])); i++) {
free(GLoaderState.disks[i]);
GLoaderState.disks[i] = NULL;
}
free(GLoaderState.pib.pib_pchenv);
memset(&GLoaderState, '\0', sizeof (GLoaderState));
pthread_key_delete(tlskey);
lib2ine_shutdown();
}
// end of lib2ine.c ...
+9
View File
@@ -273,6 +273,13 @@ typedef struct LxPostTIB
void *anchor_block;
} LxPostTIB;
// There are a few things that need access to the sound hardware:
// DosBeep(), MMOS/2, DART, basic system sounds, etc. We unify all this
// into a single SDL audio device open where possible, so that we don't
// have to deal with multiple devices, but that means it has to move into
// lib2ine so multiple libraries can access it.
typedef int (*LxAudioGeneratorFn)(void *data, float *stream, int len, int freq);
#define LXTIBSIZE (sizeof (LxTIB) + sizeof (LxTIB2) + sizeof (LxPostTIB))
#define LX_MAX_LDT_SLOTS 8192
@@ -318,6 +325,8 @@ typedef struct LxLoaderState
LxModule *(*loadModule)(const char *modname);
char *(*makeUnixPath)(const char *os2path, uint32 *err);
void __attribute__((noreturn)) (*terminate)(const uint32 exitcode);
int (*registerAudioGenerator)(LxAudioGeneratorFn fn, void *data, const int singleton);
void (*lib2ine_shutdown)(void);
} LxLoaderState;
typedef const LxExport *(*LxNativeModuleInitEntryPoint)(uint32 *lx_num_exports);
+2
View File
@@ -667,6 +667,8 @@ static void freeLxModule(LxModule *lxmod);
static __attribute__((noreturn)) void lxTerminate(const uint32 exitcode)
{
GLoaderState.lib2ine_shutdown();
// free the actual .exe
freeLxModule(GLoaderState.main_module);
+10
View File
@@ -72,6 +72,12 @@ static APIRET16 bridge16to32_Dos16GetMachineMode(uint8 *args) {
return Dos16GetMachineMode(pmode);
}
static APIRET16 bridge16to32_Dos16Beep(uint8 *args) {
LX_NATIVE_MODULE_16BIT_BRIDGE_ARG(USHORT, dur);
LX_NATIVE_MODULE_16BIT_BRIDGE_ARG(USHORT, freq);
return Dos16Beep(freq, dur);
}
static APIRET16 bridge16to32_Dos16ChDir(uint8 *args) {
LX_NATIVE_MODULE_16BIT_BRIDGE_ARG(ULONG, res);
LX_NATIVE_MODULE_16BIT_BRIDGE_PTRARG(PSZ, pszDir);
@@ -264,6 +270,7 @@ LX_NATIVE_MODULE_16BIT_SUPPORT()
LX_NATIVE_MODULE_16BIT_API(Dos16FreeSeg)
LX_NATIVE_MODULE_16BIT_API(Dos16GetHugeShift)
LX_NATIVE_MODULE_16BIT_API(Dos16GetMachineMode)
LX_NATIVE_MODULE_16BIT_API(Dos16Beep)
LX_NATIVE_MODULE_16BIT_API(Dos16ChDir)
LX_NATIVE_MODULE_16BIT_API(Dos16ChgFilePtr)
LX_NATIVE_MODULE_16BIT_API(Dos16Close)
@@ -308,6 +315,7 @@ static int init16_doscalls(void) {
LX_NATIVE_INIT_16BIT_BRIDGE(Dos16FreeSeg, 2)
LX_NATIVE_INIT_16BIT_BRIDGE(Dos16GetHugeShift, 4)
LX_NATIVE_INIT_16BIT_BRIDGE(Dos16GetMachineMode, 4)
LX_NATIVE_INIT_16BIT_BRIDGE(Dos16Beep, 4)
LX_NATIVE_INIT_16BIT_BRIDGE(Dos16ChDir, 8)
LX_NATIVE_INIT_16BIT_BRIDGE(Dos16ChgFilePtr, 12)
LX_NATIVE_INIT_16BIT_BRIDGE(Dos16Close, 2)
@@ -349,6 +357,7 @@ LX_NATIVE_MODULE_INIT({ if (!init16_doscalls()) return 0; })
LX_NATIVE_EXPORT16(Dos16FreeSeg, 39),
LX_NATIVE_EXPORT16(Dos16GetHugeShift, 41),
LX_NATIVE_EXPORT16(Dos16GetMachineMode, 49),
LX_NATIVE_EXPORT16(Dos16Beep, 50),
LX_NATIVE_EXPORT16(Dos16ChDir, 57),
LX_NATIVE_EXPORT16(Dos16ChgFilePtr, 58),
LX_NATIVE_EXPORT16(Dos16Close, 59),
@@ -404,6 +413,7 @@ LX_NATIVE_MODULE_INIT({ if (!init16_doscalls()) return 0; })
LX_NATIVE_EXPORT(DosRead, 281),
LX_NATIVE_EXPORT(DosWrite, 282),
LX_NATIVE_EXPORT(DosExecPgm, 283),
LX_NATIVE_EXPORT(DosBeep, 286),
LX_NATIVE_EXPORT(DosSetProcessCp, 289),
LX_NATIVE_EXPORT(DosQueryCp, 291),
LX_NATIVE_EXPORT(DosExitList, 296),
+143
View File
@@ -25,6 +25,7 @@
#include "doscalls-lx.h"
static pthread_mutex_t GMutexDosCalls;
static pthread_mutex_t GMutexDosBeep;
typedef struct ExitListItem
{
@@ -3133,6 +3134,132 @@ APIRET DosDupHandle(HFILE hFile, PHFILE pHfile)
return DosDupHandle_implementation(hFile, pHfile);
} // DosDupHandle
typedef struct DosBeepGeneratorInfo
{
ULONG samples_remaining;
ULONG positive;
ULONG square_remaining;
ULONG square_length;
ULONG square_remainder;
ULONG accumulator;
ULONG next_freq;
ULONG next_duration;
} DosBeepGeneratorInfo;
static DosBeepGeneratorInfo dos_beep_info;
static int DosBeepGenerator(void *userdata, float *stream, int samples, int freq)
{
DosBeepGeneratorInfo *info = &dos_beep_info;
assert(userdata == &dos_beep_info);
while (samples > 0) {
if (!info->samples_remaining) { // current beep command is finished?
if (!info->next_freq) {
return 0; // out of things to play, remove ourselves from the mixer.
}
// set up next beep command.
info->square_length = ((ULONG) freq) / info->next_freq;
info->square_remainder = ((ULONG) freq) % info->next_freq;
info->samples_remaining = ((ULONG) ((info->next_duration / 1000.0) * ((ULONG) freq))) * 1;
info->accumulator = 0;
info->next_freq = 0;
info->next_duration = 0;
} else if (!info->square_remaining) { // this part of the square wave is finished, flip it and start the next part.
info->positive = !info->positive;
info->square_remaining = info->square_length;
info->accumulator += info->square_remainder;
if (info->accumulator >= info->square_length) {
info->accumulator -= info->square_length;
info->square_remaining++;
}
} else { // still generating the current piece of the square wave, mix it into the stream.
// VMware emulates the PC Speaker on a sound card _really_ quietly.
const float val = info->positive ? 0.05f : -0.05f;
int total = info->square_remaining;
if (info->samples_remaining < total) total = info->samples_remaining;
if (samples < total) total = samples;
FIXME("good candidate for SSE");
for (int i = 0; i < total; i++) {
stream[i] += val;
}
samples -= total;
info->samples_remaining -= total;
info->square_remaining -= total;
stream += total;
}
}
return 1; // remain in the mixer to keep generating audio.
} // DosBeepGenerator
static APIRET DosBeep_implementation(ULONG freq, ULONG duration)
{
if ((freq < 0x25) || (freq > 0x7FFF)) {
return ERROR_INVALID_FREQUENCY;
} else if (duration == 0) {
return NO_ERROR;
}
// Either OS/2 or Vmware seems to be doubling the frequency requested, I
// think, as this sounds closer to what DosBeep() generates there.
//FIXME("sanity check this");
//freq *= 2;
// assuming OS/2 ran the PC Speaker off the BIOS timer, firing 18.2 times
// a second (about 55 milliseconds), we'll round up the requested
// duration to match.
FIXME("sanity check this");
const ULONG duration_diff = duration % 55;
const ULONG mix_duration = duration + (duration_diff ? (55 - duration_diff) : 0);
// we chain beep commands together and return a little before they'll
// end, so the caller can queue another without a gap in playback if
// they're making multiple calls to DosBeep to make a cute little tune.
// OS/2's implementation appears to hold a mutex while driving the PC
// speaker; if you DosBeep() from two threads at once, one will block
// until the other finishes playing.
grabLock(&GMutexDosBeep);
FIXME("should be atomic");
while (*((volatile ULONG *) &dos_beep_info.next_freq) != 0) {
usleep(1000);
}
dos_beep_info.next_duration = mix_duration;
dos_beep_info.next_freq = freq;
// wait about the time this was requested to play, but since there is
// variance on when the audio thread will run again, and we padded out
// the play time to 55 milliseconds, chances are good that the next
// DosBeep call will queue more sound to play seamlessly.
// If less than 50 milliseconds, don't sleep at all; if they call DosBeep
// again, it'll block until it can queue the command anyhow.
if (duration > 55) {
usleep((duration-55) * 1000);
}
const int rc = GLoaderState.registerAudioGenerator(DosBeepGenerator, &dos_beep_info, 1);
if (!rc) {
memset(&dos_beep_info, '\0', sizeof (dos_beep_info)); // oh well.
}
ungrabLock(&GMutexDosBeep);
return NO_ERROR;
} // DosBeep_implementation
APIRET DosBeep(ULONG freq, ULONG duration)
{
TRACE_NATIVE("DosBeep(%u, %u)", freq, duration);
return DosBeep_implementation(freq, duration);
} // DosBeep
APIRET16 Dos16GetVersion(PUSHORT pver)
{
TRACE_NATIVE("Dos16GetVersion(%p)", pver);
@@ -3486,6 +3613,12 @@ APIRET16 Dos16ChgFilePtr(USHORT handle, LONG distance, USHORT whence, PULONG new
return (APIRET16) DosSetFilePtr_implementation(handle, distance, whence, newoffset);
} // Dos16ChgFilePtr
APIRET16 Dos16Beep(USHORT freq, USHORT duration)
{
TRACE_NATIVE("Dos16Beep(%u, %u)", freq, duration);
return DosBeep_implementation(freq, duration);
} // Dos16Beep
LX_NATIVE_CONSTRUCTOR(doscalls)
{
@@ -3496,10 +3629,17 @@ LX_NATIVE_CONSTRUCTOR(doscalls)
abort();
} // if
if (pthread_mutex_init(&GMutexDosBeep, NULL) == -1) {
pthread_mutex_destroy(&GMutexDosCalls);
fprintf(stderr, "pthread_mutex_init failed!\n");
abort();
} // if
MaxHFiles = 20; // seems to be OS/2's default.
HFiles = (HFileInfo *) malloc(sizeof (HFileInfo) * MaxHFiles);
if (!HFiles) {
pthread_mutex_destroy(&GMutexDosCalls);
pthread_mutex_destroy(&GMutexDosBeep);
fprintf(stderr, "Out of memory!\n");
abort();
} // if
@@ -3532,6 +3672,8 @@ LX_NATIVE_CONSTRUCTOR(doscalls)
ginfo = (GINFOSEG *) GLoaderState.allocSegment(&ginfosel, 0);
linfo = (LINFOSEG *) GLoaderState.allocSegment(&linfosel, 0);
if ((!ginfo) || (!linfo)) {
pthread_mutex_destroy(&GMutexDosCalls);
pthread_mutex_destroy(&GMutexDosBeep);
fprintf(stderr, "Failed to allocate info segments!\n");
abort();
}
@@ -3589,6 +3731,7 @@ LX_NATIVE_DESTRUCTOR(doscalls)
GLoaderState.freeSelector(ginfosel);
GLoaderState.freeSelector(linfosel);
pthread_mutex_destroy(&GMutexDosCalls);
pthread_mutex_destroy(&GMutexDosBeep);
}
// end of doscalls.c ...
+2
View File
@@ -544,6 +544,7 @@ OS2EXPORT APIRET16 OS2API16 Dos16Read(USHORT hFile, PVOID pBuffer, USHORT cbRead
OS2EXPORT APIRET16 OS2API16 Dos16ExecPgm(PCHAR pObjname, SHORT cbObjname, USHORT execFlag, PSZ pArg, PSZ pEnv, PRESULTCODES16 pRes, PSZ pName) OS2APIINFO(144);
OS2EXPORT APIRET16 OS2API16 Dos16CWait(USHORT action, USHORT option, PRESULTCODES16 pres, PUSHORT ppid, USHORT pid) OS2APIINFO(2);
OS2EXPORT APIRET16 OS2API16 Dos16ChgFilePtr(USHORT handle, LONG distance, USHORT whence, PULONG newoffset) OS2APIINFO(58);
OS2EXPORT APIRET16 OS2API16 Dos16Beep(USHORT freq, USHORT dur) OS2APIINFO(50);
// !!! FIXME: these should probably get sorted alphabetically and/or grouped
// !!! FIXME: into areas of functionality, but for now, I'm just listing them
@@ -619,6 +620,7 @@ OS2EXPORT APIRET OS2API DosSetProcessCp(ULONG cp) OS2APIINFO(289);
OS2EXPORT APIRET OS2API DosQueryFSAttach(PSZ pszDeviceName, ULONG ulOrdinal, ULONG ulFSAInfoLevel, PFSQBUFFER2 pfsqb, PULONG pcbBuffLength) OS2APIINFO(277);
OS2EXPORT APIRET OS2API DosSetFileSize(HFILE hFile, ULONG cbSize) OS2APIINFO(272);
OS2EXPORT APIRET OS2API DosSetCurrentDir(PSZ pszName) OS2APIINFO(255);
OS2EXPORT APIRET OS2API DosBeep(ULONG freq, ULONG dur) OS2APIINFO(286);
#ifdef __cplusplus
}