{-# LANGUAGE ExistentialQuantification #-} {- | Convert MIDI events of a MIDI controller to a control signal. -} {-# LANGUAGE NoImplicitPrelude #-} module Synthesizer.MIDI.Generic where import Synthesizer.MIDI.EventList (LazyTime, StrictTime, Filter, Channel, Program, embedPrograms, makeInstrumentArray, getInstrumentFromArray, Note(Note), matchNoteEvents, getNoteEvents, ) import qualified Sound.MIDI.Message.Class.Check as Check import qualified Sound.MIDI.Message.Channel as ChannelMsg import qualified Synthesizer.PiecewiseConstant.Signal as PC import qualified Synthesizer.Generic.Cut as CutG import qualified Synthesizer.Generic.Signal as SigG import qualified Synthesizer.MIDI.Value as MV import qualified Data.EventList.Relative.MixedBody as EventListMB import qualified Data.EventList.Relative.BodyTime as EventListBT import qualified Data.EventList.Relative.TimeBody as EventList import Data.Monoid (Monoid, mempty, ) import qualified Numeric.NonNegative.Class as NonNeg import qualified Numeric.NonNegative.Wrapper as NonNegW import qualified Numeric.NonNegative.Chunky as NonNegChunky import qualified Algebra.Transcendental as Trans import Control.Arrow (Arrow, arr, first, ) import Control.Category (Category, id, (.), ) import qualified Control.Monad.Trans.State.Strict as MS import Control.Monad.Trans.State (State, evalState, runState, state, gets, put, get, ) import Control.Monad (liftM, ) import Data.Traversable (traverse, ) import Data.Foldable (traverse_, ) import Control.DeepSeq (NFData, ) import NumericPrelude.Base hiding (id, (.), ) import NumericPrelude.Numeric import Prelude () {- ToDo: move to Generic.Signal -} replicateLong :: (SigG.Write sig y) => StrictTime -> y -> sig y replicateLong tl y = CutG.concat $ map (\t -> SigG.replicate -- (SigG.LazySize $ fromIntegral $ maxBound::Int) SigG.defaultLazySize (NonNegW.toNumber t) y) $ PC.chopLongTime tl {- ToDo: move to Generic.Signal -} {-# INLINE piecewiseConstant #-} piecewiseConstant :: (SigG.Write sig y) => EventListBT.T StrictTime y -> sig y piecewiseConstant = EventListBT.foldrPair (\y t -> SigG.append (replicateLong t y)) SigG.empty {-# INLINE piecewiseConstantInit #-} piecewiseConstantInit :: (SigG.Write sig y) => y -> EventList.T StrictTime y -> sig y piecewiseConstantInit initial = (\ ~(t,rest) -> SigG.append (replicateLong t initial) rest) . EventList.foldr (,) (\y ~(t,rest) -> SigG.append (replicateLong t y) rest) (0, SigG.empty) {-# INLINE piecewiseConstantInitWith #-} piecewiseConstantInitWith :: (SigG.Write sig c) => (y -> c) -> c -> EventList.T StrictTime [y] -> sig c piecewiseConstantInitWith f initial = piecewiseConstantInit initial . flip evalState initial . traverse (\evs -> traverse_ (put . f) evs >> get) type Instrument y signal = y -> y -> LazyTime -> signal type Bank y signal = Program -> Instrument y signal {- | Instrument parameters are: velocity from -1 to 1 (0 is the normal pressure, no pressure aka NoteOff is not supported), frequency is given in Hertz -} renderInstrument :: (Trans.C y) => Bank y signal -> Note -> signal renderInstrument instrument (Note pgm pitch vel dur) = instrument pgm (MV.velocity vel) (MV.frequencyFromPitch pitch) dur renderInstrumentIgnoreProgram :: (Trans.C y) => Instrument y signal -> Note -> signal renderInstrumentIgnoreProgram instrument = renderInstrument (const instrument) {- | Turn an event list with bundles of elements into an event list with single events. ToDo: Move to event-list package? -} flatten :: (Monoid signal, NonNeg.C time) => EventList.T time [signal] -> EventList.T time signal flatten = EventList.foldr EventListMB.consTime (\bt xs -> uncurry EventListMB.consBody $ case bt of [] -> (mempty, xs) b:bs -> (b, foldr (EventList.cons NonNeg.zero) xs bs)) EventList.empty applyModulation :: (CutG.Transform signal, CutG.NormalForm signal) => signal -> Modulator (signal -> instr, note) (instr, note) applyModulation ctrl = first $ Modulator ctrl advanceModulationChunk gets {- | We have to evaluate the head value at each 'drop' in order to avoid growing thunks that lead to a space leak. -} evaluateVectorHead :: (CutG.NormalForm signal) => signal -> t -> t evaluateVectorHead xs t = case CutG.evaluateHead xs of () -> t -- if CutG.null xs then t else t advanceModulation :: (CutG.Transform signal, CutG.NormalForm signal) => LazyTime -> State signal LazyTime advanceModulation = liftM NonNegChunky.fromChunks . mapM advanceModulationChunk . NonNegChunky.toChunks advanceModulationChunk :: (CutG.Transform signal, CutG.NormalForm signal) => StrictTime -> State signal StrictTime advanceModulationChunk t = state $ \xs -> let ys = CutG.drop (fromIntegral t) xs in (evaluateVectorHead ys t, ys) advanceModulationChunkStrict :: (CutG.Transform signal, CutG.NormalForm signal) => StrictTime -> MS.State signal StrictTime advanceModulationChunkStrict t = MS.state $ \xs -> let ys = CutG.drop (fromIntegral t) xs in (evaluateVectorHead ys t, ys) advanceModulationChunkPC :: (NFData body) => StrictTime -> State (EventListBT.T StrictTime body) StrictTime advanceModulationChunkPC t = state $ \xs -> let ys = EventListBT.fromPairList $ tail $ EventListBT.toPairList xs in (evaluateVectorHead ys t, ys) type FilterSequence event signal = Filter event (EventList.T PC.ShortStrictTime signal) {- | The state action for the time should just return the argument time. However we need this time (or alternatively another result type) for triggering the 'drop' in 'advanceModulationChunk'. Without this strict evaluation, the drop will be delayed until the control curve is actually needed. -} data Modulator note signal = forall state. Modulator state (StrictTime -> State state StrictTime) (note -> State state signal) instance Category Modulator where id = Modulator () return return (Modulator yInit yTime yBody) . (Modulator xInit xTime xBody) = let compose ym xm r0 = state $ \(xState0,yState0) -> let (r1, xState1) = runState (xm r0) xState0 (r2, yState1) = runState (ym r1) yState0 in (r2, (xState1,yState1)) in Modulator (xInit,yInit) (compose yTime xTime) (compose yBody xBody) instance Arrow Modulator where arr f = Modulator () return (return . f) first (Modulator xInit xTime xBody) = Modulator xInit xTime (\(a0,c) -> fmap (\a1 -> (a1,c)) $ xBody a0) applyModulator :: Modulator a b -> EventList.T StrictTime [a] -> EventList.T StrictTime [b] applyModulator (Modulator modulatorInit modulatorTime modulatorBody) = flip evalState modulatorInit . EventList.traverse modulatorTime (traverse modulatorBody) {-# INLINE sequenceCore #-} sequenceCore :: (Check.C event, Monoid signal) => Channel -> Program -> Modulator Note signal -> FilterSequence event signal sequenceCore chan initPgm md = fmap (EventList.mapTime fromIntegral . flatten . applyModulator md . matchNoteEvents . embedPrograms initPgm) $ getNoteEvents chan errorNoProgram :: Program errorNoProgram = ChannelMsg.toProgram 0 {- Since we compute the current program strictly in embedPrograms, initializing with undefined does no longer work. error "MIDI program not initialized" -} {-# INLINE sequence #-} sequence :: (Check.C event, Monoid signal, Trans.C y) => Channel -> Instrument y signal -> FilterSequence event signal sequence chan instr = sequenceCore chan errorNoProgram (Modulator () return (return . renderInstrumentIgnoreProgram instr)) {-# INLINE sequenceModulated #-} sequenceModulated :: (Check.C event, CutG.Transform ctrl, CutG.NormalForm ctrl, Monoid signal, Trans.C y) => ctrl -> Channel -> (ctrl -> Instrument y signal) -> FilterSequence event signal sequenceModulated ctrl chan instr = sequenceCore chan errorNoProgram (Modulator ctrl advanceModulationChunk (\note -> gets $ \c -> renderInstrumentIgnoreProgram (instr c) note)) {-# INLINE sequenceMultiModulated #-} sequenceMultiModulated :: (Check.C event, Monoid signal, Trans.C y) => Channel -> instrument -> Modulator (instrument, Note) (Instrument y signal, Note) -> FilterSequence event signal sequenceMultiModulated chan instr (Modulator modulatorInit modulatorTime modulatorBody) = sequenceCore chan errorNoProgram (Modulator modulatorInit modulatorTime (fmap (uncurry renderInstrumentIgnoreProgram) . modulatorBody . (,) instr)) {-# INLINE sequenceMultiProgram #-} sequenceMultiProgram :: (Check.C event, Monoid signal, Trans.C y) => Channel -> Program -> [Instrument y signal] -> FilterSequence event signal sequenceMultiProgram chan initPgm instrs = let bank = makeInstrumentArray instrs in sequenceCore chan initPgm (Modulator () return (return . renderInstrument (getInstrumentFromArray bank initPgm))) {-# INLINE sequenceModulatedMultiProgram #-} sequenceModulatedMultiProgram :: (CutG.Transform ctrl, CutG.NormalForm ctrl, Check.C event, Monoid signal, Trans.C y) => ctrl -> Channel -> Program -> [ctrl -> Instrument y signal] -> FilterSequence event signal sequenceModulatedMultiProgram ctrl chan initPgm instrs = let bank = makeInstrumentArray instrs in sequenceCore chan initPgm (Modulator ctrl advanceModulationChunk (\note -> gets $ \c -> renderInstrument (\pgm -> getInstrumentFromArray bank initPgm pgm c) note))