Link to home
Start Free TrialLog in
Avatar of bti_admin
bti_adminFlag for United States of America

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.
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

It sounds like the method you are using to play the sounds only allows one "source" to play at a time.

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?...
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.
Avatar of bti_admin

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(thisSig)) >= Now
                    Application.DoEvents()
                    System.Threading.Thread.Sleep(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.Sleep(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

ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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
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(thisSig)) >= Now
                    Application.DoEvents()
                    System.Threading.Thread.Sleep(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.
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%.
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!
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(thisSig)) >= Now
                        System.Threading.Thread.Sleep(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
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!