
Hi! I've been trying to use the C library FMOD in Haskell for about an hour now. The first test was the simple FSOUND_Init(int,int,uint) function which I got working by doing the following: foreign import ccall "fmod.h FSOUND_Init" fsound_Init :: CInt -> CInt -> CUInt -> IO CChar Which appeared to work just fine (the call went through, and I got sensible return values). Then I wanted to play some music so I tried to bring in the function FMUSIC_LoadSong which has the following C prototype (copy-pasted from the API): FMUSIC_MODULE * F_API FMUSIC_LoadSong( const char *name ); By doing this in Haskell: data MusicModule = MusicModule foreign import ccall "fmod.h FMUSIC_LoadSong" fmusic_LoadSong :: CString -> IO ForeignPtr MusicModule) I assume that this is how the ForeignPtr is meant to be used (with a dummy data type). Now if I compile this (under windows) with: ghc -fglasgow-exts --make FMODTest.hs -o test.exe -Llib -Iincludes -lfmod I get the following error: ./FMOD.hs:9: Unacceptable result type in foreign declaration: IO (ForeignPtr MusicModule) When checking declaration: foreign import ccall safe "static fmod.h &FMUSIC_LoadSong" fmusic_LoadSo ng :: Ptr CChar -> IO (ForeignPtr MusicModule) That's not a very helpful error message! Any ideas? /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

On Wed, Dec 15, 2004 at 04:09:04AM +0100, Sebastian Sylvan wrote:
FMUSIC_MODULE * F_API FMUSIC_LoadSong( const char *name );
By doing this in Haskell:
data MusicModule = MusicModule
foreign import ccall "fmod.h FMUSIC_LoadSong" fmusic_LoadSong :: CString -> IO ForeignPtr MusicModule)
I assume that this is how the ForeignPtr is meant to be used (with a dummy data type).
No, a ForeignPtr is a purely haskell object. The C function returns a Ptr. You could create a ForeignPtr from the Ptr if you want it to be automatically freed (calling somesort of FreeSong, presumably) when it gets garbage collected. -- David Roundy http://www.darcs.net

On Wed, 15 Dec 2004 06:04:37 -0500, David Roundy
On Wed, Dec 15, 2004 at 04:09:04AM +0100, Sebastian Sylvan wrote:
FMUSIC_MODULE * F_API FMUSIC_LoadSong( const char *name );
By doing this in Haskell:
data MusicModule = MusicModule
foreign import ccall "fmod.h FMUSIC_LoadSong" fmusic_LoadSong :: CString -> IO ForeignPtr MusicModule)
I assume that this is how the ForeignPtr is meant to be used (with a dummy data type).
No, a ForeignPtr is a purely haskell object. The C function returns a Ptr. You could create a ForeignPtr from the Ptr if you want it to be automatically freed (calling somesort of FreeSong, presumably) when it gets garbage collected.
Ah. Thanks. Another problem! When a handle is not being referenced I don't want it to be garbage collected if the isPlaying function returns True (in other words I want the song to finish playing even if it's not being referenced anymore). The current plan of attack is to have the finalizer fork off a thread which does nothing but check the status of the song every 500ms or so and when it's finished, releases it. But since I really only need to check it every time the garbage collector wants to release it maybe there's a better way? So I want to annotate the ForeignPtr with a function which can defer it's release based on the status of the handle. Is there a way to do this? Sadly the C library doesn't seem to provide a callback for when the song has finished playing which would be ideal. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

On Wed, Dec 15, 2004 at 01:13:19PM +0100, Sebastian Sylvan wrote:
Another problem! When a handle is not being referenced I don't want it to be garbage collected if the isPlaying function returns True (in other words I want the song to finish playing even if it's not being referenced anymore).
The current plan of attack is to have the finalizer fork off a thread which does nothing but check the status of the song every 500ms or so and when it's finished, releases it. But since I really only need to check it every time the garbage collector wants to release it maybe there's a better way? So I want to annotate the ForeignPtr with a function which can defer it's release based on the status of the handle. Is there a way to do this?
If you're going to determine when to release the pointer manually (which is probably best anyways), then there's no need to mess with a ForeignPtr. Just stick with a Ptr, and spawn your thread to decide when to fall the free function. I presume that you really do want to play the song asynchronously, rather than just returning when the song is over?
Sadly the C library doesn't seem to provide a callback for when the song has finished playing which would be ideal.
Indeed. -- David Roundy http://www.darcs.net

