#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <process.h>
#include <stdbool.h>
HMIDIOUT hMidiOut;
float playSpeed = 0.5;
bool stopPlaying = false;
bool pausePlaying = false;
int currentNoteIndex = 0;
typedef struct {
int pitch;
int duration;
} Note;
Note parseNote(const char* noteStr) {
Note note;
note.pitch = -1;
note.duration = 200;
int octave = 4;
int basePitch = -1;
const char* p = noteStr;
while (*p == '+' || *p == '-' || *p == '#' || *p == '!') {
if (*p == '+') {
octave++;
} else if (*p == '-') {
octave--;
} else if (*p == '#') {
basePitch++;
} else if (*p == '!') {
basePitch--;
}
p++;
}
if (*p >= '0' && *p <= '7') {
basePitch = *p - '0';
if (basePitch == 0) {
note.pitch = -1;
} else {
static const int basePitches[] = { 60, 62, 64, 65, 67, 69, 71 };
note.pitch = basePitches[basePitch - 1] + (octave - 4) * 12;
}
p++;
}
while (*p) {
if (*p == '/') {
note.duration /= 2;
p++;
if (*p == '/') {
note.duration /= 2;
p++;
}
} else if (*p == '-') {
note.duration *= 2;
p++;
} else if (*p == '.') {
note.duration += note.duration / 2;
p++;
if (*p == '.') {
note.duration += note.duration / 4;
p++;
}
} else {
break;
}
}
printf("Parsed Note - Pitch: %d, Duration: %d\n", note.pitch, note.duration);
return note;
}
void parseScore(const char* scoreStr, Note** notes, int* noteCount) {
int capacity = 10;
*notes = (Note*)malloc(capacity * sizeof(Note));
*noteCount = 0;
char tempStr[1024];
strcpy(tempStr, scoreStr);
for (int i = 0; tempStr[i] != '\0'; i++) {
if (tempStr[i] == '\n') {
tempStr[i] = ',';
}
}
char* token = strtok(tempStr, ",");
while (token != NULL) {
if (*noteCount >= capacity) {
capacity *= 2;
*notes = (Note*)realloc(*notes, capacity * sizeof(Note));
}
(*notes)[*noteCount] = parseNote(token);
(*noteCount)++;
token = strtok(NULL, ",");
}
}
unsigned int __stdcall PlayScoreThread(void* param) {
Note* notes = *(Note**)param;
int noteCount = *((int*)param + 1);
printf("PlayScoreThread started\n");
stopPlaying = false;
pausePlaying = false;
currentNoteIndex = 0;
for (; currentNoteIndex < noteCount; currentNoteIndex++) {
if (stopPlaying) {
break;
}
while (pausePlaying) {
Sleep(100);
}
int adjustedDuration = (int)(notes[currentNoteIndex].duration / playSpeed);
if (notes[currentNoteIndex].pitch != -1) {
printf("Playing Note - Index: %d, Pitch: %d, Adjusted Duration: %d\n", currentNoteIndex, notes[currentNoteIndex].pitch, adjustedDuration);
MMRESULT result = midiOutShortMsg(hMidiOut, 0x90 | (notes[currentNoteIndex].pitch << 8) | (127 << 16));
if (result != MMSYSERR_NOERROR) {
char errorMsg[256];
sprintf(errorMsg, "midiOutShortMsg error for note %d, error code: %d", currentNoteIndex, result);
MessageBox(NULL, errorMsg, "Error", MB_OK | MB_ICONERROR);
}
Sleep(adjustedDuration);
result = midiOutShortMsg(hMidiOut, 0x80 | (notes[currentNoteIndex].pitch << 8) | (0 << 16));
if (result != MMSYSERR_NOERROR) {
char errorMsg[256];
sprintf(errorMsg, "midiOutShortMsg error for note %d, error code: %d", currentNoteIndex, result);
MessageBox(NULL, errorMsg, "Error", MB_OK | MB_ICONERROR);
}
} else {
printf("Rest - Index: %d, Duration: %d\n", currentNoteIndex, adjustedDuration);
Sleep(adjustedDuration);
}
}
free(notes);
free(param);
printf("PlayScoreThread ended\n");
_endthreadex(0);
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE: {
CreateWindow("EDIT", "", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_WANTRETURN,
10, 10, 400, 200, hwnd, (HMENU)100, NULL, NULL);
CreateWindow("BUTTON", "播放", WS_VISIBLE | WS_CHILD,
20, 230, 80, 30, hwnd, (HMENU)101, NULL, NULL);
CreateWindow("BUTTON", "停止", WS_VISIBLE | WS_CHILD,
120, 230, 80, 30, hwnd, (HMENU)104, NULL, NULL);
CreateWindow("BUTTON", "暂停", WS_VISIBLE | WS_CHILD,
220, 230, 80, 30, hwnd, (HMENU)105, NULL, NULL);
CreateWindow("STATIC", "播放速度 (倍数):", WS_VISIBLE | WS_CHILD,
20, 270, 120, 20, hwnd, (HMENU)102, NULL, NULL);
CreateWindow("EDIT", "0.5", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_NUMBER,
160, 270, 50, 20, hwnd, (HMENU)103, NULL, NULL);
if (midiOutOpen(&hMidiOut, MIDI_MAPPER, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR) {
char errorMsg[256];
sprintf(errorMsg, "无法打开 MIDI 输出设备,错误码: %d", midiOutOpen(&hMidiOut, MIDI_MAPPER, 0, 0, CALLBACK_NULL));
MessageBox(hwnd, errorMsg, "错误", MB_OK | MB_ICONERROR);
} else {
}
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case 101: {
char buffer[1024];
GetWindowText((HWND)GetDlgItem(hwnd, 100), buffer, sizeof(buffer));
Note* notes;
int noteCount;
parseScore(buffer, ¬es, ¬eCount);
char speedBuffer[20];
GetWindowText((HWND)GetDlgItem(hwnd, 103), speedBuffer, sizeof(speedBuffer));
playSpeed = (float)atof(speedBuffer);
if (playSpeed <= 0) {
playSpeed = 0.5;
MessageBox(hwnd, "速度必须为正数,已恢复默认速度 0.5", "错误", MB_OK | MB_ICONERROR);
}
void* threadParam = malloc(sizeof(Note*) + sizeof(int));
memcpy(threadParam, ¬es, sizeof(Note*));
memcpy((char*)threadParam + sizeof(Note*), ¬eCount, sizeof(int));
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, PlayScoreThread, threadParam, 0, NULL);
CloseHandle(hThread);
break;
}
case 104: {
stopPlaying = true;
pausePlaying = false;
currentNoteIndex = 0;
SetWindowText((HWND)GetDlgItem(hwnd, 105), "暂停");
break;
}
case 105: {
if (pausePlaying) {
pausePlaying = false;
SetWindowText((HWND)GetDlgItem(hwnd, 105), "暂停");
} else {
pausePlaying = true;
SetWindowText((HWND)GetDlgItem(hwnd, 105), "继续");
}
break;
}
}
break;
}
case WM_DESTROY: {
midiOutClose(hMidiOut);
PostQuitMessage(0);
break;
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "SheetMusicEditorClass";
RegisterClass(&wc);
HWND hwnd = CreateWindow(wc.lpszClassName, "简谱编辑程序", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 520, 420, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}