[Haskell-cafe] Call for GUI examples - Functional Reactive Programming

Heinrich Apfelmus apfelmus at quantentunnel.de
Sun Jul 10 09:19:14 CEST 2011


Henning Thielemann wrote:
> Heinrich Apfelmus wrote:
>>
>> 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.
>
>  [..]
> 
> 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.

Ok, that sounds nasty.

>> 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.

The guitar simulator is a great example! And much to my surprise, it's 
already possible to implement (parts of) it in reactive-banana 0.4.1! At 
least in the real-time version, where we have access to timers from 
wxHaskell.

In particular, have a look at the new  Wave.hs  example, to be found at 
the bottom of

   http://haskell.org/haskellwiki/Reactive-banana/Examples

It generates a wave-like pattern whenever you click on of the buttons 
(strum a chord). When you click two buttons in rapid succession (think 
left = key-down, right = key-up), the second wave will not start after 
the first one has completed.

(I think this example also makes it clear that it's already to possible 
to implement the arpeggiator as you described with timers.)

Of course, the code is not particularly high-level; after all, I am 
essentially duplicating event streams as [(Duration,a)]. Then again, we 
cannot describe each wave as  Event a  because we need a notion of "this 
event stream has finished" in order to be able to stall the second wave. 
  In other words, the vanilla  Event a  is potentially infinite, there 
is no way to test whether it has "finished", so *some* other notion is 
needed anyway.

Question: how would you actually like to describe the guitar simulator 
at a high-level? Did you already wish for some specific combinators? 
Assume that you had something like reactive-banana available and imagine 
that there were a benevolent djinn granting you three new primitive 
combinators of your choice.


Best regards,
Heinrich Apfelmus

--
http://apfelmus.nfshost.com




More information about the Haskell-Cafe mailing list