On Wed, 15 Dec 2004 07:55:42 -0500, David Roundy
On Wed, Dec 15, 2004 at 01:13:19PM +0100, Sebastian Sylvan wrote:
Another problem! When a handle is not being referenced I don't want it to be garbage collected if the isPlaying function returns True (in other words I want the song to finish playing even if it's not being referenced anymore).
The current plan of attack is to have the finalizer fork off a thread which does nothing but check the status of the song every 500ms or so and when it's finished, releases it. But since I really only need to check it every time the garbage collector wants to release it maybe there's a better way? So I want to annotate the ForeignPtr with a function which can defer it's release based on the status of the handle. Is there a way to do this?
If you're going to determine when to release the pointer manually (which is probably best anyways), then there's no need to mess with a ForeignPtr. Just stick with a Ptr, and spawn your thread to decide when to fall the free function.
If I do that then I might remove the song (after it has played) even though the handle is still alive. So I need to start checking if the song is playing after the handle has been deemed dead by Haskell's GC.
I presume that you really do want to play the song asynchronously, rather than just returning when the song is over?
Yeah... /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Another question! Is there a way to force the garbage collector to kick in? I''m trying to find out if my finalizer gets called correctly but I don't know if the garbage collector is run. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Okay. Yet another question.
I have a datatype called SoundSample which is just a ForeignPtr to an
actual sound resource.
Playing a resource will in the C library return a handle to the
channel. I use a simliar concept where playing a resource will return
a SamplePlaybackRaw.
The sound resource will get freed up using the appropriate C function
when its finalizer is called.
So to keep the sound resource from being finalized while there still
exists a SamplePlaybackRaw value which is playing I let the
SamplePlaybackRaw type contain the SoundSample.
This way the SoundSample can only be freed if all SamplePlaybackRaws
it has spawned are already dead.
Okay, so far so good.
Here's the problem though. If the SamplePlaybackRaw is currently
playing sound when it gets garbage collected, I want to keep the sound
resource (SoundSample) alive until the playback has finished.
To do this the plan was this:
1. Find a way to attatch a finalizer to SamplePlayback.
2. In that finalizer, spawn a new thread which simply loops and checks
the channel (stored in SamplePlaybackRaw) and touches the ForeignPtr
to the sound resource (also stored in the SamplePlaybackRaw) if the
channel is currently playing.
The first part I accomplished via a series of casts: StablePtr -> Ptr
-> ForeignPtr
To this ForeignPtr I then attatched a finalizer.
This didn't work (the finalizer did get called, but the garbage
collection appears to happen even though the ForeignPtr to the
SamplePlaybackRaw is still referenced). Is there a better way to
attatch a finalizer to a Haskell value?
/S
On Thu, 16 Dec 2004 11:09:47 +1100, Ben Lippmeier
System.Mem.performGC?
Sebastian Sylvan wrote:
Another question!
Is there a way to force the garbage collector to kick in?
I''m trying to find out if my finalizer gets called correctly but I don't know if the garbage collector is run.
/S
-- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

On 16 Dec 2004, at 06:57, Sebastian Sylvan wrote:
Here's the problem though. If the SamplePlaybackRaw is currently playing sound when it gets garbage collected, I want to keep the sound resource (SoundSample) alive until the playback has finished.
I probably haven't understood the details correctly but why not just spawn a thread in your 'start playback' function. This thread (a) keeps a reference to the soundsample (which guarantees it will never be GC'ed) and (b) checks every second or so to see if the playing has finished. If the playing has finished, it arranges to no longer hold the reference, and in due course GC will take it out. Jules

On Thu, 16 Dec 2004 12:38:26 +0000, Jules Bean
On 16 Dec 2004, at 06:57, Sebastian Sylvan wrote:
Here's the problem though. If the SamplePlaybackRaw is currently playing sound when it gets garbage collected, I want to keep the sound resource (SoundSample) alive until the playback has finished.
I probably haven't understood the details correctly but why not just spawn a thread in your 'start playback' function. This thread (a) keeps a reference to the soundsample (which guarantees it will never be GC'ed) and (b) checks every second or so to see if the playing has finished. If the playing has finished, it arranges to no longer hold the reference, and in due course GC will take it out.
This is my "if-all-else-fails" backup. I really don't want to poll the sound sample all the time since it uses up resources needlessly. If it comes to that, though, it's pretty much guaranteed that it will get the job done. The plan was to try to initiate the polling loop only when the GC wants to delete the sound channel. Here's what I imagine a typical execution could look like 1. Sound resource, s, is loaded 2. Calling "play" on s, returns a "channel" in which the sound is being played. 3. Repeat 2 any number of times. 4. The GC is being invoked. s may no longer be in scope, but the "channels" are still referencing the sound resource so it won't get collected. If the channel(s) are out of scope and the GC tries to collect them, they will each spawn a "keep-alive-loop" in their finalizer which has no other purpose but to keep the sound resource alive for however long it takes to finish playing. This is what I tried to do. I've now tried to do it with weak pointers as well. I get the finalizer to run, but for some reason the finalizer for the sound resource gets invoked before the finalizer for the channel. This is extremply strange since the channel contains a reference to the "ForeignPtr CSoundSample" so I wouldn't expect the sound resource finalizer to EVER get invoked while there are still channels around that are referencing it. Here's the latest attempt: -- the sound resource. -- The finalizer will simply call the C-library's "free"-function sampleLoad :: String -> IO SoundSample sampleLoad s = do cs <- newCString s samplePtr <- fsound_SampleLoad (-1) cs 0 0 0 finalizer <- mkSampleFinalizer sampleFinalizer newForeignPtr finalizer samplePtr -- The playback -- contains the channel AND a reference to the SoundSample -- so the soundsample should NEVER get finalized before the -- SamplePlaybackRaw data SamplePlaybackRaw = SP !CInt !SoundSample type SamplePlayback = Weak SamplePlaybackRaw -- play a sample samplePlay :: SoundSample -> IO SamplePlayback samplePlay sample = do ch <- withForeignPtr sample (fsound_PlaySound (-1)) let spb = SP ch sample -- SamplePlaybackRaw mkWeakPtr spb (Just (samplePlaybackFinalizer spb)) samplePlaybackFinalizer :: SamplePlaybackRaw -> IO () samplePlaybackFinalizer (SP ch sample) = do putStrLn "Starting keep-alive loop for sample playback" hFlush stdout touchForeignPtr sample forkIO (keepAlive ch sample) return () keepAlive :: CInt -> SoundSample -> IO () keepAlive ch sample = do putStrLn "Keeping channel alive!" hFlush stdout touchForeignPtr sample threadDelay 500000 -- wait a while b <- fsound_IsPlaying ch case cscharToBool b of True -> keepAlive ch sample False -> putStrLn "Finished keeping sample alive" /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

