C# 语音和音调警报生成器






4.70/5 (21投票s)
2006 年 8 月 25 日
9分钟阅读

160602

4333
本文介绍了一个用于生成和测试声音警报的应用程序。
引言
本文介绍了一个用于生成和测试声音警报的应用程序;我最初编写该应用程序是为了能够定义和测试驾驶舱模拟器中使用的声音警报。但是,您也可以将该应用程序用于生成任何用途的声音警报。该应用程序还可用于定义语音消息,这些消息可以选择性地保存为 Wave 文件,以便在其他应用程序中使用。可以定义音调,但我没有提供将音调保存为 Wave 文件的选项;但是,代码中包含了一个关于如何以编程方式添加使用该应用程序定义的音调的示例。
如果您不需要为任何特定目的生成音调,该应用程序是一个打扰您的朋友、家人、室友和同事的绝佳工具。
入门
要开始使用,请解压缩随附的项目,然后在 Visual Studio 2005 环境中打开解决方案(代码是在 2003 年编写的,在 2003 年也能正常运行,但我已将其更新到 2005)。您会注意到该项目包含两个重要文件:CannedWCAtones.cs 和 frmMain.cs。CannedWCAtones
类提供了一个关于如何以编程方式向 C# 项目添加音调的示例,而 frmMain
类提供了驱动应用程序所需的 GUI 和代码。
首先,您可能没有必要的引用,因为该应用程序需要安装 Microsoft 的 Speech 5.1 SDK 和 Microsoft 示例 TTS 引擎库。这些可以免费与 SDK 一起下载,网址如下:
您还可以通过下载 Microsoft Reader 和其他 TTS 组件获得几个额外的声音(SDK 包括 Microsoft Mary、Microsoft Mike 和 Microsoft Sam),这些组件可在 此处找到(不是必需的,但如果您将它们添加到系统中,将获得两个额外的声音)。
您无需激活阅读器即可使其正常工作,但是,除非安装了阅读器,否则您无法安装额外的声音。
如果您需要更新项目引用,请在尝试运行应用程序之前进行更新。安装 Speech SDK 后,请返回项目并运行生成。如果引用缺失,请删除这些(已突出显示的)引用:(图 1)
图 1:与语音相关的项目引用
删除旧引用后,右键单击项目并选择“添加引用”。对话框打开后,选择 COM 选项卡(然后去喝杯咖啡,因为它需要很长时间加载,回来后),查找并添加这两个引用(图 2 和 3)。
图 2:添加 Microsoft Speech Object Library 引用
图 3:添加示例 TTS 引擎类型库引用
添加这些引用后,继续执行生成以查看是否还有其他缺失项。如果出现任何问题,请以相同的方式添加缺失的项目引用,然后再次生成。生成成功后,即可运行应用程序。启动时,您将看到此表单出现
图 4:音调生成器应用程序的主窗体
查看表单,请注意它包含一个带四个选项卡的选项卡式面板。第一个选项卡用于定义正弦波音调。要尝试一下,请输入 700 作为起始频率,输入 1700 作为结束频率,输入 85 作为音调的持续时间(以毫秒为单位),输入 20 作为步长(这定义了频率在起始频率和结束频率之间将被划分成的步数;较小的数字会使音调变得断断续续,较大的值会使其更平滑,过大的数字会减慢速度),输入 15 作为停顿时间(重复之间音调休息的时间),输入 20 作为重复次数。单击“播放音调”,您应该能听到类似于军事飞行员在紧急情况下听到的 MIL-STD-411 警告音。
接下来,单击“锯齿波”选项卡。对话框将显示这些控件选项:(图 5)
图 5:锯齿波音调定义选项
输入图中所示的值,然后单击“播放音调”,您应该会听到类似廉价电话铃声的声音。此定义用于使两个音调相互反弹。首先播放第一个频率(毫不奇怪),其占空比设置为 50:50。接下来定义第二个音调的频率和占空比,最后设置重复次数。您可以尝试更改这些值,并注意它们对音调的影响。
接下来,单击“预设”选项卡。这将使对话框更新为以下配置:(图 6)
图 6:预设音调
预设音调是与 Canned WCA 类文件中以编程方式定义和存储的音调相关联的。打开组合框,选择一个音调,然后按“播放预设”按钮来收听其中一个音调。
要查看的最后一个选项卡是“语音”选项卡。单击“语音”,然后检查对话框:(图 7)
图 7:语音对话框选项
要尝试此选项,请在消息区域中键入一些文本,从组合框中选择一个扬声器,然后按“播放音调”按钮。如果单击“保存到 WAV 文件”复选框,系统将提示您定义 Wave 文件生成的存储位置。请注意,您可以使用标点符号来改变语音播放的节奏;例如,“ENGINE FIRE! LEFT!”的播放方式与“ENGINE FIRE LEFT”或“ENGINE FIRE, LEFT”不同。我发现拼错一些单词是改变播放效果使其听起来更自然的有用方法,因此如果您想尝试一下,您会发现“ENGIN FIRE LEFT”的播放方式与“ENGINE FIRE LEFT”不同。要尝试此操作,请在消息文本框中键入“japanese,,,,,jaupa-knees”,然后单击“播放音调”。
关闭应用程序,让我们看一下驱动它的代码。
frmMain.cs 的代码
首先,看一下类中的引用,它们如下:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;
using SAMPLETTSENGLib;
using System.Threading;
using SpeechLib;
namespace ToneGenerator
{
/// <summary>
/// This application is used to define and test tones for use in the
/// warning/caution/advisory system. It supports sine and sawtooth
/// wave forms and can be used to define tones in accordance with
/// MIL-STD-411F (or other non-standardized aural alerts)
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
请注意,项目正在使用“SAMPLETTSENGLib
”和“SpeechLib
”;这些是基于项目早期添加的语音相关引用。检查已使用的库并确认您已列出其中每个库。在引用之后是标准的命名空间和类定义。请注意 Form1
作为类名的巧妙用法;您可能希望在此方面更有创意一些。
接下来是重要的一项。在类定义之后,请注意以下代码:
[DllImport("kernel32.dll")]
private static extern bool Beep( int frequency, int duration );
这段代码对应用程序的运行至关重要,因为整个过程在很大程度上基于操作 Kernel32 DLL 的旧“Beep
”调用。通过此 DLL 导入将此引用添加到项目中。另外请注意,“Beep
”接受两个参数:频率和持续时间。除了在此应用程序中使用“Beep
”调用之外,您还可以做一些有趣的事情,例如使用相同的调用来构建钢琴键盘。
在 DLL 导入之后,请注意以下代码:
private SpeechVoiceSpeakFlags SpFlags = SpeechVoiceSpeakFlags.SVSFlagsAsync;
private SpVoice Voice = new SpVoice();
此代码片段中的第二行创建了一个语音实例。第一行将语音标志(语音模式)设置为 SpeechVoiceSpeakFlags
枚举中的一个选项。在此情况下,它设置为异步模式。该枚举包含以下选项:
public enum SpeechVoiceSpeakFlags
{
SVSFUnusedFlags = -128,
SVSFDefault = 0,
SVSFlagsAsync = 1,
SVSFPurgeBeforeSpeak = 2,
SVSFIsFilename = 4,
SVSFIsXML = 8,
SVSFIsNotXML = 16,
SVSFPersistXML = 32,
SVSFNLPMask = 64,
SVSFNLPSpeakPunc = 64,
SVSFVoiceMask = 127,
}
}
接下来值得关注的是初始化,向下滚动直到找到此代码:
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
// Get System Voices
string strVoice;
foreach (SpeechLib.ISpeechObjectToken sot in Voice.GetVoices("", "") )
{
strVoice = sot.GetDescription(0); //'The token's name
cboVox.Items.Add(strVoice);
}
if (cboVox.Items.Count <= 0)
{
MessageBox.Show(this, "This system does not " +
"contain Text-to-Speech capability.");
}
}
在调用 InitializeComponent
之后,应用程序会检查机器上是否存在现有语音,并将其添加到语音选项卡的扬声器组合框中。如果未找到语音,则会向用户发出语音功能缺失的警报。
下一个感兴趣的函数是从第一个选项卡播放正弦波音调的调用;此函数是正弦选项卡中“播放音调”按钮的单击事件处理程序。代码如下:
private void btnPlayAdhoc_Click(object sender, System.EventArgs e)
{
try
{
// Set vars for sine wave tone
int startFreq = Convert.ToInt32(txtStartFreq.Text);
int endFreq = Convert.ToInt32(txtEndFreq.Text);
int duration = Convert.ToInt32(txtDuration.Text);
int dwell = Convert.ToInt32(txtDwell.Text);
int steps = Convert.ToInt32(txtSteps.Text);
int reps = Convert.ToInt32(txtRepetitions.Text);
int diff = Math.Abs(startFreq - endFreq);
diff = Convert.ToInt32(diff/duration);
for (int rep=0; rep<reps; rep++)
{
// tone
int CurrentFreq = startFreq;
for(int i=0; i<steps-1; i++)
{
Beep(CurrentFreq, Convert.ToInt32(duration/steps));
CurrentFreq = CurrentFreq + diff;
}
// dwell
Thread.Sleep(dwell);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString(), "Error");
}
}
在此代码段中,从文本框中收集正弦波选项卡的值,将其转换为整数值,并用于设置函数中使用的一些局部变量。在设置变量后,代码确定了扫描的高低端之间的差值,并将该值赋给一个整数变量。该变量除以持续时间,以得出在频率值每次增加的步长中添加的频率变化量。
在设置好之后,函数循环指定的重复次数,每次重复时,音调都会恢复到原始值,然后循环步长,并使用当前指定的频率和持续时间调用 Beep
函数。然后,当前频率值将增加为每个步长计算出的频率差。
最后,函数通过调用线程休眠来休息,以指定的毫秒数为当前停顿值;我这里只是使用线程休眠调用来进行粗略计时。这将插入停顿时间,然后才会启动下一次音调重复。
下一个值得关注的代码块用于播放锯齿波形式的音调,如下所示:
private void btnPlaySawtooth_Click(object sender, System.EventArgs e)
{
try
{
int freq1 = Convert.ToInt32(txtFreq1.Text);
int duration1 = Convert.ToInt32(txtDuration1.Text);
int dwell1 = Convert.ToInt32(txtDwell1.Text);
int freq2 = Convert.ToInt32(txtFreq2.Text);
int duration2 = Convert.ToInt32(txtDuration2.Text);
int dwell2 = Convert.ToInt32(txtDwell2.Text);
int reps = Convert.ToInt32(txtSawToothReps.Text);
for (int i = 0; i < reps; i++)
{
Beep(freq1, duration1);
Thread.Sleep(dwell1);
Beep(freq2, duration2);
Thread.Sleep(dwell2);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString(), "Error");
}
}
此代码的工作方式与正弦波音调的方法一致;唯一不同的是,该函数循环指定的每次重复,并将输入的两个频率背靠背播放,中间由停顿时间分隔。
接下来是用于合成语音并保存语音的代码,该代码如下:
private void btnPlayVox_Click(object sender, System.EventArgs e)
{
try
{
if (chkSaveToWavFile.Checked)
{
Voice.Speak(txtSpeakText.Text, SpFlags);
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "All files (*.*)|*.*|wav files (*.wav)|*.wav";
sfd.Title = "Save to a wave file";
sfd.FilterIndex = 2;
sfd.RestoreDirectory = true;
if (sfd.ShowDialog()== DialogResult.OK)
{
SpeechStreamFileMode SpFileMode =
SpeechStreamFileMode.SSFMCreateForWrite;
SpFileStream SpFileStream = new SpFileStream();
SpFileStream.Open(sfd.FileName, SpFileMode, false);
Voice.AudioOutputStream = SpFileStream;
Voice.Speak(txtSpeakText.Text, SpFlags);
Voice.WaitUntilDone(Timeout.Infinite);
SpFileStream.Close();
}
}
else
{
try
{
Voice.Speak(txtSpeakText.Text, SpFlags);
}
catch (System.Exception ex)
{
MessageBox.Show(this, ex.Message.ToString() +
ex.StackTrace.ToString());
}
}
}
catch(Exception error)
{
MessageBox.Show(error.Message.ToString(), "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
此代码首先检查用户是否勾选了“保存到 WAV 文件”复选框,如果勾选了,则会打开一个文件保存对话框,用于捕获用户希望保存 Wave 文件的文件名和位置。之后,该函数将音频输出保存到指定的文件位置。
如果用户未勾选“保存到 WAV 文件”复选框,则该函数仅播放语音消息。
最后,如果您查看 CannedWCAtones.cs 文件,您将看到用于以编程方式生成音调的示例。在这些示例中,函数的工作方式与前面提到的函数完全相同;但是,在初始化函数时,函数会将所有内部值设置为应用程序中硬编码的值,而不是将变量设置为用户指定的某个值。
结论
就是这样;您可能希望检查项目文件以在上下文中查看其余代码,但本文档中已涵盖了所有相关要点。