[Haskell-cafe] RFC and Announcement: HLADSPA, LADSPA for Haskell

Alfonso Acosta alfonso.acosta at gmail.com
Sun Nov 5 15:56:02 EST 2006


Hi all,

Let me introduce myself. I'm a computer science engineering student,
writing his masters thesis about a VHDL translator for ForSyDe
(http://www.imit.kth.se/info/FOFU/ForSyDe/ , a Hardware Description
Language embedded in Haskell)

In order to show a practical application of ForSyDe I decided to try
with its audio processing design capabilities. For that purpouse I
decided to port LADSPA (http://www.ladspa.org/ ) to Haskell.

The result so far is HLADSPA 0.1, which can be downloaded from
http://www.student.nada.kth.se/~alfonsoa/HLADSPA-0.1.tgz and contains
a testing plugin (Null.hs).

It requires GHC>=6.6 due to the use of template haskell and
existentially quantified records (read on for more on this).

Even for people not interested in LADSPA itself, the project can be
regarded as an example of how to create plugins and shared libraries
in Haskell without making use hs-plugins.

I'll be happy to read any comments and/or questions regarding the
project. Here are the design questions on my side:

The most important part for creating the HLADSPA, was modelling the
LADSPA header file (http://www.ladspa.org/ladspa_sdk/ladspa.h.txt ) in
Haskell (http://www.student.nada.kth.se/~alfonsoa/HLADSPA-0.1/HLADSPA.hs
)

The essential code snippet is,

=====================
-- Existentially quantified records allow the plugin developer
-- to chose hd and id types at will and, allowing to declare "heterogeneous"
-- Descriptor lists. Drawback: it only works in ghc 6.6 and
-- makes the design tricky. The problem comes from modelling (void*) in Haskell

-- id is the implementation data
data Descriptor = forall id.
     Descriptor {uniqueID               :: LadspaIndex,
                 label                  :: String,
                 properties             :: LadspaProperties,
                 name, maker, copyright :: String,
                 portCount              :: LadspaIndex,
                 portDescriptors        :: [PortDescriptor],
                 portNames              :: [String],
                 portRangeHints         :: [PortRangeHint],
                 _implementationData    :: id,
                 _instantiate           :: Descriptor -> LadspaIndex ->
                                           Maybe Instance}

-- hd is the handle
data Instance = forall hd.
    Instance {
              _this :: hd, -- initial handle
-- In this case we are using lists to represent the port I/O buffers, so the
-- port connections (buffer pointers of ports) is handled by the marshaller
--            connectPort   :: (hd -> LadspaIndex -> Ptr LadspaData -> IO hd)
              _activate               :: Maybe(hd -> IO ()),
              -- (LadspaIndex,PortData) indicates the portnumber and its data
              _run                    :: hd                       ->
                                         LadspaIndex              ->
                                         [(LadspaIndex,PortData)] ->
                                         ([(LadspaIndex,PortData)], hd),
-- Not yet implemented (is not mandatory for a plugin to provide them)
--            _runAdding              ::
--            _setAddingGain          ::
              _deactivate             :: Maybe (hd -> IO ()),
              _cleanup                :: hd -> IO () }
=====================


This is the best solution I could come up with and I'm not still happy
with it. The trickiest part, was modelling (void*) in Haskell.

I know that using lists for I/O buffers is inefficient but I coded it
having ForSyDe in mind. If people show interest I can code a faster
StorableArray-version.


Here are my questions.

* I'm using GHC's existentially quantified records extension to hide
the hd and id parameters because the plugin programmer has to be able
to provide a collection of Descriptor (a Descriptor list in current
release). I would love to find a solution in plain Haskell98. Any
ideas?

* This approach requires splitting the original C LADSPA_Descriptor
struct in the Descriptor and Instance Haskell types, which leads to a
design error:  there are functions (e.g. _run, _activate ... ) in
Instance which really belong to Descriptor (those functions shouldn't
change with the plugin instance). For example, with this approach, it
is not possible to tell the plugin host if activate() or deactivate()
will be used because this is "asked" any instances is created.

* Real Time support in HLADSPA. From LADSPA's documentation:

   "Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
   is capable of running not only in a conventional host but also in a
   `hard real-time' environment.  To qualify for this the plugin must
   satisfy all of the following:
      (1) The plugin must not use malloc(), free() or other heap memory
   management within its run() or run_adding() functions. All new
   memory used in run() must be managed via the stack. These
   restrictions only apply to the run() function."

Should I forget about HARD_RT_CAPABLE with Haskell? Is there a way to
control the use of the heap by a function?


* I'm not so sure about the types of _activate, _deactivate and
_cleanup. I don't even know if I should include them because they only
seem to be required by a language with side effects. Furthermore,
_activate() and _deactivate() don't seem to have sense with current
implementation cause they are "separated from instantiate() to aid
real-time support" and using lists as I/O buffers discards RT support
(see above)


Thanks in advance for your comments/answers,

Alfonso Acosta


PS1: Big thanks and claps for the people at #haskell at Freenode . They
helped a lot to make this initial release possible.
PS2: I would like to get the project hosted at the darcs repository at
haskell.org. Do you consider it interesting enough for it?


More information about the Haskell-Cafe mailing list