tl;dr version: How can I start playing a sound in the MouseDown event of a WinForms button and stop it from playing in the same button's MouseUp event?
Intermediate C# dev here and I've been trying to write a simple Simon clone.
I'm currently stuck trying to make it play the sound for the colored button only while the user is clicking the button. (I use "button" and "tile" interchangeably. Both refer to the colored button the user will press on the form)
I originally tried this:
public partial class frmMain : Form
{
private SoundPlayer soundPlayer;
private void btnGreenTile_MouseDown(object sender, MouseEventArgs e)
{
soundPlayer = new SoundPlayer(Properties.Resources.greenTileSound)
soundPlayer.Play();
}
private void btnGreenTile_MouseUp(object sender, MouseEventArgs e)
{
soundPlayer.Stop();
}
}
But this didn't stop the sound because the MouseUp event didn't fire due to MouseDown not being finished (still playing the sound which is like 5 seconds long in case someone holds the button for longer than a simple click). As mentioned by Luis Tellez in the comments, SoundPlayer plays the sound on it a new thread...so I have no idea why this code doesn't work now.
So I looked into multithreading and tried this:
public partial class frmMain : Form
{
private Thread soundThread = new Thread(new ParameterizedThreadStart(PlaySound));
// Create stream objects for each sound (needed to allow SoundPlayer to use Resources)
private Stream greenTileSound = Properties.Resources.greenTilePress;
private Stream redTileSound = Properties.Resources.redTilePress;
private Stream yellowTileSound = Properties.Resources.yellowTilePress;
private Stream blueTileSound = Properties.Resources.blueTilePress;
public frmMain()
{
InitializeComponent();
}
private void btnGreenTile_MouseDown(object sender, MouseEventArgs e)
{
soundThread.Start(greenTileSound);
}
private void btnGreenTile_MouseUp(object sender, MouseEventArgs e)
{
soundThread.Abort();
}
// Have to use object as parameter because ParamterizedThreadStart() only takes object arguments
private static void PlaySound(object soundToPlay)
{
SoundPlayer soundPlayer = new SoundPlayer((Stream)soundToPlay);
soundPlayer.Play();
}
}
With the above code, it does not stop playing the sound on MouseUp, and even better it throws a ThreadStateException with the message "Thread is running or terminated; it cannot restart."
As you can probably tell, I only just learned about multithreading while trying to write this code. I have to use ParameterizedThreadStart because the method it calls when the thread starts, PlaySound(), needs to pass a parameter to soundPlayer with the resource corresponding to the colored button that was pressed by the player (a .wav file).
I then thought maybe I should try using soundThread.Suspend() instead of soundThread.Abort() but Suspend is deprecated...
Can anyone point me in the right direction to get the sound to stop on MouseUp? Do I need to work with multithreading? I think my problem just comes down to logic but I am fully stuck. Thank you for any and all help! :)
As a side note, I'm kind of surprised that this question or something similiar has not been asked yet (at least I couldn't find it with google searches or stackExchange searches).
You can see in the documentation that the play() method run in other thread, you can do something else after it and it should run, so your problem with the first approach is something different from what you were thinking.
The Play method plays the sound using a new thread. If you call Play
before the .wav file has been loaded into memory, the .wav file will
be loaded before playback starts. You can use the LoadAsync or Load
method to load the .wav file to memory in advance. After a .wav file
is successfully loaded from a Stream or URL, future calls to playback
methods for the SoundPlayer will not need to reload the .wav file
until the path for the sound changes. If the .wav file has not been
specified or it fails to load, the Play method will play the default
beep sound.
http://msdn.microsoft.com/en-us/library/system.media.soundplayer.play%28v=vs.110%29.aspx
Related
So I am using a win form application using the latest framework for c#.
Here I have a button that plays a sound (asterisk) and then after 5 secs or 20 secs(I let the user pick) I want it to play a different sound (beep in this case) But when it calls the code to play beep the sound I hear is Asterisk.
finally at the end I want another sound to signal that it has finished. I have chosen (Exclamation) but again I hear asterisk.
Does anyone know why I would only hear the one sound?
P.S. I have tried switching the start sound to beep and the "next" sound to asterisk. I clean and rebuild but then the only sound I hear is a beep.
Here is the code behind for the button click
private void btnStart_Click(object sender, EventArgs e)
{
BusinessLayer.ToneApp.PlaySound play = new BusinessLayer.ToneApp.PlaySound();
_countDown = play.IsValid(txtTime.Text);
numreps = play.IsValid(txtReps.Text);
numsets = play.IsValid(txtSets.Text);
lblFinished.Text = "";
play.PlaySoundStart();
_timer = new System.Windows.Forms.Timer();
_timer.Enabled = true;
_timer.Tick += new EventHandler(timer1_Tick);
_timer.Interval = 1000;
_timer.Start();
}
in the timer1_tick event I have
private void timer1_Tick(object sender, EventArgs e)
{
BusinessLayer.ToneApp.PlaySound play = new BusinessLayer.ToneApp.PlaySound();
_countDown--;
if (_countDown < 1)
{
//my code
play.PlaySoundNext();
}
}
and finally here is my playsound
public class PlaySound
{
public void PlaySoundStart()
{
System.Media.SystemSounds.Asterisk.Play();
}
public void PlaySoundNext()
{
System.Media.SystemSounds.Beep.Play();
}
public void PlaySoundStop()
{
System.Media.SystemSounds.Exclamation.Play();
}
public int IsValid(string textToInt)
{
int.TryParse(textToInt, out int timeInSeconds);
return timeInSeconds;
}
}
You should not expect SystemSounds to have any particular sound, because the user can change them. Call Beep or Asterisk to signal something or bring more attention to something. I think Hand is meant to signal an event where something stops unexpectedly or in an unusual way. There is a broader answer regarding this, which also points out the possibility of adding your own system sounds into Windows.
It seems that on latest versions of Windows, the default sounds for Asterisk, Beep and Exclamation are all the same sound. I checked on Windows 10, version 1909. Hand is different and Question has no sound at all. I guess it was made this way, because the first three were used interchangeably due to lack of clear distinction of when it is appropriate to use each of them. And the Question sound probably got removed, because prompts with questions are annoying enough even without the sound.
Custom sounds
You can embed your own sounds into the app. For that you can check out System.Media.SoundPlayer. Embedded resources can be loaded using Assembly.GetManifestResourceStream. Alternatively the sound file can be added to a resources file and accessed though an auto-generated class.
This question already has answers here:
How to use System.Media.SoundPlayer to asynchronously play a sound file?
(2 answers)
Closed 5 years ago.
I've been trying to get this button to do what I want for a little now. I want it to play a sound when I press it, which is what I managed to do. However, its like the application freezes every time you press the button, giving the sound all of its attention, etc. So basically my goal is to make the button play a sound without making the UI have to stop and allow it to play, before moving on. I also would like to know if there is a way to make a button play sound when pressed, but when pressed again the current sound is stopped and plays again, to prevent it from playing "X" amount of clicks you clicked the button, etc.
Here is my code:
public static void ButtonSound()
{
SoundPlayer _sound = new SoundPlayer();
try
{
_sound.Stop();
_sound.SoundLocation = path + "ButtonTap.wav";
_sound.Load();
_sound.PlaySync();
}
catch
{
}
finally
{
_sound.Dispose();
}
}
And Button code:
private void Button()
{
SoundPlayers.ButtonSound();
}
Note, I have my SoundPlayer in another class, and I am using DelegateCommands to reference my Button() method, etc. I am also using .wav files. Also, is there a more efficient way to achieve that task I am trying to accomplish? If you need anything else, just ask.
Thanks!
Your UI is freezing because that is what you are asking for when you call _sound.PlaySync().
Looking at the SoundPlayer documentation, you could use the Play() method:
Plays the .wav file using a new thread, and loads the .wav file first
if it has not been loaded.
Using this method should also solve the problem of playing the sound repeatedly, since you will no longer be queuing your calls.
Also, what you did there with Try-Finally-Dispose can be simplified with using, so your code would look like this:
public static void ButtonSound()
{
using (var _sound = new SoundPlayer())
{
_sound.SoundLocation = path + "ButtonTap.wav";
_sound.Play();
}
}
Note that your _sound.Stop() doesn't make sense, since you are calling it on a new object, but just calling Play() on a new object will make the old sound stop and then play the new one.
Also, the doc says that Play() loads the file if it has not been loaded, that is why I skipped the _sound.Load() call.
I'm working on a music player, it finds from web, downloads the bytes async, plays it, and when the program closes, it deletes all downloaded musics. So, it downloads temporarily.
I want to play the music while it was downloading. i guess it can be done from
private static void Downloader_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
}
this but this e only shows the percentage, not downloaded bytes. It's something like loading a video from youtube, you can watch it even not fully loaded. I trying to make it happen with musics.
I am creating a game in WPF. To manage sounds I've created a AudioController, which will create MediaPlayers and manage playing, stopping, looping, pausing, etc. I have the base of it working. If i'm in a control, I can call
AudioController.Play("MySound");
And it will decide if it has a MediaPlayer for that sound already, if not it will create one.
A problem appears in the following situations:
Task.Run(() => { DoSomething(); AudioController.Play("MySound"); });
_timer_tick(object sender...) { AudioController.Play("MySound"); }
When using a Task my Sound will play properly, but the MediaEnded event never gets fired, causing me not to be able to manage the MediaPlayers properly.
With the Timer Tick, the Sound will play the first time, but never again, even though its using a new MediaPlayer and also never fires the MediaEnded event
I've tried Dispatching the calls, but it doesn't seem to make a difference.
I can only imagine the problems lies with the idea that the Play() call is being sent from a different thread.
public static void Play(string sound)
{
Debug.WriteLine("Publish called on Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
if (!Application.Current.CheckAccess())
{
Application.Current.Dispatcher.Invoke(() => Play(sound, TimeSpan.Zero), DispatcherPriority.Normal);
return;
}
Play(sound, TimeSpan.Zero);
}
private static void Play(string sound, TimeSpan position)
{
//...some preemptive code that's just searching a list
//reuse
if (ap == null)
{
ap = new AudioPlayer(entry);
_players.Add(ap);
}
ap.Play(position);
}
Note: AudioPlayer is a wrapper class around MediaPlayer so that I can add some additional properties such as LastActive, IsLooping, and handles replaying a sound by calling:
_player.Stop();
_player.Position = position;
_player.Play();
Update:
I just decided to run a different sound in the above scenario's and everything seems to work fine. The only difference between the audio's is that one is 40kb and the other 67kb. Could it be that the file is just so small MediaPlayer doesn't register the NaturalDuration and therefore assumes its 0 which stops the events from being triggered?
I am currently implementing my final year project as a Music Software Development student.
I am making a software that plays a midi track that is generated earlier in the process.
In my interface, there is a button "play" and a button "stop".
When I click "play", a loop is called that plays this track. In this loop, I call a function from an instance of an other class.
What I would like is that when the user press "pause", the loop is temporary stopped, and when he press "pause" again, it goes back exactly where it was in the loop.
So far, when I press play, the track plays exactly how I want to, but I can't use any other element of my window until the loop is not completly processed.
Here is my code.
private void playStopButton_Click(object sender, RoutedEventArgs e)
{
// Initialising my output midi devices
outDevice.Send(new ChannelMessage(ChannelCommand.ProgramChange, information.bassChannel, information.bassSound));//bass
outDevice.Send(new ChannelMessage(ChannelCommand.ProgramChange, information.guitarChannel, information.guitarSound));//guitar
for (int barNum = 0; barNum < Globals.numberOfBars; barNum++)
{
// playing the first beat
rythmicPattern.play(0, ref outDevice, information, Globals.chordProgression.Bars[barNum].Chord);
//second beat
rythmicPattern.play(1, ref outDevice, information, Globals.chordProgression.Bars[barNum].Chord);
//thrid beat
rythmicPattern.play(2, ref outDevice, information, Globals.chordProgression.Bars[barNum].Chord);
//fourth beat
rythmicPattern.play(3, ref outDevice, information, Globals.chordProgression.Bars[barNum].Chord);
}
}
private void playPauseButton_Click(object sender, RoutedEventArgs e)
{
// test if the track is being played
if (isPlaying)
rythmicPattern.Pause();
else
// if it was already paused, playing back from where I stopped
rythmicPattern.PlayAfterPause(beatNumber);
}
Thank you for your help.
Greg
You must use threading where you can play the sound in another thread, so you can execute pause method
Check this link Multi-threading in .NET: Introduction and suggestions