On 16 Dec 2004, at 15:20, Sebastian Sylvan wrote:
I really don't want to poll the sound sample all the time since it uses up resources needlessly. If it comes to that, though, it's pretty much guaranteed that it will get the job done.
A thread which polls once per second (and sleeps otherwise) is a negligible overhead. I wouldn't lose sleep over that.
This is what I tried to do. I've now tried to do it with weak pointers as well. I get the finalizer to run, but for some reason the finalizer for the sound resource gets invoked before the finalizer for the channel. This is extremply strange since the channel contains a reference to the "ForeignPtr CSoundSample" so I wouldn't expect the sound resource finalizer to EVER get invoked while there are still channels around that are referencing it.
Well suppose the channel goes out of scope. Now the channel and the sound are both out of scope, so they can be GC'ed. There is no guarantee in which order the finalizers will be invoked, so it's perfectly possible that the sound finalizer runs before the channel finalizer. Jules

-- play a sample samplePlay :: SoundSample -> IO SamplePlayback samplePlay sample = do ch <- withForeignPtr sample (fsound_PlaySound (-1)) let spb = SP ch sample -- SamplePlaybackRaw mkWeakPtr spb (Just (samplePlaybackFinalizer spb))
From the System.Mem.Weak docs for addFinalizer: Note: adding a finalizer to a ForeignPtr using addFinalizer won't work as well as using the specialised version addForeignPtrFinalizer because the latter version adds the finalizer to the primitive ForeignPtr# object inside, whereas the generic addFinalizer will add the finalizer to the box. Optimisations tend to remove the box, which may cause the finalizer to run earlier than you intended. The same motivation justifies the existence of addMVarFinalizer and mkWeakIORef (the non-unformity is accidental). This note also applies to the use of mkWeakPtr. So if you want to protect the raw pointer, using weak pointer finalizers is not the way to go, because of the problem with optimizations. If you are going to use finalizers, you should use the ForeignPtr versions (that's the main reason why ForeignPtr exists). The usage I suggested only used weak pointers to detect the fact that the foreign pointer had been garbage collected, and did not use finalizers at all.

On Thu, 2004-12-16 at 01:05 +0100, Sebastian Sylvan wrote:
Another question!
Is there a way to force the garbage collector to kick in?
I''m trying to find out if my finalizer gets called correctly but I don't know if the garbage collector is run.
I'm kind of surprised no one has yet mentioned this, so I will. Relying on finalizers to perform significant clean up actions (ie, anything besides memory deallocation) is rather frowned upon. I think that spawing a thread and doing other heavy-duty actions inside a finalizer is a bad idea, for several reasons: 1) Finalizers are not (some say cannot) be guaranteed to run, even on normal program termination, even if you force GC before exiting. 2) Finalizers can run when the RTS is in a bizarre state (eg, STDOUT might not be avaliable, because it has already been finalized) 3) Finalizers cannot be ordered (2 is a consequence of this) 4) Finalizers are run at unpredictable times. I would suggest you find some way to accomplish what you want without using finalizers.

On Wed, 15 Dec 2004 22:58:53 -0500, Robert Dockins
On Thu, 2004-12-16 at 01:05 +0100, Sebastian Sylvan wrote:
Another question!
Is there a way to force the garbage collector to kick in?
I''m trying to find out if my finalizer gets called correctly but I don't know if the garbage collector is run.
I'm kind of surprised no one has yet mentioned this, so I will. Relying on finalizers to perform significant clean up actions (ie, anything besides memory deallocation) is rather frowned upon. I think that spawing a thread and doing other heavy-duty actions inside a finalizer is a bad idea, for several reasons:
1) Finalizers are not (some say cannot) be guaranteed to run, even on normal program termination, even if you force GC before exiting.
I only need a guarantee that it will be run if the Ptr is no longer being referenced.
2) Finalizers can run when the RTS is in a bizarre state (eg, STDOUT might not be avaliable, because it has already been finalized)
Fair enough.
I would suggest you find some way to accomplish what you want without using finalizers.
That would require the user to manually free up resources once they're not needed anymore. Something which I believe is a bit too low-level for my tastes. I basically want the user to be able to just create a sound resource and then play it, without having to do any book-keeping as to when the sound resource is not used anymore and can be released. If there was a way to simply defer GC (like you attatch a function to an object which can simply deny the GC the right to remove it depending on its state) then I wouldn't have to do anything significant in the finalizer. I haven't found a way to do this though, so the plan is instead to extract the parts of the object which needs to be kept alive and then keep them alive just long enough to finish playback and then release them. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Sebastian Sylvan wrote:
If there was a way to simply defer GC (like you attatch a function to an object which can simply deny the GC the right to remove it depending on its state) then I wouldn't have to do anything significant in the finalizer.
Why not spawn a thread which starts the playback, waits for it to finish, and then exits, and wrap the whole thread in a call to withForeignPtr? Then your finalizer won't be called prematurely. -- Ben

