C#: Produce a continuous tone until interrupted

mmr picture mmr · Jul 28, 2009 · Viewed 8.7k times · Source

I have a device that a user interacts with alongside of a C# WPF program. This program must beep when the user presses a button on the device, for either a specified amount of time or for as long as the user presses the button, whichever is shorter. The only speaker available to produce the beep/tone is the computer BIOS speaker; we cannot assume that other speakers are around (and it's actually safe to assume that there won't be any other speakers).

How can I produce a continuous tone for the necessary duration?

What I have so far produces lots of beeps, but not a continuous tone.

First, a thread is started:

    private void UserControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) {
        if(this.Visibility == Visibility.Visible){
            mBeepThread = new Thread(new ThreadStart(ProduceTone));
            mBeepThread.Name = "Beep Thread";
            mBeepThread.Start();
        }
    }

The thread itself:

    bool mMakeTone = false;
    private void ProduceTone(){
        while(this.Visibility == Visibility.Visible){
            if(mMakeTone ){
                Console.Beep();
            }
            else{
                Thread.Sleep(10);
            }
        }
    }

And then the mMakeTone boolean is flipped to true during the duration of the button press, up to a time specified by the device itself.

I suspect that it's just a quick change to the Console.Beep() line above, but I'm not sure what it would be.

Answer

Michael A. McCloskey picture Michael A. McCloskey · Jul 28, 2009

There is an overload for Console.Beep that takes a frequency and duration, which is useful if you want to produce a sound for a specified duration. The ability to turn on a sound and then later turn it off using the BIOS speaker is not directly supported by the Console.Beep method, or any other API that I am aware of, without perhaps installing a fake sound card driver.

A little experimentation has discovered the following:

  • Console.Beep() is synchronous and does not return until the sound is finished.
  • Calling Console.Beep() from a separate thread interrupts whatever is currently in progress.

So... You should be able to accomplish your task by creating a new background thread to call Console.Beep() with the frequency of your choosing and a very long duration. To later terminate the sound, you may just call Console.Beep() on your main thread with any frequency and an extremely, non-zero duration (e.g. 1) to terminate the sound playing from the background thread. You should not make the sound playing on the background thread any longer that a reasonable duration because the thread will live until the sound would have ordinarily stopped playing and you don't want a lot of background threads piling up on you.

private void button1_Click(object sender, EventArgs e)
{
    // Starts beep on background thread
    Thread beepThread = new Thread(new ThreadStart(PlayBeep));
    beepThread.IsBackground = true;
    beepThread.Start();
}

private void button2_Click(object sender, EventArgs e)
{
    // Terminates beep from main thread
    Console.Beep(1000, 1);
}

private void PlayBeep()
{
    // Play 1000 Hz for 5 seconds
    Console.Beep(1000, 5000);
}