Qt 4.8 和製MESS その5

Windows環境のみですがdsound.libを使って、サウンド機能を実装してみました。

qt_sound.cpp

/*
	Skelton for retropc emulator

	Qt Version : tanam
	Date   : 2013.05.18 -
*/

#include "emu.h"
#include "vm/vm.h"
#include "fileio.h"

#define DSOUND_BUFFER_SIZE (DWORD)(sound_samples * 8)
#define DSOUND_BUFFER_HALF (DWORD)(sound_samples * 4)

void EMU::initialize_sound()
{
	sound_ok = sound_started = now_mute = now_rec_snd = false;
	
	// initialize direct sound
	PCMWAVEFORMAT pcmwf;
	DSBUFFERDESC dsbd;
	WAVEFORMATEX wfex;
	
	if(FAILED(DirectSoundCreate(NULL, &lpds, NULL))) {
		return;
	}
	if(FAILED(lpds->SetCooperativeLevel(main_window_handle, DSSCL_PRIORITY))) {
		return;
	}
	
	// primary buffer
	ZeroMemory(&dsbd, sizeof(dsbd));
	dsbd.dwSize = sizeof(dsbd);
	dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
	if(FAILED(lpds->CreateSoundBuffer(&dsbd, &lpdsp, NULL))) {
		return;
	}
	ZeroMemory(&wfex, sizeof(wfex));
	wfex.wFormatTag = WAVE_FORMAT_PCM;
	wfex.nChannels = 2;
	wfex.wBitsPerSample = 16;
	wfex.nSamplesPerSec = sound_rate;
	wfex.nBlockAlign = wfex.nChannels * wfex.wBitsPerSample / 8;
	wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
	if(FAILED(lpdsp->SetFormat(&wfex))) {
		return;
	}
	
	// secondary buffer
	ZeroMemory(&pcmwf, sizeof(pcmwf));
	pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
	pcmwf.wf.nChannels = 2;
	pcmwf.wBitsPerSample = 16;
	pcmwf.wf.nSamplesPerSec = sound_rate;
	pcmwf.wf.nBlockAlign = pcmwf.wf.nChannels * pcmwf.wBitsPerSample / 8;
	pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
	ZeroMemory(&dsbd, sizeof(dsbd));
	dsbd.dwSize = sizeof(dsbd);
	dsbd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2;
	dsbd.dwBufferBytes = DSOUND_BUFFER_SIZE;
	dsbd.lpwfxFormat = (LPWAVEFORMATEX)
	if(FAILED(lpds->CreateSoundBuffer(&dsbd, &lpdsb, NULL))) {
		return;
	}
	
	sound_ok = first_half = true;
}

void EMU::release_sound()
{
	// release direct sound
	if(lpdsp) {
		lpdsp->Release();
	}
	if(lpdsb) {
		lpdsb->Release();
	}
	if(lpds) {
		lpds->Release();
	}
	lpdsp = NULL;
	lpdsb = NULL;
	lpds = NULL;
	
	// stop recording
	stop_rec_sound();
}

void EMU::update_sound(int* extra_frames)
{
	*extra_frames = 0;
	now_mute = false;
	
	if(sound_ok) {
		DWORD play_c, write_c, offset, size1, size2;
		WORD *ptr1, *ptr2;
		
		// start play
		if(!sound_started) {
			lpdsb->Play(0, 0, DSBPLAY_LOOPING);
			sound_started = true;
			return;
		}
		
		// check current position
		if(FAILED(lpdsb->GetCurrentPosition(&play_c, &write_c))) {
			return;
		}
		if(first_half) {
			if(play_c < DSOUND_BUFFER_HALF) {
				return;
			}
			offset = 0;
		}
		else {
			if(play_c > DSOUND_BUFFER_HALF) {
				return;
			}
			offset = DSOUND_BUFFER_HALF;
		}
		
		// sound buffer must be updated
		uint16* sound_buffer = vm->create_sound(extra_frames);
		if(now_rec_snd) {
			// record sound
			int length = sound_samples * sizeof(uint16) * 2; // stereo
			rec->Fwrite(sound_buffer, length, 1);
			rec_bytes += length;
		}
		if(lpdsb->Lock(offset, DSOUND_BUFFER_HALF, (void **)&ptr1, &size1, (void**)&ptr2, &size2, 0) == DSERR_BUFFERLOST) {
			lpdsb->Restore();
		}
		if(sound_buffer) {
			if(ptr1) {
				CopyMemory(ptr1, sound_buffer, size1);
			}
			if(ptr2) {
				CopyMemory(ptr2, sound_buffer + size1, size2);
			}
		}
		lpdsb->Unlock(ptr1, size1, ptr2, size2);
		first_half = !first_half;
	}
}

void EMU::mute_sound()
{
	if(!now_mute && sound_ok) {
		// check current position
		DWORD size1, size2;
		WORD *ptr1, *ptr2;
		
		if(lpdsb->Lock(0, DSOUND_BUFFER_SIZE, (void **)&ptr1, &size1, (void**)&ptr2, &size2, 0) == DSERR_BUFFERLOST) {
			lpdsb->Restore();
		}
		if(ptr1) {
			ZeroMemory(ptr1, size1);
		}
		if(ptr2) {
			ZeroMemory(ptr2, size2);
		}
		lpdsb->Unlock(ptr1, size1, ptr2, size2);
	}
	now_mute = true;
}

void EMU::start_rec_sound()
{
	if(!now_rec_snd) {
		rec = new FILEIO();
		if(rec->Fopen(bios_path(_T("sound.wav")), FILEIO_WRITE_BINARY)) {
			// write dummy wave header
			struct wavheader_t header;
			memset(&header, 0, sizeof(wavheader_t));
			rec->Fwrite(&header, sizeof(wavheader_t), 1);
			rec_bytes = 0;
			now_rec_snd = true;
		}
		else {
			// failed to open the wave file
			delete rec;
		}
	}
}

void EMU::stop_rec_sound()
{
	if(now_rec_snd) {
		// update wave header
		struct wavheader_t header;
		header.dwRIFF = 0x46464952;
		header.dwFileSize = rec_bytes + sizeof(wavheader_t) - 8;
		header.dwWAVE = 0x45564157;
		header.dwfmt_ = 0x20746d66;
		header.dwFormatSize = 16;
		header.wFormatTag = 1;
		header.wChannels = 2;
		header.wBitsPerSample = 16;
		header.dwSamplesPerSec = sound_rate;
		header.wBlockAlign = header.wChannels * header.wBitsPerSample / 8;
		header.dwAvgBytesPerSec = header.dwSamplesPerSec * header.wBlockAlign;
		header.dwdata = 0x61746164;
		header.dwDataLength = rec_bytes;
		
		rec->Fseek(0, FILEIO_SEEK_SET);
		rec->Fwrite(&header, sizeof(wavheader_t), 1);
		rec->Fclose();
		
		delete rec;
		now_rec_snd = false;
	}
}

void EMU::restart_rec_sound()
{
	if(now_rec_snd) {
		rec->Fclose();
		delete rec;
		now_rec_snd = false;
		
		start_rec_sound();
	}
}