On Thu, 16 Dec 2004 08:46:58 +0000, Ben Rudiak-Gould
Sebastian Sylvan wrote:
If there was a way to simply defer GC (like you attatch a function to an object which can simply deny the GC the right to remove it depending on its state) then I wouldn't have to do anything significant in the finalizer.
Why not spawn a thread which starts the playback, waits for it to finish, and then exits, and wrap the whole thread in a call to withForeignPtr? Then your finalizer won't be called prematurely.
Well I could do this, but for one it would be cumbersome to stop playback. I could, of course, return some sort of Playback datatype which contains a Chan that can be written to with some commands, and then have the playback-loop (spawned with a forkIO) check this periodically and apply the commands to the "real" playback channel. However I'd need a pretty tight polling-loop to get these commands responsive, and that just seems like a waste of processing power. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Sebastian Sylvan wrote:
Ben Rudiak-Gould wrote:
Why not spawn a thread which starts the playback, waits for it to finish, and then exits, and wrap the whole thread in a call to withForeignPtr? Then your finalizer won't be called prematurely.
Well I could do this, but for one it would be cumbersome to stop playback.
I don't think it is. Let me try to specify the problem precisely to make sure we're talking about the same thing. We have an interface something like this: loadSong :: String -> IO (Ptr SongRep) playSong :: Ptr SongRep -> IO () stopPlaying :: Ptr SongRep -> IO () -- it will also stop by itself isPlaying :: Ptr SongRep -> IO Bool freeSong :: Ptr SongRep -> IO () -- unsafe if song is playing and we want an interface like this: loadSong' :: String -> IO Song playSong' :: Song -> IO () stopPlaying' :: Song -> IO () isPlaying' :: Song -> IO Bool I think this implementation will work: type Song = ForeignPtr SongRep loadSong' name = loadSong name >>= newForeignPtr freeSong playSong' song = forkIO (withForeignPtr song (\s -> playSong s >> waitForSilence s)) waitForSilence s = do threadDelay 500000 b <- isPlaying s when b (waitForSilence s) stopPlaying' song = withForeignPtr song stopPlaying isPlaying' song = withForeignPtr song isPlaying If you need the return value from playSong, this should also work: playSong' song = do rtn <- withForeignPtr song playSong forkIO (withForeignPtr song waitForSilence) return rtn But this won't work, because the withForeignPtr call will return before the thread exits: brokenPlaySong' song = withForeignPtr song (\s -> playSong >> forkIO (waitForSilence s)) -- Ben

What about using a StablePtr? If you're dealing with hardware you would get the end-of-sample interrupt to free the buffer... Either way you either have to delay GC until the sample playback has finished... (well technically until the soundcard DMA has transferred the sample to the soundcards onboard memory) or free the memory when playback has finished. These ammount to the same thing... but using a StablePtr and explicit free seems much nicer than hacking strange stuff into a finalizer. Keean.

On Thu, 16 Dec 2004 09:46:34 +0000, Keean Schupke
What about using a StablePtr? If you're dealing with hardware you would get the end-of-sample interrupt to free the buffer... Either way you either have to delay GC until the sample playback has finished... (well technically until the soundcard DMA has transferred the sample to the soundcards onboard memory) or free the memory when playback has finished. These ammount to the same thing... but using a StablePtr and explicit free seems much nicer than hacking strange stuff into a finalizer.
Explicitly freeing works as it is. I was just hoping to get a higher level abstraction on top of this so that the user could just create a sound resource, and then play it back all over the place (even in functions with short-lifespans) and have the system "Do The Right Thing" with regards to cleaning up once nothing references the sound resource, or the channels playing it, anymore.. Of couse, as stated, the problem comes in with the fact that channels will get cleaned up even if they are currently playing stuff. It would be ideal if one could just attatch a "guard" against the GC to a value so that each time the GC wants to collect unreferenced stuff it will first apply the guard-function, if there is one, to the value and only free it up if that returns True (this would be tested every time the GC does a "collection sweep"). Then I could just do something like the following each time a playback is started: attatchGCGuard playback isPlaying where: isPlaying :: Playback -> IO Bool attatchGCGuard :: a -> (a -> IO Bool) -> IO () This would mean that the playback won't get cleaned up if it's currently playing something. And since the Playback value has a reference to the "ForeignPtr CSoundSample" where the sound data lies, that won't get freed up either (freeing of that resource happens in the finalizer for that ForeignPtr). Maybe that's not possible for reasons beyond my understanding, but perhaps it illustrates my problem in a clearer way. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Okay, you want to poll to see if the sound is still playing... This is not very easy to do... You really want to use the low-buffer callback from the soundcard driver to load the next sample into the buffer using DMA. (IE you can pass a function to the driver to call when you sample has been copied completely to the sound-cards memory). This is the way to do it... play_then_free = do set_callback (free_sample my_sample) play_sample my_sample You can see that this provides the interface you want to the user (IE samples are freed once they are finished with)... Keean. Sebastian Sylvan wrote:
On Thu, 16 Dec 2004 09:46:34 +0000, Keean Schupke
wrote: What about using a StablePtr? If you're dealing with hardware you would get the end-of-sample interrupt to free the buffer... Either way you either have to delay GC until the sample playback has finished... (well technically until the soundcard DMA has transferred the sample to the soundcards onboard memory) or free the memory when playback has finished. These ammount to the same thing... but using a StablePtr and explicit free seems much nicer than hacking strange stuff into a finalizer.
Explicitly freeing works as it is. I was just hoping to get a higher level abstraction on top of this so that the user could just create a sound resource, and then play it back all over the place (even in functions with short-lifespans) and have the system "Do The Right Thing" with regards to cleaning up once nothing references the sound resource, or the channels playing it, anymore.. Of couse, as stated, the problem comes in with the fact that channels will get cleaned up even if they are currently playing stuff. It would be ideal if one could just attatch a "guard" against the GC to a value so that each time the GC wants to collect unreferenced stuff it will first apply the guard-function, if there is one, to the value and only free it up if that returns True (this would be tested every time the GC does a "collection sweep").
Then I could just do something like the following each time a playback is started:
attatchGCGuard playback isPlaying
where: isPlaying :: Playback -> IO Bool attatchGCGuard :: a -> (a -> IO Bool) -> IO ()
This would mean that the playback won't get cleaned up if it's currently playing something. And since the Playback value has a reference to the "ForeignPtr CSoundSample" where the sound data lies, that won't get freed up either (freeing of that resource happens in the finalizer for that ForeignPtr).
Maybe that's not possible for reasons beyond my understanding, but perhaps it illustrates my problem in a clearer way.
/S

