c语言编程笔录

首页 >   > 笔记大全

笔记大全

基于C怎么编写一个键盘提示音程序

更新时间:2023-11-07
首先,我们需要使用C++中的Win32 API函数来控制计算机的音频。为了播放声音,我们将使用Waveform Audio接口(简称WAV)。 第一段落 下面是使用Win32 API的代码段来初始化音频设备,并设置使用的WAV格式。我们可以使用WAVEFORMATEX结构来设置需要使用的WAV格式。这个结构定义了声音数据位宽、采样率等音频格式相关的信息。请参考以下代码示例:

代码段 1: 初始化音频设备和WAV格式

#include <windows.h>
#include <mmsystem.h>

// 定义结构体: WAVEFORMATEX
WAVEFORMATEX wfx = 
{
    WAVE_FORMAT_PCM, // 音频的类型 (PCM)
    1,               // 声道数
    8000,            // 采样率 (每秒的样本数)
    16,              // 每个采样点的位数 (16 位)
    2,               // 数据块的对齐方式
    0,               // 数据块的附加信息
    0                // 保留的信息
};

HWAVEOUT hWaveOut; // 这是音频设备的句柄

// 初始化音频设备
waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);

第二段落 接下来,我们将使用一个函数来生成频率序列和持续时间序列,然后使用这些信息来创建声音数据块。有了声音数据块,我们就可以将它们传递给音频设备来播放声音。

代码段 2:生成声音数据块并播放声音

// 定义一组常数
const int SAMPLE_RATE = 8000; // WAV格式的采样率
const int AMPLITUDE = 32000; // 最大振幅

// 生成频率序列
const double FREQ_C4 = 261.63; // 中央C音
const double FREQ_E4 = 329.63; // E音
const double FREQ_G4 = 392.00; // G音

// 生成持续时间序列 (单位:毫秒)
const int DURATION_QUARTER_NOTE = 500; // 四分音符长
const int DURATION_HALF_NOTE = 2 * DURATION_QUARTER_NOTE; // 二分音符长
const int DURATION_WHOLE_NOTE = 4 * DURATION_QUARTER_NOTE; // 全音符长

// 生成数据块
void GenerateSoundData(std::vector<short>& data, double frequency, int duration)
{
    const int numSamples = SAMPLE_RATE * duration / 1000;
    const double delta = 2 * M_PI * frequency / SAMPLE_RATE;

    data.resize(numSamples);

    for (int i = 0; i < numSamples; i++)
    {
        short sample = (short)(AMPLITUDE * sin(i * delta));
        data[i] = sample;
    }
}

// 播放音符
void PlayNote(double frequency, int duration)
{
    std::vector<short> data;
    GenerateSoundData(data, frequency, duration);

    // 创建声音数据块
    WAVEHDR header;
    memset(&header, 0, sizeof(header));

    header.lpData = (LPSTR)data.data();
    header.dwBufferLength = data.size() * sizeof(short);

    // 准备音频数据块
    waveOutPrepareHeader(hWaveOut, &header, sizeof(header));

    // 发送音频数据块到设备来播放声音
    waveOutWrite(hWaveOut, &header, sizeof(header));

    // 等待音频数据块完成
    while (header.dwFlags & WHDR_BUSY)
        Sleep(10);

    // 释放音频数据块
    waveOutUnprepareHeader(hWaveOut, &header, sizeof(header));
}

第三段落 现在,在获得每个音符的频率和持续时间后,我们可以将其转换为指示音符的符号,“do”、“re”,等等。以下代码段展示了如何实现这个功能:

代码段 3: 将音高和持续时间转换为符号

// 将音高转换为符号
std::string PitchToSymbol(double pitch)
{
    int semitones = (int)round(12 * log(pitch / FREQ_C4) / log(2));

    switch (semitones)
    {
    case 0: return "do";
    case 2: return "re";
    case 4: return "mi";
    case 5: return "fa";
    case 7: return "so";
    case 9: return "la";
    case 11: return "xi";
    default: return "-";
    }
}

// 将持续时间转换为符号
std::string DurationToSymbol(int duration)
{
    if (duration == DURATION_QUARTER_NOTE) return "4";
    if (duration == DURATION_HALF_NOTE) return "2";
    if (duration == DURATION_WHOLE_NOTE) return "1";
    else return "";
}

第四段落 组合所有的部分,我们就可以编写一个完整的键盘提示音程序了。请参考下面这个基于控制台的键盘提示音程序的代码示例:

代码段 4: 完整的键盘提示音程序

