forked from donya/Kulitta
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPlayK.lhs
107 lines (82 loc) · 3.71 KB
/
PlayK.lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
Special playback functions
Donya Quick
Last modified: 01-July-2015
Kulitta-specific playback functions.
Most of these functions are slight modifications of Euterpea's
functions in ExperimentalPlay and MidiIO.
> module PlayK where
> import Euterpea
> import Codec.Midi
> import Sound.PortMidi
> import System.IO.Unsafe (unsafePerformIO)
> import Euterpea.IO.MIDI.MidiIO
> import System.Directory
> import Euterpea.ExperimentalPlay
Play a MIDI file to a user-specified device. Unlike the regular play function,
this version also checks whether the file exists to avoid crashing the calling
program. If the file doesn't exist, an error message will be printed.
> play' fp devID = do
> x <- tryImportFile fp
> seq x $ case x of Left err -> putStrLn ("Error: "++err) >> return ()
> Right m -> playM' devID m
> playX fp devID chanOffset vol = do
> x <- tryImportFile fp
> seq x $ case x of Left err -> putStrLn ("Error: "++err) >> return ()
> Right m -> playM' devID (trackMod chanOffset vol m)
> writeX fp m chanOffset =
> let x = trackMod 0 (-1) $ testMidi m
> in exportMidiFile fp x
The trackMod function takes a channel offset, x, and a list of tick-stamped messages
and offsets the track numbers by x amount. The purpose of this was really a single
use case: running the Kulitta GUI alongside other programs that used tracks 1 through
(x-1) so that Kulitta's playback would not "step on the toes" of the other programs'
playback.
> trackMod x vol m =
> let t = tracks m
> t' = map (map (trackMod' x vol)) t
> in m{tracks = t'}
> trackMod' :: Channel -> Double -> (Ticks, Message) -> (Ticks, Message)
> trackMod' x vol (a, NoteOff c k v) = (a, NoteOff (c+x) k (if vol <0 then v else volMod v vol))
> trackMod' x vol (a, NoteOn c k v) = (a, NoteOn (c+x) k (if vol <0 then v else volMod v vol))
> trackMod' x vol (a, ProgramChange c p) = (a, ProgramChange (c+x) p)
> trackMod' x vol (a, ControlChange c v1 v2) = (a, ControlChange (c+x) v1 v2)
> trackMod' x vol (a,v) = (a,v)
> volMod v vol = floor(fromIntegral v * vol)
> playF fp fmid devID = do
> x <- tryImportFile fp
> seq x $ case x of Left err -> putStrLn ("Error: "++err) >> return ()
> Right m -> playM' devID (fmid m)
Code to check whether the file exists:
> tryImportFile fp = do
> let theDir = getDir fp
> theFile = getFile fp
> files <- getDirectoryContents theDir
> if elem theFile files then importFile fp >>= return
> else return $ Left ("File "++fp ++ " does not exist, so it cannot be played!") where
> getDir fp = reverse $ dropWhile (not.(`elem` "/\\")) $ reverse fp
> getFile fp = drop (length $ getDir fp) fp
=====================
DATA MANIPULATION FUNCTIONS
For playback, it's useful to be able to set the volume on some systems.
> setVol :: Int -> Music Pitch -> Music1
> setVol v = mMap (\p -> (p, [Volume v]))
> setVol1 :: Int -> Music1 -> Music1
> setVol1 v = mMap (\(p,nas) -> (p, Volume v : filter f nas)) where
> f (Volume v) = False
> f _ = True
Sometimes it is also useful to play to devices with instrument
information stripped (i.e. sending to a single synth such that
only one channel can be used).
> stripInstrs :: Music a -> Music a
> stripInstrs (a :+: b) = stripInstrs a :+: stripInstrs b
> stripInstrs (a :=: b) = stripInstrs a :=: stripInstrs b
> stripInstrs (Prim p) = Prim p
> stripInstrs (Modify (Instrument i) m) = stripInstrs m
> stripInstrs (Modify c m) = Modify c $ stripInstrs m
> m2m :: (Music1 -> Music1) -> Midi -> Midi
> m2m fm1 midi =
> let (m1, _, _) = fromMidi midi
> in testMidi $ fm1 m1
> ch1 :: Int -> Midi -> Midi
> ch1 v = if v <=0 then m2m stripInstrs
> else m2m (setVol1 v . stripInstrs)