├── .gitignore ├── API.md ├── README.md ├── bower.json ├── examples ├── decode │ ├── Main.purs │ ├── SimpleDom.js │ ├── SimpleDom.purs │ ├── decode-audio.wav │ └── index.html ├── decodeAsync │ ├── Main.purs │ ├── hihat.wav │ ├── index.html │ ├── kick.wav │ └── snare.wav ├── gain │ ├── Main.purs │ ├── gain.wav │ └── index.html └── square-wave │ ├── Main.purs │ └── index.html ├── package.json ├── src └── Audio │ └── WebAudio │ ├── AnalyserNode.js │ ├── AnalyserNode.purs │ ├── AudioBufferSourceNode.js │ ├── AudioBufferSourceNode.purs │ ├── AudioContext.js │ ├── AudioContext.purs │ ├── AudioParam.js │ ├── AudioParam.purs │ ├── BaseAudioContext.js │ ├── BaseAudioContext.purs │ ├── BiquadFilterNode.js │ ├── BiquadFilterNode.purs │ ├── ConvolverNode.js │ ├── ConvolverNode.purs │ ├── DelayNode.purs │ ├── DestinationNode.purs │ ├── DynamicsCompressorNode.js │ ├── DynamicsCompressorNode.purs │ ├── GainNode.js │ ├── GainNode.purs │ ├── MediaElementAudioSourceNode.purs │ ├── Oscillator.js │ ├── Oscillator.purs │ ├── StereoPannerNode.js │ ├── StereoPannerNode.purs │ ├── Types.js │ ├── Types.purs │ ├── Utils.js │ └── Utils.purs └── test ├── Main.purs └── props ├── TestProps.purs └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | .psc-ide-port 4 | .psci 5 | bower_components 6 | node_modules 7 | output 8 | deprecated 9 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Module Documentation 2 | 3 | ## Module Audio.WebAudio.AudioBufferSourceNode 4 | 5 | ### Type Class Instances 6 | 7 | instance audioNodeAudioBufferSourceNode :: AudioNode AudioBufferSourceNode 8 | 9 | 10 | ### Values 11 | 12 | setBuffer :: forall wau eff. AudioBuffer -> AudioBufferSourceNode -> Eff (wau :: WebAudio | eff) Unit 13 | 14 | startBufferSource :: forall wau eff. Number -> AudioBufferSourceNode -> Eff (wau :: WebAudio | eff) Unit 15 | 16 | 17 | ## Module Audio.WebAudio.AudioContext 18 | 19 | ### Values 20 | 21 | connect :: forall m n wau eff. (AudioNode m, AudioNode n) => m -> n -> Eff (wau :: WebAudio | eff) Unit 22 | 23 | createBufferSource :: forall wau eff. AudioContext -> Eff (wau :: WebAudio | eff) AudioBufferSourceNode 24 | 25 | createGain :: forall wau eff. AudioContext -> Eff (wau :: WebAudio | eff) GainNode 26 | 27 | createMediaElementSource :: forall elt wau dom eff. AudioContext -> elt -> Eff (wau :: WebAudio | eff) MediaElementAudioSourceNode 28 | 29 | createOscillator :: forall wau eff. AudioContext -> Eff (wau :: WebAudio | eff) OscillatorNode 30 | 31 | currentTime :: forall wau eff. AudioContext -> Eff (wau :: WebAudio | eff) Number 32 | 33 | decodeAudioData :: forall wau e f 34 | . AudioContext 35 | -> String 36 | -> (AudioBuffer -> Eff (wau :: WebAudio | e) Unit) -- success 37 | -> (String -> Eff (console :: CONSOLE | e) Unit) -- failure (warn, log, etc.) 38 | -> Eff (wau :: WebAudio | f) Unit 39 | 40 | destination :: forall wau eff. AudioContext -> Eff (wau :: WebAudio | eff) DestinationNode 41 | 42 | makeAudioContext :: forall wau eff. Eff (wau :: WebAudio | eff) AudioContext 43 | 44 | 45 | ## Module Audio.WebAudio.AudioParam 46 | 47 | ### Values 48 | 49 | cancelScheduledValues :: forall wau eff. Number -> AudioParam -> Eff (wau :: WebAudio | eff) Number 50 | 51 | exponentialRampToValueAtTime :: forall wau eff. Number -> Number -> AudioParam -> Eff (wau :: WebAudio | eff) Number 52 | 53 | getValue :: forall wau eff. AudioParam -> Eff (wau :: WebAudio | eff) Number 54 | 55 | linearRampToValueAtTime :: forall wau eff. Number -> Number -> AudioParam -> Eff (wau :: WebAudio | eff) Number 56 | 57 | setValue :: forall wau eff. Number -> AudioParam -> Eff (wau :: WebAudio | eff) Unit 58 | 59 | setValueAtTime :: forall wau eff. Number -> Number -> AudioParam -> Eff (wau :: WebAudio | eff) Number 60 | 61 | 62 | ## Module Audio.WebAudio.DestinationNode 63 | 64 | ### Type Class Instances 65 | 66 | instance audioNodeDestinationNode :: AudioNode DestinationNode 67 | 68 | 69 | ## Module Audio.WebAudio.GainNode 70 | 71 | ### Type Class Instances 72 | 73 | instance audioNodeGainNode :: AudioNode GainNode 74 | 75 | 76 | ### Values 77 | 78 | gain :: forall wau eff. GainNode -> Eff (wau :: WebAudio | eff) AudioParam 79 | 80 | 81 | ## Module Audio.WebAudio.MediaElementAudioSourceNode 82 | 83 | ### Type Class Instances 84 | 85 | instance audioNodeMediaElementAudioSourceNode :: AudioNode MediaElementAudioSourceNode 86 | 87 | 88 | ## Module Audio.WebAudio.OscillatorNode 89 | 90 | ### Types 91 | 92 | data OscillatorType where 93 | Sine :: OscillatorType 94 | Square :: OscillatorType 95 | Sawtooth :: OscillatorType 96 | Triangle :: OscillatorType 97 | Custom :: OscillatorType 98 | 99 | 100 | ### Type Class Instances 101 | 102 | instance audioNodeOscillatorNode :: AudioNode OscillatorNode 103 | 104 | instance oscillatorTypeShow :: Show OscillatorType 105 | 106 | 107 | ### Values 108 | 109 | frequency :: forall wau eff. OscillatorNode -> Eff (wau :: WebAudio | eff) AudioParam 110 | 111 | oscillatorType :: forall wau eff. OscillatorNode -> Eff (wau :: WebAudio | eff) OscillatorType 112 | 113 | readOscillatorType :: String -> OscillatorType 114 | 115 | setOscillatorType :: forall wau eff. OscillatorType -> OscillatorNode -> Eff (wau :: WebAudio | eff) Unit 116 | 117 | startOscillator :: forall wau eff. Number -> OscillatorNode -> Eff (wau :: WebAudio | eff) Unit 118 | 119 | stopOscillator :: forall wau eff. Number -> OscillatorNode -> Eff (wau :: WebAudio | eff) Unit 120 | 121 | 122 | ## Module Audio.WebAudio.Types 123 | 124 | ### Types 125 | 126 | data AudioBuffer :: * 127 | 128 | data AudioBufferSourceNode :: * 129 | 130 | data AudioContext :: * 131 | 132 | data AudioParam :: * 133 | 134 | data DestinationNode :: * 135 | 136 | data GainNode :: * 137 | 138 | data MediaElementAudioSourceNode :: * 139 | 140 | data OscillatorNode :: * 141 | 142 | data WebAudio :: ! 143 | 144 | 145 | ### Type Classes 146 | 147 | class AudioNode n where 148 | 149 | 150 | ## Module Audio.WebAudio.Utils 151 | 152 | ### Values 153 | 154 | unsafeGetProp :: forall obj val eff. String -> obj -> Eff eff val 155 | 156 | unsafeSetProp :: forall obj val eff. String -> obj -> val -> Eff eff Unit 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-webaudio 2 | 3 | ## About 4 | 5 | This is an experimental library for dealing with the HTML5 [Web Audio 6 | API](https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html). 7 | 8 | Module documentation is available [here](API.md). 9 | 10 | 11 | To build the examples to run in your browser, perform the following scripts in order: 12 | 1. `npm run build:example:xx` where xx is the example (siren, gain, decode, decodeAsync) 13 | 14 | To run the examples in your browser, perform the following scripts in order: 15 | 1. `npm run exec:example:xx` where xx is the example (siren, gain, decode, decodeAsync) 16 | 17 | To build the test suite 18 | 1. `npm run build:test:props` 19 | 20 | To run the test suite 21 | 1. `npm run exec:test:props` and inspect the output log 22 | 23 | 24 | ## Breaking Changes 25 | * Updated to work with `purs 0.11.x` 26 | * Renamed the `WebAudio` effect to `AUDIO` to conform with best practices 27 | * Renamed the `AudioNode` class as `RawAudioNode` 28 | * `AudioNode` is now a sum type over all the raw nodes. 29 | * Renamed `wau` to `audio` 30 | * Moved several fns from `AudioContext.purs` to `BaseAudiocontext` to match web audio spec 31 | * Created a `Connectable` class encompassing `connect`, `disconnect` & `connectParam` with AudioNode as an instance. This lives in `Types.purs` 32 | 33 | ## Improvements 34 | * Moved test/Test0X to `examples/` and renamed appropriately 35 | * Added `AudioParam.setTargetAtTime` 36 | * Updated `API.md` to reflect `decodeAudioData` error handling change 37 | * New type synomymns `Value` and `Seconds` for `AudioParam` methods 38 | * Eliminated `gulp`, putting new build test scripts in `package.json` 39 | * Added `decodeAudioDataAsync` to `BaseAudioContext`. This runs in Aff not Eff but has the advantage that audio buffers can be returned directly. This, of course, introduces a dependency on Aff 4.0.0 and requires users to lift the original Eff functions into Aff if they wish to use it. I hope that this overhead should not be too restrictive given that a natural way to load sound resources is via Aff anyway. 40 | * Added `decodeAsync` to illustrate basic usage. devDependencies now include `Affjax`. 41 | * Added `test/props` to test some simple properties of the new Node types. 42 | * Added `BiquadFilterNode`. 43 | * Added `detune` property to `Oscillator`. 44 | * Added `DelayNode`. 45 | * Added `disconnect` to Types. 46 | * Experimented with shorthand setters for `AudioParam` properties on some nodes. 47 | * Added `AnalyserNode` plus buffer creation functions in Utils. This introduces a dependency on `Data.ArrayBuffer`. 48 | * Added `StereoPannerNode`. 49 | * Added `DynamicsCompressorNode` 50 | * Added `ConvolverNode` 51 | 52 | ## adkelley ToDo: 53 | * Add further error handling options for `decodeAudioData` besides writing to console 54 | * Support for further nodes (e.g., ChannelSplitterNode), and interfaces 55 | 56 | ## newlandsvalley ToDo: 57 | * Document the changes from merging my fork 58 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-webaudio", 3 | "version": "0.1.0", 4 | "description": "A PureScript wrapper for the WebAudio API.", 5 | "authors": [ 6 | "Chris Waterson ", 7 | "Sean Seefried ", 8 | "Alex Kelley ", 9 | "John Watson " 10 | ], 11 | "license": "MIT", 12 | "private": true, 13 | "ignore": [ 14 | "**/.*", 15 | "node_modules", 16 | "bower_components", 17 | "test", 18 | "tests" 19 | ], 20 | "dependencies": { 21 | "purescript-arrays": "^4.2.0", 22 | "purescript-foldable-traversable": "^3.6.0", 23 | "purescript-lists": "^4.10.0", 24 | "purescript-math": "^2.1.0", 25 | "purescript-maybe": "^3.0.0", 26 | "purescript-tuples": "^4.1.0", 27 | "purescript-strings": "^3.3.1", 28 | "purescript-eff": "^3.1.0", 29 | "purescript-aff": "^4.0.0", 30 | "purescript-arraybuffer-types": "^2.0.0", 31 | "jacereda/purescript-arraybuffer": "^6.0.0" 32 | }, 33 | "devDependencies": { 34 | "purescript-console": "^3.0.0", 35 | "purescript-psci-support": "^3.0.0", 36 | "purescript-dom": "^4.16.0", 37 | "purescript-js-timers": "^3.0.0", 38 | "purescript-affjax": "^5.0.0", 39 | "purescript-assert": "^3.0.0", 40 | "purescript-refs": "^3.0.0" 41 | }, 42 | "resolutions": { 43 | "purescript-arraybuffer-types": "^2.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/decode/Main.purs: -------------------------------------------------------------------------------- 1 | module DecodeAudio where 2 | 3 | import Prelude 4 | 5 | import Audio.WebAudio.AudioBufferSourceNode (setBuffer, startBufferSource) 6 | import Audio.WebAudio.BaseAudioContext (createBufferSource, decodeAudioData, destination, newAudioContext) 7 | import Audio.WebAudio.Types (AudioBuffer, AudioBufferSourceNode, connect, AUDIO) 8 | import Control.Monad.Eff (Eff) 9 | import Control.Monad.Eff.Console (warn) 10 | import DOM (DOM) 11 | import Data.ArrayBuffer.Types (ArrayBuffer) 12 | import Partial.Unsafe (unsafePartial) 13 | import SimpleDom 14 | (DOMEvent, HttpData(..), HttpMethod(..), ProgressEventType(..), XMLHttpRequest 15 | , addProgressEventListener, makeXMLHttpRequest, open, response, send, setResponseType) 16 | 17 | toArrayBuffer :: forall a. (HttpData a) -> ArrayBuffer 18 | toArrayBuffer hd = 19 | unsafePartial 20 | case hd of 21 | (ArrayBufferData a) -> a 22 | 23 | main :: forall eff. (Eff (audio :: AUDIO, dom :: DOM | eff) Unit) 24 | main = do 25 | req <- makeXMLHttpRequest 26 | open GET "decode-audio.wav" req 27 | setResponseType "arraybuffer" req 28 | addProgressEventListener ProgressLoadEvent (play req) req 29 | send NoData req 30 | pure unit 31 | 32 | play :: forall eff. XMLHttpRequest -- |^ the request object 33 | -> DOMEvent -- |^ the load event 34 | -> (Eff (audio :: AUDIO, dom :: DOM | eff) Unit) 35 | play req ev = do 36 | ctx <- newAudioContext 37 | src <- createBufferSource ctx 38 | dst <- destination ctx 39 | connect src dst 40 | audioData <- response req 41 | decodeAudioData ctx (toArrayBuffer audioData) (play0 src) warn 42 | 43 | play0 :: forall eff. AudioBufferSourceNode 44 | -> AudioBuffer 45 | -> (Eff (audio :: AUDIO, dom :: DOM | eff) Unit) 46 | play0 src buf = do 47 | setBuffer buf src 48 | startBufferSource 0.0 src 49 | -------------------------------------------------------------------------------- /examples/decode/SimpleDom.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.maybeFn = function (nothing, just, a) { 4 | return a == null ? nothing : just(a); 5 | }; 6 | 7 | exports.makeXMLHttpRequest = function () { 8 | return new XMLHttpRequest(); 9 | }; 10 | 11 | exports.unsafeOpen = function (obj, method, url) { 12 | return function () { 13 | obj.open(method, url); 14 | return {}; 15 | }; 16 | }; 17 | 18 | exports.unsafeResponseType = function (obj) { 19 | return function () { 20 | return obj.responseType; 21 | }; 22 | }; 23 | 24 | exports.unsafeSetResponseType = function (obj, rt) { 25 | return function () { 26 | obj.responseType = rt; 27 | return {}; 28 | }; 29 | }; 30 | 31 | exports.unsafeResponse = function (obj) { 32 | return function () { 33 | return obj.response; 34 | }; 35 | }; 36 | 37 | exports.unsafeSend = function (obj) { 38 | return function () { 39 | obj.send(); 40 | return {}; 41 | }; 42 | }; 43 | 44 | exports.unsafeSendWithPayload = function (obj, payload) { 45 | return function () { 46 | obj.send(payload); 47 | return {}; 48 | }; 49 | }; 50 | 51 | exports.unsafeAddEventListener = function (targ) { 52 | return function (cb) { 53 | return function (src) { 54 | return function () { 55 | src.addEventListener(targ, function (evt) { 56 | cb(evt)(); 57 | }); 58 | }; 59 | }; 60 | }; 61 | }; 62 | 63 | exports.unsafeEventTarget = function (event) { 64 | return function () { 65 | return event.target; 66 | }; 67 | }; 68 | 69 | exports.unsafeEventProp = function (prop) { 70 | return function (event) { 71 | return function () { 72 | return event[prop]; 73 | }; 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /examples/decode/SimpleDom.purs: -------------------------------------------------------------------------------- 1 | module SimpleDom where 2 | 3 | -- Ported necessary components from https://github.com/aktowns/purescript-simple-dom 4 | -- to run Test03 only 5 | 6 | import Prelude 7 | 8 | import Control.Monad.Eff (Eff) 9 | import DOM (DOM) 10 | import Data.ArrayBuffer.Types (ArrayBuffer) 11 | import Data.Function.Uncurried (Fn2, Fn3, Fn1, runFn1, runFn2, runFn3) 12 | 13 | type Url = String 14 | 15 | data HttpMethod = GET 16 | instance showHttpMethod :: Show HttpMethod where 17 | show GET = "GET" 18 | 19 | data ResponseType = ArrayBuffer | Default 20 | instance showResponseType :: Show ResponseType where 21 | show ArrayBuffer = "arraybuffer" 22 | show Default = "" 23 | 24 | data ProgressEventType = ProgressLoadEvent 25 | instance showProgressEventType :: Show ProgressEventType where 26 | show ProgressLoadEvent = "load" 27 | 28 | data HttpData a 29 | = NoData 30 | | ArrayBufferData ArrayBuffer 31 | | TextData String 32 | 33 | foreign import maybeFn :: forall a b. Fn3 b (a -> b) a b 34 | 35 | foreign import data DOMEvent :: Type 36 | foreign import data XMLHttpRequest :: Type 37 | foreign import makeXMLHttpRequest :: forall eff. (Eff (dom :: DOM | eff) XMLHttpRequest) 38 | foreign import unsafeOpen :: forall eff. Fn3 XMLHttpRequest String String (Eff (dom :: DOM | eff) Unit) 39 | foreign import unsafeSend :: forall eff. Fn1 XMLHttpRequest (Eff (dom :: DOM | eff) Unit) 40 | foreign import unsafeSendWithPayload :: forall eff a. Fn2 XMLHttpRequest a (Eff (dom :: DOM | eff) Unit) 41 | foreign import unsafeAddEventListener :: forall eff t e b. String -> (e -> Eff (dom :: DOM | t) Unit) -> b -> (Eff (dom :: DOM | eff) Unit) 42 | foreign import unsafeEventTarget :: forall eff a. DOMEvent -> (Eff (dom :: DOM | eff) a) 43 | foreign import unsafeEventProp :: forall v eff. String -> DOMEvent -> (Eff (dom :: DOM | eff) v) 44 | foreign import unsafeResponseType :: forall eff. XMLHttpRequest -> Eff (dom :: DOM | eff) String 45 | foreign import unsafeResponse :: forall eff a. XMLHttpRequest -> Eff (dom :: DOM | eff) a 46 | foreign import unsafeSetResponseType :: forall eff. Fn2 XMLHttpRequest String (Eff (dom :: DOM | eff) Unit) 47 | 48 | open :: forall eff. HttpMethod -> Url -> XMLHttpRequest -> Eff (dom :: DOM | eff) Unit 49 | open m u x = runFn3 unsafeOpen x (show m) u 50 | 51 | send :: forall eff a. HttpData a -> XMLHttpRequest -> Eff (dom :: DOM | eff) Unit 52 | send _ x = runFn1 unsafeSend x -- NoData (GET) 53 | 54 | class Event e where 55 | eventTarget :: forall eff a. e -> (Eff (dom :: DOM | eff) a) 56 | 57 | instance eventDOMEvent :: Event DOMEvent where 58 | eventTarget = unsafeEventTarget 59 | 60 | readProgressEventType :: String -> ProgressEventType 61 | readProgressEventType "load" = ProgressLoadEvent 62 | readProgressEventType _ = ProgressLoadEvent 63 | 64 | class (Event e) <= ProgressEvent e where 65 | progressEventType :: forall eff. e -> (Eff (dom :: DOM | eff) ProgressEventType) 66 | 67 | instance progressEventDOMEvent :: ProgressEvent DOMEvent where 68 | progressEventType ev = readProgressEventType <$> unsafeEventProp "type" ev 69 | 70 | class ProgressEventTarget b where 71 | addProgressEventListener :: forall e t ta. (ProgressEvent e) => 72 | ProgressEventType 73 | -> (e -> Eff (dom :: DOM | t) Unit) 74 | -> b 75 | -> (Eff (dom :: DOM | ta) Unit) 76 | 77 | 78 | instance progressEventTargetXMLHttpRequest :: ProgressEventTarget XMLHttpRequest where 79 | addProgressEventListener typ = unsafeAddEventListener (show typ) 80 | 81 | responseType :: forall eff. XMLHttpRequest -> Eff (dom :: DOM | eff) ResponseType 82 | responseType obj = do 83 | rt <- unsafeResponseType obj 84 | pure $ case rt of 85 | "arraybuffer" -> ArrayBuffer 86 | _ -> Default 87 | 88 | setResponseType :: forall eff. String -> XMLHttpRequest -> Eff (dom :: DOM | eff) Unit 89 | setResponseType rt xhr = runFn2 unsafeSetResponseType xhr rt 90 | 91 | response :: forall eff a. XMLHttpRequest -> Eff (dom :: DOM | eff) (HttpData a) 92 | response xhr = do 93 | rt <- responseType xhr 94 | case rt of 95 | ArrayBuffer -> get ArrayBufferData 96 | _ -> get TextData -- Default 97 | where 98 | get :: forall eff' a' b. (a' -> HttpData b) -> Eff (dom :: DOM | eff') (HttpData b) 99 | get rt = runFn3 maybeFn NoData rt <$> unsafeResponse xhr 100 | -------------------------------------------------------------------------------- /examples/decode/decode-audio.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waterson/purescript-webaudio/c16fb6faae9c61f0183b787427a31bbade31bac3/examples/decode/decode-audio.wav -------------------------------------------------------------------------------- /examples/decode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Decode Audio Data 5 | 6 | 7 | 8 | 9 |