#include <iostream>
#include <vector>
#include <string>

#include <windows.h>
#include <mmsystem.h>

WAVEFORMATEX wfx =
{
    WAVE_FORMAT_PCM, // 音频的类型 (PCM)
    1,               // 声道数
    8000,            // 采样率 (每秒的样本数)
    16,              // 每个采样点的位数 (16 位)
    2,               // 数据块的对齐方式
    0,               // 数据块的附加信息
    0                // 保留的信息
};

const int SAMPLE_RATE = 8000; // WAV格式的采样率
const int AMPLITUDE = 32000; // 当前音乐的最大振幅

const double FREQ_C4 = 261.63; // 中央C音
const double FREQ_E4 = 329.63; // E音
const double FREQ_G4 = 392.00; // G音

const int DURATION_QUARTER_NOTE = 500; // 四分音符长
const int DURATION_HALF_NOTE = 2 * DURATION_QUARTER_NOTE; // 二分音符长
const int DURATION_WHOLE_NOTE = 4 * DURATION_QUARTER_NOTE; // 全音符长

HWAVEOUT hWaveOut; // 这是音频设备的句柄

// 生成数据块
void GenerateSoundData(std::vector<short>& data, double frequency, int duration)
{
    const int numSamples = SAMPLE_RATE * duration / 1000;
    const double delta = 2 * M_PI * frequency / SAMPLE_RATE;

    data.resize(numSamples);

    for (int i = 0; i < numSamples; i++)
    {
        short sample = (short)(AMPLITUDE * sin(i * delta));
        data[i] = sample;
    }
}

// 播放音符
void PlayNote(double frequency, int duration)
{
    std::vector<short> data;
    GenerateSoundData(data, frequency, duration);

    // 创建声音数据块
    WAVEHDR header;
    memset(&header, 0, sizeof(header));

    header.lpData = (LPSTR)data.data();
    header.dwBufferLength = data.size() * sizeof(short);

    // 准备音频数据块
    waveOutPrepareHeader(hWaveOut, &header, sizeof(header));

    // 发送音频数据块到设备来播放声音
    waveOutWrite(hWaveOut, &header, sizeof(header));

    // 等待音频数据块完成
    while (header.dwFlags & WHDR_BUSY)
        Sleep(10);

    // 释放音频数据块
    waveOutUnprepareHeader(hWaveOut, &header, sizeof(header));
}

// 将音高转换为符号
std::string PitchToSymbol(double pitch)
{
    int semitones = (int)round(12 * log(pitch / FREQ_C4) / log(2));

    switch (semitones)
    {
    case 0: return "do";
    case 2: return "re";
    case 4: return "mi";
    case 5: return "fa";
    case 7: return "so";
    case 9: return "la";
    case 11: return "xi";
    default: return "-";
    }
}

// 将持续时间转换为符号
std::string DurationToSymbol(int duration)
{
    if (duration == DURATION_QUARTER_NOTE) return "4";
    if (duration == DURATION_HALF_NOTE) return "2";
    if (duration == DURATION_WHOLE_NOTE) return "1";
    else return "";
}

// 播放键盘音符序列
void PlayKeyboardNotes(const std::vector<double>& notes, const std::vector<int>& durations)
{
    std::cout << "\n开始弹奏\n";

    for (int i = 0; i < notes.size(); i++)
    {
        std::string pitchSymbol = PitchToSymbol(notes[i]);
        std::string durationSymbol = DurationToSymbol(durations[i]);

        std::cout << pitchSymbol << durationSymbol << "\n";

        PlayNote(notes[i], durations[i]);
        Sleep(durations[i]);
    }

    std::cout << "\n结束弹奏\n";
}

// 测试代码
int main()
{
    // 初始化音频设备
    waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);

    // C(四分音符)
    // E(四分音符)
    // G(四分音符)
    std::vector<double> notes = { FREQ_C4, FREQ_E4, FREQ_G4 };
    std::vector<int> durations = { DURATION_QUARTER_NOTE, DURATION_QUARTER_NOTE, DURATION_QUARTER_NOTE };

    PlayKeyboardNotes(notes, durations);

    // 释放音频设备
    waveOutClose(hWaveOut);

    return 0;
}
总结: 通过使用Win32 API的Waveform Audio接口,我们可以使用C ++语言在Windows环境下编写键盘提示音程序。程序的关键部分包括初始化音频设备、生成声音数据块、播放声音和将音高和持续时间转换为可读的符号。我们可以根据需要自由发挥,创建不同的音符序列和节奏组合,从而实现不同的歌曲。