2014-11-28 20:49:41 -05:00
|
|
|
CocoClass = require 'core/CocoClass'
|
2014-01-03 13:32:13 -05:00
|
|
|
cache = {}
|
2014-11-28 20:49:41 -05:00
|
|
|
{me} = require 'core/auth'
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
# Top 20 obscene words (plus 'fiddlesticks') will trigger swearing Simlish with *beeps*.
|
|
|
|
# Didn't like leaving so much profanity lying around in the source, so rot13'd.
|
2014-06-30 22:16:26 -04:00
|
|
|
rot13 = (s) -> s.replace /[A-z]/g, (c) -> String.fromCharCode c.charCodeAt(0) + (if c.toUpperCase() <= 'M' then 13 else -13)
|
|
|
|
swears = (rot13 s for s in ['nefrubyr', 'nffubyr', 'onfgneq', 'ovgpu', 'oybbql', 'obyybpxf', 'ohttre', 'pbpx', 'penc', 'phag', 'qnza', 'qnea', 'qvpx', 'qbhpur', 'snt', 'shpx', 'cvff', 'chffl', 'fuvg', 'fyhg', 'svqqyrfgvpxf'])
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-11-25 12:16:55 -05:00
|
|
|
# IE(11, 10, 9) throws an exception if createjs.FlashPlugin is undefined
|
|
|
|
# Chrome and Firefox don't seem to care that it's undefined
|
|
|
|
if createjs.FlashPlugin?
|
|
|
|
soundPlugins = [createjs.WebAudioPlugin, createjs.FlashPlugin, createjs.HTMLAudioPlugin]
|
|
|
|
else
|
|
|
|
soundPlugins = [createjs.WebAudioPlugin, createjs.HTMLAudioPlugin]
|
|
|
|
createjs.Sound.registerPlugins(soundPlugins)
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
class Manifest
|
|
|
|
constructor: -> @storage = {}
|
|
|
|
|
|
|
|
add: (filename, group='misc') ->
|
|
|
|
name = name or filename
|
|
|
|
@storage[group] = [] unless @storage[group]?
|
|
|
|
return if filename in @storage[group]
|
|
|
|
@storage[group].push(filename)
|
|
|
|
|
|
|
|
addPrimarySound: (filename) -> @add(filename, 'primarySounds')
|
|
|
|
addSecondarySound: (filename) -> @add(filename, 'secondarySounds')
|
|
|
|
getData: -> return @storage
|
|
|
|
|
|
|
|
class Media
|
|
|
|
constructor: (name) -> @name = name if name
|
|
|
|
|
|
|
|
loaded: false
|
|
|
|
data: null
|
|
|
|
progress: 0.0
|
|
|
|
error: null
|
|
|
|
name: ''
|
|
|
|
|
|
|
|
class AudioPlayer extends CocoClass
|
|
|
|
subscriptions:
|
2014-08-27 15:24:03 -04:00
|
|
|
'audio-player:play-sound': (e) -> @playInterfaceSound e.trigger, e.volume
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
constructor: () ->
|
|
|
|
super()
|
|
|
|
@ext = if createjs.Sound.getCapability('mp3') then '.mp3' else '.ogg'
|
2014-03-05 22:39:14 -05:00
|
|
|
@camera = null
|
2014-01-03 13:32:13 -05:00
|
|
|
@listenToSound()
|
|
|
|
@createNewManifest()
|
|
|
|
@soundsToPlayWhenLoaded = {}
|
|
|
|
|
|
|
|
createNewManifest: ->
|
|
|
|
@manifest = new Manifest()
|
|
|
|
|
|
|
|
listenToSound: ->
|
|
|
|
# I would like to go through PreloadJS to organize loading by queue, but
|
|
|
|
# when I try to set it up, I get an error with the Sound plugin.
|
|
|
|
# So for now, we'll just load through SoundJS instead.
|
|
|
|
createjs.Sound.on 'fileload', @onSoundLoaded
|
|
|
|
|
2014-03-05 22:39:14 -05:00
|
|
|
applyPanning: (options, pos) ->
|
|
|
|
sup = @camera.worldToSurface pos
|
|
|
|
svp = @camera.surfaceViewport
|
2014-03-05 23:12:42 -05:00
|
|
|
pan = Math.max -1, Math.min 1, ((sup.x - svp.x) - svp.width / 2) / svp.width
|
|
|
|
dst = @camera.distanceRatioTo pos
|
|
|
|
vol = Math.min 1, options.volume / Math.pow (dst + 0.2), 2
|
2014-03-05 22:39:14 -05:00
|
|
|
volume: options.volume, delay: options.delay, pan: pan
|
|
|
|
|
2014-01-03 13:32:13 -05:00
|
|
|
# PUBLIC LOADING METHODS
|
|
|
|
|
|
|
|
soundForDialogue: (message, soundTriggers) ->
|
|
|
|
if _.isArray message then message = message.join ' '
|
|
|
|
return message unless _.isString message
|
|
|
|
return null unless say = soundTriggers?.say
|
|
|
|
message = _.string.slugify message
|
|
|
|
return sound if sound = say[message]
|
2014-11-01 12:35:19 -04:00
|
|
|
if _.string.startsWith message, 'attack'
|
|
|
|
return sound if sound = say.attack
|
2014-12-06 21:45:15 -05:00
|
|
|
if message.indexOf("i-dont-see-anyone") isnt -1
|
|
|
|
return sound if sound = say['i-dont-see-anyone']
|
|
|
|
if message.indexOf("i-see-you") isnt -1
|
|
|
|
return sound if sound = say['i-see-you']
|
|
|
|
if message.indexOf("repeating-loop") isnt -1
|
|
|
|
return sound if sound = say['repeating-loop']
|
|
|
|
if /move(up|down|left|right)/.test message
|
|
|
|
return sound if sound = say["move-#{message[4...]}"]
|
2014-01-03 13:32:13 -05:00
|
|
|
defaults = say.defaultSimlish
|
|
|
|
if say.swearingSimlish?.length and _.find(swears, (s) -> message.search(s) isnt -1)
|
|
|
|
defaults = say.swearingSimlish
|
|
|
|
return null unless defaults?.length
|
|
|
|
return defaults[message.length % defaults.length]
|
|
|
|
|
|
|
|
preloadInterfaceSounds: (names) ->
|
|
|
|
for name in names
|
|
|
|
filename = "/file/interface/#{name}#{@ext}"
|
|
|
|
@preloadSound filename, name
|
|
|
|
|
2014-02-11 12:28:26 -05:00
|
|
|
playInterfaceSound: (name, volume=1) ->
|
2014-01-03 13:32:13 -05:00
|
|
|
filename = "/file/interface/#{name}#{@ext}"
|
2014-09-10 22:55:00 -04:00
|
|
|
if @hasLoadedSound filename
|
2014-02-11 12:28:26 -05:00
|
|
|
@playSound name, volume
|
2014-01-03 13:32:13 -05:00
|
|
|
else
|
|
|
|
@preloadInterfaceSounds [name] unless filename of cache
|
2014-02-11 12:28:26 -05:00
|
|
|
@soundsToPlayWhenLoaded[name] = volume
|
|
|
|
|
2014-03-05 22:39:14 -05:00
|
|
|
playSound: (name, volume=1, delay=0, pos=null) ->
|
2014-08-28 21:55:08 -04:00
|
|
|
return console.error 'Trying to play empty sound?' unless name
|
2014-09-10 22:55:00 -04:00
|
|
|
audioOptions = {volume: volume, delay: delay}
|
2014-08-26 18:22:13 -04:00
|
|
|
filename = if _.string.startsWith(name, '/file/') then name else '/file/' + name
|
2014-09-10 22:55:00 -04:00
|
|
|
unless @hasLoadedSound filename
|
2014-08-26 18:22:13 -04:00
|
|
|
@soundsToPlayWhenLoaded[name] = audioOptions.volume
|
2014-10-24 16:03:10 -04:00
|
|
|
audioOptions = @applyPanning audioOptions, pos if @camera and not @camera.destroyed and pos
|
2014-03-05 22:39:14 -05:00
|
|
|
instance = createjs.Sound.play name, audioOptions
|
2014-03-01 19:57:29 -05:00
|
|
|
instance
|
2014-01-03 13:32:13 -05:00
|
|
|
|
2014-09-10 22:55:00 -04:00
|
|
|
hasLoadedSound: (filename, name) ->
|
|
|
|
return false unless filename of cache
|
|
|
|
return false unless createjs.Sound.loadComplete filename
|
|
|
|
true
|
|
|
|
|
|
|
|
# TODO: load Interface sounds somehow, somewhere, somewhen
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
preloadSoundReference: (sound) ->
|
2014-09-23 00:50:48 -04:00
|
|
|
return unless name = @nameForSoundReference sound
|
2014-01-03 13:32:13 -05:00
|
|
|
filename = '/file/' + name
|
|
|
|
@preloadSound filename, name
|
|
|
|
filename
|
|
|
|
|
|
|
|
nameForSoundReference: (sound) ->
|
|
|
|
sound[@ext.slice(1)] # mp3 or ogg
|
|
|
|
|
|
|
|
preloadSound: (filename, name) ->
|
|
|
|
return unless filename
|
|
|
|
return if filename of cache
|
|
|
|
name ?= filename
|
|
|
|
# SoundJS flips out if you try to register the same file twice
|
2014-11-18 18:26:26 -05:00
|
|
|
result = createjs.Sound.registerSound(filename, name, 1) # 1: 1 channel
|
2014-01-03 13:32:13 -05:00
|
|
|
cache[filename] = new Media(name)
|
|
|
|
|
|
|
|
# PROGRESS CALLBACKS
|
|
|
|
|
|
|
|
onSoundLoaded: (e) =>
|
|
|
|
media = cache[e.src]
|
|
|
|
return if not media
|
|
|
|
media.loaded = true
|
|
|
|
media.progress = 1.0
|
2014-02-11 12:28:26 -05:00
|
|
|
if volume = @soundsToPlayWhenLoaded[media.name]
|
|
|
|
@playSound media.name, volume
|
2014-01-03 13:32:13 -05:00
|
|
|
@soundsToPlayWhenLoaded[media.name] = false
|
|
|
|
@notifyProgressChanged()
|
|
|
|
|
|
|
|
onSoundLoadError: (e) =>
|
|
|
|
console.error 'Could not load sound', e
|
|
|
|
|
|
|
|
notifyProgressChanged: ->
|
2014-06-30 22:16:26 -04:00
|
|
|
Backbone.Mediator.publish('audio-player:loaded', {sender: @})
|
2014-01-03 13:32:13 -05:00
|
|
|
|
|
|
|
getStatus: (src) ->
|
|
|
|
return cache[src] or null
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = new AudioPlayer()
|