I just uploaded the fruit of a little side project. Hemkay [1] is an
oldschool module music [2] player that performs all the hard work in
Haskell. If there was any goal, it was to express the transformation
from the song structure to the output of the mixer as a series of
function compositions, maintaining a style that one might call idiomatic
Haskell. Considering the dirtiness of the format in question, I'm quite
pleased with the initial version.
Still, I'd be curious to see how the overall quality of the code could
be improved. In particular, retrieving and updating record fields is
somewhat inconvenient. Also, the actual mixing (limited to the mixChunk
function) is embarrassingly slow, and I wonder how much it could be
improved without leaving the pure world.
The program uses Portaudio for playback, but that might easily change in
the future. The problem is that I couldn't get sound to work smoothly
when producing samples in batches, so I'm sending them off one by one
(!) at the moment, which doesn't help with performance either. I'm open
to suggestions as to what library to use to push data to the sound card.
Gergely
[1] http://hackage.haskell.org/package/hemkay
[2] http://en.wikipedia.org/wiki/MOD_(file_format)
--
http://www.fastmail.fm - Or how I learned to stop worrying and
love email again
_______________________________________________
Haskell-Cafe mailing list
Haskel...@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe
Did you try the OpenAL binding? Not sure if that works.
2009/12/14 Patai Gergely <patai_...@fastmail.fm>:
> Did you try the OpenAL binding? Not sure if that works.
No, I haven't really looked hard, to be honest. Portaudio looked simple
enough, so I picked it. But it could be anything, since the mixing is
done on my side anyway, and all I need is a way to push (preferably
Float) samples to the sound unit. I'm sure there are several viable
options, so the deciding factor is rather portability, and secondly ease
of use.
--
http://www.fastmail.fm - The professional email service
The interface for writeStream is not very fortunate. It forces me to
pack samples into lists no matter what, even if the interleaved list is
actually easier to produce. So it should probably provide alternative
interfaces for interleaved lists (which would actually be possible right
away if writeStream didn't ignore its third argument altogether), and
maybe an array interface as well. I don't know if the callback interface
works better, maybe that's also worth a shot.
Ultimately, it would be probably best if it gave the programmer a higher
abstraction, where it is passed a potentially infinite list of samples,
and takes care of all the buffering duties. Also, this could be made to
play nice with stream fusion in order to get the maximum performance out
of it.
Gergely
--
http://www.fastmail.fm - One of many happy users:
http://www.fastmail.fm/docs/quotes.html
2009/12/14 Patai Gergely <patai_...@fastmail.fm>
--- On Mon, 12/14/09, M Xyz <functionall...@yahoo.com> wrote:
From: M Xyz <functionall...@yahoo.com>
Subject: Re: [Haskell-cafe] ANN: Hemkay, the 100% Haskell MOD player
To: "Patai Gergely" <patai_...@fastmail.fm>
Date: Monday, December 14, 2009, 5:50 PM
--- On Mon, 12/14/09, Patai Gergely <patai_...@fastmail.fm> wrote:
Also, the actual mixing (limited to the mixChunk
function) is embarrassingly slow, and I wonder how much it could be
improved without leaving the pure world.
The program uses Portaudio for playback...
Patai, I asked a similar question about a week ago, inquiring about efficient buffers
for audio. I got a good response:
http://thread.gmane.org/gmane.comp.lang.haskell.cafe/67258/focus=67293
Working with immutable trees instead of arrays still freaks me out, but page 289 of RWH
actually made me feel a little better about it. :)
I could try rewriting the mixer to return an iterative generator
function instead, which could be passed to an appropriate sound
interface. The latter would have to be written once and for all.
Gergely
--
http://www.fastmail.fm - Does exactly what it says on the tin
Your message has motivated me to publish my own PortAudio binding,
which provides a simpler, more efficient callback-based interface:
http://github.com/mietek/portaudio
The documentation is incomplete, but along with the example program,
it should be enough to get you going:
http://github.com/mietek/portaudio/blob/master/examples/Play.hs
Best regards,
--
Mietek Bąk
Gergely
--
http://www.fastmail.fm - The way an email service should be
> Your message has motivated me to publish my own PortAudio binding,
> which provides a simpler, more efficient callback-based interface:
> http://github.com/mietek/portaudio
I tried this, and after rewriting the code a bit, I managed to decrease
CPU load by 70-80%, which is not bad for starters. However, I'm getting
random segfaults, and I've no idea why.
Here's the modified player module (hpaste kindly rearranged the empty
lines for some reason):
http://hpaste.org/fastcgi/hpaste.fcgi/view?id=14347
Instead of building a list of samples right away, I use an unfoldr-style
generator to fill the buffer, whose initial state is created by
mixGenerator, and its stepper function is nextSample.
In order to get this working, I renamed your module to avoid conflict
with the other PortAudio binding, and I had to change the dependencies
in the cabal file to base >= 4 && < 5 because of the exception handling
code.
There's also some broken MVar-based code to handle the end of the song,
you can ignore that for the time being.
Gergely
--
http://www.fastmail.fm - The professional email service
> Hello all,
>
> I just uploaded the fruit of a little side project. Hemkay [1] is an
> oldschool module music [2] player that performs all the hard work in
> Haskell.
Cool.
The most complicated I tried was to import OctaMED printout to Haskore:
http://darcs.haskell.org/haskore/src/Haskore/Interface/MED/Text.hs
http://hackage.haskell.org/packages/archive/haskore/0.1/doc/html/Haskore-Interface-MED-Text.html
> Still, I'd be curious to see how the overall quality of the code could
> be improved. In particular, retrieving and updating record fields is
> somewhat inconvenient. Also, the actual mixing (limited to the mixChunk
> function) is embarrassingly slow, and I wonder how much it could be
> improved without leaving the pure world.
I have a function for mixing sounds at different (relative) start times. I
feel that it does not get maximum speed in GHC, but is still ready for
realtime application.
http://hackage.haskell.org/packages/archive/synthesizer-core/0.2.1/doc/html/Synthesizer-Storable-Cut.html#v%3Aarrange
> I have a function for mixing sounds at different (relative) start times.
> I feel that it does not get maximum speed in GHC, but is still ready for
> realtime application.
> http://hackage.haskell.org/packages/archive/synthesizer-core/0.2.1/doc/html/Synthesizer-Storable-Cut.html#v%3Aarrange
And how can you mix that with changing frequencies (effectively
resampling on the fly)?
By the way, the latest code I have in my hands typically uses 2-3% CPU
time on my machine (Core 2 Duo at 2 GHz), which is okay for now, but I
think going at least ten times as fast is a realistic goal.
Gergely
--
http://www.fastmail.fm - Accessible with your email software
or over the web
>> I have a function for mixing sounds at different (relative) start times.
>> I feel that it does not get maximum speed in GHC, but is still ready for
>> realtime application.
>> http://hackage.haskell.org/packages/archive/synthesizer-core/0.2.1/doc/html/Synthesizer-Storable-Cut.html#v%3Aarrange
> And how can you mix that with changing frequencies (effectively
> resampling on the fly)?
I would do resampling (with some of the Interpolation routines) and
mixing in two steps, that is I would prepare (lazy) storable vectors
with the resampled sounds and mix them. Since Haskell is lazy, this is
still somehow "on the fly", although one could still wish to eliminate
the interim storable vectors.
You could use stream fusion, although you will need to adapt that for
the interpolation, but it should work.
--
http://www.fastmail.fm - The way an email service should be
_______________________________________________
I did some refactoring, and separated the device independent part of
Hemkay into a package of its own, hemkay-core [1]. This is a library
that provides facilities to load MOD music and render it in various
ways, including a direct-to-buffer option that's considerably more
efficient than creating a list of samples. You can use it to create a
MOD player with any sound-making API or even write the mixer output into
a wav file.
The hemkay package [2] is now reduced to an example that shows how to
use the core library with PortAudio. It is also considerably faster than
the previous version, because now I push as many samples as possible at
a time without blocking. I had no luck with Mietek's callback interface
so far, because it keeps randomly segfaulting on me, but when it works,
it's about four times as fast as the current player. You are encouraged
to try adapting it (using mixToBuffer) to other systems.
Gergely
[1] http://hackage.haskell.org/package/hemkay-core
[2] http://hackage.haskell.org/package/hemkay
--
http://www.fastmail.fm - Choose from over 50 domains or use your own
Hmm, it's a pity that they don't build on Hackage due to a conflict
between two bytestring versions -- the current and the one binary was
compiled with, I assume. It would be nice if at least the haddock docs
could be generated when this happens.
Gergely
--
http://www.fastmail.fm - Access all of your messages and folders
wherever you are
> And is that straightforward considering the peculiarities of tracked
> music? After all, frequency can change between ticks thanks to
> portamento effects, and samples can loop or end in the middle of a tick.
> Do I have to trim and pad the samples manually to be able to describe
> these transformations?
I see no problem. I would generate a frequency and a volume control
curve for each channel and apply this to the played instrument, then I
would mix it. It is the strength of Haskell to separate everything into
logical steps and let laziness do things simultaneously. Stream fusion
can eliminate interim lists, and final conversion to storable vector using
http://hackage.haskell.org/package/storablevector-streamfusion/
can eliminate lists at all.
> It is the strength of Haskell to separate everything into
> logical steps and let laziness do things simultaneously. Stream fusion
> can eliminate interim lists, and final conversion to storable vector
> using http://hackage.haskell.org/package/storablevector-streamfusion/
> can eliminate lists at all.
But in my understanding that elimination is only possible if lists are
not used as persistent containers, only to mimic control structures. Now
I rely on samples being stored as lists, so I can represent looping
samples with infinite lists and not worry about the wrap-around at all.
So in order to have any chance for fusion I'd have to store samples as
vectors and wrap them in some kind of unfold mechanism to turn them into
lists that can be potentially fused away. In other words, besides a
'good consumer', I need a 'good producer' too.
However, there seems to be a conflict between the nature of mixing and
stream processing when it comes to efficiency. As it turns out, it's
more efficient to process channels one by one within a chunk instead of
producing samples one by one. It takes a lot less context switching to
first generate the output of channel 1, then generate channel 2 (and
simultaneously add it to the mix) and so on, than to mix sample 1 of all
channels, then sample 2 etc., since we can write much tighter loops when
we only deal with one channel at a time. On the other hand, stream
fusion is naturally fit to generate samples one by one. It looks like
the general solution requires a fusable transpose operation, otherwise
we're back to hand-coding the mixer. Have you found a satisfying
solution to this problem?
Gergely
--
http://www.fastmail.fm - A no graphics, no pop-ups email service
>> It is the strength of Haskell to separate everything into
>> logical steps and let laziness do things simultaneously. Stream fusion
>> can eliminate interim lists, and final conversion to storable vector
>> using http://hackage.haskell.org/package/storablevector-streamfusion/
>> can eliminate lists at all.
>
> But in my understanding that elimination is only possible if lists are
> not used as persistent containers, only to mimic control structures. Now
> I rely on samples being stored as lists, so I can represent looping
> samples with infinite lists and not worry about the wrap-around at all.
> So in order to have any chance for fusion I'd have to store samples as
> vectors and wrap them in some kind of unfold mechanism to turn them into
> lists that can be potentially fused away. In other words, besides a
> 'good consumer', I need a 'good producer' too.
Right. The conversion from storablevector to stream-fusion:Stream is
such a good producer.
> However, there seems to be a conflict between the nature of mixing and
> stream processing when it comes to efficiency. As it turns out, it's
> more efficient to process channels one by one within a chunk instead of
> producing samples one by one. It takes a lot less context switching to
> first generate the output of channel 1, then generate channel 2 (and
> simultaneously add it to the mix) and so on, than to mix sample 1 of all
> channels, then sample 2 etc., since we can write much tighter loops when
> we only deal with one channel at a time.
Yes, I would also do it this way. So in the end you will have some
storablevectors as intermediate data structures.
I wonder if data-parallel haskell won't be able to help here, mod
rendering is a
scatter-gather style of processing, the problem is that the different channels
trigger different processing.