Aural Alert Generator (Voice and Tones)

Introduction

This article describes an application used to generate and test aural alerts; I wrote the application originally to allow me to define and test aural alerts for use in a couple of cockpit simulators, however, you could use the application to generate aural alerts for any purpose.  The application also may be used to define voice messages which may be optionally saved as wave files for subsequent use in other applications.  Tones may be defined but I did not provide an option to save the tones as wave files, however, there is an example of how to programmatically add the tones defined using the application included within the code.

If you don't need to generate tones for any specific purpose, the application is an excellent tool for annoying your friends, family, roommates, and co-workers.

Getting Started

In order to get started, unzip the included project and open the solution in the Visual Studio 2005 environment (the code was written in 2003 and it works fine in 2003 but I have updated it to 2005).  You will note that the project contains two significant files:  CannedWCAtones.cs and frmMain.cs.  The canned WCA tones class provides an example of how to programmatically add tones to a C# project while the form main class provides the GUI and the code necessary to drive the application.

To begin, you may not have the necessary references on your machine as the application requires the installation of Microsoft's speech 5.1 SDK and the Microsoft sample TTS engine library.  These may be downloaded with the SDK at no cost from this URL:

Speech 5.1 SDK: 

http://www.microsoft.com/downloads/details.aspx?FamilyId=5E86EC97-40A7-453F-B0EE-6583171B4530&displaylang=en

You may also obtain a couple of additional voices (the SDK includes Microsoft Mary, Microsoft Mike, and Microsoft Sam) by downloading and the Microsoft Reader and additional TTS components found on this URL: (not required, but you will gain two additional voices if you do add these to your system)

http://www.microsoft.com/reader/downloads/default.asp

You do not need to activate the reader for this to work, however, you can't install the additional voices unless you have the reader installed.

If you need to update the project references, do so prior to attempting to run the application.  Once you have installed the speech SDK, go back to the project and run a build.  If the references are absent, remove these (highlighted) references: (Figure 1)

tonealert1.gif

Figure 1:  Speech Related Project References

After removing the old references, right click on the project and select "Add Reference".  Once the dialog opens, select the COM tab (then go get a cup of coffee while it takes forever to load) and when you get back, look for and add these two references (figures 2 and 3):

tonealert2.gif

Figure 2:  Add the Microsoft Speech Object Library Reference

tonealert3.gif

Figure 3:  Add the Sample TTS Engine Type Library Reference

Having added these references, go ahead and do a build to see if anything else is missing.  If anything turns up, add the missing project references in the same manner and build again.  Once you have a  good build, go ahead and run the application.  On start, you will see this form appear:

tonealert4.gif

Figure 4:  The main form of the Tone Generator Application

