
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?