開発環境 Microsoft Visual C++ 2010 Express (SP1)
実行環境 Microsoft Windows XP Home Edition (SP3)
プロジェクトの種類 Win32 プロジェクト
プロジェクト名 WaveGraph
アプリケーションの種類 Windows アプリケーション
追加のオプション 空のプロジェクト
文字セット Unicode

Waveファイル(PCM 11.025kHz 8bit mono)を想定。

WaveGraph.cpp
// WaveGraph4 Waveファイル音階表示
 
#pragma comment(lib, "winmm")
 
#define _USE_MATH_DEFINES
 
#include <Windows.h>
#include <tchar.h>
#include <math.h>
#include <vector>
 
using namespace std;
 
typedef struct {
	int pos;	// 位置
	int len;	// 波長
	int noteNum;	// 音の高さ
	double amp;	// 振幅(=波高/2)
} Wave;
typedef vector<Wave> VWave;
 
#define SAFE_FREE(p)	if (p) { free(p); p = NULL; }
#define NORM(b)		((b - 128.0) / 128)
#define log2(x)		(log(x) / log(2.0))
#define A4		69
#define APP_NAME	TEXT("WaveGraph")
 
// 関数プロトタイプ宣言
void Trace(LPCTSTR format, ...);
BOOL Load(LPTSTR pszFileName);
BOOL ReadWaveFile(LPTSTR pszFileName);
BOOL AnalyzeWave(void);
double Amplitude(int pos, int wl);
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnCreate(HWND hWnd);
void OnSize(HWND hWnd, WPARAM wParam, LPARAM lParam);
void OnHScroll(HWND hWnd, WPARAM wParam);
void OnPaint(HWND hWnd);
void OnChar(HWND hWnd, WPARAM wParam, LPARAM lParam);
void Play(HWND hWnd);
void OnWomOpen(void);
void OnWomDone(HWND hWnd);
 
// 音階
LPCTSTR scale[] = {L"C",L"C#",L"D",L"D#",L"E",L"F",L"F#",L"G",L"G#",L"A",L"A#",L"B"};
 
// 外部変数
SCROLLINFO siHorz;
DWORD sampleLength = 0;
PBYTE waveformData = NULL;
WAVEFORMATEX wfx;
VWave vWave;
int noteLen = 0;
int *noteNum = NULL;
DWORD sampleLength2 = 0;
PBYTE waveformData2 = NULL;
HWAVEOUT hwo;
WAVEHDR wh;
HCURSOR hCursor = NULL;
 
//==============================================================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
	// プログラム引数
	LPTSTR lpCmdLine = GetCommandLine();
	int argc;
	LPTSTR *argv = CommandLineToArgvW(lpCmdLine, &argc);
	if (2 <= argc) {
		Load(argv[1]);
	}
 
	// ウィンドウクラスの登録
	WNDCLASSEX wcx;
	ZeroMemory(&wcx, sizeof wcx);
	wcx.cbSize		= sizeof wcx;
	wcx.style		= CS_HREDRAW | CS_VREDRAW;
	wcx.lpfnWndProc		= WndProc;
	wcx.hInstance		= hInstance;
	wcx.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcx.hbrBackground	= (HBRUSH)(COLOR_WINDOW + 1);
	wcx.lpszClassName	= APP_NAME;
	if (RegisterClassEx(&wcx) == 0) {
		return 0;
	}
 
	// ウィンドウの作成
	HWND hWnd = CreateWindow(
		APP_NAME, APP_NAME,
		WS_OVERLAPPEDWINDOW | WS_HSCROLL,
		CW_USEDEFAULT, 0,
		CW_USEDEFAULT, 0,
		NULL, NULL, hInstance, NULL);
	if (hWnd == NULL) {
		return 0;
	}
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
 
	// メッセージループ
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}
 
void Trace(LPCTSTR format, ...)
{
	va_list arg_ptr;
	TCHAR buffer[256];
	int size;
 
	va_start(arg_ptr, format);
	size = _vsntprintf_s(buffer, _countof(buffer), _TRUNCATE, format, arg_ptr);
	va_end(arg_ptr);
	OutputDebugString(buffer);
	if (size < 0) {
		OutputDebugString(_T("...\n"));
	}
}
 
