├── .gitignore ├── Audio.elm ├── AudioTest.elm ├── LICENSE.txt ├── Native └── Audio.js ├── README.md ├── elm-package.json └── snd ├── click.wav ├── click2.wav ├── theme.mp3 └── woosh.wav /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | elm.js 3 | -------------------------------------------------------------------------------- /Audio.elm: -------------------------------------------------------------------------------- 1 | module Audio 2 | ( Action(..) 3 | , Properties 4 | , Event(..) 5 | , audio 6 | , defaultTriggers 7 | ) where 8 | 9 | {-| The Audio provides an interface for playing audio 10 | 11 | # Definition 12 | @docs Action, Properties, Event 13 | 14 | # Common Helpers 15 | @docs defaultTriggers, audio 16 | 17 | -} 18 | 19 | import Native.Audio 20 | import Signal 21 | import Keyboard 22 | import Set 23 | import Time exposing (Time) 24 | 25 | {-| An Action controls how audio is heard. -} 26 | type Action = Play | Pause | Seek Time | NoChange 27 | 28 | {-| A Properties record contains information related to audio -} 29 | type alias Properties = { duration : Time, currentTime : Time, ended : Bool } 30 | 31 | {-| A Triggers record describes on what events you would like to receive 32 | a Properties record -} 33 | type alias Triggers = { timeupdate : Bool, ended : Bool } 34 | 35 | {-| The defaultTriggers is a record for ease of use. All Triggers are 36 | set to False. 37 | -} 38 | defaultTriggers : Triggers 39 | defaultTriggers = { timeupdate = False, ended = False } 40 | 41 | {-| An Event is used to describe why the Signal returned by the 42 | audio function was fired. 43 | -} 44 | type Event = TimeUpdate 45 | | Ended 46 | | Created 47 | 48 | {-| A Builder Record is used to desribe how audio should be treated. 49 | src - The Path to an audio file 50 | triggers - A Triggers Record describing on which events we want to receive Properties 51 | propertiesHandler - A Function that is called each time Properties are calculated. 52 | actions - A Signal of incomming actions that manipulate the audio 53 | -} 54 | type alias Builder = { src : String, 55 | triggers : Triggers, 56 | propertiesHandler : (Properties -> Maybe Action), 57 | actions : Signal Action } 58 | 59 | {-| Given a Builder, creates an Signal that fires on the specified events 60 | and produces the properties during those events. 61 | -} 62 | audio : Builder -> Signal (Event, Properties) 63 | audio audioBuilder = 64 | let handleEvent = 65 | (\sound action -> 66 | case action of 67 | Play -> Native.Audio.play sound 68 | Pause -> Native.Audio.pause sound 69 | Seek t -> Native.Audio.seek sound t 70 | NoChange -> ()) 71 | in Native.Audio.audio 72 | handleEvent 73 | audioBuilder.src 74 | audioBuilder.triggers 75 | audioBuilder.propertiesHandler 76 | audioBuilder.actions 77 | -------------------------------------------------------------------------------- /AudioTest.elm: -------------------------------------------------------------------------------- 1 | import Audio 2 | import Audio exposing (defaultTriggers) 3 | import Signal exposing (..) 4 | import Keyboard 5 | import Char 6 | import Text 7 | import Graphics.Element exposing (..) 8 | import List 9 | 10 | -- We are either Playing or Not Playing 11 | type alias State = { playing : Bool } 12 | 13 | -- We start by not Playing 14 | initialState : State 15 | initialState = { playing = False } 16 | 17 | -- When the p key is pressed, we toggle the playing state 18 | update : Char.KeyCode -> State -> State 19 | update key state = 20 | if key == Char.toCode 'p' 21 | then {state | playing = not state.playing} 22 | else state 23 | 24 | -- Be Stateful! 25 | stateful : Signal State 26 | stateful = foldp update initialState Keyboard.presses 27 | 28 | -- If we've reached 37.6 seconds into the piece, jump to 0.05. 29 | propertiesHandler : Audio.Properties -> Maybe Audio.Action 30 | propertiesHandler properties = 31 | if properties.currentTime > 37.6 then Just (Audio.Seek 0.05) else Nothing 32 | 33 | -- If the State says we are playing, Play else Pause 34 | handleAudio : State -> Audio.Action 35 | handleAudio state = 36 | if state.playing then Audio.Play 37 | else Audio.Pause 38 | 39 | -- Audio Player with Tetris Theme that triggers when the time changes 40 | -- The property Handler will loop at the correct time. 41 | builder : Signal (Audio.Event, Audio.Properties) 42 | builder = Audio.audio { src = "snd/theme.mp3", 43 | triggers = {defaultTriggers | timeupdate = True}, 44 | propertiesHandler = propertiesHandler, 45 | actions = map handleAudio stateful } 46 | 47 | -- A Simple Display 48 | display : (State, (Audio.Event, Audio.Properties)) -> Element 49 | display (state, (event, properties)) = 50 | let playing = if state.playing then "Playing" else "Paused" 51 | progress = "Current Time: " ++ toString (properties.currentTime) 52 | duration = "Duration: " ++ toString (properties.duration) 53 | in flow down <| List.map (leftAligned << Text.fromString) 54 | [ "Tap 'P' to toggle between playing and paused." 55 | , playing 56 | , progress 57 | , duration 58 | ] 59 | 60 | main = let output = map2 (,) stateful builder in map display output 61 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Joseph Collard, Dr. Gergő Érdi 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of elm-audio nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Native/Audio.js: -------------------------------------------------------------------------------- 1 | 2 | Elm.Native.Audio = {}; 3 | Elm.Native.Audio.make = function(elm) { 4 | elm.Native = elm.Native || {}; 5 | elm.Native.Audio = elm.Native.Audio || {}; 6 | if (elm.Native.Audio.values) return elm.Native.Audio.values; 7 | 8 | // Imports 9 | var Signal = Elm.Native.Signal.make(elm); 10 | var Maybe = Elm.Maybe.make(elm); 11 | 12 | var TimeUpdate = {ctor : "TimeUpdate"}; 13 | var Ended = {ctor : "Ended"}; 14 | var Created = {ctor : "Created"}; 15 | 16 | // Helper Functions... Do these exist already? 17 | function Tuple2(fst, snd){ 18 | return {ctor: "_Tuple2", _0 : fst, _1 : snd}; 19 | } 20 | 21 | function Properties(duration, currentTime, ended){ 22 | return { _ : {}, duration : duration, currentTime : currentTime, ended : ended}; 23 | } 24 | 25 | // Creates a Signal (Event, Properties) 26 | function audio(handler, path, alerts, propHandler, actions) { 27 | 28 | var sound = new Audio(path); 29 | var clock = new Object(); 30 | var event = Signal.constant(Tuple2(Created, Properties(0,0,0))); 31 | 32 | var handle = handler({ "sound": sound, "clock": clock }); 33 | 34 | function fireProp(eventCons){ 35 | var props = Properties(sound.duration, sound.currentTime, sound.ended); 36 | elm.notify(event.id, Tuple2(eventCons, props)); 37 | var action = propHandler(props); 38 | if(action.ctor == "Just") 39 | handle(action._0) 40 | } 41 | 42 | function addAudioListener(eventString, eventCons){ 43 | sound.addEventListener(eventString, function () { fireProp(eventCons); }); 44 | } 45 | 46 | if(alerts.timeupdate) 47 | { 48 | var timer; 49 | clock.start = function() { timer = setInterval(function(){ fireProp(TimeUpdate); }, 10); }; 50 | clock.stop = function() { clearInterval(timer); }; 51 | } else { 52 | clock.start = function() {}; 53 | clock.stop = function() {}; 54 | } 55 | 56 | if(alerts.ended) 57 | addAudioListener('ended', Ended); 58 | 59 | Signal.output('audio-handler',handle,actions); 60 | return event; 61 | } 62 | 63 | function play(o){ 64 | o.sound.play(); 65 | o.clock.start(); 66 | } 67 | 68 | function pause(o){ 69 | o.clock.stop(); 70 | o.sound.pause() 71 | } 72 | 73 | function seek(o, time){ 74 | o.sound.currentTime = time; 75 | } 76 | 77 | return elm.Native.Audio.values = { 78 | audio : F5(audio), 79 | play : play, 80 | pause : pause, 81 | seek : F2(seek) 82 | }; 83 | 84 | }; 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Elm Audio 2 | 3 | This is an elm library for interacting with HTML5 Audio. 4 | 5 | You may also see it here: http://people.cs.umass.edu/~jcollard/examples/Audio/build/AudioTest.html 6 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "summary": "Audio playing capabilities for elm", 4 | "description": "This library provides Elm with functionalities of HTML5 Audio objects.", 5 | "license": "BSD3", 6 | "repository": "https://github.com/jcollard/elm-audio.git", 7 | "exposed-modules": ["Audio"], 8 | "native-modules": true, 9 | "source-directories": [ 10 | "." 11 | ], 12 | "dependencies": { 13 | "elm-lang/core": "3.0.0 <= v < 4.0.0" 14 | }, 15 | "elm-version": "0.15.0 <= v < 0.17.0" 16 | } 17 | -------------------------------------------------------------------------------- /snd/click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcollard/elm-audio/03d0aa13c7068f40de6a67c18c61ced1ab59c0d7/snd/click.wav -------------------------------------------------------------------------------- /snd/click2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcollard/elm-audio/03d0aa13c7068f40de6a67c18c61ced1ab59c0d7/snd/click2.wav -------------------------------------------------------------------------------- /snd/theme.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcollard/elm-audio/03d0aa13c7068f40de6a67c18c61ced1ab59c0d7/snd/theme.mp3 -------------------------------------------------------------------------------- /snd/woosh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcollard/elm-audio/03d0aa13c7068f40de6a67c18c61ced1ab59c0d7/snd/woosh.wav --------------------------------------------------------------------------------