Decode Audio Data - decode-audio.wav

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/decodeAsync/Main.purs: -------------------------------------------------------------------------------- 1 | module DecodeAsync where 2 | 3 | import Prelude 4 | 5 | import Audio.WebAudio.AudioBufferSourceNode (setBuffer, startBufferSource) 6 | import Audio.WebAudio.BaseAudioContext (createBufferSource, currentTime, decodeAudioDataAsync, destination, newAudioContext) 7 | import Audio.WebAudio.Types (AudioContext, AudioBuffer, AUDIO, connect) 8 | import Control.Monad.Aff (Aff, Fiber, launchAff) 9 | import Control.Monad.Eff (Eff) 10 | import Control.Monad.Eff.Class (liftEff) 11 | import Control.Parallel (parallel, sequential) 12 | import Data.Either (Either(..)) 13 | import Data.HTTP.Method (Method(..)) 14 | import Data.Traversable (traverse) 15 | import Data.Maybe (Maybe(..)) 16 | import Data.Array ((!!)) 17 | import Network.HTTP.Affjax (AJAX, affjax, defaultRequest) 18 | 19 | type ElapsedTime = Number 20 | 21 | -- | load a single sound buffer resource and decode it 22 | loadSoundBuffer :: ∀ eff. 23 | AudioContext 24 | -> String 25 | -> Aff 26 | ( ajax :: AJAX 27 | , audio :: AUDIO 28 | | eff 29 | ) 30 | AudioBuffer 31 | loadSoundBuffer ctx fileName = do 32 | res <- affjax $ defaultRequest { url = fileName, method = Left GET } 33 | buffer <- decodeAudioDataAsync ctx res.response 34 | pure buffer 35 | 36 | -- | load and decode an array of audio buffers from a set of resources 37 | loadSoundBuffers :: ∀ e. 38 | AudioContext 39 | -> (Array String) 40 | -> Aff 41 | ( ajax :: AJAX 42 | , audio :: AUDIO 43 | | e 44 | ) 45 | (Array AudioBuffer) 46 | loadSoundBuffers ctx fileNames = 47 | sequential $ traverse (\name -> parallel (loadSoundBuffer ctx name)) fileNames 48 | 49 | -- | Play a sound at a sepcified elapsed time 50 | playSoundAt :: ∀ eff. 51 | AudioContext 52 | -> Maybe AudioBuffer 53 | -> ElapsedTime 54 | -> Eff 55 | ( audio :: AUDIO 56 | | eff ) 57 | Unit 58 | playSoundAt ctx mbuffer elapsedTime = 59 | case mbuffer of 60 | Just buffer -> 61 | do 62 | startTime <- currentTime ctx 63 | src <- createBufferSource ctx 64 | dst <- destination ctx 65 | _ <- connect src dst 66 | _ <- setBuffer buffer src 67 | -- // We'll start playing the sound 100 milliseconds from "now" 68 | startBufferSource (startTime + elapsedTime + 0.1) src 69 | _ -> 70 | pure unit 71 | 72 | main :: ∀ eff. 73 | Eff 74 | ( ajax :: AJAX 75 | , audio :: AUDIO 76 | | eff 77 | ) 78 | (Fiber 79 | ( ajax :: AJAX 80 | , audio :: AUDIO 81 | | eff 82 | ) 83 | Unit 84 | ) 85 | main = launchAff $ do 86 | ctx <- liftEff newAudioContext 87 | buffers <- loadSoundBuffers ctx ["hihat.wav", "kick.wav", "snare.wav"] 88 | _ <- liftEff $ playSoundAt ctx (buffers !! 0) 0.0 89 | _ <- liftEff $ playSoundAt ctx (buffers !! 1) 0.5 90 | _ <- liftEff $ playSoundAt ctx (buffers !! 2) 1.0 91 | _ <- liftEff $ playSoundAt ctx (buffers !! 0) 1.5 92 | _ <- liftEff $ playSoundAt ctx (buffers !! 1) 2.0 93 | _ <- liftEff $ playSoundAt ctx (buffers !! 2) 2.5 94 | pure unit 95 | -------------------------------------------------------------------------------- /examples/decodeAsync/hihat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waterson/purescript-webaudio/c16fb6faae9c61f0183b787427a31bbade31bac3/examples/decodeAsync/hihat.wav -------------------------------------------------------------------------------- /examples/decodeAsync/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Test04 - Load Audio Buffers asynchronously

