/
SoundUtil.purs
143 lines (122 loc) 路 4.15 KB
/
SoundUtil.purs
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
module Emo8.SoundUtil
( ChannelSets
, ChannelSet
, getChannelSet
, mkChannelSets
, prepareSound
, setVol
, cancelVol
, setEfct
, cancelEfct
, setScale
, unsetScale
, cancelScale
) where
import Prelude
import Audio.WebAudio.AudioParam (cancelScheduledValues, setTargetAtTime, setValue, setValueAtTime)
import Audio.WebAudio.BaseAudioContext (createGain, createOscillator, currentTime, destination)
import Audio.WebAudio.GainNode (gain)
import Audio.WebAudio.Oscillator (detune, frequency, startOscillator)
import Audio.WebAudio.Types (AudioContext, GainNode, OscillatorNode, Seconds, connect)
import Data.Foldable (for_)
import Data.Unfoldable (replicateA)
import Effect (Effect)
import Emo8.Constants (maxNoteSize)
import Emo8.Data.Audio (Efct, Vol, efctToDetune, noteToFreq, octaveToMult, volToGain)
import Emo8.Data.Channel (Channel(..), channels)
import Emo8.Data.Tick (Scale)
type Frequency = Number
type ChannelSets =
{ set1 :: ChannelSet
, set2 :: ChannelSet
, set3 :: ChannelSet
, set4 :: ChannelSet
}
type ChannelSet =
{ osclNodes :: Array OscillatorNode
, gainNode :: GainNode
}
getChannelSet :: Channel -> ChannelSets -> ChannelSet
getChannelSet CH1 sets = sets.set1
getChannelSet CH2 sets = sets.set2
getChannelSet CH3 sets = sets.set3
getChannelSet CH4 sets = sets.set4
mkChannelSets :: AudioContext -> Effect ChannelSets
mkChannelSets ctx =
{ set1: _
, set2: _
, set3: _
, set4: _
}
<$> mkChannelSet ctx
<*> mkChannelSet ctx
<*> mkChannelSet ctx
<*> mkChannelSet ctx
mkChannelSet :: AudioContext -> Effect ChannelSet
mkChannelSet ctx =
{ osclNodes: _
, gainNode: _
}
<$> (replicateA maxNoteSize <<< createOscillator) ctx
<*> createGain ctx
prepareSound :: ChannelSets -> AudioContext -> Effect Unit
prepareSound sets ctx = do
setSoundNodes sets ctx
startSoundNodes sets ctx
setSoundNodes :: ChannelSets -> AudioContext -> Effect Unit
setSoundNodes sets ctx = for_ channels \ch -> setSoundNode (getChannelSet ch sets) ctx
setSoundNode :: ChannelSet-> AudioContext -> Effect Unit
setSoundNode set ctx = do
for_ set.osclNodes \osc -> connect osc set.gainNode
dest <- destination ctx
connect set.gainNode dest
startSoundNodes :: ChannelSets -> AudioContext -> Effect Unit
startSoundNodes sets ctx = for_ channels \ch -> startSoundNode (getChannelSet ch sets) ctx
startSoundNode :: ChannelSet-> AudioContext -> Effect Unit
startSoundNode set ctx = do
now <- currentTime ctx
for_ set.osclNodes $ startOscillator now
-- NOTE: initialize gain to prevent initial unwanted sound
cancelVol now set.gainNode ctx
setVol :: Seconds -> Seconds -> GainNode -> Vol -> Effect Unit
setVol t itv gn vol = do
gainParam <- gain gn
void $ setTargetAtTime volume t decay gainParam
where
volume = volToGain vol
-- NOTE: https://developer.mozilla.org/en-US/docs/Web/API/AudioParam/setTargetAtTime#Choosing_a_good_timeConstant
-- Time since startTime: Value
-- 1 * timeConstant: 63.2%
-- n * timeConstant: 1 - e^n
decay = itv / 2.0
cancelVol :: Seconds -> GainNode -> AudioContext -> Effect Unit
cancelVol now gn ctx = do
gainParam <- gain gn
void $ cancelScheduledValues now gainParam
setValue 0.0 gainParam
setEfct :: Seconds -> OscillatorNode -> Efct -> Effect Unit
setEfct t on efct = do
detParam <- detune on
void $ setValueAtTime det t detParam
where
det = efctToDetune efct
cancelEfct :: Seconds -> OscillatorNode -> AudioContext -> Effect Unit
cancelEfct now on ctx = do
detParam <- detune on
void $ cancelScheduledValues now detParam
setValue 0.0 detParam
unsetScale :: Seconds -> OscillatorNode -> Effect Unit
unsetScale t on = setFreq t on freq
where freq = 0.0
setScale :: Seconds -> OscillatorNode -> Scale -> Effect Unit
setScale t on scale = setFreq t on freq
where freq = noteToFreq scale.note * octaveToMult scale.octave
setFreq :: Seconds -> OscillatorNode -> Frequency -> Effect Unit
setFreq t on freq = do
freqParam <- frequency on
void $ setValueAtTime freq t freqParam
cancelScale :: Seconds -> OscillatorNode -> AudioContext -> Effect Unit
cancelScale now on ctx = do
freqParam <- frequency on
void $ cancelScheduledValues now freqParam
setValue 0.0 freqParam