bti_admin
asked on
Do methods of multiple instances of a class share the same resources?
OK! Weird question.
I'm working on a program that is like a little music player. You press the keys, it makes instrument sounds. It's my first windows application, and my first attempt at OOP.
I've integrated a little looping feature that allows you to record what you're playing, loop it, and play over it. I have a looper class that captures which sounds are being played, and when, and then plays it back. Right now there are four loopers registered in the form1 class, and they all work just like they're supposed to... that is until you try to play back more than one at the same time. You turn one on, it works fine. You turn a second one on, and the first one turns off, and the second one plays. And, weirdly, if you turn the second one off, the first one comes back on. It's like they're masking each other in the order they're being played.
Anyway, I've been over the code over and over, and the only thing I can imagine is that one instance of the Looper.Play method is somehow stepping on the others, making them pause until it's done doing its thing. It that possible?
Thanks.
I'm working on a program that is like a little music player. You press the keys, it makes instrument sounds. It's my first windows application, and my first attempt at OOP.
I've integrated a little looping feature that allows you to record what you're playing, loop it, and play over it. I have a looper class that captures which sounds are being played, and when, and then plays it back. Right now there are four loopers registered in the form1 class, and they all work just like they're supposed to... that is until you try to play back more than one at the same time. You turn one on, it works fine. You turn a second one on, and the first one turns off, and the second one plays. And, weirdly, if you turn the second one off, the first one comes back on. It's like they're masking each other in the order they're being played.
Anyway, I've been over the code over and over, and the only thing I can imagine is that one instance of the Looper.Play method is somehow stepping on the others, making them pause until it's done doing its thing. It that possible?
Thanks.
It's probably has nothing to do with your class code and everything to do with the underlying code in whatever sound library or API you are using.
ASKER
Hey, thanks for the replies.
So, in response to your question, I am using Managed DirectX & DirectSound. But I'm not doing anything fancy with it.
Here's a further breakdown of how it works:
There's a Sound object that looks like this:
Public Class Sound
Private buf As SecondaryBuffer
Public keyList() As Keys
Public mutes As MuteGroup
Public hasMutes As Boolean = False
Sub New(ByVal path As String, ByRef dev As Device, ByVal triggerKeys() As Keys)
buf = New SecondaryBuffer(path, dev)
keyList = triggerKeys
End Sub
Public Sub AddMuteGroup(ByRef newMuteGroup As MuteGroup)
mutes = newMuteGroup
hasMutes = True
End Sub
Public Sub play()
If hasMutes Then
mutes.mute()
End If
stp()
buf.Play(0, BufferPlayFlags.Default)
End Sub
Public Sub stp()
If Not buf Is Nothing Then
If buf.PlayPosition > 0 Then
buf.Stop()
buf.SetCurrentPosition(0)
End If
End If
End Sub
End Class
Dev is just a DirectSound Device, which is declared and initialized in the form1 class.
So you can see, each sound can play only once at a time, you're correct. However, each sound can only interrupt itself, which might sound a little nasty, but wont's stop anything from playing, at least as far as the API goes.
NOW! I did a little more testing, and found that, if we have two loopers, looper1 and looper2, both containing a recording, and we start playing looper1, and then start playing looper2, looper1 isn't being maksked or muted, as I originally thought, but paused. It's being interrupted, and waiting for its turn, it seems. I feel like this has to be a quirk of the runtime environment, since nothing in my code provides for it.
I think it must have something to do with the way the loopers play back. When we're recording, we have two ArrayLists that get populated; timeSig gets a TimeSpan, which is the difference between Now and a startTime which is set when recording starts, and soundSig gets a reference to the Sound object played at that time.
When we play it back, we do this:
Sub Play()
If IsLocked Then
Dim numSigs As Integer = soundSig.Count
Dim thisSig As Integer = 0
IsPlaying = True
startTime = Now
Do While thisSig < numSigs
Do While startTime.Add(timeSig(this Sig)) >= Now
Application.DoEvents()
System.Threading.Thread.Sl eep(1)
If Not IsPlaying Then
Exit Do
End If
Loop
If Not IsPlaying Then
Exit Do
End If
soundSig(thisSig).Play()
thisSig = thisSig + 1
Loop
If Not IsPlaying Then
Exit Sub
End If
Play()
End If
End Sub
I suspect the culprit is how we get it to wait to play the next sound:
Application.DoEvents() 'Keeps the application responsive
System.Threading.Thread.Sl eep(1) 'Waits one millisecond before trying again
But I'm not sure. And I have no idea what to do about it, other than to "bounce" to loops into one.
Thanks!
-J
So, in response to your question, I am using Managed DirectX & DirectSound. But I'm not doing anything fancy with it.
Here's a further breakdown of how it works:
There's a Sound object that looks like this:
Public Class Sound
Private buf As SecondaryBuffer
Public keyList() As Keys
Public mutes As MuteGroup
Public hasMutes As Boolean = False
Sub New(ByVal path As String, ByRef dev As Device, ByVal triggerKeys() As Keys)
buf = New SecondaryBuffer(path, dev)
keyList = triggerKeys
End Sub
Public Sub AddMuteGroup(ByRef newMuteGroup As MuteGroup)
mutes = newMuteGroup
hasMutes = True
End Sub
Public Sub play()
If hasMutes Then
mutes.mute()
End If
stp()
buf.Play(0, BufferPlayFlags.Default)
End Sub
Public Sub stp()
If Not buf Is Nothing Then
If buf.PlayPosition > 0 Then
buf.Stop()
buf.SetCurrentPosition(0)
End If
End If
End Sub
End Class
Dev is just a DirectSound Device, which is declared and initialized in the form1 class.
So you can see, each sound can play only once at a time, you're correct. However, each sound can only interrupt itself, which might sound a little nasty, but wont's stop anything from playing, at least as far as the API goes.
NOW! I did a little more testing, and found that, if we have two loopers, looper1 and looper2, both containing a recording, and we start playing looper1, and then start playing looper2, looper1 isn't being maksked or muted, as I originally thought, but paused. It's being interrupted, and waiting for its turn, it seems. I feel like this has to be a quirk of the runtime environment, since nothing in my code provides for it.
I think it must have something to do with the way the loopers play back. When we're recording, we have two ArrayLists that get populated; timeSig gets a TimeSpan, which is the difference between Now and a startTime which is set when recording starts, and soundSig gets a reference to the Sound object played at that time.
When we play it back, we do this:
Sub Play()
If IsLocked Then
Dim numSigs As Integer = soundSig.Count
Dim thisSig As Integer = 0
IsPlaying = True
startTime = Now
Do While thisSig < numSigs
Do While startTime.Add(timeSig(this
Application.DoEvents()
System.Threading.Thread.Sl
If Not IsPlaying Then
Exit Do
End If
Loop
If Not IsPlaying Then
Exit Do
End If
soundSig(thisSig).Play()
thisSig = thisSig + 1
Loop
If Not IsPlaying Then
Exit Sub
End If
Play()
End If
End Sub
I suspect the culprit is how we get it to wait to play the next sound:
Application.DoEvents() 'Keeps the application responsive
System.Threading.Thread.Sl
But I'm not sure. And I have no idea what to do about it, other than to "bounce" to loops into one.
Thanks!
-J
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Looper.Play is called from a textBox KeyDown event, and also by itself (that's why it loops). They Play() you see above (the one containing the System.Threading.Sleep comman) is a method of the Looper class.
AND... I agree with you. I'm not sure how I got this idea, but I was thinking that new thread were somehow getting auto-generated, which, after some research, I see is not true AT ALL. So there you have it.
Anyway, I do believe you answered the question, explicity: YES, unless you specifically create a new thread in which to run a procedure, multiple instances of a class and its methods share the same resources, at least in terms of getting scheduled for processor time. Thanks.
You get the points. Let me know if you want them right now, or if you're interested in continuing this conversation.
-J
AND... I agree with you. I'm not sure how I got this idea, but I was thinking that new thread were somehow getting auto-generated, which, after some research, I see is not true AT ALL. So there you have it.
Anyway, I do believe you answered the question, explicity: YES, unless you specifically create a new thread in which to run a procedure, multiple instances of a class and its methods share the same resources, at least in terms of getting scheduled for processor time. Thanks.
You get the points. Let me know if you want them right now, or if you're interested in continuing this conversation.
-J
ASKER
IT'S ALIIIIIIVE!
Here's a follow up for those actually trying to gain some information by reading this question.
I had to add Imports System.Threading at the top of the document to be able to explicitly create and manipulate threads. Then I added a private property looperThread as Thread, which means that each instance of the Looper class gets its very own thread to work with. I changed the Play method to ThreadedPlay, and created a different play method, which initializes looperThread, sticks ThreadedPlay inside of it, and starts it. I added a line in ThreadedPlay that aborts the thread if the Looper's IsPlaying property has been set to False, which happens if the user calls the Looper.StopPlay() method. And, instead of still calling Play() to loop the Looper, we call ThreadedPlay; we want to run the ThreadedPlay again, not re-instantiate looperThread.
Whew!
The Looper class now looks like this:
Public Class Looper
Private timeSig As ArrayList = New ArrayList
Private soundSig As ArrayList = New ArrayList
Public IsArmed As Boolean = False
Public IsRecording As Boolean = False
Public IsPlaying As Boolean = False
Public IsLocked As Boolean = False
Private startTime As DateTime
Public LoopId As String
Private looperThread As Thread
Sub New(ByVal loopNumber As String)
LoopId = loopNumber
End Sub
Sub Arm()
IsArmed = True
End Sub
Sub Record(ByRef FirstSound As Sound)
IsArmed = False
IsRecording = True
startTime = Now
AddSignature(FirstSound)
End Sub
Sub AddSignature(ByRef NextSound As Sound)
Dim timeDif As TimeSpan = Now.Subtract(startTime)
timeSig.Add(timeDif)
soundSig.Add(NextSound)
End Sub
Sub StopRecording(ByRef EndSound As Sound)
IsRecording = False
IsLocked = True
AddSignature(EndSound)
Play()
End Sub
Sub Play()
looperThread = New Thread(AddressOf ThreadedPlay)
looperThread.Start()
End Sub
Private Sub ThreadedPlay()
If IsLocked Then
Dim numSigs As Integer = soundSig.Count
Dim thisSig As Integer = 0
IsPlaying = True
startTime = Now
Do While thisSig < numSigs
Do While startTime.Add(timeSig(this Sig)) >= Now
Application.DoEvents()
System.Threading.Thread.Sl eep(1)
If Not IsPlaying Then
Exit Do
End If
Loop
If Not IsPlaying Then
Exit Do
End If
soundSig(thisSig).Play()
thisSig = thisSig + 1
Loop
If Not IsPlaying Then
looperThread.Abort()
Exit Sub
End If
ThreadedPlay()
End If
End Sub
Sub StopPlay()
IsPlaying = False
End Sub
Sub Reset()
timeSig.Clear()
soundSig.Clear()
IsArmed = False
IsLocked = False
IsPlaying = False
IsRecording = False
End Sub
End Class
Idle Mind: Not only did you answer my question, but my issue is solved entirely, so THANKS, and here are your points.
Here's a follow up for those actually trying to gain some information by reading this question.
I had to add Imports System.Threading at the top of the document to be able to explicitly create and manipulate threads. Then I added a private property looperThread as Thread, which means that each instance of the Looper class gets its very own thread to work with. I changed the Play method to ThreadedPlay, and created a different play method, which initializes looperThread, sticks ThreadedPlay inside of it, and starts it. I added a line in ThreadedPlay that aborts the thread if the Looper's IsPlaying property has been set to False, which happens if the user calls the Looper.StopPlay() method. And, instead of still calling Play() to loop the Looper, we call ThreadedPlay; we want to run the ThreadedPlay again, not re-instantiate looperThread.
Whew!
The Looper class now looks like this:
Public Class Looper
Private timeSig As ArrayList = New ArrayList
Private soundSig As ArrayList = New ArrayList
Public IsArmed As Boolean = False
Public IsRecording As Boolean = False
Public IsPlaying As Boolean = False
Public IsLocked As Boolean = False
Private startTime As DateTime
Public LoopId As String
Private looperThread As Thread
Sub New(ByVal loopNumber As String)
LoopId = loopNumber
End Sub
Sub Arm()
IsArmed = True
End Sub
Sub Record(ByRef FirstSound As Sound)
IsArmed = False
IsRecording = True
startTime = Now
AddSignature(FirstSound)
End Sub
Sub AddSignature(ByRef NextSound As Sound)
Dim timeDif As TimeSpan = Now.Subtract(startTime)
timeSig.Add(timeDif)
soundSig.Add(NextSound)
End Sub
Sub StopRecording(ByRef EndSound As Sound)
IsRecording = False
IsLocked = True
AddSignature(EndSound)
Play()
End Sub
Sub Play()
looperThread = New Thread(AddressOf ThreadedPlay)
looperThread.Start()
End Sub
Private Sub ThreadedPlay()
If IsLocked Then
Dim numSigs As Integer = soundSig.Count
Dim thisSig As Integer = 0
IsPlaying = True
startTime = Now
Do While thisSig < numSigs
Do While startTime.Add(timeSig(this
Application.DoEvents()
System.Threading.Thread.Sl
If Not IsPlaying Then
Exit Do
End If
Loop
If Not IsPlaying Then
Exit Do
End If
soundSig(thisSig).Play()
thisSig = thisSig + 1
Loop
If Not IsPlaying Then
looperThread.Abort()
Exit Sub
End If
ThreadedPlay()
End If
End Sub
Sub StopPlay()
IsPlaying = False
End Sub
Sub Reset()
timeSig.Clear()
soundSig.Clear()
IsArmed = False
IsLocked = False
IsPlaying = False
IsRecording = False
End Sub
End Class
Idle Mind: Not only did you answer my question, but my issue is solved entirely, so THANKS, and here are your points.
Glad you figured it out!
Just a couple comments on your threaded version.
If Not IsPlaying Then
looperThread.Abort()
Exit Sub
End If
You don't need the Abort() call. In fact, it's a bad practive as things don't get cleaned up properly in the thread when you use it.
How about:
... code ...
If IsPlaying Then
ThreadedPlay()
End If
End If
End Sub
This way the end of the sub is hit if we are NOT to continue and the thread ends by itself properly.
Also, since this is in a different thread now...Application.DoEvents () should be unnecessay. Leave the small call to Sleep() in there though, since without it your CPU usage will ping to 100%.
Just a couple comments on your threaded version.
If Not IsPlaying Then
looperThread.Abort()
Exit Sub
End If
You don't need the Abort() call. In fact, it's a bad practive as things don't get cleaned up properly in the thread when you use it.
How about:
... code ...
If IsPlaying Then
ThreadedPlay()
End If
End If
End Sub
This way the end of the sub is hit if we are NOT to continue and the thread ends by itself properly.
Also, since this is in a different thread now...Application.DoEvents
ASKER
So you're saying that a thread will stop and clean itself up when the procedure assigned to it ends? That's cool.
AND, you're right, it works just fine now without the Application.DoEvents()
Say.... you're good at this.
Thanks!
AND, you're right, it works just fine now without the Application.DoEvents()
Say.... you're good at this.
Thanks!
Lol... =)
The fact that you are using recursion for this is really bothering me though!
It would be really easy to convert it to an iterative version. You're not really gaining anything by using recursion here...except using up resources in your call stack. Eventually you will run out of stack space and it will error out...if you let it "loop" long enough.
I think this will do it: (save, save, save your work before making changes)
Private Sub ThreadedPlay()
If IsLocked Then
Dim numSigs As Integer = soundSig.Count
Dim thisSig As Integer = 0
IsPlaying = True
While IsPlaying
thisSig = 0
startTime = Now
Do While thisSig < numSigs
Do While startTime.Add(timeSig(this Sig)) >= Now
System.Threading.Thread.Sl eep(1)
If Not IsPlaying Then
Exit Sub
End If
Loop
If IsPlaying Then
soundSig(thisSig).Play()
thisSig = thisSig + 1
End If
Loop
Wend
End If
End Sub
The fact that you are using recursion for this is really bothering me though!
It would be really easy to convert it to an iterative version. You're not really gaining anything by using recursion here...except using up resources in your call stack. Eventually you will run out of stack space and it will error out...if you let it "loop" long enough.
I think this will do it: (save, save, save your work before making changes)
Private Sub ThreadedPlay()
If IsLocked Then
Dim numSigs As Integer = soundSig.Count
Dim thisSig As Integer = 0
IsPlaying = True
While IsPlaying
thisSig = 0
startTime = Now
Do While thisSig < numSigs
Do While startTime.Add(timeSig(this
System.Threading.Thread.Sl
If Not IsPlaying Then
Exit Sub
End If
Loop
If IsPlaying Then
soundSig(thisSig).Play()
thisSig = thisSig + 1
End If
Loop
Wend
End If
End Sub
ASKER
Hey! That's important! Thanks for pointing that out. I will try it out, and make sure I'm not making the same mistake anywhere else in the code.
Thanks again!
Thanks again!
You play the first...sounds come out.
(first sounds still playing)
You play the second...first sounds are "muted"...second sounds come out.
You turn off the seond...first sounds (which were still playing albeit muted) are unmuted.
How are you playing the sounds?...