-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Programmatically edit MIDI event streams via ALSA -- -- Please note: This package shall be replaced by reactive-balsa -- in the future. -- -- MIDI is the Musical Instrument Digital Interface, ALSA is the Advanced -- Linux Sound Architecture. This package allows to manipulate a sequence -- of MIDI events via ALSA. It is intended to be plugged as a playing -- assistant between a MIDI input device (e.g. a keyboard or a controller -- bank) and a MIDI controlled synthesizer (e.g. a software synthesizer -- or an external synthesizer). For software synthesizers see the Haskell -- packages synthesizer-alsa, synthesizer-llvm, -- supercollider-midi, hsc3, YampaSynth or the -- C packages fluidsynth and Timidity. -- -- Applications include: Remapping of channels, controller, instruments, -- keys, Keyboard splitting, Conversion from notes to controllers, Latch -- mode, Convert parallel chords to serial patterns, Automated change of -- MIDI controllers, Echo simulation. -- -- It is intended that you write programs for MIDI stream manipulation. -- It is not intended to provide an executable program with all the -- functionality available in a custom programming interface. It is most -- fun to play with the stream editors in GHCi. However we provide an -- example module that demonstrates various effects. @package streamed @version 0.2 module Sound.MIDI.ALSA.Common data Handle Handle :: T DuplexMode -> T -> T -> T -> T -> Handle sequ :: Handle -> T DuplexMode client :: Handle -> T portPublic :: Handle -> T portPrivate :: Handle -> T queue :: Handle -> T init :: IO Handle exit :: Handle -> IO () with :: ReaderT Handle IO a -> IO a -- | make ALSA set the time stamps in incoming events setTimeStamping :: ReaderT Handle IO () startQueue :: ReaderT Handle IO () connect :: String -> String -> ReaderT Handle IO () connectTimidity :: ReaderT Handle IO () connectLLVM :: ReaderT Handle IO () connectSuperCollider :: ReaderT Handle IO () sendNote :: Channel -> Time -> Velocity -> Pitch -> ReaderT Handle IO () sendKey :: Channel -> Bool -> Velocity -> Pitch -> ReaderT Handle IO () sendController :: Channel -> Controller -> Int -> ReaderT Handle IO () sendProgram :: Channel -> Program -> ReaderT Handle IO () sendMode :: Channel -> T -> ReaderT Handle IO () channel :: Int -> Channel pitch :: Int -> Pitch velocity :: Int -> Velocity controller :: Int -> Controller program :: Int -> Program normalVelocity :: Velocity -- | The Time types are used instead of floating point types, -- because the latter ones caused unpredictable 'negative number' errors. -- The denominator must always be a power of 10, this way we can prevent -- unlimited grow of denominators. type TimeAbs = Rational newtype Time Time :: Rational -> Time deconsTime :: Time -> Rational consTime :: String -> Rational -> Time incTime :: Time -> TimeAbs -> TimeAbs nano :: Num a => a makeEvent :: Handle -> TimeAbs -> Data -> T makeEcho :: Handle -> TimeAbs -> Custom -> T outputEvent :: TimeAbs -> Data -> ReaderT Handle IO () simpleNote :: Channel -> Pitch -> Velocity -> Note -- | The times are relative to the start time of the bundle and do not need -- to be ordered. type Bundle a = [(Time, a)] type EventDataBundle = Bundle Data singletonBundle :: a -> Bundle a immediateBundle :: [a] -> Bundle a timeFromStamp :: TimeStamp -> Time defaultTempoCtrl :: (Channel, Controller) -- | Transpose a note event by the given number of semitones. Non-note -- events are returned without modification. If by transposition a note -- leaves the range of representable MIDI notes, then we return Nothing. transpose :: Int -> Data -> Maybe Data -- | Swap order of keys. Non-note events are returned without modification. -- If by reversing a note leaves the range of representable MIDI notes, -- then we return Nothing. reverse :: Data -> Maybe Data setChannel :: Channel -> Data -> Data -- |
--   > replaceProgram [1,2,3,4] 5 [10,11,12,13]
--   (True,[10,11,2,13])
--   
replaceProgram :: [Int32] -> Int32 -> [Int32] -> (Bool, [Int32]) programFromBanks :: [Int32] -> [Int32] -> Int32 -- | Interpret program changes as a kind of bank switches in order to -- increase the range of instruments that can be selected via a block of -- patch select buttons. -- -- programAsBanks ns divides the first sum ns -- instruments into sections of sizes ns!!0, ns!!1, .... Each -- program in those sections is interpreted as a bank in a hierarchy, -- where the lower program numbers are the least significant banks. -- Programs from sum ns on are passed through as they are. -- product ns is the number of instruments that you can address -- using this trick. In order to avoid overflow it should be less than -- 128. -- -- E.g. programAsBanks [n,m] interprets subsequent program -- changes to a (0<=a<n) and n+b -- (0<=b<m) as a program change to b*n+a. -- programAsBanks [8,8] allows to select 64 instruments by 16 -- program change buttons, whereas programAsBanks [8,4,4] allows -- to address the full range of MIDI 128 instruments with the same number -- of buttons. programsAsBanks :: [Int32] -> Data -> State [Int32] Data nextProgram :: Note -> State [Program] [Data] -- | Before every note switch to another instrument according to a list of -- programs given as state of the State monad. I do not know how to -- handle multiple channels in a reasonable way. Currently I just switch -- the instrument independent from the channel, and send the program -- switch to the same channel as the beginning note. traversePrograms :: Data -> State [Program] [Data] -- | This function extends traversePrograms. It reacts on external -- program changes by seeking an according program in the list. This way -- we can reset the pointer into the instrument list. However the search -- must be limited in order to prevent an infinite loop if we receive a -- program that is not contained in the list. traverseProgramsSeek :: Int -> Data -> State [Program] [Data] reduceNoteVelocity :: Word8 -> Note -> Note delayAdd :: Word8 -> Time -> Data -> EventDataBundle -- | Map NoteOn events to a controller value. This way you may play notes -- via the resonance frequency of a filter. controllerFromNote :: (Int -> Int) -> Controller -> Data -> Maybe Data type KeySet = Map (Pitch, Channel) Velocity type KeyQueue = [((Pitch, Channel), Velocity)] eventsFromKey :: Time -> ((Pitch, Channel), Velocity) -> EventDataBundle selectFromLimittedChord :: Int -> Time -> KeyQueue -> EventDataBundle -- | Generate notes according to the key set, where notes for negative and -- too large indices are padded with keys that are transposed by octaves. selectFromOctaveChord :: Int -> Time -> KeyQueue -> EventDataBundle selectFromChord :: Integer -> Time -> KeyQueue -> EventDataBundle selectFromChordRatio :: Double -> Time -> KeyQueue -> EventDataBundle maybePitch :: Int -> Maybe Pitch increasePitch :: Int -> Pitch -> Maybe Pitch selectInversion :: Double -> Time -> KeyQueue -> EventDataBundle updateChord :: NoteEv -> Note -> KeySet -> KeySet controllerMatch :: Channel -> Controller -> Ctrl -> Bool updateDur :: Ctrl -> (Time, Time) -> Time updateDurLinear :: Ctrl -> (Time, Time) -> Time updateDurExponential :: Ctrl -> (Time, Time) -> Time -- | Compute base ** expo approximately to result type -- Rational such that the result has a denominator which is a -- power of digitBase and a relative precision of numerator of -- precision digits with respect to digitBase-ary -- numbers. powerRationalFromFloat :: (Floating a, RealFrac a) => Int -> Int -> a -> a -> Rational type Selector i = i -> Time -> KeyQueue -> EventDataBundle data PatternMono i PatternMono :: (Selector i) -> [i] -> PatternMono i data IndexNote i IndexNote :: Int -> i -> IndexNote i item :: i -> Int -> IndexNote i data PatternPoly i PatternPoly :: (Selector i) -> (T Int [IndexNote i]) -> PatternPoly i fraction :: RealFrac a => a -> a data SweepState SweepState :: Double -> Double -> Double -> Double -> SweepState sweepSpeed :: SweepState -> Double sweepDepth :: SweepState -> Double sweepCenter :: SweepState -> Double sweepPhase :: SweepState -> Double -- | See Haskore/FlipSong -- -- flipSeq m !! n = cross sum of the m-ary representation of n modulo m. -- -- For m=2 this yields -- http:www.research.att.comcgi-binaccess.cgiasnjassequenceseisA.cgi?Anum=A010060 flipSeq :: Int -> [Int] -- | bruijn n k is a sequence with length n^k where cycle -- (bruijn n k) contains all n-ary numbers with k digits as infixes. -- The function computes the lexicographically smallest of such -- sequences. bruijn :: Int -> Int -> [Int] -- | All Bruijn sequences with a certain bruijnAll :: Int -> Int -> [[Int]] bruijnAllMap :: Int -> Int -> [[Int]] testBruijn :: Int -> Int -> [Int] -> Bool testBruijnAll :: Int -> Int -> Bool bruijnAllTrie :: Int -> Int -> [[Int]] data Trie a b Leaf :: b -> Trie a b Branch :: [(a, Trie a b)] -> Trie a b fullTrie :: b -> [a] -> Int -> Trie a b nullTrie :: Trie a [b] -> Bool deleteWord :: (Eq a, Eq b) => b -> [a] -> Trie a [b] -> Trie a [b] lookupWord :: Eq a => [a] -> Trie a b -> Maybe b bruijnAllBits :: Int -> Int -> [[Int]] cycleUp, crossSum, pingPong, cycleDown :: Int -> PatternMono Int bruijnPat :: Int -> Int -> PatternMono Int cycleUpAuto, crossSumAuto, pingPongAuto, cycleDownAuto :: PatternMono Integer binaryStaccato, binaryAccident, binaryLegato :: PatternPoly Int decomposePositional :: Integer -> Integer -> [Integer] cycleUpOctave :: Int -> PatternMono Int random, randomInversions :: PatternMono Double cycleUpInversions :: Int -> PatternMono Double inversions :: [Double] -> PatternMono Double examplePatternPolyTempo0 :: T Int [IndexNote Int] examplePatternPolyTempo1 :: T Int [IndexNote Int] checkChannel :: (Channel -> Bool) -> (Data -> Bool) checkPitch :: (Pitch -> Bool) -> (Data -> Bool) checkController :: (Controller -> Bool) -> (Data -> Bool) checkMode :: (T -> Bool) -> (Data -> Bool) checkProgram :: (Program -> Bool) -> (Data -> Bool) mergeStable :: C time => T time body -> T time body -> T time body mergeEither :: C time => T time a -> T time b -> T time (Either a b) instance Show Time instance Eq Time instance Ord Time instance Num Time instance Fractional Time instance Show i => Show (IndexNote i) instance Eq i => Eq (IndexNote i) instance Ord i => Ord (IndexNote i) instance (Show a, Show b) => Show (Trie a b) instance C Time instance Monoid Time module Sound.MIDI.ALSA.Guitar class Transpose pitch getPitch :: Transpose pitch => pitch -> Int transpose :: Transpose pitch => Int -> pitch -> Maybe pitch mapChordToString :: (Transpose pitch, Ord pitch) => [Pitch] -> [pitch] -> [pitch] choosePitchForString :: (Transpose pitch, Ord pitch) => [pitch] -> Pitch -> Maybe pitch stringPitches :: [Pitch] instance Transpose Pitch module Sound.MIDI.ALSA.Training all :: RandomGen g => g -> [([Pitch], [Pitch])] -- | intervals within an octave, all starting with a C intervals :: RandomGen g => g -> [([Pitch], [Pitch])] -- | choose two arbitrary notes from an increasing set of notes twoNotes :: RandomGen g => g -> [([Pitch], [Pitch])] -- | choose three arbitrary notes from an increasing set of notes threeNotes :: RandomGen g => g -> [([Pitch], [Pitch])] reverseThreeNotes :: RandomGen g => g -> [([Pitch], [Pitch])] sortThreeNotes :: RandomGen g => g -> [([Pitch], [Pitch])] -- | transpose an interval to begin with C transposeTwoNotes :: RandomGen g => g -> [([Pitch], [Pitch])] module Sound.MIDI.ALSA.EventList ioToLazyList :: IO a -> IO [a] inputEventsCore :: ReaderT Handle IO [T] inputEvents :: ReaderT Handle IO (T Time Data) pairListFromRelativeEvents :: T Time a -> [(TimeAbs, a)] outputEvents :: T Time Data -> ReaderT Handle IO () -- | Sends (drain) each event individually since the events in the bundle -- might be created in a lazy manner. outputEventBundles :: T Time EventDataBundle -> ReaderT Handle IO () outputEventBundled :: T Time EventDataBundle -> ReaderT Handle IO () data Trigger a Regular :: a -> Trigger a Trigger :: Trigger a type EventDataTrigger = Bundle (Trigger Data) makeTriggerEvent :: Handle -> TimeAbs -> Trigger Data -> T makeTriggerEvents :: Handle -> TimeAbs -> EventDataTrigger -> [T] -- | This function distinguishes between events from portIn and events that -- are generated by us. Our generated events must also send an echo to -- the input port in order to break event_input and thus trigger -- their delivery. outputTriggerEvents :: T Time EventDataTrigger -> ReaderT Handle IO () mergeGenerated :: T Time (Bundle a) -> T Time (Bundle a) -> T Time (Bundle (Trigger a)) equidistantEvents :: Time -> [a] -> T Time a whirl :: T Time EventDataBundle mergeGeneratedAtoms :: (Time -> a) -> T Time a -> T Time a -> T Time a patternMono :: PatternMono i -> Time -> T Time Data -> T Time EventDataTrigger patternMonoTempo :: PatternMono i -> ((Channel, Controller), (Time, Time, Time)) -> T Time Data -> T Time EventDataTrigger -- | This allows more complex patterns including pauses, notes of different -- lengths and simultaneous notes. patternPolyTempo :: PatternPoly i -> ((Channel, Controller), (Time, Time, Time)) -> T Time Data -> T Time EventDataTrigger class Pattern pat patternTempo :: Pattern pat => pat -> ((Channel, Controller), (Time, Time, Time)) -> T Time Data -> T Time EventDataTrigger -- | Automatically changes the value of a MIDI controller every -- period seconds according to a periodic wave. The wave -- function is a mapping from the phase in [0,1) to a controller -- value in the range (-1,1). The generation of the wave is -- controlled by a speed controller (minSpeed and -- maxSpeed are in waves per second), the modulation depth and -- the center value. The center controller is also the one where we emit -- our wave. That is, when modulation depth is zero then this effect is -- almost the same as forwarding the controller without modification. The -- small difference is, that we emit a controller value at a regular -- patternMono, whereas direct control would mean that only controller -- value changes are transfered. -- --
--   sweep channel
--      period (speedCtrl, (minSpeed, maxSpeed)) depthCtrl centerCtrl
--      (ctrlRange (-1,1) (sin . (2*pi*)))
--   
-- -- We could use the nice Wave abstraction from the synthesizer package, -- but that's a heavy dependency because of multi-parameter type classes. sweep :: Channel -> Time -> (Controller, (Time, Time)) -> Controller -> Controller -> (Double -> Double) -> T Time Data -> T Time EventDataTrigger -- | The function maintains empty bundles in order to maintain laziness -- breaks. These breaks are import for later merging of the streams. filter :: (a -> Bool) -> State (T Time (Bundle a)) (T Time (Bundle a)) filterSimple :: (a -> Bool) -> T Time (Bundle a) -> T Time (Bundle a) merge :: T Time (Bundle a) -> T Time (Bundle a) -> T Time (Bundle a) process :: (T Time Data -> T Time EventDataTrigger) -> ReaderT Handle IO () processSimple :: (T Time Data -> T Time EventDataBundle) -> ReaderT Handle IO () runWhirl :: ReaderT Handle IO () runDelay :: ReaderT Handle IO () runKeyboardSplit :: ReaderT Handle IO () runKeyboardSplitLow :: ReaderT Handle IO () runKeyboardSplitHigh :: ReaderT Handle IO () -- |
--   runCyclePrograms (map VoiceMsg.toProgram [8..12])
--   
runCyclePrograms :: [Program] -> ReaderT Handle IO () -- |
--   runProgramsAsBanks [8,4,4]
--   
runProgramsAsBanks :: [Int32] -> ReaderT Handle IO () -- |
--   runPattern 0.12 (cycleUp 4)
--   
runPattern :: Time -> PatternMono i -> ReaderT Handle IO () -- |
--   runPatternTempo 0.12 (cycleUp 4)
--   
-- --
--   runPatternTempo 0.2 (PatternMono selectFromOctaveChord (cycle [0,1,2,0,1,2,0,1]))
--   
-- --
--   runPatternTempo 0.1 (PatternPoly selectFromLimittedChord (let pat = [item 0 1] ./ 1 /. [item 1 1] ./ 2 /. [item 1 1] ./ 1 /. [item 0 1] ./ 2 /. pat in 0 /. pat))
--   
runPatternTempo :: Pattern pat => Time -> pat -> ReaderT Handle IO () runFilterSweep :: ReaderT Handle IO () main :: IO () instance Pattern (PatternPoly i) instance Pattern (PatternMono i) instance Traversable Trigger instance Foldable Trigger instance Functor Trigger module Sound.MIDI.ALSA.Causal -- | The list of scheduled triggers must be finite. -- -- This process cannot drop an incoming event. In order to do so, you -- must write something of type T a (Maybe b). For convenience -- you could wrap this in something like Ext a b. data T a b -- | Here we abuse the Applicative constraint. Actually we only -- need pure. lift :: (Applicative t, Traversable t) => T a b -> T (t a) (t b) -- | Typical instance for the traversable type t are '[]' and -- Maybe. liftPoint :: Traversable t => (b -> t b) -> T a b -> T (t a) (t b) map :: (a -> b) -> T a b -- | Run two stream processor in parallel. We cannot use the Arrow -- method &&& since we cannot define the -- first method of the Arrow class. Consider first -- :: arrow a b -> arrow (c,a) (c,b) and a trigger where -- arrow a b generates an event of type b. How could we -- generate additionally an event of type c without having an -- input event? parallel :: Monoid b => T a b -> T a b -> T a b eitherIn :: T a c -> T b c -> T (Either a b) c traverse :: s -> (a -> State s b) -> T a b -- | input is most oftenly of type EventDataBundle flatten :: T (Bundle a) (Maybe a) -- | TODO: We should allow the process to access and modify the ALSA port -- number. process :: T Data EventDataBundle -> ReaderT Handle IO () transpose :: Int -> T Data (Maybe Data) -- | Swap order of keys. This is a funny effect and a new challenge to -- playing a keyboard. reverse :: T Data (Maybe Data) delayAdd :: Word8 -> Time -> T Data EventDataBundle class Pattern pat patternTempo :: Pattern pat => pat -> TempoControl -> T Data EventDataBundle patternMono :: PatternMono i -> Time -> T Data EventDataBundle type TempoControl = ((Channel, Controller), (Time, Time, Time)) patternMonoTempo :: PatternMono i -> TempoControl -> T Data EventDataBundle patternPolyTempo :: PatternPoly i -> TempoControl -> T Data EventDataBundle patternSerialTempo :: Int -> PatternMono i -> TempoControl -> T Data EventDataBundle sweep :: Channel -> Time -> (Controller, (Time, Time)) -> Controller -> Controller -> (Double -> Double) -> T Data [Data] partition :: (a -> Bool) -> T a (Maybe a, Maybe a) guide :: (a -> Bool) -> T a b -> T a b -> T a b guideWithMode :: Monoid b => (Data -> Bool) -> T Data b -> T Data b -> T Data b cyclePrograms :: [Program] -> T Data [Data] -- |
--   cycleProgramsDefer t
--   
-- -- After a note that triggers a program change, we won't change the -- program in the next t seconds. This is in order to allow -- chords being played and in order to skip accidentally played notes. cycleProgramsDefer :: Time -> [Program] -> T Data [Data] latch :: T Data (Maybe Data) -- | All pressed keys are latched until a key is pressed after a pause -- (i.e. all keys released). For aborting the pattern you have to send a -- AllNotesOff or AllSoundOff message. groupLatch :: T Data [Data] -- | A key is hold until n times further keys are pressed. The -- n-th pressed key replaces the current one. serialLatch :: Int -> T Data [Data] -- | Try for instance guitar 0.05 0.03. -- -- This process simulates playing chords on a guitar. If you press some -- keys like C, E, G on the keyboard, then this process figures out what -- tones would be played on a guitar and plays them one after another -- with short delays. If you release the keys then the chord is played in -- reverse order. This simulates the hand going up and down on the guitar -- strings. Unfortunatley it is not possible to go up twice or go down -- twice this way. The octaves of the pressed keys are ignored. -- -- In detail calling guitar collectTime stepTime means: If a key -- is pressed, then collect all key-press events for the next -- collectTime seconds. After this period, send out a -- guitar-like chord pattern for the pressed keys with a delay of -- stepTime between the notes. Now wait until all keys are -- released. Note that in the meantime keys could have been pressed or -- released. They are registered, but not played. If all keys are -- released then send out the reverse chord. -- -- On an AllSoundOff message, release all played tones. -- -- I don't know whether emitted key-events are always consistent. guitar :: Time -> Time -> T Data EventDataBundle -- | Audio perception trainer -- -- Play sets of notes and let the human player answer to them according -- to a given scheme. Repeat playing the notes sets until the trainee -- answers correctly. Then continue with other sequences, maybe more -- complicated ones. -- -- possible tasks: -- -- -- -- The difficulty can be increased by not connecting the keyboard -- directly with the sound generator. This way, the trainee cannot -- verify, how the pressed keys differ from the target keys. -- -- Sometimes it seems that you are catched in an infinite loop. This -- happens if there were too many keys pressed. The trainer collects all -- key press events, not only the ones that occur after the target set is -- played. This way you can correct yourself immediately, before the -- target is repeatedly played. The downside is, that there may be key -- press events hanging around. You can get rid of them by pressing a key -- again and again, but slowly, until the target is played, again. Then -- the queue of registered keys should be empty and you can proceed -- training. trainer :: Channel -> Time -> Time -> [([Pitch], [Pitch])] -> T Data EventDataBundle instance Show PitchChannel instance Transpose PitchChannel instance Ord PitchChannel instance Eq PitchChannel instance Pattern (PatternPoly i) instance Pattern (PatternMono i) instance Category T instance Monoid (Triggers c) instance Functor Triggers module Sound.MIDI.ALSA.CausalExample defaultTempo :: TempoControl run :: ReaderT Handle IO a -> IO a pass, releaseAllKeys, sendMode, sendProgram, guitar, filterKey, groupRandomInversions, groupRandom, groupBruijn, groupCrossSum, groupBinary, groupCycleUp, groupLatch, latch, bruijn, crossSum, binary, cycleProgramsDefer, cyclePrograms, serialLatch, serialPattern, splitPattern, split, sweep, cycleUpPoly, cycleUpTempo, cycleUp, delay, reverse :: ReaderT Handle IO () withGroup :: Pattern pat => pat -> ReaderT Handle IO () trainer :: RandomGen g => g -> ReaderT Handle IO ()