On Thu, 16 Dec 2004 12:35:55 +0000, Keean Schupke
Okay, you want to poll to see if the sound is still playing... This is not very easy to do...
You really want to use the low-buffer callback from the soundcard driver to load the next sample into the buffer using DMA. (IE you can pass a function to the driver to call when you sample has been copied completely to the sound-cards memory). This is the way to do it...
play_then_free = do set_callback (free_sample my_sample) play_sample my_sample
You can see that this provides the interface you want to the user (IE samples are freed once they are finished with)...
Keean.
This is exactly how I do it for the Stream sound type, since this type of sound supplies a callback for when it reaches the end. The sound sample does not, though. (stream is streamed from disk, sound sample is loaded into memory). /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

In which case I would assume the whole sample is copied to the sound cards buffer before the play call returns, so you don't have to worry and just let it be garbage collected. Keean Sebastian Sylvan wrote:
On Thu, 16 Dec 2004 12:35:55 +0000, Keean Schupke
wrote: Okay, you want to poll to see if the sound is still playing... This is not very easy to do...
You really want to use the low-buffer callback from the soundcard driver to load the next sample into the buffer using DMA. (IE you can pass a function to the driver to call when you sample has been copied completely to the sound-cards memory). This is the way to do it...
play_then_free = do set_callback (free_sample my_sample) play_sample my_sample
You can see that this provides the interface you want to the user (IE samples are freed once they are finished with)...
Keean.
This is exactly how I do it for the Stream sound type, since this type of sound supplies a callback for when it reaches the end. The sound sample does not, though. (stream is streamed from disk, sound sample is loaded into memory).
/S

On Thu, 16 Dec 2004 18:20:52 +0000, Keean Schupke
In which case I would assume the whole sample is copied to the sound cards buffer before the play call returns, so you don't have to worry and just let it be garbage collected.
What do you mean? Yes the sample is copied to memory when loaded, but I still have to "worry" because if it's garbage colleced (ie, a "free" call is called on it) then the sound can not be played anymore (because the data is gone). /S
Keean
Sebastian Sylvan wrote:
On Thu, 16 Dec 2004 12:35:55 +0000, Keean Schupke
wrote: Okay, you want to poll to see if the sound is still playing... This is not very easy to do...
You really want to use the low-buffer callback from the soundcard driver to load the next sample into the buffer using DMA. (IE you can pass a function to the driver to call when you sample has been copied completely to the sound-cards memory). This is the way to do it...
play_then_free = do set_callback (free_sample my_sample) play_sample my_sample
You can see that this provides the interface you want to the user (IE samples are freed once they are finished with)...
Keean.
This is exactly how I do it for the Stream sound type, since this type of sound supplies a callback for when it reaches the end. The sound sample does not, though. (stream is streamed from disk, sound sample is loaded into memory).
/S
-- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Sebastian Sylvan wrote:
What do you mean? Yes the sample is copied to memory when loaded, but I still have to "worry" because if it's garbage colleced (ie, a "free" call is called on it) then the sound can not be played anymore (because the data is gone).
I mean the sound card has its own, independant memory, so it should be okay to let the sample be garbage collected. I suggest this because if the sound library has no way to tell if the sample has finished playing (they provide a callback in some cases but not this case) then the library author must have considered it not necessary for some reason. The only reason I can think of that it would not be necessary to be notified of when the sample has been finished with is if the 'play' call makes an internal copy of the sample in the sound-card buffer, and it is safe to delete the sample immediately play returns. The other possibility is that the library author intended all samples to remain in memory until the program finishes (like sound effects in a game, you load them once and just keep them). Keean.

On Sat, 18 Dec 2004 09:07:12 +0000, Keean Schupke
Sebastian Sylvan wrote:
What do you mean? Yes the sample is copied to memory when loaded, but I still have to "worry" because if it's garbage colleced (ie, a "free" call is called on it) then the sound can not be played anymore (because the data is gone).
I mean the sound card has its own, independant memory, so it should be okay to let the sample be garbage collected. I suggest this because if the sound library has no way to tell if the sample has finished playing (they provide a callback in some cases but not this case) then the library author must have considered it not necessary for some reason. The only reason I can think of that it would not be necessary to be notified of when the sample has been finished with is if the 'play' call makes an internal copy of the sample in the sound-card buffer, and it is safe to delete the sample immediately play returns. The other possibility is that the library author intended all samples to remain in memory until the program finishes (like sound effects in a game, you load them once and just keep them).
If the sound data is removed, then the playback will stop because the data is gone. There is a way to find if a sound data is playing, but only if you know which channels you have loaded it into. Then you can see if that channel is currently playing something. When the sound data is garbage collected the sound data will be removed via a call to the C library function "free". This causes all playback of that sound to stop, which exactly the problem I'm trying to solve! I want a "channel" object (spawned with a call to "play") with a reference to the sound data, so that the sound data will be kept alive as long as the channel is kept alive. That's easy and already solved. Then, however, I also want to defer collection of the channel object while it's playing something - which I have not been able to solve yet. I've tried to create a ForeignPtr to this Haskell value (StablePtr -> Ptr -> ForeignPtr) and attatch a finalizer to it, which spawns a keep-alive loop when the RTS tries to collect the channel. I've tried weak pointers to attatch a finalizer which also spawns a keep-alive loop. So far nothing's worked. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

