view win95kggui/dep/ft2play/audiodrivers/winmm/winmm.c @ 133:0d8eabdd12ab default tip

create: write H:MM:SS timestamps, add option to fill with gaussian-blur instead of black many albums are longer than one hour so writing H:MM:SS is a necessity. if anything there will just be verbose info that isn't important for my use-case. however the gaussian-blur is simply broken. It works, and it plays locally just fine, but YouTube in particular elongates the video to fit the full width. I'm not entirely sure why it does this, but it makes it useless and ugly.
author Paper <paper@tflc.us>
date Sat, 03 Jan 2026 20:25:38 -0500
parents 8e4ee43d3b81
children
line wrap: on
line source

/* winmm audio driver for ft2play
**
** Warning: This might not be 100% thread-safe or lock-safe!
*/

#define WIN32_LEAN_AND_MEAN

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <windows.h>
#include <mmsystem.h>
#include "../../pmp_mix.h"

#define MIX_BUF_NUM 4

static volatile BOOL mixerOpened, mixerBusy, mixerLocked;
static uint8_t currBuffer;
static int16_t *audioBuffer[MIX_BUF_NUM];
static int32_t bufferSize;
static HANDLE hThread, hAudioSem;
static WAVEHDR waveBlocks[MIX_BUF_NUM];
static HWAVEOUT hWave;

static DWORD WINAPI mixThread(LPVOID lpParam)
{
	(void)lpParam;
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
	while (mixerOpened)
	{
		WAVEHDR *waveBlock = &waveBlocks[currBuffer];

		if (!mixerLocked)
		{
			mixerBusy = true;
			mix_UpdateBuffer((int16_t *)waveBlock->lpData, bufferSize); // pmp_mix.c function
			mixerBusy = false;
		}

		waveOutWrite(hWave, waveBlock, sizeof (WAVEHDR));
		currBuffer = (currBuffer + 1) % MIX_BUF_NUM;
		WaitForSingleObject(hAudioSem, INFINITE); // wait for buffer fill request
	}

	return 0;
}

static void CALLBACK waveProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
	if (uMsg == WOM_DONE)
		ReleaseSemaphore(hAudioSem, 1, NULL);

	(void)hWaveOut;
	(void)uMsg;
	(void)dwInstance;
	(void)dwParam1;
	(void)dwParam2;
}

void lockMixer(void)
{
	mixerLocked = true;
	while (mixerBusy);
}

void unlockMixer(void)
{
	mixerBusy = false;
	mixerLocked = false;
}

void closeMixer(void)
{
	mixerOpened = false;
	mixerBusy = false;

	if (hAudioSem != NULL)
		ReleaseSemaphore(hAudioSem, 1, NULL);

	if (hThread != NULL)
	{
		WaitForSingleObject(hThread, INFINITE);
		CloseHandle(hThread);
		hThread = NULL;
	}

	if (hAudioSem != NULL)
	{
		CloseHandle(hAudioSem);
		hAudioSem = NULL;
	}

	if (hWave != NULL)
	{
		waveOutReset(hWave);

		for (int32_t i = 0; i < MIX_BUF_NUM; i++)
		{
			if (waveBlocks[i].dwUser != 0xFFFF)
				waveOutUnprepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR));
		}

		waveOutClose(hWave);
		hWave = NULL;
	}

	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
	{
		if (audioBuffer[i] != NULL)
		{
			free(audioBuffer[i]);
			audioBuffer[i] = NULL;
		}
	}
}

bool openMixer(int32_t mixingFrequency, int32_t mixingBufferSize)
{
	DWORD threadID;
	WAVEFORMATEX wfx;

	// don't unprepare headers on error
	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
		waveBlocks[i].dwUser = 0xFFFF;

	closeMixer();
	bufferSize = mixingBufferSize;

	ZeroMemory(&wfx, sizeof (wfx));
	wfx.nSamplesPerSec = mixingFrequency;
	wfx.wBitsPerSample = 16;
	wfx.nChannels = 2;
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
	wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;

	if (waveOutOpen(&hWave, WAVE_MAPPER, &wfx, (DWORD_PTR)&waveProc, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
		goto omError;

	// create semaphore for buffer fill requests
	hAudioSem = CreateSemaphore(NULL, MIX_BUF_NUM - 1, MIX_BUF_NUM, NULL);
	if (hAudioSem == NULL)
		goto omError;

	// allocate WinMM mix buffers
	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
	{
		audioBuffer[i] = (int16_t *)calloc(mixingBufferSize, wfx.nBlockAlign);
		if (audioBuffer[i] == NULL)
			goto omError;
	}

	// initialize WinMM mix headers
	memset(waveBlocks, 0, sizeof (waveBlocks));
	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
	{
		waveBlocks[i].lpData = (LPSTR)audioBuffer[i];
		waveBlocks[i].dwBufferLength = mixingBufferSize * wfx.nBlockAlign;
		waveBlocks[i].dwFlags = WHDR_DONE;

		if (waveOutPrepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR)) != MMSYSERR_NOERROR)
			goto omError;
	}

	currBuffer = 0;
	mixerOpened = true;

	hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)mixThread, NULL, 0, &threadID);
	if (hThread == NULL)
		goto omError;

	return TRUE;

omError:
	closeMixer();
	return FALSE;
}