6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/decodeAsync/kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waterson/purescript-webaudio/c16fb6faae9c61f0183b787427a31bbade31bac3/examples/decodeAsync/kick.wav -------------------------------------------------------------------------------- /examples/decodeAsync/snare.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waterson/purescript-webaudio/c16fb6faae9c61f0183b787427a31bbade31bac3/examples/decodeAsync/snare.wav -------------------------------------------------------------------------------- /examples/gain/Main.purs: -------------------------------------------------------------------------------- 1 | module Gain where 2 | 3 | import Prelude 4 | 5 | import Audio.WebAudio.BaseAudioContext (createGain, currentTime, destination, newAudioContext, sampleRate) 6 | import Audio.WebAudio.AudioContext (createMediaElementSource) 7 | import Audio.WebAudio.AudioParam (setValueAtTime) 8 | import Audio.WebAudio.GainNode (gain) 9 | import Audio.WebAudio.Types (connect, AUDIO) 10 | import Control.Monad.Eff (Eff) 11 | import Control.Monad.Eff.Console (CONSOLE, logShow) 12 | import Control.Monad.Eff.Exception (EXCEPTION, throw) 13 | import DOM (DOM) 14 | import DOM.HTML (window) 15 | import DOM.HTML.Types (htmlDocumentToNonElementParentNode) 16 | import DOM.HTML.Window (document) 17 | import DOM.Node.NonElementParentNode (getElementById) 18 | import Data.Maybe (Maybe(..)) 19 | import Data.Newtype (wrap) 20 | 21 | -- | 3 secs after the audio begins playing, set the value (i.e., volume) 22 | -- | of the gain node to 0.3 (i.e., a 70% reduction) 23 | 24 | main :: Eff ( dom :: DOM 25 | , audio :: AUDIO 26 | , console :: CONSOLE 27 | , exception :: EXCEPTION 28 | ) Unit 29 | main = do 30 | doc <- map htmlDocumentToNonElementParentNode (window >>= document) 31 | noise <- getElementById (wrap "noise") doc 32 | case noise of 33 | Just el -> void do 34 | cx <- newAudioContext 35 | src <- createMediaElementSource cx el 36 | gainNode <- createGain cx 37 | dest <- destination cx 38 | connect src gainNode 39 | connect gainNode dest 40 | gainParam <- gain gainNode 41 | _ <- setValueAtTime 0.3 3.0 gainParam 42 | sr <- sampleRate cx 43 | logShow sr 44 | t <- currentTime cx 45 | logShow t 46 | Nothing -> throw "No 'noise' node!" 47 | -------------------------------------------------------------------------------- /examples/gain/gain.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waterson/purescript-webaudio/c16fb6faae9c61f0183b787427a31bbade31bac3/examples/gain/gain.wav -------------------------------------------------------------------------------- /examples/gain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gain 5 | 6 | 7 | 8 | 9 |

Play gain.wav, lower volume 3-seconds from start

10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/square-wave/Main.purs: -------------------------------------------------------------------------------- 1 | module SquareWave where 2 | -- Oscillator nodes and gain nodes. 3 | 4 | import Prelude 5 | 6 | import Audio.WebAudio.Types (AUDIO, AudioContext, GainNode, OscillatorNode, AudioContextState(..), connect) 7 | import Audio.WebAudio.BaseAudioContext (createGain, createOscillator, currentTime, destination, newAudioContext, resume, state, suspend) 8 | import Audio.WebAudio.AudioParam (getValue, setValue, setValueAtTime) 9 | import Audio.WebAudio.GainNode (gain) 10 | import Audio.WebAudio.Oscillator (OscillatorType(..), frequency, setOscillatorType, startOscillator) 11 | import Control.Monad.Eff (Eff) 12 | import Control.Monad.Eff.Exception (EXCEPTION, throw) 13 | import Control.Monad.Eff.Ref (REF, Ref, newRef, readRef, writeRef) 14 | import Control.Monad.Eff.Timer (IntervalId, TIMER, clearInterval, setInterval) 15 | import DOM (DOM) 16 | import DOM.Event.EventTarget (addEventListener, eventListener) 17 | import DOM.Event.Types (EventTarget) 18 | import DOM.HTML (window) 19 | import DOM.HTML.Types (htmlDocumentToParentNode) 20 | import DOM.HTML.Window (document) 21 | import DOM.Node.ParentNode (querySelector) 22 | import Data.Maybe (Maybe(..)) 23 | import Data.Newtype (wrap) 24 | import Unsafe.Coerce (unsafeCoerce) 25 | 26 | beep :: ∀ e. AudioContext 27 | -> OscillatorNode 28 | -> GainNode 29 | -> Eff (audio :: AUDIO, timer :: TIMER | e) Unit 30 | beep ctx osc g = do 31 | freqParam <- frequency osc 32 | f <- getValue freqParam 33 | setValue (if f == 55.0 then 53.0 else 55.0) freqParam 34 | 35 | t <- currentTime ctx 36 | gainParam <- gain g 37 | _ <- setValueAtTime 0.5 t gainParam 38 | _ <- setValueAtTime 0.001 (t + 0.2) gainParam 39 | pure unit 40 | 41 | controls :: 42 | ∀ e. Ref IntervalId 43 | -> AudioContext 44 | -> OscillatorNode 45 | -> GainNode 46 | -> Eff (audio :: AUDIO, timer :: TIMER, ref :: REF | e) Unit 47 | controls ref ctx osc g = do 48 | s <- state ctx 49 | if (s == SUSPENDED) 50 | then do 51 | resume ctx 52 | t <- setInterval 1000 $ beep ctx osc g 53 | writeRef ref t 54 | else do 55 | suspend ctx 56 | val <- readRef ref 57 | clearInterval val 58 | 59 | main :: 60 | ∀ e. Eff ( audio :: AUDIO 61 | , dom :: DOM 62 | , exception :: EXCEPTION 63 | , timer :: TIMER 64 | , ref :: REF | e 65 | ) Unit 66 | main = do 67 | ctx <- newAudioContext 68 | 69 | osc <- createOscillator ctx 70 | setOscillatorType Square osc 71 | startOscillator 0.0 osc 72 | 73 | g <- createGain ctx 74 | setValue 0.0 =<< gain g 75 | 76 | connect osc g 77 | connect g =<< destination ctx 78 | 79 | suspend ctx 80 | 81 | let id = unsafeCoerce (newRef 0) :: Ref IntervalId 82 | 83 | doc <- map htmlDocumentToParentNode (window >>= document) 84 | play <- querySelector (wrap "#play") doc 85 | case play of 86 | Just e -> addEventListener (wrap "click") (eventListener \_ -> controls id ctx osc g) false (unsafeCoerce e :: EventTarget) 87 | Nothing -> throw "No 'play' button" 88 | stop <- querySelector (wrap "#stop") doc 89 | case stop of 90 | Just e -> addEventListener (wrap "click") (eventListener \_ -> controls id ctx osc g) false (unsafeCoerce e :: EventTarget) 91 | Nothing -> throw "No 'stop' button" 92 | pure unit 93 | -------------------------------------------------------------------------------- /examples/square-wave/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Square Wave 6 | 7 | 8 | 9 | 10 | 25 |
26 |

SquareWave - Play a square wave every second