BOOL Load(LPTSTR pszFileName)
{
	if (ReadWaveFile(pszFileName) == FALSE) {
		return FALSE;
	}
	AnalyzeWave();
	return TRUE;
}
 
BOOL ReadWaveFile(LPTSTR pszFileName)
{
	MMRESULT mmr;
 
	sampleLength = 0;
	SAFE_FREE(waveformData)
 
	// Open
	HMMIO hmmio = mmioOpen(pszFileName, NULL, MMIO_READ);
	if (hmmio == NULL) {
		return FALSE;
	}
 
	// RIFFチャンク
	MMCKINFO ckParent;
	ckParent.fccType = mmioFOURCC('W','A','V','E');
	mmr = mmioDescend(hmmio, &ckParent, NULL, MMIO_FINDRIFF);
	if (mmr != MMSYSERR_NOERROR) {
		return FALSE;
	}
 
	// fmtチャンク
	MMCKINFO ckSub;
	ckSub.ckid = mmioFOURCC('f','m','t',' ');
	mmr = mmioDescend(hmmio, &ckSub, &ckParent, MMIO_FINDCHUNK);
	if (mmr != MMSYSERR_NOERROR) {
		return FALSE;
	}
 
	LONG read = mmioRead(hmmio, (HPSTR)&wfx, 16);
	if (read != 16) {
		return FALSE;
	}
 
	mmioAscend(hmmio, &ckSub, 0);
 
	// dataチャンク
	ckSub.ckid = mmioFOURCC('d','a','t','a');
	mmr = mmioDescend(hmmio, &ckSub, &ckParent, MMIO_FINDCHUNK);
	if (mmr != MMSYSERR_NOERROR) {
		return FALSE;
	}
 
	sampleLength = ckSub.cksize;
	waveformData = (PBYTE)malloc(ckSub.cksize);
	if (waveformData == NULL) {
		return FALSE;
	}
 
	read = mmioRead(hmmio, (HPSTR)waveformData, ckSub.cksize);
	if (read != ckSub.cksize) {
		return FALSE;
	}
 
	mmioAscend(hmmio, &ckSub, 0);
 
	// RIFFチャンク
	mmioAscend(hmmio, &ckParent, 0);
 
	// Close
	mmioClose(hmmio, 0);
 
	return TRUE;
}
 
BOOL AnalyzeWave(void)
{
	// 自己相関による波長解析
	for (int pos = 0; pos <= (int)sampleLength - 200 * 2; ) {	// 11,025Hz / 55Hz = 200.45
		double r = -1;
		int waveLen;
		for (int wl = 12; wl <= 200; wl++) {
			double sum = 0;
			for (int i = 0; i < wl; i++) {
				sum += NORM(waveformData[pos + i]) * NORM(waveformData[pos + wl + i]);
			}
			sum /= wl;
			sum *= pow(1.1, log2(200.0 / wl));	// 倍音抑制バイアス
			if (r < sum) {
				r = sum;
				waveLen = wl;
			}
		}
		double freq = (double)wfx.nSamplesPerSec / waveLen;
		double d = A4 + 12 * log2(freq / 440);
		Wave wave;
		wave.pos	= pos;
		wave.len	= waveLen;
		wave.noteNum	= int(d + 0.5);
		wave.amp	= Amplitude(pos, waveLen);
		vWave.push_back(wave);
 
		pos += waveLen;
	}
 
	// 一定以上の振幅の音の高さ
	int sl = 0;
	for (VWave::iterator it = vWave.begin(); it != vWave.end(); it++) {
		sl += it->len;
	}
	PBYTE note = (PBYTE)malloc(sl);
	int pos = 0;
	for (VWave::iterator it = vWave.begin(); it != vWave.end(); it++) {
		for (int i = 0; i < it->len; i++) {
			note[pos + i] = BYTE(0.05 <= it->amp ? it->noteNum : 0);
		}
		pos += it->len;
	}
 
	// 一定の長さ毎の一番多い音の高さ
	noteLen = sl / 50;
	noteNum = (int *)malloc(sizeof(int) * noteLen);
	for (pos = 0; pos < noteLen; pos++) {
		int hist[128] = {0};
		for (int i = 0; i < 50; i++) {
			hist[note[pos * 50 + i]]++;
		}
		if (hist[0] < 10) {
			int noteMax;
			int histMax = 0;
			for (int n = 1; n < 128; n++) {
				if (histMax < hist[n]) {
					noteMax = n;
					histMax = hist[n];
				}
			}
			noteNum[pos] = noteMax;
			Trace(_T("%d #%d %s%d\n"), pos, noteMax, scale[noteMax % 12], noteMax / 12 - 1);
		} else {
			noteNum[pos] = 0;
		}
	}
	SAFE_FREE(note);
 
	// 波形データに変換
	sampleLength2 = noteLen * 50;
	waveformData2 = (PBYTE)malloc(sampleLength2);
	double t = 0;
	pos = 0;
	for (int n = 0; n < noteLen; n++) {
		double delta;
		if (noteNum[n] == 0) {
			delta = 0;
			t = 0;
		} else {
			double freq = 440 * pow(pow(2.0, noteNum[n] - A4), 1.0 / 12);
			delta = freq / wfx.nSamplesPerSec;
		}
		for (int i = 0; i < 50; i++) {
			t = fmod(t + delta, 1.0);
//			double y = sin(2 * M_PI * t);
			double y = delta ? (t < 0.5 ? 1 : -1) : 0;
			waveformData2[pos++] = BYTE(128 + 64 * y);
		}
	}
	return TRUE;
}
 