I still think you need to use a StablePtr, so the sample will not get garbage collected. You then need to free the sample explicitly when playback has finished... Ideally you want to add a callback to do the free-ing. In my opinion if the library you are using does not provide this callback then the library is broken - and you should try and find a different library which does things properly. If you cannot find an alternate library I guess you have to poll playback of the sample before freeing. with_channel 1 $ do play_sample my_sample wait_for_playback free my_sample If you wish to support multiple channels, you would fork a thread to deal with each channel... There are too many unknowns to say much more than this... If you queue two samples to the same channel, how do you detemine when the first sample has finished (as polling the channed will tell you it is still playing). I get the feeling the traditional way to manage this is to load _all_ the samples for a song/game level in one go, and keep them until the whole song or game-level is finished. Keean. Sebastian Sylvan wrote:
On Sat, 18 Dec 2004 09:07:12 +0000, Keean Schupke
wrote: Sebastian Sylvan wrote:
What do you mean? Yes the sample is copied to memory when loaded, but I still have to "worry" because if it's garbage colleced (ie, a "free" call is called on it) then the sound can not be played anymore (because the data is gone).
I mean the sound card has its own, independant memory, so it should be okay to let the sample be garbage collected. I suggest this because if the sound library has no way to tell if the sample has finished playing (they provide a callback in some cases but not this case) then the library author must have considered it not necessary for some reason. The only reason I can think of that it would not be necessary to be notified of when the sample has been finished with is if the 'play' call makes an internal copy of the sample in the sound-card buffer, and it is safe to delete the sample immediately play returns. The other possibility is that the library author intended all samples to remain in memory until the program finishes (like sound effects in a game, you load them once and just keep them).
If the sound data is removed, then the playback will stop because the data is gone.
There is a way to find if a sound data is playing, but only if you know which channels you have loaded it into. Then you can see if that channel is currently playing something. When the sound data is garbage collected the sound data will be removed via a call to the C library function "free". This causes all playback of that sound to stop, which exactly the problem I'm trying to solve! I want a "channel" object (spawned with a call to "play") with a reference to the sound data, so that the sound data will be kept alive as long as the channel is kept alive. That's easy and already solved. Then, however, I also want to defer collection of the channel object while it's playing something - which I have not been able to solve yet.
I've tried to create a ForeignPtr to this Haskell value (StablePtr -> Ptr -> ForeignPtr) and attatch a finalizer to it, which spawns a keep-alive loop when the RTS tries to collect the channel. I've tried weak pointers to attatch a finalizer which also spawns a keep-alive loop.
So far nothing's worked.
/S

On Sat, 18 Dec 2004 10:33:25 +0000, Keean Schupke
I still think you need to use a StablePtr, so the sample will not get garbage collected.
You then need to free the sample explicitly when playback has finished...
Ideally you want to add a callback to do the free-ing. In my opinion if the library you are using does not provide this callback then the library is broken - and you should try and find a different library which does things properly.
I'm not sure why it doesn't do this, it provide a callback for streams. Anyway, if I use polling to check if the sample is playing then I don't need the stableptr because in the polling loop the channel will be kept alive since it's used to check if its playing.
There are too many unknowns to say much more than this... If you queue two samples to the same channel, how do you detemine when the first sample has finished (as polling the channed will tell you it is still playing).
I don't, basically. Well I could implement sort of a queue for each channel but basically channels are resources and when you play a sample it will just find a free channel for you. It's possible, but not that useful, to do your own channel management.
I get the feeling the traditional way to manage this is to load _all_ the samples for a song/game level in one go, and keep them until the whole song or game-level is finished.
Well that is how the C library does it, but if I could get the whole "delay GC while channel is active" working it would amount to very much the same thing, except a few resources might get freed up earlier, and the programmer doesn't need to explicitly delete things. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

1) Finalizers are not (some say cannot) be guaranteed to run, even on normal program termination, even if you force GC before exiting.
I only need a guarantee that it will be run if the Ptr is no longer being referenced.
This is exactly the guarantee you _don't_ have. The only guarantee you have is that it _won't_ be run if the Ptr _is_ being referenced.
I would suggest you find some way to accomplish what you want without using finalizers.
That would require the user to manually free up resources once they're not needed anymore. Something which I believe is a bit too low-level for my tastes. I basically want the user to be able to just create a sound resource and then play it, without having to do any book-keeping as to when the sound resource is not used anymore and can be released.
My concern is that this approach is a little too non-deterministic. Because playing sound involves HW resources, it is better to make users do the bookkeeping and have well-defined behavior rwt releasing resources. That said, you can probably get what you want by using weak pointers. Wrap up your Ptr in a ForiegnPtr and wrap _that_ up in an abstract datatype, and attach a weak pointer to the ForeignPtr (make sure all functions that access the Ptr use 'withForeignPtr' or 'touchForeignPtr'). Add a (Ptr,WeakPtr ()) pair to a list of "open" songs, which is monitored by a single clean-up thread (use an MVar for the list). Return the high level value to the user; when the enclosed ForeignPtr is garbage collected deRefWeak on the weak pointer will return Nothing. Then the clean-up thread can begin polling the song object to see if it is finished. You can also poke this thread at program-end to force it to free all resources it knows about, so that your program behaves well, assuming normal termination. Weak pointers are documented here: http://www.haskell.org/ghc/docs/latest/html/libraries/base/System.Mem.Weak.h... I think that something like this would work: module MySongs ( Song, mkSong, something ) where foreign .... makeASong :: whatever -> IO (Ptr ()) foreign .... doSomething :: Ptr () -> IO () data Song = Song ForeignPtr mkSong :: whatever -> MVar [(Ptr,Weak ())] -> IO Song mkSong x mvar = do p <- makeASong x f <- newForeignPtr_ p w <- mkWeak f () l <- takeMVar mvar putMVar mvar ((p,w):l) return Song f something :: Song -> IO () something (Song f) = do withForeignPtr f \p -> do doSomething p