27 | 28 | 29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-webaudio", 3 | "version": "0.1.0", 4 | "description": "A PureScript wrapper for the HTML5 Web Audio API", 5 | "main": "", 6 | "scripts": { 7 | "postinstall": "bower install", 8 | "build:clean": "rm -rf dist output examples/*/output test/*/output", 9 | "build": "pulp build", 10 | "build:example:square-wave": "pulp browserify --main SquareWave --to examples/square-wave/output/bundle.js -I examples/square-wave", 11 | "exec:example:square-wave": "cd examples/square-wave/ && python -m SimpleHTTPServer 8000", 12 | "build:example:gain": "pulp build --main Gain --to examples/gain/output/bundle.js -I examples/gain", 13 | "exec:example:gain": "cd examples/gain/ && python -m SimpleHTTPServer 8000", 14 | "build:example:decode": "pulp build --main DecodeAudio --to examples/decode/output/bundle.js -I examples/decode", 15 | "exec:example:decode": "cd examples/decode/ && python -m SimpleHTTPServer 8000", 16 | "build:example:decode-async": "pulp build --main DecodeAsync --to examples/decodeAsync/output/bundle.js -I examples/decodeAsync", 17 | "exec:example:decode-async": "cd examples/decodeAsync/ && python -m SimpleHTTPServer 8000", 18 | "build:test:props": "pulp build --main TestProps --to test/props/output/bundle.js -I test/props", 19 | "exec:test:props": "cd test/props/ && python -m SimpleHTTPServer 8000" 20 | }, 21 | "private": true, 22 | "author": { 23 | "name": "Chris Waterson", 24 | "email": "waterson@maubi.net" 25 | }, 26 | "contributors": [ 27 | { 28 | "name": "Alex Kelley", 29 | "email": "", 30 | "url": "https://github.com/adkelley" 31 | }, 32 | { 33 | "name": "John Watson", 34 | "email": "", 35 | "url": "https://github.com/newlandsvalley" 36 | } 37 | ], 38 | "license": "MIT", 39 | "devDependencies": { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AnalyserNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.getFloatFrequencyData = function(analyserNode) { 4 | return function(dataArray) { 5 | return function() { 6 | return analyserNode.getFloatFrequencyData(dataArray); 7 | }; 8 | }; 9 | }; 10 | 11 | exports.getByteFrequencyData = function(analyserNode) { 12 | return function(dataArray) { 13 | return function() { 14 | return analyserNode.getByteFrequencyData(dataArray); 15 | }; 16 | }; 17 | }; 18 | 19 | exports.getFloatTimeDomainData = function(analyserNode) { 20 | return function(dataArray) { 21 | return function() { 22 | return analyserNode.getFloatTimeDomainData(dataArray); 23 | }; 24 | }; 25 | }; 26 | 27 | exports.getByteTimeDomainData = function(analyserNode) { 28 | return function(dataArray) { 29 | return function() { 30 | return analyserNode.getByteTimeDomainData(dataArray); 31 | }; 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AnalyserNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.AnalyserNode 2 | (fftSize, frequencyBinCount, setFftSize, setFrequencyBinCount, 3 | minDecibels, setMinDecibels, maxDecibels, setMaxDecibels, 4 | smoothingTimeConstant, setSmoothingTimeConstant, 5 | getFloatFrequencyData, getByteFrequencyData, getFloatTimeDomainData, 6 | getByteTimeDomainData ) where 7 | 8 | import Prelude (Unit) 9 | import Data.ArrayBuffer.Types (ByteLength, Uint8Array, Float32Array) 10 | import Audio.WebAudio.Types (AnalyserNode, AUDIO) 11 | import Control.Monad.Eff (Eff) 12 | import Audio.WebAudio.Utils (unsafeGetProp, unsafeSetProp) 13 | 14 | 15 | -- | The fftSize property's value must be a non-zero power of 2 in a range 16 | -- | from 32 to 32768; its default value is 2048. 17 | -- | If its value is not a power of 2, or it is outside the specified range, 18 | -- | the exception INDEX_SIZE_ERR is thrown. 19 | fftSize :: ∀ eff. AnalyserNode -> (Eff (audio :: AUDIO | eff) ByteLength) 20 | fftSize n = unsafeGetProp "fftSize" n 21 | 22 | setFftSize :: ∀ eff. ByteLength -> AnalyserNode -> (Eff (audio :: AUDIO | eff) Unit) 23 | setFftSize size n = unsafeSetProp "fftSize" n size 24 | 25 | frequencyBinCount :: ∀ eff. AnalyserNode -> (Eff (audio :: AUDIO | eff) ByteLength) 26 | frequencyBinCount n = unsafeGetProp "frequencyBinCount" n 27 | 28 | setFrequencyBinCount :: ∀ eff. ByteLength -> AnalyserNode -> (Eff (audio :: AUDIO | eff) Unit) 29 | setFrequencyBinCount count n = unsafeSetProp "frequencyBinCount" n count 30 | 31 | minDecibels :: ∀ eff. AnalyserNode -> (Eff (audio :: AUDIO | eff) Number) 32 | minDecibels n = unsafeGetProp "minDecibels" n 33 | 34 | setMinDecibels :: ∀ eff. Number -> AnalyserNode -> (Eff (audio :: AUDIO | eff) Unit) 35 | setMinDecibels db n = unsafeSetProp "minDecibels" n db 36 | 37 | maxDecibels :: ∀ eff. AnalyserNode -> (Eff (audio :: AUDIO | eff) Number) 38 | maxDecibels n = unsafeGetProp "maxDecibels" n 39 | 40 | setMaxDecibels :: ∀ eff. Number -> AnalyserNode -> (Eff (audio :: AUDIO | eff) Unit) 41 | setMaxDecibels db n = unsafeSetProp "maxDecibels" n db 42 | 43 | smoothingTimeConstant :: ∀ eff. AnalyserNode -> (Eff (audio :: AUDIO | eff) Number) 44 | smoothingTimeConstant n = unsafeGetProp "smoothingTimeConstant" n 45 | 46 | setSmoothingTimeConstant :: ∀ eff. Number -> AnalyserNode -> (Eff (audio :: AUDIO | eff) Unit) 47 | setSmoothingTimeConstant tc n = unsafeSetProp "smoothingTimeConstant" n tc 48 | 49 | 50 | 51 | foreign import getFloatFrequencyData :: ∀ eff. AnalyserNode -> Float32Array -> (Eff (audio :: AUDIO | eff) Unit) 52 | 53 | foreign import getByteFrequencyData :: ∀ eff. AnalyserNode -> Uint8Array -> (Eff (audio :: AUDIO | eff) Unit) 54 | 55 | foreign import getFloatTimeDomainData :: ∀ eff. AnalyserNode -> Float32Array -> (Eff (audio :: AUDIO | eff) Unit) 56 | 57 | foreign import getByteTimeDomainData :: ∀ eff. AnalyserNode -> Uint8Array -> (Eff (audio :: AUDIO | eff) Unit) 58 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AudioBufferSourceNode.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.AudioBufferSourceNode 6 | 7 | exports.setBuffer = function(buf) { 8 | return function(src) { 9 | return function() { 10 | src.buffer = buf; 11 | }; 12 | }; 13 | }; 14 | 15 | exports.startBufferSource = function(when) { 16 | return function(src) { 17 | return function() { 18 | src.start(when); 19 | }; 20 | }; 21 | }; 22 | 23 | exports.stopBufferSource = function(when) { 24 | return function(src) { 25 | return function() { 26 | src.stop(when); 27 | }; 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AudioBufferSourceNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.AudioBufferSourceNode 2 | ( setBuffer, startBufferSource, stopBufferSource 3 | , loop, setLoop, loopStart, setLoopStart, loopEnd, setLoopEnd ) where 4 | 5 | import Prelude 6 | import Control.Monad.Eff (Eff) 7 | import Audio.WebAudio.Types (AudioBuffer, AudioBufferSourceNode, AUDIO) 8 | import Audio.WebAudio.Utils (unsafeGetProp, unsafeSetProp) 9 | 10 | foreign import setBuffer 11 | :: ∀ eff. AudioBuffer 12 | -> AudioBufferSourceNode 13 | -> (Eff (audio :: AUDIO | eff) Unit) 14 | 15 | foreign import startBufferSource 16 | :: ∀ eff. Number 17 | -> AudioBufferSourceNode 18 | -> (Eff (audio :: AUDIO | eff) Unit) 19 | 20 | foreign import stopBufferSource 21 | :: ∀ eff. Number 22 | -> AudioBufferSourceNode 23 | -> (Eff (audio :: AUDIO | eff) Unit) 24 | 25 | loop :: ∀ eff. AudioBufferSourceNode -> (Eff (audio :: AUDIO | eff) Boolean) 26 | loop = unsafeGetProp "loop" 27 | 28 | setLoop :: ∀ eff. Boolean -> AudioBufferSourceNode -> (Eff (audio :: AUDIO | eff) Unit) 29 | setLoop l n = unsafeSetProp "loop" n l 30 | 31 | loopStart :: ∀ eff. AudioBufferSourceNode -> (Eff (audio :: AUDIO | eff) Number) 32 | loopStart = unsafeGetProp "loopStart" 33 | 34 | setLoopStart :: ∀ eff. Number -> AudioBufferSourceNode -> (Eff (audio :: AUDIO | eff) Unit) 35 | setLoopStart l n = unsafeSetProp "loopStart" n l 36 | 37 | loopEnd :: ∀ eff. AudioBufferSourceNode -> (Eff (audio :: AUDIO | eff) Number) 38 | loopEnd = unsafeGetProp "loopEnd" 39 | 40 | setLoopEnd :: ∀ eff. Number -> AudioBufferSourceNode -> (Eff (audio :: AUDIO | eff) Unit) 41 | setLoopEnd l n = unsafeSetProp "loopEnd" n l 42 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AudioContext.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.AudioContext 6 | 7 | exports.createMediaElementSource = function(ctx) { 8 | return function(elt) { 9 | return function() { 10 | return ctx.createMediaElementSource(elt); 11 | }; 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AudioContext.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.AudioContext 2 | ( createMediaElementSource 3 | ) where 4 | 5 | import Audio.WebAudio.Types (AUDIO, AudioContext, MediaElementAudioSourceNode) 6 | import Control.Monad.Eff (Eff) 7 | 8 | -- | Creates a MediaElementAudioSourceNode given an HTMLMediaElement. As a consequence of 9 | -- | calling this method, audio playback from the HTMLMediaElement will be re-routed into 10 | -- | the processing graph of the AudioContext. 11 | foreign import createMediaElementSource 12 | :: ∀ elt eff. AudioContext 13 | -> elt -- |^ a DOM element from which to construct the source node. todo: HTML? 14 | -> (Eff (audio :: AUDIO | eff) MediaElementAudioSourceNode) 15 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AudioParam.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.AudioParam 6 | 7 | exports.setValue = function(value) { 8 | return function(param) { 9 | return function() { 10 | param.value = value; 11 | }; 12 | }; 13 | }; 14 | 15 | 16 | exports.getValue = function(param) { 17 | return function() { 18 | return param.value; 19 | }; 20 | }; 21 | 22 | 23 | exports.setValueAtTime = function(value) { 24 | return function(startTime) { 25 | return function(param) { 26 | return function() { 27 | param.setValueAtTime(value, startTime); 28 | }; 29 | }; 30 | }; 31 | }; 32 | 33 | exports.setTargetAtTime = function(value) { 34 | return function(startTime) { 35 | return function(timeConstant) { 36 | return function(param) { 37 | return function() { 38 | param.setTargetAtTime(value, startTime, timeConstant); 39 | }; 40 | }; 41 | }; 42 | }; 43 | }; 44 | 45 | 46 | exports.linearRampToValueAtTime = function(value) { 47 | return function(endTime) { 48 | return function(param) { 49 | return function() { 50 | param.linearRampToValueAtTime(value, endTime); 51 | }; 52 | }; 53 | }; 54 | }; 55 | 56 | 57 | exports.exponentialRampToValueAtTime = function(value) { 58 | return function(endTime) { 59 | return function(param) { 60 | return function() { 61 | param.exponentialRampToValueAtTime(value, endTime); 62 | }; 63 | }; 64 | }; 65 | }; 66 | 67 | 68 | exports.cancelScheduledValues = function(startTime) { 69 | return function(param) { 70 | return function() { 71 | param.cancelScheduledValues(startTime); 72 | }; 73 | }; 74 | }; 75 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/AudioParam.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.AudioParam 2 | (setTargetAtTime, setValueAtTime, getValue, setValue 3 | , linearRampToValueAtTime, exponentialRampToValueAtTime, cancelScheduledValues 4 | ) where 5 | 6 | import Prelude (Unit) 7 | import Control.Monad.Eff (Eff) 8 | import Audio.WebAudio.Types (AudioParam, AUDIO, Value, Seconds) 9 | 10 | foreign import setValue 11 | :: ∀ eff. Value -> AudioParam -> (Eff (audio :: AUDIO | eff) Unit) 12 | 13 | foreign import setValueAtTime 14 | :: ∀ eff. Value -> Seconds -> AudioParam -> (Eff (audio :: AUDIO | eff) Value) 15 | 16 | foreign import setTargetAtTime 17 | :: ∀ eff. Value -> Seconds -> Seconds -> AudioParam -> (Eff (audio :: AUDIO | eff) Value) 18 | 19 | foreign import getValue 20 | :: ∀ eff. AudioParam -> (Eff (audio :: AUDIO | eff) Value) 21 | 22 | foreign import linearRampToValueAtTime 23 | :: ∀ eff. Value -> Seconds -> AudioParam -> (Eff (audio :: AUDIO | eff) Value) 24 | 25 | foreign import exponentialRampToValueAtTime 26 | :: ∀ eff. Value -> Seconds -> AudioParam -> (Eff (audio :: AUDIO | eff) Value) 27 | 28 | foreign import cancelScheduledValues 29 | :: ∀ eff. Value -> AudioParam -> (Eff (audio :: AUDIO | eff) Value) 30 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/BaseAudioContext.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.AudioContext 6 | 7 | exports.sampleRate = function(cx) { 8 | return function() { 9 | return cx.sampleRate; 10 | }; 11 | }; 12 | 13 | exports.currentTime = function(cx) { 14 | return function() { 15 | return cx.currentTime; 16 | }; 17 | }; 18 | 19 | exports._state = function(ctx) { 20 | return function() { 21 | return ctx.state; 22 | }; 23 | }; 24 | 25 | exports.suspend = function(ctx) { 26 | return function() { 27 | return ctx.suspend(); 28 | }; 29 | }; 30 | 31 | exports.resume = function(ctx) { 32 | return function() { 33 | return ctx.resume(); 34 | }; 35 | }; 36 | 37 | exports.decodeAudioData = function(cx) { 38 | return function(audioData) { 39 | return function(success) { 40 | return function(failure) { 41 | return function() { 42 | cx.decodeAudioData(audioData, 43 | function(data) { 44 | success(data)(); 45 | }, 46 | function(e) { 47 | failure(e.err); 48 | }); 49 | }; 50 | }; 51 | }; 52 | }; 53 | }; 54 | 55 | /* uncurrried version */ 56 | function _decodeAudioData (cx, audioData, onError, onSuccess) { 57 | cx.decodeAudioData(audioData, function (buff) { 58 | // console.log('buffer decoded OK '); 59 | onSuccess(buff); 60 | }, 61 | function (e) { 62 | // console.log('buffer decode failed '); 63 | onError(e.err); 64 | }); 65 | }; 66 | 67 | 68 | exports.decodeAudioDataAsyncImpl = function(cx) { 69 | return function(audioData) { 70 | return function (onError, onSuccess) { 71 | _decodeAudioData (cx, audioData, onError, onSuccess); 72 | // Return a canceler, which is just another Aff effect. 73 | return function (cancelError, cancelerError, cancelerSuccess) { 74 | cancelerSuccess(); // invoke the success callback for the canceler 75 | }; 76 | }; 77 | }; 78 | }; 79 | 80 | 81 | exports.createBufferSource = function(cx) { 82 | return function() { 83 | return cx.createBufferSource(); 84 | }; 85 | }; 86 | 87 | exports.createGain = function(ctx) { 88 | return function() { 89 | return ctx.createGain(); 90 | }; 91 | }; 92 | 93 | exports.createOscillator = function(ctx) { 94 | return function() { 95 | return ctx.createOscillator(); 96 | }; 97 | }; 98 | 99 | exports.createBiquadFilter = function(ctx) { 100 | return function() { 101 | return ctx.createBiquadFilter(); 102 | }; 103 | }; 104 | 105 | exports.createDelay = function(ctx) { 106 | return function() { 107 | return ctx.createDelay(); 108 | }; 109 | }; 110 | 111 | exports.createAnalyser = function(ctx) { 112 | return function() { 113 | return ctx.createAnalyser(); 114 | }; 115 | }; 116 | 117 | exports.createStereoPanner = function(ctx) { 118 | return function() { 119 | return ctx.createStereoPanner(); 120 | }; 121 | }; 122 | 123 | exports.createDynamicsCompressor = function(ctx) { 124 | return function() { 125 | return ctx.createDynamicsCompressor(); 126 | }; 127 | }; 128 | 129 | exports.createConvolver = function(ctx) { 130 | return function() { 131 | return ctx.createConvolver(); 132 | }; 133 | }; 134 | 135 | exports.newAudioContext = function() { 136 | return new (window.AudioContext || window.webkitAudioContext)(); 137 | }; 138 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/BaseAudioContext.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.BaseAudioContext 2 | ( newAudioContext, destination, sampleRate, currentTime, state 3 | , suspend, resume, decodeAudioData, decodeAudioDataAsync 4 | , createBufferSource, createGain, createOscillator 5 | , createAnalyser, createBiquadFilter, createConvolver, createDelay 6 | , createDynamicsCompressor, createStereoPanner 7 | ) where 8 | 9 | import Prelude 10 | 11 | import Audio.WebAudio.Types 12 | ( AUDIO, AudioBuffer, AudioBufferSourceNode, AudioContext 13 | , DestinationNode, GainNode, OscillatorNode 14 | , DelayNode, BiquadFilterNode, AnalyserNode 15 | , StereoPannerNode, DynamicsCompressorNode, ConvolverNode 16 | , Seconds, Value, AudioContextState(..)) 17 | import Audio.WebAudio.Utils (unsafeGetProp) 18 | import Control.Monad.Eff (Eff) 19 | import Control.Monad.Eff.Console (CONSOLE) 20 | import Control.Monad.Aff (Aff) 21 | import Control.Monad.Aff.Compat (EffFnAff, fromEffFnAff) 22 | import Data.ArrayBuffer.Types (ArrayBuffer) 23 | 24 | -- | The audio graph whose AudioDestinationNode is routed to a real-time output device 25 | -- | that produces a signal directed at the user. 26 | -- | var context = new AudioContext() 27 | foreign import newAudioContext 28 | :: ∀ eff. (Eff (audio :: AUDIO | eff) AudioContext) 29 | 30 | -- | An AudioDestinationNode with a single input representing the final destination for all audio. 31 | destination 32 | :: ∀ eff. AudioContext 33 | -> (Eff (audio :: AUDIO | eff) DestinationNode) 34 | destination = unsafeGetProp "destination" 35 | 36 | -- | The sample rate (in sample-frames per second) at which the BaseAudioContext handles audio. 37 | foreign import sampleRate 38 | :: ∀ eff. AudioContext 39 | -> (Eff (audio :: AUDIO | eff) Value) 40 | 41 | foreign import currentTime 42 | :: ∀ eff. AudioContext 43 | -> (Eff (audio :: AUDIO | eff) Seconds) 44 | 45 | -- | An AudioListener which is used for 3D spatialization. 46 | -- | todo: listener :: .. 47 | 48 | foreign import _state :: ∀ eff. AudioContext -> Eff (audio :: AUDIO | eff) String 49 | 50 | -- | Describes the current state of this BaseAudioContext. (reaonly) 51 | state :: ∀ eff. AudioContext -> Eff (audio :: AUDIO | eff) AudioContextState 52 | state ctx = do 53 | s <- _state ctx 54 | pure $ 55 | case s of 56 | "suspended" -> SUSPENDED 57 | "running" -> RUNNING 58 | "closed" -> CLOSED 59 | _ -> CLOSED -- ^avoid making a Partial instance 60 | 61 | foreign import suspend :: ∀ eff. AudioContext -> Eff (audio :: AUDIO | eff) Unit 62 | 63 | foreign import resume :: ∀ eff. AudioContext -> Eff (audio :: AUDIO | eff) Unit 64 | 65 | -- | Closes the audio context, releasing any system audio resources used by the BaseAudioContext. 66 | -- | todo: close :: .. 67 | 68 | -- | A property used to set the EventHandler for an event that is dispatched to BaseAudioContext 69 | -- | when the state of the AudioContext has changed (i.e. when the corresponding promise would have resolved). 70 | -- | todo: onstatechange :: .. 71 | 72 | -- | Asynchronously decodes the audio file data contained in the ArrayBuffer. 73 | foreign import decodeAudioData 74 | :: ∀ eff f. 75 | AudioContext 76 | -> ArrayBuffer 77 | -> (AudioBuffer -> Eff (audio :: AUDIO | eff) Unit) -- sucesss 78 | -> (String -> Eff (console :: CONSOLE | eff) Unit) -- failure 79 | -> (Eff (audio :: AUDIO | f) Unit) 80 | 81 | foreign import decodeAudioDataAsyncImpl 82 | :: ∀ eff. 83 | AudioContext 84 | -> ArrayBuffer 85 | -> EffFnAff (audio :: AUDIO | eff) AudioBuffer 86 | 87 | -- | decode the Audio Buffer asynchronously 88 | decodeAudioDataAsync 89 | :: ∀ eff. 90 | AudioContext 91 | -> ArrayBuffer 92 | -> Aff (audio :: AUDIO | eff) AudioBuffer 93 | decodeAudioDataAsync ctx = 94 | fromEffFnAff <<< (decodeAudioDataAsyncImpl ctx) 95 | 96 | 97 | -- | Creates an AudioBufferSourceNode. 98 | foreign import createBufferSource 99 | :: ∀ eff. AudioContext 100 | -> (Eff (audio :: AUDIO | eff) AudioBufferSourceNode) 101 | 102 | -- | Create a GainNode. 103 | foreign import createGain 104 | :: ∀ eff. AudioContext 105 | -> (Eff (audio :: AUDIO | eff) GainNode) 106 | 107 | -- | Create an OscillatorNode 108 | foreign import createOscillator 109 | :: ∀ eff. AudioContext 110 | -> (Eff (audio :: AUDIO | eff) OscillatorNode) 111 | 112 | -- | Create a DelayNode. 113 | -- | createDelay also has an alternative constructor with a maximum delay 114 | -- | note, if you don't set a max, it defaults to 1.0 and any attempt to set a greater value gives 115 | -- | "paramDelay.delayTime.value 2 outside nominal range [0, 1]; value will be clamped." 116 | foreign import createDelay 117 | :: ∀ eff. AudioContext 118 | -> (Eff (audio :: AUDIO | eff) DelayNode) 119 | 120 | -- | Create a BiquadFilterNode. 121 | foreign import createBiquadFilter 122 | :: ∀ eff. AudioContext 123 | -> (Eff (audio :: AUDIO | eff) BiquadFilterNode) 124 | 125 | -- | create an AnalyserNode. 126 | foreign import createAnalyser 127 | :: ∀ eff. AudioContext 128 | -> (Eff (audio :: AUDIO | eff) AnalyserNode) 129 | 130 | -- | Create a StereoPannerNode, 131 | foreign import createStereoPanner 132 | :: ∀ eff. AudioContext 133 | -> (Eff (audio :: AUDIO | eff) StereoPannerNode) 134 | 135 | -- | Create a DynamicsCompressorNode. 136 | foreign import createDynamicsCompressor 137 | :: ∀ eff. AudioContext 138 | -> (Eff (audio :: AUDIO | eff) DynamicsCompressorNode) 139 | 140 | -- | Create a ConvolverNode. 141 | foreign import createConvolver 142 | :: ∀ eff. AudioContext 143 | -> (Eff (audio :: AUDIO | eff) ConvolverNode) 144 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/BiquadFilterNode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // module Audio.WebAudio.BiquadFilterNode 4 | 5 | exports.gain = function(node) { 6 | return function() { 7 | return node.gain; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/BiquadFilterNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.BiquadFilterNode 2 | ( BiquadFilterType(..), readBiquadFilterType, filterType, setFilterType 3 | , filterFrequency, quality, gain) where 4 | 5 | import Audio.WebAudio.Types 6 | import Audio.WebAudio.Utils (unsafeGetProp, unsafeSetProp) 7 | import Prelude (class Show, class Eq, class Ord, Unit, show, (<$>)) 8 | 9 | import Control.Monad.Eff (Eff) 10 | 11 | data BiquadFilterType = 12 | Lowpass 13 | | Highpass 14 | | Bandpass 15 | | Lowshelf 16 | | Highshelf 17 | | Peaking 18 | | Notch 19 | | Allpass 20 | 21 | instance biquadFilterTypeShow :: Show BiquadFilterType where 22 | show Lowpass = "lowpass" 23 | show Highpass = "highpass" 24 | show Bandpass = "bandpass" 25 | show Lowshelf = "lowshelf" 26 | show Highshelf = "highshelf" 27 | show Peaking = "peaking" 28 | show Notch = "notch" 29 | show Allpass = "allpass" 30 | 31 | derive instance eqBiquadFilterType :: Eq BiquadFilterType 32 | derive instance ordBiquadFilterType :: Ord BiquadFilterType 33 | 34 | 35 | readBiquadFilterType :: String -> BiquadFilterType 36 | readBiquadFilterType "lowpass" = Lowpass 37 | readBiquadFilterType "highpass" = Highpass 38 | readBiquadFilterType "bandpass" = Bandpass 39 | readBiquadFilterType "highshelf" = Highshelf 40 | readBiquadFilterType "peaking" = Peaking 41 | readBiquadFilterType "notch" = Notch 42 | readBiquadFilterType "allpass" = Allpass 43 | readBiquadFilterType _ = Lowpass 44 | 45 | filterType :: ∀ eff. BiquadFilterNode -> (Eff (audio :: AUDIO | eff) BiquadFilterType) 46 | filterType n = readBiquadFilterType <$> unsafeGetProp "type" n 47 | 48 | setFilterType :: ∀ eff. BiquadFilterType -> BiquadFilterNode -> (Eff (audio :: AUDIO | eff) Unit) 49 | setFilterType t n = unsafeSetProp "type" n (show t) 50 | 51 | filterFrequency :: ∀ eff. BiquadFilterNode -> (Eff (audio :: AUDIO | eff) AudioParam) 52 | filterFrequency = unsafeGetProp "frequency" 53 | 54 | quality :: ∀ eff. BiquadFilterNode -> (Eff (audio :: AUDIO | eff) AudioParam) 55 | quality = unsafeGetProp "Q" 56 | 57 | foreign import gain 58 | :: forall eff. BiquadFilterNode -> (Eff (audio :: AUDIO | eff) AudioParam) 59 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/ConvolverNode.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.ConvolverNode 6 | 7 | 8 | exports.setBuffer = function(buf) { 9 | return function(src) { 10 | return function() { 11 | src.buffer = buf; 12 | }; 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/ConvolverNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.ConvolverNode (setBuffer, normalize, isNormalized) where 2 | 3 | import Prelude (Unit) 4 | import Control.Monad.Eff (Eff) 5 | import Audio.WebAudio.Types (AudioBuffer, ConvolverNode, AUDIO) 6 | import Audio.WebAudio.Utils (unsafeGetProp, unsafeSetProp) 7 | 8 | foreign import setBuffer 9 | :: ∀ eff. AudioBuffer 10 | -> ConvolverNode 11 | -> (Eff (audio :: AUDIO | eff) Unit) 12 | 13 | normalize :: ∀ eff. Boolean -> ConvolverNode -> (Eff (audio :: AUDIO | eff) Unit) 14 | normalize l n = unsafeSetProp "normalize" n l 15 | 16 | isNormalized :: ∀ eff. ConvolverNode -> (Eff (audio :: AUDIO | eff) Boolean) 17 | isNormalized = unsafeGetProp "normalize" 18 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/DelayNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.DelayNode (delayTime) where 2 | 3 | import Control.Monad.Eff (Eff) 4 | import Audio.WebAudio.Types (AudioParam, DelayNode, AUDIO) 5 | import Audio.WebAudio.Utils (unsafeGetProp) 6 | 7 | delayTime :: ∀ eff. DelayNode -> (Eff (audio :: AUDIO | eff) AudioParam) 8 | delayTime = unsafeGetProp "delayTime" 9 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/DestinationNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.DestinationNode where 2 | 3 | -- import Audio.WebAudio.Types 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/DynamicsCompressorNode.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.DynamicsCompressorNode 6 | 7 | exports.threshold = function(node) { 8 | return function() { 9 | return node.threshold; 10 | }; 11 | }; 12 | 13 | exports.knee = function(node) { 14 | return function() { 15 | return node.knee; 16 | }; 17 | }; 18 | 19 | exports.ratio = function(node) { 20 | return function() { 21 | return node.ratio; 22 | }; 23 | }; 24 | 25 | exports.attack = function(node) { 26 | return function() { 27 | return node.attack; 28 | }; 29 | }; 30 | 31 | exports.release = function(node) { 32 | return function() { 33 | return node.release; 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/DynamicsCompressorNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.DynamicsCompressorNode where 2 | 3 | import Control.Monad.Eff (Eff) 4 | import Audio.WebAudio.Types (AudioParam, DynamicsCompressorNode, AUDIO) 5 | import Audio.WebAudio.Utils (unsafeGetProp) 6 | 7 | foreign import threshold 8 | :: forall eff. DynamicsCompressorNode -> (Eff (audio :: AUDIO | eff) AudioParam) 9 | 10 | foreign import knee 11 | :: forall eff. DynamicsCompressorNode -> (Eff (audio :: AUDIO | eff) AudioParam) 12 | 13 | foreign import ratio 14 | :: forall eff. DynamicsCompressorNode -> (Eff (audio :: AUDIO | eff) AudioParam) 15 | 16 | -- | reduction is read-only 17 | reduction :: ∀ eff. DynamicsCompressorNode -> (Eff (audio :: AUDIO | eff) Number) 18 | reduction n = unsafeGetProp "reduction" n 19 | 20 | foreign import attack 21 | :: forall eff. DynamicsCompressorNode -> (Eff (audio :: AUDIO | eff) AudioParam) 22 | 23 | foreign import release 24 | :: forall eff. DynamicsCompressorNode -> (Eff (audio :: AUDIO | eff) AudioParam) 25 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/GainNode.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.GainNode 6 | 7 | exports.gain = function(node) { 8 | return function() { 9 | return node.gain; 10 | }; 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/GainNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.GainNode (gain, setGain) where 2 | 3 | import Prelude (Unit, (=<<)) 4 | import Control.Monad.Eff (Eff) 5 | import Audio.WebAudio.Types (AudioParam, GainNode, AUDIO) 6 | import Audio.WebAudio.AudioParam (setValue) 7 | 8 | foreign import gain 9 | :: forall eff. GainNode -> (Eff (audio :: AUDIO | eff) AudioParam) 10 | 11 | setGain :: ∀ eff. Number -> GainNode -> (Eff (audio :: AUDIO | eff) Unit) 12 | setGain num node = 13 | setValue num =<< gain node 14 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/MediaElementAudioSourceNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.MediaElementAudioSourceNode where 2 | 3 | -- import Audio.WebAudio.Types 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/Oscillator.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.Oscillator 6 | 7 | exports.startOscillator = function(when) { 8 | return function(n) { 9 | return function() { 10 | return n[n.start ? 'start' : 'noteOn'](when); 11 | }; 12 | }; 13 | }; 14 | 15 | 16 | exports.stopOscillator = function(when) { 17 | return function(n) { 18 | return function() { 19 | return n[n.stop ? 'stop' : 'noteOff'](when); 20 | }; 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/Oscillator.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.Oscillator 2 | ( OscillatorType(..), readOscillatorType, frequency, detune, setFrequency 3 | , setDetune, oscillatorType, setOscillatorType, startOscillator 4 | , stopOscillator) where 5 | 6 | import Prelude 7 | import Control.Monad.Eff (Eff) 8 | import Audio.WebAudio.Types (AudioParam, OscillatorNode, AUDIO) 9 | import Audio.WebAudio.Utils (unsafeGetProp, unsafeSetProp) 10 | import Audio.WebAudio.AudioParam (setValue) 11 | 12 | data OscillatorType = Sine | Square | Sawtooth | Triangle | Custom 13 | 14 | instance oscillatorTypeShow :: Show OscillatorType where 15 | show Sine = "sine" 16 | show Square = "square" 17 | show Sawtooth = "sawtooth" 18 | show Triangle = "triangle" 19 | show Custom = "custom" 20 | 21 | readOscillatorType :: String -> OscillatorType 22 | readOscillatorType "sine" = Sine 23 | readOscillatorType "square" = Square 24 | readOscillatorType "sawtooth" = Sawtooth 25 | readOscillatorType "triangle" = Triangle 26 | readOscillatorType "custom" = Custom 27 | readOscillatorType _ = Sine 28 | 29 | derive instance eqOscillatorType :: Eq OscillatorType 30 | derive instance ordOscillatorType :: Ord OscillatorType 31 | 32 | frequency :: ∀ eff. OscillatorNode -> (Eff (audio :: AUDIO| eff) AudioParam) 33 | frequency = unsafeGetProp "frequency" 34 | 35 | setFrequency :: ∀ eff. Number -> OscillatorNode -> (Eff (audio :: AUDIO| eff) Unit) 36 | setFrequency num node = 37 | setValue num =<< frequency node 38 | 39 | detune :: ∀ eff. OscillatorNode -> (Eff (audio :: AUDIO | eff) AudioParam) 40 | detune = unsafeGetProp "detune" 41 | 42 | setDetune :: ∀ eff. Number -> OscillatorNode -> (Eff (audio :: AUDIO | eff) Unit) 43 | setDetune num node = 44 | setValue num =<< detune node 45 | 46 | oscillatorType :: ∀ eff. OscillatorNode -> (Eff (audio :: AUDIO | eff) OscillatorType) 47 | oscillatorType n = readOscillatorType <$> unsafeGetProp "type" n 48 | 49 | setOscillatorType :: ∀ eff. OscillatorType -> OscillatorNode -> (Eff (audio :: AUDIO | eff) Unit) 50 | setOscillatorType t n = unsafeSetProp "type" n $ show t 51 | 52 | foreign import startOscillator :: ∀ eff. Number -> OscillatorNode -> (Eff (audio :: AUDIO | eff) Unit) 53 | foreign import stopOscillator :: ∀ eff. Number -> OscillatorNode -> (Eff (audio :: AUDIO | eff) Unit) 54 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/StereoPannerNode.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.StereoPannerNode 6 | 7 | exports.pan = function(node) { 8 | return function() { 9 | return node.pan; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/StereoPannerNode.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.StereoPannerNode where 2 | 3 | import Control.Monad.Eff (Eff) 4 | import Audio.WebAudio.Types (AudioParam, StereoPannerNode, AUDIO) 5 | 6 | foreign import pan 7 | :: forall eff. StereoPannerNode -> (Eff (audio :: AUDIO | eff) AudioParam) 8 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/Types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | exports.nodeConnect = function(_) { 5 | return function(_) { 6 | return function(source) { 7 | return function(sink) { 8 | return function() { 9 | source.connect(sink); 10 | }; 11 | }; 12 | }; 13 | }; 14 | }; 15 | 16 | exports.nodeDisconnect = function(_) { 17 | return function(_) { 18 | return function(source) { 19 | return function(sink) { 20 | return function() { 21 | source.disconnect(sink); 22 | }; 23 | }; 24 | }; 25 | }; 26 | }; 27 | 28 | exports.unsafeConnectParam = function(_) { 29 | return function(_) { 30 | return function(source) { 31 | return function(target) { 32 | return function(prop) { 33 | return function() { 34 | var value = target[prop]; 35 | source.connect(value); 36 | }; 37 | }; 38 | }; 39 | }; 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/Types.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.Types 2 | ( class RawAudioNode, class Connectable, AudioNode(..) 3 | , AudioBuffer, AudioBufferSourceNode, AudioContext 4 | , AudioParam, DestinationNode, BiquadFilterNode 5 | , GainNode, MediaElementAudioSourceNode 6 | , DelayNode, OscillatorNode, AnalyserNode, StereoPannerNode 7 | , DynamicsCompressorNode, ConvolverNode, AUDIO 8 | , AudioContextState(..), AudioContextPlaybackCategory(..) 9 | , Value, Seconds 10 | , connect, disconnect, connectParam) where 11 | 12 | import Control.Monad.Eff (kind Effect, Eff) 13 | import Prelude (class Show, class Eq, Unit, pure, unit) 14 | foreign import data AUDIO :: Effect 15 | 16 | foreign import data AudioBuffer :: Type 17 | foreign import data AudioBufferSourceNode :: Type 18 | foreign import data AudioContext :: Type 19 | foreign import data AudioParam :: Type 20 | foreign import data DestinationNode :: Type 21 | foreign import data GainNode :: Type 22 | foreign import data MediaElementAudioSourceNode :: Type 23 | foreign import data OscillatorNode :: Type 24 | foreign import data BiquadFilterNode :: Type 25 | foreign import data DelayNode :: Type 26 | foreign import data AnalyserNode :: Type 27 | foreign import data StereoPannerNode :: Type 28 | foreign import data DynamicsCompressorNode :: Type 29 | foreign import data ConvolverNode :: Type 30 | 31 | -- | a 'raw' web-audio node 32 | class RawAudioNode n 33 | 34 | data AudioContextState = SUSPENDED | RUNNING | CLOSED 35 | 36 | derive instance eqAudioContextState :: Eq AudioContextState 37 | instance showAudioContextState :: Show AudioContextState where 38 | show SUSPENDED = "suspended" 39 | show RUNNING = "running" 40 | show CLOSED = "closed" 41 | 42 | data AudioContextPlaybackCategory = BALANCED | INTERACTIVE | PLAYBACK 43 | derive instance eqAudioPlaybackCategory :: Eq AudioContextPlaybackCategory 44 | instance showAudioContextPlaybackCategory :: Show AudioContextPlaybackCategory where 45 | show BALANCED = "balanced" 46 | show INTERACTIVE = "interactive" 47 | show PLAYBACK = "playback" 48 | 49 | 50 | -- | Type synonym for the value argument (e.g., Volume) 51 | type Value = Number 52 | 53 | -- | Type synonym for time (in seconds) argument 54 | type Seconds = Number 55 | 56 | 57 | instance audioNodeAudioBufferSourceNodeconnectParam :: RawAudioNode AudioBufferSourceNode 58 | instance audioNodeMediaElementAudioSourceNode :: RawAudioNode MediaElementAudioSourceNode 59 | instance audioNodeGainNode :: RawAudioNode GainNode 60 | instance audioNodeDestinationNode :: RawAudioNode DestinationNode 61 | instance audioNodeOscillatorNode :: RawAudioNode OscillatorNode 62 | instance audioNodeBiquadFilterNode :: RawAudioNode BiquadFilterNode 63 | instance audioNodeDelayNode :: RawAudioNode DelayNode 64 | instance audioNodeAnalyserNode :: RawAudioNode AnalyserNode 65 | instance audioNodeStereoPannerNode :: RawAudioNode StereoPannerNode 66 | instance audioNodeDynamicsCompressorNode :: RawAudioNode DynamicsCompressorNode 67 | instance audioNodeDynamicsCConvolverNode :: RawAudioNode ConvolverNode 68 | 69 | -- | a web audio node that is connectable/disconnectable from another node 70 | -- | or whose parameter(s) may be connectable from another node 71 | class Connectable target where 72 | connect :: ∀ eff source. RawAudioNode source => source -> target -> (Eff (audio :: AUDIO | eff) Unit) 73 | disconnect :: ∀ eff source. RawAudioNode source => source -> target -> (Eff (audio :: AUDIO | eff) Unit) 74 | connectParam :: ∀ eff source. RawAudioNode source => source -> target -> String -> (Eff (audio :: AUDIO| eff) Unit) 75 | 76 | instance connectableGainNode :: Connectable GainNode where 77 | connect = nodeConnect 78 | disconnect = nodeDisconnect 79 | connectParam = unsafeConnectParam 80 | 81 | instance connectableOscillatorNode :: Connectable OscillatorNode where 82 | connect = nodeConnect 83 | disconnect = nodeDisconnect 84 | connectParam = unsafeConnectParam 85 | 86 | instance connectableBiquadFilterNode :: Connectable BiquadFilterNode where 87 | connect = nodeConnect 88 | disconnect = nodeDisconnect 89 | connectParam = unsafeConnectParam 90 | 91 | instance connectableDelayNode :: Connectable DelayNode where 92 | connect = nodeConnect 93 | disconnect = nodeDisconnect 94 | connectParam = unsafeConnectParam 95 | 96 | instance connectableAnalyserNode :: Connectable AnalyserNode where 97 | connect = nodeConnect 98 | disconnect = nodeDisconnect 99 | connectParam = unsafeConnectParam 100 | 101 | instance connectableStereoPannerNode :: Connectable StereoPannerNode where 102 | connect = nodeConnect 103 | disconnect = nodeDisconnect 104 | connectParam = unsafeConnectParam 105 | 106 | instance connectableDynamicsCompressorNode :: Connectable DynamicsCompressorNode where 107 | connect = nodeConnect 108 | disconnect = nodeDisconnect 109 | connectParam = unsafeConnectParam 110 | 111 | instance connectableConvolverNode :: Connectable ConvolverNode where 112 | connect = nodeConnect 113 | disconnect = nodeDisconnect 114 | connectParam = unsafeConnectParam 115 | 116 | instance connectableDestinationNode :: Connectable DestinationNode where 117 | connect = nodeConnect 118 | disconnect = nodeDisconnect 119 | connectParam = unsafeConnectParam 120 | 121 | instance connectableAudioNode :: Connectable AudioNode where 122 | connect s (Gain n) = nodeConnect s n 123 | connect s (Oscillator n) = nodeConnect s n 124 | connect s (BiquadFilter n) = nodeConnect s n 125 | connect s (Delay n) = nodeConnect s n 126 | connect s (Analyser n) = nodeConnect s n 127 | connect s (StereoPanner n) = nodeConnect s n 128 | connect s (DynamicsCompressor n) = nodeConnect s n 129 | connect s (Convolver n) = nodeConnect s n 130 | connect s (Destination n) = nodeConnect s n 131 | connect s _ = pure unit -- you can't connect to source nodes 132 | 133 | disconnect s (Gain n) = nodeDisconnect s n 134 | disconnect s (Oscillator n) = nodeConnect s n 135 | disconnect s (BiquadFilter n) = nodeDisconnect s n 136 | disconnect s (Delay n) = nodeDisconnect s n 137 | disconnect s (Analyser n) = nodeDisconnect s n 138 | disconnect s (StereoPanner n) = nodeDisconnect s n 139 | disconnect s (DynamicsCompressor n) = nodeDisconnect s n 140 | disconnect s (Convolver n) = nodeDisconnect s n 141 | disconnect s (Destination n) = nodeConnect s n 142 | disconnect s _ = pure unit -- you can't disconnect from source nodes 143 | 144 | connectParam s (Gain n) p = unsafeConnectParam s n p 145 | -- not sure yet if you can connect to params on source nodes like the next two 146 | connectParam s (AudioBufferSource n) p = unsafeConnectParam s n p 147 | connectParam s (Oscillator n) p = unsafeConnectParam s n p 148 | connectParam s (BiquadFilter n) p = unsafeConnectParam s n p 149 | connectParam s (Delay n) p = unsafeConnectParam s n p 150 | connectParam s (Analyser n) p = unsafeConnectParam s n p 151 | connectParam s (StereoPanner n) p = unsafeConnectParam s n p 152 | connectParam s (DynamicsCompressor n) p = unsafeConnectParam s n p 153 | connectParam s (Convolver n) p = unsafeConnectParam s n p 154 | connectParam s (Destination n) p = pure unit 155 | 156 | -- foreign import connect 157 | foreign import nodeConnect :: ∀ m n eff. RawAudioNode m => RawAudioNode n => m 158 | -> n 159 | -> (Eff (audio :: AUDIO | eff) Unit) 160 | 161 | -- There are multiple disconnect options - this one seems the most useful 162 | -- foreign import disconnectRawA 163 | foreign import nodeDisconnect :: ∀ m n eff. RawAudioNode m => RawAudioNode n => m 164 | -> n 165 | -> (Eff (audio :: AUDIO | eff) Unit) 166 | 167 | -- | Connect a source Node to a parameter on a destination Node. 168 | -- | the String parameter names an audio parameter on the target node, n 169 | -- | this function connects this audio parameter to node m 170 | -- | 171 | -- | it seems we need to do this as an atomic operation 172 | -- | If we have separate monadic functions to get the audio parameter 173 | -- | and to connect a node to it, then it fails to work. 174 | -- | The Web-Audio JavaScript requires the original parameter, not a copy 175 | -- | 176 | -- | This is very unsafe. The parameter must exist on the target. 177 | foreign import unsafeConnectParam :: ∀ m n eff. RawAudioNode m => RawAudioNode n => m 178 | -> n 179 | -> String 180 | -> (Eff (audio :: AUDIO | eff) Unit) 181 | 182 | data AudioNode = 183 | Gain GainNode 184 | | AudioBufferSource AudioBufferSourceNode 185 | | Oscillator OscillatorNode 186 | | BiquadFilter BiquadFilterNode 187 | | Delay DelayNode 188 | | Analyser AnalyserNode 189 | | StereoPanner StereoPannerNode 190 | | DynamicsCompressor DynamicsCompressorNode 191 | | Convolver ConvolverNode 192 | | Destination DestinationNode 193 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/Utils.js: -------------------------------------------------------------------------------- 1 | /* jshint maxparams: false */ 2 | /* global exports, XMLHttpRequest */ 3 | "use strict"; 4 | 5 | // module Audio.WebAudio.Utils 6 | 7 | 8 | exports.unsafeSetProp = function(prop) { 9 | return function(obj) { 10 | return function(value) { 11 | return function() { 12 | obj[prop] = value; 13 | }; 14 | }; 15 | }; 16 | }; 17 | 18 | 19 | exports.unsafeGetProp = function(prop) { 20 | return function(obj) { 21 | return function() { 22 | return obj[prop]; 23 | }; 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/Audio/WebAudio/Utils.purs: -------------------------------------------------------------------------------- 1 | module Audio.WebAudio.Utils 2 | ( createUint8Buffer, createFloat32Buffer, unsafeGetProp, unsafeSetProp) where 3 | 4 | import Prelude 5 | import Control.Monad.Eff (Eff) 6 | import Data.ArrayBuffer.Types (ByteLength, Uint8Array, Float32Array) 7 | import Data.ArrayBuffer.ArrayBuffer (ARRAY_BUFFER, create) as ArrayBuffer 8 | import Data.ArrayBuffer.DataView (whole) 9 | import Data.ArrayBuffer.Typed (asUint8Array, asFloat32Array) 10 | 11 | foreign import unsafeSetProp :: forall obj val eff. String -> obj -> val -> (Eff eff Unit) 12 | foreign import unsafeGetProp :: forall obj val eff. String -> obj -> (Eff eff val) 13 | 14 | 15 | 16 | -- | create an unsigned 8-bit integer buffer for use with an analyser node 17 | createUint8Buffer :: ∀ e. ByteLength -> Eff (arrayBuffer :: ArrayBuffer.ARRAY_BUFFER | e) Uint8Array 18 | createUint8Buffer len = 19 | map (whole >>> asUint8Array) $ ArrayBuffer.create len 20 | 21 | -- | create a Float 32 buffer for use with an analyser node 22 | createFloat32Buffer :: ∀ e. ByteLength -> Eff (arrayBuffer :: ArrayBuffer.ARRAY_BUFFER | e) Float32Array 23 | createFloat32Buffer len = 24 | map (whole >>> asFloat32Array) $ ArrayBuffer.create len 25 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | import Control.Monad.Eff (Eff) 5 | import Control.Monad.Eff.Console (CONSOLE, log) 6 | 7 | main :: forall e. Eff (console :: CONSOLE | e) Unit 8 | main = do 9 | log "You should add some tests." 10 | -------------------------------------------------------------------------------- /test/props/TestProps.purs: -------------------------------------------------------------------------------- 1 | module TestProps where 2 | 3 | -- | just test some basic getters and setters 4 | 5 | import Prelude 6 | 7 | import Audio.WebAudio.AudioBufferSourceNode (loop, setLoop, loopStart, setLoopStart, loopEnd, setLoopEnd) 8 | import Audio.WebAudio.BaseAudioContext (createBufferSource, createBiquadFilter, createDelay, createGain, 9 | createOscillator, createAnalyser, createStereoPanner, createDynamicsCompressor, 10 | createConvolver, newAudioContext, destination) 11 | import Audio.WebAudio.Types (AUDIO, AudioContext, AudioNode(..), connect, connectParam) 12 | import Audio.WebAudio.BiquadFilterNode (BiquadFilterType(..), filterFrequency, filterType, setFilterType, quality) 13 | import Audio.WebAudio.GainNode (gain, setGain) 14 | import Audio.WebAudio.DelayNode (delayTime) 15 | import Audio.WebAudio.StereoPannerNode (pan) 16 | import Audio.WebAudio.DynamicsCompressorNode (threshold, knee, ratio, attack, release, reduction) 17 | import Audio.WebAudio.ConvolverNode (normalize, isNormalized) 18 | import Audio.WebAudio.AnalyserNode (fftSize, getByteTimeDomainData, minDecibels, setMinDecibels, 19 | smoothingTimeConstant, setSmoothingTimeConstant) 20 | import Audio.WebAudio.AudioParam (setValue, getValue) 21 | import Audio.WebAudio.Utils (createUint8Buffer) 22 | import Control.Monad.Eff (Eff) 23 | import Data.ArrayBuffer.ArrayBuffer (ARRAY_BUFFER) 24 | import Control.Monad.Eff.Console (CONSOLE, log) 25 | import Test.Assert (ASSERT, assert') 26 | 27 | main :: ∀ eff.(Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT, arrayBuffer :: ARRAY_BUFFER | eff) Unit) 28 | main = do 29 | ctx <- newAudioContext 30 | _ <- sourceBufferTests ctx 31 | _ <- biquadFilterTests ctx 32 | _ <- delayNodeTests ctx 33 | _ <- gainNodeTests ctx 34 | _ <- analyserNodeTests ctx 35 | _ <- stereoPannerNodeTests ctx 36 | _ <- dynamicsCompressorNodeTests ctx 37 | _ <- convolverNodeTests ctx 38 | connectionTests ctx 39 | 40 | sourceBufferTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 41 | sourceBufferTests ctx = do 42 | src <- createBufferSource ctx 43 | _ <- setLoop true src 44 | isLoop <- loop src 45 | _ <- assert' "setLoop failed" (isLoop == true) 46 | _ <- setLoopStart 2.0 src 47 | loops <- loopStart src 48 | _ <- assert' "setLoopStart failed" (loops == 2.0) 49 | _ <- setLoopEnd 4.0 src 50 | loope <- loopEnd src 51 | _ <- assert' "setLoopEnd failed" (loope == 4.0) 52 | log "source buffer tests passed" 53 | 54 | 55 | biquadFilterTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 56 | biquadFilterTests ctx = do 57 | filter <- createBiquadFilter ctx 58 | fType <- filterType filter 59 | _ <- assert' "incorrect biquad filter type" (fType == Lowpass) 60 | _ <- setFilterType Highpass filter 61 | fType' <- filterType filter 62 | _ <- assert' "set filter type failure" (fType' == Highpass) 63 | freqParam <- filterFrequency filter 64 | frequency <- getValue freqParam 65 | _ <- assert' "incorrect filter frequency" (frequency == 350.0) 66 | _ <- setValue 400.0 freqParam 67 | freqParam' <- filterFrequency filter 68 | frequency' <- getValue freqParam' 69 | _ <- assert' "incorrect set filter frequency" (frequency' == 400.0) 70 | qParam <- quality filter 71 | q <- getValue qParam 72 | _ <- assert' "incorrect filter quality" (q == 1.0) 73 | _ <- setValue 30.0 qParam 74 | qParam' <- quality filter 75 | q' <- getValue qParam' 76 | _ <- assert' "incorrect set filter quality" (q' == 30.0) 77 | log "biquad filter tests passed" 78 | 79 | delayNodeTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 80 | delayNodeTests ctx = do 81 | delayNode <- createDelay ctx 82 | delayParam <- delayTime delayNode 83 | -- by default we're restricted to values between 0 and 1 second 84 | _ <- setValue 0.75 delayParam 85 | delay <- getValue delayParam 86 | _ <- assert' ("incorrect delay time: " <> (show delay)) (delay == 0.75) 87 | log "delay node tests passed" 88 | 89 | gainNodeTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 90 | gainNodeTests ctx = do 91 | gainNode <- createGain ctx 92 | _ <- setGain 30.0 gainNode 93 | gainParam <- gain gainNode 94 | gain <- getValue gainParam 95 | _ <- assert' ("shorthand set gain failure: ") (gain == 30.0) 96 | log "gain node tests passed" 97 | 98 | analyserNodeTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT, arrayBuffer :: ARRAY_BUFFER | eff) Unit) 99 | analyserNodeTests ctx = do 100 | analyser <- createAnalyser ctx 101 | size <- fftSize analyser 102 | _ <- assert' ("default fft buffer size failure: ") (size == 2048) 103 | _ <- setMinDecibels (-140.0) analyser 104 | minDB <- minDecibels analyser 105 | _ <- assert' ("minDecibels failure: ") (minDB == (-140.0)) 106 | _ <- setSmoothingTimeConstant (0.8) analyser 107 | tc <- smoothingTimeConstant analyser 108 | _ <- assert' ("smoothingTimeConstant failure: ") (tc == (0.8)) 109 | -- these next 2 lines just test that nothing crashes 110 | buffer <- createUint8Buffer size 111 | _ <- getByteTimeDomainData analyser buffer 112 | log "analyser node tests passed" 113 | 114 | stereoPannerNodeTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 115 | stereoPannerNodeTests ctx = do 116 | stereoPannerNode <- createStereoPanner ctx 117 | panParam <- pan stereoPannerNode 118 | -- by default we're restricted to values between -1 and +1 119 | _ <- setValue (-0.75) panParam 120 | panVal <- getValue panParam 121 | _ <- assert' ("incorrect pan value: " <> (show panVal)) (panVal == -0.75) 122 | log "stereo panner node tests passed" 123 | 124 | dynamicsCompressorNodeTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 125 | dynamicsCompressorNodeTests ctx = do 126 | compressorNode <- createDynamicsCompressor ctx 127 | thresholdParam <- threshold compressorNode 128 | -- by default we're restricted to values between -100 and 0 129 | _ <- setValue (-50.0) thresholdParam 130 | thresholdVal <- getValue thresholdParam 131 | _ <- assert' ("incorrect threshold value: " <> (show thresholdVal)) (thresholdVal == -50.0) 132 | kneeParam <- knee compressorNode 133 | _ <- setValue (30.0) kneeParam 134 | kneeVal <- getValue kneeParam 135 | _ <- assert' ("incorrect knee value: " <> (show kneeVal)) (kneeVal == 30.0) 136 | ratioParam <- ratio compressorNode 137 | -- by default we're restricted to values between 1 and 20 138 | _ <- setValue (20.0) ratioParam 139 | ratioVal <- getValue ratioParam 140 | _ <- assert' ("incorrect ratio value: " <> (show ratioVal)) (ratioVal == 20.0) 141 | attackParam <- attack compressorNode 142 | -- by default we're restricted to values between 0 and 1 143 | _ <- setValue (0.5) attackParam 144 | attackVal <- getValue attackParam 145 | _ <- assert' ("incorrect attack value: " <> (show attackVal)) (attackVal == 0.5) 146 | releaseParam <- release compressorNode 147 | -- by default we're restricted to values between 0 and 1 148 | _ <- setValue (0.5) releaseParam 149 | releaseVal <- getValue releaseParam 150 | _ <- assert' ("incorrect release value: " <> (show releaseVal)) (releaseVal == 0.5) 151 | -- 0.0 is the default 152 | reduction' <- reduction compressorNode 153 | _ <- assert' ("get reduction failure" <> (show reduction')) (reduction' == 0.0) 154 | log "dynamics compressor node tests passed" 155 | 156 | convolverNodeTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 157 | convolverNodeTests ctx = do 158 | convolverNode <- createConvolver ctx 159 | _ <- normalize true convolverNode 160 | normalized <- isNormalized convolverNode 161 | _ <- assert' "normalize failed" (normalized == true) 162 | log "convolver node tests passed" 163 | 164 | connectionTests :: ∀ eff. AudioContext -> (Eff (audio :: AUDIO, console :: CONSOLE, assert :: ASSERT | eff) Unit) 165 | connectionTests ctx = do 166 | modulator <- createOscillator ctx 167 | modGainNode <- createGain ctx 168 | _ <- connectParam modGainNode modulator "frequency" 169 | -- simple connect 170 | osc1 <- createOscillator ctx 171 | gain1 <- createGain ctx 172 | dest <- destination ctx 173 | _ <- connect gain1 dest 174 | _ <- connect osc1 gain1 175 | -- audio node connect 176 | osc2 <- createOscillator ctx 177 | gain2 <- createGain ctx 178 | _ <- connect gain2 (Destination dest) 179 | _ <- connect osc2 (Gain gain2) 180 | log "connection tests passed" 181 | -------------------------------------------------------------------------------- /test/props/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Test getting/setting properties

6 | 7 | 8 | 9 | --------------------------------------------------------------------------------