// 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;
}