// Native Audio // 5argon - Exceed7 Experiments // Problems/suggestions : 5argon@exceed7.com using System; using UnityEngine; namespace E7.Native { /// /// This is a referece to one of all native sources you obtained at . /// Parallels of Unity except they are at native side, you play an audio using it. /// /// Main way to get this is by , /// , or /// public partial struct NativeSource { /// /// This is used to separate a `struct` returned from Native Audio's method /// from a default `struct`. (A trick to make `struct` kinda nullable.) /// public bool IsValid { get; private set; } /// /// It's like an ID of this native source. This is zero-indexed of how many native sources you get at /// If you initialize 3 native sources, then this could be 0, 1, or 2. /// public int Index { get; private set; } private void AssertInitialized() { if (NativeAudio.Initialized == false) { throw new InvalidOperationException("You cannot use NativeSource while Native Audio itself is not yet in initialized state."); } } //Constructing this is reserved for the lib since there are only some certain moment we can assure a valid index. internal NativeSource(int index) { this.Index = index; this.IsValid = true; } /// /// Immediately stop this native source. If it was playing an audio then effectively it stops the audio. /// /// /// [iOS] One of all OpenAL sources that was used to play this sound will stop. /// /// [Android] One of all SLAndroidSimpleBufferQueue that was used to play this sound will stop. /// public void Stop() { AssertInitialized(); #if UNITY_IOS NativeAudio._StopAudio(Index); #elif UNITY_ANDROID NativeAudio.stopAudio(Index); #endif } /// /// Change the volume of native source while it is playing. /// /// /// [iOS] Maps to `AL_GAIN`. It is a scalar amplitude multiplier, so the value can go over 1.0 for increasing volume but can be clipped. /// If you put 0.5f, it is attenuated by 6 dB. /// /// [Android] Maps to `SLVolumeItf` interface -> `SetVolumeLevel`. /// The floating volume parameter will be converted to millibel (20xlog10x100) so that putting 0.5f here results in 6dB attenuation. /// public void SetVolume(float volume) { AssertInitialized(); #if UNITY_IOS NativeAudio._SetVolume(Index, volume); #elif UNITY_ANDROID NativeAudio.setVolume(Index, volume); #endif } /// /// This pan is based on "balance effect" and not a "constant energy pan". That is /// at the center you hear each side fully. (Constant energy pan has 3dB attenuation to both on center.) /// /// /// [iOS] 2D panning in iOS will be emulated in OpenAL's 3D audio engine by splitting your stereo sound into a separated mono sounds, /// then position each one on left and right ear of the listener. When panning, instead of adjusting gain we will just move the source /// further from the listener and the distance attenuation will do the work. (Gain is reserved to the setting volume command, /// so we have 2 stage of gain adjustment this way. /// /// [Android] Maps to SLVolumeItf interface -> SetStereoPosition /// /// /// -1 for full left, 0 for center, 1 for full right. /// public void SetPan(float pan) { AssertInitialized(); #if UNITY_IOS NativeAudio._SetPan(Index, pan); #elif UNITY_ANDROID NativeAudio.setPan(Index, pan); #endif } /// /// Return the current playback time of this native source. /// It is relative to the start of audio data currently playing on the source in **seconds**. /// /// /// The API is very time sensitive and may or may not change the value in the same frame. /// (depending on where you call it in the script) /// /// This behaviour is similar to when calling /// or property, those two are in the same update step. /// /// Note that is not in an update step unlike audio time, /// and will change every time you call even in 2 consecutive lines of code. /// /// A looping audio played by has a playback time resets to 0 everytime a new loop arrives. /// /// [iOS] Get `AL_SEC_OFFSET` attribute. It update in a certain discrete step, and if that step happen in the middle of /// the frame this method will return different value depending on where in the script you call it. The update step timing is THE SAME as /// and . /// /// I observed (in iPad 3, iOS 9) that this function sometimes lags on first few calls. /// It might help to pre-warm by calling this several times in loading screen or something. /// /// [Android] Use `GetPosition` of `SLPlayItf` interface. It update in a certain discrete step, and if that step happen in the middle of /// the frame this method will return different value depending on where in the script you call it. The update step timing is INDEPENDENT from /// and . /// /// Because of how "stop hack" was implemented, any stopped audio will have a playback time equals to audio's length (not 0) /// public float GetPlaybackTime() { AssertInitialized(); #if UNITY_IOS return NativeAudio._GetPlaybackTime(Index); #elif UNITY_ANDROID return NativeAudio.getPlaybackTime(Index); #else return 0; #endif } /// /// Set a playback time of this native source. If the source is in a paused state it is immediately resumed. /// You can set it even while the native source is playing. /// /// public void SetPlaybackTime(float offsetSeconds) { AssertInitialized(); #if UNITY_IOS NativeAudio._SetPlaybackTime(Index, offsetSeconds); #elif UNITY_ANDROID NativeAudio.setPlaybackTime(Index, offsetSeconds); #endif } /// /// Pause this native source. /// /// The source is not protected against being chosen for other audio while pausing, /// and if that happens the pause status will be cleared out. /// public void Pause() { AssertInitialized(); #if UNITY_IOS NativeAudio._Pause(Index); #elif UNITY_ANDROID NativeAudio.pause(Index); #endif } /// /// Resume this native source. /// /// If by the time you call resume the source has already been used to play other audio, /// the resume will have no effect since the pause status had already been clreared out. /// public void Resume() { AssertInitialized(); #if UNITY_IOS NativeAudio._Resume(Index); #elif UNITY_ANDROID NativeAudio.resume(Index); #endif } /// /// A native source will play an audio using loaded audio memory at native side, specified by . /// /// Thrown when you attempt to play an unloaded audio. public void Play(NativeAudioPointer nativeAudioPointer) { Play(nativeAudioPointer, PlayOptions.defaultOptions); } /// /// A native source will play an audio using loaded audio memory at native side, specified by . /// /// Thrown when you attempt to play an unloaded audio. /// Customize your play. Begin creating the option from public void Play(NativeAudioPointer nativeAudioPointer, PlayOptions playOptions) { nativeAudioPointer.AssertLoadedAndInitialized(); #if UNITY_IOS NativeAudio._PrepareAudio(nativeAudioPointer.NextIndex, Index); NativeAudio._PlayAudioWithNativeSourceIndex(Index, playOptions); #elif UNITY_ANDROID NativeAudio.prepareAudio(nativeAudioPointer.NextIndex, Index); NativeAudio.playAudioWithNativeSourceIndex(Index, playOptions); #endif } /// /// (**EXPERIMENTAL**) Try to make the next faster by pre-associating /// the pointer to this native source. Whether if this is possible or not depends on platform. /// /// To "fire" the prepared audio, use the parameterless play method. /// /// Not recommended to care about this generally, because the gain could be next to nothing for hassle you get. /// But it is a method stub for the future where there maybe a significant optimization in doing so. /// /// [iOS] Implemented, but likely negligible.. /// (didn't profile extensively yet, but theoretically there is something to prepare here.) /// /// [Android] Not implemented, no effect. /// /// /// [iOS] Normally on OpenAL will /// /// 1. Choose a source at native side, depending on your /// when using if manually. /// Or automatically round-robin without options. /// 2. Stop that source, and then assign a new audio buffer to it. /// 3. Play that source. /// /// Preparing make it do 1. and 2. preemptively. Then performs 3. "blindly" /// without caring about the current audio. If you didn't wait too long, the preparation should be usable. /// /// [Android] No effect as OpenSL ES play audio by pushing data into `SLAndroidSimpleBufferQueueItf`. /// All the prepare is already at the . I cannot find any other way /// to pre-speeding this up. /// /// An audio to prepare into this native source. public void Prepare(NativeAudioPointer nativeAudioPointer) { nativeAudioPointer.AssertLoadedAndInitialized(); #if UNITY_IOS NativeAudio._PrepareAudio(nativeAudioPointer.NextIndex, Index); #elif UNITY_ANDROID //There is no possible preparation for OpenSL ES at the moment.. #endif } /// /// (**EXPERIMENTAL**) /// Play the audio "blindly" without , /// but **believing** that the prepared audio at is still /// associated with this native source. /// If successful, the play could be potentially faster depending on platforms. /// /// If you waited too long and the native source has already been used with other audio, this may produce unexpected /// result such as repeating an audio you were not expecting when you prepared. With careful native source /// planning, you can know that this will or will not happen. /// /// [iOS] Use this after . /// /// [Android] No effect, Android has no prepare implemented yet. /// public void PlayPrepared() { PlayPrepared(PlayOptions.defaultOptions); } /// /// (**EXPERIMENTAL**) /// Play the audio "blindly" without , /// but **believing** that the prepared audio at is still /// associated with this native source. /// If successful, the play could be potentially faster depending on platforms. /// /// If you waited too long and the native source has already been used with other audio, this may produce unexpected /// result such as repeating an audio you were not expecting when you prepared. With careful native source /// planning, you can know that this will or will not happen. /// /// [iOS] Use this after . /// /// [Android] No effect, Android has no prepare implemented yet. /// public void PlayPrepared(PlayOptions playOptions) { #if UNITY_IOS NativeAudio._PlayAudioWithNativeSourceIndex(Index, playOptions); #elif UNITY_ANDROID #endif } } }