// 振幅
double Amplitude(int pos, int wl)
{
	double sum = 0;
	for (int i = 0; i < wl; i++) {
		sum += abs(NORM(waveformData[pos + i]));
	}
	return sum / wl;
}
 
//------------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {
	case MM_WOM_OPEN:
		OnWomOpen();
		return 0;
	case MM_WOM_DONE:
		OnWomDone(hWnd);
		return 0;
	case WM_PAINT:
		OnPaint(hWnd);
		return 0;
	case WM_HSCROLL:
		OnHScroll(hWnd, wParam);
		return 0;
	case WM_SIZE:
		OnSize(hWnd, wParam, lParam);
		return 0;
	case WM_CHAR:
		OnChar(hWnd, wParam, lParam);
		return 0;
	case WM_CREATE:
		OnCreate(hWnd);
		return 0;
	case WM_DESTROY:
		SAFE_FREE(waveformData);
		SAFE_FREE(waveformData2);
		SAFE_FREE(noteNum);
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
 
void OnCreate(HWND hWnd)
{
	RECT rc;
	GetClientRect(hWnd, &rc);
 
	siHorz.cbSize	= sizeof siHorz;
	siHorz.fMask	= SIF_RANGE | SIF_PAGE | SIF_POS | SIF_DISABLENOSCROLL;
	siHorz.nMin	= 0;
	siHorz.nMax	= noteLen - 1;
	siHorz.nPage	= rc.right;
	siHorz.nPos	= 0;
	SetScrollInfo(hWnd, SB_HORZ, &siHorz, FALSE);
}
 
void OnSize(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	if (wParam == SIZE_MINIMIZED) return;
 
	siHorz.nPage	= LOWORD(lParam);
	int nPosMax	= max(siHorz.nMax - (int)siHorz.nPage + 1, 0);
	siHorz.nPos	= min(siHorz.nPos, nPosMax);
	SetScrollInfo(hWnd, SB_HORZ, &siHorz, TRUE);
}
 
void OnHScroll(HWND hWnd, WPARAM wParam)
{
	int nPos = siHorz.nPos;
 
	switch (LOWORD(wParam)) {
	case SB_LINEUP:
		nPos -= 10;
		break;
	case SB_LINEDOWN:
		nPos += 10;
		break;
	case SB_PAGEUP:
		nPos -= siHorz.nPage;
		break;
	case SB_PAGEDOWN:
		nPos += siHorz.nPage;
		break;
	case SB_THUMBTRACK:
		SCROLLINFO si;
		si.cbSize	= sizeof si;
		si.fMask	= SIF_TRACKPOS;
		if (GetScrollInfo(hWnd, SB_HORZ, &si) != 0) {
			nPos = si.nTrackPos;
		}
		break;
	}
 
	int nPosMax = max(siHorz.nMax - (int)siHorz.nPage + 1, 0);
	nPos = min(nPos, nPosMax);
	nPos = max(nPos, 0);
	if (nPos == siHorz.nPos) return;
 
	ScrollWindowEx(hWnd, siHorz.nPos - nPos, 0,
		NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_ERASE);
	siHorz.nPos = nPos;
	SetScrollInfo(hWnd, SB_HORZ, &siHorz, TRUE);
	UpdateWindow(hWnd);
}
 
