基于VSstudioDevC++的模拟钢琴程序(略复杂)

📅 2026/7/22 2:46:35
基于VSstudioDevC++的模拟钢琴程序(略复杂)
键盘音名映射。首先确定窗口程序按下指定的按键输出相关音名。DevC版较为简单#include windows.h #include mmsystem.h #include math.h #include imm.h #include stdio.h #pragma comment(lib, winmm.lib) #pragma comment(lib, imm32.lib) // 琴键数量 #define OCTAVE_COUNT 4 #define WHITE_KEY_COUNT (7 * OCTAVE_COUNT) #define BLACK_KEY_COUNT (5 * OCTAVE_COUNT) #define TOTAL_KEYS (WHITE_KEY_COUNT BLACK_KEY_COUNT) // 48 #define SAMPLE_RATE 44100 #define DURATION_SEC 1.5f #define MAX_SAMPLES (int)(SAMPLE_RATE * DURATION_SEC) #define CONTROLS_HEIGHT 80 // 增加控件栏高度容纳多行输入框 // 频率表 (C3 ~ B6) int frequencies[TOTAL_KEYS]; void InitFrequencies() { for (int i 0; i TOTAL_KEYS; i) { int midiNote 48 i; double freq 440.0 * pow(2.0, (midiNote - 69) / 12.0); frequencies[i] (int)(freq 0.5); } } // 黑白键索引 int whiteIndices[WHITE_KEY_COUNT]; int blackIndices[BLACK_KEY_COUNT]; void InitKeyIndices() { int wi 0, bi 0; for (int oct 0; oct OCTAVE_COUNT; oct) { int base oct * 12; int whiteOffsets[7] {0,2,4,5,7,9,11}; int blackOffsets[5] {1,3,6,8,10}; for (int i 0; i 7; i) whiteIndices[wi] base whiteOffsets[i]; for (int i 0; i 5; i) blackIndices[bi] base blackOffsets[i]; } } // 琴键结构 struct Key { RECT rect; int freq; bool isBlack; }; Key keys[TOTAL_KEYS]; bool keyPressed[TOTAL_KEYS] {false}; bool autoPressed[TOTAL_KEYS] {false}; int mouseDownKey -1; // 键盘映射 struct KeyMap { int vkCode; int globalIdx; }; KeyMap keyMap[48]; void InitKeyMap() { int idx 0; for (int i 0; i 9; i) { keyMap[idx].vkCode 1 i; keyMap[idx].globalIdx i; idx; } keyMap[idx].vkCode 0; keyMap[idx].globalIdx 9; idx; keyMap[idx].vkCode VK_SPACE; keyMap[idx].globalIdx 11; idx; for (int i 0; i 26; i) { keyMap[idx].vkCode A i; keyMap[idx].globalIdx 12 i; idx; } keyMap[idx].vkCode VK_OEM_3; keyMap[idx].globalIdx 10; idx; keyMap[idx].vkCode VK_OEM_MINUS; keyMap[idx].globalIdx 38; idx; keyMap[idx].vkCode VK_OEM_PLUS; keyMap[idx].globalIdx 39; idx; keyMap[idx].vkCode VK_OEM_4; keyMap[idx].globalIdx 40; idx; keyMap[idx].vkCode VK_OEM_6; keyMap[idx].globalIdx 41; idx; keyMap[idx].vkCode VK_OEM_1; keyMap[idx].globalIdx 42; idx; keyMap[idx].vkCode VK_OEM_7; keyMap[idx].globalIdx 43; idx; keyMap[idx].vkCode VK_OEM_COMMA; keyMap[idx].globalIdx 44; idx; keyMap[idx].vkCode VK_OEM_PERIOD; keyMap[idx].globalIdx 45; idx; keyMap[idx].vkCode VK_OEM_2; keyMap[idx].globalIdx 46; idx; keyMap[idx].vkCode VK_OEM_5; keyMap[idx].globalIdx 47; idx; } #define MAP_SIZE (sizeof(keyMap)/sizeof(keyMap[0])) // 音频 HWAVEOUT hWaveOut NULL; WAVEHDR waveHdr; short* pAudioBuffer NULL; int bufferSampleCount 0; bool isPlaying false; bool isGenerating false; // 预生成波形纯正弦波带包络 float* noteWaveforms[TOTAL_KEYS] {0}; void GenerateAllWaveforms() { float amplitude 0.5f; float decay 2.0f; int attack (int)(0.005 * SAMPLE_RATE); int release (int)(0.005 * SAMPLE_RATE); for (int note 0; note TOTAL_KEYS; note) { float* wave new float[MAX_SAMPLES]; float freq (float)frequencies[note]; float phaseStep 2.0f * 3.1415926535f * freq / SAMPLE_RATE; float phase 0.0f; for (int n 0; n MAX_SAMPLES; n) { float t (float)n / SAMPLE_RATE; float env expf(-decay * t); if (env 0.001f) env 0.0f; if (n attack) env * (float)n / attack; if (n MAX_SAMPLES - release) env * (float)(MAX_SAMPLES - n) / release; wave[n] sinf(phase) * amplitude * env; phase phaseStep; if (phase 2.0f * 3.1415926535f) phase - 2.0f * 3.1415926535f; } noteWaveforms[note] wave; } } void CleanNoteWaveforms() { for (int i 0; i TOTAL_KEYS; i) { if (noteWaveforms[i]) { delete[] noteWaveforms[i]; noteWaveforms[i] NULL; } } } // 函数原型 void StopSound(); void PlaySound(); // 波形混合 void GenerateWave(short* buffer, int sampleCount) { bool combined[TOTAL_KEYS]; for (int i 0; i TOTAL_KEYS; i) { combined[i] keyPressed[i] || autoPressed[i]; } memset(buffer, 0, sampleCount * sizeof(short)); for (int i 0; i TOTAL_KEYS; i) { if (combined[i] noteWaveforms[i]) { float* wave noteWaveforms[i]; for (int n 0; n sampleCount; n) { int sum buffer[n] (int)(wave[n] * 32767.0f); if (sum 32767) sum 32767; if (sum -32768) sum -32768; buffer[n] (short)sum; } } } } // 播放 void PlaySound() { if (isGenerating) return; bool anyPressed false; for (int i 0; i TOTAL_KEYS; i) { if (keyPressed[i] || autoPressed[i]) { anyPressed true; break; } } if (!anyPressed) { StopSound(); return; } if (isPlaying) { waveOutReset(hWaveOut); isPlaying false; } if (pAudioBuffer NULL) { bufferSampleCount MAX_SAMPLES; pAudioBuffer new short[bufferSampleCount]; } GenerateWave(pAudioBuffer, bufferSampleCount); WAVEFORMATEX wf; wf.wFormatTag WAVE_FORMAT_PCM; wf.nChannels 1; wf.nSamplesPerSec SAMPLE_RATE; wf.wBitsPerSample 16; wf.nBlockAlign wf.nChannels * wf.wBitsPerSample / 8; wf.nAvgBytesPerSec wf.nSamplesPerSec * wf.nBlockAlign; wf.cbSize 0; if (hWaveOut NULL) { if (waveOutOpen(hWaveOut, WAVE_MAPPER, wf, 0, 0, CALLBACK_NULL) ! MMSYSERR_NOERROR) { MessageBox(NULL, Cannot open audio device, Error, MB_OK); return; } } ZeroMemory(waveHdr, sizeof(WAVEHDR)); waveHdr.lpData (LPSTR)pAudioBuffer; waveHdr.dwBufferLength bufferSampleCount * sizeof(short); waveHdr.dwFlags 0; if (waveOutPrepareHeader(hWaveOut, waveHdr, sizeof(WAVEHDR)) ! MMSYSERR_NOERROR) { MessageBox(NULL, Prepare header failed, Error, MB_OK); return; } if (waveOutWrite(hWaveOut, waveHdr, sizeof(WAVEHDR)) ! MMSYSERR_NOERROR) { MessageBox(NULL, Write failed, Error, MB_OK); return; } isPlaying true; } void StopSound() { if (isPlaying) { waveOutReset(hWaveOut); waveOutUnprepareHeader(hWaveOut, waveHdr, sizeof(WAVEHDR)); isPlaying false; } } // 自动演奏支持 BPM 和音符时值 #define MAX_AUTO_NOTES 256 #define MAX_NOTES_PER_BEAT 16 struct Beat { int notes[MAX_NOTES_PER_BEAT]; int count; int duration; // 持续时间 (ms) int interval; // 间隔时间 (ms) }; Beat autoBeats[MAX_AUTO_NOTES]; int autoBeatCount 0; bool autoPlaying false; int autoIndex 0; bool waitingInterval false; // 是否在等待间隔 int globalTempo 120; // 默认 BPM bool showNote false; HWND hStaticNote; HWND hBtnShowNote; HWND hBtnPlay; HWND hComboMode, hEditScore; // 函数声明 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); void InitKeys(HWND hwnd, int topOffset); const char* GetNoteName(int idx, char* buffer); void UpdateNoteDisplay(HWND hwnd); void ParseScore(const char* text); void StartAutoPlay(HWND hwnd); void StopAutoPlay(HWND hwnd); // 获取音名 const char* GetNoteName(int idx, char* buffer) { if (idx 0 || idx TOTAL_KEYS) return ?; int semitone idx % 12; int octave idx / 12 3; const char* names[12] {C, C#, D, D#, E, F, F#, G, G#, A, A#, B}; sprintf(buffer, %s%d, names[semitone], octave); return buffer; } // 更新显示当前按下的所有音符 void UpdateNoteDisplay(HWND hwnd) { if (!showNote) { SetWindowTextA(hStaticNote, ); return; } char buffer[256] ; int first 1; for (int i 0; i TOTAL_KEYS; i) { if (keyPressed[i] || autoPressed[i]) { char note[8]; GetNoteName(i, note); if (!first) strcat(buffer, , ); strcat(buffer, note); first 0; } } if (strlen(buffer) 0) { SetTimer(hwnd, 2, 1000, NULL); } else { KillTimer(hwnd, 2); SetWindowTextA(hStaticNote, buffer); } } // 音符名称转索引 int NoteNameToIndex(const char* name) { char note[3] {0}; int octave 4; if (strlen(name) 2) return -1; note[0] name[0]; int pos 1; if (name[1] # || name[1] b) { note[1] name[1]; pos 2; } octave name[pos] - 0; if (octave 3 || octave 6) return -1; int semitone -1; if (strcmp(note, C) 0) semitone 0; else if (strcmp(note, C#) 0) semitone 1; else if (strcmp(note, D) 0) semitone 2; else if (strcmp(note, D#) 0) semitone 3; else if (strcmp(note, E) 0) semitone 4; else if (strcmp(note, F) 0) semitone 5; else if (strcmp(note, F#) 0) semitone 6; else if (strcmp(note, G) 0) semitone 7; else if (strcmp(note, G#) 0) semitone 8; else if (strcmp(note, A) 0) semitone 9; else if (strcmp(note, A#) 0) semitone 10; else if (strcmp(note, B) 0) semitone 11; else return -1; int idx (octave - 3) * 12 semitone; if (idx 0 || idx TOTAL_KEYS) return -1; return idx; } // 解析谱子支持 tempo120 和时值符号 void ParseScore(const char* text) { autoBeatCount 0; globalTempo 120; // 默认 const char* p text; while (*p) { // 跳过空格 while (*p ) p; if (*p \0) break; // 检查是否是 tempo 设置 if (strncmp(p, tempo, 6) 0) { p 6; globalTempo atoi(p); // 跳过数字到空格或结尾 while (*p *p ! ) p; continue; } // 找到节拍结束位置空格或结尾 const char* beatStart p; while (*p *p ! ) p; char beat[64]; int len p - beatStart; if (len sizeof(beat)) len sizeof(beat) - 1; strncpy(beat, beatStart, len); beat[len] \0; Beat beatObj autoBeats[autoBeatCount]; beatObj.count 0; beatObj.duration 500; // 默认值将在后面根据时值重算 beatObj.interval 0; char notePart[64] {0}; char durationSymbol[8] ; int intervalMs 0; char beatCopy[64]; strcpy(beatCopy, beat); char* token strtok(beatCopy, ); if (token) { strcpy(notePart, token); token strtok(NULL, ); if (token) { if (token[0] ,) { intervalMs atoi(token 1); } else { strcpy(durationSymbol, token); token strtok(NULL, ); if (token token[0] ,) { intervalMs atoi(token 1); } } } } else { strcpy(notePart, beat); } char noteCopy[64]; strcpy(noteCopy, notePart); char* noteToken strtok(noteCopy, ); while (noteToken ! NULL beatObj.count MAX_NOTES_PER_BEAT) { int idx NoteNameToIndex(noteToken); if (idx 0) { beatObj.notes[beatObj.count] idx; } noteToken strtok(NULL, ); } float durationFactor 1.0f; if (strlen(durationSymbol) 0) { if (strcmp(durationSymbol, w) 0) durationFactor 4.0f; else if (strcmp(durationSymbol, h) 0) durationFactor 2.0f; else if (strcmp(durationSymbol, q) 0) durationFactor 1.0f; else if (strcmp(durationSymbol, e) 0) durationFactor 0.5f; else if (strcmp(durationSymbol, s) 0) durationFactor 0.25f; if (strlen(durationSymbol) 1 durationSymbol[1] .) { durationFactor * 1.5f; } } int beatDuration (int)((60.0f / globalTempo) * 1000.0f * durationFactor); if (beatDuration 10) beatDuration 10; beatObj.duration beatDuration; beatObj.interval intervalMs; if (beatObj.count 0) { autoBeatCount; } } } // 自动演奏 void StartAutoPlay(HWND hwnd) { if (autoBeatCount 0) return; if (autoPlaying) StopAutoPlay(hwnd); autoPlaying true; autoIndex 0; waitingInterval false; if (autoIndex autoBeatCount) { Beat beat autoBeats[autoIndex]; for (int i 0; i beat.count; i) { int idx beat.notes[i]; autoPressed[idx] true; InvalidateRect(hwnd, keys[idx].rect, FALSE); } UpdateNoteDisplay(hwnd); PlaySound(); SetTimer(hwnd, 1, beat.duration, NULL); } } void StopAutoPlay(HWND hwnd) { if (autoPlaying) { for (int i 0; i TOTAL_KEYS; i) { if (autoPressed[i]) { autoPressed[i] false; InvalidateRect(hwnd, keys[i].rect, FALSE); } } autoPlaying false; waitingInterval false; KillTimer(hwnd, 1); PlaySound(); UpdateNoteDisplay(hwnd); } } // 更新窗口标题 void UpdateWindowTitle(HWND hwnd) { SetWindowTextA(hwnd, Piano - Pure Sine (Clear)); } // 初始化琴键位置 void InitKeys(HWND hwnd, int topOffset) { RECT client; GetClientRect(hwnd, client); int margin 10; int top topOffset margin; int bottom client.bottom - margin; int whiteHeight bottom - top; int whiteWidth (client.right - client.left - 2 * margin) / WHITE_KEY_COUNT; if (whiteWidth 5) whiteWidth 5; int totalWhiteWidth whiteWidth * WHITE_KEY_COUNT; int left margin (client.right - client.left - 2 * margin - totalWhiteWidth) / 2; for (int i 0; i WHITE_KEY_COUNT; i) { int idx whiteIndices[i]; keys[idx].rect.left left i * whiteWidth; keys[idx].rect.right keys[idx].rect.left whiteWidth; keys[idx].rect.top top; keys[idx].rect.bottom bottom; keys[idx].freq frequencies[idx]; keys[idx].isBlack false; } int blackWidth (int)(whiteWidth * 0.6); int blackHeight (int)(whiteHeight * 0.6); int blackTop top; int blackBottom top blackHeight; int blackOffset[5] {0, 1, 3, 4, 5}; for (int oct 0; oct OCTAVE_COUNT; oct) { int baseWhite oct * 7; int baseBlack oct * 5; for (int b 0; b 5; b) { int whiteIdx baseWhite blackOffset[b]; int blackIdx blackIndices[baseBlack b]; RECT leftWhite keys[whiteIndices[whiteIdx]].rect; RECT rightWhite; if (whiteIdx 1 WHITE_KEY_COUNT) { rightWhite keys[whiteIndices[whiteIdx 1]].rect; } else { rightWhite leftWhite; rightWhite.left leftWhite.right; rightWhite.right leftWhite.right whiteWidth; } int centerX (leftWhite.right rightWhite.left) / 2; int blackLeft centerX - blackWidth / 2; if (blackLeft leftWhite.left) blackLeft leftWhite.left; if (blackLeft blackWidth rightWhite.right) blackLeft rightWhite.right - blackWidth; keys[blackIdx].rect.left blackLeft; keys[blackIdx].rect.right blackLeft blackWidth; keys[blackIdx].rect.top blackTop; keys[blackIdx].rect.bottom blackBottom; keys[blackIdx].freq frequencies[blackIdx]; keys[blackIdx].isBlack true; } } } // 窗口过程 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { ImmAssociateContext(hwnd, NULL); InitFrequencies(); InitKeyIndices(); InitKeyMap(); InitKeys(hwnd, CONTROLS_HEIGHT); UpdateWindowTitle(hwnd); if (pAudioBuffer NULL) { bufferSampleCount MAX_SAMPLES; pAudioBuffer new short[bufferSampleCount]; } GenerateAllWaveforms(); int margin 10; int y 8; int x margin; // 模式下拉框 hComboMode CreateWindowA(COMBOBOX, NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS, x, y, 120, 100, hwnd, (HMENU)1001, GetModuleHandle(NULL), NULL); SendMessageA(hComboMode, CB_ADDSTRING, 0, (LPARAM)Manual Mode); SendMessageA(hComboMode, CB_ADDSTRING, 0, (LPARAM)Auto-Play Mode); SendMessageA(hComboMode, CB_SETCURSEL, 0, 0); x 130; // 多行输入框支持换行、滚动 hEditScore CreateWindowA(EDIT, , WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | WS_VSCROLL | ES_AUTOVSCROLL, x, y, 350, 60, // 宽度、高度均加大 hwnd, (HMENU)1002, GetModuleHandle(NULL), NULL); EnableWindow(hEditScore, FALSE); x 360; // Play 按钮静态文本点击 hBtnPlay CreateWindowA(STATIC, Play, WS_CHILD | WS_VISIBLE | SS_CENTER | SS_NOTIFY, x, y, 60, 22, hwnd, (HMENU)1003, GetModuleHandle(NULL), NULL); x 70; // Show Note 切换静态文本点击 hBtnShowNote CreateWindowA(STATIC, Show Note: OFF, WS_CHILD | WS_VISIBLE | SS_CENTER | SS_NOTIFY, x, y, 120, 22, hwnd, (HMENU)1004, GetModuleHandle(NULL), NULL); x 130; // 显示当前音符的静态框 hStaticNote CreateWindowA(STATIC, , WS_CHILD | WS_VISIBLE | SS_CENTER, x, y, 200, 22, hwnd, (HMENU)1005, GetModuleHandle(NULL), NULL); SetWindowTextA(hStaticNote, ); break; } case WM_SIZE: { InitKeys(hwnd, CONTROLS_HEIGHT); InvalidateRect(hwnd, NULL, TRUE); break; } case WM_COMMAND: { int id LOWORD(wParam); int code HIWORD(wParam); if (id 1001 code CBN_SELCHANGE) { int sel SendMessageA(hComboMode, CB_GETCURSEL, 0, 0); StopAutoPlay(hwnd); if (sel 0) { EnableWindow(hEditScore, FALSE); EnableWindow(hBtnPlay, FALSE); SetFocus(hwnd); } else { EnableWindow(hEditScore, TRUE); EnableWindow(hBtnPlay, TRUE); SetFocus(hEditScore); } InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); } else if (id 1003 (code STN_CLICKED || code BN_CLICKED)) { // 从多行编辑框获取文本 char text[4096]; // 增大缓冲区 GetWindowTextA(hEditScore, text, sizeof(text)); ParseScore(text); StartAutoPlay(hwnd); } else if (id 1004 (code STN_CLICKED || code BN_CLICKED)) { showNote !showNote; SetWindowTextA(hBtnShowNote, showNote ? Show Note: ON : Show Note: OFF); if (!showNote) SetWindowTextA(hStaticNote, ); else UpdateNoteDisplay(hwnd); } break; } case WM_TIMER: { if (wParam 1) { if (!waitingInterval) { if (autoIndex autoBeatCount) { Beat beat autoBeats[autoIndex]; for (int i 0; i beat.count; i) { int idx beat.notes[i]; autoPressed[idx] false; InvalidateRect(hwnd, keys[idx].rect, FALSE); } } if (autoIndex autoBeatCount autoBeats[autoIndex].interval 0) { waitingInterval true; SetTimer(hwnd, 1, autoBeats[autoIndex].interval, NULL); } else { autoIndex; if (autoIndex autoBeatCount) { StopAutoPlay(hwnd); } else { Beat beat autoBeats[autoIndex]; for (int i 0; i beat.count; i) { int idx beat.notes[i]; autoPressed[idx] true; InvalidateRect(hwnd, keys[idx].rect, FALSE); } UpdateNoteDisplay(hwnd); PlaySound(); SetTimer(hwnd, 1, beat.duration, NULL); } } } else { waitingInterval false; autoIndex; if (autoIndex autoBeatCount) { StopAutoPlay(hwnd); } else { Beat beat autoBeats[autoIndex]; for (int i 0; i beat.count; i) { int idx beat.notes[i]; autoPressed[idx] true; InvalidateRect(hwnd, keys[idx].rect, FALSE); } UpdateNoteDisplay(hwnd); PlaySound(); SetTimer(hwnd, 1, beat.duration, NULL); } } } else if (wParam 2) { SetWindowTextA(hStaticNote, ); KillTimer(hwnd, 2); } break; } case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hwnd, ps); HBRUSH bgBrush CreateSolidBrush(RGB(50, 50, 50)); RECT client; GetClientRect(hwnd, client); RECT keysRect client; keysRect.top CONTROLS_HEIGHT; FillRect(hdc, keysRect, bgBrush); DeleteObject(bgBrush); HBRUSH whiteBrush CreateSolidBrush(RGB(255, 255, 255)); HBRUSH pressedWhite CreateSolidBrush(RGB(200, 200, 200)); HPEN blackPen CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); HPEN oldPen (HPEN)SelectObject(hdc, blackPen); for (int i 0; i TOTAL_KEYS; i) { if (!keys[i].isBlack) { RECT r keys[i].rect; HBRUSH brush (keyPressed[i] || autoPressed[i]) ? pressedWhite : whiteBrush; FillRect(hdc, r, brush); FrameRect(hdc, r, (HBRUSH)GetStockObject(BLACK_BRUSH)); } } HBRUSH blackBrush CreateSolidBrush(RGB(0, 0, 0)); HBRUSH pressedBlack CreateSolidBrush(RGB(80, 80, 80)); HPEN grayPen CreatePen(PS_SOLID, 1, RGB(60, 60, 60)); SelectObject(hdc, grayPen); for (int i 0; i TOTAL_KEYS; i) { if (keys[i].isBlack) { RECT r keys[i].rect; HBRUSH brush (keyPressed[i] || autoPressed[i]) ? pressedBlack : blackBrush; FillRect(hdc, r, brush); FrameRect(hdc, r, (HBRUSH)GetStockObject(BLACK_BRUSH)); } } SelectObject(hdc, oldPen); DeleteObject(whiteBrush); DeleteObject(pressedWhite); DeleteObject(blackBrush); DeleteObject(pressedBlack); DeleteObject(blackPen); DeleteObject(grayPen); EndPaint(hwnd, ps); break; } // 鼠标事件 case WM_LBUTTONDOWN: { int x LOWORD(lParam), y HIWORD(lParam); if (y CONTROLS_HEIGHT) break; SetFocus(hwnd); POINT pt {x, y}; int idx -1; for (int i 0; i TOTAL_KEYS; i) { if (keys[i].isBlack PtInRect(keys[i].rect, pt)) { idx i; break; } } if (idx -1) { for (int i 0; i TOTAL_KEYS; i) { if (!keys[i].isBlack PtInRect(keys[i].rect, pt)) { idx i; break; } } } if (idx ! -1 !keyPressed[idx]) { mouseDownKey idx; keyPressed[idx] true; UpdateNoteDisplay(hwnd); InvalidateRect(hwnd, keys[idx].rect, FALSE); PlaySound(); SetCapture(hwnd); } break; } case WM_LBUTTONUP: { if (mouseDownKey ! -1) { int idx mouseDownKey; if (keyPressed[idx]) { keyPressed[idx] false; UpdateNoteDisplay(hwnd); InvalidateRect(hwnd, keys[idx].rect, FALSE); PlaySound(); } mouseDownKey -1; ReleaseCapture(); } break; } case WM_CAPTURECHANGED: { if (mouseDownKey ! -1) { int idx mouseDownKey; if (keyPressed[idx]) { keyPressed[idx] false; UpdateNoteDisplay(hwnd); InvalidateRect(hwnd, keys[idx].rect, FALSE); PlaySound(); } mouseDownKey -1; } break; } // 键盘事件 case WM_KEYDOWN: { HWND hFocus GetFocus(); // 如果焦点在编辑框则让编辑框处理输入不截获按键 if (hFocus hEditScore) { break; } int vk (int)wParam; BOOL isMapped FALSE; for (int i 0; i MAP_SIZE; i) { if (keyMap[i].vkCode vk) { isMapped TRUE; int idx keyMap[i].globalIdx; if (idx 0 idx TOTAL_KEYS !keyPressed[idx]) { keyPressed[idx] true; UpdateNoteDisplay(hwnd); InvalidateRect(hwnd, keys[idx].rect, FALSE); PlaySound(); } break; } } if (isMapped) { return 0; } break; } case WM_KEYUP: { HWND hFocus GetFocus(); if (hFocus hEditScore) { break; } int vk (int)wParam; for (int i 0; i MAP_SIZE; i) { if (keyMap[i].vkCode vk) { int idx keyMap[i].globalIdx; if (idx 0 idx TOTAL_KEYS keyPressed[idx]) { keyPressed[idx] false; UpdateNoteDisplay(hwnd); InvalidateRect(hwnd, keys[idx].rect, FALSE); PlaySound(); } return 0; } } break; } case WM_DESTROY: StopAutoPlay(hwnd); StopSound(); if (hWaveOut) { waveOutClose(hWaveOut); hWaveOut NULL; } if (pAudioBuffer) { delete[] pAudioBuffer; pAudioBuffer NULL; } CleanNoteWaveforms(); PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } // 入口 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSA wc {0}; wc.lpfnWndProc WndProc; wc.hInstance hInstance; wc.hbrBackground (HBRUSH)(COLOR_WINDOW 1); wc.lpszClassName PianoSingleRow; if (!RegisterClassA(wc)) return 0; HWND hwnd CreateWindowA(PianoSingleRow, Piano - Pure Sine (Clear), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 1400, 600, // 增大窗口 NULL, NULL, hInstance, NULL); if (!hwnd) return 0; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); MSG msg; while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return msg.wParam; }这里采取窗口程序实现需要在链接器里面添加别的东西。并且存在缺陷。模拟正弦波听起来很奇怪可能出现短促或者类似敲击声的东西。但是胜在简单。这里提到一点很多这种要使用键盘进行操作的软件会首先禁用输入法防止拦截。另外有时候使用鼠标之后点击空格会操作按钮导致错误。所以这里使用静态文本避免错误。这里实现了蛮多功能。自动输入说明键盘按键与音名的对应关系数字行1C3, 2C#3, 3D3, 4D#3, 5E3, 6F3, 7F#3, 8G3, 9G#3, 0A3, A#3, 空格B3, -D6, D#6字母行QC4, WC#4, ED4, RD#4, TE4, YF4, UF#4, IG4, OG#4, PA4, AA#4, SB4, DC5, FC#5, GD5, HD#5, JE5, KF5, LF#5, ZG5, XG#5, CA5, VA#5, BB5, NC6, MC#6符号键[E6, ]F6, ;F#6, G6, ,G#6, .A6, /A#6, \B6覆盖音域 C3B6 共48个半音。自动演奏输入语法速度设置在乐谱开头写 tempo数字例如 tempo120表示每分钟节拍数默认120。该设置仅对之后所有时值符号的计算生效。节拍格式每个节拍由三部分组成各部分用空格分隔音符组合 时值符号 间隔。其中时值符号和间隔均为可选但顺序固定。若省略时值符号则默认为四分音符若省略间隔则默认为0。音符组合一个或多个音名用加号连接例如 C4 或 C4E4G4。表示该节拍同时按下这些音。时值符号指定该节拍的相对长度。w全音符4拍h二分音符2拍q四分音符1拍默认e八分音符0.5拍s十六分音符0.25拍。支持附点在符号后加小数点如 q. 表示附点四分音符1.5拍e. 表示附点八分音符0.75拍。每个节拍实际持续毫秒数 (60 / tempo) * 1000 * 时值倍数。间隔用逗号开头后接数字表示该节拍结束后到下一个节拍开始前的空白停顿毫秒数例如 ,200。仅影响节拍间的间隔不影响当前节拍的持续时间。分隔符多个节拍之间用空格分隔。程序按顺序依次播放每个节拍。默认值若未设置 tempo则为120若节拍未写时值符号则为 q若未写间隔则为0。所有设置以当前节拍前最后一次出现的设置为准tempo 可全局设置一次也可在中间修改但仅影响之后的节拍。VSstudio版需要配置很多东西和资源优点更真实可以录音能够使用shift键进行延音操作更贴切现实添加了歌曲播放和歌词滚动可以加载midi文件。篇幅限制先写在这里给个图片测试版本未完待续