On Thu, 16 Dec 2004 08:56:15 -0500, Robert Dockins
Return the high level value to the user; when the enclosed ForeignPtr is garbage collected deRefWeak on the weak pointer will return Nothing. Then the clean-up thread can begin polling the song object to see if it is finished.
But if the ForeignPtr has been garbage collected, it's too late! The sound data is *gone* and the channel can no longer play the sound. I need to catch it just before the *channel* gets garbage collected and extract the vital information from the channel so that I can keep them alive long enough to finish playback. One of the vital pieces of information is a reference to the sound data and the other is the physical channel (CInt). Using those two pieces of information I *should* be able to poll the channel to see if it is playing, and also keep the sound data alive (touchForeignPtr). Once the channel has finished playing I just need to return and let the GC take care of the sound data (which may or may not have additional references from other channels), since ITS finalizer properly releases its resources. However this doesn't work. Strangely I do this (see other post for additional code) main = do hSetBuffering stdout NoBuffering x <- fsound_Init 44100 32 0 c <- callStuff threadDelay 1000000 putStrLn "Performing GC!" performGC threadDelay 1000000 b <- isPlaying c print b getLine return () callStuff = do s <- sampleLoad "test.mp3" c <- samplePlay s threadDelay 3000000 b <- isPlaying c print b threadDelay 1000000 return c And expect to get output along the lines of: True Performing GC Starting keep-alive loop for sample playback Keeping channel alive! Keeping channel alive! Keeping channel alive! Keeping channel alive! Finished keeping sample alive Freeing up sample! False Instead I get: True Performing GC! Freeing up sample Starting keep-alive loop for sample playback Keeping channel alive! Finished keeping sample alive False In other words after the GC the first thing that happens is that the _sound_data_ is being garbage collected! That's extremly freaky since the channel (which is used later on to produce the "False" value from isPlaying) contains a reference to this sound data! THEN the keep-alive loop starts (and since isPlaying naturally returns False since there is no sound data anymore, it immediatly returns). I don't understand this behaviour. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Sebastian Sylvan wrote:
On Thu, 16 Dec 2004 08:56:15 -0500, Robert Dockins
wrote: Return the high level value to the user; when the enclosed ForeignPtr is garbage collected deRefWeak on the weak pointer will return Nothing. Then the clean-up thread can begin polling the song object to see if it is finished.
But if the ForeignPtr has been garbage collected, it's too late! The sound data is *gone* and the channel can no longer play the sound.
If you look closely at the code I posted, I do something tricky; I form a ForeignPtr (without a finalizer) from a Ptr, and then _keep them both_. So, when the ForeignPtr is GCed, I still have the original pointer. I detect the fact that the ForeignPtr is GCed by using a weak pointer. Because of the way I set up my code, I know that when the ForeignPtr is gone, my clean-up thread is the only place that I have a pointer to the original C object. So then I start polling the sound object to see if it is done playing, and only then free the C object.
-- play a sample samplePlay :: SoundSample -> IO SamplePlayback samplePlay sample = do ch <- withForeignPtr sample (fsound_PlaySound (-1)) let spb = SP ch sample -- SamplePlaybackRaw mkWeakPtr spb (Just (samplePlaybackFinalizer spb))
Ahh... look. You are returning a weak pointer! Those specifically allow the referenced object to be garbage collected. So the only references left to your SamplePlaybackRaw are (a) inside a weak pointer and (b) inside a finalizer closure. Such references are NOT considered reachable from the root set, and can thus be GCed (I am about 90% sure this is true about finalizers). You have arranged it so that the RTS can garbage collect spb, and thus the ch and sample, whenever it likes.

