diff --git a/.gitmodules b/.gitmodules index b51d0f9a35..9df06fc143 100644 --- a/.gitmodules +++ b/.gitmodules @@ -70,3 +70,13 @@ path = project/lib/hashlink url = https://github.com/HaxeFoundation/hashlink shallow = true +[submodule "project/lib/dr_libs"] + path = project/lib/dr_libs + url = https://github.com/mackron/dr_libs + shallow = true +[submodule "project/lib/opusfile"] + path = project/lib/opusfile + url = https://github.com/xiph/opusfile +[submodule "project/lib/opus"] + path = project/lib/opus + url = https://github.com/xiph/opus diff --git a/NOTICE.md b/NOTICE.md index 71a6f679da..1c3486634f 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -10,6 +10,9 @@ This product bundles cairo 1.15.2, which is available under an This product bundles libcurl 7.56.1, which is available under an "MIT/X derivate" license. For details, see [project/lib/curl/](project/lib). +This product bundles dr_libs (dr_flac 0.13.3, dr_mp3 0.7.4, dr_wav 0.14.5), which is available under an +"MIT No Attribution" license. For details, see [project/lib/dr_libs/](project/lib). + This product bundles efsw, which is available under an "MIT" license. For details, see [project/lib/efsw/](project/lib). @@ -29,16 +32,19 @@ This product bundles libogg 1.3.0, which is available under a "BSD" license. For details, see [project/lib/ogg/](project/lib). This product bundles LZMA SDK 4.65, which is available under -public domain. For details, see [project/lib/lzma/](project/lzma). +public domain. For details, see [project/lib/lzma/](project/lib). This product bundles mbedTLS 2.6.0, which is available under an "Apache 2.0" license. For details, see [project/lib/mbedtls/](project/lib). -This product bundles OpenAL-Soft 1.19.0, which is available under an +This product bundles OpenAL-Soft 1.25.1, which is available under an "LGPLv2" license. For details, see [project/lib/openal/](project/lib). -_OpenAL-Soft is only included in dynamically-linked builds, it is excluded -from Lime static builds in order to preserve Lime's permissive nature._ +This product bundles opus, which is available under a +"BSD" license. For details, see [project/lib/opus/](project/lib). + +This product bundles opusfile, which is available under a +"BSD" license. For details, see [project/lib/opusfile/](project/lib). This product bundles pixman 0.32.8, which is available under an "MIT" license. For details, see [project/lib/pixman/](project/lib). @@ -94,7 +100,7 @@ _The following are not embedded in Lime applications directly, but are used as dependencies for web-based builds. Their licensing does not affect products created with Lime._ -This product bundles howler.js 2.1.1, which is available under an +This product bundles howler.js 2.2.4 (Modified), which is available under an "MIT" license. For details, see [dependencies/howler.min.js](dependencies/howler.min.js). This product bundles FileSaver.js 1.3.3, which is available under an diff --git a/dependencies/howler.js b/dependencies/howler.js new file mode 100644 index 0000000000..a0ec4ed245 --- /dev/null +++ b/dependencies/howler.js @@ -0,0 +1,3303 @@ +/*! + * howler.js v2.2.4 + * howlerjs.com + * + * (c) 2013-2020, James Simpson of GoldFire Studios + * goldfirestudios.com + * + * MIT License + */ + +(function() { + + 'use strict'; + + /** Global Methods **/ + /***************************************************************************/ + + /** + * Create the global controller. All contained methods and properties apply + * to all sounds that are currently playing or will be in the future. + */ + var HowlerGlobal = function() { + this.init(); + }; + HowlerGlobal.prototype = { + /** + * Initialize the global Howler object. + * @return {Howler} + */ + init: function() { + var self = this || Howler; + + // Create a global ID counter. + self._counter = 1000; + + // Pool of unlocked HTML5 Audio objects. + self._html5AudioPool = []; + self.html5PoolSize = 10; + + // Internal properties. + self._codecs = {}; + self._howls = []; + self._muted = false; + self._volume = 1; + self._canPlayEvent = 'canplaythrough'; + self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null; + + // Public properties. + self.masterGain = null; + self.noAudio = false; + self.usingWebAudio = true; + self.autoSuspend = true; + self.ctx = null; + + // Set to false to disable the auto audio unlocker. + self.autoUnlock = true; + + // Setup the various state values for global tracking. + self._setup(); + + return self; + }, + + /** + * Get/set the global volume for all sounds. + * @param {Float} vol Volume from 0.0 to 1.0. + * @return {Howler/Float} Returns self or current volume. + */ + volume: function(vol) { + var self = this || Howler; + vol = parseFloat(vol); + + // If we don't have an AudioContext created yet, run the setup. + if (!self.ctx) { + self._setupAudioContext(); + } + + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + self._volume = vol; + + // Don't update any of the nodes if we are muted. + if (self._muted) { + return self; + } + + // When using Web Audio, we just need to adjust the master gain. + if (self.usingWebAudio) { + self.masterGain.gain.setValueAtTime(vol, Howler.ctx.currentTime); + } + + // Loop through and change volume for all HTML5 audio nodes. + for (var i=0; i=0; i--) { + self._howls[i].unload(); + } + + // Create a new AudioContext to make sure it is fully reset. + if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== 'undefined') { + self.ctx.close(); + self.ctx = null; + self._setupAudioContext(); + } + + return self; + }, + + /** + * Check for codec support of specific extension. + * @param {String} ext Audio file extention. + * @return {Boolean} + */ + codecs: function(ext) { + return (this || Howler)._codecs[ext.replace(/^x-/, '')]; + }, + + /** + * Setup various state values for global tracking. + * @return {Howler} + */ + _setup: function() { + var self = this || Howler; + + // Keeps track of the suspend/resume state of the AudioContext. + self.state = self.ctx ? self.ctx.state || 'suspended' : 'suspended'; + + // Automatically begin the 30-second suspend process + self._autoSuspend(); + + // Check if audio is available. + if (!self.usingWebAudio) { + // No audio is available on this system if noAudio is set to true. + if (typeof Audio !== 'undefined') { + try { + var test = new Audio(); + + // Check if the canplaythrough event is available. + if (typeof test.oncanplaythrough === 'undefined') { + self._canPlayEvent = 'canplay'; + } + } catch(e) { + self.noAudio = true; + } + } else { + self.noAudio = true; + } + } + + // Test to make sure audio isn't disabled in Internet Explorer. + try { + var test = new Audio(); + if (test.muted) { + self.noAudio = true; + } + } catch (e) {} + + // Check for supported codecs. + if (!self.noAudio) { + self._setupCodecs(); + } + + return self; + }, + + /** + * Check for browser support for various codecs and cache the results. + * @return {Howler} + */ + _setupCodecs: function() { + var self = this || Howler; + var audioTest = null; + + // Must wrap in a try/catch because IE11 in server mode throws an error. + try { + audioTest = (typeof Audio !== 'undefined') ? new Audio() : null; + } catch (err) { + return self; + } + + if (!audioTest || typeof audioTest.canPlayType !== 'function') { + return self; + } + + var mpegTest = audioTest.canPlayType('audio/mpeg;').replace(/^no$/, ''); + + // Opera version <33 has mixed MP3 support, so we need to check for and block it. + var ua = self._navigator ? self._navigator.userAgent : ''; + var checkOpera = ua.match(/OPR\/(\d+)/g); + var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33); + var checkSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') === -1; + var safariVersion = ua.match(/Version\/(.*?) /); + var isOldSafari = (checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15); + + self._codecs = { + mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType('audio/mp3;').replace(/^no$/, ''))), + mpeg: !!mpegTest, + opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''), + ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), + oga: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), + wav: !!(audioTest.canPlayType('audio/wav; codecs="1"') || audioTest.canPlayType('audio/wav')).replace(/^no$/, ''), + aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''), + caf: !!audioTest.canPlayType('audio/x-caf;').replace(/^no$/, ''), + m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + m4b: !!(audioTest.canPlayType('audio/x-m4b;') || audioTest.canPlayType('audio/m4b;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + weba: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')), + webm: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')), + dolby: !!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ''), + flac: !!(audioTest.canPlayType('audio/x-flac;') || audioTest.canPlayType('audio/flac;')).replace(/^no$/, '') + }; + + return self; + }, + + /** + * Some browsers/devices will only allow audio to be played after a user interaction. + * Attempt to automatically unlock audio on the first user interaction. + * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ + * @return {Howler} + */ + _unlockAudio: function() { + var self = this || Howler; + + // Only run this if Web Audio is supported and it hasn't already been unlocked. + if (self._audioUnlocked || !self.ctx) { + return; + } + + self._audioUnlocked = false; + self.autoUnlock = false; + + // Some mobile devices/platforms have distortion issues when opening/closing tabs and/or web views. + // Bugs in the browser (especially Mobile Safari) can cause the sampleRate to change from 44100 to 48000. + // By calling Howler.unload(), we create a new AudioContext with the correct sampleRate. + if (!self._mobileUnloaded && self.ctx.sampleRate !== 44100) { + self._mobileUnloaded = true; + self.unload(); + } + + // Scratch buffer for enabling iOS to dispose of web audio buffers correctly, as per: + // http://stackoverflow.com/questions/24119684 + self._scratchBuffer = self.ctx.createBuffer(1, 1, 22050); + + // Call this method on touch start to create and play a buffer, + // then check if the audio actually played to determine if + // audio has now been unlocked on iOS, Android, etc. + var unlock = function(e) { + // Create a pool of unlocked HTML5 Audio objects that can + // be used for playing sounds without user interaction. HTML5 + // Audio objects must be individually unlocked, as opposed + // to the WebAudio API which only needs a single activation. + // This must occur before WebAudio setup or the source.onended + // event will not fire. + while (self._html5AudioPool.length < self.html5PoolSize) { + try { + var audioNode = new Audio(); + + // Mark this Audio object as unlocked to ensure it can get returned + // to the unlocked pool when released. + audioNode._unlocked = true; + + // Add the audio node to the pool. + self._releaseHtml5Audio(audioNode); + } catch (e) { + self.noAudio = true; + break; + } + } + + // Loop through any assigned audio nodes and unlock them. + for (var i=0; i= 55. + if (typeof self.ctx.resume === 'function') { + self.ctx.resume(); + } + + // Setup a timeout to check that we are unlocked on the next event loop. + source.onended = function() { + source.disconnect(0); + + // Update the unlocked state and prevent this check from happening again. + self._audioUnlocked = true; + + // Remove the touch start listener. + document.removeEventListener('touchstart', unlock, true); + document.removeEventListener('touchend', unlock, true); + document.removeEventListener('click', unlock, true); + document.removeEventListener('keydown', unlock, true); + + // Let all sounds know that audio has been unlocked. + for (var i=0; i 0 ? sound._seek : self._sprite[sprite][0] / 1000); + var duration = Math.max(0, ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek); + var timeout = (duration * 1000) / Math.abs(sound._rate); + var start = self._sprite[sprite][0] / 1000; + var stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000; + sound._sprite = sprite; + + // Mark the sound as ended instantly so that this async playback + // doesn't get grabbed by another call to play while this one waits to start. + sound._ended = false; + + // Update the parameters of the sound. + var setParams = function() { + sound._paused = false; + sound._seek = seek; + sound._start = start; + sound._stop = stop; + sound._loop = !!(sound._loop || self._sprite[sprite][2]); + }; + + // End the sound instantly if seek is at the end. + if (seek >= stop) { + self._ended(sound); + return; + } + + // Begin the actual playback. + var node = sound._node; + if (self._webAudio) { + // Fire this when the sound is ready to play to begin Web Audio playback. + var playWebAudio = function() { + self._playLock = false; + setParams(); + self._refreshBuffer(sound); + + // Setup the playback params. + var vol = (sound._muted || self._muted) ? 0 : sound._volume; + node.gain.setValueAtTime(vol, Howler.ctx.currentTime); + sound._playStart = Howler.ctx.currentTime; + + // Play the sound using the supported method. + if (typeof node.bufferSource.start === 'undefined') { + sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration); + } else { + sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration); + } + + // Start a new timer if none is present. + if (timeout !== Infinity) { + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } + + if (!internal) { + setTimeout(function() { + self._emit('play', sound._id); + self._loadQueue(); + }, 0); + } + }; + + if (Howler.state === 'running' && Howler.ctx.state !== 'interrupted') { + playWebAudio(); + } else { + self._playLock = true; + + // Wait for the audio context to resume before playing. + self.once('resume', playWebAudio); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } else { + // Fire this when the sound is ready to play to begin HTML5 Audio playback. + var playHtml5 = function() { + node.currentTime = seek; + node.muted = sound._muted || self._muted || Howler._muted || node.muted; + node.volume = sound._volume * Howler.volume(); + node.playbackRate = sound._rate; + + // Some browsers will throw an error if this is called without user interaction. + try { + var play = node.play(); + + // Support older browsers that don't support promises, and thus don't have this issue. + if (play && typeof Promise !== 'undefined' && (play instanceof Promise || typeof play.then === 'function')) { + // Implements a lock to prevent DOMException: The play() request was interrupted by a call to pause(). + self._playLock = true; + + // Set param values immediately. + setParams(); + + // Releases the lock and executes queued actions. + play + .then(function() { + self._playLock = false; + node._unlocked = true; + if (!internal) { + self._emit('play', sound._id); + } else { + self._loadQueue(); + } + }) + .catch(function() { + self._playLock = false; + self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' + + 'on mobile devices and Chrome where playback was not within a user interaction.'); + + // Reset the ended and paused values. + sound._ended = true; + sound._paused = true; + }); + } else if (!internal) { + self._playLock = false; + setParams(); + self._emit('play', sound._id); + } + + // Setting rate before playing won't work in IE, so we set it again here. + node.playbackRate = sound._rate; + + // If the node is still paused, then we can assume there was a playback issue. + if (node.paused) { + self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' + + 'on mobile devices and Chrome where playback was not within a user interaction.'); + return; + } + + // Setup the end timer on sprites or listen for the ended event. + if (sprite !== '__default' || sound._loop) { + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } else { + self._endTimers[sound._id] = function() { + // Fire ended on this audio node. + self._ended(sound); + + // Clear this listener. + node.removeEventListener('ended', self._endTimers[sound._id], false); + }; + node.addEventListener('ended', self._endTimers[sound._id], false); + } + } catch (err) { + self._emit('playerror', sound._id, err); + } + }; + + // If this is streaming audio, make sure the src is set and load again. + if (node.src === 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA') { + node.src = self._src; + node.load(); + } + + // Play immediately if ready, or wait for the 'canplaythrough'e vent. + var loadedNoReadyState = (window && window.ejecta) || (!node.readyState && Howler._navigator.isCocoonJS); + if (node.readyState >= 3 || loadedNoReadyState) { + playHtml5(); + } else { + self._playLock = true; + self._state = 'loading'; + + var listener = function() { + self._state = 'loaded'; + + // Begin playback. + playHtml5(); + + // Clear this listener. + node.removeEventListener(Howler._canPlayEvent, listener, false); + }; + node.addEventListener(Howler._canPlayEvent, listener, false); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } + + return sound._id; + }, + + /** + * Pause playback and save current position. + * @param {Number} id The sound ID (empty to pause all in group). + * @return {Howl} + */ + pause: function(id) { + var self = this; + + // If the sound hasn't loaded or a play() promise is pending, add it to the load queue to pause when capable. + if (self._state !== 'loaded' || self._playLock) { + self._queue.push({ + event: 'pause', + action: function() { + self.pause(id); + } + }); + + return self; + } + + // If no id is passed, get all ID's to be paused. + var ids = self._getSoundIds(id); + + for (var i=0; i Returns the group's volume value. + * volume(id) -> Returns the sound id's current volume. + * volume(vol) -> Sets the volume of all sounds in this Howl group. + * volume(vol, id) -> Sets the volume of passed sound id. + * @return {Howl/Number} Returns self or current volume. + */ + volume: function() { + var self = this; + var args = arguments; + var vol, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // Return the value of the groups' volume. + return self._volume; + } else if (args.length === 1 || args.length === 2 && typeof args[1] === 'undefined') { + // First check if this is an ID, and if not, assume it is a new volume. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + vol = parseFloat(args[0]); + } + } else if (args.length >= 2) { + vol = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the volume or return the current volume. + var sound; + if (typeof vol !== 'undefined') { + vol = Math.max(vol, 0); + + // If the sound hasn't loaded, add it to the load queue to change volume when capable. + if (self._state !== 'loaded'|| self._playLock) { + self._queue.push({ + event: 'volume', + action: function() { + self.volume.apply(self, args); + } + }); + + return self; + } + + // Set the group volume. + if (typeof id === 'undefined') { + self._volume = vol; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i 0) ? len / steps : len); + var lastTick = Date.now(); + + // Store the value being faded to. + sound._fadeTo = to; + + // Update the volume value on each interval tick. + sound._interval = setInterval(function() { + // Update the volume based on the time since the last tick. + var tick = (Date.now() - lastTick) / len; + lastTick = Date.now(); + vol += diff * tick; + + // Round to within 2 decimal points. + vol = Math.round(vol * 100) / 100; + + // Make sure the volume is in the right bounds. + if (diff < 0) { + vol = Math.max(to, vol); + } else { + vol = Math.min(to, vol); + } + + // Change the volume. + if (self._webAudio) { + sound._volume = vol; + } else { + self.volume(vol, sound._id, true); + } + + // Set the group's volume. + if (isGroup) { + self._volume = vol; + } + + // When the fade is complete, stop it and fire event. + if ((to < from && vol <= to) || (to > from && vol >= to)) { + clearInterval(sound._interval); + sound._interval = null; + sound._fadeTo = null; + self.volume(to, sound._id); + self._emit('fade', sound._id); + } + }, stepLen); + }, + + /** + * Internal method that stops the currently playing fade when + * a new fade starts, volume is changed or the sound is stopped. + * @param {Number} id The sound id. + * @return {Howl} + */ + _stopFade: function(id) { + var self = this; + var sound = self._soundById(id); + + if (sound && sound._interval) { + if (self._webAudio) { + sound._node.gain.cancelScheduledValues(Howler.ctx.currentTime); + } + + clearInterval(sound._interval); + sound._interval = null; + self.volume(sound._fadeTo, id); + sound._fadeTo = null; + self._emit('fade', id); + } + + return self; + }, + + /** + * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments. + * loop() -> Returns the group's loop value. + * loop(id) -> Returns the sound id's loop value. + * loop(loop) -> Sets the loop value for all sounds in this Howl group. + * loop(loop, id) -> Sets the loop value of passed sound id. + * @return {Howl/Boolean} Returns self or current loop value. + */ + loop: function() { + var self = this; + var args = arguments; + var loop, id, sound; + + // Determine the values for loop and id. + if (args.length === 0) { + // Return the grou's loop value. + return self._loop; + } else if (args.length === 1) { + if (typeof args[0] === 'boolean') { + loop = args[0]; + self._loop = loop; + } else { + // Return this sound's loop value. + sound = self._soundById(parseInt(args[0], 10)); + return sound ? sound._loop : false; + } + } else if (args.length === 2) { + loop = args[0]; + id = parseInt(args[1], 10); + } else if (args.length === 3) { + id = parseInt(args[2], 10); + sound = self._soundById(id); + if (sound) { + sound._loop = true; + sound._start = parseFloat(args[0]) || 0; + sound._stop = parseFloat(args[1]) || self._duration; + if (self._webAudio && sound._node && sound._node.bufferSource) { + sound._node.bufferSource.loop = true; + sound._node.bufferSource.loopStart = sound._start; + sound._node.bufferSource.loopEnd = sound._stop; + + // If playing, restart playback to ensure looping updates. + if (self.playing(id)) { + self.pause(id, true); + self.play(id, true); + } + } + } + return; + } + + // If no id is passed, get all ID's to be looped. + var ids = self._getSoundIds(id); + for (var i=0; i Returns the first sound node's current playback rate. + * rate(id) -> Returns the sound id's current playback rate. + * rate(rate) -> Sets the playback rate of all sounds in this Howl group. + * rate(rate, id) -> Sets the playback rate of passed sound id. + * @return {Howl/Number} Returns self or the current playback rate. + */ + rate: function() { + var self = this; + var args = arguments; + var rate, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current rate of the first node. + id = self._sounds[0]._id; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new rate value. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + rate = parseFloat(args[0]); + } + } else if (args.length === 2) { + rate = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the playback rate or return the current value. + var sound; + if (typeof rate === 'number') { + // If the sound hasn't loaded, add it to the load queue to change playback rate when capable. + if (self._state !== 'loaded' || self._playLock) { + self._queue.push({ + event: 'rate', + action: function() { + self.rate.apply(self, args); + } + }); + + return self; + } + + // Set the group rate. + if (typeof id === 'undefined') { + self._rate = rate; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i Returns the first sound node's current seek position. + * seek(id) -> Returns the sound id's current seek position. + * seek(seek) -> Sets the seek position of the first sound node. + * seek(seek, id) -> Sets the seek position of passed sound id. + * @return {Howl/Number} Returns self or the current seek position. + */ + seek: function() { + var self = this; + var args = arguments; + var seek, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current position of the first node. + if (self._sounds.length) { + id = self._sounds[0]._id; + } + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new seek position. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else if (self._sounds.length) { + id = self._sounds[0]._id; + seek = parseFloat(args[0]); + } + } else if (args.length === 2) { + seek = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // If there is no ID, bail out. + if (typeof id === 'undefined') { + return 0; + } + + // If the sound hasn't loaded, add it to the load queue to seek when capable. + if (typeof seek === 'number' && (self._state !== 'loaded' || self._playLock)) { + self._queue.push({ + event: 'seek', + action: function() { + self.seek.apply(self, args); + } + }); + + return self; + } + + // Get the sound. + var sound = self._soundById(id); + + if (sound) { + if (typeof seek === 'number' && seek >= 0) { + // Pause the sound and update position for restarting playback. + var playing = self.playing(id); + if (playing) { + self.pause(id, true); + } + + // Move the position of the track and cancel timer. + sound._seek = seek; + sound._ended = false; + self._clearTimer(id); + + // Update the seek position for HTML5 Audio. + if (!self._webAudio && sound._node && !isNaN(sound._node.duration)) { + sound._node.currentTime = seek; + } + + // Seek and emit when ready. + var seekAndEmit = function() { + // Restart the playback if the sound was playing. + if (playing) { + self.play(id, true); + } + + self._emit('seek', id); + }; + + // Wait for the play lock to be unset before emitting (HTML5 Audio). + if (playing && !self._webAudio) { + var emitSeek = function() { + if (!self._playLock) { + seekAndEmit(); + } else { + setTimeout(emitSeek, 0); + } + }; + setTimeout(emitSeek, 0); + } else { + seekAndEmit(); + } + } else { + if (self._webAudio) { + var realTime = self.playing(id) ? Howler.ctx.currentTime - sound._playStart : 0; + var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0; + return sound._seek + (rateSeek + realTime * Math.abs(sound._rate)); + } else { + return sound._node.currentTime; + } + } + } + + return self; + }, + + /** + * Check if a specific sound is currently playing or not (if id is provided), or check if at least one of the sounds in the group is playing or not. + * @param {Number} id The sound id to check. If none is passed, the whole sound group is checked. + * @return {Boolean} True if playing and false if not. + */ + playing: function(id) { + var self = this; + + // Check the passed sound ID (if any). + if (typeof id === 'number') { + var sound = self._soundById(id); + return sound ? !sound._paused : false; + } + + // Otherwise, loop through all sounds and check if any are playing. + for (var i=0; i= 0) { + Howler._howls.splice(index, 1); + } + + // Delete this sound from the cache (if no other Howl is using it). + var remCache = true; + for (i=0; i= 0)) { + remCache = false; + break; + } + } + + if (cache && remCache) { + delete cache[self._src]; + } + + // Clear global errors. + Howler.noAudio = false; + + // Clear out `self`. + self._state = 'unloaded'; + self._sounds = []; + self = null; + + return null; + }, + + /** + * Listen to a custom event. + * @param {String} event Event name. + * @param {Function} fn Listener to call. + * @param {Number} id (optional) Only listen to events for this sound. + * @param {Number} once (INTERNAL) Marks event to fire only once. + * @return {Howl} + */ + on: function(event, fn, id, once) { + var self = this; + var events = self['_on' + event]; + + if (typeof fn === 'function') { + events.push(once ? {id: id, fn: fn, once: once} : {id: id, fn: fn}); + } + + return self; + }, + + /** + * Remove a custom event. Call without parameters to remove all events. + * @param {String} event Event name. + * @param {Function} fn Listener to remove. Leave empty to remove all. + * @param {Number} id (optional) Only remove events for this sound. + * @return {Howl} + */ + off: function(event, fn, id) { + var self = this; + var events = self['_on' + event]; + var i = 0; + + // Allow passing just an event and ID. + if (typeof fn === 'number') { + id = fn; + fn = null; + } + + if (fn || id) { + // Loop through event store and remove the passed function. + for (i=0; i=0; i--) { + // Only fire the listener if the correct ID is used. + if (!events[i].id || events[i].id === id || event === 'load') { + setTimeout(function(fn) { + fn.call(this, id, msg); + }.bind(self, events[i].fn), 0); + + // If this event was setup with `once`, remove it. + if (events[i].once) { + self.off(event, events[i].fn, events[i].id); + } + } + } + + // Pass the event type into load queue so that it can continue stepping. + self._loadQueue(event); + + return self; + }, + + /** + * Queue of actions initiated before the sound has loaded. + * These will be called in sequence, with the next only firing + * after the previous has finished executing (even if async like play). + * @return {Howl} + */ + _loadQueue: function(event) { + var self = this; + + if (self._queue.length > 0) { + var task = self._queue[0]; + + // Remove this task if a matching event was passed. + if (task.event === event) { + self._queue.shift(); + self._loadQueue(); + } + + // Run the task if no event type is passed. + if (!event) { + task.action(); + } + } + + return self; + }, + + /** + * Fired when playback ends at the end of the duration. + * @param {Sound} sound The sound object to work with. + * @return {Howl} + */ + _ended: function(sound) { + var self = this; + var sprite = sound._sprite; + + // If we are using IE and there was network latency we may be clipping + // audio before it completes playing. Lets check the node to make sure it + // believes it has completed, before ending the playback. + if (!self._webAudio && sound._node && !sound._node.paused && !sound._node.ended && sound._node.currentTime < sound._stop) { + setTimeout(self._ended.bind(self, sound), 100); + return self; + } + + // Should this sound loop? + var loop = !!(sound._loop || self._sprite[sprite][2]); + + // Fire the ended event. + self._emit('end', sound._id); + + // Restart the playback for HTML5 Audio loop. + if (!self._webAudio && loop) { + self.stop(sound._id, true).play(sound._id); + } + + // Restart this timer if on a Web Audio loop. + if (self._webAudio && loop) { + self._emit('play', sound._id); + sound._seek = sound._start || 0; + sound._rateSeek = 0; + sound._playStart = Howler.ctx.currentTime; + + var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate); + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + } + + // Mark the node as paused. + if (self._webAudio && !loop) { + sound._paused = true; + sound._ended = true; + sound._seek = sound._start || 0; + sound._rateSeek = 0; + self._clearTimer(sound._id); + + // Clean up the buffer source. + self._cleanBuffer(sound._node); + + // Attempt to auto-suspend AudioContext if no sounds are still playing. + Howler._autoSuspend(); + } + + // When using a sprite, end the track. + if (!self._webAudio && !loop) { + self.stop(sound._id, true); + } + + return self; + }, + + /** + * Clear the end timer for a sound playback. + * @param {Number} id The sound ID. + * @return {Howl} + */ + _clearTimer: function(id) { + var self = this; + + if (self._endTimers[id]) { + // Clear the timeout or remove the ended listener. + if (typeof self._endTimers[id] !== 'function') { + clearTimeout(self._endTimers[id]); + } else { + var sound = self._soundById(id); + if (sound && sound._node) { + sound._node.removeEventListener('ended', self._endTimers[id], false); + } + } + + delete self._endTimers[id]; + } + + return self; + }, + + /** + * Return the sound identified by this ID, or return null. + * @param {Number} id Sound ID + * @return {Object} Sound object or null. + */ + _soundById: function(id) { + var self = this; + + // Loop through all sounds and find the one with this ID. + for (var i=0; i=0; i--) { + if (cnt <= limit) { + return; + } + + if (self._sounds[i]._ended) { + // Disconnect the audio source when using Web Audio. + if (self._webAudio && self._sounds[i]._node) { + self._sounds[i]._node.disconnect(0); + } + + // Remove sounds until we have the pool size. + self._sounds.splice(i, 1); + cnt--; + } + } + }, + + /** + * Get all ID's from the sounds pool. + * @param {Number} id Only return one ID if one is passed. + * @return {Array} Array of IDs. + */ + _getSoundIds: function(id) { + var self = this; + + if (typeof id === 'undefined') { + var ids = []; + for (var i=0; i= 0; + + if (!node.bufferSource) { + return self; + } + + if (Howler._scratchBuffer && node.bufferSource) { + node.bufferSource.onended = null; + node.bufferSource.disconnect(0); + if (isIOS) { + try { node.bufferSource.buffer = Howler._scratchBuffer; } catch(e) {} + } + } + node.bufferSource = null; + + return self; + }, + + /** + * Set the source to a 0-second silence to stop any downloading (except in IE). + * @param {Object} node Audio node to clear. + */ + _clearSound: function(node) { + var checkIE = /MSIE |Trident\//.test(Howler._navigator && Howler._navigator.userAgent); + if (!checkIE) { + node.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA'; + } + } + }; + + /** Single Sound Methods **/ + /***************************************************************************/ + + /** + * Setup the sound object, which each node attached to a Howl group is contained in. + * @param {Object} howl The Howl parent group. + */ + var Sound = function(howl) { + this._parent = howl; + this.init(); + }; + Sound.prototype = { + /** + * Initialize a new Sound object. + * @return {Sound} + */ + init: function() { + var self = this; + var parent = self._parent; + + // Setup the default parameters. + self._muted = parent._muted; + self._loop = parent._loop; + self._volume = parent._volume; + self._rate = parent._rate; + self._seek = 0; + self._paused = true; + self._ended = true; + self._sprite = '__default'; + + // Generate a unique ID for this sound. + self._id = ++Howler._counter; + + // Add itself to the parent's pool. + parent._sounds.push(self); + + // Create the new node. + self.create(); + + return self; + }, + + /** + * Create and setup a new sound object, whether HTML5 Audio or Web Audio. + * @return {Sound} + */ + create: function() { + var self = this; + var parent = self._parent; + var volume = (Howler._muted || self._muted || self._parent._muted) ? 0 : self._volume; + + if (parent._webAudio) { + // Create the gain node for controlling volume (the source will connect to this). + self._node = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain(); + self._node.gain.setValueAtTime(volume, Howler.ctx.currentTime); + self._node.paused = true; + self._node.connect(Howler.masterGain); + } else if (!Howler.noAudio) { + // Get an unlocked Audio object from the pool. + self._node = Howler._obtainHtml5Audio(); + + // Listen for errors (http://dev.w3.org/html5/spec-author-view/spec.html#mediaerror). + self._errorFn = self._errorListener.bind(self); + self._node.addEventListener('error', self._errorFn, false); + + // Listen for 'canplaythrough' event to let us know the sound is ready. + self._loadFn = self._loadListener.bind(self); + self._node.addEventListener(Howler._canPlayEvent, self._loadFn, false); + + // Listen for the 'ended' event on the sound to account for edge-case where + // a finite sound has a duration of Infinity. + self._endFn = self._endListener.bind(self); + self._node.addEventListener('ended', self._endFn, false); + + // Setup the new audio node. + self._node.src = parent._src; + self._node.preload = parent._preload === true ? 'auto' : parent._preload; + self._node.volume = volume * Howler.volume(); + + // Begin loading the source. + self._node.load(); + } + + return self; + }, + + /** + * Reset the parameters of this sound to the original state (for recycle). + * @return {Sound} + */ + reset: function() { + var self = this; + var parent = self._parent; + + // Reset all of the parameters of this sound. + self._muted = parent._muted; + self._loop = parent._loop; + self._volume = parent._volume; + self._rate = parent._rate; + self._seek = 0; + self._rateSeek = 0; + self._paused = true; + self._ended = true; + self._sprite = '__default'; + + // Generate a new ID so that it isn't confused with the previous sound. + self._id = ++Howler._counter; + + return self; + }, + + /** + * HTML5 Audio error listener callback. + */ + _errorListener: function() { + var self = this; + + // Fire an error event and pass back the code. + self._parent._emit('loaderror', self._id, self._node.error ? self._node.error.code : 0); + + // Clear the event listener. + self._node.removeEventListener('error', self._errorFn, false); + }, + + /** + * HTML5 Audio canplaythrough listener callback. + */ + _loadListener: function() { + var self = this; + var parent = self._parent; + + // Round up the duration to account for the lower precision in HTML5 Audio. + parent._duration = Math.ceil(self._node.duration * 10) / 10; + + // Setup a sprite if none is defined. + if (Object.keys(parent._sprite).length === 0) { + parent._sprite = {__default: [0, parent._duration * 1000]}; + } + + if (parent._state !== 'loaded') { + parent._state = 'loaded'; + parent._emit('load'); + parent._loadQueue(); + } + + // Clear the event listener. + self._node.removeEventListener(Howler._canPlayEvent, self._loadFn, false); + }, + + /** + * HTML5 Audio ended listener callback. + */ + _endListener: function() { + var self = this; + var parent = self._parent; + + // Only handle the `ended`` event if the duration is Infinity. + if (parent._duration === Infinity) { + // Update the parent duration to match the real audio duration. + // Round up the duration to account for the lower precision in HTML5 Audio. + parent._duration = Math.ceil(self._node.duration * 10) / 10; + + // Update the sprite that corresponds to the real duration. + if (parent._sprite.__default[1] === Infinity) { + parent._sprite.__default[1] = parent._duration * 1000; + } + + // Run the regular ended method. + parent._ended(self); + } + + // Clear the event listener since the duration is now correct. + self._node.removeEventListener('ended', self._endFn, false); + } + }; + + /** Helper Methods **/ + /***************************************************************************/ + + var cache = {}; + + /** + * Buffer a sound from URL, Data URI or cache and decode to audio source (Web Audio API). + * @param {Howl} self + */ + var loadBuffer = function(self) { + var src = self._src; + + // Check if the src is arraybuffer or audiobuffer. + // Disable caching if its not a url. + if (src instanceof ArrayBuffer) { + decodeAudioData(src, self); + return; + } else if (src instanceof AudioBuffer) { + loadSound(self, src); + return; + } + + // Check if the buffer has already been cached and use it instead. + if (cache[src]) { + loadSound(self, cache[src]); + return; + } + + if (/^data:[^;]+;base64,/.test(src)) { + // Decode the base64 data URI without XHR, since some browsers don't support it. + var data = atob(src.split(',')[1]); + var dataView = new Uint8Array(data.length); + for (var i=0; i=0; i--) { + self._howls[i].stereo(pan); + } + + return self; + }; + + /** + * Get/set the position of the listener in 3D cartesian space. Sounds using + * 3D position will be relative to the listener's position. + * @param {Number} x The x-position of the listener. + * @param {Number} y The y-position of the listener. + * @param {Number} z The z-position of the listener. + * @return {Howler/Array} Self or current listener position. + */ + HowlerGlobal.prototype.pos = function(x, y, z) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self.ctx || !self.ctx.listener) { + return self; + } + + // Set the defaults for optional 'y' & 'z'. + y = (typeof y !== 'number') ? self._pos[1] : y; + z = (typeof z !== 'number') ? self._pos[2] : z; + + if (typeof x === 'number') { + self._pos = [x, y, z]; + + if (typeof self.ctx.listener.positionX !== 'undefined') { + self.ctx.listener.positionX.setTargetAtTime(self._pos[0], Howler.ctx.currentTime, 0.1); + self.ctx.listener.positionY.setTargetAtTime(self._pos[1], Howler.ctx.currentTime, 0.1); + self.ctx.listener.positionZ.setTargetAtTime(self._pos[2], Howler.ctx.currentTime, 0.1); + } else { + self.ctx.listener.setPosition(self._pos[0], self._pos[1], self._pos[2]); + } + } else { + return self._pos; + } + + return self; + }; + + /** + * Get/set the direction the listener is pointing in the 3D cartesian space. + * A front and up vector must be provided. The front is the direction the + * face of the listener is pointing, and up is the direction the top of the + * listener is pointing. Thus, these values are expected to be at right angles + * from each other. + * @param {Number} x The x-orientation of the listener. + * @param {Number} y The y-orientation of the listener. + * @param {Number} z The z-orientation of the listener. + * @param {Number} xUp The x-orientation of the top of the listener. + * @param {Number} yUp The y-orientation of the top of the listener. + * @param {Number} zUp The z-orientation of the top of the listener. + * @return {Howler/Array} Returns self or the current orientation vectors. + */ + HowlerGlobal.prototype.orientation = function(x, y, z, xUp, yUp, zUp) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self.ctx || !self.ctx.listener) { + return self; + } + + // Set the defaults for optional 'y' & 'z'. + var or = self._orientation; + y = (typeof y !== 'number') ? or[1] : y; + z = (typeof z !== 'number') ? or[2] : z; + xUp = (typeof xUp !== 'number') ? or[3] : xUp; + yUp = (typeof yUp !== 'number') ? or[4] : yUp; + zUp = (typeof zUp !== 'number') ? or[5] : zUp; + + if (typeof x === 'number') { + self._orientation = [x, y, z, xUp, yUp, zUp]; + + if (typeof self.ctx.listener.forwardX !== 'undefined') { + self.ctx.listener.forwardX.setTargetAtTime(x, Howler.ctx.currentTime, 0.1); + self.ctx.listener.forwardY.setTargetAtTime(y, Howler.ctx.currentTime, 0.1); + self.ctx.listener.forwardZ.setTargetAtTime(z, Howler.ctx.currentTime, 0.1); + self.ctx.listener.upX.setTargetAtTime(xUp, Howler.ctx.currentTime, 0.1); + self.ctx.listener.upY.setTargetAtTime(yUp, Howler.ctx.currentTime, 0.1); + self.ctx.listener.upZ.setTargetAtTime(zUp, Howler.ctx.currentTime, 0.1); + } else { + self.ctx.listener.setOrientation(x, y, z, xUp, yUp, zUp); + } + } else { + return or; + } + + return self; + }; + + /** Group Methods **/ + /***************************************************************************/ + + /** + * Add new properties to the core init. + * @param {Function} _super Core init method. + * @return {Howl} + */ + Howl.prototype.init = (function(_super) { + return function(o) { + var self = this; + + // Setup user-defined default properties. + self._orientation = o.orientation || [1, 0, 0]; + self._stereo = o.stereo || null; + self._pos = o.pos || null; + self._pannerAttr = { + coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : 360, + coneOuterAngle: typeof o.coneOuterAngle !== 'undefined' ? o.coneOuterAngle : 360, + coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : 0, + distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : 'inverse', + maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : 10000, + panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : 'HRTF', + refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : 1, + rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : 1 + }; + + // Setup event listeners. + self._onstereo = o.onstereo ? [{fn: o.onstereo}] : []; + self._onpos = o.onpos ? [{fn: o.onpos}] : []; + self._onorientation = o.onorientation ? [{fn: o.onorientation}] : []; + + // Complete initilization with howler.js core's init function. + return _super.call(this, o); + }; + })(Howl.prototype.init); + + /** + * Get/set the stereo panning of the audio source for this sound or all in the group. + * @param {Number} pan A value of -1.0 is all the way left and 1.0 is all the way right. + * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated. + * @return {Howl/Number} Returns self or the current stereo panning value. + */ + Howl.prototype.stereo = function(pan, id) { + var self = this; + + // Stop right here if not using Web Audio. + if (!self._webAudio) { + return self; + } + + // If the sound hasn't loaded, add it to the load queue to change stereo pan when capable. + if (self._state !== 'loaded') { + self._queue.push({ + event: 'stereo', + action: function() { + self.stereo(pan, id); + } + }); + + return self; + } + + // Check for PannerStereoNode support and fallback to PannerNode if it doesn't exist. + var pannerType = (typeof Howler.ctx.createStereoPanner === 'undefined') ? 'spatial' : 'stereo'; + + // Setup the group's stereo panning if no ID is passed. + if (typeof id === 'undefined') { + // Return the group's stereo panning if no parameters are passed. + if (typeof pan === 'number') { + self._stereo = pan; + self._pos = [pan, 0, 0]; + } else { + return self._stereo; + } + } + + // Change the streo panning of one or all sounds in group. + var ids = self._getSoundIds(id); + for (var i=0; i Returns the group's values. + * pannerAttr(id) -> Returns the sound id's values. + * pannerAttr(o) -> Set's the values of all sounds in this Howl group. + * pannerAttr(o, id) -> Set's the values of passed sound id. + * + * Attributes: + * coneInnerAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees, + * inside of which there will be no volume reduction. + * coneOuterAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees, + * outside of which the volume will be reduced to a constant value of `coneOuterGain`. + * coneOuterGain - (0 by default) A parameter for directional audio sources, this is the gain outside of the + * `coneOuterAngle`. It is a linear value in the range `[0, 1]`. + * distanceModel - ('inverse' by default) Determines algorithm used to reduce volume as audio moves away from + * listener. Can be `linear`, `inverse` or `exponential. + * maxDistance - (10000 by default) The maximum distance between source and listener, after which the volume + * will not be reduced any further. + * refDistance - (1 by default) A reference distance for reducing volume as source moves further from the listener. + * This is simply a variable of the distance model and has a different effect depending on which model + * is used and the scale of your coordinates. Generally, volume will be equal to 1 at this distance. + * rolloffFactor - (1 by default) How quickly the volume reduces as source moves from listener. This is simply a + * variable of the distance model and can be in the range of `[0, 1]` with `linear` and `[0, ∞]` + * with `inverse` and `exponential`. + * panningModel - ('HRTF' by default) Determines which spatialization algorithm is used to position audio. + * Can be `HRTF` or `equalpower`. + * + * @return {Howl/Object} Returns self or current panner attributes. + */ + Howl.prototype.pannerAttr = function() { + var self = this; + var args = arguments; + var o, id, sound; + + // Stop right here if not using Web Audio. + if (!self._webAudio) { + return self; + } + + // Determine the values based on arguments. + if (args.length === 0) { + // Return the group's panner attribute values. + return self._pannerAttr; + } else if (args.length === 1) { + if (typeof args[0] === 'object') { + o = args[0]; + + // Set the grou's panner attribute values. + if (typeof id === 'undefined') { + if (!o.pannerAttr) { + o.pannerAttr = { + coneInnerAngle: o.coneInnerAngle, + coneOuterAngle: o.coneOuterAngle, + coneOuterGain: o.coneOuterGain, + distanceModel: o.distanceModel, + maxDistance: o.maxDistance, + refDistance: o.refDistance, + rolloffFactor: o.rolloffFactor, + panningModel: o.panningModel + }; + } + + self._pannerAttr = { + coneInnerAngle: typeof o.pannerAttr.coneInnerAngle !== 'undefined' ? o.pannerAttr.coneInnerAngle : self._coneInnerAngle, + coneOuterAngle: typeof o.pannerAttr.coneOuterAngle !== 'undefined' ? o.pannerAttr.coneOuterAngle : self._coneOuterAngle, + coneOuterGain: typeof o.pannerAttr.coneOuterGain !== 'undefined' ? o.pannerAttr.coneOuterGain : self._coneOuterGain, + distanceModel: typeof o.pannerAttr.distanceModel !== 'undefined' ? o.pannerAttr.distanceModel : self._distanceModel, + maxDistance: typeof o.pannerAttr.maxDistance !== 'undefined' ? o.pannerAttr.maxDistance : self._maxDistance, + refDistance: typeof o.pannerAttr.refDistance !== 'undefined' ? o.pannerAttr.refDistance : self._refDistance, + rolloffFactor: typeof o.pannerAttr.rolloffFactor !== 'undefined' ? o.pannerAttr.rolloffFactor : self._rolloffFactor, + panningModel: typeof o.pannerAttr.panningModel !== 'undefined' ? o.pannerAttr.panningModel : self._panningModel + }; + } + } else { + // Return this sound's panner attribute values. + sound = self._soundById(parseInt(args[0], 10)); + return sound ? sound._pannerAttr : self._pannerAttr; + } + } else if (args.length === 2) { + o = args[0]; + id = parseInt(args[1], 10); + } + + // Update the values of the specified sounds. + var ids = self._getSoundIds(id); + for (var i=0; i=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator?e._navigator.userAgent:"",a=r.match(/OPR\/(\d+)/g),u=a&&parseInt(a[0].split("/")[1],10)<33,d=-1!==r.indexOf("Safari")&&-1===r.indexOf("Chrome"),i=r.match(/Version\/(.*?) /),_=d&&i&&parseInt(i[1],10)<15;return e._codecs={mp3:!(u||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(o.canPlayType('audio/wav; codecs="1"')||o.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(o.canPlayType("audio/x-m4b;")||o.canPlayType("audio/m4b;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(;e._html5AudioPool.length0?d._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(d._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;d._sprite=e,d._ended=!1;var p=function(){d._paused=!1,d._seek=_,d._start=c,d._stop=f,d._loop=!(!d._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(d);var m=d._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(d);var e=d._muted||t._muted?0:d._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),d._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?d._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):d._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l)),o||setTimeout(function(){t._emit("play",d._id),t._loadQueue()},0)};"running"===n.state&&"interrupted"!==n.ctx.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(d._id))}else{var h=function(){m.currentTime=_,m.muted=d._muted||t._muted||n._muted||m.muted,m.volume=d._volume*n.volume(),m.playbackRate=d._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o?t._loadQueue():t._emit("play",d._id)}).catch(function(){t._playLock=!1,t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),d._ended=!0,d._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",d._id)),m.playbackRate=d._rate,m.paused)return void t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||d._loop?t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l):(t._endTimers[d._id]=function(){t._ended(d),m.removeEventListener("ended",t._endTimers[d._id],!1)},m.addEventListener("ended",t._endTimers[d._id],!1))}catch(e){t._emit("playerror",d._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0,t._state="loading";var g=function(){t._state="loaded",h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(d._id)}}return d._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),d+=i*r,d=Math.round(100*d)/100,d=i<0?Math.max(o,d):Math.min(o,d),u._webAudio?e._volume=d:u.volume(d,e._id,!0),a&&(u._volume=d),(on&&d>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var d;if("number"!=typeof e)return d=t._soundById(o),d?d._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var i=0;i=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return 0;if("number"==typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var d=t._soundById(o);if(d){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var i=t.playing(o)?n.ctx.currentTime-d._playStart:0,_=d._rateSeek?d._rateSeek-d._seek:0;return d._seek+(_+i*Math.abs(d._rate))}return d._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),d._seek=e,d._ended=!1,t._clearTimer(o),t._webAudio||!d._node||isNaN(d._node.duration)||(d._node.currentTime=e);var l=function(){s&&t.play(o,!0),t._emit("seek",o)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t=0&&n._howls.splice(a,1);var u=!0;for(t=0;t=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t=0;if(!e.bufferSource)return o;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=o._src,e._node.preload=!0===o._preload?"auto":o._preload,e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void i(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a0?(r[o._src]=e,i(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},i=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());n._navigator&&!r&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof global?(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t):"undefined"!=typeof window&&(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t)}(); -/*! Spatial Plugin */ -!function(){"use strict";HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;for(var t=n._howls.length-1;t>=0;t--)n._howls[t].stereo(e);return n},HowlerGlobal.prototype.pos=function(e,n,t){var r=this;return r.ctx&&r.ctx.listener?(n="number"!=typeof n?r._pos[1]:n,t="number"!=typeof t?r._pos[2]:t,"number"!=typeof e?r._pos:(r._pos=[e,n,t],void 0!==r.ctx.listener.positionX?(r.ctx.listener.positionX.setTargetAtTime(r._pos[0],Howler.ctx.currentTime,.1),r.ctx.listener.positionY.setTargetAtTime(r._pos[1],Howler.ctx.currentTime,.1),r.ctx.listener.positionZ.setTargetAtTime(r._pos[2],Howler.ctx.currentTime,.1)):r.ctx.listener.setPosition(r._pos[0],r._pos[1],r._pos[2]),r)):r},HowlerGlobal.prototype.orientation=function(e,n,t,r,o,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var p=a._orientation;return n="number"!=typeof n?p[1]:n,t="number"!=typeof t?p[2]:t,r="number"!=typeof r?p[3]:r,o="number"!=typeof o?p[4]:o,i="number"!=typeof i?p[5]:i,"number"!=typeof e?p:(a._orientation=[e,n,t,r,o,i],void 0!==a.ctx.listener.forwardX?(a.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.forwardY.setTargetAtTime(n,Howler.ctx.currentTime,.1),a.ctx.listener.forwardZ.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.upX.setTargetAtTime(r,Howler.ctx.currentTime,.1),a.ctx.listener.upY.setTargetAtTime(o,Howler.ctx.currentTime,.1),a.ctx.listener.upZ.setTargetAtTime(i,Howler.ctx.currentTime,.1)):a.ctx.listener.setOrientation(e,n,t,r,o,i),a)},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._stereo=n.stereo||null,t._pos=n.pos||null,t._pannerAttr={coneInnerAngle:void 0!==n.coneInnerAngle?n.coneInnerAngle:360,coneOuterAngle:void 0!==n.coneOuterAngle?n.coneOuterAngle:360,coneOuterGain:void 0!==n.coneOuterGain?n.coneOuterGain:0,distanceModel:void 0!==n.distanceModel?n.distanceModel:"inverse",maxDistance:void 0!==n.maxDistance?n.maxDistance:1e4,panningModel:void 0!==n.panningModel?n.panningModel:"HRTF",refDistance:void 0!==n.refDistance?n.refDistance:1,rolloffFactor:void 0!==n.rolloffFactor?n.rolloffFactor:1},t._onstereo=n.onstereo?[{fn:n.onstereo}]:[],t._onpos=n.onpos?[{fn:n.onpos}]:[],t._onorientation=n.onorientation?[{fn:n.onorientation}]:[],e.call(this,n)}}(Howl.prototype.init),Howl.prototype.stereo=function(n,t){var r=this;if(!r._webAudio)return r;if("loaded"!==r._state)return r._queue.push({event:"stereo",action:function(){r.stereo(n,t)}}),r;var o=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===t){if("number"!=typeof n)return r._stereo;r._stereo=n,r._pos=[n,0,0]}for(var i=r._getSoundIds(t),a=0;a + @@ -24,6 +25,7 @@ + @@ -81,7 +83,7 @@ - + diff --git a/project/Build.xml b/project/Build.xml index 404cffcd73..6009b3ae6f 100644 --- a/project/Build.xml +++ b/project/Build.xml @@ -12,7 +12,8 @@ - + + @@ -22,6 +23,7 @@ + @@ -77,6 +79,18 @@ +
+ + + + + + + + + +
+
@@ -156,10 +170,28 @@ - - +
+ + + + + + + +
+ +
+ + + + + + + + +
@@ -237,17 +269,6 @@ -
- - - - - - - - -
-
@@ -267,8 +288,6 @@ - - @@ -309,6 +328,7 @@ + @@ -318,6 +338,7 @@ + @@ -340,6 +361,7 @@ + @@ -348,6 +370,7 @@ + diff --git a/project/include/media/AudioBuffer.h b/project/include/media/AudioBuffer.h deleted file mode 100644 index f5744e986a..0000000000 --- a/project/include/media/AudioBuffer.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef LIME_MEDIA_AUDIO_BUFFER_H -#define LIME_MEDIA_AUDIO_BUFFER_H - - -#include -#include - -#ifdef ANDROID -#include -#endif - - -#ifdef ANDROID -#define LOG_SOUND(args,...) __android_log_print(ANDROID_LOG_INFO, "Lime", args, ##__VA_ARGS__) -#else -#ifdef IPHONE -//#define LOG_SOUND(args,...) printf(args, ##__VA_ARGS__) -#define LOG_SOUND(args...) { } -#else -#define LOG_SOUND(args,...) printf(args, ##__VA_ARGS__) -#endif -#endif -//#define LOG_SOUND(args...) { } - - -namespace lime { - - - struct AudioBuffer { - - hl_type* t; - int bitsPerSample; - int channels; - ArrayBufferView* data; - int dataFormat; - int sampleRate; - - AudioBuffer (value audioBuffer); - ~AudioBuffer (); - value Value (value audioBuffer); - value Value (); - - }; - - -} - - -#endif \ No newline at end of file diff --git a/project/include/media/codecs/flac/DrFlac.h b/project/include/media/codecs/flac/DrFlac.h new file mode 100644 index 0000000000..e92548ab7d --- /dev/null +++ b/project/include/media/codecs/flac/DrFlac.h @@ -0,0 +1,29 @@ +#ifndef LIME_MEDIA_CODECS_FLAC_DR_FLAC_H +#define LIME_MEDIA_CODECS_FLAC_DR_FLAC_H + + +#include +#include + + +namespace lime { + + + class DrFlac { + + + public: + + static drflac* FromBytes (Bytes* bytes); + static drflac* FromFile (const char* path); + static void Close (drflac* pFlac); + + + }; + + +} + + + +#endif \ No newline at end of file diff --git a/project/include/media/codecs/mp3/DrMp3.h b/project/include/media/codecs/mp3/DrMp3.h new file mode 100644 index 0000000000..f5b5ce1b19 --- /dev/null +++ b/project/include/media/codecs/mp3/DrMp3.h @@ -0,0 +1,29 @@ +#ifndef LIME_MEDIA_CODECS_MP3_DR_MP3_H +#define LIME_MEDIA_CODECS_MP3_DR_MP3_H + + +#include +#include + + +namespace lime { + + + class DrMp3 { + + + public: + + static drmp3* FromBytes (Bytes* bytes); + static drmp3* FromFile (const char* path); + static void Close (drmp3* pMp3); + + + }; + + +} + + + +#endif \ No newline at end of file diff --git a/project/include/media/codecs/opus/OpusFile.h b/project/include/media/codecs/opus/OpusFile.h new file mode 100644 index 0000000000..52ee12cf84 --- /dev/null +++ b/project/include/media/codecs/opus/OpusFile.h @@ -0,0 +1,28 @@ +#ifndef LIME_MEDIA_CODECS_OPUS_OPUS_FILE_H +#define LIME_MEDIA_CODECS_OPUS_OPUS_FILE_H + + +#include +#include + + +namespace lime { + + + class OpusFile { + + + public: + + + static OggOpusFile* FromBytes (Bytes* bytes); + static OggOpusFile* FromFile (const char* path); + + + }; + + +} + + +#endif \ No newline at end of file diff --git a/project/include/media/codecs/vorbis/VorbisFile.h b/project/include/media/codecs/vorbis/VorbisFile.h index a2f2487ab0..8c3011601d 100644 --- a/project/include/media/codecs/vorbis/VorbisFile.h +++ b/project/include/media/codecs/vorbis/VorbisFile.h @@ -3,6 +3,7 @@ #include +#include #include diff --git a/project/include/media/codecs/wav/DrWav.h b/project/include/media/codecs/wav/DrWav.h new file mode 100644 index 0000000000..1c903cf1c6 --- /dev/null +++ b/project/include/media/codecs/wav/DrWav.h @@ -0,0 +1,29 @@ +#ifndef LIME_MEDIA_CODECS_WAV_DR_WAV_H +#define LIME_MEDIA_CODECS_WAV_DR_WAV_H + + +#include +#include + + +namespace lime { + + + class DrWav { + + + public: + + static drwav* FromBytes (Bytes* bytes); + static drwav* FromFile (const char* path); + static void Close (drwav* pWav); + + + }; + + +} + + + +#endif \ No newline at end of file diff --git a/project/include/media/containers/OGG.h b/project/include/media/containers/OGG.h deleted file mode 100644 index 0283e3fee2..0000000000 --- a/project/include/media/containers/OGG.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef LIME_MEDIA_CONTAINERS_OGG_H -#define LIME_MEDIA_CONTAINERS_OGG_H - - -#include -#include - - -namespace lime { - - - class OGG { - - - public: - - static bool Decode (Resource *resource, AudioBuffer *audioBuffer); - - - }; - - -} - - -#endif \ No newline at end of file diff --git a/project/include/media/containers/WAV.h b/project/include/media/containers/WAV.h deleted file mode 100644 index f9117033ff..0000000000 --- a/project/include/media/containers/WAV.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef LIME_MEDIA_CONTAINERS_WAV_H -#define LIME_MEDIA_CONTAINERS_WAV_H - - -#include -#include - - -namespace lime { - - - struct RIFF_Header { - - char chunkID[4]; - unsigned int chunkSize; //size not including chunkSize or chunkID - char format[4]; - - }; - - - struct WAVE_Format { - - char subChunkID[4]; - unsigned int subChunkSize; - short audioFormat; - short numChannels; - unsigned int sampleRate; - unsigned int byteRate; - short blockAlign; - short bitsPerSample; - - }; - - - struct WAVE_Data { - - char subChunkID[4]; //should contain the word data - unsigned int subChunkSize; //Stores the size of the data block - - }; - - - class WAV { - - - public: - - static bool Decode (Resource *resource, AudioBuffer *audioBuffer); - - - }; - - -} - - -#endif \ No newline at end of file diff --git a/project/lib/README.md b/project/lib/README.md index 101f8530b5..595703a1b9 100644 --- a/project/lib/README.md +++ b/project/lib/README.md @@ -5,6 +5,7 @@ Lime includes code from several other C/C++ libraries, listed below. Lime prefer - [**Cairo**](https://www.cairographics.org/) | [primary repo](https://gitlab.freedesktop.org/cairo/cairo) | [GitHub mirror](https://github.com/freedesktop/cairo) - [**cURL**](https://curl.se/) | [primary repo](https://github.com/curl/curl) +- [**dr_libs**](https://github.com/mackron/dr_libs) | [primary repo](https://github.com/mackron/dr_libs) - **efsw** | [primary repo](https://github.com/SpartanJ/efsw) - [**FreeType**](https://freetype.org/) | [primary repo](https://gitlab.freedesktop.org/freetype/freetype) | [GitHub mirror](https://github.com/freetype/freetype) - [**HarfBuzz**](https://harfbuzz.github.io/) | [primary repo](https://github.com/harfbuzz/harfbuzz) @@ -14,6 +15,8 @@ Lime includes code from several other C/C++ libraries, listed below. Lime prefer - [**mbed TLS**](https://tls.mbed.org/) | [primary repo](https://github.com/Mbed-TLS/mbedtls) - [**Ogg**](https://www.xiph.org/ogg/) | [primary repo](https://github.com/xiph/ogg) - [**OpenAL Soft**](https://openal-soft.org/) | [primary repo](https://github.com/kcat/openal-soft) +- [**opus**](https://www.opus-codec.org/) | [primary repo](https://github.com/xiph/opus) +- [**opusfile**](https://opus-codec.org/) | [primary repo](https://github.com/xiph/opusfile) - [**Pixman**](http://pixman.org/) | [primary repo](https://gitlab.freedesktop.org/pixman/pixman) | [GitHub mirror](https://github.com/freedesktop/pixman) - [**libpng**](http://www.libpng.org/pub/png/libpng.html) | [primary repo](https://sourceforge.net/p/libpng/code) | [GitHub mirror](https://github.com/glennrp/libpng)[^1] - [**SDL**](https://www.libsdl.org/) | [primary repo](https://github.com/libsdl-org/SDL) diff --git a/project/lib/custom/dr_libs/dr_libs.c b/project/lib/custom/dr_libs/dr_libs.c new file mode 100644 index 0000000000..7e52bd556f --- /dev/null +++ b/project/lib/custom/dr_libs/dr_libs.c @@ -0,0 +1,11 @@ +#define DR_FLAC_IMPLEMENTATION +#define DR_MP3_IMPLEMENTATION +#define DR_WAV_IMPLEMENTATION + +#define DR_FLAC_NO_STDIO +#define DR_MP3_NO_STDIO +#define DR_WAV_NO_STDIO + +#include "dr_flac.h" +#include "dr_mp3.h" +#include "dr_wav.h" \ No newline at end of file diff --git a/project/lib/custom/openal/alc/alconfig.cpp b/project/lib/custom/openal/alc/alconfig.cpp new file mode 100644 index 0000000000..8d9c48f4dc --- /dev/null +++ b/project/lib/custom/openal/alc/alconfig.cpp @@ -0,0 +1,551 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "alconfig.h" + +#ifdef _WIN32 +#include +#include +#endif +#ifdef __APPLE__ +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alnumeric.h" +#include "alstring.h" +#include "core/helpers.h" +#include "filesystem.h" +#include "fmt/ranges.h" +#include "gsl/gsl" +#include "strutils.hpp" + +#if ALSOFT_UWP +#include // !!This is important!! +#include +#include +#include +using namespace winrt; +#endif + +#if HAVE_CXXMODULES +import logging; +#else +#include "core/logging.h" +#endif + +namespace { + +using namespace std::string_view_literals; + +const auto EmptyString = std::string{}; + +struct ConfigEntry { + std::string key; + std::string value; + + ConfigEntry(auto&& key_, auto&& value_) + : key{std::forward(key_)}, value{std::forward(value_)} + { } +}; +std::vector ConfOpts; + + +/* True UTF-8 validation is way beyond me. However, this should weed out any + * obviously non-UTF-8 text. + * + * The general form of the byte stream is relatively simple. The first byte of + * a codepoint either has a 0 bit for the msb, indicating a single-byte ASCII- + * compatible codepoint, or the number of bytes that make up the codepoint + * (including itself) indicated by the number of successive 1 bits, and each + * successive byte of the codepoint has '10' for the top bits. That is: + * + * 0xxxxxxx - single-byte ASCII-compatible codepoint + * 110xxxxx 10xxxxxx - two-byte codepoint + * 1110xxxx 10xxxxxx 10xxxxxx - three-byte codepoint + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - four-byte codepoint + * ... etc ... + * + * Where the 'x' bits are concatenated together to form a 32-bit Unicode + * codepoint. This doesn't check whether the codepoints themselves are valid, + * it just validates the correct number of bytes for multi-byte sequences. + */ +auto validate_utf8(const std::string_view str) -> bool +{ + auto const end = str.end(); + /* Look for the first multi-byte/non-ASCII codepoint. */ + auto current = std::ranges::find_if(str.begin(), end, + [](const char ch) -> bool { return (ch&0x80) != 0; }); + while(const auto remaining = std::distance(current, end)) + { + /* Get the number of bytes that make up this codepoint (must be at + * least 2). This includes the current byte. + */ + const auto tocheck = std::countl_one(as_unsigned(*current)); + if(tocheck < 2 || tocheck > remaining) + return false; + + const auto next = std::next(current, tocheck); + + /* Check that the following bytes are a proper continuation. */ + const auto valid = std::ranges::all_of(std::next(current), next, + [](const char ch) -> bool { return (ch&0xc0) == 0x80; }); + if(not valid) + return false; + + /* Seems okay. Look for the next multi-byte/non-ASCII codepoint. */ + current = std::ranges::find_if(next, end, [](const char ch) -> bool { return ch&0x80; }); + } + return true; +} + +auto lstrip(std::string &line) -> std::string& +{ + auto iter = std::ranges::find_if_not(line, [](const char c) { return std::isspace(c); }); + line.erase(line.begin(), iter); + return line; +} + +auto expdup(std::string_view str) -> std::string +{ + auto output = std::string{}; + + while(!str.empty()) + { + if(auto nextpos = str.find('$')) + { + output += str.substr(0, nextpos); + if(nextpos == std::string_view::npos) + break; + + str.remove_prefix(nextpos); + } + + str.remove_prefix(1); + if(str.empty()) + { + output += '$'; + break; + } + if(str.front() == '$') + { + output += '$'; + str.remove_prefix(1); + continue; + } + + const auto hasbraces = bool{str.front() == '{'}; + if(hasbraces) str.remove_prefix(1); + + const auto envenditer = std::ranges::find_if_not(str, + [](const char c) { return c == '_' || std::isalnum(c); }); + + if(hasbraces && (envenditer == str.end() || *envenditer != '}')) + continue; + + const auto envend = gsl::narrow(std::distance(str.begin(), envenditer)); + const auto envname = std::string{str.substr(0, envend)}; + str.remove_prefix(envend + hasbraces); + + if(const auto envval = al::getenv(envname.c_str())) + output += *envval; + } + + return output; +} + +auto GetConfigValue(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> const std::string& +{ + if(keyName.empty()) + return EmptyString; + + auto key = std::string{}; + if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) + { + key = blockName; + key += '/'; + } + if(!devName.empty()) + { + key += devName; + key += '/'; + } + key += keyName; + + const auto iter = std::ranges::find(ConfOpts, key, &ConfigEntry::key); + if(iter != ConfOpts.cend()) + { + TRACE("Found option {} = \"{}\"", key, iter->value); + if(!iter->value.empty()) + return iter->value; + return EmptyString; + } + + if(devName.empty()) + return EmptyString; + return GetConfigValue({}, blockName, keyName); +} + +} // namespace + +void SetConfigValue(const std::string_view key, const std::string_view value) +{ + /* Check if we already have this option set */ + const auto ent = std::ranges::find(ConfOpts, key, &ConfigEntry::key); + if(ent != ConfOpts.end()) + { + if(!value.empty()) + ent->value = expdup(value); + else + ConfOpts.erase(ent); + } + else if(!value.empty()) + ConfOpts.emplace_back(std::move(key), expdup(value)); +} + +void LoadConfigFromFile(std::istream &f) +{ + constexpr auto whitespace_chars = " \t\n\f\r\v"sv; + + auto curSection = std::string{}; + auto buffer = std::string{}; + auto linenum = std::size_t{0}; + + while(std::getline(f, buffer)) + { + ++linenum; + if(lstrip(buffer).empty()) + continue; + + auto cmtpos = std::min(buffer.find('#'), buffer.size()); + if(cmtpos != 0) + cmtpos = buffer.find_last_not_of(whitespace_chars, cmtpos-1)+1; + if(cmtpos == 0) continue; + buffer.erase(cmtpos); + + if(not validate_utf8(buffer)) + { + ERR(" config parse error: non-UTF-8 characters on line {}", linenum); + ERR("{}", fmt::format(" {::#04x}", + buffer|std::views::transform([](auto c) { return as_unsigned(c); }))); + continue; + } + + if(buffer[0] == '[') + { + const auto endpos = buffer.find(']', 1); + if(endpos == 1 || endpos == std::string::npos) + { + ERR(" config parse error on line {}: bad section \"{}\"", linenum, buffer); + continue; + } + if(const auto last = buffer.find_first_not_of(whitespace_chars, endpos+1); + last < buffer.size() && buffer[last] != '#') + { + ERR(" config parse error on line {}: extraneous characters after section \"{}\"", + linenum, buffer); + continue; + } + + auto section = std::string_view{buffer}.substr(1, endpos-1); + + curSection.clear(); + if(al::case_compare(section, "general"sv) == 0) + continue; + + while(!section.empty()) + { + const auto nextp = section.find('%'); + if(nextp == std::string_view::npos) + { + curSection += section; + break; + } + + curSection += section.substr(0, nextp); + section.remove_prefix(nextp); + + if(section.size() > 2 + && ((section[1] >= '0' && section[1] <= '9') + || (section[1] >= 'a' && section[1] <= 'f') + || (section[1] >= 'A' && section[1] <= 'F')) + && ((section[2] >= '0' && section[2] <= '9') + || (section[2] >= 'a' && section[2] <= 'f') + || (section[2] >= 'A' && section[2] <= 'F'))) + { + auto b = 0; + if(section[1] >= '0' && section[1] <= '9') + b = (section[1]-'0') << 4; + else if(section[1] >= 'a' && section[1] <= 'f') + b = (section[1]-'a'+0xa) << 4; + else if(section[1] >= 'A' && section[1] <= 'F') + b = (section[1]-'A'+0x0a) << 4; + if(section[2] >= '0' && section[2] <= '9') + b |= section[2]-'0'; + else if(section[2] >= 'a' && section[2] <= 'f') + b |= section[2]-'a'+0xa; + else if(section[2] >= 'A' && section[2] <= 'F') + b |= section[2]-'A'+0x0a; + curSection += gsl::narrow_cast(b); + section.remove_prefix(3); + } + else if(section.size() > 1 && section[1] == '%') + { + curSection += '%'; + section.remove_prefix(2); + } + else + { + curSection += '%'; + section.remove_prefix(1); + } + } + + continue; + } + + auto sep = buffer.find('='); + if(sep == std::string::npos) + { + ERR(" config parse error on line {}: malformed option \"{}\"", linenum, buffer); + continue; + } + auto keypart = std::string_view{buffer}.substr(0, sep++); + keypart.remove_suffix(keypart.size() - (keypart.find_last_not_of(whitespace_chars)+1)); + if(keypart.empty()) + { + ERR(" config parse error on line {}: malformed option \"{}\"", linenum, buffer); + continue; + } + auto valpart = std::string_view{buffer}.substr(sep); + valpart.remove_prefix(std::min(valpart.find_first_not_of(whitespace_chars), + valpart.size())); + + auto fullKey = curSection; + if(!fullKey.empty()) + fullKey += '/'; + fullKey += keypart; + + if(valpart.size() > size_t{std::numeric_limits::max()}) + { + ERR(" config parse error on line {}: value too long \"{}\"", linenum, buffer); + continue; + } + if(valpart.size() > 1) + { + if((valpart.front() == '"' && valpart.back() == '"') + || (valpart.front() == '\'' && valpart.back() == '\'')) + { + valpart.remove_prefix(1); + valpart.remove_suffix(1); + } + } + + TRACE(" setting '{}' = '{}'", fullKey, valpart); + + SetConfigValue (fullKey, valpart); + } + ConfOpts.shrink_to_fit(); +} + +void FunkinALConfigDefault() +{ + SetConfigValue("frequency", "48000"); + SetConfigValue("sample-type", "float32"); + SetConfigValue("stereo-mode", "speakers"); + SetConfigValue("stereo-encoding", "basic"); + SetConfigValue("cf_level", "0"); + SetConfigValue("output-limiter", "false"); + SetConfigValue("front-stablizer", "false"); + SetConfigValue("volume-adjust", "0"); + SetConfigValue("period_size", "480"); + SetConfigValue("periods", "4"); + SetConfigValue("sends", "64"); + SetConfigValue("dither", "false"); + SetConfigValue("dither-depth", "0"); + SetConfigValue("decoder/hq-mode", "false"); + SetConfigValue("decoder/distance-comp", "false"); + SetConfigValue("decoder/nfc", "false"); + // exclusive-mode removes the latency about 20ms (from 80ms to 40ms in my testing -ralty) + // but game recorders wont be able to record the game because of the audio mixer being exclusive and not shared. + SetConfigValue("wasapi/exclusive-mode", "false"); + SetConfigValue("wasapi/spatial-api", "false"); + SetConfigValue("wasapi/allow-resampler", "false"); + ConfOpts.shrink_to_fit(); +} + +void ReadALConfig() +{ + FunkinALConfigDefault(); + + #ifdef _WIN32 + + auto path = fs::path{}; + + path = fs::path(al::char_as_u8(GetProcBinary().path)); + if(!path.empty()) + { + path /= L"alsoft.ini"; + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(auto f = fs::ifstream{path}; f.is_open()) + LoadConfigFromFile(f); + } + + if(auto confpath = al::getenv(L"ALSOFT_CONF")) + { + path = *confpath; + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(auto f = fs::ifstream{path}; f.is_open()) + LoadConfigFromFile(f); + } + + #else + + auto path = fs::path{"/etc/openal/alsoft.conf"}; + + path = GetProcBinary().path; + if(!path.empty()) + { + path /= "alsoft.conf"; + + TRACE("Loading config {}...", al::u8_as_char(path.u8string())); + if(auto f = std::ifstream{path}; f.is_open()) + LoadConfigFromFile(f); + } + + if(auto confname = al::getenv("ALSOFT_CONF")) + { + TRACE("Loading config {}...", *confname); + if(auto f = std::ifstream{*confname}; f.is_open()) + LoadConfigFromFile(f); + } + + #endif +} + +auto ConfigValueStr(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) -> std::optional +{ + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) + return val; + return std::nullopt; +} + +auto ConfigValueI32(std::string_view const devName, std::string_view const blockName, + std::string_view const keyName) -> std::optional +{ + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return std::stoi(val, nullptr, 0); + } + catch(std::out_of_range&) { + WARN("Option is out of range of i32: {} = {}", keyName, val); + } + catch(std::exception&) { + WARN("Option is not an i32: {} = {}", keyName, val); + } + + return std::nullopt; +} + +auto ConfigValueU32(std::string_view const devName, std::string_view const blockName, + std::string_view const keyName) -> std::optional +{ + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return gsl::narrow(std::stoul(val, nullptr, 0)); + } + catch(std::out_of_range&) { + WARN("Option is out of range of u32: {} = {}", keyName, val); + } + catch(gsl::narrowing_error&) { + WARN("Option is out of range of u32: {} = {}", keyName, val); + } + catch(std::exception&) { + WARN("Option is not an u32: {} = {}", keyName, val); + } + return std::nullopt; +} + +auto ConfigValueF32(std::string_view const devName, std::string_view const blockName, + std::string_view const keyName) -> std::optional +{ + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return std::stof(val); + } + catch(std::exception&) { + WARN("Option is not a float: {} = {}", keyName, val); + } + return std::nullopt; +} + +auto ConfigValueBool(std::string_view const devName, std::string_view const blockName, + std::string_view const keyName) -> std::optional +{ + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0 + || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0; + } + catch(std::out_of_range&) { + /* If out of range, the value is some non-0 (true) value and it doesn't + * matter that it's too big or small. + */ + return true; + } + catch(std::exception&) { + /* If stoll fails to convert for any other reason, it's some other word + * that's treated as false. + */ + return false; + } + return std::nullopt; +} + +auto GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def) -> bool +{ + if(auto&& val = GetConfigValue(devName, blockName, keyName); !val.empty()) try { + return al::case_compare(val, "on"sv) == 0 || al::case_compare(val, "yes"sv) == 0 + || al::case_compare(val, "true"sv) == 0 || std::stoll(val) != 0; + } + catch(std::out_of_range&) { + return true; + } + catch(std::exception&) { + return false; + } + return def; +} diff --git a/project/lib/custom/openal/include/platforms/android/config_backends.h b/project/lib/custom/openal/include/platforms/android/config_backends.h index 846380f595..e3de1614db 100644 --- a/project/lib/custom/openal/include/platforms/android/config_backends.h +++ b/project/lib/custom/openal/include/platforms/android/config_backends.h @@ -1,3 +1,13 @@ +#ifdef NATIVE_TOOLKIT_HAVE_SDL + +#define HAVE_OPENSL 0 + +#else + +#define HAVE_OPENSL 1 + +#endif + #define HAVE_ALSA 0 #define HAVE_OSS 0 @@ -22,6 +32,4 @@ #define HAVE_COREAUDIO 0 -#define HAVE_OPENSL 1 - #define HAVE_OBOE 0 diff --git a/project/lib/custom/openal/include/platforms/apple/config_backends.h b/project/lib/custom/openal/include/platforms/apple/config_backends.h index 0bb6c9b8e0..5a58db4e98 100644 --- a/project/lib/custom/openal/include/platforms/apple/config_backends.h +++ b/project/lib/custom/openal/include/platforms/apple/config_backends.h @@ -1,3 +1,13 @@ +#ifdef NATIVE_TOOLKIT_HAVE_SDL + +#define HAVE_COREAUDIO 0 + +#else + +#define HAVE_COREAUDIO 1 + +#endif + #define HAVE_ALSA 0 #define HAVE_OSS 0 @@ -20,8 +30,6 @@ #define HAVE_JACK 0 -#define HAVE_COREAUDIO 1 - #define HAVE_OPENSL 0 #define HAVE_OBOE 0 diff --git a/project/lib/custom/openal/include/platforms/linux/config_backends.h b/project/lib/custom/openal/include/platforms/linux/config_backends.h index c2a67ff433..8fde1bce3b 100644 --- a/project/lib/custom/openal/include/platforms/linux/config_backends.h +++ b/project/lib/custom/openal/include/platforms/linux/config_backends.h @@ -1,9 +1,23 @@ -#define HAVE_ALSA 1 +#ifdef NATIVE_TOOLKIT_HAVE_SDL -#define HAVE_OSS 0 +#define HAVE_ALSA 0 + +#define HAVE_PIPEWIRE 0 + +#define HAVE_PULSEAUDIO 0 + +#else + +#define HAVE_ALSA 1 #define HAVE_PIPEWIRE 1 +#define HAVE_PULSEAUDIO 1 + +#endif + +#define HAVE_OSS 0 + #define HAVE_SOLARIS 0 #define HAVE_SNDIO 0 @@ -16,8 +30,6 @@ #define HAVE_PORTAUDIO 0 -#define HAVE_PULSEAUDIO 1 - #define HAVE_JACK 0 #define HAVE_COREAUDIO 0 diff --git a/project/lib/custom/openal/include/platforms/windows/config_backends.h b/project/lib/custom/openal/include/platforms/windows/config_backends.h index 55bf169b75..39ce4b1bad 100644 --- a/project/lib/custom/openal/include/platforms/windows/config_backends.h +++ b/project/lib/custom/openal/include/platforms/windows/config_backends.h @@ -1,3 +1,17 @@ +#ifdef NATIVE_TOOLKIT_HAVE_SDL + +#define HAVE_WASAPI 0 + +#define HAVE_DSOUND 0 + +#else + +#define HAVE_WASAPI 1 + +#define HAVE_DSOUND 1 + +#endif + #define HAVE_ALSA 0 #define HAVE_OSS 0 @@ -8,10 +22,6 @@ #define HAVE_SNDIO 0 -#define HAVE_WASAPI 1 - -#define HAVE_DSOUND 1 - #define HAVE_WINMM 0 #define HAVE_PORTAUDIO 0 diff --git a/project/lib/custom/openal/include/version.h b/project/lib/custom/openal/include/version.h index 46d0fe2c94..2357d51b07 100644 --- a/project/lib/custom/openal/include/version.h +++ b/project/lib/custom/openal/include/version.h @@ -6,4 +6,4 @@ #define ALSOFT_GIT_BRANCH "master" /* Define the hash of the head commit */ -#define ALSOFT_GIT_COMMIT_HASH "1d52120" +#define ALSOFT_GIT_COMMIT_HASH "ae409215" diff --git a/project/lib/dr-libs-files.xml b/project/lib/dr-libs-files.xml new file mode 100644 index 0000000000..94c6524355 --- /dev/null +++ b/project/lib/dr-libs-files.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project/lib/dr_libs b/project/lib/dr_libs new file mode 160000 index 0000000000..fa931f3285 --- /dev/null +++ b/project/lib/dr_libs @@ -0,0 +1 @@ +Subproject commit fa931f3285ced10ace628f7f1ac951e1951e7ea6 diff --git a/project/lib/openal b/project/lib/openal index e9c479eb41..ae4092158e 160000 --- a/project/lib/openal +++ b/project/lib/openal @@ -1 +1 @@ -Subproject commit e9c479eb4190101bc51179afae56fc6dd5d26066 +Subproject commit ae4092158e64b8b6222e76af5d36411c82eebaf2 diff --git a/project/lib/openal-files.xml b/project/lib/openal-files.xml index c021774e52..63f3f00644 100644 --- a/project/lib/openal-files.xml +++ b/project/lib/openal-files.xml @@ -131,13 +131,15 @@
-
+ + + - - - +
-
+ + +
@@ -158,8 +160,6 @@
- -
@@ -184,7 +184,7 @@
- + diff --git a/project/lib/opus b/project/lib/opus new file mode 160000 index 0000000000..2d862ea14b --- /dev/null +++ b/project/lib/opus @@ -0,0 +1 @@ +Subproject commit 2d862ea14b233e5a3f3afaf74d96050691af3cd5 diff --git a/project/lib/opus-files.xml b/project/lib/opus-files.xml new file mode 100644 index 0000000000..7d5934ccb5 --- /dev/null +++ b/project/lib/opus-files.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project/lib/opusfile b/project/lib/opusfile new file mode 160000 index 0000000000..cc04a82f79 --- /dev/null +++ b/project/lib/opusfile @@ -0,0 +1 @@ +Subproject commit cc04a82f7964067da6e3f5dddc7634b833c9402c diff --git a/project/lib/vorbis-files.xml b/project/lib/vorbis-files.xml index 38519189fe..446b41efc8 100644 --- a/project/lib/vorbis-files.xml +++ b/project/lib/vorbis-files.xml @@ -11,12 +11,9 @@ + - - - - @@ -37,10 +34,10 @@ + + - - diff --git a/project/src/ExternalInterface.cpp b/project/src/ExternalInterface.cpp index 3705d40927..9f036eeafb 100644 --- a/project/src/ExternalInterface.cpp +++ b/project/src/ExternalInterface.cpp @@ -16,9 +16,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -315,124 +312,6 @@ namespace lime { } - value lime_audio_load_bytes (value data, value buffer) { - - Resource resource; - Bytes bytes; - - AudioBuffer audioBuffer = AudioBuffer (buffer); - - bytes.Set (data); - resource = Resource (&bytes); - - - if (WAV::Decode (&resource, &audioBuffer)) { - - return audioBuffer.Value (buffer); - - } - - #ifdef LIME_OGG - if (OGG::Decode (&resource, &audioBuffer)) { - - return audioBuffer.Value (buffer); - - } - #endif - - return alloc_null (); - - } - - - HL_PRIM AudioBuffer* HL_NAME(hl_audio_load_bytes) (Bytes* data, AudioBuffer* buffer) { - - Resource resource = Resource (data); - - if (WAV::Decode (&resource, buffer)) { - - return buffer; - - } - - #ifdef LIME_OGG - if (OGG::Decode (&resource, buffer)) { - - return buffer; - - } - #endif - - return 0; - - } - - - value lime_audio_load_file (value data, value buffer) { - - Resource resource; - - AudioBuffer audioBuffer = AudioBuffer (buffer); - - resource = Resource (val_string (data)); - - if (WAV::Decode (&resource, &audioBuffer)) { - - return audioBuffer.Value (buffer); - - } - - #ifdef LIME_OGG - if (OGG::Decode (&resource, &audioBuffer)) { - - return audioBuffer.Value (buffer); - - } - #endif - - return alloc_null (); - - } - - - HL_PRIM AudioBuffer* HL_NAME(hl_audio_load_file) (hl_vstring* data, AudioBuffer* buffer) { - - Resource resource = Resource (data ? hl_to_utf8 ((const uchar*)data->bytes) : NULL); - - if (WAV::Decode (&resource, buffer)) { - - return buffer; - - } - - #ifdef LIME_OGG - if (OGG::Decode (&resource, buffer)) { - - return buffer; - - } - #endif - - return 0; - - } - - - value lime_audio_load (value data, value buffer) { - - if (val_is_string (data)) { - - return lime_audio_load_file (data, buffer); - - } else { - - return lime_audio_load_bytes (data, buffer); - - } - - } - - value lime_bytes_from_data_pointer (double data, int length, value _bytes) { uintptr_t ptr = (uintptr_t)data; @@ -4128,9 +4007,6 @@ namespace lime { DEFINE_PRIME1 (lime_application_quit); DEFINE_PRIME2v (lime_application_set_frame_rate); DEFINE_PRIME1 (lime_application_update); - DEFINE_PRIME2 (lime_audio_load); - DEFINE_PRIME2 (lime_audio_load_bytes); - DEFINE_PRIME2 (lime_audio_load_file); DEFINE_PRIME3 (lime_bytes_from_data_pointer); DEFINE_PRIME1 (lime_bytes_get_data_pointer); DEFINE_PRIME2 (lime_bytes_get_data_pointer_offset); @@ -4328,8 +4204,6 @@ namespace lime { DEFINE_HL_PRIM (_I32, hl_application_quit, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_application_set_frame_rate, _TCFFIPOINTER _F64); DEFINE_HL_PRIM (_BOOL, hl_application_update, _TCFFIPOINTER); - DEFINE_HL_PRIM (_TAUDIOBUFFER, hl_audio_load_bytes, _TBYTES _TAUDIOBUFFER); - DEFINE_HL_PRIM (_TAUDIOBUFFER, hl_audio_load_file, _STRING _TAUDIOBUFFER); DEFINE_HL_PRIM (_TBYTES, hl_bytes_from_data_pointer, _F64 _I32 _TBYTES); DEFINE_HL_PRIM (_F64, hl_bytes_get_data_pointer, _TBYTES); DEFINE_HL_PRIM (_F64, hl_bytes_get_data_pointer_offset, _TBYTES _I32); @@ -4503,6 +4377,12 @@ extern "C" int lime_curl_register_prims (); extern "C" int lime_curl_register_prims () { return 0; } #endif +#ifdef LIME_DRLIBS +extern "C" int lime_drlibs_register_prims (); +#else +extern "C" int lime_drlibs_register_prims () { return 0; } +#endif + #ifdef LIME_HARFBUZZ extern "C" int lime_harfbuzz_register_prims (); #else @@ -4521,6 +4401,12 @@ extern "C" int lime_opengl_register_prims (); extern "C" int lime_opengl_register_prims () { return 0; } #endif +#ifdef LIME_OPUS +extern "C" int lime_opus_register_prims (); +#else +extern "C" int lime_opus_register_prims () { return 0; } +#endif + #ifdef LIME_VORBIS extern "C" int lime_vorbis_register_prims (); #else @@ -4532,9 +4418,11 @@ extern "C" int lime_register_prims () { lime_cairo_register_prims (); lime_curl_register_prims (); + lime_drlibs_register_prims (); lime_harfbuzz_register_prims (); lime_openal_register_prims (); lime_opengl_register_prims (); + lime_opus_register_prims (); lime_vorbis_register_prims (); return 0; diff --git a/project/src/media/AudioBuffer.cpp b/project/src/media/AudioBuffer.cpp deleted file mode 100644 index f295ea62dc..0000000000 --- a/project/src/media/AudioBuffer.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include - - -namespace lime { - - - static int id_bitsPerSample; - static int id_channels; - static int id_data; - static int id_dataFormat; - static int id_sampleRate; - static bool init = false; - - - AudioBuffer::AudioBuffer (value audioBuffer) { - - if (!init) { - - id_bitsPerSample = val_id ("bitsPerSample"); - id_channels = val_id ("channels"); - id_data = val_id ("data"); - id_dataFormat = val_id ("dataFormat"); - id_sampleRate = val_id ("sampleRate"); - init = true; - - } - - if (!val_is_null (audioBuffer)) { - - bitsPerSample = val_int (val_field (audioBuffer, id_bitsPerSample)); - channels = val_int (val_field (audioBuffer, id_channels)); - data = new ArrayBufferView (val_field (audioBuffer, id_data)); - dataFormat = val_int (val_field (audioBuffer, id_dataFormat)); - sampleRate = val_int (val_field (audioBuffer, id_sampleRate)); - - } else { - - bitsPerSample = 0; - channels = 0; - // data = new ArrayBufferView (); - dataFormat = 0; - sampleRate = 0; - - } - - // _value = audioBuffer; - - } - - - AudioBuffer::~AudioBuffer () { - - if (data) { - - delete data; - - } - - } - - - value AudioBuffer::Value () { - - return Value (alloc_empty_object ()); - - } - - - value AudioBuffer::Value (value audioBuffer) { - - if (!init) { - - id_bitsPerSample = val_id ("bitsPerSample"); - id_channels = val_id ("channels"); - id_data = val_id ("data"); - id_dataFormat = val_id ("dataFormat"); - id_sampleRate = val_id ("sampleRate"); - init = true; - - } - - alloc_field (audioBuffer, id_bitsPerSample, alloc_int (bitsPerSample)); - alloc_field (audioBuffer, id_channels, alloc_int (channels)); - alloc_field (audioBuffer, id_data, data ? data->Value (val_field (audioBuffer, id_data)) : alloc_null ()); - alloc_field (audioBuffer, id_dataFormat, alloc_int (dataFormat)); - alloc_field (audioBuffer, id_sampleRate, alloc_int (sampleRate)); - return audioBuffer; - - } - - -} \ No newline at end of file diff --git a/project/src/media/codecs/DrLibsBindings.cpp b/project/src/media/codecs/DrLibsBindings.cpp new file mode 100644 index 0000000000..711d11e0f9 --- /dev/null +++ b/project/src/media/codecs/DrLibsBindings.cpp @@ -0,0 +1,889 @@ +#include +#include +#include +#include +#include +#include + + +namespace lime { + + + static int id_bitsPerSample; + static int id_channels; + static int id_high; + static int id_low; + static int id_sampleRate; + static value infoValue; + static value int64Value; + static vdynamic *hl_infoValue; + static vdynamic *hl_int64Value; + static bool init = false; + + + inline void _initializeDrLibs () { + + if (!init) { + + id_bitsPerSample = val_id ("bitsPerSample"); + id_channels = val_id ("channels"); + id_high = val_id ("high"); + id_low = val_id ("low"); + id_sampleRate = val_id ("sampleRate"); + + infoValue = alloc_empty_object (); + int64Value = alloc_empty_object (); + + value* root = alloc_root (); + *root = infoValue; + + value* root2 = alloc_root (); + *root2 = int64Value; + + init = true; + + } + + } + + + inline void _hl_initializeDrLibs () { + + if (!init) { + + id_bitsPerSample = hl_hash_utf8 ("bitsPerSample"); + id_channels = hl_hash_utf8 ("channels"); + id_high = hl_hash_utf8 ("high"); + id_low = hl_hash_utf8 ("low"); + id_sampleRate = hl_hash_utf8 ("sampleRate"); + + hl_infoValue = (vdynamic*)hl_alloc_dynobj (); + hl_int64Value = (vdynamic*)hl_alloc_dynobj (); + + hl_add_root(&hl_infoValue); + hl_add_root(&hl_int64Value); + + init = true; + + } + + } + + + value allocDrFlacInt64 (drflac_int64 val) { + + drflac_int32 low = val; + drflac_int32 high = (val >> 32); + + _initializeDrLibs (); + + alloc_field (int64Value, id_low, alloc_int (low)); + alloc_field (int64Value, id_high, alloc_int (high)); + + return int64Value; + + } + + + vdynamic* hl_allocDrFlacInt64 (drflac_int64 val) { + + drflac_int32 low = val; + drflac_int32 high = (val >> 32); + + _hl_initializeDrLibs (); + + hl_dyn_seti (hl_int64Value, id_low, &hlt_i32, low); + hl_dyn_seti (hl_int64Value, id_high, &hlt_i32, high); + + return hl_int64Value; + + } + + + value allocDrMp3Int64 (drmp3_int64 val) { + + drmp3_int32 low = val; + drmp3_int32 high = (val >> 32); + + _initializeDrLibs (); + + alloc_field (int64Value, id_low, alloc_int (low)); + alloc_field (int64Value, id_high, alloc_int (high)); + + return int64Value; + + } + + + vdynamic* hl_allocDrMp3Int64 (drmp3_int64 val) { + + drmp3_int32 low = val; + drmp3_int32 high = (val >> 32); + + _hl_initializeDrLibs (); + + hl_dyn_seti (hl_int64Value, id_low, &hlt_i32, low); + hl_dyn_seti (hl_int64Value, id_high, &hlt_i32, high); + + return hl_int64Value; + + } + + + value allocDrWavInt64 (drwav_int64 val) { + + drwav_int32 low = val; + drwav_int32 high = (val >> 32); + + _initializeDrLibs (); + + alloc_field (int64Value, id_low, alloc_int (low)); + alloc_field (int64Value, id_high, alloc_int (high)); + + return int64Value; + + } + + + vdynamic* hl_allocDrWavInt64 (drwav_int64 val) { + + drwav_int32 low = val; + drwav_int32 high = (val >> 32); + + _hl_initializeDrLibs (); + + hl_dyn_seti (hl_int64Value, id_low, &hlt_i32, low); + hl_dyn_seti (hl_int64Value, id_high, &hlt_i32, high); + + return hl_int64Value; + + } + + + void lime_drlibs_flac_close (value flac); + HL_PRIM void HL_NAME(hl_drlibs_flac_close) (HL_CFFIPointer* flac); + + + void gc_drflac (value flac) { + + lime_drlibs_flac_close (flac); + + } + + + void hl_gc_drflac (HL_CFFIPointer* flac) { + + lime_hl_drlibs_flac_close (flac); + + } + + + void lime_drlibs_flac_close (value flac) { + + if (!val_is_null (flac)) { + + drflac* pFlac = (drflac*)(uintptr_t)val_data (flac); + val_gc (flac, 0); + DrFlac::Close (pFlac); + + } + + } + + + HL_PRIM void HL_NAME(hl_drlibs_flac_close) (HL_CFFIPointer* flac) { + + if (flac) { + + drflac* pFlac = (drflac*)(uintptr_t)flac->ptr; + flac->finalizer = 0; + DrFlac::Close (pFlac); + + } + + } + + + int lime_drlibs_flac_decode (value flac, value buffer, int position, int length, int word) { + + if (val_is_null (buffer)) { + return 0; + } + + drflac* pFlac = (drflac*)(uintptr_t)val_data (flac); + + Bytes bytes; + bytes.Set (buffer); + + if (word == 4) + { + + return drflac_read_pcm_frames_s32 (pFlac, (int)(length / pFlac->channels / 4), (drflac_int32 *)(bytes.b + position)) * 4 * pFlac->channels; + + } + else if (word == 2) + { + + return drflac_read_pcm_frames_s16 (pFlac, (int)(length / pFlac->channels / 2), (drflac_int16 *)(bytes.b + position)) * 2 * pFlac->channels; + + } + else + { + + return 0; + + } + + } + + + HL_PRIM int HL_NAME(hl_drlibs_flac_decode) (HL_CFFIPointer* flac, Bytes* buffer, int position, int length, int word) { + + if (!buffer) { + return 0; + } + + drflac* pFlac = (drflac*)(uintptr_t)flac->ptr; + + if (word == 4) + { + + return drflac_read_pcm_frames_s32 (pFlac, (int)(length / pFlac->channels / 4), (drflac_int32 *)(buffer->b + position)) * 4 * pFlac->channels; + + } + else if (word == 2) + { + + return drflac_read_pcm_frames_s16 (pFlac, (int)(length / pFlac->channels / 2), (drflac_int16 *)(buffer->b + position)) * 2 * pFlac->channels; + + } + else + { + + return 0; + + } + + } + + + value lime_drlibs_flac_from_bytes (value data) { + + Bytes bytes; + bytes.Set (data); + + drflac* pFlac = DrFlac::FromBytes (&bytes); + + if (pFlac) return CFFIPointer ((void*)(uintptr_t)pFlac, gc_drflac); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_drlibs_flac_from_bytes) (Bytes* data) { + + drflac* pFlac = DrFlac::FromBytes (data); + + if (pFlac) return HLCFFIPointer ((void*)(uintptr_t)pFlac, (hl_finalizer)hl_gc_drflac); + return NULL; + + } + + + value lime_drlibs_flac_from_file (HxString path) { + + drflac* pFlac = DrFlac::FromFile (path.c_str ()); + + if (pFlac) return CFFIPointer ((void*)(uintptr_t)pFlac, gc_drflac); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_drlibs_flac_from_file) (hl_vstring* path) { + + drflac* pFlac = DrFlac::FromFile (path ? hl_to_utf8 (path->bytes) : NULL); + + if (pFlac) return HLCFFIPointer ((void*)(uintptr_t)pFlac, (hl_finalizer)hl_gc_drflac); + return NULL; + + } + + + value lime_drlibs_flac_info (value flac) { + + drflac* pFlac = (drflac*)(uintptr_t)val_data (flac); + + _initializeDrLibs (); + + alloc_field (infoValue, id_bitsPerSample, alloc_int (pFlac->bitsPerSample)); + alloc_field (infoValue, id_channels, alloc_int (pFlac->channels)); + alloc_field (infoValue, id_sampleRate, alloc_int (pFlac->sampleRate)); + + return infoValue; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_flac_info) (HL_CFFIPointer* flac) { + + drflac* pFlac = (drflac*)(uintptr_t)flac->ptr; + + _initializeDrLibs (); + + hl_dyn_seti (hl_infoValue, id_bitsPerSample, &hlt_i32, pFlac->bitsPerSample); + hl_dyn_seti (hl_infoValue, id_channels, &hlt_i32, pFlac->channels); + hl_dyn_seti (hl_infoValue, id_sampleRate, &hlt_i32, pFlac->sampleRate); + + return hl_infoValue; + + } + + + int lime_drlibs_flac_seek (value flac, value posLow, value posHigh) { + + drflac* pFlac = (drflac*)(uintptr_t)val_data (flac); + drflac_uint64 pos = ((drflac_uint64)val_number (posHigh) << 32) | (drflac_uint64)val_number (posLow); + return drflac_seek_to_pcm_frame (pFlac, pos); + + } + + + HL_PRIM int HL_NAME(hl_drlibs_flac_seek) (HL_CFFIPointer* flac, int posLow, int posHigh) { + + drflac* pFlac = (drflac*)(uintptr_t)flac->ptr; + drflac_uint64 pos = ((drflac_uint64)posHigh << 32) | (drflac_uint64)posLow; + return drflac_seek_to_pcm_frame (pFlac, pos); + + } + + + value lime_drlibs_flac_tell (value flac) { + + drflac* pFlac = (drflac*)(uintptr_t)val_data (flac); + return allocDrFlacInt64 ((drflac_int64)pFlac->currentPCMFrame); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_flac_tell) (HL_CFFIPointer* flac) { + + drflac* pFlac = (drflac*)(uintptr_t)flac->ptr; + return hl_allocDrFlacInt64 ((drflac_int64)pFlac->currentPCMFrame); + + } + + + value lime_drlibs_flac_total (value flac) { + + drflac* pFlac = (drflac*)(uintptr_t)val_data (flac); + return allocDrFlacInt64 ((drflac_int64)pFlac->totalPCMFrameCount); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_flac_total) (HL_CFFIPointer* flac) { + + drflac* pFlac = (drflac*)(uintptr_t)flac->ptr; + return hl_allocDrFlacInt64 ((drflac_int64)pFlac->totalPCMFrameCount); + + } + + + void lime_drlibs_mp3_uninit (value mp3); + HL_PRIM void HL_NAME(hl_drlibs_mp3_uninit) (HL_CFFIPointer* mp3); + + + void gc_drmp3 (value mp3) { + + lime_drlibs_mp3_uninit (mp3); + + } + + + void hl_gc_drmp3 (HL_CFFIPointer* mp3) { + + lime_hl_drlibs_mp3_uninit (mp3); + + } + + + int lime_drlibs_mp3_decode (value mp3, value buffer, int position, int length) { + + if (val_is_null (buffer)) { + return 0; + } + + drmp3* pMp3 = (drmp3*)(uintptr_t)val_data (mp3); + + Bytes bytes; + bytes.Set (buffer); + + return drmp3_read_pcm_frames_s16 (pMp3, (int)(length / pMp3->channels / 2), (drmp3_int16 *)(bytes.b + position)) * 2 * pMp3->channels; + + } + + + HL_PRIM int HL_NAME(hl_drlibs_mp3_decode) (HL_CFFIPointer* mp3, Bytes* buffer, int position, int length) { + + if (!buffer) { + return 0; + } + + drmp3* pMp3 = (drmp3*)(uintptr_t)mp3->ptr; + + return drmp3_read_pcm_frames_s16 (pMp3, (int)(length / pMp3->channels / 2), (drmp3_int16 *)(buffer->b + position)) * 2 * pMp3->channels; + + } + + + value lime_drlibs_mp3_from_bytes (value data) { + + Bytes bytes; + bytes.Set (data); + + drmp3* pMp3 = DrMp3::FromBytes (&bytes); + + if (pMp3) return CFFIPointer ((void*)(uintptr_t)pMp3, gc_drmp3); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_drlibs_mp3_from_bytes) (Bytes* data) { + + drmp3* pMp3 = DrMp3::FromBytes (data); + + if (pMp3) return HLCFFIPointer ((void*)(uintptr_t)pMp3, (hl_finalizer)hl_gc_drmp3); + return NULL; + + } + + + value lime_drlibs_mp3_from_file (HxString path) { + + drmp3* pMp3 = DrMp3::FromFile (path.c_str ()); + + if (pMp3) return CFFIPointer ((void*)(uintptr_t)pMp3, gc_drmp3); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_drlibs_mp3_from_file) (hl_vstring* path) { + + drmp3* pMp3 = DrMp3::FromFile (path ? hl_to_utf8 (path->bytes) : NULL); + + if (pMp3) return HLCFFIPointer ((void*)(uintptr_t)pMp3, (hl_finalizer)hl_gc_drmp3); + return NULL; + + } + + + value lime_drlibs_mp3_info (value mp3) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)val_data (mp3); + + _initializeDrLibs (); + + alloc_field (infoValue, id_bitsPerSample, alloc_int (16)); + alloc_field (infoValue, id_channels, alloc_int (pMp3->channels)); + alloc_field (infoValue, id_sampleRate, alloc_int (pMp3->sampleRate)); + + return infoValue; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_mp3_info) (HL_CFFIPointer* mp3) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)mp3->ptr; + + _initializeDrLibs (); + + hl_dyn_seti (hl_infoValue, id_bitsPerSample, &hlt_i32, 16); + hl_dyn_seti (hl_infoValue, id_channels, &hlt_i32, pMp3->channels); + hl_dyn_seti (hl_infoValue, id_sampleRate, &hlt_i32, pMp3->sampleRate); + + return hl_infoValue; + + } + + + int lime_drlibs_mp3_seek (value mp3, value posLow, value posHigh) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)val_data (mp3); + drmp3_uint64 pos = ((drmp3_uint64)val_number (posHigh) << 32) | (drmp3_uint64)val_number (posLow); + return drmp3_seek_to_pcm_frame (pMp3, pos); + + } + + + HL_PRIM int HL_NAME(hl_drlibs_mp3_seek) (HL_CFFIPointer* mp3, int posLow, int posHigh) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)mp3->ptr; + drmp3_uint64 pos = ((drmp3_uint64)posHigh << 32) | (drmp3_uint64)posLow; + return drmp3_seek_to_pcm_frame (pMp3, pos); + + } + + + value lime_drlibs_mp3_tell (value mp3) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)val_data (mp3); + return allocDrMp3Int64 ((drmp3_int64)pMp3->currentPCMFrame); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_mp3_tell) (HL_CFFIPointer* mp3) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)mp3->ptr; + return hl_allocDrMp3Int64 ((drmp3_int64)pMp3->currentPCMFrame); + + } + + + value lime_drlibs_mp3_total (value mp3) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)val_data (mp3); + return allocDrMp3Int64 ((drmp3_int64)drmp3_get_pcm_frame_count (pMp3)); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_mp3_total) (HL_CFFIPointer* mp3) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)mp3->ptr; + return hl_allocDrMp3Int64 ((drmp3_int64)drmp3_get_pcm_frame_count (pMp3)); + + } + + + void lime_drlibs_mp3_uninit (value mp3) { + + if (!val_is_null (mp3)) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)val_data (mp3); + val_gc (mp3, 0); + DrMp3::Close (pMp3); + + } + + } + + + HL_PRIM void HL_NAME(hl_drlibs_mp3_uninit) (HL_CFFIPointer* mp3) { + + if (mp3) { + + drmp3* pMp3 = (drmp3*)(uintptr_t)mp3->ptr; + mp3->finalizer = 0; + DrMp3::Close (pMp3); + + } + + } + + + void lime_drlibs_wav_uninit (value wav); + HL_PRIM void HL_NAME(hl_drlibs_wav_uninit) (HL_CFFIPointer* wav); + + + void gc_drwav (value wav) { + + lime_drlibs_wav_uninit (wav); + + } + + + void hl_gc_drwav (HL_CFFIPointer* wav) { + + lime_hl_drlibs_wav_uninit (wav); + + } + + + int lime_drlibs_wav_decode (value wav, value buffer, int position, int length, int word) { + + if (val_is_null (buffer)) { + return 0; + } + + drwav* pWav = (drwav*)(uintptr_t)val_data (wav); + + Bytes bytes; + bytes.Set (buffer); + + if (word == 4) + { + + return drwav_read_pcm_frames_s32 (pWav, (int)(length / pWav->channels / 4), (drwav_int32 *)(bytes.b + position)) * 4 * pWav->channels; + + } + else if (word == 2) + { + + return drwav_read_pcm_frames_s16 (pWav, (int)(length / pWav->channels / 2), (drwav_int16 *)(bytes.b + position)) * 2 * pWav->channels; + + } + else + { + + return 0; + + } + + } + + + HL_PRIM int HL_NAME(hl_drlibs_wav_decode) (HL_CFFIPointer* wav, Bytes* buffer, int position, int length, int word) { + + if (!buffer) { + return 0; + } + + drwav* pWav = (drwav*)(uintptr_t)wav->ptr; + + if (word == 4) + { + + return drwav_read_pcm_frames_s32 (pWav, (int)(length / pWav->channels / 4), (drwav_int32 *)(buffer->b + position)) * 4 * pWav->channels; + + } + else if (word == 2) + { + + return drwav_read_pcm_frames_s16 (pWav, (int)(length / pWav->channels / 2), (drwav_int16 *)(buffer->b + position)) * 2 * pWav->channels; + + } + else + { + + return 0; + + } + + } + + + value lime_drlibs_wav_from_bytes (value data) { + + Bytes bytes; + bytes.Set (data); + + drwav* pWav = DrWav::FromBytes (&bytes); + + if (pWav) return CFFIPointer ((void*)(uintptr_t)pWav, gc_drwav); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_drlibs_wav_from_bytes) (Bytes* data) { + + drwav* pWav = DrWav::FromBytes (data); + + if (pWav) return HLCFFIPointer ((void*)(uintptr_t)pWav, (hl_finalizer)hl_gc_drwav); + return NULL; + + } + + + value lime_drlibs_wav_from_file (HxString path) { + + drwav* pWav = DrWav::FromFile (path.c_str ()); + + if (pWav) return CFFIPointer ((void*)(uintptr_t)pWav, gc_drwav); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_drlibs_wav_from_file) (hl_vstring* path) { + + drwav* pWav = DrWav::FromFile (path ? hl_to_utf8 (path->bytes) : NULL); + + if (pWav) return HLCFFIPointer ((void*)(uintptr_t)pWav, (hl_finalizer)hl_gc_drwav); + return NULL; + + } + + + value lime_drlibs_wav_info (value wav) { + + drwav* pWav = (drwav*)(uintptr_t)val_data (wav); + + _initializeDrLibs (); + + alloc_field (infoValue, id_bitsPerSample, alloc_int (pWav->bitsPerSample)); + alloc_field (infoValue, id_channels, alloc_int (pWav->channels)); + alloc_field (infoValue, id_sampleRate, alloc_int (pWav->sampleRate)); + + return infoValue; + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_wav_info) (HL_CFFIPointer* wav) { + + drwav* pWav = (drwav*)(uintptr_t)wav->ptr; + + _initializeDrLibs (); + + hl_dyn_seti (hl_infoValue, id_bitsPerSample, &hlt_i32, pWav->bitsPerSample); + hl_dyn_seti (hl_infoValue, id_channels, &hlt_i32, pWav->channels); + hl_dyn_seti (hl_infoValue, id_sampleRate, &hlt_i32, pWav->sampleRate); + + return hl_infoValue; + + } + + + int lime_drlibs_wav_seek (value wav, value posLow, value posHigh) { + + drwav* pWav = (drwav*)(uintptr_t)val_data (wav); + drwav_uint64 pos = ((drwav_uint64)val_number (posHigh) << 32) | (drwav_uint64)val_number (posLow); + return drwav_seek_to_pcm_frame (pWav, pos); + + } + + + HL_PRIM int HL_NAME(hl_drlibs_wav_seek) (HL_CFFIPointer* wav, int posLow, int posHigh) { + + drwav* pWav = (drwav*)(uintptr_t)wav->ptr; + drwav_uint64 pos = ((drwav_uint64)posHigh << 32) | (drwav_uint64)posLow; + return drwav_seek_to_pcm_frame (pWav, pos); + + } + + + value lime_drlibs_wav_tell (value wav) { + + drwav* pWav = (drwav*)(uintptr_t)val_data (wav); + drwav_uint64 cursor; + if (drwav_get_cursor_in_pcm_frames(pWav, &cursor) == DRWAV_SUCCESS) return allocDrWavInt64 ((drwav_int64)cursor); + else return allocDrWavInt64 (0); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_wav_tell) (HL_CFFIPointer* wav) { + + drwav* pWav = (drwav*)(uintptr_t)wav->ptr; + drwav_uint64 cursor; + if (drwav_get_cursor_in_pcm_frames(pWav, &cursor) == DRWAV_SUCCESS) return hl_allocDrWavInt64 ((drwav_int64)cursor); + else return hl_allocDrWavInt64 (0); + + } + + + value lime_drlibs_wav_total (value wav) { + + drwav* pWav = (drwav*)(uintptr_t)val_data (wav); + drwav_uint64 length; + if (drwav_get_length_in_pcm_frames(pWav, &length) == DRWAV_SUCCESS) return allocDrWavInt64 ((drwav_int64)length); + else return allocDrWavInt64 (0); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_drlibs_wav_total) (HL_CFFIPointer* wav) { + + drwav* pWav = (drwav*)(uintptr_t)wav->ptr; + drwav_uint64 length; + if (drwav_get_length_in_pcm_frames(pWav, &length) == DRWAV_SUCCESS) return hl_allocDrWavInt64 ((drwav_int64)length); + else return hl_allocDrWavInt64 (0); + + } + + + void lime_drlibs_wav_uninit (value wav) { + + if (!val_is_null (wav)) { + + drwav* pWav = (drwav*)(uintptr_t)val_data (wav); + val_gc (wav, 0); + DrWav::Close (pWav); + + } + + } + + + HL_PRIM void HL_NAME(hl_drlibs_wav_uninit) (HL_CFFIPointer* wav) { + + if (wav) { + + drwav* pWav = (drwav*)(uintptr_t)wav->ptr; + wav->finalizer = 0; + DrWav::Close (pWav); + + } + + } + + + DEFINE_PRIME1v (lime_drlibs_flac_close); + DEFINE_PRIME5 (lime_drlibs_flac_decode); + DEFINE_PRIME1 (lime_drlibs_flac_from_bytes); + DEFINE_PRIME1 (lime_drlibs_flac_from_file); + DEFINE_PRIME1 (lime_drlibs_flac_info); + DEFINE_PRIME3 (lime_drlibs_flac_seek); + DEFINE_PRIME1 (lime_drlibs_flac_tell); + DEFINE_PRIME1 (lime_drlibs_flac_total); + DEFINE_PRIME4 (lime_drlibs_mp3_decode); + DEFINE_PRIME1 (lime_drlibs_mp3_from_bytes); + DEFINE_PRIME1 (lime_drlibs_mp3_from_file); + DEFINE_PRIME1 (lime_drlibs_mp3_info); + DEFINE_PRIME3 (lime_drlibs_mp3_seek); + DEFINE_PRIME1 (lime_drlibs_mp3_tell); + DEFINE_PRIME1 (lime_drlibs_mp3_total); + DEFINE_PRIME1v (lime_drlibs_mp3_uninit); + DEFINE_PRIME5 (lime_drlibs_wav_decode); + DEFINE_PRIME1 (lime_drlibs_wav_from_bytes); + DEFINE_PRIME1 (lime_drlibs_wav_from_file); + DEFINE_PRIME1 (lime_drlibs_wav_info); + DEFINE_PRIME3 (lime_drlibs_wav_seek); + DEFINE_PRIME1 (lime_drlibs_wav_tell); + DEFINE_PRIME1 (lime_drlibs_wav_total); + DEFINE_PRIME1v (lime_drlibs_wav_uninit); + + + #define _TBYTES _OBJ (_I32 _BYTES) + #define _TCFFIPOINTER _DYN + + DEFINE_HL_PRIM (_VOID, hl_drlibs_flac_close, _TCFFIPOINTER); + DEFINE_HL_PRIM (_I32, hl_drlibs_flac_decode, _TCFFIPOINTER _TBYTES _I32 _I32 _I32); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_drlibs_flac_from_bytes, _TBYTES); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_drlibs_flac_from_file, _STRING); + DEFINE_HL_PRIM (_DYN, hl_drlibs_flac_info, _TCFFIPOINTER); + DEFINE_HL_PRIM (_I32, hl_drlibs_flac_seek, _TCFFIPOINTER _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_drlibs_flac_tell, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_drlibs_flac_total, _TCFFIPOINTER); + DEFINE_HL_PRIM (_I32, hl_drlibs_mp3_decode, _TCFFIPOINTER _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_drlibs_mp3_from_bytes, _TBYTES); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_drlibs_mp3_from_file, _STRING); + DEFINE_HL_PRIM (_DYN, hl_drlibs_mp3_info, _TCFFIPOINTER); + DEFINE_HL_PRIM (_I32, hl_drlibs_mp3_seek, _TCFFIPOINTER _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_drlibs_mp3_tell, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_drlibs_mp3_total, _TCFFIPOINTER); + DEFINE_HL_PRIM (_VOID, hl_drlibs_mp3_uninit, _TCFFIPOINTER); + DEFINE_HL_PRIM (_I32, hl_drlibs_wav_decode, _TCFFIPOINTER _TBYTES _I32 _I32 _I32); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_drlibs_wav_from_bytes, _TBYTES); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_drlibs_wav_from_file, _STRING); + DEFINE_HL_PRIM (_DYN, hl_drlibs_wav_info, _TCFFIPOINTER); + DEFINE_HL_PRIM (_I32, hl_drlibs_wav_seek, _TCFFIPOINTER _I32 _I32); + DEFINE_HL_PRIM (_DYN, hl_drlibs_wav_tell, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_drlibs_wav_total, _TCFFIPOINTER); + DEFINE_HL_PRIM (_VOID, hl_drlibs_wav_uninit, _TCFFIPOINTER); + + +} + + +extern "C" int lime_drlibs_register_prims () { + + return 0; + +} \ No newline at end of file diff --git a/project/src/media/codecs/OpusBindings.cpp b/project/src/media/codecs/OpusBindings.cpp new file mode 100644 index 0000000000..5b238a930a --- /dev/null +++ b/project/src/media/codecs/OpusBindings.cpp @@ -0,0 +1,378 @@ +#include +#include +#include +#include + + +namespace lime { + + + static int id_high; + static int id_low; + static value infoValue; + static value int64Value; + static vdynamic *hl_int64Value; + static bool init = false; + + + inline void _initializeOpus () { + + if (!init) { + + id_high = val_id ("high"); + id_low = val_id ("low"); + + int64Value = alloc_empty_object (); + + value* root = alloc_root (); + *root = int64Value; + + init = true; + + } + + } + + + inline void _hl_initializeOpus () { + + if (!init) { + + id_high = hl_hash_utf8 ("high"); + id_low = hl_hash_utf8 ("low"); + + hl_int64Value = (vdynamic*)hl_alloc_dynobj (); + + hl_add_root(&hl_int64Value); + + init = true; + + } + + } + + + value allocOpusInt64 (opus_int64 val) { + + opus_int32 low = val; + opus_int32 high = (val >> 32); + + _initializeOpus (); + + alloc_field (int64Value, id_low, alloc_int (low)); + alloc_field (int64Value, id_high, alloc_int (high)); + + return int64Value; + + } + + + vdynamic* hl_allocOpusInt64 (opus_int64 val) { + + opus_int32 low = val; + opus_int32 high = (val >> 32); + + _hl_initializeOpus (); + + hl_dyn_seti (hl_int64Value, id_low, &hlt_i32, low); + hl_dyn_seti (hl_int64Value, id_high, &hlt_i32, high); + + return hl_int64Value; + + } + + + void lime_opus_file_free (value opusFile); + HL_PRIM void HL_NAME(hl_opus_file_free) (HL_CFFIPointer* opusFile); + + + void gc_opus_file (value opusFile) { + + lime_opus_file_free (opusFile); + + } + + + void hl_gc_opus_file (HL_CFFIPointer* opusFile) { + + lime_hl_opus_file_free (opusFile); + + } + + + int lime_opus_file_channel_count (value opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)val_data (opusFile); + return op_channel_count (of, -1); + + } + + + HL_PRIM int HL_NAME(hl_opus_file_channel_count) (HL_CFFIPointer* opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)opusFile->ptr; + return op_channel_count (of, -1); + + } + + + int lime_opus_file_decode (value opusFile, value buffer, int position, int length) { + + if (val_is_null (buffer)) { + return 0; + } + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)val_data (opusFile); + + Bytes bytes; + bytes.Set (buffer); + + length >>= 1; + + opus_int16* data = (opus_int16*)(bytes.b + position); + int read = 0; + + while (read < length) { + + int result = op_read (of, data, length - read, NULL); + + if (result != OP_HOLE) { + + if (result <= OP_EREAD) { + + return 0; + + } + else if (result == 0) { + + break; + + } + else { + + result *= op_channel_count (of, -1); + read += result; + data += result; + + } + + } + + } + + return read << 1; + + } + + + HL_PRIM int HL_NAME(hl_opus_file_decode) (HL_CFFIPointer* opusFile, Bytes* buffer, int offset, int samples) { + + if (!buffer) { + return 0; + } + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)opusFile->ptr; + + opus_int16* data = (opus_int16*)(buffer->b + offset); + int read = 0; + + while (read < samples) { + + int result = op_read (of, data, samples, NULL); + + if (result != OP_HOLE) { + + if (result <= OP_EREAD) { + + return 0; + + } + else if (result == 0) { + + break; + + } + else { + + read += result; + data += result; + samples -= result; + + } + } + } + + return read; + } + + + void lime_opus_file_free (value opusFile) { + + if (!val_is_null (opusFile)) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)val_data (opusFile); + val_gc (opusFile, 0); + op_free (of); + + } + + } + + + HL_PRIM void HL_NAME(hl_opus_file_free) (HL_CFFIPointer* opusFile) { + + if (opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)opusFile->ptr; + opusFile->finalizer = 0; + op_free (of); + + } + + } + + + value lime_opus_file_from_bytes (value data) { + + Bytes bytes; + bytes.Set (data); + + OggOpusFile* of = OpusFile::FromBytes (&bytes); + + if (of) return CFFIPointer ((void*)(uintptr_t)of, gc_opus_file); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_opus_file_from_bytes) (Bytes* data) { + + OggOpusFile* of = OpusFile::FromBytes (data); + + if (of) return HLCFFIPointer ((void*)(uintptr_t)of, (hl_finalizer)hl_gc_opus_file); + return NULL; + + } + + + value lime_opus_file_from_file (HxString path) { + + OggOpusFile* of = OpusFile::FromFile (path.c_str ()); + + if (of) return CFFIPointer ((void*)(uintptr_t)of, gc_opus_file); + return alloc_null (); + + } + + + HL_PRIM HL_CFFIPointer* HL_NAME(hl_opus_file_from_file) (hl_vstring* path) { + + OggOpusFile* of = OpusFile::FromFile (path ? hl_to_utf8 (path->bytes) : NULL); + + if (of) return HLCFFIPointer ((void*)(uintptr_t)of, (hl_finalizer)hl_gc_opus_file); + return NULL; + + } + + + int lime_opus_file_seek (value opusFile, value posLow, value posHigh) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)val_data (opusFile); + opus_int64 pos = ((opus_int64)val_number (posHigh) << 32) | (opus_int64)val_number (posLow); + if (pos > 0) return op_pcm_seek (of, pos); + else return op_raw_seek (of, 0); + + } + + + HL_PRIM int HL_NAME(hl_opus_file_seek) (HL_CFFIPointer* opusFile, int posLow, int posHigh) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)opusFile->ptr; + opus_int64 pos = ((opus_int64)posHigh << 32) | (opus_int64)posLow; + if (pos > 0) return op_pcm_seek (of, pos); + else return op_raw_seek (of, 0); + + } + + + bool lime_opus_file_seekable (value opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)val_data (opusFile); + return op_seekable (of); + + } + + + HL_PRIM bool HL_NAME(hl_opus_file_seekable) (HL_CFFIPointer* opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)opusFile->ptr; + return op_seekable (of); + + } + + + value lime_opus_file_tell (value opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)val_data (opusFile); + return allocOpusInt64 (op_pcm_tell (of)); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_opus_file_tell) (HL_CFFIPointer* opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)opusFile->ptr; + return hl_allocOpusInt64 (op_pcm_tell (of)); + + } + + + value lime_opus_file_total (value opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)val_data (opusFile); + return allocOpusInt64 (op_pcm_total (of, -1)); + + } + + + HL_PRIM vdynamic* HL_NAME(hl_opus_file_total) (HL_CFFIPointer* opusFile) { + + OggOpusFile* of = (OggOpusFile*)(uintptr_t)opusFile->ptr; + return hl_allocOpusInt64 (op_pcm_total (of, -1)); + + } + + + DEFINE_PRIME1 (lime_opus_file_channel_count); + DEFINE_PRIME4 (lime_opus_file_decode); + DEFINE_PRIME1v (lime_opus_file_free); + DEFINE_PRIME1 (lime_opus_file_from_bytes); + DEFINE_PRIME1 (lime_opus_file_from_file); + DEFINE_PRIME3 (lime_opus_file_seek); + DEFINE_PRIME1 (lime_opus_file_seekable); + DEFINE_PRIME1 (lime_opus_file_tell); + DEFINE_PRIME1 (lime_opus_file_total); + + + #define _TBYTES _OBJ (_I32 _BYTES) + #define _TCFFIPOINTER _DYN + + DEFINE_HL_PRIM (_I32, hl_opus_file_channel_count, _TCFFIPOINTER); + DEFINE_HL_PRIM (_I32, hl_opus_file_decode, _TCFFIPOINTER _TBYTES _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_opus_file_free, _TCFFIPOINTER); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_opus_file_from_bytes, _TBYTES); + DEFINE_HL_PRIM (_TCFFIPOINTER, hl_opus_file_from_file, _STRING); + DEFINE_HL_PRIM (_I32, hl_opus_file_seek, _TCFFIPOINTER _I32 _I32); + DEFINE_HL_PRIM (_BOOL, hl_opus_file_seekable, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_opus_file_tell, _TCFFIPOINTER); + DEFINE_HL_PRIM (_DYN, hl_opus_file_total, _TCFFIPOINTER); + + +} + + +extern "C" int lime_opus_register_prims () { + + return 0; + +} \ No newline at end of file diff --git a/project/src/media/codecs/vorbis/VorbisBindings.cpp b/project/src/media/codecs/VorbisBindings.cpp similarity index 90% rename from project/src/media/codecs/vorbis/VorbisBindings.cpp rename to project/src/media/codecs/VorbisBindings.cpp index 02353086b5..a77fcddc5e 100644 --- a/project/src/media/codecs/vorbis/VorbisBindings.cpp +++ b/project/src/media/codecs/VorbisBindings.cpp @@ -7,6 +7,13 @@ namespace lime { + #ifdef HXCPP_BIG_ENDIAN + #define BUFFER_READ_TYPE 1 + #else + #define BUFFER_READ_TYPE 0 + #endif + + static int id_bitrateUpper; static int id_bitrateNominal; static int id_bitrateLower; @@ -91,7 +98,7 @@ namespace lime { } - value allocInt64 (ogg_int64_t val) { + value allocVorbisInt64 (ogg_int64_t val) { ogg_int32_t low = val; ogg_int32_t high = (val >> 32); @@ -106,7 +113,7 @@ namespace lime { } - vdynamic* hl_allocInt64 (ogg_int64_t val) { + vdynamic* hl_allocVorbisInt64 (ogg_int64_t val) { ogg_int32_t low = val; ogg_int32_t high = (val >> 32); @@ -345,7 +352,8 @@ namespace lime { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)val_data (vorbisFile); ogg_int64_t pos = ((ogg_int64_t)val_number (posHigh) << 32) | (ogg_int64_t)val_number (posLow); - return ov_pcm_seek (file, pos); + if (pos > 0) return ov_pcm_seek (file, pos); + else return ov_raw_seek (file, 0); } @@ -416,7 +424,7 @@ namespace lime { value lime_vorbis_file_pcm_tell (value vorbisFile) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)val_data (vorbisFile); - return allocInt64 (ov_pcm_tell (file)); + return allocVorbisInt64 (ov_pcm_tell (file)); } @@ -424,7 +432,7 @@ namespace lime { HL_PRIM vdynamic* HL_NAME(hl_vorbis_file_pcm_tell) (HL_CFFIPointer* vorbisFile) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)vorbisFile->ptr; - return hl_allocInt64 (ov_pcm_tell (file)); + return hl_allocVorbisInt64 (ov_pcm_tell (file)); } @@ -432,7 +440,7 @@ namespace lime { value lime_vorbis_file_pcm_total (value vorbisFile, int bitstream) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)val_data (vorbisFile); - return allocInt64 (ov_pcm_total (file, bitstream)); + return allocVorbisInt64 (ov_pcm_total (file, bitstream)); } @@ -440,7 +448,7 @@ namespace lime { HL_PRIM vdynamic* HL_NAME(hl_vorbis_file_pcm_total) (HL_CFFIPointer* vorbisFile, int bitstream) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)vorbisFile->ptr; - return hl_allocInt64 (ov_pcm_total (file, bitstream)); + return hl_allocVorbisInt64 (ov_pcm_total (file, bitstream)); } @@ -484,7 +492,7 @@ namespace lime { value lime_vorbis_file_raw_tell (value vorbisFile) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)val_data (vorbisFile); - return allocInt64 (ov_raw_tell (file)); + return allocVorbisInt64 (ov_raw_tell (file)); } @@ -492,7 +500,7 @@ namespace lime { HL_PRIM vdynamic* HL_NAME(hl_vorbis_file_raw_tell) (HL_CFFIPointer* vorbisFile) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)vorbisFile->ptr; - return hl_allocInt64 (ov_raw_tell (file)); + return hl_allocVorbisInt64 (ov_raw_tell (file)); } @@ -500,7 +508,7 @@ namespace lime { value lime_vorbis_file_raw_total (value vorbisFile, int bitstream) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)val_data (vorbisFile); - return allocInt64 (ov_raw_total (file, bitstream)); + return allocVorbisInt64 (ov_raw_total (file, bitstream)); } @@ -508,7 +516,7 @@ namespace lime { HL_PRIM vdynamic* HL_NAME(hl_vorbis_file_raw_total) (HL_CFFIPointer* vorbisFile, int bitstream) { OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)vorbisFile->ptr; - return hl_allocInt64 (ov_raw_total (file, bitstream)); + return hl_allocVorbisInt64 (ov_raw_total (file, bitstream)); } @@ -562,6 +570,101 @@ namespace lime { } + int lime_vorbis_file_decode (value vorbisFile, value buffer, int position, int length, int word) { + + if (val_is_null (buffer)) { + + return 0; + + } + + OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)val_data (vorbisFile); + + Bytes bytes; + bytes.Set (buffer); + + char* data = (char*)(bytes.b + position); + int size = 0; + int result; + + while (size < length) { + + long result = ov_read (file, data, length - size, BUFFER_READ_TYPE, word, 1, NULL); + + if (result != OV_HOLE) { + + if (result <= OV_EREAD) { + + return 0; + + } + else if (result == 0) { + + break; + + } + else { + + size += result; + data += result; + + } + + } + + } + + return size; + + } + + + HL_PRIM int HL_NAME(hl_vorbis_file_decode) (HL_CFFIPointer* vorbisFile, Bytes* buffer, int position, int length, int word) { + + if (!buffer) { + + return 0; + + } + + OggVorbis_File* file = (OggVorbis_File*)(uintptr_t)vorbisFile->ptr; + + char* data = (char*)(buffer->b + position); + int size = 0; + int result; + + while (size < length) { + + long result = ov_read (file, data, length - size, BUFFER_READ_TYPE, word, 1, NULL); + + if (result != OV_HOLE) { + + if (result <= OV_EREAD) { + + return 0; + + } + else if (result == 0) { + + break; + + } + else { + + size += result; + data += result; + + } + + } + + } + + return size; + + } + + value lime_vorbis_file_read_float (value vorbisFile, value pcmChannels, int samples) { //Bytes bytes; @@ -752,6 +855,7 @@ namespace lime { DEFINE_PRIME1 (lime_vorbis_file_raw_tell); DEFINE_PRIME2 (lime_vorbis_file_raw_total); DEFINE_PRIME7 (lime_vorbis_file_read); + DEFINE_PRIME5 (lime_vorbis_file_decode); DEFINE_PRIME3 (lime_vorbis_file_read_float); DEFINE_PRIME1 (lime_vorbis_file_seekable); DEFINE_PRIME2 (lime_vorbis_file_serial_number); @@ -786,6 +890,7 @@ namespace lime { DEFINE_HL_PRIM (_DYN, hl_vorbis_file_raw_tell, _TCFFIPOINTER); DEFINE_HL_PRIM (_DYN, hl_vorbis_file_raw_total, _TCFFIPOINTER _I32); DEFINE_HL_PRIM (_DYN, hl_vorbis_file_read, _TCFFIPOINTER _TBYTES _I32 _I32 _BOOL _I32 _BOOL); + DEFINE_HL_PRIM (_I32, hl_vorbis_file_decode, _TCFFIPOINTER _TBYTES _I32 _I32 _I32); DEFINE_HL_PRIM (_DYN, hl_vorbis_file_read_float, _TCFFIPOINTER _TBYTES _I32); DEFINE_HL_PRIM (_BOOL, hl_vorbis_file_seekable, _TCFFIPOINTER); DEFINE_HL_PRIM (_I32, hl_vorbis_file_serial_number, _TCFFIPOINTER _I32); diff --git a/project/src/media/codecs/flac/DrFlac.cpp b/project/src/media/codecs/flac/DrFlac.cpp new file mode 100644 index 0000000000..a50c135a41 --- /dev/null +++ b/project/src/media/codecs/flac/DrFlac.cpp @@ -0,0 +1,91 @@ +#include +#include + + +namespace lime { + + + static size_t DrFlac_FileRead (void* pUserData, void* pBufferOut, size_t bytesToRead) { + + return lime::fread (pBufferOut, 1, bytesToRead, (FILE_HANDLE*)pUserData); + + } + + + static drflac_bool32 DrFlac_FileSeek (void* pUserData, int offset, drflac_seek_origin origin) { + + int whence = 0; + if (origin == DRFLAC_SEEK_CUR) { + whence = 1; + } + else if (origin == DRFLAC_SEEK_END) { + whence = 2; + } + + return lime::fseek ((FILE_HANDLE*)pUserData, offset, whence) == 0; + + } + + + static drflac_bool32 DrFlac_FileTell (void* pUserData, drflac_int64* pCursor) { + + *pCursor = lime::ftell ((FILE_HANDLE*)pUserData); + return DRFLAC_TRUE; + + } + + + drflac* DrFlac::FromBytes (Bytes* bytes) { + + if (!bytes) return 0; + + drflac* pFlac = drflac_open_memory (bytes->b, bytes->length, NULL); + if (!pFlac) { + + return 0; + + } + + return pFlac; + + } + + + drflac* DrFlac::FromFile (const char* path) { + + if (!path) return 0; + + FILE_HANDLE *file = lime::fopen (path, "rb"); + if (!file) return 0; + + drflac* pFlac = drflac_open (DrFlac_FileRead, DrFlac_FileSeek, DrFlac_FileTell, file, NULL); + if (!pFlac) { + + lime::fclose (file); + return 0; + + } + + return pFlac; + + } + + + void DrFlac::Close (drflac* pFlac) { + + if (!pFlac) return; + + if (pFlac->bs.onRead == DrFlac_FileRead) { + + FILE_HANDLE *file = (FILE_HANDLE*)pFlac->bs.pUserData; + if (file) lime::fclose (file); + + } + + //delete pFlac->bs.pUserData; + drflac_close (pFlac); + + } + + +} \ No newline at end of file diff --git a/project/src/media/codecs/mp3/DrMp3.cpp b/project/src/media/codecs/mp3/DrMp3.cpp new file mode 100644 index 0000000000..83c3e0d9b1 --- /dev/null +++ b/project/src/media/codecs/mp3/DrMp3.cpp @@ -0,0 +1,97 @@ +#include +#include + + +namespace lime { + + + static size_t DrMp3_FileRead (void* pUserData, void* pBufferOut, size_t bytesToRead) { + + return lime::fread (pBufferOut, 1, bytesToRead, (FILE_HANDLE*)pUserData); + + } + + + static drmp3_bool32 DrMp3_FileSeek (void* pUserData, int offset, drmp3_seek_origin origin) { + + int whence = 0; + if (origin == DRMP3_SEEK_CUR) { + whence = 1; + } + else if (origin == DRMP3_SEEK_END) { + whence = 2; + } + + return lime::fseek ((FILE_HANDLE*)pUserData, offset, whence) == 0; + + } + + + static drmp3_bool32 DrMp3_FileTell (void* pUserData, drmp3_int64* pCursor) { + + *pCursor = lime::ftell ((FILE_HANDLE*)pUserData); + return DRMP3_TRUE; + + } + + + drmp3* DrMp3::FromBytes (Bytes* bytes) { + + if (!bytes) return 0; + + drmp3* pMp3 = new drmp3; + memset (pMp3, 0, sizeof (DrMp3)); + + if (drmp3_init_memory (pMp3, bytes->b, bytes->length, NULL) == DRMP3_FALSE) { + + delete pMp3; + return 0; + + } + + return pMp3; + + } + + + drmp3* DrMp3::FromFile (const char* path) { + + if (!path) return 0; + + FILE_HANDLE *file = lime::fopen (path, "rb"); + if (!file) return 0; + + drmp3* pMp3 = new drmp3; + memset (pMp3, 0, sizeof (drmp3)); + + if (drmp3_init (pMp3, DrMp3_FileRead, DrMp3_FileSeek, DrMp3_FileTell, NULL, file, NULL) == DRMP3_FALSE) { + + delete pMp3; + lime::fclose (file); + return 0; + + } + + return pMp3; + + } + + + void DrMp3::Close (drmp3* pMp3) { + + if (!pMp3) return; + + if (pMp3->onRead == DrMp3_FileRead) { + + FILE_HANDLE *file = (FILE_HANDLE*)pMp3->pUserData; + if (file) lime::fclose (file); + + } + + //delete pMp3->pUserData; + drmp3_uninit (pMp3); + + } + + +} \ No newline at end of file diff --git a/project/src/media/codecs/opus/OpusFile.cpp b/project/src/media/codecs/opus/OpusFile.cpp new file mode 100644 index 0000000000..991ba3445b --- /dev/null +++ b/project/src/media/codecs/opus/OpusFile.cpp @@ -0,0 +1,186 @@ +#include +#include + + +namespace lime { + + + typedef struct { + + unsigned char* data; + opus_int64 size; + opus_int64 pos; + + } OpusFile_Buffer; + + + static int OpusFile_BufferRead (OpusFile_Buffer* src, void* dest, int bytesToRead) { + + if ((src->pos + bytesToRead) > src->size) { + + bytesToRead = src->size - src->pos; + + } + + if (bytesToRead > 0) { + + memcpy (dest, (src->data + src->pos), bytesToRead); + src->pos += bytesToRead; + return bytesToRead; + + } + + return 0; + + } + + + static int OpusFile_BufferSeek (OpusFile_Buffer* src, opus_int64 offset, int whence) { + + switch (whence) { + + case SEEK_CUR: + + src->pos += offset; + break; + + case SEEK_END: + + src->pos = src->size - offset; + break; + + case SEEK_SET: + + src->pos = offset; + break; + + default: + + return -1; + + } + + if (src->pos < 0) { + + src->pos = 0; + return -1; + + } + + if (src->pos > src->size) { + + return -1; + + } + + return 0; + + } + + + static opus_int64 OpusFile_BufferTell (OpusFile_Buffer* src) { + + return src->pos; + + } + + + static int OpusFile_BufferClose (OpusFile_Buffer* src) { + + delete src; + return 0; + + } + + + static OpusFileCallbacks OPUS_FILE_BUFFER_CALLBACKS = { + + (int (*)(void *, unsigned char *, int)) OpusFile_BufferRead, + (int (*)(void *, opus_int64, int)) OpusFile_BufferSeek, + (opus_int64 (*)(void *)) OpusFile_BufferTell, + (int (*)(void *)) OpusFile_BufferClose + + }; + + + static int OpusFile_FileRead (FILE_HANDLE* file, void* dest, int bytesToRead) { + + return lime::fread (dest, 1, bytesToRead, file); + + } + + + static int OpusFile_FileSeek (FILE_HANDLE* file, opus_int64 offset, int whence) { + + return lime::fseek (file, offset, whence); + + } + + + static opus_int64 OpusFile_FileTell (FILE_HANDLE* file) { + + return (opus_int64)lime::ftell (file); + + } + + + static int OpusFile_FileClose (FILE_HANDLE* file) { + + return lime::fclose (file); + + } + + + static OpusFileCallbacks OPUS_FILE_FILE_CALLBACKS = { + + (int (*)(void *, unsigned char *, int)) OpusFile_FileRead, + (int (*)(void *, opus_int64, int)) OpusFile_FileSeek, + (opus_int64 (*)(void *)) OpusFile_FileTell, + (int (*)(void *)) OpusFile_FileClose + + }; + + + OggOpusFile* OpusFile::FromBytes (Bytes* bytes) { + + if (!bytes) return 0; + + OpusFile_Buffer* buffer = new OpusFile_Buffer (); + buffer->data = bytes->b; + buffer->size = bytes->length; + buffer->pos = 0; + + OggOpusFile* opusFile = op_open_callbacks (buffer, &OPUS_FILE_BUFFER_CALLBACKS, NULL, 0, NULL); + if (!opusFile) { + + delete buffer; + return 0; + + } + + return opusFile; + + } + + + OggOpusFile* OpusFile::FromFile (const char* path) { + + if (!path) return 0; + + FILE_HANDLE *file = lime::fopen (path, "rb"); + if (!file) return 0; + + OggOpusFile* opusFile = op_open_callbacks (file, &OPUS_FILE_FILE_CALLBACKS, NULL, 0, NULL); + if (!opusFile) { + + lime::fclose (file); + return 0; + + } + + return opusFile; + + } + + +} \ No newline at end of file diff --git a/project/src/media/codecs/vorbis/VorbisFile.cpp b/project/src/media/codecs/vorbis/VorbisFile.cpp index ab4a0a441c..82f5c43235 100644 --- a/project/src/media/codecs/vorbis/VorbisFile.cpp +++ b/project/src/media/codecs/vorbis/VorbisFile.cpp @@ -28,31 +28,32 @@ namespace lime { memcpy (dest, (src->data + src->pos), len); src->pos += len; + return len; } - return len; + return 0; } - static int VorbisFile_BufferSeek (VorbisFile_Buffer* src, ogg_int64_t pos, int whence) { + static int VorbisFile_BufferSeek (VorbisFile_Buffer* src, ogg_int64_t offset, int whence) { switch (whence) { case SEEK_CUR: - src->pos += pos; + src->pos += offset; break; case SEEK_END: - src->pos = src->size - pos; + src->pos = src->size - offset; break; case SEEK_SET: - src->pos = pos; + src->pos = offset; break; default: @@ -111,9 +112,9 @@ namespace lime { } - static int VorbisFile_FileSeek (FILE_HANDLE* file, ogg_int64_t pos, int whence) { + static int VorbisFile_FileSeek (FILE_HANDLE* file, ogg_int64_t offset, int whence) { - return lime::fseek (file, pos, whence); + return lime::fseek (file, offset, whence); } @@ -144,6 +145,8 @@ namespace lime { OggVorbis_File* VorbisFile::FromBytes (Bytes* bytes) { + if (!bytes) return 0; + OggVorbis_File* vorbisFile = new OggVorbis_File; memset (vorbisFile, 0, sizeof (OggVorbis_File)); @@ -167,30 +170,23 @@ namespace lime { OggVorbis_File* VorbisFile::FromFile (const char* path) { - if (path) { - - FILE_HANDLE *file = lime::fopen (path, "rb"); - - if (file) { - - OggVorbis_File* vorbisFile = new OggVorbis_File; - memset (vorbisFile, 0, sizeof (OggVorbis_File)); - - if (ov_open_callbacks (file, vorbisFile, NULL, 0, VORBIS_FILE_FILE_CALLBACKS) != 0) { + if (!path) return 0; - delete vorbisFile; - lime::fclose (file); - return 0; + FILE_HANDLE *file = lime::fopen (path, "rb"); + if (!file) return 0; - } + OggVorbis_File* vorbisFile = new OggVorbis_File; + memset (vorbisFile, 0, sizeof (OggVorbis_File)); - return vorbisFile; + if (ov_open_callbacks (file, vorbisFile, NULL, 0, VORBIS_FILE_FILE_CALLBACKS) != 0) { - } + delete vorbisFile; + lime::fclose (file); + return 0; } - return 0; + return vorbisFile; } diff --git a/project/src/media/codecs/wav/DrWav.cpp b/project/src/media/codecs/wav/DrWav.cpp new file mode 100644 index 0000000000..7f6a19b3af --- /dev/null +++ b/project/src/media/codecs/wav/DrWav.cpp @@ -0,0 +1,97 @@ +#include +#include + + +namespace lime { + + + static size_t DrWav_FileRead (void* pUserData, void* pBufferOut, size_t bytesToRead) { + + return lime::fread (pBufferOut, 1, bytesToRead, (FILE_HANDLE*)pUserData); + + } + + + static drwav_bool32 DrWav_FileSeek (void* pUserData, int offset, drwav_seek_origin origin) { + + int whence = 0; + if (origin == DRWAV_SEEK_CUR) { + whence = 1; + } + else if (origin == DRWAV_SEEK_END) { + whence = 2; + } + + return lime::fseek ((FILE_HANDLE*)pUserData, offset, whence) == 0; + + } + + + static drwav_bool32 DrWav_FileTell (void* pUserData, drwav_int64* pCursor) { + + *pCursor = lime::ftell ((FILE_HANDLE*)pUserData); + return DRWAV_TRUE; + + } + + + drwav* DrWav::FromBytes (Bytes* bytes) { + + if (!bytes) return 0; + + drwav* pWav = new drwav; + memset (pWav, 0, sizeof (DrWav)); + + if (drwav_init_memory (pWav, bytes->b, bytes->length, NULL) == DRWAV_FALSE) { + + delete pWav; + return 0; + + } + + return pWav; + + } + + + drwav* DrWav::FromFile (const char* path) { + + if (!path) return 0; + + FILE_HANDLE *file = lime::fopen (path, "rb"); + if (!file) return 0; + + drwav* pWav = new drwav; + memset (pWav, 0, sizeof (drwav)); + + if (drwav_init (pWav, DrWav_FileRead, DrWav_FileSeek, DrWav_FileTell, file, NULL) == DRWAV_FALSE) { + + delete pWav; + lime::fclose (file); + return 0; + + } + + return pWav; + + } + + + void DrWav::Close (drwav* pWav) { + + if (!pWav) return; + + if (pWav->onRead == DrWav_FileRead) { + + FILE_HANDLE *file = (FILE_HANDLE*)pWav->pUserData; + if (file) lime::fclose (file); + + } + + //delete pWav->pUserData; + drwav_uninit (pWav); + + } + + +} \ No newline at end of file diff --git a/project/src/media/containers/OGG.cpp b/project/src/media/containers/OGG.cpp deleted file mode 100644 index b045d56b31..0000000000 --- a/project/src/media/containers/OGG.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include - - -namespace lime { - - - bool OGG::Decode (Resource *resource, AudioBuffer *audioBuffer) { - - OggVorbis_File* oggFile; - Bytes *data = NULL; - - if (resource->path) { - - oggFile = VorbisFile::FromFile (resource->path); - - } else { - - oggFile = VorbisFile::FromBytes (resource->data); - - } - - if (!oggFile) { - - return false; - - } - - // 0 for Little-Endian, 1 for Big-Endian - #ifdef HXCPP_BIG_ENDIAN - #define BUFFER_READ_TYPE 1 - #else - #define BUFFER_READ_TYPE 0 - #endif - - int bitStream; - long bytes = 1; - int totalBytes = 0; - - #define BUFFER_SIZE 4096 - - vorbis_info *pInfo = ov_info (oggFile, -1); - - if (pInfo == NULL) { - - //LOG_SOUND("FAILED TO READ OGG SOUND INFO, IS THIS EVEN AN OGG FILE?\n"); - ov_clear (oggFile); - delete oggFile; - - return false; - - } - - audioBuffer->channels = pInfo->channels; - audioBuffer->sampleRate = pInfo->rate; - - audioBuffer->bitsPerSample = 16; - audioBuffer->dataFormat = 1; - - int dataLength = ov_pcm_total (oggFile, -1) * audioBuffer->channels * audioBuffer->bitsPerSample / 8; - audioBuffer->data->Resize (dataLength); - - while (bytes > 0) { - - bytes = ov_read (oggFile, (char *)audioBuffer->data->buffer->b + totalBytes, BUFFER_SIZE, BUFFER_READ_TYPE, 2, 1, &bitStream); - - if (bytes > 0) { - - totalBytes += bytes; - - } - - } - - if (dataLength != totalBytes) { - - audioBuffer->data->Resize (totalBytes); - - } - - ov_clear (oggFile); - delete oggFile; - - #undef BUFFER_READ_TYPE - - return true; - - } - - -} \ No newline at end of file diff --git a/project/src/media/containers/WAV.cpp b/project/src/media/containers/WAV.cpp deleted file mode 100644 index 1723e7bf5d..0000000000 --- a/project/src/media/containers/WAV.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include -#include - - -namespace lime { - - - template - inline const char* readStruct (T& dest, const char*& ptr) { - - const char* ret; - memcpy (&dest, ptr, sizeof (T)); - ptr += sizeof (WAVE_Data); - ret = ptr; - ptr += dest.subChunkSize; - return ret; - - } - - - const char* find_chunk (const char* start, const char* end, const char* chunkID) { - - WAVE_Data chunk; - const char* ptr = start; - - while (ptr < (end - sizeof(WAVE_Data))) { - - memcpy (&chunk, ptr, sizeof (WAVE_Data)); - - if (chunk.subChunkID[0] == chunkID[0] && chunk.subChunkID[1] == chunkID[1] && chunk.subChunkID[2] == chunkID[2] && chunk.subChunkID[3] == chunkID[3]) { - - return ptr; - - } - - ptr += sizeof (WAVE_Data) + chunk.subChunkSize; - - } - - return 0; - - } - - - bool WAV::Decode (Resource *resource, AudioBuffer *audioBuffer) { - - WAVE_Format wave_format; - RIFF_Header riff_header; - WAVE_Data wave_data; - unsigned char* data; - - FILE_HANDLE *file = NULL; - - if (resource->path) { - - file = lime::fopen (resource->path, "rb"); - - if (!file) { - - return false; - - } - - int result = lime::fread (&riff_header, sizeof (RIFF_Header), 1, file); - - if ((riff_header.chunkID[0] != 'R' || riff_header.chunkID[1] != 'I' || riff_header.chunkID[2] != 'F' || riff_header.chunkID[3] != 'F') || (riff_header.format[0] != 'W' || riff_header.format[1] != 'A' || riff_header.format[2] != 'V' || riff_header.format[3] != 'E')) { - - lime::fclose (file); - return false; - - } - - long int currentHead = 0; - bool foundFormat = false; - - while (!foundFormat) { - - currentHead = lime::ftell (file); - result = lime::fread (&wave_format, sizeof (WAVE_Format), 1, file); - - if (result != 1) { - - LOG_SOUND ("Invalid Wave Format!\n"); - lime::fclose (file); - return false; - - } - - if (wave_format.subChunkID[0] != 'f' || wave_format.subChunkID[1] != 'm' || wave_format.subChunkID[2] != 't' || wave_format.subChunkID[3] != ' ') { - - lime::fseek (file, currentHead + sizeof (WAVE_Data) + wave_format.subChunkSize, SEEK_SET); - - } else { - - foundFormat = true; - - } - - } - - bool foundData = false; - - while (!foundData) { - - currentHead = lime::ftell (file); - result = lime::fread (&wave_data, sizeof (WAVE_Data), 1, file); - - if (result != 1) { - - LOG_SOUND ("Invalid Wav Data Header!\n"); - lime::fclose (file); - return false; - - } - - if (wave_data.subChunkID[0] != 'd' || wave_data.subChunkID[1] != 'a' || wave_data.subChunkID[2] != 't' || wave_data.subChunkID[3] != 'a') { - - lime::fseek (file, currentHead + sizeof (WAVE_Data) + wave_data.subChunkSize, SEEK_SET); - - } else { - - foundData = true; - - } - - } - - audioBuffer->data->Resize (wave_data.subChunkSize); - - if (!lime::fread (audioBuffer->data->buffer->b, wave_data.subChunkSize, 1, file)) { - - LOG_SOUND ("error loading WAVE data into struct!\n"); - lime::fclose (file); - return false; - - } - - lime::fclose (file); - - } else { - - const char* start = (const char*)resource->data->b; - const char* end = start + resource->data->length; - const char* ptr = start; - - memcpy (&riff_header, ptr, sizeof (RIFF_Header)); - ptr += sizeof (RIFF_Header); - - if ((riff_header.chunkID[0] != 'R' || riff_header.chunkID[1] != 'I' || riff_header.chunkID[2] != 'F' || riff_header.chunkID[3] != 'F') || (riff_header.format[0] != 'W' || riff_header.format[1] != 'A' || riff_header.format[2] != 'V' || riff_header.format[3] != 'E')) { - - return false; - - } - - ptr = find_chunk (ptr, end, "fmt "); - - if (!ptr) { - - return false; - - } - - readStruct (wave_format, ptr); - - if (wave_format.subChunkID[0] != 'f' || wave_format.subChunkID[1] != 'm' || wave_format.subChunkID[2] != 't' || wave_format.subChunkID[3] != ' ') { - - LOG_SOUND ("Invalid Wave Format!\n"); - return false; - - } - - ptr = find_chunk (ptr, end, "data"); - - if (!ptr) { - - return false; - - } - - const char* base = readStruct (wave_data, ptr); - - if (wave_data.subChunkID[0] != 'd' || wave_data.subChunkID[1] != 'a' || wave_data.subChunkID[2] != 't' || wave_data.subChunkID[3] != 'a') { - - LOG_SOUND ("Invalid Wav Data Header!\n"); - return false; - - } - - audioBuffer->data->Resize (wave_data.subChunkSize); - - size_t size = wave_data.subChunkSize; - - if (size > (end - base)) { - - return false; - - } - - unsigned char* bytes = audioBuffer->data->buffer->b; - memcpy (bytes, base, size); - - } - - audioBuffer->sampleRate = (int)wave_format.sampleRate; - audioBuffer->channels = wave_format.numChannels; - audioBuffer->bitsPerSample = wave_format.bitsPerSample; - audioBuffer->dataFormat = wave_format.audioFormat; - - return true; - - } - - -} \ No newline at end of file diff --git a/project/src/media/openal/OpenALBindings.cpp b/project/src/media/openal/OpenALBindings.cpp index 42a7742a1c..53d1f81502 100644 --- a/project/src/media/openal/OpenALBindings.cpp +++ b/project/src/media/openal/OpenALBindings.cpp @@ -132,33 +132,6 @@ namespace lime { } - /*This has been removed after updating to openal 1.20.0+ since the cleanup functions involved - * lead to deadlocking. See https://github.com/openfl/lime/issues/1803 for more info. - * Developers should use lime.system.System.exit() instead of Sys.exit() to clean up any system - * resources - */ - /* - void lime_al_atexit () { - - ALCcontext* alcContext = alcGetCurrentContext (); - - if (alcContext) { - - ALCdevice* alcDevice = alcGetContextsDevice (alcContext); - - alcMakeContextCurrent (0); - alcDestroyContext (alcContext); - - if (alcDevice) { - - alcCloseDevice (alcDevice); - - } - - } - - } - */ void lime_al_auxf (value aux, int param, float value) { @@ -2184,7 +2157,9 @@ namespace lime { bool lime_alc_is_extension_present (value device, HxString extname) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + if (val_is_null (device)) return alcIsExtensionPresent (0, extname.__s); + + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)val_data (device); return alcIsExtensionPresent (alcDevice, extname.__s); #else return false; @@ -2196,7 +2171,9 @@ namespace lime { HL_PRIM bool HL_NAME(hl_alc_is_extension_present) (HL_CFFIPointer* device, hl_vstring* extname) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = (ALCdevice*)device->ptr; + if (!device) return alcIsExtensionPresent (0, extname ? hl_to_utf8 (extname->bytes) : NULL); + + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)device->ptr; return alcIsExtensionPresent (alcDevice, extname ? hl_to_utf8 (extname->bytes) : NULL); #else return false; @@ -2781,50 +2758,92 @@ namespace lime { } - void lime_al_source3i (value source, int param, value value1, int value2, int value3) { + void lime_al_source3i (value source, int param, value value1, int value2, value value3) { ALuint id = (ALuint)(uintptr_t)val_data (source); ALuint data1; + ALuint data3; #ifdef LIME_OPENALSOFT if (param == AL_AUXILIARY_SEND_FILTER) { - data1 = (ALuint)(uintptr_t)val_data (value1); + if (val_is_null (value1)) { + + data1 = 0; + + } else { + + data1 = (ALuint)(uintptr_t)val_data (value1); + + } + + if (val_is_null (value3)) { + + data3 = 0; + + } else { + + data3 = (ALuint)(uintptr_t)val_data (value3); + + } } else { data1 = val_int (value1); + data3 = val_int (value3); } #else data1 = val_int (value1); + data3 = val_int (value3); #endif - alSource3i (id, param, data1, value2, value3); + alSource3i (id, param, data1, value2, data3); } - HL_PRIM void HL_NAME(hl_al_source3i) (HL_CFFIPointer* source, int param, vdynamic* value1, int value2, int value3) { + HL_PRIM void HL_NAME(hl_al_source3i) (HL_CFFIPointer* source, int param, vdynamic* value1, int value2, vdynamic* value3) { ALuint id = (ALuint)(uintptr_t)source->ptr; ALuint data1; + ALuint data3; #ifdef LIME_OPENALSOFT if (param == AL_AUXILIARY_SEND_FILTER) { - data1 = (ALuint)(uintptr_t)((HL_CFFIPointer*)value1)->ptr; + if (value1) { + + data1 = (ALuint)(uintptr_t)((HL_CFFIPointer*)value1)->ptr; + + } else { + + data1 = 0; + + } + + if (value3) { + + data3 = (ALuint)(uintptr_t)((HL_CFFIPointer*)value3)->ptr; + + } else { + + data3 = 0; + + } } else { data1 = value1->v.i; + data3 = value3->v.i; } #else data1 = value1->v.i; + data3 = value3->v.i; #endif - alSource3i (id, param, data1, value2, value3); + alSource3i (id, param, data1, value2, data3); } @@ -3007,11 +3026,12 @@ namespace lime { bool lime_alc_close_device (value device) { + if (val_is_null (device)) return false; + + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)val_data (device); al_gc_mutex.Lock (); - ALCdevice* alcDevice = (ALCdevice*)val_data (device); alcObjects.erase (alcDevice); al_gc_mutex.Unlock (); - return alcCloseDevice (alcDevice); } @@ -3019,11 +3039,12 @@ namespace lime { HL_PRIM bool HL_NAME(hl_alc_close_device) (HL_CFFIPointer* device) { + if (!device) return false; + + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)device->ptr; al_gc_mutex.Lock (); - ALCdevice* alcDevice = (ALCdevice*)device->ptr; alcObjects.erase (alcDevice); al_gc_mutex.Unlock (); - return alcCloseDevice (alcDevice); } @@ -3031,7 +3052,7 @@ namespace lime { value lime_alc_create_context (value device, value attrlist) { - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)val_data (device); ALCint* list = NULL; if (!val_is_null (attrlist)) { @@ -3055,119 +3076,129 @@ namespace lime { } + value ptr = CFFIPointer ((void*)(uintptr_t)alcContext, gc_alc_object); al_gc_mutex.Lock (); - value object = CFFIPointer (alcContext, gc_alc_object); - alcObjects[alcContext] = object; + alcObjects[alcDevice] = ptr; al_gc_mutex.Unlock (); - return object; + return ptr; } HL_PRIM HL_CFFIPointer* HL_NAME(hl_alc_create_context) (HL_CFFIPointer* device, varray* attrlist) { - ALCdevice* alcDevice = (ALCdevice*)device->ptr; + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)device->ptr; ALCcontext* alcContext = alcCreateContext (alcDevice, attrlist ? hl_aptr (attrlist, int) : NULL); + HL_CFFIPointer* ptr = HLCFFIPointer ((void*)(uintptr_t)alcContext, (hl_finalizer)gc_alc_object); al_gc_mutex.Lock (); - HL_CFFIPointer* object = HLCFFIPointer (alcContext, (hl_finalizer)hl_gc_alc_object); - alcObjects[alcContext] = object; + alcObjects[alcDevice] = ptr; al_gc_mutex.Unlock (); - return object; + return ptr; } void lime_alc_destroy_context (value context) { - al_gc_mutex.Lock (); - ALCcontext* alcContext = (ALCcontext*)val_data (context); + if (!val_is_null (context)) { - if (alcObjects.find (alcContext) != alcObjects.end ()) { + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)val_data (context); + al_gc_mutex.Lock (); alcObjects.erase (alcContext); + al_gc_mutex.Unlock (); - } + if (alcContext == alcGetCurrentContext ()) { - if (alcContext == alcGetCurrentContext ()) { + alcMakeContextCurrent (0); - alcMakeContextCurrent (0); + } - } + alcDestroyContext (alcContext); - alcDestroyContext (alcContext); - al_gc_mutex.Unlock (); + } } HL_PRIM void HL_NAME(hl_alc_destroy_context) (HL_CFFIPointer* context) { - al_gc_mutex.Lock (); - ALCcontext* alcContext = (ALCcontext*)context->ptr; + if (context) { - if (alcObjects.find (alcContext) != alcObjects.end ()) { + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)context->ptr; + al_gc_mutex.Lock (); alcObjects.erase (alcContext); + al_gc_mutex.Unlock (); - } + if (alcContext == alcGetCurrentContext ()) { - if (alcContext == alcGetCurrentContext ()) { + alcMakeContextCurrent (0); - alcMakeContextCurrent (0); + } - } + alcDestroyContext (alcContext); - alcDestroyContext (alcContext); - al_gc_mutex.Unlock (); + } } value lime_alc_get_contexts_device (value context) { - ALCcontext* alcContext = (ALCcontext*)val_data (context); + if (val_is_null (context)) return alloc_null (); + + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)val_data (context); ALCdevice* alcDevice = alcGetContextsDevice (alcContext); - value result; + value ptr; al_gc_mutex.Lock (); + if (alcObjects.find (alcDevice) != alcObjects.end ()) { - result = (value)alcObjects[alcDevice]; + ptr = (value)alcObjects[alcDevice]; - } else { + } + else { - value object = CFFIPointer (alcDevice, gc_alc_object); - alcObjects[alcDevice] = object; - result = object; + ptr = CFFIPointer ((void*)(uintptr_t)alcDevice, gc_alc_object); + alcObjects[alcDevice] = ptr; } + al_gc_mutex.Unlock (); - return result; + + return ptr; } HL_PRIM HL_CFFIPointer* HL_NAME(hl_alc_get_contexts_device) (HL_CFFIPointer* context) { - ALCcontext* alcContext = (ALCcontext*)context->ptr; + if (!context) return NULL; + + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)context->ptr; ALCdevice* alcDevice = alcGetContextsDevice (alcContext); - HL_CFFIPointer* result; + HL_CFFIPointer* ptr; al_gc_mutex.Lock (); + if (alcObjects.find (alcDevice) != alcObjects.end ()) { - result = (HL_CFFIPointer*)alcObjects[alcDevice]; + ptr = (HL_CFFIPointer*)alcObjects[alcDevice]; - } else { + } + else { - HL_CFFIPointer* object = HLCFFIPointer (alcDevice, (hl_finalizer)hl_gc_alc_object); - alcObjects[alcDevice] = object; - result = object; + ptr = HLCFFIPointer ((void*)(uintptr_t)alcDevice, (hl_finalizer)gc_alc_object); + alcObjects[alcDevice] = ptr; } + al_gc_mutex.Unlock (); - return result; + + return ptr; } @@ -3175,22 +3206,26 @@ namespace lime { value lime_alc_get_current_context () { ALCcontext* alcContext = alcGetCurrentContext (); + if (!alcContext) return alloc_null (); - value result; + value ptr; al_gc_mutex.Lock (); + if (alcObjects.find (alcContext) != alcObjects.end ()) { - result = (value)alcObjects[alcContext]; + ptr = (value)alcObjects[alcContext]; - } else { + } + else { - value object = CFFIPointer (alcContext, gc_alc_object); - alcObjects[alcContext] = object; - result = object; + ptr = CFFIPointer ((void*)(uintptr_t)alcContext, gc_alc_object); + alcObjects[alcContext] = ptr; } + al_gc_mutex.Unlock (); - return result; + + return ptr; } @@ -3198,29 +3233,33 @@ namespace lime { HL_PRIM HL_CFFIPointer* HL_NAME(hl_alc_get_current_context) () { ALCcontext* alcContext = alcGetCurrentContext (); + if (!alcContext) return NULL; - HL_CFFIPointer* result; + HL_CFFIPointer* ptr; al_gc_mutex.Lock (); + if (alcObjects.find (alcContext) != alcObjects.end ()) { - result = (HL_CFFIPointer*)alcObjects[alcContext]; + ptr = (HL_CFFIPointer*)alcObjects[alcContext]; - } else { + } + else { - HL_CFFIPointer* object = HLCFFIPointer (alcContext, (hl_finalizer)hl_gc_alc_object); - alcObjects[alcContext] = object; - result = object; + ptr = HLCFFIPointer ((void*)(uintptr_t)alcContext, (hl_finalizer)gc_alc_object); + alcObjects[alcContext] = ptr; } + al_gc_mutex.Unlock (); - return result; + + return ptr; } int lime_alc_get_error (value device) { - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)val_data (device); return alcGetError (alcDevice); } @@ -3228,22 +3267,22 @@ namespace lime { HL_PRIM int HL_NAME(hl_alc_get_error) (HL_CFFIPointer* device) { - ALCdevice* alcDevice = (ALCdevice*)device->ptr; + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)device->ptr; return alcGetError (alcDevice); } - value lime_alc_get_integerv (value device, int param, int size) { + value lime_alc_get_integerv (value device, int param, int count) { - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = val_is_null (device) ? 0 : (ALCdevice*)(uintptr_t)val_data (device); - ALCint* values = new ALCint[size]; - alcGetIntegerv (alcDevice, param, size, values); + ALCint* values = new ALCint[count]; + alcGetIntegerv (alcDevice, param, count, values); - value result = alloc_array (size); + value result = alloc_array (count); - for (int i = 0; i < size; i++) { + for (int i = 0; i < count; i++) { val_array_set_i (result, i, alloc_int (values[i])); @@ -3255,11 +3294,11 @@ namespace lime { } - HL_PRIM varray* HL_NAME(hl_alc_get_integerv) (HL_CFFIPointer* device, int param, int size) { + HL_PRIM varray* HL_NAME(hl_alc_get_integerv) (HL_CFFIPointer* device, int param, int count) { - ALCdevice* alcDevice = (ALCdevice*)device->ptr; - varray* result = hl_alloc_array (&hlt_i32, size); - alcGetIntegerv (alcDevice, param, size, hl_aptr (result, int)); + ALCdevice* alcDevice = device ? (ALCdevice*)(uintptr_t)device->ptr : 0; + varray* result = hl_alloc_array (&hlt_i32, count); + alcGetIntegerv (alcDevice, param, count, hl_aptr (result, int)); return result; } @@ -3267,7 +3306,7 @@ namespace lime { value lime_alc_get_string (value device, int param) { - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = val_is_null (device) ? 0 : (ALCdevice*)(uintptr_t)val_data (device); const char* result = alcGetString (alcDevice, param); return result ? alloc_string (result) : alloc_null (); @@ -3276,7 +3315,7 @@ namespace lime { HL_PRIM vbyte* HL_NAME(hl_alc_get_string) (HL_CFFIPointer* device, int param) { - ALCdevice* alcDevice = device ? (ALCdevice*)device->ptr : 0; + ALCdevice* alcDevice = device ? (ALCdevice*)(uintptr_t)device->ptr : 0; const char* result = alcGetString (alcDevice, param); int length = strlen (result); char* _result = (char*)malloc (length + 1); @@ -3288,74 +3327,77 @@ namespace lime { value lime_alc_get_string_list (value device, int param) { - ALCdevice* alcDevice = (ALCdevice*)val_data (device); - const char* result = alcGetString (alcDevice, param); - - if (!result || *result == '\0') { - - return alloc_null (); + #ifdef ALC_ENUMERATE_ALL_EXT + ALCdevice* alcDevice = val_is_null (device) ? 0 : (ALCdevice*)(uintptr_t)val_data (device); + const char* values = alcGetString (alcDevice, param); + if (!values) { + return alloc_array (0); } - value list = alloc_array (0); - - while (*result != '\0') { - - val_array_push (list, alloc_string (result)); - result += strlen (result) + 1; + int count = 0; + const char* ptr = values; + while (*ptr) { + count++; + ptr += strlen (ptr) + 1; + } + value result = alloc_array (count); + ptr = values; + count = 0; + while (*ptr) { + val_array_set_i (result, count, alloc_string (ptr)); + count++; + ptr += strlen (ptr) + 1; } - return list; + return result; + #else + return alloc_array (0); + #endif } HL_PRIM varray* HL_NAME(hl_alc_get_string_list) (HL_CFFIPointer* device, int param) { - ALCdevice* alcDevice = device ? (ALCdevice*)device->ptr : 0; - const char* result = alcGetString(alcDevice, param); - - if (!result || *result == '\0') { - - return 0; + #ifdef ALC_ENUMERATE_ALL_EXT + ALCdevice* alcDevice = device ? (ALCdevice*)(uintptr_t)device->ptr : 0; + const char* values = alcGetString (alcDevice, param); + if (!values) { + return hl_alloc_array (&hlt_bytes, 0); } int count = 0; - const char* temp = result; - while (*temp != '\0') { - + const char* ptr = values; + while (*ptr) { count++; - temp += strlen (temp) + 1; - + ptr += strlen (ptr) + 1; } - varray* list = hl_alloc_array (&hlt_bytes, count); - vbyte** listData = hl_aptr (list, vbyte*); - - while (*result != '\0') { - - int length = strlen (result) + 1; - char* _result = (char*)malloc (length); - strcpy (_result, result); - - *listData = (vbyte*)_result; - listData++; - - result += length; - free(_result); - + varray* result = hl_alloc_array(&hlt_bytes, count); + ptr = values; + count = 0; + while (*ptr) { + char* _result = (char*)malloc (strlen (ptr) + 1); + strcpy (_result, ptr); + hl_aptr (result, vbyte*)[count] = (vbyte*)_result; + count++; + ptr += strlen (ptr) + 1; } - return list; + return result; + #else + return hl_alloc_array (&hlt_bytes, 0); + #endif } bool lime_alc_make_context_current (value context) { - ALCcontext* alcContext = (ALCcontext*)val_data (context); + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)val_data (context); return alcMakeContextCurrent (alcContext); } @@ -3363,7 +3405,7 @@ namespace lime { HL_PRIM bool HL_NAME(hl_alc_make_context_current) (HL_CFFIPointer* context) { - ALCcontext* alcContext = context ? (ALCcontext*)context->ptr : 0; + ALCcontext* alcContext = context ? (ALCcontext*)(uintptr_t)context->ptr : 0; return alcMakeContextCurrent (alcContext); } @@ -3372,11 +3414,10 @@ namespace lime { value lime_alc_open_device (HxString devicename) { ALCdevice* alcDevice = alcOpenDevice (devicename.__s); - //TODO: Can we work out our own cleanup for openal? - //atexit (lime_al_atexit); - - value ptr = CFFIPointer (alcDevice, gc_alc_object); + value ptr = (value)CFFIPointer ((void*)(uintptr_t)alcDevice, gc_alc_object); + al_gc_mutex.Lock (); alcObjects[alcDevice] = ptr; + al_gc_mutex.Unlock (); return ptr; } @@ -3385,11 +3426,10 @@ namespace lime { HL_PRIM HL_CFFIPointer* HL_NAME(hl_alc_open_device) (hl_vstring* devicename) { ALCdevice* alcDevice = alcOpenDevice (devicename ? (char*)hl_to_utf8 ((const uchar*)devicename->bytes) : 0); - //TODO: Can we work out our own cleanup for openal? - //atexit (lime_al_atexit); - - HL_CFFIPointer* ptr = HLCFFIPointer (alcDevice, (hl_finalizer)hl_gc_alc_object); + HL_CFFIPointer* ptr = HLCFFIPointer ((void*)(uintptr_t)alcDevice, (hl_finalizer)gc_alc_object); + al_gc_mutex.Lock (); alcObjects[alcDevice] = ptr; + al_gc_mutex.Unlock (); return ptr; } @@ -3398,7 +3438,7 @@ namespace lime { void lime_alc_pause_device (value device) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)val_data (device); alcDevicePauseSOFT (alcDevice); #endif @@ -3408,7 +3448,7 @@ namespace lime { HL_PRIM void HL_NAME(hl_alc_pause_device) (HL_CFFIPointer* device) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = (ALCdevice*)device->ptr; + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)device->ptr; alcDevicePauseSOFT (alcDevice); #endif @@ -3417,7 +3457,7 @@ namespace lime { void lime_alc_process_context (value context) { - ALCcontext* alcContext = (ALCcontext*)val_data (context); + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)val_data (context); alcProcessContext (alcContext); } @@ -3425,7 +3465,7 @@ namespace lime { HL_PRIM void HL_NAME(hl_alc_process_context) (HL_CFFIPointer* context) { - ALCcontext* alcContext = (ALCcontext*)context->ptr; + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)context->ptr; alcProcessContext (alcContext); } @@ -3434,7 +3474,7 @@ namespace lime { void lime_alc_resume_device (value device) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)val_data (device); alcDeviceResumeSOFT (alcDevice); #endif @@ -3444,7 +3484,7 @@ namespace lime { HL_PRIM void HL_NAME(hl_alc_resume_device) (HL_CFFIPointer* device) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = device ? (ALCdevice*)device->ptr : NULL; + ALCdevice* alcDevice = device ? (ALCdevice*)(uintptr_t)device->ptr : NULL; alcDeviceResumeSOFT (alcDevice); #endif @@ -3453,7 +3493,7 @@ namespace lime { void lime_alc_suspend_context (value context) { - ALCcontext* alcContext = (ALCcontext*)val_data (context); + ALCcontext* alcContext = (ALCcontext*)(uintptr_t)val_data (context); alcSuspendContext (alcContext); } @@ -3461,7 +3501,7 @@ namespace lime { HL_PRIM void HL_NAME(hl_alc_suspend_context) (HL_CFFIPointer* context) { - ALCcontext* alcContext = context ? (ALCcontext*)context->ptr : NULL; + ALCcontext* alcContext = context ? (ALCcontext*)(uintptr_t)context->ptr : NULL; alcSuspendContext (alcContext); } @@ -3510,12 +3550,25 @@ namespace lime { if (alSoftEventCallback) { + value ptrDevice; al_gc_mutex.Lock (); - alSoftEventCallback->Call (alloc_int((int)eventType), alloc_int((int)deviceType), CFFIPointer (device), message ? alloc_string(message) : alloc_null()); + if (alcObjects.find (device) != alcObjects.end ()) { + + ptrDevice = (value)alcObjects[device]; + + } + else { + + ptrDevice = CFFIPointer ((void*)(uintptr_t)device, gc_alc_object); + alcObjects[device] = ptrDevice; + + } al_gc_mutex.Unlock (); + alSoftEventCallback->Call (alloc_int ((int)eventType), alloc_int ((int)deviceType), ptrDevice, message ? alloc_string (message) : alloc_null ()); + } gc_set_top_of_stack((int*)0, true); @@ -3538,8 +3591,23 @@ namespace lime { if (alSoftEventCallback) { + HL_CFFIPointer* ptrDevice; al_gc_mutex.Lock (); + if (alcObjects.find (device) != alcObjects.end ()) { + + ptrDevice = (HL_CFFIPointer*)alcObjects[device]; + + } + else { + + ptrDevice = HLCFFIPointer ((void*)(uintptr_t)device, (hl_finalizer)gc_alc_object); + alcObjects[device] = ptrDevice; + + } + + al_gc_mutex.Unlock (); + vdynamic* _eventType = hl_alloc_dynamic (&hlt_i32); _eventType->v.i = (int)eventType; @@ -3549,9 +3617,7 @@ namespace lime { vdynamic* _message = hl_alloc_dynamic (&hlt_bytes); _message->v.bytes = (vbyte*)message; - alSoftEventCallback->Call (_eventType, _deviceType, HLCFFIPointer(device), _message); - - al_gc_mutex.Unlock (); + alSoftEventCallback->Call (_eventType, _deviceType, ptrDevice, _message); } @@ -3599,7 +3665,7 @@ namespace lime { bool lime_alc_reopen_device_soft(value device, HxString devicename, value attributes) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = (ALCdevice*)val_data (device); + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)val_data (device); if (!val_is_null (attributes)) { @@ -3635,7 +3701,7 @@ namespace lime { HL_PRIM bool HL_NAME(hl_alc_reopen_device_soft) (HL_CFFIPointer* device, hl_vstring* devicename, varray* attributes) { #ifdef LIME_OPENALSOFT - ALCdevice* alcDevice = (ALCdevice*)device->ptr; + ALCdevice* alcDevice = (ALCdevice*)(uintptr_t)device->ptr; ALCboolean result = alcReopenDeviceSOFT (alcDevice, devicename ? hl_to_utf8 (devicename->bytes) : NULL, attributes ? hl_aptr (attributes, ALCint) : NULL); return result == ALC_TRUE; #else @@ -3740,6 +3806,53 @@ namespace lime { } + value lime_alc_get_doublev_soft (value device, int param, int count) { + + #ifdef LIME_OPENALSOFT + ALCdevice* alcDevice = val_is_null (device) ? 0 : (ALCdevice*)(uintptr_t)val_data (device); + + ALCint64SOFT* values = new ALCint64SOFT[count]; + alcGetInteger64vSOFT (alcDevice, param, count, values); + + value result = alloc_array (count); + + for (int i = 0; i < count; i++) { + + val_array_set_i (result, i, alloc_float (values[i])); + + } + + delete[] values; + return result; + #else + return alloc_array (0); + #endif + + } + + + HL_PRIM varray* HL_NAME(hl_alc_get_doublev_soft) (HL_CFFIPointer* device, int param, int count) { + + #ifdef LIME_OPENALSOFT + ALCdevice* alcDevice = device ? (ALCdevice*)(uintptr_t)device->ptr : 0; + + ALCint64SOFT* values = new ALCint64SOFT[count]; + alcGetInteger64vSOFT (alcDevice, param, count, values); + + varray* result = hl_alloc_array (&hlt_f64, count); + + for (int i = 0; i < count; i++) { + + hl_aptr (result, double)[i] = (double)values[i]; + + } + + return result; + #else + return hl_alloc_array(&hlt_f64, 0); + #endif + + } DEFINE_PRIME3v (lime_al_auxf); @@ -3866,6 +3979,7 @@ namespace lime { DEFINE_PRIME1v (lime_alc_capture_start); DEFINE_PRIME1v (lime_alc_capture_stop); DEFINE_PRIME3v (lime_alc_capture_samples); + DEFINE_PRIME3 (lime_alc_get_doublev_soft); #define _TBYTES _OBJ (_I32 _BYTES) @@ -3971,7 +4085,7 @@ namespace lime { DEFINE_HL_PRIM (_VOID, hl_al_source_stopv, _I32 _ARR); DEFINE_HL_PRIM (_ARR, hl_al_source_unqueue_buffers, _TCFFIPOINTER _I32); DEFINE_HL_PRIM (_VOID, hl_al_source3f, _TCFFIPOINTER _I32 _F32 _F32 _F32); - DEFINE_HL_PRIM (_VOID, hl_al_source3i, _TCFFIPOINTER _I32 _DYN _I32 _I32); + DEFINE_HL_PRIM (_VOID, hl_al_source3i, _TCFFIPOINTER _I32 _DYN _I32 _DYN); DEFINE_HL_PRIM (_VOID, hl_al_sourcef, _TCFFIPOINTER _I32 _F32); DEFINE_HL_PRIM (_VOID, hl_al_sourcefv, _TCFFIPOINTER _I32 _ARR); DEFINE_HL_PRIM (_VOID, hl_al_sourcei, _TCFFIPOINTER _I32 _DYN); @@ -4000,6 +4114,7 @@ namespace lime { DEFINE_HL_PRIM (_VOID, hl_alc_capture_start, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_alc_capture_stop, _TCFFIPOINTER); DEFINE_HL_PRIM (_VOID, hl_alc_capture_samples, _TCFFIPOINTER _TBYTES _I32); + DEFINE_HL_PRIM (_ARR, hl_alc_get_doublev_soft, _TCFFIPOINTER _I32 _I32); } diff --git a/src/lime/_internal/backend/html5/HTML5AudioSource.hx b/src/lime/_internal/backend/html5/HTML5AudioSource.hx index 9657487c43..9960089275 100644 --- a/src/lime/_internal/backend/html5/HTML5AudioSource.hx +++ b/src/lime/_internal/backend/html5/HTML5AudioSource.hx @@ -1,38 +1,116 @@ package lime._internal.backend.html5; -import lime.media.AudioManager; +import lime.app.Event; import lime.math.Vector4; import lime.media.AudioSource; +import lime.utils.Float32Array; +import lime.utils.UInt8Array; + +#if lime_howlerjs +import js.html.audio.AudioNode; +import js.html.audio.AnalyserNode; +import js.html.audio.AudioBufferSourceNode; +import js.html.audio.BaseAudioContext; +import js.html.audio.ChannelSplitterNode; +import js.html.audio.MediaElementAudioSourceNode; +import js.html.MediaElement; +import js.lib.Float32Array as JSFloat32Array; +import lime.media.howlerjs.Howl; +import lime.media.howlerjs.Howler; +#end @:access(lime.media.AudioBuffer) class HTML5AudioSource { + public static function playSources(sources:Array):Void + { + for (source in sources) source.play(); + } + + public static function pauseSources(sources:Array):Void + { + for (source in sources) source.pause(); + } + + public static function stopSources(sources:Array):Void + { + for (source in sources) source.stop(); + } + + public var parent:AudioSource; + private var completed:Bool; private var gain:Float; - private var id:Int; - private var length:Int; + private var length:Float; + private var loopTime:Float; private var loops:Int; - private var parent:AudioSource; - private var playing:Bool; + private var pauseTime:Float; + private var peaks:Array; + private var pitch:Float; private var position:Vector4; + #if lime_howlerjs + public var onRefresh = new EventVoid>(); + public var id:Int; + + public var howl:Howl; + public var howlSound:Dynamic; + public var audioNode:AudioNode; + public var lastAudioNode:AudioNode; + + private var playing:Bool; + private var analyser:AnalyserNode; + private var analysers:Array; + private var channelSplitter:ChannelSplitterNode; + private var analyserAudioNode:AudioNode; + private var timeDomainData:JSFloat32Array; + private var timerID:Int; + #end public function new(parent:AudioSource) { this.parent = parent; - - id = -1; + length = 0; gain = 1; - position = new Vector4(); + pitch = 1; + #if lime_howlerjs + id = -1; + timerID = -1; + #end } - public function dispose():Void {} + public function dispose():Void + { + #if lime_howlerjs + timeDomainData = null; + + if (channelSplitter != null) + { + channelSplitter.disconnect(); + channelSplitter = null; + } - public function init():Void + if (analysers != null) + { + for (analyser in analysers) analyser.disconnect(); + analysers = null; + } + + if (analyser != null) + { + analyser.disconnect(); + analyser = null; + } + #end + } + + public function load():Void { #if lime_howlerjs - // Initialize the panner with default values - parent.buffer.src.pannerAttr( - { + if (parent.buffer != null) howl = parent.buffer.__srcHowl; + if (howl != null) + { + // Initialize the panner with default values + parent.buffer.src.pannerAttr({ coneInnerAngle: 360, coneOuterAngle: 360, coneOuterGain: 0, @@ -42,127 +120,274 @@ class HTML5AudioSource rolloffFactor: 1, panningModel: "equalpower" // Default to equalpower for better performance }); + howl.load(); + if (!loadAudio()) + { + var backend = this; + howl.on("load", function() + { + if (backend.playing) backend.play(); + }); + } + } + id = -1; #end } + public function unload():Void + { + // Howl sounds are automatically unloaded if it has stopped. + #if lime_howlerjs + disposeNode(); + howl = null; + id = -1; + #end + length = 0; + loopTime = 0; + pauseTime = 0; + } + public function play():Void { #if lime_howlerjs - if (playing || parent.buffer == null || parent.buffer.__srcHowl == null) + if (howl == null || (id != -1 && howl.playing(id))) return; + + playing = true; + completed = false; + + if (!loadAudio()) return; + + inline function setParams():Void { - return; + howl.rate(pitch, id); + howl.seek(pauseTime / 1000, id); + howl.volume(gain, id); + updateLoop(); } - playing = true; + if (howlSound != null && id != -1) + { + setParams(); + howl.play(id); + } + else + { + id = howl.play(); + setParams(); + } - var time = getCurrentTime(); + refreshNode(); + resetTimer(Std.int((length - pauseTime) / pitch)); + #end + } - completed = false; + private function disposeNode():Void + { + #if lime_howlerjs + howlSound = null; + audioNode = null; + lastAudioNode = null; + #end + } - var cacheVolume = untyped parent.buffer.__srcHowl._volume; - untyped parent.buffer.__srcHowl._volume = parent.gain; + private function refreshNode():Void + { + #if lime_howlerjs + disposeNode(); - id = parent.buffer.__srcHowl.play(); + howlSound = untyped howl._soundById(id); - untyped parent.buffer.__srcHowl._volume = cacheVolume; - // setGain (parent.gain); + if (untyped howlSound) + @:privateAccess + { + var node = untyped howlSound._node; - setPosition(parent.position); + if ((node is MediaElement)) + { + lime.utils.Log.warn("HTML5 Element Audios are not fully supported! (and buggy) Expect unexpected behaviour."); + } + else + { + if (untyped howlSound._panner) audioNode = untyped howlSound._panner; + else audioNode = untyped node; + } - parent.buffer.__srcHowl.on("end", howl_onEnd, id); + if (parent.__effects != null && parent.__effects.length > 0) + { + for (effect in parent.__effects) + { + if (lastAudioNode == null) + { + audioNode.disconnect(); + lastAudioNode = audioNode; + } + + for (node in effect.__audioNodes) + { + lastAudioNode.connect(node); + lastAudioNode = node; + } + } + lastAudioNode.connect(untyped Howler.masterGain); + } + + onRefresh.dispatch(this); + } + #end + } - // Calling setCurrentTime causes html5 audio to replay from this position on next frame - #if force_html5_audio - if (time == 0) setCurrentTime(time); + private function loadAudio():Bool + { + #if lime_howlerjs + if (length != 0) return true; + + length = howl.duration() * 1000; + return length != 0; #else - setCurrentTime(time); - #end + return false; #end } public function pause():Void { #if lime_howlerjs - playing = false; - - if (parent.buffer != null && parent.buffer.__srcHowl != null) + if (howl != null && id != -1) + { + pauseTime = howl.seek(id) * 1000; + howl.pause(id); + } + else { - parent.buffer.__srcHowl.pause(id); + pauseTime = 0; } + playing = false; + stopTimer(); #end } public function stop():Void { + pauseTime = 0; + #if lime_howlerjs + if (howl != null && id != -1) + { + howl.stop(id); + } playing = false; + stopTimer(); + #end + } - if (parent.buffer != null && parent.buffer.__srcHowl != null) + public function prepare(value:Float):Void + { + pauseTime = value + parent.offset; + if (pauseTime < 0 || !Math.isFinite(pauseTime)) pauseTime = 0; + + #if lime_howlerjs + if (howl != null && id != -1) { - parent.buffer.__srcHowl.stop(id); - parent.buffer.__srcHowl.off("end", howl_onEnd, id); + howl.stop(id); + howl.seek(pauseTime / 1000, id); } + playing = false; + stopTimer(); #end } // Event Handlers - private function howl_onEnd() + private inline function stopTimer():Void { #if lime_howlerjs - playing = false; + if (timerID != -1) + { + untyped clearInterval(timerID); + timerID = -1; + } + #end + } + + private inline function resetTimer(ms:Int):Void + { + #if lime_howlerjs + stopTimer(); + var me = this; + timerID = untyped setInterval(function() me.complete(), ms); + #end + } + + private function complete() + { + #if lime_howlerjs if (loops > 0) { + var wasLooping = howl.loop(id); loops--; - stop(); - // currentTime = 0; - play(); - return; + updateLoop(); + if (wasLooping && howl.playing(id)) + { + resetTimer(Std.int((length - (howl.seek(id) * 1000) - parent.offset) / howl.rate(id))); + howl.play(id); + } + else + { + resetTimer(Std.int((length - loopTime - parent.offset) / howl.rate(id))); + howl.seek((loopTime + parent.offset) / 1000, id); + howl.play(id); + } + pauseTime = loopTime; } - else if (parent.buffer != null && parent.buffer.__srcHowl != null) + else { - parent.buffer.__srcHowl.stop(id); - parent.buffer.__srcHowl.off("end", howl_onEnd, id); + howl.stop(id); + + stopTimer(); + playing = false; + completed = true; + pauseTime = 0; } - completed = true; parent.onComplete.dispatch(); #end } // Get & Set Methods - public function getCurrentTime():Int + public function getCurrentTime():Float { - if (id == -1) - { - return 0; - } - #if lime_howlerjs - if (completed) + var loaded = howl != null && id != -1; + if (completed || loaded && playing && !howl.playing(id)) { - return getLength(); + return length - parent.offset; } - else if (parent.buffer != null && parent.buffer.__srcHowl != null) + else if (loaded) { - var time = Std.int(parent.buffer.__srcHowl.seek(id) * 1000) - parent.offset; - if (time < 0) return 0; - return time; + return howl.seek(id) * 1000 - parent.offset; } #end - return 0; + return pauseTime - parent.offset; } - public function setCurrentTime(value:Int):Int + public function setCurrentTime(value:Float):Float { + pauseTime = value + parent.offset; + if (pauseTime < 0 || !Math.isFinite(pauseTime)) pauseTime = 0; + #if lime_howlerjs - if (parent.buffer != null && parent.buffer.__srcHowl != null) + if (howl != null && id != -1) { - // if (playing) buffer.__srcHowl.play (id); - var pos = (value + parent.offset) / 1000; - if (pos < 0) pos = 0; - parent.buffer.__srcHowl.seek(pos, id); + howl.seek(pauseTime / 1000, id); + if (howl.playing(id)) + { + if (pauseTime >= length && !completed) + { + completed = true; + resetTimer(0); + } + else resetTimer(Std.int((length - pauseTime - parent.offset) / howl.rate(id))); + } } #end @@ -177,38 +402,73 @@ class HTML5AudioSource public function setGain(value:Float):Float { #if lime_howlerjs - // set howler volume only if we have an active id. - // Passing -1 might create issues in future play()'s. - - if (parent.buffer != null && parent.buffer.__srcHowl != null && id != -1) + if (howl != null && id != -1) { - parent.buffer.__srcHowl.volume(value, id); + howl.volume(value, id); } #end - return gain = value; } - public function getLength():Int + public function getLatency():Float { - if (length != 0) + return 0; + } + + public function getLength():Float + { + #if lime_howlerjs + if (length == 0) { - return length; + length = howl.duration() * 1000; + if (length == 0) return 0; } + #end + + if (length <= parent.offset) return 0; + return length - parent.offset; + } + + public function setLength(value:Float):Float + { + length = value + parent.offset; #if lime_howlerjs - if (parent.buffer != null && parent.buffer.__srcHowl != null) + if (howl != null) { - return Std.int(parent.buffer.__srcHowl.duration() * 1000); + var duration = howl.duration() * 1000; + if (duration != 0 && (length <= 0 || length >= duration)) length = duration; + if (id != -1 && howl.playing(id)) resetTimer(Std.int((length - howl.seek(id) * 1000) / howl.rate(id))); } #end + updateLoop(); - return 0; + return value; } - public function setLength(value:Int):Int + public function getLoopTime():Float { - return length = value; + if (loopTime <= parent.offset) return 0; + return loopTime - parent.offset; + } + + public function setLoopTime(value:Float):Float + { + loopTime = value + parent.offset; + + #if lime_howlerjs + if (howl != null) + { + if (loopTime < 0) loopTime = 0; + else + { + var duration = howl.duration() * 1000; + if (loopTime >= duration) loopTime = duration; + } + } + #end + updateLoop(); + return value; } public function getLoops():Int @@ -218,68 +478,288 @@ class HTML5AudioSource public function setLoops(value:Int):Int { - return loops = value; + loops = value; + updateLoop(); + return value; } - public function getPitch():Float + private function updateLoop() { #if lime_howlerjs - return parent.buffer.__srcHowl.rate(); - #else - return 1; + if (howl != null && id != -1) + { + if (loops > 0) howl.loop(loopTime, length, id); + else howl.loop(false, id); + } #end } - public function setPitch(value:Float):Float + public function getPan():Float { + if (position == null) position = new Vector4(); + return position.x; + } + + public function setPan(value:Float):Float + { + if (position == null) position = new Vector4(); + position.setTo(value, 0, -Math.sqrt(1 - value * value)); + #if lime_howlerjs - parent.buffer.__srcHowl.rate(value); + if (howl != null && id != -1) + { + //howl.pos(0, 0, 0, id); + howl.stereo(value, id); + } #end + return value; + } - return getPitch(); + public function getPitch():Float + { + return pitch; } - public function getPosition():Vector4 + public function setPitch(value:Float):Float { #if lime_howlerjs - // This should work, but it returns null (But checking the inside of the howl, the _pos is actually null... so ¯\_(ツ)_/¯) - /* - var arr = parent.buffer.__srcHowl.pos()) - position.x = arr[0]; - position.y = arr[1]; - position.z = arr[2]; - */ + if (howl != null && id != -1) + { + if (value > 1e-2) + { + howl.rate(value, id); + if (playing && !howl.playing(id)) + { + howl.seek(pauseTime / 1000, id); + howl.play(id); + } + + resetTimer(Std.int((length - howl.seek(id) * 1000) / howl.rate(id))); + } + else if (playing) + { + pauseTime = howl.seek(id) * 1000; + howl.pause(id); + } + } #end + return pitch = value; + } + public function getPlaying():Bool + { + #if lime_howlerjs + if (howl != null && id != -1) return playing && howl.playing(id); + #end + return false; + } + + public function getPosition():Vector4 + { + if (position == null) position = new Vector4(); return position; } public function setPosition(value:Vector4):Vector4 { - position.x = value.x; - position.y = value.y; - position.z = value.z; - position.w = value.w; + if (position == null) position = new Vector4(); + position.setTo(value.x, value.y, value.z); - #if lime_howlerjs - if (parent.buffer != null && parent.buffer.__srcHowl != null && parent.buffer.__srcHowl.pos != null) parent.buffer.__srcHowl.pos(position.x, position.y, position.z, id); - // There are more settings to the position of the sound on the "pannerAttr()" function of howler. Maybe somebody who understands sound should look into it? - #end + /*#if lime_howlerjs + if (howl != null && id != -1) + { + howl.pos(position.x, position.y, position.z, id); + } + #end*/ return position; } - public function getLatency():Float + // Waveform related functions + public function getPeaks(offsetMs:Float):Array + { + if (peaks == null) peaks = []; + + #if lime_howlerjs + updateAnalyserAudioNode(); + if (analyserAudioNode == null || !playing) + { + for (i in 0...(parent.buffer.channels > 0 ? parent.buffer.channels : 2)) peaks[i] = 0; + return peaks; + } + + if (timeDomainData == null) timeDomainData = new JSFloat32Array(2048); + + var min:Float, max:Float; + for (i in 0...channelSplitter.numberOfOutputs) + { + min = 1; + max = -1; + + analysers[i].fftSize = 2048; + analysers[i].getFloatTimeDomainData(timeDomainData); + for (v in timeDomainData) + { + if (v > max) max = v; + else if (v < min) min = v; + } + + peaks[i] = (max - min) / 2; + } + #end + return peaks; + } + + public function getFloatTimeDomainData(array:Float32Array, size:Int, channel:Int, offset:Int):Int { - var ctx = AudioManager.context.web; - if (ctx != null) + #if lime_howlerjs + updateAnalyserAudioNode(); + if (analyserAudioNode == null || !playing) return 0; + + var bits = 0; + while ((size >>= 1) > 0) bits++; + + if (channel == -1) + { + analyser.fftSize = 1 << bits; + analyser.getFloatTimeDomainData(array); + return analyser.fftSize; + } + else if (analysers[channel] != null) { - var baseLatency:Float = untyped ctx.baseLatency != null ? untyped ctx.baseLatency : 0; - var outputLatency:Float = untyped ctx.outputLatency != null ? untyped ctx.outputLatency : 0; + var analyser = analysers[channel]; + analyser.fftSize = 1 << bits; + analyser.getFloatTimeDomainData(array); + return analyser.fftSize; + } + #end + + return 0; + } + + public function getByteTimeDomainData(array:UInt8Array, size:Int, channel:Int, offset:Int):Int + { + #if lime_howlerjs + updateAnalyserAudioNode(); + if (analyserAudioNode == null || !playing) return 0; + + var bits = 0; + while ((size >>= 1) > 0) bits++; - return (baseLatency + outputLatency) * 1000; + if (channel == -1) + { + analyser.fftSize = 1 << bits; + analyser.getByteTimeDomainData(array); + return analyser.fftSize; + } + else if (analysers[channel] != null) + { + var analyser = analysers[channel]; + analyser.fftSize = 1 << bits; + analyser.getByteTimeDomainData(array); + return analyser.fftSize; } + #end return 0; } + + inline function updateAnalyserAudioNode():Void + { + #if lime_howlerjs + var previousAnalyserAudioNode = analyserAudioNode; + if (howlSound != null && (untyped howlSound._node)) + { + if (untyped howlSound._node.bufferSource) analyserAudioNode = untyped howlSound._node.bufferSource; + else analyserAudioNode = null; + } + else analyserAudioNode = null; + + if (previousAnalyserAudioNode != analyserAudioNode) + { + if (analyserAudioNode != null) + { + var context:BaseAudioContext = untyped audioNode.context; + + var channels = parent.buffer.channels > 0 ? parent.buffer.channels : 2; + if (channelSplitter == null || channelSplitter.context != context || channelSplitter.numberOfOutputs != channels) + channelSplitter = new ChannelSplitterNode(context, {numberOfOutputs: channels}); + + var analyser:AnalyserNode; + if (analysers == null) analysers = []; + for (i in 0...channels) + { + analyser = analysers[i]; + if (analyser == null || analyser.context != context) analysers[i] = analyser = new AnalyserNode(context); + else analyser.disconnect(); + + analyser.maxDecibels = 0; + analyser.minDecibels = -120; + + channelSplitter.connect(analyser, i); + } + + analyser = this.analyser; + if (analyser == null || analyser.context != context) analyser = this.analyser = new AnalyserNode(context); + else analyser.disconnect(); + + analyser.maxDecibels = 0; + analyser.minDecibels = -120; + + analyserAudioNode.connect(channelSplitter); + analyserAudioNode.connect(analyser); + } + } + #end + } + + // Real-time audio effects + public function addEffect(index:Int):Void + { + #if lime_howlerjs + if (audioNode != null) + @:privateAccess + { + if (lastAudioNode == null) lastAudioNode = audioNode; + lastAudioNode.disconnect(); + + var effect = parent.__effects[index]; + for (node in effect.__audioNodes) + { + lastAudioNode.connect(node); + lastAudioNode = node; + } + lastAudioNode.connect(untyped Howler.masterGain); + } + #end + } + + public function updateEffect(index:Int):Void + { + // do nothing + } + + public function removeEffect(index:Int):Void + { + #if lime_howlerjs + if (audioNode != null && lastAudioNode != null) + @:privateAccess + { + lastAudioNode.disconnect(); + lastAudioNode = audioNode; + + for (i => effect in parent.__effects) + { + if (i == index) continue; + + for (node in effect.__audioNodes) + { + lastAudioNode.connect(node); + lastAudioNode = node; + } + } + lastAudioNode.connect(untyped Howler.masterGain); + } + #end + } } diff --git a/src/lime/_internal/backend/native/NativeAudioSource.hx b/src/lime/_internal/backend/native/NativeAudioSource.hx index c43e98bf5b..928d93cab8 100644 --- a/src/lime/_internal/backend/native/NativeAudioSource.hx +++ b/src/lime/_internal/backend/native/NativeAudioSource.hx @@ -1,581 +1,807 @@ package lime._internal.backend.native; import haxe.Int64; -import haxe.Timer; + +import sys.thread.Thread; +import sys.thread.Mutex; + import lime.app.Application; +import lime.app.Event; +import lime.math.Vector2; import lime.math.Vector4; import lime.media.openal.AL; import lime.media.openal.ALBuffer; +import lime.media.openal.ALC; +import lime.media.openal.ALFilter; import lime.media.openal.ALSource; -import lime.media.vorbis.VorbisFile; +import lime.media.AudioBuffer; +import lime.media.AudioDecoder; +import lime.media.AudioEffect; import lime.media.AudioManager; import lime.media.AudioSource; +import lime.system.System; +import lime.utils.ArrayBuffer; +import lime.utils.ArrayBufferView; +import lime.utils.ArrayBufferView.ArrayBufferIO; +import lime.utils.Float32Array; import lime.utils.UInt8Array; +@:access(lime.media.AudioBuffer) +@:access(lime.media.AudioDecoder) +@:access(lime.media.AudioManager) +@:access(lime.media.AudioSource) +@:access(lime.utils.ArrayBufferView) #if !lime_debug @:fileXml('tags="haxe,release"') @:noDebug #end -@:access(lime.media.AudioBuffer) class NativeAudioSource { - private static var STREAM_BUFFER_SIZE = 48000; - #if (native_audio_buffers && !macro) - private static var STREAM_NUM_BUFFERS = Std.parseInt(haxe.macro.Compiler.getDefine("native_audio_buffers")); - #else - private static var STREAM_NUM_BUFFERS = 3; - #end - private static var STREAM_TIMER_FREQUENCY = 100; - - #if lime_openalsoft - private static var hasDirectChannelsExt:Null; - private static var hasALSoftLatencyExt:Null; - #end + /** + What size the buffers (in hertz, excluding channels, bitsPerSample) will be to use for stream processing. + **/ + public static final STREAM_BUFFER_SAMPLES:Int = 0x4000; + + /** + How much length in bytes can a buffer hold maximum (have to be pow of 2). + This is used when converting `STREAM_BUFFER_SAMPLES` to byteLength in intialize. + **/ + public static final STREAM_BUFFER_MAX_LENGTH:Int = 0x10000; + + /** + How much buffers can the stream processing hold minimally. + **/ + public static final STREAM_MIN_BUFFERS:Int = 3; + + /** + What buffer limit can a stream processing hold maximally, must be higher than `STREAM_MIN_BUFFERS`. + **/ + public static final STREAM_MAX_BUFFERS:Int = 8; + + /** + How much buffers can it be used to be passed into stream processing. + This is for to preserve previous buffers to use whenever a seeking/small rewinding is requested. + Recommended to be below and not equal to `STREAM_MAX_BUFFERS`. + **/ + public static final STREAM_USABLE_BUFFERS:Int = 6; + + /** + How much buffers can it be played in stream processing tick. + This is to prevent to hit the max hardware buffer limit which is usually `1024`, and hxvlc can sometime uses more buffers maximum of `512`. + **/ + public static final STREAM_FLUSH_BUFFERS:Int = 4; + + /** + How much buffers to be prepared in normal starting. + **/ + public static final STREAM_START_BUFFERS:Int = 1; + + /** + How much buffers to be processed and prepared in `prepare()` function before playing. + **/ + public static final STREAM_PREPARE_BUFFERS:Int = 3; + + /** + How much buffers can be processed in a stream processing tick. + **/ + public static final STREAM_PROCESS_BUFFERS:Int = 1; + + /** + What delay (in seconds) to wait between updating the buffers. + **/ + public static final STREAM_UPDATE_DELAY:Float = 0.04; + + /** + What ticks it need to be passed in stream flush tick to process, unless a source doesnt have much buffers to play. + **/ + public static final STREAM_PROCESS_TICKS:Int = 3; + + /** + How much buffer views to be reused for the next audio source. + This is for to prevent reallocating potentially the same buffer size constantly. + **/ + public static final POOL_MAX_BUFFERS:Int = 32; + + public static function playSources(sources:Array):Void + { + var alSources = [], streams:Array = [], backend:NativeAudioSource; + for (source in sources) + { + backend = source.__backend; + if (backend != null && backend.loaded && !backend.playing && !backend.completed) + { + if (!backend.prepared) backend.prepare(backend.getCurrentTime()); + alSources.push(backend.source); - private var buffers:Array; - private var bufferTimeBlocks:Array; - private var completed:Bool; - private var dataLength:Int; - private var format:Int; - private var handle:ALSource; - private var length:Null; - private var loops:Int; - private var parent:AudioSource; - private var playing:Bool; - private var position:Vector4; - private var samples:Int; - private var stream:Bool; - private var streamTimer:Timer; - private var timer:Timer; + backend.playing = true; + backend.prepared = false; - public function new(parent:AudioSource) - { - this.parent = parent; + // We have to resume the stream processing after it plays, or it'll throw away prepared buffers. + if (backend.streamed && !backend.streamEnded) streams.push(backend); + + backend.resetTimer((backend.loopPoints[1] - backend.pauseSample) + * 1000.0 / source.buffer.sampleRate / backend.getPitch()); + } + } - position = new Vector4(); + AL.sourcePlayv(alSources); + + for (backend in streams) backend.resumeStream(false); } - public function dispose():Void + public static function pauseSources(sources:Array):Void { - if (handle != null) + var alSources = [], backend:NativeAudioSource; + for (source in sources) { - if (Application.current != null && !stream) + backend = source.__backend; + if (backend != null && backend.loaded) { - if (Application.current.onUpdate.has(checkPlay)) - { - // trace('[AUDIO] removed play check event!'); - Application.current.onUpdate.remove(checkPlay); - } + backend.stopTimer(); + if (backend.streamed) backend.stopStream(false); + + backend.pauseSample = backend.getCurrentSampleOffset(); + backend.playing = false; + backend.completed = false; + + alSources.push(backend.source); } - stop(); - AL.sourcei(handle, AL.BUFFER, null); - AL.deleteSource(handle); - if (buffers != null) + } + + AL.sourcePausev(alSources); + } + + public static function stopSources(sources:Array):Void + { + var alSources = [], backend:NativeAudioSource; + for (source in sources) + { + backend = source.__backend; + if (backend != null) { - for (buffer in buffers) + backend.playing = false; + backend.completed = false; + + if (backend.loaded) { - AL.deleteBuffer(buffer); + alSources.push(backend.source); + backend.pauseSample = 0; + + backend.stopTimer(); + if (backend.streamed) backend.stopStream(false); } - buffers = null; } - handle = null; } + + AL.sourceStopv(alSources); } - public function init():Void + private static var bufferViewPool:Array = []; + private static var streamAudios:Array = []; + private static var queuedStreamAudios:Array = []; + private static var playingAudios:Array = []; + private static var threadRunning:Bool = false; + private static var streamThread:Thread; + private static var streamMutex:Mutex = new Mutex(); + private static var queueMutex:Mutex = new Mutex(); + + public var onRefresh = new EventVoid>(); + public var parent:AudioSource; + public var source:ALSource; + + private var completed:Bool; + private var format:Int; + private var loops:Int; + private var pauseSample:Int; + private var peaks:Array; + private var playing:Bool; + private var position:Vector4; + private var samples:Int; + private var streamed:Bool; + private var timeEnd:Float; + private var lastReadSampleOffset:Int; + private var lastReadTime:Float; + + private var standaloneBuffer:Bool; + private var buffer:ALBuffer; + private var standaloneDecoder:Bool; + private var decoder:AudioDecoder; + private var anglesArray:Array; + private var loopPoints:Array; + private var mins:Array; + private var maxs:Array; + + public var bufferLen:Int; + public var queuedBuffers:Int; + public var filledBuffers:Int; + public var streamLoops:Int; + public var streamEnded:Bool; + public var streaming:Bool; + public var loaded:Bool; + public var pending:Bool; + + // ORDERING IS CURRENT TO NEXT, STARTS FROM THE LENGTH OF THE ARRAYS + public var bufferViews:Array; + public var bufferCurs:Array; + public var bufferLens:Array; + + public var mutex:Mutex; + public var seekMutex:Mutex; + private var prepared:Bool; + private var buffers:Array; + private var nextBuffer:Int = 0; + + public function new(parent:AudioSource) { - #if lime_openalsoft - if (hasALSoftLatencyExt == null) - { - hasALSoftLatencyExt = AL.isExtensionPresent("AL_SOFT_source_latency"); - } - #end + this.parent = parent; + init(); + } + + private function init():Void + { + if (source != null) return; + + source = AL.createSource(); + if (source == null) return; + + AL.sourcef(source, AL.MAX_GAIN, 10); + AL.sourcef(source, AL.MAX_DISTANCE, 1); + + if (loopPoints == null) loopPoints = [0, 0]; + if (anglesArray == null) anglesArray = [Math.PI / 6, -Math.PI / 6]; + + if (AudioManager.__directChannelsExtSupported) AL.sourcei(source, AL.DIRECT_CHANNELS_SOFT, AL.REMIX_UNMATCHED_SOFT); + if (AudioManager.__spatializeSupported) AL.sourcei(source, AL.SOURCE_SPATIALIZE_SOFT, AL.FALSE); + if (AudioManager.__stereoAnglesSupported) AL.sourcefv(source, AL.STEREO_ANGLES, anglesArray); - dataLength = 0; - format = 0; + onRefresh.dispatch(this); + } + + public function dispose():Void + { + loopPoints = null; + mins = null; + maxs = null; - if (parent.buffer.channels == 1) + position = null; + + if (buffers != null) { - if (parent.buffer.bitsPerSample == 8) - { - format = AL.FORMAT_MONO8; - } - else if (parent.buffer.bitsPerSample == 16) - { - format = AL.FORMAT_MONO16; - } + AL.deleteBuffers(buffers); + buffers = null; } - else if (parent.buffer.channels == 2) + + bufferCurs = null; + bufferLens = null; + + if (source != null) { - if (parent.buffer.bitsPerSample == 8) - { - format = AL.FORMAT_STEREO8; - } - else if (parent.buffer.bitsPerSample == 16) - { - format = AL.FORMAT_STEREO16; - } + AL.deleteSource(source); + source = null; } - if (parent.buffer.__srcVorbisFile != null) + mutex = null; + seekMutex = null; + } + + public function load():Void + { + init(); + + loaded = source != null && !parent.buffer.__isDisposed; + streamed = loaded && parent.buffer.data == null && parent.buffer.decoder != null && !parent.buffer.decoder.__isDisposed; + format = AudioBuffer.__getALFormat(parent.buffer.bitsPerSample, parent.buffer.channels); + + if (streamed) { - stream = true; + if (mutex == null) mutex = new Mutex(); + if (seekMutex == null) seekMutex = new Mutex(); + mutex.acquire(); + + decoder = parent.buffer.decoder.clone(); + standaloneDecoder = decoder != null; + if (!standaloneDecoder) decoder = parent.buffer.decoder; - var vorbisFile = parent.buffer.__srcVorbisFile; - dataLength = Std.int(Int64.toInt(vorbisFile.pcmTotal()) * parent.buffer.channels * (parent.buffer.bitsPerSample / 8)); + samples = Int64.toInt(decoder.total()); - buffers = new Array(); - bufferTimeBlocks = new Array(); + bufferLen = STREAM_BUFFER_SAMPLES * parent.buffer.channels * (parent.buffer.bitsPerSample >> 3); + if (bufferLen > STREAM_BUFFER_MAX_LENGTH) bufferLen = STREAM_BUFFER_MAX_LENGTH; - for (i in 0...STREAM_NUM_BUFFERS) + if (buffers == null) buffers = AL.genBuffers(STREAM_FLUSH_BUFFERS); + if (bufferCurs == null) bufferCurs = [for (i in 0...STREAM_MAX_BUFFERS) 0]; + if (bufferLens == null) bufferLens = [for (i in 0...STREAM_MAX_BUFFERS) 0]; + + bufferViews = [for (i in 0...STREAM_MAX_BUFFERS) { - buffers.push(AL.createBuffer()); - bufferTimeBlocks.push(0); - } + var data = bufferViewPool.pop(); + if (data == null) data = new UInt8Array(bufferLen); + else + { + if (data.byteLength < bufferLen) data.buffer = new ArrayBuffer(bufferLen); + data.byteLength = bufferLen; + data.length = bufferLen; + } + data; + }]; + + // Initialize the openal buffers first by allocating, before processing them. + for (i in 0...STREAM_FLUSH_BUFFERS) AL.bufferData(buffers[i], format, bufferViews[i], bufferLen, parent.buffer.sampleRate); + + nextBuffer = 0; - handle = AL.createSource(); + mutex.release(); } - else + else if (loaded && parent.buffer.data != null) { - if (parent.buffer.__srcBuffer == null) + samples = idiv(parent.buffer.data.byteLength, (parent.buffer.bitsPerSample >> 3) * parent.buffer.channels); + + var shouldGenerateBuffer = parent.buffer.__srcBuffer == null; + if (!shouldGenerateBuffer && AL.getBufferi(parent.buffer.__srcBuffer, AL.SIZE) != parent.buffer.data.byteLength) { - parent.buffer.__srcBuffer = AL.createBuffer(); + AL.deleteBuffer(parent.buffer.__srcBuffer); + shouldGenerateBuffer = true; + } + if (shouldGenerateBuffer) + { + parent.buffer.__srcBuffer = AL.createBuffer(); if (parent.buffer.__srcBuffer != null) { - AL.bufferData(parent.buffer.__srcBuffer, format, parent.buffer.data, parent.buffer.data.length, parent.buffer.sampleRate); + AL.bufferData(parent.buffer.__srcBuffer, format, parent.buffer.data, parent.buffer.data.byteLength, parent.buffer.sampleRate); } } - dataLength = parent.buffer.data.length; + standaloneBuffer = false; + buffer = parent.buffer.__srcBuffer; + loaded = buffer != null; + streamed = false; - handle = AL.createSource(); - - if (handle != null) - { - AL.sourcei(handle, AL.BUFFER, parent.buffer.__srcBuffer); - } + if (loaded) AL.sourcei(source, AL.BUFFER, buffer); } - - #if lime_openalsoft - if (hasDirectChannelsExt == null) + else { - hasDirectChannelsExt = AL.isExtensionPresent("AL_SOFT_direct_channels") && AL.isExtensionPresent("AL_SOFT_direct_channels_remix"); + samples = 0; + loaded = false; + streamed = false; } - if (hasDirectChannelsExt) + if (loaded) { - AL.sourcei(handle, AL.DIRECT_CHANNELS_SOFT, AL.REMIX_UNMATCHED_SOFT); + AL.sourceRewind(source); } - #end - samples = Std.int((dataLength * 8.0) / (parent.buffer.channels * parent.buffer.bitsPerSample)); + loopPoints[0] = 0; + loopPoints[1] = samples; + streamLoops = 0; + } - if (Application.current != null && !stream) + public function unload():Void + { + if (loaded) { - if (!Application.current.onUpdate.has(checkPlay)) + if (streamed) { - // trace('[AUDIO] added play check event!'); - Application.current.onUpdate.add(checkPlay); - } - } - } + streamMutex.acquire(); + queuedStreamAudios.remove(this); + removeStream(); - public function play():Void - { - /*var pitch:Float = AL.getSourcef (handle, AL.PITCH); - trace(pitch); - AL.sourcef (handle, AL.PITCH, pitch*0.9); - pitch = AL.getSourcef (handle, AL.PITCH); - trace(pitch); */ - /*var pos = getPosition(); - trace(AL.DISTANCE_MODEL); - AL.distanceModel(AL.INVERSE_DISTANCE); - trace(AL.DISTANCE_MODEL); - AL.sourcef(handle, AL.ROLLOFF_FACTOR, 5); - setPosition(new Vector4(10, 10, -100)); - pos = getPosition(); - trace(pos); */ - /*var filter = AL.createFilter(); - trace(AL.getErrorString()); + AL.sourceStop(source); + AL.sourceUnqueueBuffers(source, AL.getSourcei(source, AL.BUFFERS_QUEUED)); + queuedBuffers = filledBuffers = 0; - AL.filteri(filter, AL.FILTER_TYPE, AL.FILTER_LOWPASS); - trace(AL.getErrorString()); + if (standaloneDecoder) decoder.dispose(); + standaloneDecoder = false; + decoder = null; - AL.filterf(filter, AL.LOWPASS_GAIN, 0.5); - trace(AL.getErrorString()); + if (bufferViews != null) + { + for (data in bufferViews) if (bufferViewPool.length < POOL_MAX_BUFFERS) bufferViewPool.push(data); + bufferViews = null; + } - AL.filterf(filter, AL.LOWPASS_GAINHF, 0.5); - trace(AL.getErrorString()); + streamMutex.release(); + } + else + { + AL.sourcei(source, AL.BUFFER, AL.NONE); - AL.sourcei(handle, AL.DIRECT_FILTER, filter); - trace(AL.getErrorString()); */ + if (standaloneBuffer) AL.deleteBuffer(buffer); + standaloneBuffer = false; + buffer = null; + } - if (playing || handle == null) - { - return; + streamed = loaded = false; } - playing = true; + if (loopPoints != null) loopPoints[0] = loopPoints[1] = 0; + pauseSample = 0; + } + + public function play():Void + { + if (!loaded || playing) return; - if (stream) + playing = true; + if (prepared) { - setCurrentTime(getCurrentTime()); + prepared = false; + + resetTimer((loopPoints[1] - pauseSample) * 1000.0 / parent.buffer.sampleRate / getPitch()); + AL.sourcePlay(source); - streamTimer = new Timer(STREAM_TIMER_FREQUENCY); - streamTimer.run = streamTimer_onRun; + if (streamed && !streamEnded) resumeStream(false); } else { - var time = completed ? 0 : getCurrentTime(); - - setCurrentTime(time); + setCurrentSampleOffset(pauseSample); } } public function pause():Void { - playing = false; + if (!loaded) return; - if (handle == null) return; - AL.sourcePause(handle); + stopTimer(); + if (streamed) stopStream(false); - if (streamTimer != null) - { - streamTimer.stop(); - } + pauseSample = getCurrentSampleOffset(); + playing = false; + completed = false; - if (timer != null) - { - timer.stop(); - } + AL.sourcePause(source); } - private function readVorbisFileBuffer(vorbisFile:VorbisFile, length:Int):UInt8Array + public function prepare(time:Float):Void { - #if lime_vorbis - var buffer = new UInt8Array(length); - var read = 0, total = 0, readMax; - - for (i in 0...STREAM_NUM_BUFFERS-1) - { - bufferTimeBlocks[i] = bufferTimeBlocks[i + 1]; - } - bufferTimeBlocks[STREAM_NUM_BUFFERS-1] = vorbisFile.timeTell(); + if (!loaded) return; - while (total < length) - { - readMax = 4096; + var sampleOffset = Std.int((time + parent.offset) / 1000 * parent.buffer.sampleRate); + if (sampleOffset < 0) sampleOffset = 0; - if (readMax > length - total) - { - readMax = length - total; - } + if (prepared && pauseSample == sampleOffset) return; - read = vorbisFile.read(buffer.buffer, total, readMax); + playing = false; + pauseSample = sampleOffset; + completed = sampleOffset >= loopPoints[1]; + prepared = !completed; - if (read > 0) + if (prepared) + { + if (streamed) { - total += read; + mutex.acquire(); + stopStream(true); + AL.sourceStop(source); + snapBuffersToSample(sampleOffset, false, STREAM_PREPARE_BUFFERS); + mutex.release(); } else { - break; + AL.sourcePause(source); + AL.sourcei(source, AL.SAMPLE_OFFSET, sampleOffset); } } + else + { + pauseSample = 0; - return buffer; - #else - return null; - #end + if (streamed) stopStream(false); + AL.sourceStop(source); + } } - private function refillBuffers(buffers:Array = null):Void + public function stop():Void { - #if lime_vorbis - var vorbisFile = null; - var position = 0; + playing = false; + completed = false; - if (buffers == null) + if (loaded) { - var buffersProcessed:Int = AL.getSourcei(handle, AL.BUFFERS_PROCESSED); + pauseSample = 0; - if (buffersProcessed > 0) - { - vorbisFile = parent.buffer.__srcVorbisFile; - position = Int64.toInt(vorbisFile.pcmTell()); + stopTimer(); + if (streamed) stopStream(false); + AL.sourceStop(source); + } + } - if (position < dataLength) - { - buffers = AL.sourceUnqueueBuffers(handle, buffersProcessed); - } - } + // Event Handlers + private function stopTimer():Void + { + var idx = playingAudios.indexOf(this); + if (idx != -1) + { + if (playingAudios.length == 1) Application.current.onUpdate.remove(timerHandler); + else playingAudios[idx] = playingAudios[playingAudios.length - 1]; + playingAudios.pop(); } + } - if (buffers != null) + private function resetTimer(ms:Float):Void + { + if (!playingAudios.contains(this)) + { + if (!Application.current.onUpdate.has(timerHandler)) Application.current.onUpdate.add(timerHandler); + playingAudios.push(this); + } + + timeEnd = AudioManager.getTimer() + ms; + } + + private static function timerHandler(_):Void + { + var timer = AudioManager.getTimer(), i = playingAudios.length, backend:NativeAudioSource; + + while (i-- > 0) { - if (vorbisFile == null) + if ((backend = playingAudios[i]) == null) { - vorbisFile = parent.buffer.__srcVorbisFile; - position = Int64.toInt(vorbisFile.pcmTell()); - } + if (playingAudios.length == 1) Application.current.onUpdate.remove(timerHandler); + else playingAudios[i] = playingAudios[playingAudios.length - 1]; + playingAudios.pop(); - var numBuffers = 0; - var data; + continue; + } + else if (timer < backend.timeEnd) continue; - for (buffer in buffers) + if (backend.streamed) { - if (dataLength - position >= STREAM_BUFFER_SIZE) + if (backend.streaming && backend.streamLoops == 0 && backend.queuedBuffers > 1) { - data = readVorbisFileBuffer(vorbisFile, STREAM_BUFFER_SIZE); - AL.bufferData(buffer, format, data, data.length, parent.buffer.sampleRate); - position += STREAM_BUFFER_SIZE; - numBuffers++; + var sampleOffset = backend.getCurrentSampleOffset(); + var remaining = (backend.loopPoints[1] - sampleOffset) * 1000.0 / backend.parent.buffer.sampleRate / backend.getPitch(); + backend.resetTimer(remaining); } - else if (position < dataLength) + else { - data = readVorbisFileBuffer(vorbisFile, dataLength - position); - AL.bufferData(buffer, format, data, data.length, parent.buffer.sampleRate); - numBuffers++; - break; + backend.complete(timer - backend.timeEnd); } } - - AL.sourceQueueBuffers(handle, numBuffers, buffers); - - // OpenAL can unexpectedly stop playback if the buffers run out - // of data, which typically happens if an operation (such as - // resizing a window) freezes the main thread. - // If AL is supposed to be playing but isn't, restart it here. - if (playing && handle != null && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.STOPPED) + else { - AL.sourcePlay(handle); + backend.complete(timer - backend.timeEnd); } } - #end } - public function stop():Void + private function complete(latency:Float):Void { - if (playing && handle != null && AL.getSourcei(handle, AL.SOURCE_STATE) == AL.PLAYING) + if (loops > 0) { - AL.sourceStop(handle); - } + inline function fallback() + { + playing = true; + setCurrentSampleOffset(loopPoints[0]); + } - playing = false; + if (streamed) + { + mutex.acquire(); + if (streamLoops > 0) + { + loops -= streamLoops; + streamLoops = 0; + pauseSample = loopPoints[0]; + resetTimer(((loopPoints[1] - pauseSample) * 1000.0 / parent.buffer.sampleRate + latency) / getPitch()); + mutex.release(); + } + else + { + loops--; + mutex.release(); + fallback(); + } + } + else + { + if (AudioManager.__loopPointsSupported && AL.getSourcei(source, AL.LOOPING) == AL.TRUE) + { + pauseSample = loopPoints[0]; + resetTimer(((loopPoints[1] - pauseSample) * 1000.0 / parent.buffer.sampleRate + latency) / getPitch()); + } + else + { + fallback(); + } - if (streamTimer != null) - { - streamTimer.stop(); + if (--loops == 0) AL.sourcei(source, AL.LOOPING, AL.FALSE); + } } - - if (timer != null) + else { - timer.stop(); + completed = true; + playing = false; + pauseSample = 0; + stopTimer(); + if (streamed) stopStream(false); } - setCurrentTime(0); + parent.onComplete.dispatch(); } - // Event Handlers - private function streamTimer_onRun():Void + // Get & Set Methods + public function getCurrentTime():Float { - refillBuffers(); + if (loaded) return (getCurrentSampleOffset() * 1000.0 / parent.buffer.sampleRate) - parent.offset; + else return 0; } - private function timer_onRun():Void + private function getCurrentSampleOffset():Int { - if (loops > 0) + if (completed) { - playing = false; - loops--; - setCurrentTime(0); - play(); - return; + return loopPoints[1]; } - else + else if (!playing) { - stop(); + return pauseSample; + } + else if (AL.getSourcei(source, AL.SOURCE_STATE) == AL.STOPPED && (!streamed || streamEnded)) + { + return loopPoints[1]; } - completed = true; - parent.onComplete.dispatch(); - } - - private function checkPlay(_):Void - { - final finished:Bool = AL.getSourcei(handle, AL.SOURCE_STATE) != AL.PLAYING; - - if (!finished) return; - if (loops > 0) + var sampleOffset:Int; + if (streamed) { - playing = false; - loops--; - setCurrentTime(0); - play(); - return; + seekMutex.acquire(); + if (queuedBuffers == 0) + { + sampleOffset = pauseSample; + if (filledBuffers > 0) sampleOffset += STREAM_BUFFER_SAMPLES; + } + else + { + sampleOffset = AL.getSourcei(source, AL.SAMPLE_OFFSET) + bufferCurs[STREAM_MAX_BUFFERS - queuedBuffers]; + if (AL.getSourcei(source, AL.SOURCE_STATE) == AL.STOPPED) sampleOffset += STREAM_BUFFER_SAMPLES; + } + seekMutex.release(); } else { - if (!completed) stop(); + sampleOffset = AL.getSourcei(source, AL.SAMPLE_OFFSET); } - if (!completed) + if (loops > streamLoops && sampleOffset >= loopPoints[1]) { - // trace('[AUDIO] audio finished playing!'); - parent.onComplete.dispatch(); + if (loopPoints[0] >= loopPoints[1]) return loopPoints[0]; + else return ((sampleOffset - loopPoints[0]) % (loopPoints[1] - loopPoints[0])) + loopPoints[0]; } - completed = true; + else return sampleOffset; } - // Get & Set Methods - public function getCurrentTime():Int + public function setCurrentTime(value:Float):Float { - if (completed) + if (!loaded) return 0; + + setCurrentSampleOffset(Std.int((value + parent.offset) / 1000 * parent.buffer.sampleRate)); + return value; + } + + private function setCurrentSampleOffset(value:Int):Void + { + pauseSample = value > 0 ? value : 0; + + if (streamed) { - return getLength(); + mutex.acquire(); + AL.sourceStop(source); } - else if (handle != null) + else { - if (stream) - { - var time = (Std.int(bufferTimeBlocks[0] * 1000) + Std.int(AL.getSourcef(handle, AL.SEC_OFFSET) * 1000)) - parent.offset; - if (time < 0) return 0; - return time; - } - else - { - var offset = AL.getSourcei(handle, AL.BYTE_OFFSET); - var ratio = (offset / dataLength); - var totalSeconds = samples / parent.buffer.sampleRate; - - var time = Std.int(totalSeconds * ratio * 1000) - parent.offset; - - // var time = Std.int (AL.getSourcef (handle, AL.SEC_OFFSET) * 1000) - parent.offset; - if (time < 0) return 0; - return time; - } + AL.sourcePause(source); + AL.sourcei(source, AL.SAMPLE_OFFSET, pauseSample); } - return 0; - } + var remaining = (loopPoints[1] - pauseSample) * 1000.0 / parent.buffer.sampleRate / getPitch(); + var canPlay = remaining > 0; - public function setCurrentTime(value:Int):Int - { - // `setCurrentTime()` has side effects and is never safe to skip. - /* if (value == getCurrentTime()) + if (playing && canPlay) { - return value; - } */ + completed = false; - if (handle != null) - { - if (stream) + if (streamed) { - AL.sourceStop(handle); + snapBuffersToSample(pauseSample, false, STREAM_START_BUFFERS); + AL.sourcePlay(source); - parent.buffer.__srcVorbisFile.timeSeek((value + parent.offset) / 1000); - AL.sourceUnqueueBuffers(handle, STREAM_NUM_BUFFERS); - refillBuffers(buffers); - - if (playing) AL.sourcePlay(handle); + if (streamEnded) stopStream(true); + else resumeStream(true); + mutex.release(); } - else if (parent.buffer != null) - { - AL.sourceRewind(handle); - - // AL.sourcef (handle, AL.SEC_OFFSET, (value + parent.offset) / 1000); - - var secondOffset = (value + parent.offset) / 1000; - var totalSeconds = samples / parent.buffer.sampleRate; - - if (secondOffset < 0) secondOffset = 0; - if (secondOffset > totalSeconds) secondOffset = totalSeconds; - - var ratio = (secondOffset / totalSeconds); - var totalOffset = Std.int(dataLength * ratio); + else AL.sourcePlay(source); - AL.sourcei(handle, AL.BYTE_OFFSET, totalOffset); - if (playing) AL.sourcePlay(handle); - } + resetTimer(remaining); } - - if (playing) + else { - if (timer != null) + if (completed == canPlay) { - timer.stop(); + completed = !canPlay; + if (playing && completed) + { + resetTimer(0); + playing = false; + } } - var timeRemaining = Std.int((getLength() - value) / getPitch()); - - if (timeRemaining > 0) - { - completed = false; - // timer = new Timer(timeRemaining); - // timer.run = timer_onRun; - } - else + if (streamed) { - playing = false; - completed = true; + stopStream(true); + mutex.release(); } } - - return value; } public function getGain():Float { - if (handle != null) - { - return AL.getSourcef(handle, AL.GAIN); - } - else - { - return 1; - } + if (source != null) return AL.getSourcef(source, AL.GAIN); + else return 1; } public function setGain(value:Float):Float { - if (handle != null) - { - AL.sourcef(handle, AL.GAIN, value); - } - + if (source != null) AL.sourcef(source, AL.GAIN, value); return value; } - public function getLength():Int + public function getLatency():Float { - if (length != null) + if (source != null && AudioManager.__latencyExtSupported) { - return length; + var offsets = AL.getSourcedvSOFT(source, AL.SEC_OFFSET_LATENCY_SOFT, 2); + if (offsets != null) return offsets[1] * 1000.0; } + return 0; + } + + public function getLength():Float + { + if (!loaded) return 0; - return Std.int(samples / parent.buffer.sampleRate * 1000) - parent.offset; + var length = loopPoints[1] * 1000.0 / parent.buffer.sampleRate; + if (length <= parent.offset) return 0; + return length - parent.offset; } - public function setLength(value:Int):Int + public function setLength(value:Float):Float { - if (playing && length != value) + if (loaded) { - if (timer != null) - { - timer.stop(); - } + var endSample = Std.int((value + parent.offset) / 1000 * parent.buffer.sampleRate); + if (endSample <= 0 || endSample >= samples) loopPoints[1] = samples; + else loopPoints[1] = endSample; - // var timeRemaining = Std.int((value - getCurrentTime()) / getPitch()); + if (loops > streamLoops) updateLoopPoints(); + else AL.sourcei(source, AL.LOOPING, AL.FALSE); - // if (timeRemaining > 0) - // { - // timer = new Timer(timeRemaining); - // timer.run = timer_onRun; - // } + if (playing) resetTimer((endSample - getCurrentSampleOffset()) * 1000.0 / parent.buffer.sampleRate / getPitch()); } + return value; + } + + public function getLoopTime():Float + { + if (!loaded) return 0; + + var loopTime = loopPoints[0] * 1000.0 / parent.buffer.sampleRate; + if (loopTime <= parent.offset) return 0; + return loopTime - parent.offset; + } + + public function setLoopTime(value:Float):Float + { + if (loaded) + { + var loopSample = Std.int((value + parent.offset) / 1000 * parent.buffer.sampleRate); + if (loopSample < 0) loopPoints[0] = 0; + else if (loopSample > samples) loopPoints[0] = samples; + else loopPoints[0] = loopSample; - return length = value; + if (loops > streamLoops) updateLoopPoints(); + else AL.sourcei(source, AL.LOOPING, AL.FALSE); + } + return value; } public function getLoops():Int @@ -585,91 +811,752 @@ class NativeAudioSource public function setLoops(value:Int):Int { - return loops = value; + loops = value; + if (loaded) + { + if (value > streamLoops) updateLoopPoints(); + else AL.sourcei(source, AL.LOOPING, AL.FALSE); + } + return value; } - public function getPitch():Float + private function updateLoopPoints():Void { - if (handle != null) + prepared = false; + + var sampleOffset = getCurrentSampleOffset(); + var canLoop = loops > streamLoops; + var fixed = sampleOffset >= loopPoints[1]; + var shouldStop = playing && fixed; + + if (fixed) sampleOffset = loopPoints[0]; + + if (streamed) { - return AL.getSourcef(handle, AL.PITCH); + AL.sourcei(source, AL.LOOPING, AL.FALSE); + + if (shouldStop) stop(); + else if (playing && canLoop && (fixed || streamLoops > 0)) + { + mutex.acquire(); + snapBuffersToSample(sampleOffset, true, STREAM_MIN_BUFFERS); + AL.sourcePlay(source); + mutex.release(); + } } else { - return 1; + if (loopPoints[0] > 0 || loopPoints[1] < samples) + { + if (!AudioManager.__loopPointsSupported) canLoop = false; + else + { + AL.sourceStop(source); + AL.sourcei(source, AL.BUFFER, AL.NONE); + if (!standaloneBuffer) + { + if (standaloneBuffer = (buffer = AL.createBuffer()) != null) + { + AL.bufferData(buffer, format, parent.buffer.data, parent.buffer.data.byteLength, parent.buffer.sampleRate); + } + else + { + buffer = parent.buffer.__srcBuffer; + canLoop = false; + } + } + if (canLoop) AL.bufferiv(buffer, AL.LOOP_POINTS_SOFT, loopPoints); + AL.sourcei(source, AL.BUFFER, buffer); + } + } + + AL.sourcei(source, AL.LOOPING, canLoop ? AL.TRUE : AL.FALSE); + if (shouldStop) stop(); + else setCurrentSampleOffset(sampleOffset); } } - public function setPitch(value:Float):Float + public function getPan():Float + { + if (position == null) position = new Vector4(); + return position.x; + } + + public function setPan(value:Float):Float { - if (playing && value != getPitch()) + if (position == null) position = new Vector4(); + position.setTo(value, 0, -Math.sqrt(1 - value * value)); + + if (source != null) { - if (timer != null) + var spatialize = Math.abs(value) > 1e-04; + + if (AudioManager.__directChannelsExtSupported) { - timer.stop(); + AL.sourcei(source, AL.DIRECT_CHANNELS_SOFT, spatialize ? AL.FALSE : AL.REMIX_UNMATCHED_SOFT); } - // var timeRemaining = Std.int((getLength() - getCurrentTime()) / value); + if (AudioManager.__spatializeSupported) + { + AL.sourcei(source, AL.SOURCE_SPATIALIZE_SOFT, !AudioManager.__stereoAnglesSupported && spatialize ? AL.TRUE : AL.FALSE); + } - // if (timeRemaining > 0) - // { - // timer = new Timer(timeRemaining); - // timer.run = timer_onRun; - // } + if (AudioManager.__stereoAnglesSupported) + { + anglesArray[0] = Math.PI * Math.min(-value * 2 + 1, 1) / 6; + anglesArray[1] = -Math.PI * Math.min(value * 2 + 1, 1) / 6; + AL.source3f(source, AL.POSITION, 0, 0, 0); + } + else + { + anglesArray[0] = Math.PI / 6; + anglesArray[1] = -Math.PI / 6; + AL.source3f(source, AL.POSITION, position.x, position.y, position.z); + } + AL.sourcefv(source, AL.STEREO_ANGLES, anglesArray); } + return value; + } - if (handle != null) - { - AL.sourcef(handle, AL.PITCH, value); - } + public function getPitch():Float + { + return source != null ? AL.getSourcef(source, AL.PITCH) : 1; + } + public function setPitch(value:Float):Float + { + value = Math.max(value, 0); + if (source == null || value == AL.getSourcef(source, AL.PITCH)) return value; + AL.sourcef(source, AL.PITCH, value); + if (playing) resetTimer((loopPoints[1] - getCurrentSampleOffset()) * 1000.0 / parent.buffer.sampleRate / value); return value; } + public function getPlaying():Bool + { + return playing && (AL.getSourcei(source, AL.SOURCE_STATE) == AL.PLAYING || streaming); + } + public function getPosition():Vector4 { - if (handle != null) + if (position == null) position = new Vector4(); + return position; + } + + public function setPosition(value:Vector4):Vector4 + { + if (position == null) position = new Vector4(); + position.setTo(value.x, value.y, value.z); + + if (source != null) { - #if !webassembly - var value = AL.getSource3f(handle, AL.POSITION); - position.x = value[0]; - position.y = value[1]; - position.z = value[2]; + anglesArray[0] = Math.PI / 6; + anglesArray[1] = -Math.PI / 6; + AL.sourcefv(source, AL.STEREO_ANGLES, anglesArray); + + #if (openfl && !openfl_funkin) + // This is for older openfl that sets position to 0, 0, -1 initially. + if (position.z == -1 && position.x == 0 && position.y == 0) + { + if (AudioManager.__directChannelsExtSupported) + { + AL.sourcei(source, AL.DIRECT_CHANNELS_SOFT, AL.REMIX_UNMATCHED_SOFT); + } + + if (AudioManager.__spatializeSupported) + { + AL.sourcei(source, AL.SOURCE_SPATIALIZE_SOFT, AL.FALSE); + } + + AL.source3f(source, AL.POSITION, 0, 0, 0); + + return value; + } #end + + if (Math.abs(position.x) > 1e-04 || Math.abs(position.y) > 1e-04 || (Math.abs(position.z) > 1e-04)) + { + if (AudioManager.__directChannelsExtSupported) + { + AL.sourcei(source, AL.DIRECT_CHANNELS_SOFT, AL.FALSE); + } + + if (AudioManager.__spatializeSupported) + { + AL.sourcei(source, AL.SOURCE_SPATIALIZE_SOFT, AL.TRUE); + } + } + else + { + if (AudioManager.__directChannelsExtSupported) + { + AL.sourcei(source, AL.DIRECT_CHANNELS_SOFT, AL.REMIX_UNMATCHED_SOFT); + } + + if (AudioManager.__spatializeSupported) + { + AL.sourcei(source, AL.SOURCE_SPATIALIZE_SOFT, AL.FALSE); + } + } + + AL.source3f(source, AL.POSITION, position.x, position.y, position.z); } - return position; + return value; } - public function setPosition(value:Vector4):Vector4 + function readToBufferData(data:ArrayBufferView, currentPCM:Int):Int { - position.x = value.x; - position.y = value.y; - position.z = value.z; - position.w = value.w; + if (decoder.eof || currentPCM >= loopPoints[1]) + { + if (streamEnded = loops <= streamLoops || !decoder.seek(currentPCM = loopPoints[0])) return 0; + streamLoops++; + } - if (handle != null) + var word = parent.buffer.bitsPerSample > 16 ? 2 : parent.buffer.bitsPerSample >> 3, total = 0, len:Int; + while (!(streamEnded = decoder.eof)) { - AL.distanceModel(AL.NONE); - AL.source3f(handle, AL.POSITION, position.x, position.y, position.z); + // use unused var from here which is currentPCM + if ((len = (loopPoints[1] - currentPCM) * parent.buffer.channels * word) <= (currentPCM = bufferLen - total)) + { + total += decoder.decode(data.buffer, total, len, word); + if (loops > streamLoops) + { + decoder.seek(currentPCM = loopPoints[0]); + streamLoops++; + } + else + { + streamEnded = true; + break; + } + } + else + { + return total + decoder.decode(data.buffer, total, currentPCM, word); + } } + return total; + } - return position; + function fillBuffers(n:Int):Void + { + var max = STREAM_MAX_BUFFERS - 1; + var i:Int, j:Int, data:ArrayBufferView, pcm:Int, decoded:Int; + while (n-- > 0 && !streamEnded) + { + data = bufferViews[(i = max - filledBuffers) > 0 ? i : 0]; + pcm = Int64.toInt(decoder.tell()); + decoded = readToBufferData(data, pcm); + + if (decoded <= 0) break; + else if (filledBuffers < STREAM_MAX_BUFFERS) filledBuffers++; + + seekMutex.acquire(); + + j = i; + while (i < max) + { + bufferViews[i] = bufferViews[++j]; + bufferCurs[i] = bufferCurs[j]; + bufferLens[i] = bufferLens[j]; + i = j; + } + bufferViews[max] = data; + bufferCurs[max] = pauseSample = pcm; + bufferLens[max] = decoded; + queuedBuffers++; + + seekMutex.release(); + } } - public function getLatency():Float + function queueBuffers():Void + { + var internalQueuedBuffers = AL.getSourcei(source, AL.BUFFERS_QUEUED); + var i = STREAM_MAX_BUFFERS - queuedBuffers + internalQueuedBuffers; + while (internalQueuedBuffers < STREAM_FLUSH_BUFFERS && internalQueuedBuffers < queuedBuffers) + { + AL.bufferData(buffers[nextBuffer], format, bufferViews[i], bufferLens[i], parent.buffer.sampleRate); + if (AL.getError() != AL.NO_ERROR) break; + + AL.sourceQueueBuffer(source, buffers[nextBuffer]); + if (AL.getError() != AL.NO_ERROR) break; + + if (++nextBuffer == STREAM_FLUSH_BUFFERS) nextBuffer = 0; + internalQueuedBuffers++; + i++; + } + } + + function skipBuffers(n:Int):Void + { + if (n > 0) + { + seekMutex.acquire(); + AL.sourceUnqueueBuffers(source, n); + if ((queuedBuffers -= n) < 0) queuedBuffers = 0; + seekMutex.release(); + } + } + + function snapBuffersToSample(sample:Int, force:Bool, n:Int):Void { - #if lime_openalsoft - if (hasALSoftLatencyExt) + if (!force) { - var offsets = AL.getSourcedvSOFT(handle, AL.SEC_OFFSET_LATENCY_SOFT, 2); - if (offsets != null) + for (i in (STREAM_MAX_BUFFERS - queuedBuffers)...STREAM_MAX_BUFFERS) + if (sample >= bufferCurs[i] && sample < bufferCurs[i] + (bufferLens[i] / (parent.buffer.bitsPerSample >> 3) / parent.buffer.channels)) { - return offsets[1] * 1000; + skipBuffers(i - STREAM_MAX_BUFFERS + queuedBuffers); + queueBuffers(); + AL.sourcei(source, AL.SAMPLE_OFFSET, sample - bufferCurs[i]); + return; } } - #end - return 0; + AL.sourceStop(source); + AL.sourceUnqueueBuffers(source, AL.getSourcei(source, AL.BUFFERS_QUEUED)); + + streamEnded = false; + queuedBuffers = filledBuffers = streamLoops = nextBuffer = 0; + + if (sample == 0) decoder.rewind(); + else decoder.seek(sample); + + fillBuffers(n); + queueBuffers(); + } + + static function streamThreadRun():Void + { + var backend:NativeAudioSource; + var i:Int; + var a:Int; + var b:Int; + + var processTicks:Int = 0; + var canProcess:Bool; + + while (true) + { + queueMutex.acquire(); + + i = queuedStreamAudios.length; + a = streamAudios.length; + if (i == 0 && a == 0) + { + queueMutex.release(); + break; + } + + while (i-- > 0) + { + backend = queuedStreamAudios[i]; + if (backend.streaming = backend.playing && backend.pending) + { + backend.pending = false; + streamAudios.push(backend); + a++; + } + } + + queuedStreamAudios.resize(0); + queueMutex.release(); + + streamMutex.acquire(); + i = a; + + if (canProcess = (++processTicks == STREAM_PROCESS_TICKS)) processTicks = 0; + while (i-- > 0) + { + backend = streamAudios[i]; + if (backend.pending || backend.mutex == null) + { + backend.removeStream(); + continue; + } + + a = AL.getSourcei(backend.source, AL.BUFFERS_PROCESSED); + if (!backend.mutex.tryAcquire()) + { + if (a >= STREAM_PROCESS_BUFFERS) + { + backend.mutex.acquire(); + if (backend.pending) + { + backend.removeStream(); + backend.mutex.release(); + continue; + } + a = AL.getSourcei(backend.source, AL.BUFFERS_PROCESSED); + } + else + { + continue; + } + } + + backend.skipBuffers(a); + + if (!backend.streamEnded && (canProcess || backend.queuedBuffers <= STREAM_MIN_BUFFERS)) + { + try + { + if (STREAM_PROCESS_BUFFERS > (a = backend.queuedBuffers < STREAM_MIN_BUFFERS ? STREAM_MIN_BUFFERS - backend.queuedBuffers : 0)) + a = STREAM_PROCESS_BUFFERS; + + if ((b = STREAM_USABLE_BUFFERS - backend.queuedBuffers) < a) + a = b; + + if (a > 0) + backend.fillBuffers(a); + } + catch (e:haxe.Exception) + { + haxe.MainLoop.runInMainThread(haxe.Log.trace.bind(e.details() + '\n' + e.stack)); + } + } + + backend.queueBuffers(); + + if (AL.getSourcei(backend.source, AL.SOURCE_STATE) != AL.PLAYING) + { + AL.sourcePlay(backend.source); + backend.resetTimer((backend.loopPoints[1] - backend.bufferCurs[STREAM_MAX_BUFFERS - backend.queuedBuffers]) + * 1000.0 / backend.parent.buffer.sampleRate / backend.getPitch()); + } + + if (backend.streamEnded && backend.queuedBuffers == AL.getSourcei(backend.source, AL.BUFFERS_QUEUED)) backend.removeStream(); + + backend.mutex.release(); + } + + streamMutex.release(); + Sys.sleep(STREAM_UPDATE_DELAY); + } + + threadRunning = false; + } + + inline function removeStream():Void + { + pending = streaming = false; + streamAudios.remove(this); + } + + function stopStream(acquired:Bool):Void + { + if (streaming) + { + if (acquired) pending = true; + else + { + mutex.acquire(); + pending = true; + mutex.release(); + } + } + else if (pending) + { + if (queueMutex.tryAcquire()) + { + pending = streaming; + if (!streaming) queuedStreamAudios.remove(this); + queueMutex.release(); + } + else if (acquired) pending = streaming; + else + { + mutex.acquire(); + pending = streaming; + mutex.release(); + } + } } -} + + function resumeStream(acquired:Bool):Void + { + if (!streaming) + { + if (!pending) + { + queueMutex.acquire(); + queuedStreamAudios.push(this); + queueMutex.release(); + } + pending = true; + } + else if (pending) + { + if (queueMutex.tryAcquire()) + { + pending = !streaming; + if (pending) queuedStreamAudios.push(this); + queueMutex.release(); + } + else if (acquired) pending = !streaming; + else + { + mutex.acquire(); + pending = !streaming; + mutex.release(); + } + } + + if (!threadRunning || streamThread == null) + { + streamThread = Thread.create(streamThreadRun); + threadRunning = true; + } + } + + // Waveform related functions + public function getPeaks(offsetMs:Float):Array + { + if (peaks == null) peaks = []; + + if (loaded && parent.buffer.channels != peaks.length) peaks.resize(parent.buffer.channels); + + if (!playing) + { + for (i in 0...peaks.length) peaks[i] = 0; + return peaks; + } + + var byteSize = 1 << parent.buffer.bitsPerSample; + + if (mins == null) + { + mins = []; + maxs = []; + } + + for (i in 0...parent.buffer.channels) + { + mins[i] = byteSize; + maxs[i] = -byteSize; + } + + var samplesToRead = parent.buffer.sampleRate >> 4; + readTimeDomainData(function(byte:Int, channel:Int):Void + { + if (byte > maxs[channel]) maxs[channel] = byte; + else if (byte < mins[channel]) mins[channel] = byte; + }, samplesToRead, Std.int(offsetMs / 1000 * parent.buffer.sampleRate) - samplesToRead); + + for (i in 0...parent.buffer.channels) peaks[i] = (maxs[i] - mins[i]) / byteSize; + return peaks; + } + + public function getFloatTimeDomainData(array:Float32Array, size:Int, channel:Int, offset:Int):Int + { + if (!playing || array == null) return 0; + + var valueSize = 1 << (parent.buffer.bitsPerSample - 1); + var channelToNext = parent.buffer.channels - 1, i = 0, v = 0, func:Int->Int->Void; + + if (channel == -1) + { + func = function(signal:Int, signalChannel:Int):Void + { + if (i < array.length) + { + if (signalChannel == 0) v = idiv(signal, parent.buffer.channels); + else v += idiv(signal, parent.buffer.channels); + + if (signalChannel == channelToNext) array[i++] = v / valueSize; + } + } + } + else + { + func = function(signal:Int, signalChannel:Int):Void + { + if (i < array.length) + { + if (signalChannel == channel) v = signal; + if (signalChannel == channelToNext) array[i++] = v / valueSize; + } + } + } + + readTimeDomainData(func, size, offset); + return i; + } + + public function getByteTimeDomainData(array:UInt8Array, size:Int, channel:Int, offset:Int):Int + { + if (!playing || array == null) return 0; + + var valueDiv = 1 << (parent.buffer.bitsPerSample - 8); + var channelToNext = parent.buffer.channels - 1, i = 0, v = 0, func:Int->Int->Void; + + if (channel == -1) + { + func = function(signal:Int, signalChannel:Int):Void + { + if (i < array.length) + { + if (signalChannel == 0) v = idiv(signal, parent.buffer.channels); + else v += idiv(signal, parent.buffer.channels); + + if (signalChannel == channelToNext) + { + if (v < 0) array[i++] = idiv(v, valueDiv) + 0x100; + else array[i++] = idiv(v, valueDiv); + } + } + } + } + else + { + func = function(signal:Int, signalChannel:Int):Void + { + if (i < array.length) + { + if (signalChannel == channel) v = signal; + if (signalChannel == channelToNext) + { + if (v < 0) array[i++] = idiv(v, valueDiv) + 0x100; + else array[i++] = idiv(v, valueDiv); + } + } + } + } + + readTimeDomainData(func, size, offset); + return i; + } + + function readTimeDomainData(callback:Int->Int->Void, size:Int, offsetSample:Int):Void + { + var pos = AL.getSourcei(source, AL.SAMPLE_OFFSET); + if (lastReadSampleOffset == pos) + { + offsetSample += Std.int((System.getTimer() - lastReadTime) / 1000.0 * parent.buffer.sampleRate * AL.getSourcef(source, AL.PITCH)); + } + else + { + lastReadTime = System.getTimer(); + lastReadSampleOffset = pos; + } + + var i = 0, buffer:ArrayBuffer, bufferLen:Int; + if (streamed) + { + if (filledBuffers == 0) return; + mutex.acquire(); + + i = STREAM_MAX_BUFFERS - queuedBuffers; + buffer = bufferViews[i].buffer; + bufferLen = bufferLens[i]; + } + else + { + buffer = parent.buffer.data.buffer; + bufferLen = buffer.length; + } + + var byteRate = parent.buffer.bitsPerSample >> 3; + pos = (pos + offsetSample) * parent.buffer.channels * byteRate; + + if (pos < 0) + { + if (size < pos) + { + if (streamed) mutex.release(); + return; + } + + size -= pos; + do { + if (i == 0) pos = 0; + else + { + buffer = bufferViews[--i].buffer; + bufferLen = bufferLens[i]; + pos += bufferLen; + } + } while (pos < 0); + } + else if (pos >= bufferLen) + { + if (!streamed) return; + + do + { + if (++i >= bufferLens.length) + { + mutex.release(); + return; + } + + pos -= bufferLen; + buffer = bufferViews[i].buffer; + bufferLen = bufferLens[i]; + } while (pos >= bufferLen); + } + + var c = 0; + while (size > 0) + { + callback(switch (byteRate) + { + case 2: ArrayBufferIO.getInt16(buffer, pos); + case 3: + // make use of the unused variable from here. + if ((offsetSample = ArrayBufferIO.getUint16(buffer, pos) | (buffer.get(pos + 2) << 16)) & 0x800000 != 0) offsetSample -= 0x1000000; + offsetSample; + case 4: ArrayBufferIO.getInt32(buffer, pos); + default: ArrayBufferIO.getInt8(buffer, pos); + }, c); + + pos += byteRate; + if (pos >= bufferLen) + { + if (!streamed || ++i >= bufferLens.length || --size == 0) break; + + c = pos = 0; + buffer = bufferViews[i].buffer; + bufferLen = bufferLens[i]; + } + + c++; + if (c == parent.buffer.channels) + { + c = 0; + size--; + } + } + + if (streamed) mutex.release(); + } + + // Real-time audio effects + public function addEffect(index:Int):Void + { + updateEffect(index); + } + + public function updateEffect(index:Int):Void + { + if (source != null) + @:privateAccess + { + var effect = parent.__effects[index]; + AL.source3i(source, AL.AUXILIARY_SEND_FILTER, effect.__alAux, index, effect.__alFilter); + if (effect.__alFilter != null) AL.sourcei(source, AL.DIRECT_FILTER, effect.__alFilter); + } + } + + public function removeEffect(index:Int):Void + { + if (source != null) + { + //AL.source3i(source, AL.AUXILIARY_SEND_FILTER, AL.EFFECTSLOT_NULL, index, AL.FILTER_NULL); + AL.removeSend(source, index); + } + } + + static inline function idiv(num:Int, denom:Int):Int return #if (cpp && !cppia) cpp.NativeMath.idiv(num, denom) #else Std.int(num / denom) #end; +} \ No newline at end of file diff --git a/src/lime/_internal/backend/native/NativeCFFI.hx b/src/lime/_internal/backend/native/NativeCFFI.hx index 69dca151eb..8f717f1dd4 100644 --- a/src/lime/_internal/backend/native/NativeCFFI.hx +++ b/src/lime/_internal/backend/native/NativeCFFI.hx @@ -69,12 +69,6 @@ class NativeCFFI @:cffi private static function lime_application_update(handle:Dynamic):Bool; - @:cffi private static function lime_audio_load(data:Dynamic, buffer:Dynamic):Dynamic; - - @:cffi private static function lime_audio_load_bytes(data:Dynamic, buffer:Dynamic):Dynamic; - - @:cffi private static function lime_audio_load_file(path:Dynamic, buffer:Dynamic):Dynamic; - @:cffi private static function lime_bytes_from_data_pointer(data:Float, length:Int, bytes:Dynamic):Dynamic; @:cffi private static function lime_bytes_get_data_pointer(data:Dynamic):Float; @@ -390,11 +384,6 @@ class NativeCFFI private static var lime_application_set_frame_rate = new cpp.CallableFloat->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_application_set_frame_rate", "odv", false)); private static var lime_application_update = new cpp.CallableBool>(cpp.Prime._loadPrime("lime", "lime_application_update", "ob", false)); - private static var lime_audio_load = new cpp.Callablecpp.Object->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_audio_load", "ooo", false)); - private static var lime_audio_load_bytes = new cpp.Callablecpp.Object->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_audio_load_bytes", - "ooo", false)); - private static var lime_audio_load_file = new cpp.Callablecpp.Object->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_audio_load_file", "ooo", - false)); private static var lime_bytes_from_data_pointer = new cpp.CallableInt->cpp.Object->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_bytes_from_data_pointer", "dioo", false)); private static var lime_bytes_get_data_pointer = new cpp.CallableFloat>(cpp.Prime._loadPrime("lime", "lime_bytes_get_data_pointer", "od", @@ -670,9 +659,6 @@ class NativeCFFI private static var lime_application_quit = CFFI.load("lime", "lime_application_quit", 1); private static var lime_application_set_frame_rate = CFFI.load("lime", "lime_application_set_frame_rate", 2); private static var lime_application_update = CFFI.load("lime", "lime_application_update", 1); - private static var lime_audio_load = CFFI.load("lime", "lime_audio_load", 2); - private static var lime_audio_load_bytes = CFFI.load("lime", "lime_audio_load_bytes", 2); - private static var lime_audio_load_file = CFFI.load("lime", "lime_audio_load_file", 2); private static var lime_bytes_from_data_pointer = CFFI.load("lime", "lime_bytes_from_data_pointer", 3); private static var lime_bytes_get_data_pointer = CFFI.load("lime", "lime_bytes_get_data_pointer", 1); private static var lime_bytes_get_data_pointer_offset = CFFI.load("lime", "lime_bytes_get_data_pointer_offset", 2); @@ -854,17 +840,6 @@ class NativeCFFI return false; } - // @:cffi private static function lime_audio_load (data:Dynamic, buffer:Dynamic):Dynamic; - @:hlNative("lime", "hl_audio_load_bytes") private static function lime_audio_load_bytes(data:Bytes, buffer:AudioBuffer):AudioBuffer - { - return null; - } - - @:hlNative("lime", "hl_audio_load_file") private static function lime_audio_load_file(path:String, buffer:AudioBuffer):AudioBuffer - { - return null; - } - @:hlNative("lime", "hl_bytes_from_data_pointer") private static function lime_bytes_from_data_pointer(data:Float, length:Int, bytes:Bytes):Bytes { return null; @@ -1563,6 +1538,12 @@ class NativeCFFI @:cffi private static function lime_al_delete_sources(n:Int, sources:Dynamic):Void; + @:cffi private static function lime_al_delete_effect(buffer:CFFIPointer):Void; + + @:cffi private static function lime_al_delete_filter(buffer:CFFIPointer):Void; + + @:cffi private static function lime_al_delete_auxiliary_effect_slot(slot:CFFIPointer):Void; + @:cffi private static function lime_al_disable(capability:Int):Void; @:cffi private static function lime_al_distance_model(distanceModel:Int):Void; @@ -1641,6 +1622,8 @@ class NativeCFFI @:cffi private static function lime_al_get_sourceiv(source:CFFIPointer, param:Int, count:Int):Array; + @:cffi private static function lime_al_get_sourcedv_soft(source:CFFIPointer, param:Int, count:Int):Array; + @:cffi private static function lime_al_get_string(param:Int):Dynamic; @:cffi private static function lime_al_is_buffer(buffer:CFFIPointer):Bool; @@ -1687,7 +1670,7 @@ class NativeCFFI @:cffi private static function lime_al_source3f(source:CFFIPointer, param:Int, value1:Float32, value2:Float32, value3:Float32):Void; - @:cffi private static function lime_al_source3i(source:CFFIPointer, param:Int, value1:Dynamic, value2:Int, value3:Int):Void; + @:cffi private static function lime_al_source3i(source:CFFIPointer, param:Int, value1:Dynamic, value2:Int, value3:Dynamic):Void; @:cffi private static function lime_al_sourcef(source:CFFIPointer, param:Int, value:Float32):Void; @@ -1711,7 +1694,7 @@ class NativeCFFI @:cffi private static function lime_alc_get_error(device:CFFIPointer):Int; - @:cffi private static function lime_alc_get_integerv(device:CFFIPointer, param:Int, size:Int):Dynamic; + @:cffi private static function lime_alc_get_integerv(device:CFFIPointer, param:Int, count:Int):Dynamic; @:cffi private static function lime_alc_get_string(device:CFFIPointer, param:Int):Dynamic; @@ -1745,6 +1728,8 @@ class NativeCFFI @:cffi private static function lime_alc_capture_samples(device:CFFIPointer, buffer:Dynamic, samples:Int):Void; + @:cffi private static function lime_alc_get_doublev_soft(device:CFFIPointer, param:Int, count:Int):Array; + @:cffi private static function lime_al_gen_filter():CFFIPointer; @:cffi private static function lime_al_filteri(filter:CFFIPointer, param:Int, value:Dynamic):Void; @@ -1802,6 +1787,10 @@ class NativeCFFI private static var lime_al_delete_source = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_delete_source", "ov", false)); private static var lime_al_delete_sources = new cpp.Callablecpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_delete_sources", "iov", false)); + private static var lime_al_delete_effect = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_delete_effect", "ov", false)); + private static var lime_al_delete_filter = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_delete_filter", "ov", false)); + private static var lime_al_delete_auxiliary_effect_slot = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_al_delete_auxiliary_effect_slot", "ov", false)); private static var lime_al_disable = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_disable", "iv", false)); private static var lime_al_distance_model = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_distance_model", "iv", false)); private static var lime_al_doppler_factor = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_doppler_factor", "fv", false)); @@ -1851,6 +1840,8 @@ class NativeCFFI private static var lime_al_get_sourcei = new cpp.CallableInt->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_al_get_sourcei", "oio", false)); private static var lime_al_get_sourceiv = new cpp.CallableInt->Int->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_al_get_sourceiv", "oiio", false)); + private static var lime_al_get_sourcedv_soft = new cpp.CallableInt->Int->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_al_get_sourcedv_soft", + "oiio", false)); private static var lime_al_get_string = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_al_get_string", "io", false)); private static var lime_al_is_buffer = new cpp.CallableBool>(cpp.Prime._loadPrime("lime", "lime_al_is_buffer", "ob", false)); private static var lime_al_is_enabled = new cpp.CallableBool>(cpp.Prime._loadPrime("lime", "lime_al_is_enabled", "ib", false)); @@ -1882,8 +1873,8 @@ class NativeCFFI "lime_al_source_unqueue_buffers", "oio", false)); private static var lime_al_source3f = new cpp.CallableInt->cpp.Float32->cpp.Float32->cpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_source3f", "oifffv", false)); - private static var lime_al_source3i = new cpp.CallableInt->cpp.Object->Int->Int->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_source3i", - "oioiiv", false)); + private static var lime_al_source3i = new cpp.CallableInt->cpp.Object->Int->cpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_source3i", + "oioiov", false)); private static var lime_al_sourcef = new cpp.CallableInt->cpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_sourcef", "oifv", false)); private static var lime_al_sourcefv = new cpp.CallableInt->cpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_sourcefv", "oiov", @@ -1932,6 +1923,8 @@ class NativeCFFI false)); private static var lime_alc_capture_samples = new cpp.Callablecpp.Object->Int->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_alc_capture_samples", "ooiv", false)); + private static var lime_alc_get_doublev_soft = new cpp.CallableInt->Int->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_alc_get_doublev_soft", + "oiio", false)); private static var lime_al_gen_filter = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_al_gen_filter", "o", false)); private static var lime_al_filteri = new cpp.CallableInt->cpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_al_filteri", "oiov", false)); @@ -1971,6 +1964,9 @@ class NativeCFFI private static var lime_al_delete_buffers = CFFI.load("lime", "lime_al_delete_buffers", 2); private static var lime_al_delete_source = CFFI.load("lime", "lime_al_delete_source", 1); private static var lime_al_delete_sources = CFFI.load("lime", "lime_al_delete_sources", 2); + private static var lime_al_delete_effect = CFFI.load("lime", "lime_al_delete_effect", 1); + private static var lime_al_delete_filter = CFFI.load("lime", "lime_al_delete_filter", 1); + private static var lime_al_delete_auxiliary_effect_slot = CFFI.load("lime", "lime_al_delete_auxiliary_effect_slot", 1); private static var lime_al_disable = CFFI.load("lime", "lime_al_disable", 1); private static var lime_al_distance_model = CFFI.load("lime", "lime_al_distance_model", 1); private static var lime_al_doppler_factor = CFFI.load("lime", "lime_al_doppler_factor", 1); @@ -2010,6 +2006,7 @@ class NativeCFFI private static var lime_al_get_sourcedv_soft = CFFI.load("lime", "lime_al_get_sourcedv_soft", 3); private static var lime_al_get_sourcei = CFFI.load("lime", "lime_al_get_sourcei", 2); private static var lime_al_get_sourceiv = CFFI.load("lime", "lime_al_get_sourceiv", 3); + private static var lime_al_get_sourcedv_soft = CFFI.load("lime", "lime_al_get_sourcedv_soft", 3); private static var lime_al_get_string = CFFI.load("lime", "lime_al_get_string", 1); private static var lime_al_is_buffer = CFFI.load("lime", "lime_al_is_buffer", 1); private static var lime_al_is_enabled = CFFI.load("lime", "lime_al_is_enabled", 1); @@ -2062,6 +2059,7 @@ class NativeCFFI private static var lime_alc_capture_start = CFFI.load("lime", "lime_alc_capture_start", 1); private static var lime_alc_capture_stop = CFFI.load("lime", "lime_alc_capture_stop", 1); private static var lime_alc_capture_samples = CFFI.load("lime", "lime_alc_capture_samples", 3); + private static var lime_alc_get_doublev_soft = CFFI.load("lime", "lime_alc_get_doublev_soft", 3); private static var lime_al_gen_filter = CFFI.load("lime", "lime_al_gen_filter", 0); private static var lime_al_filteri = CFFI.load("lime", "lime_al_filteri", 3); private static var lime_al_filterf = CFFI.load("lime", "lime_al_filterf", 3); @@ -2108,6 +2106,12 @@ class NativeCFFI @:hlNative("lime", "hl_al_delete_sources") private static function lime_al_delete_sources(n:Int, sources:hl.NativeArray):Void {} + @:hlNative("lime", "hl_al_delete_effect") private static function lime_al_delete_effect(buffer:CFFIPointer):Void {} + + @:hlNative("lime", "hl_al_delete_filter") private static function lime_al_delete_filter(buffer:CFFIPointer):Void {} + + @:hlNative("lime", "hl_al_delete_auxiliary_effect_slot") private static function lime_al_delete_auxiliary_effect_slot(slot:CFFIPointer):Void {} + @:hlNative("lime", "hl_al_disable") private static function lime_al_disable(capability:Int):Void {} @:hlNative("lime", "hl_al_distance_model") private static function lime_al_distance_model(distanceModel:Int):Void {} @@ -2288,6 +2292,11 @@ class NativeCFFI return null; } + @:hlNative("lime", "hl_al_get_sourcedv_soft") private static function lime_al_get_sourcedv_soft(source:CFFIPointer, param:Int, count:Int):hl.NativeArray + { + return null; + } + @:hlNative("lime", "hl_al_get_string") private static function lime_al_get_string(param:Int):hl.Bytes { return null; @@ -2359,7 +2368,7 @@ class NativeCFFI value3:hl.F32):Void {} @:hlNative("lime", "hl_al_source3i") private static function lime_al_source3i(source:CFFIPointer, param:Int, value1:Dynamic, value2:Int, - value3:Int):Void {} + value3:Dynamic):Void {} @:hlNative("lime", "hl_al_sourcef") private static function lime_al_sourcef(source:CFFIPointer, param:Int, value:hl.F32):Void {} @@ -2398,7 +2407,7 @@ class NativeCFFI return 0; } - @:hlNative("lime", "hl_alc_get_integerv") private static function lime_alc_get_integerv(device:CFFIPointer, param:Int, size:Int):hl.NativeArray + @:hlNative("lime", "hl_alc_get_integerv") private static function lime_alc_get_integerv(device:CFFIPointer, param:Int, count:Int):hl.NativeArray { return null; } @@ -2455,6 +2464,11 @@ class NativeCFFI @:hlNative("lime", "hl_alc_capture_stop") private static function lime_alc_capture_stop(device:ALDevice):Void {} @:hlNative("lime", "hl_alc_capture_samples") private static function lime_alc_capture_samples(device:ALDevice, buffer:Bytes, samples:Int):Void {} + + @:hlNative("lime", "hl_alc_get_doublev_soft") private static function lime_alc_get_doublev_soft(device:CFFIPointer, param:Int, count:Int):hl.NativeArray + { + return null; + } @:hlNative("lime", "hl_al_gen_filter") private static function lime_al_gen_filter():CFFIPointer { @@ -6839,6 +6853,8 @@ class NativeCFFI @:cffi private static function lime_vorbis_file_read(vorbisFile:Dynamic, buffer:Dynamic, position:Int, length:Int, bigendianp:Bool, word:Int, signed:Bool):Dynamic; + @:cffi private static function lime_vorbis_file_decode(vorbisFile:Dynamic, buffer:Dynamic, position:Int, length:Int, word:Int):Int; + @:cffi private static function lime_vorbis_file_read_float(vorbisFile:Dynamic, pcmChannels:Dynamic, samples:Int):Dynamic; @:cffi private static function lime_vorbis_file_seekable(vorbisFile:Dynamic):Bool; @@ -6896,6 +6912,8 @@ class NativeCFFI "oio", false)); private static var lime_vorbis_file_read = new cpp.Callablecpp.Object->Int->Int->Bool->Int->Bool->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_vorbis_file_read", "ooiibibo", false)); + private static var lime_vorbis_file_decode = new cpp.Callablecpp.Object->Int->Int->Int->Int>(cpp.Prime._loadPrime("lime", + "lime_vorbis_file_decode", "ooiiii", false)); private static var lime_vorbis_file_read_float = new cpp.Callablecpp.Object->Int->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_vorbis_file_read_float", "ooio", false)); private static var lime_vorbis_file_seekable = new cpp.CallableBool>(cpp.Prime._loadPrime("lime", "lime_vorbis_file_seekable", "ob", false)); @@ -6936,6 +6954,7 @@ class NativeCFFI private static var lime_vorbis_file_raw_tell = CFFI.load("lime", "lime_vorbis_file_raw_tell", 1); private static var lime_vorbis_file_raw_total = CFFI.load("lime", "lime_vorbis_file_raw_total", 2); private static var lime_vorbis_file_read = CFFI.load("lime", "lime_vorbis_file_read", -1); + private static var lime_vorbis_file_decode = CFFI.load("lime", "lime_vorbis_file_decode", 5); private static var lime_vorbis_file_read_float = CFFI.load("lime", "lime_vorbis_file_read_float", 3); private static var lime_vorbis_file_seekable = CFFI.load("lime", "lime_vorbis_file_seekable", 1); private static var lime_vorbis_file_serial_number = CFFI.load("lime", "lime_vorbis_file_serial_number", 2); @@ -7046,6 +7065,12 @@ class NativeCFFI return null; } + @:hlNative("lime", "hl_vorbis_file_decode") private static function lime_vorbis_file_decode(vorbisFile:CFFIPointer, buffer:Bytes, position:Int, length:Int, + word:Int):Int + { + return 0; + } + @:hlNative("lime", "hl_vorbis_file_read_float") private static function lime_vorbis_file_read_float(vorbisFile:CFFIPointer, pcmChannels:Bytes, samples:Int):Dynamic { @@ -7098,4 +7123,333 @@ class NativeCFFI } #end #end + #if (lime_cffi && !macro && lime_drlibs) + #if (cpp && !cppia) + #if (disable_cffi || haxe_ver < "3.4.0") + @:cffi private static function lime_drlibs_flac_close(flac:Dynamic):Void; + + @:cffi private static function lime_drlibs_flac_decode(flac:Dynamic, buffer:Dynamic, position:Int, length:Int, word:Int):Int; + + @:cffi private static function lime_drlibs_flac_from_bytes(bytes:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_flac_from_file(path:String):Dynamic; + + @:cffi private static function lime_drlibs_flac_info(flac:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_flac_seek(flac:Dynamic, posLow:Dynamic, posHigh:Dynamic):Int; + + @:cffi private static function lime_drlibs_flac_tell(flac:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_flac_total(flac:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_mp3_decode(mp3:Dynamic, buffer:Dynamic, position:Int, length:Int):Int; + + @:cffi private static function lime_drlibs_mp3_from_bytes(bytes:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_mp3_from_file(path:String):Dynamic; + + @:cffi private static function lime_drlibs_mp3_info(mp3:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_mp3_seek(mp3:Dynamic, posLow:Dynamic, posHigh:Dynamic):Int; + + @:cffi private static function lime_drlibs_mp3_tell(mp3:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_mp3_total(mp3:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_mp3_uninit(mp3:Dynamic):Void; + + @:cffi private static function lime_drlibs_wav_decode(wav:Dynamic, buffer:Dynamic, position:Int, length:Int, word:Int):Int; + + @:cffi private static function lime_drlibs_wav_from_bytes(bytes:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_wav_from_file(path:String):Dynamic; + + @:cffi private static function lime_drlibs_wav_info(wav:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_wav_seek(wav:Dynamic, posLow:Dynamic, posHigh:Dynamic):Int; + + @:cffi private static function lime_drlibs_wav_tell(wav:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_wav_total(wav:Dynamic):Dynamic; + + @:cffi private static function lime_drlibs_wav_uninit(wav:Dynamic):Void; + #else + private static var lime_drlibs_flac_close = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_drlibs_flac_close", "ov", false)); + private static var lime_drlibs_flac_decode = new cpp.Callablecpp.Object->Int->Int->Int->Int>(cpp.Prime._loadPrime("lime", + "lime_drlibs_flac_decode", "ooiiii", false)); + private static var lime_drlibs_flac_from_bytes = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_flac_from_bytes", + "oo", false)); + private static var lime_drlibs_flac_from_file = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_flac_from_file", "so", + false)); + private static var lime_drlibs_flac_info = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_flac_info", "oo", + false)); + private static var lime_drlibs_flac_seek = new cpp.Callablecpp.Object->cpp.Object->Int>(cpp.Prime._loadPrime("lime", + "lime_drlibs_flac_seek", "oooi", false)); + private static var lime_drlibs_flac_tell = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_flac_tell", "oo", false)); + private static var lime_drlibs_flac_total = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_flac_total", "oo", + false)); + private static var lime_drlibs_mp3_decode = new cpp.Callablecpp.Object->Int->Int->Int>(cpp.Prime._loadPrime("lime", + "lime_drlibs_mp3_decode", "ooiii", false)); + private static var lime_drlibs_mp3_from_bytes = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_mp3_from_bytes", + "oo", false)); + private static var lime_drlibs_mp3_from_file = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_mp3_from_file", "so", + false)); + private static var lime_drlibs_mp3_info = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_mp3_info", "oo", + false)); + private static var lime_drlibs_mp3_seek = new cpp.Callablecpp.Object->cpp.Object->Int>(cpp.Prime._loadPrime("lime", + "lime_drlibs_mp3_seek", "oooi", false)); + private static var lime_drlibs_mp3_tell = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_mp3_tell", "oo", false)); + private static var lime_drlibs_mp3_total = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_mp3_total", "oo", + false)); + private static var lime_drlibs_mp3_uninit = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_drlibs_mp3_uninit", "ov", false)); + private static var lime_drlibs_wav_decode = new cpp.Callablecpp.Object->Int->Int->Int->Int>(cpp.Prime._loadPrime("lime", + "lime_drlibs_wav_decode", "ooiiii", false)); + private static var lime_drlibs_wav_from_bytes = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_wav_from_bytes", + "oo", false)); + private static var lime_drlibs_wav_from_file = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_wav_from_file", "so", + false)); + private static var lime_drlibs_wav_info = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_wav_info", "oo", + false)); + private static var lime_drlibs_wav_seek = new cpp.Callablecpp.Object->cpp.Object->Int>(cpp.Prime._loadPrime("lime", + "lime_drlibs_wav_seek", "oooi", false)); + private static var lime_drlibs_wav_tell = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_wav_tell", "oo", false)); + private static var lime_drlibs_wav_total = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_drlibs_wav_total", "oo", + false)); + private static var lime_drlibs_wav_uninit = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_drlibs_wav_uninit", "ov", false)); + #end + #end + #if (neko || cppia) + private static var lime_drlibs_flac_close = CFFI.load("lime", "lime_drlibs_flac_close", 1); + private static var lime_drlibs_flac_decode = CFFI.load("lime", "lime_drlibs_flac_decode", 5); + private static var lime_drlibs_flac_from_bytes = CFFI.load("lime", "lime_drlibs_flac_from_bytes", 1); + private static var lime_drlibs_flac_from_file = CFFI.load("lime", "lime_drlibs_flac_from_file", 1); + private static var lime_drlibs_flac_info = CFFI.load("lime", "lime_drlibs_flac_info", 1); + private static var lime_drlibs_flac_seek = CFFI.load("lime", "lime_drlibs_flac_seek", 3); + private static var lime_drlibs_flac_tell = CFFI.load("lime", "lime_drlibs_flac_tell", 1); + private static var lime_drlibs_flac_total = CFFI.load("lime", "lime_drlibs_flac_total", 1); + private static var lime_drlibs_mp3_decode = CFFI.load("lime", "lime_drlibs_mp3_decode", 4); + private static var lime_drlibs_mp3_from_bytes = CFFI.load("lime", "lime_drlibs_mp3_from_bytes", 1); + private static var lime_drlibs_mp3_from_file = CFFI.load("lime", "lime_drlibs_mp3_from_file", 1); + private static var lime_drlibs_mp3_info = CFFI.load("lime", "lime_drlibs_mp3_info", 1); + private static var lime_drlibs_mp3_seek = CFFI.load("lime", "lime_drlibs_mp3_seek", 3); + private static var lime_drlibs_mp3_tell = CFFI.load("lime", "lime_drlibs_mp3_tell", 1); + private static var lime_drlibs_mp3_total = CFFI.load("lime", "lime_drlibs_mp3_total", 1); + private static var lime_drlibs_mp3_uninit = CFFI.load("lime", "lime_drlibs_mp3_uninit", 1); + private static var lime_drlibs_wav_decode = CFFI.load("lime", "lime_drlibs_wav_decode", 5); + private static var lime_drlibs_wav_from_bytes = CFFI.load("lime", "lime_drlibs_wav_from_bytes", 1); + private static var lime_drlibs_wav_from_file = CFFI.load("lime", "lime_drlibs_wav_from_file", 1); + private static var lime_drlibs_wav_info = CFFI.load("lime", "lime_drlibs_wav_info", 1); + private static var lime_drlibs_wav_seek = CFFI.load("lime", "lime_drlibs_wav_seek", 3); + private static var lime_drlibs_wav_tell = CFFI.load("lime", "lime_drlibs_wav_tell", 1); + private static var lime_drlibs_wav_total = CFFI.load("lime", "lime_drlibs_wav_total", 1); + private static var lime_drlibs_wav_uninit = CFFI.load("lime", "lime_drlibs_wav_uninit", 1); + #end + #if hl + @:hlNative("lime", "hl_drlibs_flac_close") private static function lime_drlibs_flac_close(flac:CFFIPointer):Void {} + + @:hlNative("lime", "hl_drlibs_flac_decode") private static function lime_drlibs_flac_decode(flac:CFFIPointer, buffer:Bytes, position:Int, length:Int, + word:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_drlibs_flac_from_bytes") private static function lime_drlibs_flac_from_bytes(bytes:Bytes):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_drlibs_flac_from_file") private static function lime_drlibs_flac_from_file(path:String):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_drlibs_flac_info") private static function lime_drlibs_flac_info(flac:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_flac_seek") private static function lime_drlibs_flac_seek(flac:CFFIPointer, posLow:Int, posHigh:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_drlibs_flac_tell") private static function lime_drlibs_flac_tell(flac:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_flac_total") private static function lime_drlibs_flac_total(flac:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_mp3_decode") private static function lime_drlibs_mp3_decode(mp3:CFFIPointer, buffer:Bytes, position:Int, length:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_drlibs_mp3_from_bytes") private static function lime_drlibs_mp3_from_bytes(bytes:Bytes):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_drlibs_mp3_from_file") private static function lime_drlibs_mp3_from_file(path:String):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_drlibs_mp3_info") private static function lime_drlibs_mp3_info(mp3:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_mp3_seek") private static function lime_drlibs_mp3_seek(mp3:CFFIPointer, posLow:Int, posHigh:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_drlibs_mp3_tell") private static function lime_drlibs_mp3_tell(mp3:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_mp3_total") private static function lime_drlibs_mp3_total(mp3:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_mp3_uninit") private static function lime_drlibs_mp3_uninit(mp3:CFFIPointer):Void {} + + @:hlNative("lime", "hl_drlibs_wav_decode") private static function lime_drlibs_wav_decode(wav:CFFIPointer, buffer:Bytes, position:Int, length:Int, + word:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_drlibs_wav_from_bytes") private static function lime_drlibs_wav_from_bytes(bytes:Bytes):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_drlibs_wav_from_file") private static function lime_drlibs_wav_from_file(path:String):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_drlibs_wav_info") private static function lime_drlibs_wav_info(wav:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_wav_seek") private static function lime_drlibs_wav_seek(wav:CFFIPointer, posLow:Int, posHigh:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_drlibs_wav_tell") private static function lime_drlibs_wav_tell(wav:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_wav_total") private static function lime_drlibs_wav_total(wav:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_drlibs_wav_uninit") private static function lime_drlibs_wav_uninit(wav:CFFIPointer):Void {} + #end + #end + #if (lime_cffi && !macro && lime_opus) + #if (cpp && !cppia) + #if (disable_cffi || haxe_ver < "3.4.0") + @:cffi private static function lime_opus_file_channel_count(opusFile:Dynamic):Int; + + @:cffi private static function lime_opus_file_decode(opusFile:Dynamic, buffer:Dynamic, position:Int, length:Int):Int; + + @:cffi private static function lime_opus_file_free(opusFile:Dynamic):Void; + + @:cffi private static function lime_opus_file_from_bytes(bytes:Dynamic):Dynamic; + + @:cffi private static function lime_opus_file_from_file(path:String):Dynamic; + + @:cffi private static function lime_opus_file_seek(opusFile:Dynamic, posLow:Dynamic, posHigh:Dynamic):Int; + + @:cffi private static function lime_opus_file_seekable(opusFile:Dynamic):Bool; + + @:cffi private static function lime_opus_file_tell(opusFile:Dynamic):Dynamic; + + @:cffi private static function lime_opus_file_total(opusFile:Dynamic):Dynamic; + #else + private static var lime_opus_file_channel_count = new cpp.CallableInt>(cpp.Prime._loadPrime("lime", "lime_opus_file_channel_count", + "oi", false)); + private static var lime_opus_file_decode = new cpp.Callablecpp.Object->Int->Int->Int>(cpp.Prime._loadPrime("lime", + "lime_opus_file_decode", "ooiii", false)); + private static var lime_opus_file_free = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", "lime_opus_file_free", "ov", false)); + private static var lime_opus_file_from_bytes = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_opus_file_from_bytes", + "oo", false)); + private static var lime_opus_file_from_file = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_opus_file_from_file", "so", + false)); + private static var lime_opus_file_seek = new cpp.Callablecpp.Object->cpp.Object->Int>(cpp.Prime._loadPrime("lime", + "lime_opus_file_seek", "oooi", false)); + private static var lime_opus_file_seekable = new cpp.CallableBool>(cpp.Prime._loadPrime("lime", "lime_opus_file_seekable", "ob", false)); + private static var lime_opus_file_tell = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_opus_file_tell", "oo", false)); + private static var lime_opus_file_total = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", "lime_opus_file_total", "oo", + false)); + #end + #end + #if (neko || cppia) + private static var lime_opus_file_channel_count = CFFI.load("lime", "lime_opus_file_channel_count", 1); + private static var lime_opus_file_decode = CFFI.load("lime", "lime_opus_file_decode", 4); + private static var lime_opus_file_free = CFFI.load("lime", "lime_opus_file_free", 1); + private static var lime_opus_file_from_bytes = CFFI.load("lime", "lime_opus_file_from_bytes", 1); + private static var lime_opus_file_from_file = CFFI.load("lime", "lime_opus_file_from_file", 1); + private static var lime_opus_file_seek = CFFI.load("lime", "lime_opus_file_seek", 3); + private static var lime_opus_file_seekable = CFFI.load("lime", "lime_opus_file_seekable", 1); + private static var lime_opus_file_tell = CFFI.load("lime", "lime_opus_file_tell", 1); + private static var lime_opus_file_total = CFFI.load("lime", "lime_opus_file_total", 1); + #end + #if hl + @:hlNative("lime", "hl_opus_file_channel_count") private static function lime_opus_file_channel_count(opusFile:CFFIPointer):Int + { + return 0; + } + + @:hlNative("lime", "hl_opus_file_decode") private static function lime_opus_file_decode(opusFile:CFFIPointer, buffer:Bytes, position:Int, length:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_opus_file_free") private static function lime_opus_file_free(opusFile:CFFIPointer):Void {} + + @:hlNative("lime", "hl_opus_file_from_bytes") private static function lime_opus_file_from_bytes(bytes:Bytes):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_opus_file_from_file") private static function lime_opus_file_from_file(path:String):CFFIPointer + { + return null; + } + + @:hlNative("lime", "hl_opus_file_seek") private static function lime_opus_file_seek(opusFile:CFFIPointer, posLow:Int, posHigh:Int):Int + { + return 0; + } + + @:hlNative("lime", "hl_opus_file_seekable") private static function lime_opus_file_seekable(opusFile:CFFIPointer):Bool + { + return false; + } + + @:hlNative("lime", "hl_opus_file_tell") private static function lime_opus_file_tell(opusFile:CFFIPointer):Dynamic + { + return null; + } + + @:hlNative("lime", "hl_opus_file_total") private static function lime_opus_file_total(opusFile:CFFIPointer):Dynamic + { + return null; + } + #end + #end } diff --git a/src/lime/media/AudioBuffer.hx b/src/lime/media/AudioBuffer.hx index 4a64f67af0..ed6896f305 100644 --- a/src/lime/media/AudioBuffer.hx +++ b/src/lime/media/AudioBuffer.hx @@ -1,7 +1,8 @@ package lime.media; -import haxe.io.Bytes; +import haxe.Int64; import haxe.io.Path; +import haxe.io.Input; import lime._internal.backend.native.NativeCFFI; import lime._internal.format.Base64; import lime.app.Future; @@ -9,17 +10,31 @@ import lime.app.Promise; import lime.media.openal.AL; import lime.media.openal.ALBuffer; import lime.media.vorbis.VorbisFile; +import lime.media.AudioManager; import lime.net.HTTPRequest; +import lime.utils.ArrayBuffer; +import lime.utils.Bytes; import lime.utils.Log; import lime.utils.UInt8Array; +#if lime_vorbis +import lime.media.decoders.VorbisDecoder; +#end #if lime_howlerjs import lime.media.howlerjs.Howl; #end #if (js && html5) -import js.html.Audio; +import js.lib.Promise as JSPromise; +import js.html.audio.AudioBuffer as JSAudioBuffer; +// import js.html.Audio; +#end +#if sys +import sys.io.File; +import sys.io.FileInput; +import sys.io.FileSeek; #end @:access(lime._internal.backend.native.NativeCFFI) +@:access(lime.media.AudioManager) @:access(lime.utils.Assets) #if hl @:keep @@ -42,6 +57,8 @@ class AudioBuffer { /** The number of bits per sample in the audio data. + NOTE: In native target, when decoded, the bitsPerSample cannot be higher than 16 bits per sample, + because openal can only play 16 or 8 bits per sample. **/ public var bitsPerSample:Int; @@ -51,14 +68,16 @@ class AudioBuffer public var channels:Int; /** - The raw audio data stored as a `UInt8Array`. + Native target only. + The uncompressed audio data stored as a `UInt8Array`. **/ public var data:UInt8Array; /** - The format the raw audio data is stored in. + Native target only. + The decoder for this audio buffer. **/ - public var dataFormat:AudioBufferDataFormat; + public var decoder:AudioDecoder; /** The sample rate of the audio data, in Hz. @@ -66,12 +85,13 @@ class AudioBuffer public var sampleRate:Int; /** - The source of the audio data. This can be an `Audio`, `Sound`, `Howl`, or other platform-specific object. + The source of the audio data. This can be an `js.html.audio.AudioBuffer`, `Sound`, `Howl`, or other platform-specific object. **/ public var src(get, set):Dynamic; - @:noCompletion private var __srcAudio:#if (js && html5) Audio #else Dynamic #end; - @:noCompletion private var __srcBuffer:#if lime_cffi ALBuffer #else Dynamic #end; + @:noCompletion private var __isDisposed:Bool; + @:noCompletion private var __srcAudioBuffer:#if (js && html5) JSAudioBuffer #else Dynamic #end; + @:noCompletion private var __srcBuffer:#if lime_openal ALBuffer #else Dynamic #end; @:noCompletion private var __srcCustom:Dynamic; @:noCompletion private var __srcHowl:#if lime_howlerjs Howl #else Dynamic #end; @:noCompletion private var __srcVorbisFile:#if lime_vorbis VorbisFile #else Dynamic #end; @@ -96,9 +116,112 @@ class AudioBuffer Disposes of the resources used by this `AudioBuffer`, such as unloading any associated audio data. **/ public function dispose():Void + { + __isDisposed = true; + + #if (lime_cffi && !macro) + if (decoder != null) decoder.dispose(); + decoder = null; + #end + #if (js && html5 && lime_howlerjs) + if (__srcHowl != null) __srcHowl.unload(); + __srcHowl = null; + #end + #if lime_openal + if (__srcBuffer != null) AL.deleteBuffer(__srcBuffer); + __srcBuffer = null; + #end + #if lime_vorbis + __srcVorbisFile = null; + #end + } + + /** + Loads the audio buffer from the resource. + **/ + public function load() { #if (js && html5 && lime_howlerjs) - __srcHowl.unload(); + if (__srcHowl == null || bitsPerSample > 0) return; + loadAsync(); + #elseif (lime_cffi && !macro) + if (decoder == null) return; + + // OpenAL can only play 8 or 16 bits per sample audio. + var word = decoder.bitsPerSample > 16 ? 2 : decoder.bitsPerSample >> 3; + + bitsPerSample = word << 3; + channels = decoder.channels; + sampleRate = decoder.sampleRate; + + data = new UInt8Array(Int64.toInt(decoder.total() * channels * word)); + decoder.decode(data.buffer, 0, data.byteLength, word); + #end + } + + /** + Loads asynchronously the audio buffer from the resource. + + @param onLoad The callback when its loaded. + **/ + public function loadAsync(?onLoad:AudioBuffer->Void, ?onError:String->Void) + { + #if (js && html5 && lime_howlerjs) + if (__srcHowl == null) + { + if (onError != null) onError("No Howl to load"); + else if (onLoad != null) onLoad(this); + return; + } + else if (bitsPerSample > 0) + { + if (onLoad != null) onLoad(this); + return; + } + + inline function triggerOnLoad() + { + if (untyped __srcHowl._buffer) __srcAudioBuffer = untyped __srcHowl._buffer; + if (__srcAudioBuffer != null) + { + channels = __srcAudioBuffer.numberOfChannels; + sampleRate = Std.int(__srcAudioBuffer.sampleRate); + } + bitsPerSample = 32; + + if (onLoad != null) onLoad(this); + } + + if (untyped __srcHowl._state == 'loaded') + { + triggerOnLoad(); + } + else + { + __srcHowl.on("load", function() triggerOnLoad()); + __srcHowl.on("loaderror", function(id, msg) + { + if (onError != null) onError(msg); + else if (onLoad != null) onLoad(this); + }); + __srcHowl.load(); + } + + #elseif (lime_cffi && !macro) + if (decoder == null) return; + + // OpenAL can only play 8 or 16 bits per sample audio. + var word = decoder.bitsPerSample > 16 ? 2 : decoder.bitsPerSample >> 3; + + bitsPerSample = word << 3; + channels = decoder.channels; + sampleRate = decoder.sampleRate; + + data = new UInt8Array(Int64.toInt(decoder.total() * channels * word)); + new Future(() -> { + decoder.decode(data.buffer, 0, data.byteLength, word); + return this; + }, true).onComplete(onLoad); #end } @@ -106,9 +229,11 @@ class AudioBuffer Creates an `AudioBuffer` from a Base64-encoded string. @param base64String The Base64-encoded audio data. + @param stream Optional, should it return a streamable 'AudioBuffer' instead. + @param howlHtml5 html5 only. Optional, should it load in html5 audio instead of howler's. @return An `AudioBuffer` instance with the decoded audio data. **/ - public static function fromBase64(base64String:String):AudioBuffer + public static function fromBase64(base64String:String, ?stream:Bool #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer { if (base64String == null) return null; @@ -116,27 +241,28 @@ class AudioBuffer // if base64String doesn't contain codec data, add it. if (base64String.indexOf(",") == -1) { - base64String = "data:" + __getCodec(Base64.decode(base64String)) + ";base64," + base64String; + final bytes = Base64.decode(base64String); + final codec = __getCodecFromBytes(bytes); + if (codec != null) base64String = "data:" + codec.toHTML5() + ";base64," + base64String; } var audioBuffer = new AudioBuffer(); - #if force_html5_audio - audioBuffer.src = new Howl({src: [base64String], html5: true, preload: true}); - #else - audioBuffer.src = new Howl({src: [base64String], preload: true}); - #end + if (howlHtml5) stream = true; + else if (stream) howlHtml5 = true; + else if (stream == null) stream = false; + + audioBuffer.__srcHowl = new Howl({src: [base64String], html5: #if force_html5_audio true #else howlHtml5 #end, preload: !stream}); + audioBuffer.load(); return audioBuffer; #elseif (lime_cffi && !macro) - // if base64String contains codec data, strip it then decode it. - var base64StringSplit = base64String.split(","); - var base64StringNoEncoding = base64StringSplit[base64StringSplit.length - 1]; - var bytes:Bytes = Base64.decode(base64StringNoEncoding); - var audioBuffer = new AudioBuffer(); - audioBuffer.data = new UInt8Array(Bytes.alloc(0)); + var decoder = AudioDecoder.fromBase64(base64String); - return NativeCFFI.lime_audio_load_bytes(bytes, audioBuffer); + if (decoder != null) + { + return AudioBuffer.fromDecoder(decoder, stream, true); + } #end return null; @@ -146,76 +272,158 @@ class AudioBuffer Creates an `AudioBuffer` from a `Bytes` object. @param bytes The `Bytes` object containing the audio data. + @param stream Optional, should it return a streamable 'AudioBuffer' instead. + @param howlHtml5 html5 only. Optional, should it load in html5 audio instead of howler's. @return An `AudioBuffer` instance with the decoded audio data. **/ - public static function fromBytes(bytes:Bytes):AudioBuffer + public static function fromBytes(bytes:Bytes, ?stream:Bool #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer { if (bytes == null) return null; #if (js && html5 && lime_howlerjs) var audioBuffer = new AudioBuffer(); - #if force_html5_audio - audioBuffer.src = new Howl({src: ["data:" + __getCodec(bytes) + ";base64," + Base64.encode(bytes)], html5: true, preload: true}); - #else - audioBuffer.src = new Howl({src: ["data:" + __getCodec(bytes) + ";base64," + Base64.encode(bytes)], preload: true}); - #end + if (howlHtml5) stream = true; + else if (stream) howlHtml5 = true; + else if (stream == null) stream = false; + + audioBuffer.__srcHowl = new Howl({src: [bytes.getData()], html5: #if force_html5_audio true #else howlHtml5 #end, preload: !stream}); + audioBuffer.load(); return audioBuffer; #elseif (lime_cffi && !macro) - var audioBuffer = new AudioBuffer(); - audioBuffer.data = new UInt8Array(Bytes.alloc(0)); - return NativeCFFI.lime_audio_load_bytes(bytes, audioBuffer); + var decoder = AudioDecoder.fromBytes(bytes); + + if (decoder != null) + { + return AudioBuffer.fromDecoder(decoder, stream, true); + } #end return null; } + /** + NOTE: This is here solely because for Lime 8.4.0-dev compatibility. + Creates a streamed `AudioBuffer` from a `Bytes` object. + + @param bytes The `Bytes` object containing the compressed audio data. + @return An `AudioBuffer` configured for streamed playback when supported. + **/ + public inline static function fromBytesStream(bytes:Bytes):AudioBuffer + { + return fromBytes(bytes, true); + } + + /** + Native target only. + Creates an 'AudioBuffer' from a 'AudioDecoder' object. + + @param audioDecoder The 'AudioDecoder' object. + @param stream Optional, should it return a streamable 'AudioBuffer' instead. + @param autoDisposeDecoder Optional, should it disposes the decoder after this function. + @return An `AudioBuffer` instance with the decoded audio data. + **/ + #if (lime_cffi && !macro) + public static function fromDecoder(audioDecoder:AudioDecoder, stream:Bool = false, autoDisposeDecoder:Bool = false):AudioBuffer + { + var audioBuffer = new AudioBuffer(); + audioBuffer.decoder = audioDecoder; + + if (!stream || !audioDecoder.seekable()) + { + audioBuffer.load(); + if (autoDisposeDecoder) + { + audioBuffer.decoder = null; + audioDecoder.dispose(); + } + } + else + { + // OpenAL can only play 8 or 16 bits per sample audio. + var word = audioDecoder.bitsPerSample > 16 ? 2 : audioDecoder.bitsPerSample >> 3; + + audioBuffer.bitsPerSample = word << 3; + audioBuffer.channels = audioDecoder.channels; + audioBuffer.sampleRate = audioDecoder.sampleRate; + } + + return audioBuffer; + } + #else + public static function fromDecoder(audioDecoder:Dynamic, stream:Bool = false, autoDisposeDecoder:Bool = false):AudioBuffer + { + return null; + } + #end + /** Creates an `AudioBuffer` from a file. @param path The file path to the audio data. + @param stream Optional, should it return a streamable 'AudioBuffer' instead. + @param howlHtml5 html5 only. Optional, should it load in html5 audio instead of howler's. @return An `AudioBuffer` instance with the audio data loaded from the file. **/ - public static function fromFile(path:String):AudioBuffer + public static function fromFile(path:String, ?stream:Bool #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer { if (path == null) return null; #if (js && html5 && lime_howlerjs) var audioBuffer = new AudioBuffer(); - #if force_html5_audio - audioBuffer.__srcHowl = new Howl({src: [path], html5: true, preload: false}); - #else - audioBuffer.__srcHowl = new Howl({src: [path], preload: false}); - #end + if (howlHtml5) stream = true; + else if (stream) howlHtml5 = true; + else if (stream == null) stream = false; + + audioBuffer.__srcHowl = new Howl({src: [path], html5: #if force_html5_audio true #else howlHtml5 #end, preload: !stream}); + audioBuffer.load(); return audioBuffer; #elseif (lime_cffi && !macro) - var audioBuffer = new AudioBuffer(); - audioBuffer.data = new UInt8Array(Bytes.alloc(0)); - return NativeCFFI.lime_audio_load_file(path, audioBuffer); - #else - return null; + var decoder = AudioDecoder.fromFile(path); + + if (decoder != null) + { + return AudioBuffer.fromDecoder(decoder, stream, true); + } #end + + return null; + } + + /** + NOTE: This is here solely because for Lime 8.4.0-dev compatibility. + Creates a streamed `AudioBuffer` from a file. + + @param path The file path to the audio data. + @return An `AudioBuffer` configured for streamed playback when supported. + **/ + public inline static function fromFileStream(path:String):AudioBuffer + { + return fromFile(path, true); } /** Creates an `AudioBuffer` from an array of file paths. @param paths An array of file paths to search for audio data. + @param stream Optional, should it return a streamable 'AudioBuffer' instead. + @param howlHtml5 html5 only. Optional, should it load in html5 audio instead of howler's. @return An `AudioBuffer` instance with the audio data loaded from the first valid file found. **/ - public static function fromFiles(paths:Array):AudioBuffer + public static function fromFiles(paths:Array, ?stream:Bool #if (js && html5 && lime_howlerjs), ?howlHtml5 = false #end):AudioBuffer { #if (js && html5 && lime_howlerjs) var audioBuffer = new AudioBuffer(); - #if force_html5_audio - audioBuffer.__srcHowl = new Howl({src: paths, html5: true, preload: false}); - #else - audioBuffer.__srcHowl = new Howl({src: paths, preload: false}); - #end + if (howlHtml5) stream = true; + else if (stream) howlHtml5 = true; + else if (stream == null) stream = false; + + audioBuffer.__srcHowl = new Howl({src: paths, html5: #if force_html5_audio true #else howlHtml5 #end, preload: !stream}); + audioBuffer.load(); return audioBuffer; #else @@ -223,7 +431,7 @@ class AudioBuffer for (path in paths) { - buffer = AudioBuffer.fromFile(path); + buffer = AudioBuffer.fromFile(path, stream); if (buffer != null) break; } @@ -232,30 +440,37 @@ class AudioBuffer } /** + NOTE: This is here solely because for Lime 8.4.0-dev compatibility. + Creates a streamed `AudioBuffer` from an array of file paths. + + @param paths An array of file paths to search for audio data. + @return An `AudioBuffer` configured for streamed playback when supported. + **/ + public inline static function fromFilesStream(paths:Array):AudioBuffer + { + return fromFiles(paths, true); + } + + /** + Native target only. Creates an `AudioBuffer` from a `VorbisFile`. @param vorbisFile The `VorbisFile` object containing the audio data. + @param stream Optional, should it return a streamable 'AudioBuffer' instead. @return An `AudioBuffer` instance with the decoded audio data. **/ #if lime_vorbis - - public static function fromVorbisFile(vorbisFile:VorbisFile):AudioBuffer + public static function fromVorbisFile(vorbisFile:VorbisFile, ?stream:Bool):AudioBuffer { if (vorbisFile == null) return null; - var info = vorbisFile.info(); - - var audioBuffer = new AudioBuffer(); - audioBuffer.channels = info.channels; - audioBuffer.sampleRate = info.rate; - audioBuffer.bitsPerSample = 16; - audioBuffer.dataFormat = PCM; + var audioDecoder = VorbisDecoder.fromVorbisFile(vorbisFile); + var audioBuffer = AudioBuffer.fromDecoder(audioDecoder, stream); audioBuffer.__srcVorbisFile = vorbisFile; - return audioBuffer; } #else - public static function fromVorbisFile(vorbisFile:Dynamic):AudioBuffer + public static function fromVorbisFile(vorbisFile:Dynamic, ?stream:Bool):AudioBuffer { return null; } @@ -277,20 +492,13 @@ class AudioBuffer if (audioBuffer != null) { #if (js && html5 && lime_howlerjs) - if (audioBuffer != null) + audioBuffer.loadAsync(function(_) { - audioBuffer.__srcHowl.on("load", function() - { - promise.complete(audioBuffer); - }); - - audioBuffer.__srcHowl.on("loaderror", function(id, msg) - { - promise.error(msg); - }); - - audioBuffer.__srcHowl.load(); - } + promise.complete(audioBuffer); + }, function(msg) + { + promise.error(msg); + }); #else promise.complete(audioBuffer); #end @@ -301,21 +509,40 @@ class AudioBuffer } return promise.future; + #elseif (lime_cffi && !macro) + var decoder = AudioDecoder.fromFile(path); + + if (decoder != null) + { + return AudioBuffer.loadFromDecoder(decoder, true); + } + + return cast Future.withError(""); #else - // TODO: Streaming + return cast Future.withError(""); + #end + } + + /** + NOTE: This is here solely because for Lime 8.4.0-dev compatibility. + Asynchronously loads a streamed `AudioBuffer` from a file or URL. - var request = new HTTPRequest(); - return request.load(path).then(function(buffer) + @param path The file path or URL to the audio data. + @return A `Future` that resolves to the loaded `AudioBuffer`. + **/ + public #if (!lime_cffi || macro) inline #end static function loadFromFileStream(path:String):Future + { + #if (lime_cffi && !macro) + var decoder = AudioDecoder.fromFile(path); + + if (decoder != null) { - if (buffer != null) - { - return Future.withValue(buffer); - } - else - { - return cast Future.withError(""); - } - }); + return Future.withValue(AudioBuffer.fromDecoder(decoder, true, true)); + } + + return cast Future.withError(""); + #else + return loadFromFile(path); #end } @@ -334,17 +561,13 @@ class AudioBuffer if (audioBuffer != null) { - audioBuffer.__srcHowl.on("load", function() + audioBuffer.loadAsync(function(_) { promise.complete(audioBuffer); - }); - - audioBuffer.__srcHowl.on("loaderror", function() + }, function(msg) { - promise.error(null); + promise.error(msg); }); - - audioBuffer.__srcHowl.load(); } else { @@ -352,45 +575,224 @@ class AudioBuffer } return promise.future; + #elseif (lime_cffi && !macro) + for (path in paths) + { + var decoder = AudioDecoder.fromFile(path); + + if (decoder != null) + { + return AudioBuffer.loadFromDecoder(decoder, true); + } + } + + return cast Future.withError(""); #else - return new Future(fromFiles.bind(paths), true); + return cast Future.withError(""); #end } - private static function __getCodec(bytes:Bytes):String + /** + NOTE: This is here solely because for Lime 8.4.0-dev compatibility. + Asynchronously loads a streamed `AudioBuffer` from multiple files or URLs. + + @param paths An array of file paths or URLs to search for audio data. + @return A `Future` that resolves to the loaded `AudioBuffer`. + **/ + public #if (!lime_cffi || macro) inline #end static function loadFromFilesStream(paths:Array):Future + { + #if (lime_cffi && !macro) + for (path in paths) + { + var decoder = AudioDecoder.fromFile(path); + + if (decoder != null) + { + return Future.withValue(AudioBuffer.fromDecoder(decoder, true, true)); + } + } + + return cast Future.withError(""); + #else + return loadFromFiles(paths); + #end + } + + /** + Native target only. + Asynchronously loads an 'AudioBuffer' from 'AudioDecoder'. + + @param audioDecoder The 'AudioDecoder' object. + @param autoDisposeDecoder Optional, should it disposes the decoder after this function. + @return A 'Future' that resolves to the loaded 'AudioBuffer'. + **/ + #if (lime_cffi && !macro) + public static function loadFromDecoder(audioDecoder:AudioDecoder, autoDisposeDecoder = false):Future + { + var promise = new Promise(); + var audioBuffer = new AudioBuffer(); + audioBuffer.decoder = audioDecoder; + audioBuffer.loadAsync((audioBuffer) -> + { + audioBuffer.decoder.dispose(); + audioBuffer.decoder = null; + promise.complete(audioBuffer); + }); + return promise.future; + } + #else + public static function loadFromDecoder(audioDecoder:Dynamic, autoDisposeDecoder = false):Future + { + return cast Future.withError(""); + } + #end + + /** + Get the codec that is in the audio resource. + + @param resource Any data to interpret as audio data. + @return AudioCodec + **/ + public static function getCodec(resource:Dynamic):AudioCodec + { + if (resource is haxe.io.Bytes) + { + return __getCodecFromBytes(cast resource); + } + #if sys + #if android // aassets + else if (resource is String) + { + return __getCodecFromBytes(Bytes.fromFile(cast resource)); + } + #else + else if (resource is String) + { + return __getCodecFromInput(File.read(cast resource, true)); + } + #end + else if (resource is FileInput) + { + cast(resource, FileInput).seek(0, SeekBegin); + return __getCodecFromInput(cast resource); + } + #end + else if (resource is Input) + { + return __getCodecFromInput(cast resource); + } + return null; + } + + @:noCompletion private static function __getALFormat(bitsPerSample:Int, channels:Int):Int + { + // 32 bits per sample enums doesnt seem to work... + #if (lime_openal && !macro) + if (channels > 2 && AudioManager.__moreFormatsSupported) { + if (channels == 3) return bitsPerSample == 32 ? AL.FORMAT_REAR32 : (bitsPerSample == 16 ? AL.FORMAT_REAR16 : AL.FORMAT_REAR8); + else if (channels == 4) return bitsPerSample == 32 ? AL.FORMAT_QUAD32 : (bitsPerSample == 16 ? AL.FORMAT_QUAD16 : AL.FORMAT_QUAD8); + else if (channels == 6) return bitsPerSample == 32 ? AL.FORMAT_51CHN32 : (bitsPerSample == 16 ? AL.FORMAT_51CHN16 : AL.FORMAT_51CHN8); + else if (channels == 7) return bitsPerSample == 32 ? AL.FORMAT_61CHN32 : (bitsPerSample == 16 ? AL.FORMAT_61CHN16 : AL.FORMAT_61CHN8); + else if (channels == 8) return bitsPerSample == 32 ? AL.FORMAT_71CHN32 : (bitsPerSample == 16 ? AL.FORMAT_71CHN16 : AL.FORMAT_71CHN8); + else return 0; + } + else if (bitsPerSample == 32) return channels == 2 ? AL.FORMAT_STEREO32 : channels == 1 ? AL.FORMAT_MONO32 : 0; + else if (channels == 2) return bitsPerSample == 16 ? AL.FORMAT_STEREO16 : bitsPerSample == 8 ? AL.FORMAT_STEREO8 : 0; + else if (channels == 1) return bitsPerSample == 16 ? AL.FORMAT_MONO16 : bitsPerSample == 8 ? AL.FORMAT_MONO8 : 0; + #end + return 0; + } + + @:noCompletion private static function __getCodecFromBytes(bytes:Bytes):AudioCodec { - var signature:String = null; try { - signature = bytes.getString(0, 4); + var signature = bytes.getString(0, 4); + + switch (signature.substr(0, 3)) { + case "ID3": return MPEG; + } + + switch (signature) { + case "OggS": + var fmt = bytes.getString(28, 4); + if (fmt == "Opus") return OPUS;// OpusHead + else return VORBIS; + case "fLaC": return FLAC; + case "RIFF": + var fmt = bytes.getString(8, 4); + if (fmt == "WAVE") return WAVE; + } } catch (e:Dynamic) { // if the bytes don't represent a valid UTF-8 string, getString() // may throw an exception. in that case, we expect to end up in - // the default switch case below where it tries to detect MP3. + // the default switch case below where it tries to detect MPEG. + } + + if (bytes.get(0) == 255) { + var b = bytes.get(1); + if (b == 251 || b == 250 || b == 243) return MPEG; } - switch (signature) + return null; + } + + @:noCompletion private static function __getCodecFromInput(input:Input):AudioCodec + { + var bytes = Bytes.alloc(4); + input.readBytes(bytes, 0, 4); + + try + { + var signature = bytes.getString(0, 4); + + switch (signature.substr(0, 3)) { + case "ID3": return MPEG; + } + + switch (signature) { + case "OggS": + #if sys + if (input is FileInput) cast(input, FileInput).seek(24, SeekCur); + else + #end for (i in 0...24) input.readByte(); + + var fmt = input.readString(4); + if (fmt == "Opus") return OPUS;// OpusHead + else return VORBIS; + case "fLaC": return FLAC; + case "RIFF": + #if sys + if (input is FileInput) cast(input, FileInput).seek(4, SeekCur); + else + #end for (i in 0...4) input.readByte(); + + var fmt = input.readString(4); + if (fmt == "WAVE") return WAVE; + } + } + catch (e:Dynamic) { - case "OggS": - return "audio/ogg"; - case "fLaC": - return "audio/flac"; - case "RIFF" if (bytes.getString(8, 4) == "WAVE"): - return "audio/wav"; - default: - switch ([bytes.get(0), bytes.get(1), bytes.get(2)]) - { - case [73, 68, 51] | [255, 251, _] | [255, 250, _] | [255, 243, _]: return "audio/mp3"; - default: - } - } - - Log.error("Unsupported sound format"); + // if the bytes don't represent a valid UTF-8 string, getString() + // may throw an exception. in that case, we expect to end up in + // the default switch case below where it tries to detect MPEG. + } + + if (bytes.get(0) == 255) { + var b = bytes.get(1); + if (b == 251 || b == 250 || b == 243) return MPEG; + } + return null; } + @:noCompletion private static function __isRemotePath(path:String):Bool + { + return path != null && (path.indexOf("http://") == 0 || path.indexOf("https://") == 0); + } + // Get & Set Methods @:noCompletion private function get_src():Dynamic { @@ -398,10 +800,8 @@ class AudioBuffer #if lime_howlerjs return __srcHowl; #else - return __srcAudio; + return __srcAudioBuffer; #end - #elseif lime_vorbis - return __srcVorbisFile; #else return __srcCustom; #end @@ -413,10 +813,8 @@ class AudioBuffer #if lime_howlerjs return __srcHowl = value; #else - return __srcAudio = value; + return __srcAudioBuffer = value; #end - #elseif lime_vorbis - return __srcVorbisFile = value; #else return __srcCustom = value; #end diff --git a/src/lime/media/AudioCodec.hx b/src/lime/media/AudioCodec.hx new file mode 100644 index 0000000000..7496d562a3 --- /dev/null +++ b/src/lime/media/AudioCodec.hx @@ -0,0 +1,74 @@ +package lime.media; + +#if (haxe_ver >= 4.0) enum #else @:enum #end abstract AudioCodec(Null) from Null to Null +{ + var WAVE = "WAVE"; + var MPEG = "MPEG"; + var VORBIS = "VORBIS"; + var FLAC = "FLAC"; + var OPUS = "OPUS"; + + public static function fromHTML5(value:String):AudioCodec + { + if (value == null) return null; + value = value.toLowerCase(); + + if (value.indexOf("opus") != -1) return OPUS; + else if (value.indexOf("vorbis") != -1) return VORBIS; + + value = StringTools.ltrim(value); + var idx = value.indexOf(" "); + if (idx != -1) value = value.substr(0, idx); + + return switch (value) + { + case "audio/wav": WAVE; + case "audio/mp2", "audio/mp3", "audio/mp4", "audio/mpeg": MPEG; + case "audio/webm": OPUS; + case "audio/ogg": VORBIS; + case "audio/flac", "audio/x-flac": FLAC; + default: null; + } + } + + public function toHTML5():String + { + return switch (cast this : AudioCodec) + { + case WAVE: "audio/wav"; + case MPEG: "audio/mpeg"; + case VORBIS: "audio/ogg"; + case FLAC: "audio/flac"; + case OPUS: "audio/ogg; codecs=\"opus\""; + default: "audio/null"; + } + } + + public static function fromFormat(value:String):AudioCodec + { + if (value == null) return null; + + return switch (value.toLowerCase()) + { + case "wav": WAVE; + case "mp3": MPEG; + case "ogg": VORBIS; + case "flac": FLAC; + case "opus": OPUS; + default: null; + } + } + + public function toFormat():String + { + return switch (cast this : AudioCodec) + { + case WAVE: "wav"; + case MPEG: "mp3"; + case VORBIS: "ogg"; + case FLAC: "flac"; + case OPUS: "opus"; + default: "dat"; + } + } +} diff --git a/src/lime/media/AudioContext.hx b/src/lime/media/AudioContext.hx index ddc86e9744..ae0ee70151 100644 --- a/src/lime/media/AudioContext.hx +++ b/src/lime/media/AudioContext.hx @@ -1,5 +1,10 @@ package lime.media; +#if (js && html5 && lime_howlerjs) +import lime.media.howlerjs.Howler; +#end +import lime.utils.Log; + @:access(lime.media.FlashAudioContext) @:access(lime.media.HTML5AudioContext) @:access(lime.media.OpenALAudioContext) @@ -7,9 +12,6 @@ package lime.media; class AudioContext { public var custom:Dynamic; - #if (!lime_doc_gen || (js && html5)) - public var html5(default, null):HTML5AudioContext; - #end #if (!lime_doc_gen || lime_openal) public var openal(default, null):OpenALAudioContext; #end @@ -23,22 +25,42 @@ class AudioContext if (type != CUSTOM) { #if (js && html5) - if (type == null || type == WEB) + #if lime_howlerjs + if (Howler.usingWebAudio) { - try + web = Howler.ctx; + this.type = WEB; + } + else + { + #if (!lime_doc_gen && !display) + Howler._setupAudioContext(); + #end + if (Howler.usingWebAudio) { - untyped js.Syntax.code("window.AudioContext = window.AudioContext || window.webkitAudioContext;"); - web = cast untyped js.Syntax.code("new window.AudioContext ()"); + web = Howler.ctx; this.type = WEB; } - catch (e:Dynamic) {} + else + { + Log.info("Unable to create howlerjs context for Web!"); + } } - - if (web == null && type != WEB) + #else + try { - html5 = new HTML5AudioContext(); - this.type = HTML5; + untyped js.Syntax.code("window.AudioContext = window.AudioContext || window.webkitAudioContext;"); + web = cast untyped js.Syntax.code("new window.AudioContext ()"); + this.type = WEB; } + catch (e:Dynamic) + { + Log.info("Unable to create AudioContext for Web!"); + } + #end + #elseif flash + flash = new FlashAudioContext(); + this.type = FLASH; #else openal = new OpenALAudioContext(); this.type = OPENAL; diff --git a/src/lime/media/AudioContextType.hx b/src/lime/media/AudioContextType.hx index 55d556fea3..58ab254085 100644 --- a/src/lime/media/AudioContextType.hx +++ b/src/lime/media/AudioContextType.hx @@ -2,7 +2,6 @@ package lime.media; enum abstract AudioContextType(String) from String to String { - var HTML5 = "html5"; var OPENAL = "openal"; var WEB = "web"; var CUSTOM = "custom"; diff --git a/src/lime/media/AudioDecoder.hx b/src/lime/media/AudioDecoder.hx new file mode 100644 index 0000000000..4d6929dde7 --- /dev/null +++ b/src/lime/media/AudioDecoder.hx @@ -0,0 +1,231 @@ +package lime.media; + +import haxe.Int64; +import haxe.io.Bytes; +import lime._internal.format.Base64; +import lime.utils.ArrayBuffer; +#if (lime_cffi && !macro) +#if lime_opus +import lime.media.decoders.OpusDecoder; +#end +#if lime_vorbis +import lime.media.decoders.VorbisDecoder; +#end +#if lime_drlibs +import lime.media.decoders.WaveDecoder; +import lime.media.decoders.MP3Decoder; +import lime.media.decoders.FLACDecoder; +#end +#end + +@:access(lime.media.AudioBuffer) +#if hl +@:keep +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + +**/ +class AudioDecoder +{ + /** + The number of bits per sample in the audio decoder. + **/ + public var bitsPerSample:Int; + + /** + The number of audio channels (e.g., 1 for mono, 2 for stereo). + **/ + public var channels:Int; + + /** + Read-only variable to check if the decoder has reached end of buffer/file. + **/ + public var eof:Bool; + + /** + The sample rate of the audio data, in Hz. + **/ + public var sampleRate:Int; + + @:noCompletion private var __isDisposed:Bool; + @:noCompletion private var __bytes:Null; + @:noCompletion private var __path:Null; + + @:noCompletion private function new() {} + + /** + Disposes of the resources used by this `AudioDecoder`, such as unloading any associated buffer or file. + **/ + public function dispose():Void + { + eof = true; + + __isDisposed = true; + __bytes = null; + __path = null; + } + + /** + Clones this 'AudioDecoder'. + **/ + public function clone():AudioDecoder + { + return null; + } + + /** + Decodes to an audio data. + + @param buffer An 'ArrayBuffer' to pass the decoded data to. + @param pos Offset in byte for the passed buffer. + @param len Length in byte to read to the passed buffer. + @param word What byte type to use to the passed buffer. + @return The number of bytes filled/read into the buffer. + **/ + public function decode(buffer:ArrayBuffer, pos:Int, len:Int, word:Int):Int + { + return 0; + } + + /** + Rewinds the decoder to the start of the buffer or file. + **/ + public function rewind():Bool + { + return false; + } + + /** + Sets the sample (Hz) position for the decoder to read. + **/ + public function seek(samples:Int64):Bool + { + return false; + } + + /** + Returns a boolean if this 'AudioDecoder' are able to use the 'seek' function. + **/ + public function seekable():Bool + { + return false; + } + + /** + Returns the current sample (Hz) position. + **/ + public function tell():Int64 + { + return 0; + } + + /** + Returns the total of samples (Hz) for the decoder to read. + **/ + public function total():Int64 + { + return 0; + } + + /** + Creates an `AudioDecoder` from a Base64-encoded string. + + @param base64String The Base64-encoded audio data. + @return An `AudioDecoder` instance. + **/ + public static function fromBase64(base64String:String):AudioDecoder + { + if (base64String == null) return null; + + #if (lime_cffi && !macro) + var idx = base64String.indexOf(","); + var bytes:Bytes; + if (idx == -1) + { + bytes = Base64.decode(base64String); + } + else + { + bytes = Base64.decode(base64String.substr(idx + 1)); + } + + return AudioDecoder.fromBytes(bytes); + #else + return null; + #end + } + + /** + Creates an `AudioDecoder` from a `Bytes` object. + + @param bytes The `Bytes` object containing the encoded audio data. + @return An `AudioDecoder` instance. + **/ + public static function fromBytes(bytes:Bytes):AudioDecoder + { + if (bytes == null) return null; + + #if (lime_cffi && !macro) + return switch (AudioBuffer.__getCodecFromBytes(bytes)) + { + #if lime_opus + case OPUS: OpusDecoder.fromBytes(bytes); + #end + #if lime_vorbis + case VORBIS: VorbisDecoder.fromBytes(bytes); + #end + #if lime_drlibs + case WAVE: WaveDecoder.fromBytes(bytes); + case MPEG: MP3Decoder.fromBytes(bytes); + case FLAC: FLACDecoder.fromBytes(bytes); + #end + default: null; + } + #else + return null; + #end + } + + /** + Creates an `AudioDecoder` from a file. + + @param path The file path to the audio asset file. + @return An `AudioDecoder` instance. + **/ + public static function fromFile(path:String):AudioDecoder + { + if (path == null) return null; + + #if (lime_cffi && !macro) + var decoder:AudioDecoder; + + #if lime_opus + decoder = OpusDecoder.fromFile(path); + if (decoder != null) return decoder; + #end + + #if lime_vorbis + decoder = VorbisDecoder.fromFile(path); + if (decoder != null) return decoder; + #end + + #if lime_drlibs + decoder = WaveDecoder.fromFile(path); + if (decoder != null) return decoder; + + decoder = MP3Decoder.fromFile(path); + if (decoder != null) return decoder; + + decoder = FLACDecoder.fromFile(path); + if (decoder != null) return decoder; + #end + #end + + return null; + } +} \ No newline at end of file diff --git a/src/lime/media/AudioEffect.hx b/src/lime/media/AudioEffect.hx new file mode 100644 index 0000000000..6f08bb76b1 --- /dev/null +++ b/src/lime/media/AudioEffect.hx @@ -0,0 +1,153 @@ +package lime.media; + +#if lime_openal +import lime.media.openal.AL; +import lime.media.openal.ALAuxiliaryEffectSlot; +import lime.media.openal.ALEffect; +import lime.media.openal.ALFilter; +import lime.media.openal.ALSource; +#elseif (js && html5) +import js.html.audio.AudioNode; +#end + +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +/** + Audio effects that can be used to apply to an audio source. + + NOTE: Only works in web, and native targets, flash doesn't support this. +**/ +class AudioEffect +{ + /** + Disables this audio effect. + **/ + public var bypass(default, set):Bool; + + /** + Should it dispose immediately if it has been unused. + **/ + public var autoDispose:Bool = true; + + @:allow(lime.media.AudioSource) + @:noCompletion private var __appliedSources:Array; + #if lime_openal + @:noCompletion private var __alAux:Null; + @:noCompletion private var __alEffect:Null; + @:noCompletion private var __alFilter:Null; + #elseif (js && html5) + @:noCompletion private var __audioNodes:Array; + #end + + /** + Creates a new `AudioEffect` instance. + Won't do anything since it haven't been implemented. + **/ + public function new() + { + __appliedSources = []; + + #if lime_openal + __alAux = AL.createAux(); + #elseif (js && html5) + __audioNodes = []; + #end + } + + /** + Releases any resources used by this `AudioEffect`. + **/ + public function dispose():Void + { + if (__appliedSources == null) return; + + if (!bypass) + { + for (source in __appliedSources) + { + if (source != null) + { + source.removeEffect(this); + + } + } + } + __appliedSources = null; + + #if lime_openal + if (__alAux != null) + { + AL.deleteAux(__alAux); + __alAux = null; + } + + if (__alEffect != null) + { + AL.deleteEffect(__alEffect); + __alEffect = null; + } + + if (__alFilter != null) + { + AL.deleteFilter(__alFilter); + __alFilter = null; + } + #elseif (js && html5) + if (__audioNodes != null) + { + for (node in __audioNodes) + { + node.disconnect(); + } + __audioNodes = null; + } + #end + } + + @:noCompletion private function __update():Void + { + if (!bypass) + { + for (source in __appliedSources) + { + if (source != null) + { + @:privateAccess + source.__backend.updateEffect(source.__effects.indexOf(this)); + } + } + } + } + + @:noCompletion private inline function set_bypass(value:Bool):Bool + { + if (value == bypass) return value; + + if (value) + { + for (source in __appliedSources) + { + if (source != null) + { + @:privateAccess + source.__backend.addEffect(source.__effects.indexOf(this)); + } + } + } + else + { + for (source in __appliedSources) + { + if (source != null) + { + @:privateAccess + source.__backend.removeEffect(source.__effects.indexOf(this)); + } + } + } + + return bypass = value; + } +} \ No newline at end of file diff --git a/src/lime/media/AudioManager.hx b/src/lime/media/AudioManager.hx index c755401941..5b6e7dea17 100644 --- a/src/lime/media/AudioManager.hx +++ b/src/lime/media/AudioManager.hx @@ -1,214 +1,590 @@ package lime.media; -import lime.system.CFFIPointer; -import haxe.MainLoop; -#if (windows || mac || linux || android || ios) -import haxe.io.Path; +import lime.app.Event; import lime.system.System; -import sys.FileSystem; -import sys.io.File; -#end -import haxe.Timer; -import lime._internal.backend.native.NativeCFFI; +#if lime_openal import lime.media.openal.AL; import lime.media.openal.ALC; import lime.media.openal.ALContext; import lime.media.openal.ALDevice; -import lime.app.Application; -#if (js && html5) -import js.Browser; +import lime.system.CFFIPointer; +import lime.system.CFFI; +#elseif lime_howlerjs +import lime.media.howlerjs.Howler; +#elseif flash +import flash.media.SoundMixer; +import flash.media.SoundTransform; #end #if !lime_debug @:fileXml('tags="haxe,release"') @:noDebug #end -@:access(lime._internal.backend.native.NativeCFFI) @:access(lime.media.openal.ALDevice) class AudioManager { + /** + The current used context to use for the audio manager. + **/ public static var context:AudioContext; + /** + Dispatched when the default for the playback device is changed. + 'Device Name' -> Void. + **/ + public static var onDefaultPlaybackDeviceChanged = new EventVoid>(); + + /** + Dispatched whenever a playback device is added. + 'Device Name' -> Void. + **/ + public static var onPlaybackDeviceAdded = new EventVoid>(); + + /** + Dispatched whenever a playback device is removed. + 'Device Name' -> Void. + **/ + public static var onPlaybackDeviceRemoved = new EventVoid>(); + + /** + Dispatched when the default for the capture device is changed. + 'Device Name' -> Void. + **/ + public static var onDefaultCaptureDeviceChanged = new EventVoid>(); + + /** + Dispatched whenever a capture device is added. + 'Device Name' -> Void. + **/ + public static var onCaptureDeviceAdded = new EventVoid>(); + + /** + Dispatched whenever a capture device is removed. + 'Device Name' -> Void. + **/ + public static var onCaptureDeviceRemoved = new EventVoid>(); + + /** + Should it automatically switch to the default playback device whenever it changes. + **/ + public static var automaticDefaultPlaybackDevice:Bool = true; + + /** + Mutes the audio manager playback. + **/ + public static var muted(get, set):Bool; + + /** + The gain (volume) of the audio manager. A value of `1.0` represents the default volume. + Property is in a linear scale. + **/ + public static var gain(get, set):Float; + + @:noCompletion private static var __muted:Bool = false; + @:noCompletion private static var __gain:Float = 1; + #if lime_openal + @:noCompletion private static var __effectExtSupported:Bool; + @:noCompletion private static var __captureExtSupported:Bool; + @:noCompletion private static var __disconnectExtSupported:Bool; + @:noCompletion private static var __deviceClockSupported:Bool; + @:noCompletion private static var __reopenDeviceSupported:Bool; + @:noCompletion private static var __systemEventsSupported:Bool; + @:noCompletion private static var __enumerateAllSupported:Bool; + + @:noCompletion private static var __latencyExtSupported:Bool; + @:noCompletion private static var __directChannelsExtSupported:Bool; + @:noCompletion private static var __loopPointsSupported:Bool; + @:noCompletion private static var __moreFormatsSupported:Bool; + @:noCompletion private static var __spatializeSupported:Bool; + @:noCompletion private static var __stereoAnglesSupported:Bool; + #elseif flash + @:noCompletion private static var __flashSoundTransform:SoundTransform; + #end + + /** + Initializes an `AudioManager` to playback to and capture from audio devices. + Automatically dispatched when Application is constructed. + + @param context Optional; An Audio Context to initalize the `AudioManager` with. + **/ public static function init(context:AudioContext = null) { - if (AudioManager.context == null) + if (AudioManager.context != null) return; + + if (context == null) { - if (context == null) - { - AudioManager.context = new AudioContext(); + context = new AudioContext(); + } - context = AudioManager.context; + AudioManager.context = context; - #if !lime_doc_gen - if (context.type == OPENAL) - { - #if (windows || mac || linux || android || ios) - setupConfig(); - #end - - var alc = context.openal; - var device = alc.openDevice(); - if (device != null) - { - var ctx = alc.createContext(device); - alc.makeContextCurrent(ctx); - alc.processContext(ctx); - - #if (lime_openalsoft && !mobile) - if (alc.isExtensionPresent('ALC_SOFT_system_events', device) && alc.isExtensionPresent('ALC_SOFT_reopen_device', device)) - { - if (alc.isExtensionPresent('AL_SOFT_hold_on_disconnect')) - alc.disable(AL.STOP_SOURCES_ON_DISCONNECT_SOFT); - - alc.eventControlSOFT([ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT, ALC.EVENT_TYPE_DEVICE_ADDED_SOFT, ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT], true); - - alc.eventCallbackSOFT(deviceEventCallback); - } - #end - } - } - #end + #if lime_openal + if (context.type == OPENAL) + { + refresh(); + + #if (lime_openalsoft && !mobile) + if (__reopenDeviceSupported) AL.disable(AL.STOP_SOURCES_ON_DISCONNECT_SOFT); + if (__systemEventsSupported) { + ALC.eventControlSOFT([ + ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT, + ALC.EVENT_TYPE_DEVICE_ADDED_SOFT, + ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT], + true); + ALC.eventCallbackSOFT(__deviceEventCallback); } + #end + } + #end + } + + /** + Refresh the context with optionally to different device. - AudioManager.context = context; + Only works on Native target. + + @param deviceName Optional; The device name to use to for playbacking the audios. + **/ + public static function refresh(?deviceName:String):Bool + { + #if (lime_openal && !lime_doc_gen) + if (context == null || context.type != OPENAL) return false; + + var currentContext = ALC.getCurrentContext(); + var device = currentContext != null ? ALC.getContextsDevice(currentContext) : null; + + #if (lime_openalsoft && !mobile) + if (device != null && __reopenDeviceSupported && ALC.reopenDeviceSOFT(device, deviceName, null)) + { + __refresh(); + return true; } + #end + + if (currentContext != null) + { + ALC.destroyContext(currentContext); + currentContext = null; + } + + if (device != null) + { + ALC.closeDevice(device); + device = null; + } + + if ((device = ALC.openDevice()) == null || (currentContext = ALC.createContext(device)) == null + || !ALC.makeContextCurrent(currentContext)) + { + return false; + } + + ALC.processContext(currentContext); + __refresh(); + return true; + #else + return false; + #end } - public static function resume():Void + /** + Gets the default capture audio device name from the host operating system. + + Only works on Native target. + + @return The default capture audio device name. + **/ + public static function getCaptureDefaultDeviceName():String { - #if !lime_doc_gen - if (context != null && context.type == OPENAL) + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL && __captureExtSupported) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); + return __formatDeviceName(ALC.getString(null, ALC.CAPTURE_DEFAULT_DEVICE_SPECIFIER)); + } + #end + return ''; + } + /** + Gets all of the available capture audio device names from the host operating system. + + Only works on Native target. + + @return An array containing available capture audio device names. + **/ + public static function getCaptureDeviceNames():Array + { + #if (lime_openal && !lime_doc_gen) + if (context == null || context.type != OPENAL || !__captureExtSupported) return []; + + final arr = ALC.getStringList(null, ALC.CAPTURE_DEVICE_SPECIFIER); + for (i in 0...arr.length) arr[i] = __formatDeviceName(arr[i]); + + // A bug with using SDL3 backend to wasapi + arr.remove("Default Device"); + + return arr; + #else + return []; + #end + } + + /** + Gets the current used playback audio device name. + + Only works on Native target. + + @return Current playback audio device name. + **/ + public static function getCurrentPlaybackDeviceName():String + { + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL) + { + var currentContext = ALC.getCurrentContext(); if (currentContext != null) { - var device = alc.getContextsDevice(currentContext); - alc.resumeDevice(device); - alc.processContext(currentContext); + var device = ALC.getContextsDevice(currentContext); + if (device != null) + { + if (__enumerateAllSupported) return __formatDeviceName(ALC.getString(device, ALC.ALL_DEVICES_SPECIFIER)); + else return __formatDeviceName(ALC.getString(device, ALC.DEVICE_SPECIFIER)); + } } } #end + return ''; } - public static function shutdown():Void + /** + Gets the default playback audio device name from the host operating system. + + Only works on Native target. + + @return The default playback audio device name. + **/ + public static function getPlaybackDefaultDeviceName():String { - #if !lime_doc_gen + #if (lime_openal && !lime_doc_gen) if (context != null && context.type == OPENAL) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); - var device = alc.getContextsDevice(currentContext); + if (__enumerateAllSupported) return __formatDeviceName(ALC.getString(null, ALC.DEFAULT_ALL_DEVICES_SPECIFIER)); + else return __formatDeviceName(ALC.getString(null, ALC.DEFAULT_DEVICE_SPECIFIER)); + } + #end + return ''; + } + + /** + Gets all of the available playback audio device names from the host operating system. + + Only works on Native target. + + @return An array containing available playback audio device names. + **/ + public static function getPlaybackDeviceNames():Array + { + #if (lime_openal && !lime_doc_gen) + if (context == null || context.type != OPENAL) return []; + else if (!__enumerateAllSupported) return [__formatDeviceName(ALC.getString(null, ALC.DEVICE_SPECIFIER))]; + + final arr = ALC.getStringList(null, ALC.ALL_DEVICES_SPECIFIER); + for (i in 0...arr.length) arr[i] = __formatDeviceName(arr[i]); + + // A bug with using SDL3 backend to wasapi + arr.remove("Default Device"); + + return arr; + #else + return []; + #end + } + + /** + Queries the current timer or clock from the current context, best to measure latency, timer drift, etc. + @return The current clock time from the current context. + **/ + public static function getTimer():Float + { + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL && __deviceClockSupported) + { + var currentContext = ALC.getCurrentContext(); if (currentContext != null) { - alc.makeContextCurrent(null); - alc.destroyContext(currentContext); - + var device = ALC.getContextsDevice(currentContext); if (device != null) { - alc.closeDevice(device); + return ALC.getDoublevSOFT(device, ALC.DEVICE_CLOCK_SOFT)[0] / 1e+6; } } } + #elseif (js && html5) + if (context != null && context.type == WEB) + { + return context.web.currentTime * 1000.0; + } #end - context = null; + return System.getTimer(); } - public static function suspend():Void + /** + Queries the current audio device latency. + + Only works on Native target. + + @return The current audio device latency. + **/ + public static function getLatency():Float { - #if !lime_doc_gen - if (context != null && context.type == OPENAL) + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL && __deviceClockSupported) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); - var device = alc.getContextsDevice(currentContext); - + var currentContext = ALC.getCurrentContext(); if (currentContext != null) { - alc.suspendContext(currentContext); - + var device = ALC.getContextsDevice(currentContext); if (device != null) { - alc.pauseDevice(device); + return ALC.getDoublevSOFT(device, ALC.DEVICE_LATENCY_SOFT)[0] / 1e+6; } } } #end + + return 0.0; } - #if lime_openalsoft - @:noCompletion - private static function deviceEventCallback(eventType:Int, deviceType:Int, handle:CFFIPointer, message:#if hl hl.Bytes #else String #end):Void + /** + Resumes the current `AudioManager` context. + + This function does not work on the Flash target. + **/ + public static function resume():Void { - #if !lime_doc_gen - if (eventType == ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT && deviceType == ALC.PLAYBACK_DEVICE_SOFT) + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL) { - var device = new ALDevice(handle); - - MainLoop.runInMainThread(function():Void + var currentContext = ALC.getCurrentContext(); + if (currentContext != null) { - var alc = context.openal; + var device = ALC.getContextsDevice(currentContext); + if (device != null) ALC.resumeDevice(device); - if (device == null) - { - var currentContext = alc.getCurrentContext(); + ALC.processContext(currentContext); + } + } + #elseif (js && html5) + if (context != null && context.type == WEB) + { + context.web.resume(); + } + #end + } - var device = alc.getContextsDevice(currentContext); + /** + Shutdowns the current `AudioManager` context. - if (device != null) - alc.reopenDeviceSOFT(device, null, null); - } - else - { - alc.reopenDeviceSOFT(device, null, null); - } + This function does not work on the Flash target. + **/ + public static function shutdown():Void + { + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL) + { + var currentContext = ALC.getCurrentContext(); + if (currentContext != null) + { + ALC.makeContextCurrent(null); + ALC.destroyContext(currentContext); - }); + var device = ALC.getContextsDevice(currentContext); + if (device != null) ALC.closeDevice(device); + } + } + #elseif (js && html5) + if (context != null && context.type == WEB) + { + #if lime_howlerjs + Howler.unload(); + #else + context.web.close(); + #end } #end + + context = null; } - #end - @:noCompletion - private static function setupConfig():Void + /** + Pauses the current `AudioManager` context. + + This function does not work on the Flash target. + **/ + public static function suspend():Void { - #if (lime_openal && (windows || mac || linux || android || ios)) - final alConfig:Array = []; + #if (lime_openal && !lime_doc_gen) + if (context != null && context.type == OPENAL) + { + var currentContext = ALC.getCurrentContext(); + if (currentContext != null) + { + ALC.suspendContext(currentContext); - alConfig.push('[general]'); - alConfig.push('channels=stereo'); - alConfig.push('sample-type=float32'); - alConfig.push('stereo-mode=speakers'); - alConfig.push('stereo-encoding=panpot'); - alConfig.push('hrtf=false'); - alConfig.push('cf_level=0'); - alConfig.push('resampler=fast_bsinc24'); - alConfig.push('front-stablizer=false'); - alConfig.push('output-limiter=false'); - alConfig.push('volume-adjust=0'); - alConfig.push('period_size=441'); + var device = ALC.getContextsDevice(currentContext); + if (device != null) ALC.pauseDevice(device); + } + } + #elseif (js && html5) + if (context != null && context.type == WEB) + { + context.web.suspend(); + } + #end + } - alConfig.push('[decoder]'); - alConfig.push('hq-mode=false'); - alConfig.push('distance-comp=false'); - alConfig.push('nfc=false'); + @:noCompletion private static inline function get_muted():Bool + { + return __muted; + } - try + @:noCompletion private static inline function set_muted(value:Bool):Bool + { + __muted = value; + if (context == null) return __muted; + + #if !lime_doc_gen + #if lime_openal + if (context.type == OPENAL) AL.listenerf(AL.GAIN, value ? 0 : __gain); + #elseif (js && html5) + #if lime_howlerjs + if (context.type == HTML5 || context.type == WEB) Howler.mute(value); + #end + #elseif flash + if (context.type == FLASH) { - final directory:String = Path.directory(Path.withoutExtension(System.applicationStorageDirectory)); - final path:String = Path.join([directory, #if windows 'audio-config.ini' #else 'audio-config.conf' #end]); - final content:String = alConfig.join('\n'); + if (__flashSoundTransform == null) __flashSoundTransform = new SoundTransform(); + __flashSoundTransform.gain = value ? 0 : __gain; + SoundMixer.soundTransform = __flashSoundTransform; + } + #end + #end + return value; + } - if (!FileSystem.exists(directory)) FileSystem.createDirectory(directory); + @:noCompletion private static inline function get_gain():Float + { + return __gain; + } - if (!FileSystem.exists(path)) File.saveContent(path, content); + @:noCompletion private static inline function set_gain(value:Float):Float + { + __gain = value; + if (context == null) return __gain; - Sys.putEnv('ALSOFT_CONF', path); + #if !lime_doc_gen + #if lime_openal + if (context.type == OPENAL) AL.listenerf(AL.GAIN, __muted ? 0 : value); + #elseif (js && html5) + #if lime_howlerjs + if (context.type == HTML5 || context.type == WEB) Howler.volume(value); + #end + #elseif flash + if (context.type == FLASH) + { + if (__flashSoundTransform == null) __flashSoundTransform = new SoundTransform(); + __flashSoundTransform.volume = __muted ? 0 : value; + SoundMixer.soundTransform = __flashSoundTransform; } - catch (e:Dynamic) {} #end + #end + return value; + } + + #if (lime_openal && !lime_doc_gen) + // device is null... and its actually intended cuz its tied to the device its being used rn. + // why + // and in ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT, deviceName is the device GUID + #if (lime_openalsoft && !mobile) + @:noCompletion private static function __deviceEventCallback(eventType:Int, deviceType:Int, handle:CFFIPointer, + #if hl _message:hl.Bytes #else message:String #end) + { + #if hl var message:String = CFFI.stringValue(_message); #end + var device:ALDevice = handle != null ? new ALDevice(handle) : null; + var deviceName = __getDeviceNameFromMessage(message); + + var currentContext = ALC.getCurrentContext(); + var currentDevice = currentContext != null ? ALC.getContextsDevice(currentContext) : null; + if (deviceType == ALC.PLAYBACK_DEVICE_SOFT) { + switch (eventType) { + case ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: + if (automaticDefaultPlaybackDevice) refresh(); + onDefaultPlaybackDeviceChanged.dispatch(getPlaybackDefaultDeviceName()); + case ALC.EVENT_TYPE_DEVICE_ADDED_SOFT: + onPlaybackDeviceAdded.dispatch(__formatDeviceName(deviceName)); + case ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT: + if (__disconnectExtSupported && ALC.getIntegerv(currentDevice, ALC.CONNECTED, 1)[0] != 1) refresh(); + onPlaybackDeviceRemoved.dispatch(__formatDeviceName(deviceName)); + } + } + else if (deviceType == ALC.CAPTURE_DEVICE_SOFT) { + switch (eventType) { + case ALC.EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: + onDefaultCaptureDeviceChanged.dispatch(getCaptureDefaultDeviceName()); + case ALC.EVENT_TYPE_DEVICE_ADDED_SOFT: + onCaptureDeviceAdded.dispatch(__formatDeviceName(deviceName)); + case ALC.EVENT_TYPE_DEVICE_REMOVED_SOFT: + onCaptureDeviceRemoved.dispatch(__formatDeviceName(deviceName)); + } + } + } + #end + + @:noCompletion private static function __refresh():Void + { + if (context == null || context.type != OPENAL) return; + + var currentContext = ALC.getCurrentContext(); + if (currentContext == null) return; + + var device = ALC.getContextsDevice(currentContext); + if (device == null) return; + + __effectExtSupported = ALC.isExtensionPresent(null, "ALC_EXT_EFX"); + __captureExtSupported = ALC.isExtensionPresent(null, 'ALC_EXT_CAPTURE'); + __disconnectExtSupported = ALC.isExtensionPresent(null, 'ALC_EXT_disconnect'); + __deviceClockSupported = ALC.isExtensionPresent(null, 'ALC_SOFT_device_clock'); + __reopenDeviceSupported = ALC.isExtensionPresent(null, 'ALC_SOFT_reopen_device'); + __systemEventsSupported = ALC.isExtensionPresent(null, 'ALC_SOFT_system_events'); + __enumerateAllSupported = ALC.isExtensionPresent(null, 'ALC_ENUMERATE_ALL_EXT'); + + __latencyExtSupported = AL.isExtensionPresent('AL_SOFT_source_latency'); + __directChannelsExtSupported = AL.isExtensionPresent('AL_SOFT_direct_channels') && AL.isExtensionPresent('AL_SOFT_direct_channels_remix'); + __loopPointsSupported = AL.isExtensionPresent('AL_SOFT_loop_points'); + __moreFormatsSupported = AL.isExtensionPresent('AL_EXT_MCFORMATS'); + __spatializeSupported = AL.isExtensionPresent('AL_SOFT_source_spatialize'); + __stereoAnglesSupported = AL.isExtensionPresent('AL_EXT_STEREO_ANGLES'); + + gain = __gain; + AL.distanceModel(AL.NONE); + } + + @:noCompletion private static function __formatDeviceName(deviceName:String) + { + if (StringTools.startsWith(deviceName, 'OpenAL Soft on ')) return deviceName.substr(15); + else if (StringTools.startsWith(deviceName, 'OpenAL on ')) return deviceName.substr(10); + else if (StringTools.startsWith(deviceName, 'Generic Software on ')) return deviceName.substr(20); + else return deviceName; } + + // permanent band-aid fix whatever + @:noCompletion private static function __getDeviceNameFromMessage(message:String):Null + { + if (StringTools.startsWith(message, 'Device removed: ')) return message.substr(16); + else if (StringTools.startsWith(message, 'Device added: ')) return message.substr(14); + else return null; + } + #end } diff --git a/src/lime/media/AudioSource.hx b/src/lime/media/AudioSource.hx index 6c56dd1dad..cfb979d394 100644 --- a/src/lime/media/AudioSource.hx +++ b/src/lime/media/AudioSource.hx @@ -4,6 +4,9 @@ import lime.app.Event; import lime.media.openal.AL; import lime.media.openal.ALSource; import lime.math.Vector4; +import lime.utils.ArrayBufferView; +import lime.utils.Float32Array; +import lime.utils.UInt8Array; #if !lime_debug @:fileXml('tags="haxe,release"') @@ -18,10 +21,11 @@ import lime.math.Vector4; @see lime.media.AudioBuffer **/ +@:allow(lime.media.AudioFilter) class AudioSource { /** - An event that is dispatched when the audio playback is complete. + An event that is dispatched when this audio playback have completed or looped. **/ public var onComplete = new EventVoid>(); @@ -33,44 +37,73 @@ class AudioSource /** The current playback position of the audio, in milliseconds. **/ - public var currentTime(get, set):Int; + public var currentTime(get, set):Float; /** The gain (volume) of the audio. A value of `1.0` represents the default volume. + Property is in a linear scale. **/ public var gain(get, set):Float; + /** + The estimated output latency, in miliseconds, for this `AudioSource`. If not possible to retrieve will return `0`. + **/ + public var latency(get, never):Float; + /** The length of the audio, in milliseconds. + Setting this to 0 will set back to the original length. **/ - public var length(get, set):Int; + public var length(get, set):Float; + + /** + In which audio playback time the audio will loop. + **/ + public var loopTime(get, set):Float; /** The number of times the audio will loop. A value of `0` means the audio will not loop. **/ public var loops(get, set):Int; + /** + The offset within the audio buffer to start playback, in milliseconds. + Modifying this won't affect currentTime, but internally it affects the offset. + + NOTE: The original documentation said it is in samples, but its actually in milliseconds. + **/ + public var offset(default, set):Float; + + /** + The stereo pan of the audio source. + Setting this will set the position back to default. + **/ + public var pan(get, set):Float; + + /** + The current peak or amplitude (signal level) of the channels seperated to elements in array, + from 0 (silent) to 1 (full). + **/ + public var peaks(get, never):Array; + /** The pitch of the audio. A value of `1.0` represents the default pitch. **/ public var pitch(get, set):Float; /** - The offset within the audio buffer to start playback, in samples. + An property if this 'AudioSource' is playing. **/ - public var offset:Int; + public var playing(get, never):Bool; /** The 3D position of the audio source, represented as a `Vector4`. + Setting this will set the pan back to default. **/ public var position(get, set):Vector4; - /** - The estimated output latency, in miliseconds, for this `AudioSource`. If not possible to retrieve will return `0`. - **/ - public var latency(get, never):Float; - @:noCompletion private var __backend:AudioSourceBackend; + @:noCompletion private var __effects:Array; /** Creates a new `AudioSource` instance. @@ -79,45 +112,180 @@ class AudioSource @param length The length of the audio to play, in milliseconds. If `null`, the full buffer is used. @param loops The number of times to loop the audio. `0` means no looping. **/ - public function new(buffer:AudioBuffer = null, offset:Int = 0, length:Null = null, loops:Int = 0) + public function new(buffer:AudioBuffer = null, offset:Float = 0, length:Null = null, loops:Int = 0) { + __backend = new AudioSourceBackend(this); + this.buffer = buffer; this.offset = offset; + if (length != null && length != 0) this.length = length; + this.loops = loops; - __backend = new AudioSourceBackend(this); + if (buffer != null) __backend.load(); + } + + /** + Releases any resources used by this `AudioSource`. + **/ + public function dispose():Void + { + __backend.stop(); + __backend.unload(); + __backend.dispose(); + } - if (length != null && length != 0) + /** + Adds an audio effect to this `AudioSource`. + Maximum effects is 6 per source. + + @param effect An `AudioEffect`. + @return Indicates if it's able to append the effect or not. + **/ + public function addEffect(effect:AudioEffect):Bool + { + if (__effects == null) __effects = []; + + var index = __effects.indexOf(effect); + if (index == -1) { - this.length = length; + if (__effects.length > 6) return false; + + index = __effects.indexOf(null); + if (index == -1) + { + index = __effects.length; + __effects.push(effect); + } + else + { + __effects[index] = effect; + } + + effect.__appliedSources.push(this); + if (!effect.bypass) __backend.addEffect(index); + } + else if (!effect.bypass) + { + __backend.updateEffect(index); } - this.loops = loops; + return true; + } - if (buffer != null) + /** + Removes an audio effect from this `AudioSource`. + + @param effect An `AudioEffect`. + **/ + public function removeEffect(effect:AudioEffect):Void + { + if (__effects != null) { - init(); + var index = __effects.indexOf(effect); + if (index != -1) + { + if (!effect.bypass) __backend.removeEffect(index); + __effects[index] = null; + //while (__effects[__effects.length - 1] == null) __effects.pop(); + + effect.__appliedSources.remove(this); + if (effect.autoDispose) effect.dispose(); + } } } /** - Releases any resources used by this `AudioSource`. + Clears any existing audio effects added in this `AudioSource`. **/ - public function dispose():Void + public function clearEffects():Void { - __backend.dispose(); + if (__effects != null) + { + var index = __effects.length, effect:AudioEffect; + while (index-- > 0) + { + effect = __effects[index]; + if (effect == null) continue; + + if (!effect.bypass) __backend.removeEffect(index); + if (effect.autoDispose) effect.dispose(); + } + + __effects = null; + } } - @:noCompletion private function init():Void + /** + Returns the audio effect stored at the specified index. + + @param index An index to the `AudioEffect`. + @return The specified `AudioEffect` from the index. + **/ + public function getEffectAt(index:Int):AudioEffect { - __backend.init(); + return __effects[index]; } /** - Starts or resumes audio playback. + Returns the index of an effect stored. + + @param effect An `AudioEffect`. + @return The index stored for the desired audio effect in this `AudioSource`. **/ - public function play():Void + public function getEffectIndex(effect:AudioEffect):Int { - __backend.play(); + return __effects.indexOf(effect); + } + + /** + Gets the current waveform or time-domain data of values range from -1 to 1. + `size` doesn't have to be power of 2, but still recommended to do. + If `channel` are not passed or is -1, it will get down-mixed mono result. + + @param array A `Float32Array` to fill the signals with. + @param size How much signals to copy to the array. + @param channel The channel to copy from. If it's `-1` then it down-mixes stereo signals to mono. + @param offset What offset in pcm frame should it start copying singals (In web this has no use). + @return The number of pcm frames filled into the array. + **/ + public function getFloatTimeDomainData(array:Float32Array, size:Int, channel:Int = -1, offset:Int = 0):Int + { + return __backend.getFloatTimeDomainData(array, size, channel, offset); + } + + /** + Gets the current waveform or time-domain data of values range from 0 to 127 and 128 to 256. + The same purpose as `getTimeDomainData` but it's in UInt8 instead of Float32. + Use this if you prefer performance but don't care about accuracy. + + @param array A `UInt8Array` to fill the signals with. + @param size How much signals to copy to the array. + @param channel The channel to copy from. If it's `-1` then it down-mixes stereo signals to mono. + @param offset What offset in pcm frame should it start copying singals (In web this has no use). + @return The number of pcm frames filled into the array. + **/ + public function getByteTimeDomainData(array:UInt8Array, size:Int, channel:Int = -1, offset:Int = 0):Int + { + return __backend.getByteTimeDomainData(array, size, channel, offset); + } + + /** + Loads the buffer to this 'AudioSource'. + **/ + public function load():Void + { + __backend.stop(); + __backend.unload(); + __backend.load(); + } + + /** + Unloads the current loaded buffer from this 'AudioSource'. + **/ + public function unload():Void + { + __backend.stop(); + __backend.unload(); } /** @@ -128,6 +296,22 @@ class AudioSource __backend.pause(); } + /** + Starts or resumes audio playback. + **/ + public function play():Void + { + __backend.play(); + } + + /** + Prepare an audio playback on 'play()' to avoid stutters. + **/ + public function prepare(time:Float):Void + { + __backend.prepare(time); + } + /** Stops audio playback and resets the playback position to the beginning. **/ @@ -136,75 +320,145 @@ class AudioSource __backend.stop(); } + /** + Pauses a list of audis at the same time. + **/ + public static function pauseSources(sources:Array):Void + { + AudioSourceBackend.pauseSources(sources); + } + + /** + Plays a list of audios at the same time. + **/ + public static function playSources(sources:Array):Void + { + AudioSourceBackend.playSources(sources); + } + + /** + Stops a list of audios at the same time. + **/ + public static function stopSources(sources:Array):Void + { + AudioSourceBackend.stopSources(sources); + } + + @:noCompletion private inline function init():Void + { + __backend.load(); + } + // Get & Set Methods - @:noCompletion private function get_currentTime():Int + @:noCompletion private inline function get_currentTime():Float { return __backend.getCurrentTime(); } - @:noCompletion private function set_currentTime(value:Int):Int + @:noCompletion private inline function set_currentTime(value:Float):Float { return __backend.setCurrentTime(value); } - @:noCompletion private function get_gain():Float + @:noCompletion private inline function get_gain():Float { return __backend.getGain(); } - @:noCompletion private function set_gain(value:Float):Float + @:noCompletion private inline function set_gain(value:Float):Float { return __backend.setGain(value); } - @:noCompletion private function get_length():Int + @:noCompletion private inline function get_latency():Float + { + return __backend.getLatency(); + } + + @:noCompletion private inline function get_length():Float { return __backend.getLength(); } - @:noCompletion private function set_length(value:Int):Int + @:noCompletion private inline function set_length(value:Float):Float { return __backend.setLength(value); } - @:noCompletion private function get_loops():Int + @:noCompletion private inline function get_loopTime():Float + { + return __backend.getLoopTime(); + } + + @:noCompletion private inline function set_loopTime(value:Float):Float + { + return __backend.setLoopTime(value); + } + + @:noCompletion private inline function get_loops():Int { return __backend.getLoops(); } - @:noCompletion private function set_loops(value:Int):Int + @:noCompletion private inline function set_loops(value:Int):Int { return __backend.setLoops(value); } - @:noCompletion private function get_pitch():Float + @:noCompletion private inline function set_offset(value:Float):Float + { + if (offset != value) + { + offset = 0; + __backend.setCurrentTime(__backend.getCurrentTime() + value); + offset = value; + } + return value; + } + + @:noCompletion private inline function get_pan():Float + { + return __backend.getPan(); + } + + @:noCompletion private inline function set_pan(value:Float):Float + { + return __backend.setPan(value); + } + + @:noCompletion private inline function get_peaks():Array + { + return __backend.getPeaks(0); + } + + @:noCompletion private inline function get_pitch():Float { return __backend.getPitch(); } - @:noCompletion private function set_pitch(value:Float):Float + @:noCompletion private inline function set_pitch(value:Float):Float { return __backend.setPitch(value); } - @:noCompletion private function get_position():Vector4 + @:noCompletion private inline function get_playing():Bool { - return __backend.getPosition(); + return __backend.getPlaying(); } - @:noCompletion private function set_position(value:Vector4):Vector4 + @:noCompletion private inline function get_position():Vector4 { - return __backend.setPosition(value); + return __backend.getPosition(); } - @:noCompletion private function get_latency():Float + @:noCompletion private inline function set_position(value:Vector4):Vector4 { - return __backend.getLatency(); + return __backend.setPosition(value); } } -#if (js && html5) -@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.html5.HTML5AudioSource; +#if lime_openal +@:noCompletion typedef AudioSourceBackend = lime._internal.backend.native.NativeAudioSource; #else -@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeAudioSource; -#end +@:noCompletion typedef AudioSourceBackend = lime._internal.backend.html5.HTML5AudioSource; +#end \ No newline at end of file diff --git a/src/lime/media/HTML5AudioContext.hx b/src/lime/media/HTML5AudioContext.hx deleted file mode 100644 index b6afba502a..0000000000 --- a/src/lime/media/HTML5AudioContext.hx +++ /dev/null @@ -1,419 +0,0 @@ -package lime.media; - -#if (!lime_doc_gen || (js && html5)) -#if (js && html5) -import js.html.Audio; -#end - -@:access(lime.media.AudioBuffer) -class HTML5AudioContext -{ - public var HAVE_CURRENT_DATA:Int = 2; - public var HAVE_ENOUGH_DATA:Int = 4; - public var HAVE_FUTURE_DATA:Int = 3; - public var HAVE_METADATA:Int = 1; - public var HAVE_NOTHING:Int = 0; - public var NETWORK_EMPTY:Int = 0; - public var NETWORK_IDLE:Int = 1; - public var NETWORK_LOADING:Int = 2; - public var NETWORK_NO_SOURCE:Int = 3; - - @:noCompletion private function new() {} - - public function canPlayType(buffer:AudioBuffer, type:String):String - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.canPlayType(type); - } - #end - - return null; - } - - public function createBuffer(urlString:String = null):AudioBuffer - { - #if (js && html5) - var buffer = new AudioBuffer(); - buffer.__srcAudio = new Audio(); - buffer.__srcAudio.src = urlString; - return buffer; - #else - return null; - #end - } - - public function getAutoplay(buffer:AudioBuffer):Bool - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.autoplay; - } - #end - - return false; - } - - public function getBuffered(buffer:AudioBuffer):Dynamic /*TimeRanges*/ - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.buffered; - } - #end - - return null; - } - - public function getCurrentSrc(buffer:AudioBuffer):String - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.currentSrc; - } - #end - - return null; - } - - public function getCurrentTime(buffer:AudioBuffer):Float - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.currentTime; - } - #end - - return 0; - } - - public function getDefaultPlaybackRate(buffer:AudioBuffer):Float - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.defaultPlaybackRate; - } - #end - - return 1; - } - - public function getDuration(buffer:AudioBuffer):Float - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.duration; - } - #end - - return 0; - } - - public function getEnded(buffer:AudioBuffer):Bool - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.ended; - } - #end - - return false; - } - - public function getError(buffer:AudioBuffer):Dynamic /*MediaError*/ - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.error; - } - #end - - return null; - } - - public function getLoop(buffer:AudioBuffer):Bool - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.loop; - } - #end - - return false; - } - - public function getMuted(buffer:AudioBuffer):Bool - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.muted; - } - #end - - return false; - } - - public function getNetworkState(buffer:AudioBuffer):Int - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.networkState; - } - #end - - return 0; - } - - public function getPaused(buffer:AudioBuffer):Bool - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.paused; - } - #end - - return false; - } - - public function getPlaybackRate(buffer:AudioBuffer):Float - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.playbackRate; - } - #end - - return 1; - } - - public function getPlayed(buffer:AudioBuffer):Dynamic /*TimeRanges*/ - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.played; - } - #end - - return null; - } - - public function getPreload(buffer:AudioBuffer):String - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.preload; - } - #end - - return null; - } - - public function getReadyState(buffer:AudioBuffer):Int - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.readyState; - } - #end - - return 0; - } - - public function getSeekable(buffer:AudioBuffer):Dynamic /*TimeRanges*/ - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.seekable; - } - #end - - return null; - } - - public function getSeeking(buffer:AudioBuffer):Bool - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.seeking; - } - #end - - return false; - } - - public function getSrc(buffer:AudioBuffer):String - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.src; - } - #end - - return null; - } - - public function getStartTime(buffer:AudioBuffer):Float - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.playbackRate; - } - #end - - return 0; - } - - public function getVolume(buffer:AudioBuffer):Float - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - return buffer.__srcAudio.volume; - } - #end - - return 1; - } - - public function load(buffer:AudioBuffer):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.load(); - } - #end - } - - public function pause(buffer:AudioBuffer):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.pause(); - } - #end - } - - public function play(buffer:AudioBuffer):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.play(); - } - #end - } - - public function setAutoplay(buffer:AudioBuffer, value:Bool):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.autoplay = value; - } - #end - } - - public function setCurrentTime(buffer:AudioBuffer, value:Float):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.currentTime = value; - } - #end - } - - public function setDefaultPlaybackRate(buffer:AudioBuffer, value:Float):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.defaultPlaybackRate = value; - } - #end - } - - public function setLoop(buffer:AudioBuffer, value:Bool):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.loop = value; - } - #end - } - - public function setMuted(buffer:AudioBuffer, value:Bool):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.muted = value; - } - #end - } - - public function setPlaybackRate(buffer:AudioBuffer, value:Float):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.playbackRate = value; - } - #end - } - - public function setPreload(buffer:AudioBuffer, value:String):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.preload = value; - } - #end - } - - public function setSrc(buffer:AudioBuffer, value:String):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.src = value; - } - #end - } - - public function setVolume(buffer:AudioBuffer, value:Float):Void - { - #if (js && html5) - if (buffer.__srcAudio != null) - { - buffer.__srcAudio.volume = value; - } - #end - } -} -#end diff --git a/src/lime/media/WebAudioContext.hx b/src/lime/media/WebAudioContext.hx index 89dac24a39..d0215203fa 100644 --- a/src/lime/media/WebAudioContext.hx +++ b/src/lime/media/WebAudioContext.hx @@ -20,6 +20,16 @@ class WebAudioContext } #end + public function suspend():Dynamic /*Promise*/ + { + return null; + } + + public function close():Dynamic /*Promise*/ + { + return null; + } + public function createAnalyser():Dynamic /*AnalyserNode*/ { return null; diff --git a/src/lime/media/decoders/FLACDecoder.hx b/src/lime/media/decoders/FLACDecoder.hx new file mode 100644 index 0000000000..fe72fa2065 --- /dev/null +++ b/src/lime/media/decoders/FLACDecoder.hx @@ -0,0 +1,155 @@ +package lime.media.decoders; + +#if (!lime_doc_gen || lime_drlibs) +import haxe.Int64; +import haxe.io.Bytes; +import lime.utils.ArrayBuffer; +import lime.media.AudioDecoder; + +#if (lime_cffi && lime_drlibs) +import lime._internal.backend.native.NativeCFFI; + +@:access(lime._internal.backend.native.NativeCFFI) +#end +#if hl +@:keep +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + +**/ +class FLACDecoder extends AudioDecoder +{ + public static function fromBytes(bytes:Bytes):FLACDecoder + { + #if (lime_cffi && lime_drlibs) + var handle = NativeCFFI.lime_drlibs_flac_from_bytes(bytes); + if (handle == null) return null; + + var decoder = new FLACDecoder(handle); + decoder.__bytes = bytes; + return decoder; + #else + return null; + #end + } + + public static function fromFile(path:String):FLACDecoder + { + #if (lime_cffi && lime_drlibs) + var handle = NativeCFFI.lime_drlibs_flac_from_file(path); + if (handle == null) return null; + + var decoder = new FLACDecoder(handle); + decoder.__path = path; + return decoder; + #else + return null; + #end + } + + @:noCompletion private var handle:Dynamic; + + @:noCompletion private function new(handle:Dynamic) + { + super(); + this.handle = handle; + + #if (lime_cffi && lime_drlibs) + if (handle != null) + { + var data = NativeCFFI.lime_drlibs_flac_info(handle); + bitsPerSample = data.bitsPerSample; + channels = data.channels; + sampleRate = data.sampleRate; + } + #end + } + + override function dispose():Void + { + super.dispose(); + #if (lime_cffi && lime_drlibs) + if (handle != null) NativeCFFI.lime_drlibs_flac_close(handle); + #end + handle = null; + } + + override function clone():FLACDecoder + { + #if (lime_cffi && lime_drlibs) + if (__path != null) return FLACDecoder.fromFile(__path); + else if (__bytes != null) return FLACDecoder.fromBytes(__bytes); + #end + return null; + } + + override function decode(buffer:ArrayBuffer, pos:Int, len:Int, word:Int):Int + { + #if (lime_cffi && lime_drlibs) + pos = NativeCFFI.lime_drlibs_flac_decode(handle, buffer, pos, len, word); + eof = pos < len; + return pos; + #else + return 0; + #end + } + + override function seek(samples:Int64):Bool + { + #if (lime_cffi && lime_drlibs) + if (NativeCFFI.lime_drlibs_flac_seek(handle, samples.low, samples.high) == 1) + { + eof = false; + return true; + } + #end + return false; + } + + override function rewind():Bool + { + #if (lime_cffi && lime_drlibs) + if (NativeCFFI.lime_drlibs_flac_seek(handle, 0, 0) == 1) + { + eof = false; + return true; + } + #end + return false; + } + + override function tell():Int64 + { + #if (lime_cffi && lime_drlibs) + var data = NativeCFFI.lime_drlibs_flac_tell(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function total():Int64 + { + #if (lime_cffi && lime_drlibs) + var data = NativeCFFI.lime_drlibs_flac_total(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function seekable():Bool + { + #if (lime_cffi && lime_drlibs) + return true; + #else + return false; + #end + } +} +#end \ No newline at end of file diff --git a/src/lime/media/decoders/MP3Decoder.hx b/src/lime/media/decoders/MP3Decoder.hx new file mode 100644 index 0000000000..ab40a56dae --- /dev/null +++ b/src/lime/media/decoders/MP3Decoder.hx @@ -0,0 +1,156 @@ +package lime.media.decoders; + +#if (!lime_doc_gen || lime_drlibs) +import haxe.Int64; +import haxe.io.Bytes; +import lime.utils.ArrayBuffer; +import lime.media.AudioDecoder; + +#if (lime_cffi && lime_drlibs) +import lime._internal.backend.native.NativeCFFI; + +@:access(lime._internal.backend.native.NativeCFFI) +#end +#if hl +@:keep +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + +**/ +class MP3Decoder extends AudioDecoder +{ + public static function fromBytes(bytes:Bytes):MP3Decoder + { + #if (lime_cffi && lime_drlibs) + var handle = NativeCFFI.lime_drlibs_mp3_from_bytes(bytes); + if (handle == null) return null; + + var decoder = new MP3Decoder(handle); + decoder.__bytes = bytes; + return decoder; + #else + return null; + #end + } + + public static function fromFile(path:String):MP3Decoder + { + #if (lime_cffi && lime_drlibs) + var handle = NativeCFFI.lime_drlibs_mp3_from_file(path); + if (handle == null) return null; + + var decoder = new MP3Decoder(handle); + decoder.__path = path; + return decoder; + #else + return null; + #end + } + + @:noCompletion private var handle:Dynamic; + + @:noCompletion private function new(handle:Dynamic) + { + super(); + this.handle = handle; + + #if (lime_cffi && lime_drlibs) + if (handle != null) + { + var data = NativeCFFI.lime_drlibs_mp3_info(handle); + bitsPerSample = 16; + channels = data.channels; + sampleRate = data.sampleRate; + } + #end + } + + override function dispose():Void + { + super.dispose(); + #if (lime_cffi && lime_drlibs) + if (handle != null) NativeCFFI.lime_drlibs_mp3_uninit(handle); + #end + handle = null; + } + + override function clone():MP3Decoder + { + #if (lime_cffi && lime_drlibs) + if (__path != null) return MP3Decoder.fromFile(__path); + else if (__bytes != null) return MP3Decoder.fromBytes(__bytes); + #end + return null; + } + + override function decode(buffer:ArrayBuffer, pos:Int, len:Int, word:Int):Int + { + #if (lime_cffi && lime_drlibs) + // Can only read as 16 bits per sample internally + pos = NativeCFFI.lime_drlibs_mp3_decode(handle, buffer, pos, len); + eof = pos < len; + return pos; + #else + return 0; + #end + } + + override function seek(samples:Int64):Bool + { + #if (lime_cffi && lime_drlibs) + if (NativeCFFI.lime_drlibs_mp3_seek(handle, samples.low, samples.high) == 1) + { + eof = false; + return true; + } + #end + return false; + } + + override function rewind():Bool + { + #if (lime_cffi && lime_drlibs) + if (NativeCFFI.lime_drlibs_mp3_seek(handle, 0, 0) == 1) + { + eof = false; + return true; + } + #end + return false; + } + + override function tell():Int64 + { + #if (lime_cffi && lime_drlibs) + var data = NativeCFFI.lime_drlibs_mp3_tell(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function total():Int64 + { + #if (lime_cffi && lime_drlibs) + var data = NativeCFFI.lime_drlibs_mp3_total(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function seekable():Bool + { + #if (lime_cffi && lime_drlibs) + return true; + #else + return false; + #end + } +} +#end \ No newline at end of file diff --git a/src/lime/media/decoders/OpusDecoder.hx b/src/lime/media/decoders/OpusDecoder.hx new file mode 100644 index 0000000000..10a1f261e8 --- /dev/null +++ b/src/lime/media/decoders/OpusDecoder.hx @@ -0,0 +1,155 @@ +package lime.media.decoders; + +#if (!lime_doc_gen || lime_opus) +import haxe.Int64; +import haxe.io.Bytes; +import lime.utils.ArrayBuffer; +import lime.media.AudioDecoder; + +#if (lime_cffi && lime_opus) +import lime._internal.backend.native.NativeCFFI; + +@:access(lime._internal.backend.native.NativeCFFI) +#end +#if hl +@:keep +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + +**/ +class OpusDecoder extends AudioDecoder +{ + public static function fromBytes(bytes:Bytes):OpusDecoder + { + #if (lime_cffi && lime_opus) + var handle = NativeCFFI.lime_opus_file_from_bytes(bytes); + if (handle == null) return null; + + var decoder = new OpusDecoder(handle); + decoder.__bytes = bytes; + return decoder; + #else + return null; + #end + } + + public static function fromFile(path:String):OpusDecoder + { + #if (lime_cffi && lime_opus) + var handle = NativeCFFI.lime_opus_file_from_file(path); + if (handle == null) return null; + + var decoder = new OpusDecoder(handle); + decoder.__path = path; + return decoder; + #else + return null; + #end + } + + @:noCompletion private var handle:Dynamic; + + @:noCompletion private function new(handle:Dynamic) + { + super(); + this.handle = handle; + + #if (lime_cffi && lime_opus) + if (handle != null) + { + bitsPerSample = 16; + channels = NativeCFFI.lime_opus_file_channel_count(handle); + sampleRate = 48000; + } + #end + } + + override function dispose():Void + { + super.dispose(); + #if (lime_cffi && lime_opus) + if (handle != null) NativeCFFI.lime_opus_file_free(handle); + #end + handle = null; + } + + override function clone():OpusDecoder + { + #if (lime_cffi && lime_opus) + if (__path != null) return OpusDecoder.fromFile(__path); + else if (__bytes != null) return OpusDecoder.fromBytes(__bytes); + #end + return null; + } + + override function decode(buffer:ArrayBuffer, pos:Int, len:Int, word:Int):Int + { + #if (lime_cffi && lime_opus) + // Can only read as 16 bits per sample internally in libopus + pos = NativeCFFI.lime_opus_file_decode(handle, buffer, pos, len); + eof = pos < len; + return pos; + #else + return 0; + #end + } + + override function seek(samples:Int64):Bool + { + #if (lime_cffi && lime_opus) + if (NativeCFFI.lime_opus_file_seek(handle, samples.low, samples.high) == 0) + { + eof = false; + return true; + } + #end + return false; + } + + override function rewind():Bool + { + #if (lime_cffi && lime_opus) + if (NativeCFFI.lime_opus_file_seek(handle, 0, 0) == 0) + { + eof = false; + return true; + } + #end + return false; + } + + override function tell():Int64 + { + #if (lime_cffi && lime_opus) + var data = NativeCFFI.lime_opus_file_tell(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function total():Int64 + { + #if (lime_cffi && lime_opus) + var data = NativeCFFI.lime_opus_file_total(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function seekable():Bool + { + #if (lime_cffi && lime_opus) + return NativeCFFI.lime_opus_file_seekable(handle); + #else + return false; + #end + } +} +#end \ No newline at end of file diff --git a/src/lime/media/decoders/VorbisDecoder.hx b/src/lime/media/decoders/VorbisDecoder.hx new file mode 100644 index 0000000000..3494c373c7 --- /dev/null +++ b/src/lime/media/decoders/VorbisDecoder.hx @@ -0,0 +1,173 @@ +package lime.media.decoders; + +#if (!lime_doc_gen || lime_vorbis) +import haxe.Int64; +import haxe.io.Bytes; +import lime.utils.ArrayBuffer; +import lime.media.AudioDecoder; + +#if (lime_cffi && lime_vorbis) +import lime._internal.backend.native.NativeCFFI; +import lime.media.vorbis.VorbisFile; + +@:access(lime._internal.backend.native.NativeCFFI) +@:access(lime.media.vorbis.VorbisFile) +#end +#if hl +@:keep +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + +**/ +class VorbisDecoder extends AudioDecoder +{ + public static function fromBytes(bytes:Bytes):VorbisDecoder + { + #if (lime_cffi && lime_vorbis) + var handle = NativeCFFI.lime_vorbis_file_from_bytes(bytes); + if (handle == null) return null; + + var decoder = new VorbisDecoder(handle); + decoder.__bytes = bytes; + return decoder; + #else + return null; + #end + } + + public static function fromFile(path:String):VorbisDecoder + { + #if (lime_cffi && lime_vorbis) + var handle = NativeCFFI.lime_vorbis_file_from_file(path); + if (handle == null) return null; + + var decoder = new VorbisDecoder(handle); + decoder.__path = path; + return decoder; + #else + return null; + #end + } + + #if (lime_cffi && lime_vorbis) + public static function fromVorbisFile(vorbisFile:VorbisFile) + { + var decoder = new VorbisDecoder(vorbisFile.handle); + return decoder; + } + #else + public static function fromVorbisFile(vorbisFile:Dynamic) + { + return null; + } + #end + + public var version:Int; + + @:noCompletion private var handle:Dynamic; + + @:noCompletion private function new(handle:Dynamic) + { + super(); + this.handle = handle; + + #if (lime_cffi && lime_vorbis) + if (handle != null) + { + var data = NativeCFFI.lime_vorbis_file_info(handle, -1); + bitsPerSample = 16; + channels = data.channels; + sampleRate = data.rate; + version = data.version; + } + #end + } + + override function dispose():Void + { + super.dispose(); + #if (lime_cffi && lime_vorbis) + if (handle != null) NativeCFFI.lime_vorbis_file_clear(handle); + #end + handle = null; + } + + override function clone():VorbisDecoder + { + #if (lime_cffi && lime_vorbis) + if (__path != null) return VorbisDecoder.fromFile(__path); + else if (__bytes != null) return VorbisDecoder.fromBytes(__bytes); + #end + return null; + } + + override function decode(buffer:ArrayBuffer, pos:Int, len:Int, word:Int):Int + { + #if (lime_cffi && lime_vorbis) + pos = NativeCFFI.lime_vorbis_file_decode(handle, buffer, pos, len, word); + eof = pos < len; + return pos; + #else + return 0; + #end + } + + override function seek(samples:Int64):Bool + { + #if (lime_cffi && lime_vorbis) + if (NativeCFFI.lime_vorbis_file_pcm_seek(handle, samples.low, samples.high) == 0) + { + eof = false; + return true; + } + #end + return false; + } + + override function rewind():Bool + { + #if (lime_cffi && lime_vorbis) + if (NativeCFFI.lime_vorbis_file_raw_seek(handle, 0, 0) == 0) + { + eof = false; + return true; + } + #end + return false; + } + + override function tell():Int64 + { + #if (lime_cffi && lime_vorbis) + var data = NativeCFFI.lime_vorbis_file_pcm_tell(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function total():Int64 + { + #if (lime_cffi && lime_vorbis) + var data = NativeCFFI.lime_vorbis_file_pcm_total(handle, -1); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function seekable():Bool + { + #if (lime_cffi && lime_vorbis) + return NativeCFFI.lime_vorbis_file_seekable(handle); + #else + return false; + #end + } +} +#end \ No newline at end of file diff --git a/src/lime/media/decoders/WaveDecoder.hx b/src/lime/media/decoders/WaveDecoder.hx new file mode 100644 index 0000000000..61117da343 --- /dev/null +++ b/src/lime/media/decoders/WaveDecoder.hx @@ -0,0 +1,155 @@ +package lime.media.decoders; + +#if (!lime_doc_gen || lime_drlibs) +import haxe.Int64; +import haxe.io.Bytes; +import lime.utils.ArrayBuffer; +import lime.media.AudioDecoder; + +#if (lime_cffi && lime_drlibs) +import lime._internal.backend.native.NativeCFFI; + +@:access(lime._internal.backend.native.NativeCFFI) +#end +#if hl +@:keep +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + +**/ +class WaveDecoder extends AudioDecoder +{ + public static function fromBytes(bytes:Bytes):WaveDecoder + { + #if (lime_cffi && lime_drlibs) + var handle = NativeCFFI.lime_drlibs_wav_from_bytes(bytes); + if (handle == null) return null; + + var decoder = new WaveDecoder(handle); + decoder.__bytes = bytes; + return decoder; + #else + return null; + #end + } + + public static function fromFile(path:String):WaveDecoder + { + #if (lime_cffi && lime_drlibs) + var handle = NativeCFFI.lime_drlibs_wav_from_file(path); + if (handle == null) return null; + + var decoder = new WaveDecoder(handle); + decoder.__path = path; + return decoder; + #else + return null; + #end + } + + @:noCompletion private var handle:Dynamic; + + @:noCompletion private function new(handle:Dynamic) + { + super(); + this.handle = handle; + + #if (lime_cffi && lime_drlibs) + if (handle != null) + { + var data = NativeCFFI.lime_drlibs_wav_info(handle); + bitsPerSample = data.bitsPerSample; + channels = data.channels; + sampleRate = data.sampleRate; + } + #end + } + + override function dispose():Void + { + super.dispose(); + #if (lime_cffi && lime_drlibs) + if (handle != null) NativeCFFI.lime_drlibs_wav_uninit(handle); + #end + handle = null; + } + + override function clone():WaveDecoder + { + #if (lime_cffi && lime_drlibs) + if (__path != null) return WaveDecoder.fromFile(__path); + else if (__bytes != null) return WaveDecoder.fromBytes(__bytes); + #end + return null; + } + + override function decode(buffer:ArrayBuffer, pos:Int, len:Int, word:Int):Int + { + #if (lime_cffi && lime_drlibs) + pos = NativeCFFI.lime_drlibs_wav_decode(handle, buffer, pos, len, word); + eof = pos < len; + return pos; + #else + return 0; + #end + } + + override function seek(samples:Int64):Bool + { + #if (lime_cffi && lime_drlibs) + if (NativeCFFI.lime_drlibs_wav_seek(handle, samples.low, samples.high) == 1) + { + eof = false; + return true; + } + #end + return false; + } + + override function rewind():Bool + { + #if (lime_cffi && lime_drlibs) + if (NativeCFFI.lime_drlibs_wav_seek(handle, 0, 0) == 1) + { + eof = false; + return true; + } + #end + return false; + } + + override function tell():Int64 + { + #if (lime_cffi && lime_drlibs) + var data = NativeCFFI.lime_drlibs_wav_tell(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function total():Int64 + { + #if (lime_cffi && lime_drlibs) + var data = NativeCFFI.lime_drlibs_wav_total(handle); + return Int64.make(data.high, data.low); + #else + return Int64.ofInt(0); + #end + } + + override function seekable():Bool + { + #if (lime_cffi && lime_drlibs) + return true; + #else + return false; + #end + } +} +#end \ No newline at end of file diff --git a/src/lime/media/effects/FilterEffect.hx b/src/lime/media/effects/FilterEffect.hx new file mode 100644 index 0000000000..c2c0255725 --- /dev/null +++ b/src/lime/media/effects/FilterEffect.hx @@ -0,0 +1,218 @@ +package lime.media.effects; + +import lime.media.AudioEffect; +#if lime_openal +import lime.media.openal.AL; +import lime.media.openal.ALFilter; +import lime.media.openal.ALSource; +#elseif (js && html5) +import js.html.audio.BiquadFilterNode; +import js.html.audio.BiquadFilterType; +import lime.media.howlerjs.Howler; +#end + +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +/** + An audio playback effect that can represent different kinds of filters, tone control devices, + and graphic equalizers, mainly LOWPASS, HIGHPASS, BANDPASS. + + NOTE: One audio source can only have one FilterEffect. + Only works in web, and native targets, flash doesn't support this. +**/ +class FilterEffect extends AudioEffect +{ + /** + The type of filter used on this biquad filter. + + `LOWPASS`: Standard second-order resonant lowpass filter with 12dB/octave rolloff. + Frequencies below the cutoff pass through; frequencies above it are attenuated. + + `HIGHPASS`: Standard second-order resonant highpass filter with 12dB/octave rolloff. + Frequencies below the cutoff are attenuated; frequencies above it pass through. + + `BANDPASS`: Standard second-order bandpass filter. Frequencies outside the given range of frequencies are attenuated; + the frequencies inside it pass through. + + **/ + public var type(default, set):FilterEffectType; + + /** + A variable representing a frequency in the current filtering algorithm measured, in hz; Ranging from 0 to 24000. + **/ + public var frequency(default, set):Float; + + #if (js && html5) + @:noCompletion private var __biquadFilter:BiquadFilterNode; + #end + + public function new(type:FilterEffectType = LOWPASS, frequency:Float = 1000) + { + super(); + + #if lime_openal + __alFilter = AL.createFilter(); + if (__alFilter != null && __alAux != null) + { + //AL.auxi(__alAux, AL.EFFECTSLOT_EFFECT, __alEffect = AL.createEffect()); + } + #elseif (js && html5) + __biquadFilter = new BiquadFilterNode(untyped Howler.ctx); + __audioNodes.push(__biquadFilter); + #end + + @:bypassAccessor this.type = type; + @:bypassAccessor this.frequency = frequency; + + __update(); + } + + /*#if lime_openal + @:noCompletion private inline function dbToGain(db:Float):Float + { + return Math.pow(10, db / 20); + } + + @:noCompletion private inline function correctCutoff(freq:Float):Float + { + return Math.min(freq * 1.25, 22000); + } + + @:noCompletion private inline function applyDetune(freq:Float, detuneCents:Float):Float + { + return freq * Math.pow(2, detuneCents / 1200); + } + + @:noCompletion private inline function normalizeFreq(freq:Float):Float + { + return Math.min(Math.max(freq / 22050, 0), 1); + } + + @:noCompletion private inline function qToResonance(q:Float):Float + { + return Math.min(Math.max(1 + (q - 0.7) * 0.6, 0.5), 3); + } + #end*/ + + @:noCompletion override function __update():Void + { + #if lime_openal + if (__alFilter == null) return; + + // An attempt to try approximate the filtering to match with web. + switch (type) + { + case LOWPASS: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_LOWPASS); + AL.filterf(__alFilter, AL.LOWPASS_GAIN, 1.0 - Math.exp(-64 * frequency / 4000.0)); + AL.filterf(__alFilter, AL.LOWPASS_GAINHF, frequency > 22000.0 ? 1.0 : 1.0 - Math.exp(-2.48 * Math.max(frequency - 200, 0) / 18000.0)); + + case HIGHPASS: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_HIGHPASS); + AL.filterf(__alFilter, AL.HIGHPASS_GAIN, Math.exp(-2.48 * (frequency + 2000.0) / 22000.0)); + AL.filterf(__alFilter, AL.HIGHPASS_GAINLF, Math.exp(-14 * frequency / 20000.0)); + + case BANDPASS: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_BANDPASS); + AL.filterf(__alFilter, AL.BANDPASS_GAIN, Math.pow(Math.sin(frequency / 7639.437268410977/*(24000.0 / Math.PI)*/), 0.01)); + AL.filterf(__alFilter, AL.BANDPASS_GAINHF, frequency / 24000.0); + AL.filterf(__alFilter, AL.BANDPASS_GAINLF, (24000.0 - frequency) / 24000.0); + + /* + case LOWPASS: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_LOWPASS); + AL.filterf(__alFilter, AL.LOWPASS_GAIN, 1); + AL.filterf(__alFilter, AL.LOWPASS_GAINHF, normalizeFreq(freq)); + AL.effecti(__alEffect, AL.EFFECT_TYPE, AL.EFFECT_EQUALIZER); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_GAIN, qToResonance(q)); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_CENTER, freq); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_WIDTH, q); + trace("lowpass", normalizeFreq(freq), qToResonance(q), freq, q); + + case HIGHPASS: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_HIGHPASS); + AL.filterf(__alFilter, AL.HIGHPASS_GAIN, 1); + AL.filterf(__alFilter, AL.HIGHPASS_GAINLF, normalizeFreq(freq)); + AL.effecti(__alEffect, AL.EFFECT_TYPE, AL.EFFECT_EQUALIZER); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_GAIN, qToResonance(q)); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_CENTER, freq); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_WIDTH, q); + trace("highpass", normalizeFreq(freq), qToResonance(q), freq, q); + + case BANDPASS: + AL.effecti(__alEffect, AL.EFFECT_TYPE, AL.EFFECT_NULL); + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_BANDPASS); + AL.filterf(__alFilter, AL.BANDPASS_GAIN, 1); + + var bw = freq / q; + AL.filterf(__alFilter, AL.BANDPASS_GAINLF, normalizeFreq(freq - bw * 0.5)); + AL.filterf(__alFilter, AL.BANDPASS_GAINHF, normalizeFreq(freq + bw * 0.5)); + trace("bandpass", freq, bw, normalizeFreq(freq - bw * 0.5), normalizeFreq(freq + bw * 0.5)); + + case LOWSHELF: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_NULL); + AL.effecti(__alEffect, AL.EFFECT_TYPE, AL.EFFECT_EQUALIZER); + AL.effectf(__alEffect, AL.EQUALIZER_LOW_GAIN, dbToGain(gain)); + AL.effectf(__alEffect, AL.EQUALIZER_LOW_CUTOFF, freq); + + case HIGHSHELF: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_NULL); + AL.effecti(__alEffect, AL.EFFECT_TYPE, AL.EFFECT_EQUALIZER); + AL.effectf(__alEffect, AL.EQUALIZER_HIGH_GAIN, dbToGain(gain)); + AL.effectf(__alEffect, AL.EQUALIZER_HIGH_CUTOFF, freq); + + case PEAKING: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_NULL); + AL.effecti(__alEffect, AL.EFFECT_TYPE, AL.EFFECT_EQUALIZER); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_GAIN, dbToGain(gain)); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_CENTER, freq); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_WIDTH, q); + + case NOTCH: + AL.filteri(__alFilter, AL.FILTER_TYPE, AL.FILTER_NULL); + AL.effecti(__alEffect, AL.EFFECT_TYPE, AL.EFFECT_EQUALIZER); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_GAIN, 0.2); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_CENTER, freq); + AL.effectf(__alEffect, AL.EQUALIZER_MID1_WIDTH, q); + */ + } + #elseif (js && html5) + __biquadFilter.type = cast type; + __biquadFilter.frequency.value = frequency; + #end + + super.__update(); + } + + @:noCompletion private inline function set_type(value:FilterEffectType):FilterEffectType + { + if (type == value) return value; + type = value; + + __update(); + return value; + } + + @:noCompletion private inline function set_frequency(value:Float):Float + { + if (frequency == value) return value; + frequency = Math.min(Math.max(value, 0), 24000); + + __update(); + return value; + } +} + +#if (haxe_ver >= 4.0) enum #else @:enum #end abstract FilterEffectType(String) from String to String +{ + var LOWPASS = "lowpass"; + var HIGHPASS = "highpass"; + var BANDPASS = "bandpass"; + //var LOWSHELF = "lowshelf"; + //var HIGHSHELF = "highshelf"; + //var PEAKING = "peaking"; + //var NOTCH = "notch"; + //var ALLPASS = "allpass"; +} diff --git a/src/lime/media/howlerjs/Howl.hx b/src/lime/media/howlerjs/Howl.hx index aa1235fb5d..a92e4f52f0 100644 --- a/src/lime/media/howlerjs/Howl.hx +++ b/src/lime/media/howlerjs/Howl.hx @@ -45,6 +45,7 @@ class Howl * loop(id) -> Returns the sound id's loop value. * loop(loop) -> Sets the loop value for all sounds in this Howl group. * loop(loop, id) -> Sets the loop value of passed sound id. + * loop(loopStart, loopEnd, id) -> Sets the loop time points of passed sound id. * @return Returns self or current loop value. */ public function loop(?loop:Dynamic, ?id:Int):Dynamic @@ -269,6 +270,7 @@ extern class Howl @:overload(function(id:Int):Bool {}) @:overload(function(loop:Bool):Howl {}) @:overload(function(loop:Bool, id:Int):Howl {}) + @:overload(function(loopStart:Float, loopEnd:Float, id:Int):Howl {}) public function loop():Bool; public function mute(muted:Bool, ?id:Int):Howl; public function off(event:String, fn:Function, ?id:Int):Howl; @@ -307,7 +309,7 @@ extern class Howl typedef HowlOptions = { - src:Array, + src:Array, ?volume:Float, ?html5:Bool, ?loop:Bool, diff --git a/src/lime/media/howlerjs/Howler.hx b/src/lime/media/howlerjs/Howler.hx index c3e636332b..d34eb361eb 100644 --- a/src/lime/media/howlerjs/Howler.hx +++ b/src/lime/media/howlerjs/Howler.hx @@ -1,6 +1,8 @@ package lime.media.howlerjs; #if (!lime_doc_gen || lime_howlerjs) +import lime.media.WebAudioContext; + #if (!lime_howlerjs || display) class Howler { @@ -53,7 +55,6 @@ class Howler #else import haxe.extern.EitherType; import js.html.audio.GainNode; -import lime.media.WebAudioContext; #if commonjs @:jsRequire("howler") @@ -72,6 +73,7 @@ extern class Howler public static function mute(muted:Bool):Howler; public static function unload():Howler; public static function volume(?vol:Float):EitherType; + public static function _setupAudioContext():Void; } #end #end diff --git a/src/lime/media/openal/AL.hx b/src/lime/media/openal/AL.hx index f9f84576a0..5332fc6e1c 100644 --- a/src/lime/media/openal/AL.hx +++ b/src/lime/media/openal/AL.hx @@ -203,7 +203,6 @@ class AL public static inline var EFFECT_VOCAL_MORPHER:Int = 0x0007; public static inline var EFFECT_PITCH_SHIFTER:Int = 0x0008; public static inline var EFFECT_RING_MODULATOR:Int = 0x0009; - public static inline var FFECT_AUTOWAH:Int = 0x000A; // TODO: deprecate and remove public static inline var EFFECT_AUTOWAH:Int = 0x000A; public static inline var EFFECT_COMPRESSOR:Int = 0x000B; public static inline var EFFECT_EQUALIZER:Int = 0x000C; @@ -233,15 +232,47 @@ class AL public static inline var FILTER_LOWPASS:Int = 0x0001; public static inline var FILTER_HIGHPASS:Int = 0x0002; public static inline var FILTER_BANDPASS:Int = 0x0003; + /* AL_EXT_float32 */ + public static inline var FORMAT_MONO_FLOAT32:Int = 0x10010; + public static inline var FORMAT_STEREO_FLOAT32:Int = 0x10011; + /* UNKNOWN */ + public static inline var FORMAT_MONO32:Int = 0x1202; + public static inline var FORMAT_STEREO32:Int = 0x1203; + /* AL_EXT_MCFORMATS */ + public static inline var FORMAT_QUAD8:Int = 0x1204; + public static inline var FORMAT_QUAD16:Int = 0x1205; + public static inline var FORMAT_QUAD32:Int = 0x1206; + public static inline var FORMAT_REAR8:Int = 0x1207; + public static inline var FORMAT_REAR16:Int = 0x1208; + public static inline var FORMAT_REAR32:Int = 0x1209; + public static inline var FORMAT_51CHN8:Int = 0x120A; + public static inline var FORMAT_51CHN16:Int = 0x120B; + public static inline var FORMAT_51CHN32:Int = 0x120C; + public static inline var FORMAT_61CHN8:Int = 0x120D; + public static inline var FORMAT_61CHN16:Int = 0x120E; + public static inline var FORMAT_61CHN32:Int = 0x120F; + public static inline var FORMAT_71CHN8:Int = 0x1210; + public static inline var FORMAT_71CHN16:Int = 0x1211; + public static inline var FORMAT_71CHN32:Int = 0x1212; #if lime_openalsoft /* AL_SOFT_direct_channels extension */ public static inline var DIRECT_CHANNELS_SOFT:Int = 0x1033; /* AL_SOFT_direct_channels_remix extension */ public static inline var DROP_UNMATCHED_SOFT:Int = 0x0001; public static inline var REMIX_UNMATCHED_SOFT:Int = 0x0002; - /* AL_SOFT_source_latency extension */ - public static inline var SAMPLE_OFFSET_LATENCY_SOFT = 0x1200; - public static inline var SEC_OFFSET_LATENCY_SOFT = 0x1201; + /* AL_SOFT_loop_points */ + public static inline var LOOP_POINTS_SOFT:Int = 0x2015; + /* AL_EXT_STEREO_ANGLES */ + public static inline var STEREO_ANGLES:Int = 0x1030; + /* AL_SOFT_source_latency */ + public static inline var SAMPLE_OFFSET_LATENCY_SOFT:Int = 0x1200; + public static inline var SEC_OFFSET_LATENCY_SOFT:Int = 0x1201; + /* AL_SOFT_source_spatialize */ + public static inline var SOURCE_SPATIALIZE_SOFT:Int = 0x1214; + public static inline var AUTO_SOFT:Int = 0x0002; + /* ALC_SOFT_device_clock */ + public static inline var SAMPLE_OFFSET_CLOCK_SOFT:Int = 0x1202; + public static inline var SEC_OFFSET_CLOCK_SOFT:Int = 0x1203; /* AL_SOFT_hold_on_disconnect */ public static inline var STOP_SOURCES_ON_DISCONNECT_SOFT:Int = 0x19AB; #end @@ -446,6 +477,25 @@ class AL #end } + public static function deleteEffect(effect:ALEffect):Void + { + #if (lime_cffi && lime_openal && !macro) + NativeCFFI.lime_al_delete_effect(effect); + #end + } + + public static function deleteFilter(filter:ALFilter):Void { + #if (lime_cffi && lime_openal && !macro) + NativeCFFI.lime_al_delete_filter(filter); + #end + } + + public static function deleteAux(aux:ALAuxiliaryEffectSlot):Void { + #if (lime_cffi && lime_openal && !macro) + NativeCFFI.lime_al_delete_auxiliary_effect_slot(aux); + #end + } + public static function disable(capability:Int):Void { #if (lime_cffi && lime_openal && !macro) @@ -737,16 +787,16 @@ class AL #end } - public static function getErrorString():String + public static function getErrorString(?error:Int):String { - return switch (getError()) + return switch (error != null ? error : getError()) { case INVALID_NAME: "INVALID_NAME: Invalid parameter name"; case INVALID_ENUM: "INVALID_ENUM: Invalid enum value"; case INVALID_VALUE: "INVALID_VALUE: Invalid parameter value"; case INVALID_OPERATION: "INVALID_OPERATION: Illegal operation or call"; case OUT_OF_MEMORY: "OUT_OF_MEMORY: OpenAL has run out of memory"; - default: ""; + default: "Unknown Error Enum: " + error; } } @@ -1160,7 +1210,7 @@ class AL #end } - public static function source3i(source:ALSource, param:Int, value1:Dynamic, value2:Int, value3:Int):Void + public static function source3i(source:ALSource, param:Int, value1:Dynamic, value2:Int, value3:Dynamic):Void { #if (lime_cffi && lime_openal && !macro) NativeCFFI.lime_al_source3i(source, param, value1, value2, value3); diff --git a/src/lime/media/openal/ALC.hx b/src/lime/media/openal/ALC.hx index e545d55532..c3e3918cc8 100644 --- a/src/lime/media/openal/ALC.hx +++ b/src/lime/media/openal/ALC.hx @@ -26,18 +26,28 @@ class ALC public static inline var INVALID_ENUM:Int = 0xA003; public static inline var INVALID_VALUE:Int = 0xA004; public static inline var OUT_OF_MEMORY:Int = 0xA005; + public static inline var MAJOR_VERSION:Int = 0x1000; + public static inline var MINOR_VERSION:Int = 0x1001; public static inline var ATTRIBUTES_SIZE:Int = 0x1002; public static inline var ALL_ATTRIBUTES:Int = 0x1003; + /* ALC_ENUMERATION_EXT */ public static inline var DEFAULT_DEVICE_SPECIFIER:Int = 0x1004; public static inline var DEVICE_SPECIFIER:Int = 0x1005; public static inline var EXTENSIONS:Int = 0x1006; - public static inline var ENUMERATE_ALL_EXT:Int = 1; - public static inline var DEFAULT_ALL_DEVICES_SPECIFIER:Int = 0x1012; - public static inline var ALL_DEVICES_SPECIFIER:Int = 0x1013; + /* ALC_EXT_CAPTURE */ public static inline var CAPTURE_DEVICE_SPECIFIER:Int = 0x310; public static inline var CAPTURE_DEFAULT_DEVICE_SPECIFIER:Int = 0x311; public static inline var CAPTURE_SAMPLES:Int = 0x312; + /* ALC_ENUMERATE_ALL_EXT */ + public static inline var DEFAULT_ALL_DEVICES_SPECIFIER:Int = 0x1012; + public static inline var ALL_DEVICES_SPECIFIER:Int = 0x1013; + /* ALC_EXT_disconnect */ + public static inline var CONNECTED:Int = 0x313; #if lime_openalsoft + /* ALC_SOFT_device_clock */ + public static inline var DEVICE_CLOCK_SOFT:Int = 0x1600; + public static inline var DEVICE_LATENCY_SOFT:Int = 0x1601; + public static inline var DEVICE_CLOCK_LATENCY_SOFT:Int = 0x1602; /* ALC_SOFT_system_events */ public static inline var PLAYBACK_DEVICE_SOFT:Int = 0x19D4; public static inline var CAPTURE_DEVICE_SOFT:Int = 0x19D5; @@ -46,10 +56,6 @@ class ALC public static inline var EVENT_TYPE_DEVICE_REMOVED_SOFT:Int = 0x19D8; public static inline var EVENT_SUPPORTED_SOFT:Int = 0x19D9; public static inline var EVENT_NOT_SUPPORTED_SOFT:Int = 0x19DA; - /* ALC_SOFT_device_clock */ - public static inline var DEVICE_CLOCK_SOFT:Int = 0x1600; - public static inline var DEVICE_LATENCY_SOFT:Int = 0x1601; - public static inline var DEVICE_CLOCK_LATENCY_SOFT:Int = 0x1602; #end public static function closeDevice(device:ALDevice):Bool @@ -142,10 +148,10 @@ class ALC } } - public static function getIntegerv(device:ALDevice, param:Int, size:Int):Array + public static function getIntegerv(device:ALDevice, param:Int, count:Int = 1):Array { #if (lime_cffi && lime_openal && !macro) - var result = NativeCFFI.lime_alc_get_integerv(device, param, size); + var result = NativeCFFI.lime_alc_get_integerv(device, param, count); #if hl if (result == null) return []; var _result = []; @@ -173,23 +179,16 @@ class ALC public static function getStringList(device:ALDevice, param:Int):Array { #if (lime_cffi && lime_openal && !macro) - if (param == DEVICE_SPECIFIER || - param == ALL_DEVICES_SPECIFIER) - { - var result = NativeCFFI.lime_alc_get_string_list(device, param); - #if hl - if (result == null) return []; - var _result = []; - for (i in 0...result.length) - _result[i] = CFFI.stringValue(result[i]); - return _result; - #else - return result; - #end - - } - - return [getString(device, param)]; + var result = NativeCFFI.lime_alc_get_string_list(device, param); + #if hl + if (result == null) return []; + var _result = []; + for (i in 0...result.length) + _result[i] = CFFI.stringValue(result[i]); + return _result; + #else + return result; + #end #else return null; #end @@ -341,5 +340,24 @@ class ALC NativeCFFI.lime_alc_capture_samples(device, buffer, samples); #end } + + // TODO: getInteger64vSOFT isn't possible for now, so just make it up as of now + public static function getDoublevSOFT(device:ALDevice, param:Int, count:Int = 1):Array + { + #if (lime_cffi && lime_openal && !macro) + var result = NativeCFFI.lime_alc_get_doublev_soft(device, param, count); + #if hl + if (result == null) return []; + var _result:Array = []; + for (i in 0...result.length) + _result[i] = result[i]; + return _result; + #else + return result; + #end + #else + return null; + #end + } } #end diff --git a/src/lime/tools/AssetHelper.hx b/src/lime/tools/AssetHelper.hx index b7183e6bdd..e67fa87396 100644 --- a/src/lime/tools/AssetHelper.hx +++ b/src/lime/tools/AssetHelper.hx @@ -27,7 +27,7 @@ class AssetHelper "jpg" => IMAGE, "jpeg" => IMAGE, "png" => IMAGE, "gif" => IMAGE, "webp" => IMAGE, "bmp" => IMAGE, "tiff" => IMAGE, "jfif" => IMAGE, "otf" => FONT, "ttf" => FONT, "wav" => SOUND, "wave" => SOUND, "flac" => SOUND, "mid" => SOUND, "midi" => SOUND, "ogg" => SOUND, "spx" => SOUND, "au" => SOUND, - "aiff" => SOUND, "oga" => SOUND, "mp3" => MUSIC, "mp2" => MUSIC, "exe" => BINARY, "bin" => BINARY, "so" => BINARY, "pch" => BINARY, + "aiff" => SOUND, "oga" => SOUND, "opus" => MUSIC, "mp3" => MUSIC, "mp2" => MUSIC, "exe" => BINARY, "bin" => BINARY, "so" => BINARY, "pch" => BINARY, "dll" => BINARY, "zip" => BINARY, "tar" => BINARY, "gz" => BINARY, "fla" => BINARY, "swf" => BINARY, "atf" => BINARY, "psd" => BINARY, "awd" => BINARY, "txt" => TEXT, "text" => TEXT, "xml" => TEXT, "java" => TEXT, "hx" => TEXT, "cpp" => TEXT, "c" => TEXT, "h" => TEXT, "cs" => TEXT, "js" => TEXT, "mm" => TEXT, "hxml" => TEXT, "html" => TEXT, "json" => TEXT, "css" => TEXT, "gpe" => TEXT, "pbxproj" => TEXT, diff --git a/src/lime/utils/AssetLibrary.hx b/src/lime/utils/AssetLibrary.hx index 723deec015..8819fcf9ad 100644 --- a/src/lime/utils/AssetLibrary.hx +++ b/src/lime/utils/AssetLibrary.hx @@ -14,6 +14,7 @@ import lime.utils.AssetType; @:fileXml('tags="haxe,release"') @:noDebug #end +@:access(lime.media.AudioBuffer) @:access(lime.text.Font) @:access(lime.utils.Assets) class AssetLibrary @@ -167,9 +168,16 @@ class AssetLibrary { if (cachedAudioBuffers.exists(id)) { - return cachedAudioBuffers.get(id); + var cachedBuffer = cachedAudioBuffers.get(id); + + if (__isValidAudioBuffer(cachedBuffer)) + { + return cachedBuffer; + } + + cachedAudioBuffers.remove(id); } - else if (classTypes.exists(id)) + if (classTypes.exists(id)) { return AudioBuffer.fromBytes(cast(Type.createInstance(classTypes.get(id), []), Bytes)); } @@ -179,6 +187,42 @@ class AssetLibrary } } + public function getAudioBufferStream(id:String):AudioBuffer + { + if (cachedAudioBuffers.exists(id)) + { + var cachedBuffer = cachedAudioBuffers.get(id); + + if (__isValidAudioBuffer(cachedBuffer)) + { + return cachedBuffer; + } + + cachedAudioBuffers.remove(id); + } + if (classTypes.exists(id)) + { + #if flash + var buffer = new AudioBuffer(); + buffer.src = cast(Type.createInstance(classTypes.get(id), []), Sound); + return buffer; + #else + return AudioBuffer.fromBytesStream(cast(Type.createInstance(classTypes.get(id), []), Bytes)); + #end + } + else + { + if (pathGroups.exists(id)) + { + return AudioBuffer.fromFilesStream(pathGroups.get(id)); + } + else + { + return AudioBuffer.fromFileStream(getPath(id)); + } + } + } + public function getBytes(id:String):Bytes { if (cachedBytes.exists(id)) @@ -409,9 +453,16 @@ class AssetLibrary { if (cachedAudioBuffers.exists(id)) { - return Future.withValue(cachedAudioBuffers.get(id)); + var cachedBuffer = cachedAudioBuffers.get(id); + + if (__isValidAudioBuffer(cachedBuffer)) + { + return Future.withValue(cachedBuffer); + } + + cachedAudioBuffers.remove(id); } - else if (classTypes.exists(id)) + if (classTypes.exists(id)) { return Future.withValue(AudioBuffer.fromBytes(cast(Type.createInstance(classTypes.get(id), []), Bytes))); } @@ -428,6 +479,46 @@ class AssetLibrary } } + // Lime 8.4.0-dev BS + public function loadAudioBufferStream(id:String):Future + { + if (cachedAudioBuffers.exists(id)) + { + var cachedBuffer = cachedAudioBuffers.get(id); + + if (__isValidAudioBuffer(cachedBuffer)) + { + return Future.withValue(cachedBuffer); + } + + cachedAudioBuffers.remove(id); + } + if (classTypes.exists(id)) + { + #if flash + return Future.withValue(getAudioBufferStream(id)); + #else + return Future.withValue(AudioBuffer.fromBytesStream(cast(Type.createInstance(classTypes.get(id), []), Bytes))); + #end + } + else + { + if (pathGroups.exists(id)) + { + return AudioBuffer.loadFromFilesStream(pathGroups.get(id)); + } + else + { + return AudioBuffer.loadFromFileStream(paths.get(id)); + } + } + } + + @:noCompletion private static function __isValidAudioBuffer(buffer:AudioBuffer):Bool + { + return (buffer != null && !buffer.__isDisposed); + } + public function loadBytes(id:String):Future { if (cachedBytes.exists(id)) diff --git a/src/lime/utils/Assets.hx b/src/lime/utils/Assets.hx index 87808483ed..2f43d1327f 100644 --- a/src/lime/utils/Assets.hx +++ b/src/lime/utils/Assets.hx @@ -15,6 +15,7 @@ import lime.utils.Log; #if !macro import haxe.Json; #end +@:access(lime.media.AudioBuffer) /** *

The Assets class provides a cross-platform interface to access @@ -158,6 +159,57 @@ class Assets return cast getAsset(id, SOUND, useCache); } + public static function getAudioBufferStream(id:String, useCache:Bool = true):AudioBuffer + { + #if (tools && !display && !macro) + var type:AssetType = SOUND; + + if (useCache && cache.enabled) + { + var audio = cache.audio.get(id); + + if (isValidAudio(audio)) + { + return audio; + } + } + + var symbol = new LibrarySymbol(id); + + if (symbol.library != null) + { + if (symbol.exists(type)) + { + if (symbol.isLocal(type)) + { + var asset = symbol.library.getAudioBufferStream(symbol.symbolName); + + if (useCache && cache.enabled) + { + cache.set(id, type, asset); + } + + return asset; + } + else + { + Log.error(type + " asset \"" + id + "\" exists, but only asynchronously"); + } + } + else + { + Log.error("There is no " + type + " asset with an ID of \"" + id + "\""); + } + } + else + { + Log.error(__libraryNotFound(symbol.libraryName)); + } + #end + + return null; + } + /** * Gets an instance of an embedded binary asset * @usage var bytes = Assets.getBytes("file.zip"); @@ -287,9 +339,7 @@ class Assets private static function isValidAudio(buffer:AudioBuffer):Bool { - // TODO: Check disposed - - return buffer != null; + return (buffer != null && !buffer.__isDisposed); } private static function isValidImage(image:Image):Bool @@ -393,6 +443,50 @@ class Assets return cast loadAsset(id, SOUND, useCache); } + public static function loadAudioBufferStream(id:String, useCache:Bool = true):Future + { + #if (tools && !display && !macro) + var type:AssetType = SOUND; + + if (useCache && cache.enabled) + { + var audio = cache.audio.get(id); + + if (isValidAudio(audio)) + { + return Future.withValue(audio); + } + } + + var symbol = new LibrarySymbol(id); + + if (symbol.library != null) + { + if (symbol.exists(type)) + { + var future = symbol.library.loadAudioBufferStream(symbol.symbolName); + + if (useCache && cache.enabled) + { + future.onComplete(function(asset) cache.set(id, type, asset)); + } + + return future; + } + else + { + return cast Future.withError("There is no " + type + " asset with an ID of \"" + id + "\""); + } + } + else + { + return cast Future.withError(__libraryNotFound(symbol.libraryName)); + } + #else + return null; + #end + } + public static function loadBytes(id:String):Future { return cast loadAsset(id, BINARY, false);