On Sat, 9 Jul 2011, Heinrich Apfelmus wrote:

Oh, I am addressed explicitly, thanks! Yes, GUI for 'streamed' would be nice, too. In the meantime I switched from an approach with lazy lists to one with arrow-like stream processors. This way I could resolve all issues with wrong timing and inappropriate waiting, but now code looks more low-level. There are some examples lying around, that I even not started to implement, because I expect that implementing them in the current low-level way will yield nasty bugs. Thus I am highly interested in more sophisticated MIDI stream editor combinators. I expect that inspiration from other FRP frameworks would help me.

Of course, my intention was that you throw away your stream processors and use reactive-banana for everything. ;D I have made sure that it can be used both for real-time and for offline computations.

Could you expand a little on your arrow-like stream processors? What do the arrows look like,

   data SF a b = SF (a -> (b, SF a b))

?

My stream processors are not Arrows, because 'first' cannot be implemented. However, 'arr' and '.' can be implemented.

As far as I understand, the SF banana can only produce an output if an input event arrives. But my stream processor can set alarms in order to produce a clock signal. This way they can generate output without any input.

Currently my stream processors are defined using an explicit state, that is hidden using an existential quantifier, but of course I would prefer a Haskell 98 solution.

For example, an arpeggiator stream processor consists of two functions:
 1. Receive key up/down events and keep track of the currently pressed keys.
 2. Receive a MIDI controller that controls the tempo of the arpeggiator.
    The stream processor sets the alarm according the current tempo
    and at every alarm event it sends a single key
    chosen from the set of the currently pressed keys.

This example shows, that a stream processor cannot support Arrow.first, that is extending (arrow a b) to (arrow (a,c) (b,c)), since for an alarm event, we have no input of type c that could be passed through.

Currently I have build the two tasks into one stream processor. I would like to split these into two processors. This looks difficult to me, since the first stream processor (for management of pressed keys) must provide a state (the set of pressed keys) whenever the second stream processor (for clock generation) needs it.

Which additional primitives did you include, for instance

   switch :: SF in (out, Maybe t) -> (t -> SF in out) -> SF in out
   delay  :: Time -> SF a b -> SF a b

?

Since I have no Arrow instance I even have to write my own combinators that are counterparts to (&&&) and friends.

And of course, I am particularly interested in the nasty examples that you came up with. :)

For example I want to write a Guitar simulator: You press a set of keys together and the processor converts this into successive tones on a guitar. Technically I like to do it this way: When a key is pressed, collect all key press events in the following 10ms. After this period emit all pressed keys according to a certain pattern. When one of the pressed keys is released, then send key-press(!) events for all currently pressed keys according to another pattern. Repeat this cycle. Manage somehow the keys that are pressed after the first key-down-collecting phase and the keys that are released during this initial phase. Ignore them or do something more sensible about them, but make sure that in the output all key-down events are eventually matched with a key-up event and that for the same key you never send two successive key-down events or two successive key-up events. That is, for the same key, key-up and key-down events must alternate. An exception might be if you receive bogus input. But even then, the number of key-up and key-down events for one note in the output shall match, whenever this is true for the input.

For lazy lists you can do time related processing by splitting the list of events and process the prefix differently from the suffix and then concatenate the results. This is easy and declarative, but you have no guarantee that the resulting stream processing is causal and that the timing is correct. (The second problem arises when merging two event lists. Merging implies that when waiting for two events then actually the system waits for the later of the events, but we need to wait for the first of the two events.)

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to