Looking at the form note that it contains a tabbed panel with four tabs.  The first tab is used to define a sine wave tone.  To try it out, key in 700 for the start frequency, key in 1700 for the end frequency, key in 85 for the duration of the tone in milliseconds, key in 20 for the steps (this defines how many steps the frequency will be divided into between the start and end frequencies, a small number makes the tone choppy, a larger value makes it smoother, too large a number and it slows it down), key in 15 for the dwell (how long the tone is rested between repetitions, and key in 20 for the number of repetitions.  Click on "Play Tone" and you should be listening to a MIL-STD-411 Warning tone similar to what a military pilot would hear during an emergency.

Next, click on the Saw Tooth tab.  The dialog box will show these control options:  (figure 5)

tonealert5.gif

Figure 5:  Saw Tooth Tone Definition Options

Key in the values indicated in the figure and click on "Play Tone", you should hear something that sounds like a cheap telephone ringing.  This definition is used to bounce two tones off one another, the first frequency is played first (oddly enough) and its duty cycle in this case is set to 50:50.  The second tone's frequency and duty cycle are defined next, and lastly the number of repetitions is set.  You can play around with the values and note the impact on the tone.

Next, click on the Presets tab.  This update the dialog to this configuration:  (Figure 6)

tonealert6.gif

Figure 6:  Preset Tones

The presets are tones link to tones defined programmatically and stored in the Canned WCA class file.  Pop open the combo box, select a tone and hit the "Play Preset" button to listen to one of these tones.

The last tab to look at is the Voice tab.  Click on Voice and examine the dialog:  (figure 7)

tonealert7.gif

Figure 7:  Voice Dialog Options

To give this option a try, key some text into the message area, select a speaker from the combo box, and hit the play tone button.  If you click on the Save to WAV file checkbox, you will be prompted to define a storage location for the wave file generated by voice synthesis.  Note that you may use punctuation to alter the tempo of the voice's playback, for example, "ENGINE FIRE! LEFT!" will not play the same as "ENGINE FIRE LEFT" or "ENGINE FIRE, LEFT".  I have found that misspelling some of the words is a useful way to alter the playback to sound more human so if you care to try that out you will find that "ENGIN FIRE LEFT" does not sound the same as "ENGINE FIRE LEFT".  To try this out, key this phrase into the message text box and click "Play Tone", "japanese,,,,,jaupa-knees".

Close the application and let's take a look at the code driving it.

The Code for frmMain.cs.

First, take a look at the references in the class, they are as follows:

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
    {

Note that the project is using "SAMPLETTSENGLib" and "SpeechLib"; these are based upon the speech related references added earlier in the project; check the used libraries and verify that you have each  of those listed.  Following the references is a standard namespace and class definition.  Note the clever use of Form1 as a class name; you might want to be a little more creative than that.

Next up is an important item.  Following the class definition, note the following code:

[DllImport("kernel32.dll")]
private static extern bool Beep( int frequency, int duration );

This code is critical to the operation of the application as the entire shooting match is largely based on manipulating the old "Beep" call from the kernel32 DLL.  This reference is added into the project with this DLL import.  Also note that "Beep" accepts two arguments, frequency and duration.  Aside from using the "Beep" call in this application, you could do something interesting like build yourself a piano keyboard using the same call.

Following the DLL import, note the following code:

private SpeechVoiceSpeakFlags SpFlags = SpeechVoiceSpeakFlags.SVSFlagsAsync;
private SpVoice Voice = new SpVoice();

The second line of this snippet is creating an instance of a voice, the first line is setting the speak flag (speech mode) to one of the options in the SpeechVoiceSpeakFlags enumeration.  In this case, it is set to the asynchronous mode.  The enumeration contains the following options:

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,
}

The next thing worth looking at is initialization, scroll down until to you find this code:

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.");
    } 
}

Following the Initialize component call, the application checks for existing voices on the machine and it adds any it finds to the Voice tab's Speaker combo box.  If no voices are found, the user is alerted to the absence of voice capability.

The next function of interest is the call to play the sine wave tones from the first tab; this function is the button click event handler for the "Play Tone" button the Sine tab.  The code looks like this:

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");
    }
}

In this section of code, the sine tab's values are collected from the text boxes, converted to integer values and used to set a few local variables used within the function.  After the variables are set, the code determines the difference between the high and low ends of the sweep and assigns that value to an integer variable.  This variable is divided by the duration in order to derive the amount of frequency change added to each step's increase in the frequency value.

Following this set up, the function loops through the specified number of repetitions and on each repetition, the tone is set back to the original value and then the steps are looped and the Beep function is called with the current frequency and duration specified.  The current frequency value is then increased by the frequency difference calculated for each step.

Lastly the function rests by calling thread sleep set the current dwell value in milliseconds; here I am just using the tread sleep call for rough timing.  This will insert the dwell time before the next repetition of the tone is initiated. 

The next bit of code worth looking at is used to play the saw tooth form tone, it is as follows:

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");
    }
}

This code works in a manner consistent with the approach applied to the sine wave tone; the only difference being that the function loops through each of the specified repetitions and plays the two frequencies entered back to back with the dwell time separating them.

Next up is the code used to both save and play the synthesized voice, that code looks like this:

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);
    }
}

This code first looks to see if the user has checked the "Save to WAV file" checkbox and, if they have, it opens a file save dialog used to capture the file name and location the user would like to use to save the wave file.  After that the function saves the audio output to the specified file location.

If the user did not check the "Save to WAV file" checkbox, the function merely plays the voice message.

Lastly, if you take a look at the CannedWCAtones.cs file, you will see examples used to generate tones programmatically.   In these examples, the functions work exactly like those mentioned previously, however, when the function is initialized, the function sets all of its internal values to those hard coded into the application rather than setting the variables to some user specified value.

Conclusion

That is all there is to it; you may wish to examine the project files to take a look at the rest of the code in context but the relevant points have all been addressed in the document.


Similar Articles