void OnPaint(HWND hWnd)
{
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint(hWnd, &ps);
 
	RECT rc;
	GetClientRect(hWnd, &rc);
 
	HPEN pen = CreatePen(PS_SOLID, 0, RGB(0xc0,0xc0,0xc0));
	HGDIOBJ penOld = SelectObject(hdc, pen);
	for (int n = 0; n < 128; n += 12) {
		int y = rc.bottom * (127 - n) / 128;
		if (y < ps.rcPaint.top) continue;
		if (ps.rcPaint.bottom < y) break;
		MoveToEx(hdc, 0, y, NULL);
		LineTo(hdc, rc.right, y);
	}
	SelectObject(hdc, penOld);
	DeleteObject(pen);
 
	// noteNum
	pen = CreatePen(PS_SOLID, 0, RGB(255,0,0));
	penOld = SelectObject(hdc, pen);
	for (int x = ps.rcPaint.left; x < ps.rcPaint.right; x++) {
		int i = siHorz.nPos + x;
		if (noteLen <= i) break;
		int y = rc.bottom * (127 - noteNum[i]) / 128;
		MoveToEx(hdc, x, y, NULL);
		LineTo(hdc, x, y + 1);
	}
	SelectObject(hdc, penOld);
	DeleteObject(pen);
 
	EndPaint(hWnd, &ps);
}
 
void OnChar(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	Trace(_T("OnChar %x %x\n"), wParam, lParam);
 
	switch (wParam) {
	case VK_ESCAPE:
		DestroyWindow(hWnd);
		break;
	case 'p':
		Play(hWnd);
		break;
	}
}
 
void Play(HWND hWnd)
{
	if (hCursor) return;
 
	WAVEFORMATEX wf;
	wf.wFormatTag		= WAVE_FORMAT_PCM;
	wf.wBitsPerSample	= 8;
	wf.nChannels		= 1;
	wf.nSamplesPerSec	= wfx.nSamplesPerSec;
	wf.nBlockAlign		= (wf.wBitsPerSample / 8) * wf.nChannels;
	wf.nAvgBytesPerSec	= wf.nSamplesPerSec * wf.nBlockAlign;
	wf.cbSize		= 0;
	MMRESULT mmr = waveOutOpen(&hwo, WAVE_MAPPER, &wf, (DWORD_PTR)hWnd, 0, CALLBACK_WINDOW);
	if (mmr != MMSYSERR_NOERROR) {
		Trace(_T("waveOutOpen\n"));
		return;
	}
 
	HCURSOR hWait = LoadCursor(NULL, IDC_WAIT);
	hCursor = SetCursor(hWait);
	SetClassLong(hWnd, GCL_HCURSOR, (LONG)hWait);
}
 
void OnWomOpen(void)
{
	wh.lpData		= (LPSTR)waveformData2;
	wh.dwBufferLength	= sampleLength2;
	wh.dwBytesRecorded	= 0;
	wh.dwUser		= 0;
	wh.dwFlags		= 0;
	wh.dwLoops		= 0;
	wh.lpNext		= NULL;
	wh.reserved		= 0;
	MMRESULT mmr = waveOutPrepareHeader(hwo, &wh, sizeof wh);
	if (mmr != MMSYSERR_NOERROR) {
		Trace(_T("waveOutPrepareHeader\n"));
		return;
	}
 
	wh.dwFlags		|= WHDR_BEGINLOOP | WHDR_ENDLOOP;
	wh.dwLoops		= 1;
	mmr = waveOutWrite(hwo, &wh, sizeof wh);
	if (mmr != MMSYSERR_NOERROR) {
		Trace(_T("waveOutWrite\n"));
	}
}
 
void OnWomDone(HWND hWnd)
{
	Trace(_T("OnWomDone\n"));
	waveOutReset(hwo);
	waveOutUnprepareHeader(hwo, &wh, sizeof wh);
	waveOutClose(hwo);
 
	SetCursor(hCursor);
	SetClassLong(hWnd, GCL_HCURSOR, (LONG)hCursor);
	hCursor = NULL;
}
 
最終更新:2013年02月06日 18:36