>
> module Euterpea.Examples.MUIExamples where
> import Euterpea
> import Data.Maybe (mapMaybe)
=============
Chord builder
Here is a simple program that plays the selected chord when a root
note is entered using a Midi input device.
We define a mapping between chord extensions and their intervals with
respect to the root note.
> chordIntervals = [("Maj", [4,3,5]),
> ("Maj7", [4,3,4,1]),
> ("Maj9", [2,2,3,4,1]),
> ("6", [4,3,2,3]),
> ("m", [3,4,5]),
> ("m7", [3,4,3,2]),
> ("m9", [2,1,4,3,2]),
> ("m7b5", [3,3,4,2]),
> ("mMaj7", [3,4,4,1]),
> ("dim", [3,3,3]),
> ("7", [4,3,3,2]),
> ("9", [2,2,3,3,2]),
> ("7b9", [1,3,3,3,2])]
We display the list of extensions on the screen as radio buttons for
the user to click on.
The toChord function takes in the index of the selected chord extension
and an input message as the root note, and outputs the notes of
the selected chord based on the root note. For simplicity, we only
process the head of the message list and ignore everything else.
> toChord :: Int -> [MidiMessage] -> [MidiMessage]
> toChord i (ms@(m:_)) =
> case m of
> Std (NoteOn c k v) -> f NoteOn c k v
> Std (NoteOff c k v) -> f NoteOff c k v
> _ -> ms
> where f g c k v = map (\k -> Std (g c k v))
> (scanl (+) k (snd (chordIntervals !! i)))
The UI is arranged in the following way. On the left side, the list
of input and output devices are displayed top-down. On the right is
the list of chord extensions. We take the name of each extension from
the chordIntervals list to create the radio buttons.
When a Midi input event occurs, the input message and the currently
selected index to the list of chords is sent to the toChord function,
and the resulting chord is sent to the output device.
> buildChord = runMUI (defaultMUIParams {uiSize=(500,500), uiTitle="Chord Builder"}) $ leftRight $ proc _ -> do
> (mi,mo) <- topDown (selectInput &&& selectOutput) -< ()
> m <- midiIn -< mi
> i <- topDown $ title "Extension" $ radio (fst (unzip chordIntervals)) 0 -< ()
> midiOut -< (mo, fmap (toChord i) m)
=================
Bifurcate example
Here is an example with some ideas borrowed from Gary Lee Nelson's
composition "Bifurcate me, Baby!"
The basic idea is to evaluate the logistic growth function at
different points and convert the value to a musical note. The growth
function is given by
x_(n+1) = r x_n (1 - x_n)
We start with an initial population x_0 and iteratively apply the
growth function to it, where r is the growth rate. For certain values
of r, the population stablizes to a certain value, but as r increases,
the period doubles, quadruples, and eventually leads to chaos. It is
one of the classic examples in chaos theory.
First we define the growth function which, given a rate r and
current population x, generates the next population.
> grow :: Double -> Double -> Double
> grow r x = r * x * (1x)
Then we define a signal 'tick' that pulsates at a given frequency
specified by slider f. This is the signal that will drive the
simulation. The timer function takes in a frequency.
The next thing we need is a time-varying population. This is where
the delay function and the rec keyword come in handy. We initialize
the 'pop' signal with the value 0.1, and then on every tick, we
grow it with the instantaneous value of the growth rate signal.
We can now write a simple function that maps a population value to a
musical note:
> popToNote :: Double -> [MidiMessage]
> popToNote x = [ANote 0 n 64 0.05] where n = truncate (x * 127)
Finally, to play the note, we simply send the current population to
popToNote, and send the result to the selected Midi output device.
> bifurcate = runMUI (defaultMUIParams {uiSize=(300,500), uiTitle="Bifurcate!"}) $ proc _ -> do
> mo <- selectOutput -< ()
> f <- title "Frequency" $ withDisplay (hSlider (1, 10) 1) -< ()
> r <- title "Growth rate" $ withDisplay (hSlider (2.4, 4.0) 2.4) -< ()
>
> tick <- timer -< 1.0 / f
> rec pop <- delay 0.1 -< maybe pop (const $ grow r pop) tick
>
> _ <- title "Population" $ display -< pop
> midiOut -< (mo, fmap (const (popToNote pop)) tick)
============
Echo example
Here we present a program that takes in a Midi event stream and, in
addition to playing each note received from the input device, it also
echoes the note at a given rate, while playing each successive note
more softly until the velocity reduces to 0.
The key component we need for this problem is a delay function that
can delay a given event signal for a certain amount of time. vdelay
takes in the amount of time to delay and an input signal
and outputs the delayed signal.
There are two signals we want to attenuate. One is the signal coming
from the input device, and the other is the delayed and decayed signal
containing the echoes. In the code shown below, they are denoted as m
and s, respectively. We merge the two event streams into one and then
remove events with empty Midi messages by replacing them with Nothing.
The resulting signal, m', is then sent to the Midi output device.
The echo signal s is created recursively from m' as follows. We examine
the signal m' and decay any events that we find there, using the decay
rate indicated by the instantaneous value from the slider r. This
decayed signal is fed into the vdelay signal function along with
the amount of time to delay (the inverse of the echo frequency,
which is given by the other slider f).
> echo = runMUI (defaultMUIParams {uiSize=(500,500), uiTitle="Echo"}) $ proc _ -> do
> mi <- selectInput -< ()
> mo <- selectOutput -< ()
> m <- midiIn -< mi
> r <- title "Decay rate" $ withDisplay (hSlider (0, 0.9) 0.5) -< ()
> f <- title "Echoing frequency" $ withDisplay (hSlider (1, 10) 10) -< ()
>
> rec let m' = removeNull $ mergeS m s
> s <- vdelay -< (1.0 / f, fmap (mapMaybe (decay 0.1 r)) m')
>
> midiOut -< (mo, m')
> mergeS :: Maybe [MidiMessage] -> Maybe [MidiMessage] -> Maybe [MidiMessage]
> mergeS (Just ns1) (Just ns2) = Just (ns1 ++ ns2)
> mergeS n1 Nothing = n1
> mergeS Nothing n2 = n2
> removeNull :: Maybe [MidiMessage] -> Maybe [MidiMessage]
> removeNull Nothing = Nothing
> removeNull (Just []) = Nothing
> removeNull (Just xs) = Just xs
> decay :: Time -> Double -> MidiMessage -> Maybe MidiMessage
> decay dur r m =
> let f c k v d = if v > 0
> then Just (ANote c k (truncate (fromIntegral v * r)) d)
> else Nothing
> in case m of
> ANote c k v d -> f c k v d
> Std (NoteOn c k v) -> f c k v dur
> _ -> Nothing