/* * ADLMIDI Player is a free MIDI player based on a libADLMIDI, * a Software MIDI synthesizer library with OPL3 emulation * * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma * ADLMIDI Library API: Copyright (c) 2015-2025 Vitaly Novichkov * * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation: * http://iki.fi/bisqwit/source/adlmidi.html * * 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 3 of the License, or * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "audio.h" #include #include #include #include #include #include #define BUFFERS_COUNT 2 static HWAVEOUT g_waveOut; static WAVEHDR g_hdr[BUFFERS_COUNT]; static int g_started = 0; static void *g_audioLock = NULL; static const char *g_lastErrorMessage = ""; static UINT8 *g_buffer = NULL; static size_t g_bufferSize = 0; static HANDLE g_event; static HANDLE g_thread; static AudioOutputCallback g_audioCallback = NULL; static DWORD WINAPI s_audioThread(PVOID pDataInput) { DWORD ret = 0; UINT i; (void)pDataInput; while(g_started) { WaitForSingleObject(g_event, INFINITE); for(i = 0; i < BUFFERS_COUNT; i++) { if(g_hdr[i].dwFlags & WHDR_DONE) { g_audioCallback(NULL, (UINT8*)g_hdr[i].lpData, (int)g_hdr[i].dwBufferLength); audio_lock(); waveOutWrite(g_waveOut, &g_hdr[i], sizeof(WAVEHDR)); audio_unlock(); } } } waveOutReset(g_waveOut); return ret; } static void CALLBACK s_waveOutProc(HWAVEOUT waveout, UINT msg, DWORD_PTR userData, DWORD_PTR p1, DWORD_PTR p2) { (void)waveout; (void)userData; (void)p1; (void)p2; switch (msg) { case WOM_DONE: SetEvent(g_event); } } int audio_init(struct AudioOutputSpec *in_spec, struct AudioOutputSpec *out_obtained, AudioOutputCallback callback) { WORD bits = 16; WAVEFORMATEX wformat; MMRESULT result; g_waveOut = NULL; g_started = 0; memset(g_hdr, 0, sizeof(WAVEHDR) * BUFFERS_COUNT); g_audioCallback = callback; switch(in_spec->format) { case ADLMIDI_SampleType_S8: bits = 8; break; case ADLMIDI_SampleType_U8: bits = 8; break; case ADLMIDI_SampleType_S16: bits = 16; break; case ADLMIDI_SampleType_U16: bits = 16; break; case ADLMIDI_SampleType_S32: bits = 32; break; case ADLMIDI_SampleType_F32: bits = 32; break; } g_bufferSize = in_spec->samples * (bits / 8) * in_spec->channels; g_buffer = (UINT8 *)malloc(g_bufferSize * BUFFERS_COUNT); if(!g_buffer) { g_bufferSize = 0; g_lastErrorMessage = "Out of memory"; return -1; } out_obtained->channels = in_spec->channels; out_obtained->format = in_spec->format; out_obtained->freq = in_spec->freq; out_obtained->is_msb = in_spec->is_msb; out_obtained->samples = in_spec->samples; memset(&wformat, 0, sizeof(wformat)); wformat.cbSize = sizeof(WAVEFORMATEX); wformat.nChannels = (WORD)in_spec->channels; wformat.nSamplesPerSec = (WORD)in_spec->freq; wformat.wFormatTag = WAVE_FORMAT_PCM; wformat.wBitsPerSample = bits; wformat.nBlockAlign = wformat.nChannels * (wformat.wBitsPerSample >> 3); wformat.nAvgBytesPerSec = wformat.nSamplesPerSec * wformat.nBlockAlign; result = waveOutOpen(&g_waveOut, WAVE_MAPPER, &wformat, (DWORD_PTR)s_waveOutProc, 0, CALLBACK_FUNCTION); if(result != MMSYSERR_NOERROR) { g_lastErrorMessage = "Could not open the audio device"; return -1; } waveOutPause(g_waveOut); g_audioLock = audio_mutex_create(); return 0; } void audio_close(void) { audio_stop(); audio_mutex_lock(g_audioLock); if(g_waveOut) waveOutClose(g_waveOut); free(g_buffer); g_waveOut = NULL; g_audioCallback = NULL; audio_mutex_unlock(g_audioLock); audio_mutex_destroy(g_audioLock); } const char *audio_get_error(void) { return g_lastErrorMessage; } void audio_start(void) { DWORD dwThreadId; size_t i = 0; if(!g_audioCallback) return; if(g_started) return; audio_lock(); memset(g_buffer, 0, g_bufferSize * BUFFERS_COUNT); memset(g_hdr, 0, sizeof(WAVEHDR) * BUFFERS_COUNT); for(i = 0; i < BUFFERS_COUNT; i++) { g_hdr[i].dwBufferLength = (DWORD)g_bufferSize; g_hdr[i].lpData = (LPSTR)(g_buffer + (g_bufferSize * i)); waveOutPrepareHeader(g_waveOut, &g_hdr[i], sizeof(WAVEHDR)); g_audioCallback(NULL, (UINT8*)g_hdr[i].lpData, (int)g_hdr[i].dwBufferLength); waveOutWrite(g_waveOut, &g_hdr[i], sizeof(WAVEHDR)); } waveOutRestart(g_waveOut); audio_unlock(); g_started = 1; g_event = CreateEvent(NULL, FALSE, FALSE, NULL); g_thread = CreateThread(NULL, 0, s_audioThread, 0, 0, &dwThreadId); } void audio_stop(void) { audio_lock(); g_started = 0; if(g_thread) { SetEvent(g_event); WaitForSingleObject(g_thread, INFINITE); CloseHandle(g_event); CloseHandle(g_thread); g_event = NULL; g_thread = NULL; } audio_unlock(); } void audio_lock(void) { audio_mutex_lock(g_audioLock); } void audio_unlock(void) { audio_mutex_unlock(g_audioLock); } void audio_delay(unsigned int ms) { Sleep(ms); } void* audio_mutex_create(void) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION *)malloc(sizeof(CRITICAL_SECTION)); InitializeCriticalSection(mutex); return mutex; } void audio_mutex_destroy(void *m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION *)m; DeleteCriticalSection(mutex); free(mutex); } void audio_mutex_lock(void *m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION *)m; EnterCriticalSection(mutex); } void audio_mutex_unlock(void* m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION *)m; LeaveCriticalSection(mutex); }