RFC and Announcement: HLADSPA, LADSPA for Haskell

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@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?

On 11/5/06, Alfonso Acosta
* 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.
I meant "because this is 'asked' _before_ any instances are created", sorry

On Sun, 5 Nov 2006, Alfonso Acosta wrote:
PS1: Big thanks and claps for the people at #haskell@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?
Yes, definitely. Could you also please add some note to http://www.haskell.org/haskellwiki/Libraries_and_tools/Music_and_sound

lemming:
On Sun, 5 Nov 2006, Alfonso Acosta wrote:
PS1: Big thanks and claps for the people at #haskell@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?
Yes, definitely. Could you also please add some note to http://www.haskell.org/haskellwiki/Libraries_and_tools/Music_and_sound
I agree, and would remark for anyone reading: Please add your projects, whether they are applications, libraries, tools, darcs repos, to : http://haskell.org/haskellwiki/Libraries_and_tools *All* Haskell code that's available, and fit to compile should be findable from that page. -- Don

PS2: I would like to get the project hosted at the darcs repository at haskell.org. Do you consider it interesting enough for it?
Yes, definitely. Could you also please add some note to http://www.haskell.org/haskellwiki/Libraries_and_tools/Music_and_sound
That's something I had in mind from the very beginning, but I didn't want to add anything to the Libraries section before deciding upon where I was going to host it. As suggested at http://haskell.org/haskellwiki/How_to_write_a_Haskell_program#Hosting I'll contact Simon Marlow to ask for it. Can anyone confirm if wrting to simonmar _at_ microsoft _dot_ com is the right way to reach him for his purpose? (I couldn't find any haskell.org-specific address)

Just in case anyone is interested. I finally overcame those
difficulties and released HLADSPA 0.2 which can be found at
http://www.student.nada.kth.se/~alfonsoa/HLADSPA/HLADSPA-0.2.tgz
whose changelog is
2006-11-21 Version 0.2
* API refactored (now the plugins are coded in pure haskell98 + TH
and it doesnt depend on the latest (6.6) version of ghc anymore)
* Created proper Make system
(http://hackage.haskell.org/trac/ghc/ticket/933 + the use
of template records didn't allow it)
* Fixed lots of major and minor bugs
* Added Amp and (currently broken) ForSyDeEq plugins
On 11/5/06, Alfonso Acosta
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
participants (3)
-
Alfonso Acosta
-
dons@cse.unsw.edu.au
-
Henning Thielemann