欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 锐评 > 【C++】自实现简谱播放

【C++】自实现简谱播放

2025/11/4 4:19:00 来源:https://blog.csdn.net/2303_80346267/article/details/146924259  浏览:    关键词:【C++】自实现简谱播放

本文将介绍一套基于 ASCII 的简谱编码规则,并展示如何在 C++ 中利用这套规则实现简谱播放。该方案支持音高、时值、高低音、升降调、休止符以及小节线,确保编码规则既简洁又易于解析,同时还具备良好的扩展性。需要注意的是,此方案仅支持 Windows 操作系统。下面我将详细介绍这套规则和 C++ 实现的代码示例。


简谱编码规则

这套规则采用“音符 token”的概念,每个 token 都由以下部分组成:

  • 高低音前缀:用 ^ 表示升高一个八度,用 _ 表示降低一个八度。多个符号表示多个八度调整,如 ^^1 表示高两八度,__1 表示低两八度。
  • 音高:用 1-7 表示 Do 到 Si,数字 0 则表示休止符(无音)。
  • 升降调# 表示升半音,b 表示降半音。
  • 时值后缀:默认为四分音符(1拍),可通过后缀修改时值:
    • - 表示二分音符(2拍)
    • -- 表示全音符(4拍)
    • / 表示八分音符(1/2拍)
    • // 表示十六分音符(1/4拍)

每个音符 token 由 [高低音前缀][音高][升降调][时值后缀] 组成,不同 token 之间用空格分隔,小节线作为独立的 token,用于视觉分隔,不影响实际播放。

类别规则示例说明
音高1-7 表示 Do-Si1(Do),5(Sol)基本音阶,基于 C 大调
0 表示休止符0无音,持续时间由时值决定
时值默认:四分音符(1拍)11拍,长度由 BPM 决定
-:二分音符(2拍)1-2拍
--:全音符(4拍)1--4拍
/:八分音符(1/2拍)1/1/2拍
//:十六分音符(1/4拍)1//1/4拍
高低音^ 前缀:升高一个八度^1高音 Do,频率翻倍
_ 前缀:降低一个八度_1低音 Do,频率减半
多个 ^_:多个八度^^1(高两八度),__1(低两八度)每增加一个,频率乘以 2 或除以 2
升降调#:升半音1#Do 升为 Do#,频率增加半音
b:降半音1bDo 降为 Dob,频率减少半音
小节线||

C++ 实现说明

下面是 C++ 的完整实现代码。代码中定义了一个 NotePlayer 类,用于解析简谱字符串并播放音符。整个播放逻辑主要包含以下步骤:

  1. 解析 Token:将输入的简谱字符串按空格拆分,每个 token 根据前缀和后缀解析出音高、八度偏移、升降调及时值信息。
  2. 计算频率:根据音符的音高、八度调整和升降调计算出实际播放时的频率。
  3. 播放音符:利用 Windows 的 Beep 函数播放对应频率和时值的音符;若遇休止符则通过 Sleep 实现等待。
#pragma once
#include <iostream>
#include <sstream>
#include <string>
#include <cmath>
#include <thread>
#include <windows.h>class NotePlayer {
public:NotePlayer(int bpm = 120) : bpm(bpm), quarterDuration(60000 / bpm), isPlaying(false) {}// 播放简谱,async = true 为异步播放void play(const std::string& song, bool async = false) {if (isPlaying) return;isPlaying = true;auto playLogic = [this, song]() {std::istringstream iss(song);std::string token;while (iss >> token && isPlaying) if (token != "|") playNote(parseToken(token));isPlaying = false;};if (async) { playThread = std::thread(playLogic); playThread.detach(); } else playLogic();}// 停止播放(仅对异步有效)void stop() { isPlaying = false; }private:int bpm, quarterDuration;std::thread playThread;bool isPlaying;struct Note { int frequency = 0, duration = 0; };// 播放单个音符void playNote(const Note& note) {if (note.duration <= 0) return;if (note.frequency > 0) Beep(note.frequency, note.duration);else Sleep(note.duration);}// 解析简谱 tokenNote parseToken(const std::string& token) {Note note; size_t pos = 0; int octaveOffset = 0;while (pos < token.size() && (token[pos] == '^' || token[pos] == '_')) octaveOffset += (token[pos++] == '^') ? 1 : -1;if (pos >= token.size() || token[pos] < '0' || token[pos] > '7') return note;char pitch = token[pos++], accidental = ' ';if (pos < token.size() && (token[pos] == '#' || token[pos] == 'b')) accidental = token[pos++];double multiplier = (pos < token.size() && token.substr(pos) == "--") ? 4.0 : (pos < token.size() && token.substr(pos) == "-") ? 2.0 : (pos < token.size() && token.substr(pos) == "//") ? 0.25 : (pos < token.size() && token.substr(pos) == "/") ? 0.5 : 1.0;note.duration = static_cast<int>(quarterDuration * multiplier);note.frequency = calculateFrequency(pitch, octaveOffset, accidental);return note;}// 计算音符频率int calculateFrequency(char pitch, int octaveOffset, char accidental) {if (pitch == '0') return 0;int semitoneOffset = (pitch == '1') ? 0 : (pitch == '2') ? 2 : (pitch == '3') ? 4 : (pitch == '4') ? 5 : (pitch == '5') ? 7 : (pitch == '6') ? 9 : 11;if (accidental == '#') semitoneOffset += 1; else if (accidental == 'b') semitoneOffset -= 1;return static_cast<int>(261.63 * std::pow(2.0, octaveOffset) * std::pow(2.0, semitoneOffset / 12.0) + 0.5);}
};

使用示例

以下是一个简单的使用示例,演示如何调用 NotePlayer 类来播放一段简谱。示例中使用了斗地主主题音乐作为演示内容。

#include <iostream>
#include "NotePlayer.hpp"int main() {// 示例:斗地主主题音乐(佚名)std::string song = R"(3 3/ 2/ | 1 1/ _6/ | 2/ 3/ 2/ 3/ | _5- _6 _6/ _5/ | _6 1 | 5/ 6/ 3/ 5/ | 2- 3 3/ 2/ | 3 5 | 6/ 6/ 6/ ^1/ | 6 5/ 3/ 2 2/ 3/ | 5 _5 | 2/ 3/ 2/ 3/ | 1- 3 3/ 2/ | 3 5 | 6/ ^1/ 6/ 5/ | 6 5/ 3/ 2 2/ 3/ | 5 _5 | 2/ 3/ 2/ 3/ | 1- 2/ 2/ 2/ 3/ | 5 5/ 6/ | ^1 6 | ^1- )";NotePlayer player(120); // BPM 120std::cout << "Playing the simplified score..." << std::endl;player.play(song);return 0;
}

总结

本文介绍了如何利用 C++ 结合自定义的简谱编码规则实现简谱播放。通过简单的 ASCII 编码方式,不仅使音乐表示更加直观,同时也保证了解析的高效和扩展性。希望这篇文章能给大家在音频处理和 C++ 编程实践中带来新的灵感和帮助。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词