On Thu, 16 Dec 2004 11:08:13 -0500, Robert Dockins
-- play a sample samplePlay :: SoundSample -> IO SamplePlayback samplePlay sample = do ch <- withForeignPtr sample (fsound_PlaySound (-1)) let spb = SP ch sample -- SamplePlaybackRaw mkWeakPtr spb (Just (samplePlaybackFinalizer spb))
Ahh... look. You are returning a weak pointer! Those specifically allow the referenced object to be garbage collected. So the only references left to your SamplePlaybackRaw are (a) inside a weak pointer and (b) inside a finalizer closure. Such references are NOT considered reachable from the root set, and can thus be GCed (I am about 90% sure this is true about finalizers). You have arranged it so that the RTS can garbage collect spb, and thus the ch and sample, whenever it likes.
Well that shouldn't affect the functionality. The weak pointer was only a way of attatching a finalizer to the Playback object. It is true that I should probably wrap up the the SoundPlaybackRaw inside the SoundPlayback as well, to save CPU, but it shouldn't matter for the core functionality. It appears weak pointers don't really run the finalizer when I want them to run (they run too late!). Or maybe it's the finalizer for the ForeignPtr to the sound data which runs to early (I create it using newForeignPtr). Nevertheless, i couldn't get it to work when doing "strong" pointers either (StablePtr, cast to a Ptr, remove StablePtr, create ForeignPtr from Ptr and attach a finalizer). I really don't like the idea of having a background loop running all the time (I'd rather spawn one just before the GC tries to clean up the Playback). If I were okay with that I'd just spawn a new one for each Playback, which would be considerably cleaner I think than having a single loop with some sort of master database of playbacks... /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862

Well that shouldn't affect the functionality. The weak pointer was only a way of attatching a finalizer to the Playback object. It is true that I should probably wrap up the the SoundPlaybackRaw inside the SoundPlayback as well, to save CPU, but it shouldn't matter for the core functionality.
I'm not sure I understand.
If I were okay with that I'd just spawn a new one for each Playback, which would be considerably cleaner I think than having a single loop with some sort of master database of playbacks...
I think both of these are cleaner than starting a new thread at finalize time. At any rate, here is a quick hack of mine that I think does what you want, using a master clean up thread that is only awake when songs are "playing". Feel free to play with/use it at will. Note it doesn't guarantee that every song is freed. module Main where import List import Monad import Random import Foreign import GHC.ForeignPtr import System.Mem import System.Mem.Weak import Control.Concurrent import Control.Concurrent.MVar -- pretend these are foreign imports that really do things... newSong :: String -> IO (Ptr ()) newSong name = return nullPtr songDone :: Ptr () -> IO Bool songDone p = do x <- getStdRandom( randomR (1,100) ) if x >= (85::Int) then return True else return False freeSong :: Ptr () -> IO () freeSong p = return () -------------------- newtype Song = Song (ForeignPtr ()) data LibState = LibState { listMV :: MVar [Ptr ()] , waitMV :: MVar Bool , killMV :: MVar () } songFinalizer :: LibState -> Ptr () -> IO () songFinalizer libstate p = do modifyMVar_ (listMV libstate) (return . (p:)) tryPutMVar (waitMV libstate) False return () mkSong :: LibState -> String -> IO Song mkSong libstate name = do p <- newSong name f <- newConcForeignPtr p (songFinalizer libstate p) return (Song f) cleanupThread :: MVar [Ptr ()] -> MVar Bool -> MVar () -> IO () cleanupThread listMVar waitMVar killMVar = let loop l = do shouldDie <- takeMVar waitMVar putStrLn "cleanup thread awoken..." l' <- swapMVar listMVar [] when (null l' && not shouldDie) (threadDelay 1000000) notdone <- filterM (\x -> do d <- songDone x if d then putStrLn "freeing song..." >> freeSong x >> return False else return True ) (l ++ l') unless (null notdone) (tryPutMVar waitMVar False >> return ()) if shouldDie then return notdone else loop notdone in do putStrLn "starting cleanup loop" l <- loop [] putStrLn "done with cleanup loop" sequence_ (map (\x -> freeSong x >> putStrLn "freeing song late...") l) putMVar killMVar () initLib :: IO LibState initLib = do listMVar <- newMVar [] waitMVar <- newEmptyMVar killMVar <- newEmptyMVar putStrLn "starting cleanup thread" forkIO (cleanupThread listMVar waitMVar killMVar) let libstate = LibState { listMV = listMVar , waitMV = waitMVar , killMV = killMVar } return libstate shutdownLib :: LibState -> IO () shutdownLib libstate = do putMVar (waitMV libstate) True takeMVar (killMV libstate) main = do libstate <- initLib putStrLn "doing stuff" s <- mkSong libstate "my crazy song" let x = last $ takeWhile (< 10000000) $ iterate (+1) 1 putStrLn (show x) putStrLn "done with stuff" shutdownLib libstate putStrLn "bye"

On Sat, 18 Dec 2004 20:24:47 -0500, Robert Dockins
Well that shouldn't affect the functionality. The weak pointer was only a way of attatching a finalizer to the Playback object. It is true that I should probably wrap up the the SoundPlaybackRaw inside the SoundPlayback as well, to save CPU, but it shouldn't matter for the core functionality.
I'm not sure I understand.
Well not including the SamplePlaybackRaw in the SamplePlayback datatype means that the SamplePlaybackRaw might get prematurely GC'd, but if the finalizer had worked that wouldn't have mattered, it would've only meant that the keep-alive loop might have been started earlier. Anyway, the reason for not wanting to do polling-loops was simply a performance issue. FMOD is one of the fastest, if not THE fastest, sound libraries out there so I didn't want to "ruin it" by attatching too much extra "fluff". That's why I wanted to start a polling loop ony when it was absolutely necessary, ie only when the Playback object was being GC'd (which meant that GC'ing of the sound data might be imminent). But anyway, I decided to bite the bullet and do it "the easy way". Here's the final, short-n-sweet (albeit slightly inefficient), solution: -- A playback of a sample resource newtype SamplePlayback = SP (MVar CInt) -- play, and keep data alive until playback is done samplePlay' :: MVar CInt -> SoundSample -> IO () samplePlay' var s = do ch <- withForeignPtr s (fsound_PlaySound (-1)) putMVar var ch let loop = do b <- isPlaying (SP var) case b of True -> do touchForeignPtr s threadDelay 1000000 -- wait one second loop False -> return () loop -- asynchronous playback action samplePlay :: SoundSample -> IO SamplePlayback samplePlay s = do var <- newEmptyMVar -- spawn a new playback thread forkIO (samplePlay' var s) -- wait for the MVar to get filled in let loop = do yield b <- isEmptyMVar var if b then loop else return () loop return (SP var) isPlaying :: SamplePlayback -> IO Bool isPlaying (SP var) = do b <- withMVar var (fsound_IsPlaying) return (cscharToBool b) -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862
participants (7)
-
Ben Lippmeier
-
Ben Rudiak-Gould
-
David Roundy
-
Jules Bean
-
Keean Schupke
-
Robert Dockins
-
Sebastian Sylvan