├── .gitignore ├── .vscode └── settings.json ├── Assets ├── AudioLoader.cs ├── AudioLoader.cs.meta ├── NLayer.meta ├── NLayer │ ├── Decoder.meta │ ├── Decoder │ │ ├── BitReservoir.cs │ │ ├── BitReservoir.cs.meta │ │ ├── FrameBase.cs │ │ ├── FrameBase.cs.meta │ │ ├── Huffman.cs │ │ ├── Huffman.cs.meta │ │ ├── ID3Frame.cs │ │ ├── ID3Frame.cs.meta │ │ ├── LayerDecoderBase.cs │ │ ├── LayerDecoderBase.cs.meta │ │ ├── LayerIDecoder.cs │ │ ├── LayerIDecoder.cs.meta │ │ ├── LayerIIDecoder.cs │ │ ├── LayerIIDecoder.cs.meta │ │ ├── LayerIIDecoderBase.cs │ │ ├── LayerIIDecoderBase.cs.meta │ │ ├── LayerIIIDecoder.cs │ │ ├── LayerIIIDecoder.cs.meta │ │ ├── MpegFrame.cs │ │ ├── MpegFrame.cs.meta │ │ ├── MpegStreamReader.cs │ │ ├── MpegStreamReader.cs.meta │ │ ├── RiffHeaderFrame.cs │ │ ├── RiffHeaderFrame.cs.meta │ │ ├── VBRInfo.cs │ │ └── VBRInfo.cs.meta │ ├── Enums.cs │ ├── Enums.cs.meta │ ├── IMpegFrame.cs │ ├── IMpegFrame.cs.meta │ ├── LICENSE │ ├── MpegFile.cs │ ├── MpegFile.cs.meta │ ├── MpegFrameDecoder.cs │ ├── MpegFrameDecoder.cs.meta │ └── README.md ├── Scenes.meta └── Scenes │ ├── SampleScene.unity │ └── SampleScene.unity.meta ├── Packages └── manifest.json ├── ProjectSettings ├── AudioManager.asset ├── ClusterInputManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── GraphicsSettings.asset ├── InputManager.asset ├── NavMeshAreas.asset ├── NetworkManager.asset ├── Physics2DSettings.asset ├── PresetManager.asset ├── ProjectSettings.asset ├── ProjectVersion.txt ├── QualitySettings.asset ├── TagManager.asset ├── TimeManager.asset ├── UnityConnectSettings.asset ├── VFXManager.asset └── XRSettings.asset └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Asset meta data should only be ignored when the corresponding asset is also ignored 18 | !/[Aa]ssets/**/*.meta 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.unitypackage 61 | 62 | Assets/StreamingAssets/ 63 | 64 | # Crashlytics generated file 65 | crashlytics-build.properties 66 | 67 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": 3 | { 4 | "**/.DS_Store":true, 5 | "**/.git":true, 6 | "**/.gitignore":true, 7 | "**/.gitmodules":true, 8 | "**/*.booproj":true, 9 | "**/*.pidb":true, 10 | "**/*.suo":true, 11 | "**/*.user":true, 12 | "**/*.userprefs":true, 13 | "**/*.unityproj":true, 14 | "**/*.dll":true, 15 | "**/*.exe":true, 16 | "**/*.pdf":true, 17 | "**/*.mid":true, 18 | "**/*.midi":true, 19 | "**/*.wav":true, 20 | "**/*.gif":true, 21 | "**/*.ico":true, 22 | "**/*.jpg":true, 23 | "**/*.jpeg":true, 24 | "**/*.png":true, 25 | "**/*.psd":true, 26 | "**/*.tga":true, 27 | "**/*.tif":true, 28 | "**/*.tiff":true, 29 | "**/*.3ds":true, 30 | "**/*.3DS":true, 31 | "**/*.fbx":true, 32 | "**/*.FBX":true, 33 | "**/*.lxo":true, 34 | "**/*.LXO":true, 35 | "**/*.ma":true, 36 | "**/*.MA":true, 37 | "**/*.obj":true, 38 | "**/*.OBJ":true, 39 | "**/*.asset":true, 40 | "**/*.cubemap":true, 41 | "**/*.flare":true, 42 | "**/*.mat":true, 43 | "**/*.meta":true, 44 | "**/*.prefab":true, 45 | "**/*.unity":true, 46 | "build/":true, 47 | "Build/":true, 48 | "Library/":true, 49 | "library/":true, 50 | "obj/":true, 51 | "Obj/":true, 52 | "ProjectSettings/":true, 53 | "temp/":true, 54 | "Temp/":true 55 | } 56 | } -------------------------------------------------------------------------------- /Assets/AudioLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEngine.Networking; 5 | 6 | public class AudioLoader : MonoBehaviour 7 | { 8 | public UnityEngine.UI.Text statusText; 9 | public UnityEngine.UI.InputField url; 10 | public AudioSource source; 11 | 12 | void Start() 13 | { 14 | 15 | } 16 | 17 | public void LoadAudio() 18 | { 19 | StartCoroutine(LoadHelper(url.text)); 20 | } 21 | 22 | /* bad, no mp3 support at runtime apparently 23 | IEnumerator LoadHelper(string uri) 24 | { 25 | using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(uri, AudioType.MPEG)) 26 | { 27 | yield return www.SendWebRequest(); 28 | 29 | if (www.isNetworkError) 30 | { 31 | statusText.text = www.error; 32 | } 33 | else 34 | { 35 | statusText.text = "loaded?"; 36 | AudioClip myClip = DownloadHandlerAudioClip.GetContent(www); 37 | source.clip = myClip; 38 | } 39 | } 40 | } 41 | */ 42 | 43 | /* MP3Stream (LGPL so not compatible with how Unity builds) 44 | IEnumerator LoadHelper(string uri) 45 | { 46 | statusText.text = "Loading..."; 47 | UnityWebRequest www = UnityWebRequest.Get(uri); 48 | yield return www.SendWebRequest(); 49 | 50 | if(www.isNetworkError || www.isHttpError) 51 | { 52 | statusText.text = www.error; 53 | } 54 | else 55 | { 56 | statusText.text = "loaded"; 57 | // Or retrieve results as binary data 58 | byte[] results = www.downloadHandler.data; 59 | var memStream = new System.IO.MemoryStream(results); 60 | var mp3Stream = new MP3Sharp.MP3Stream(memStream); 61 | 62 | var audioDataStream = new System.IO.MemoryStream(); 63 | byte[] buffer = new byte[4096]; 64 | int bytesReturned = -1; 65 | 66 | while( bytesReturned != 0 ) 67 | { 68 | bytesReturned = mp3Stream.Read(buffer, 0, buffer.Length); 69 | audioDataStream.Write(buffer, 0, bytesReturned); 70 | } 71 | 72 | byte[] audioData = audioDataStream.ToArray(); 73 | 74 | float[] floatArray = new float[audioData.Length / 2]; 75 | for(int i = 0; i < floatArray.Length; ++i) 76 | { 77 | floatArray[i] = (float)(System.BitConverter.ToInt16(audioData, i * 2 ) / 32768.0f); 78 | } 79 | 80 | var clip = AudioClip.Create("foo", floatArray.Length, 2, mp3Stream.Frequency, false); 81 | clip.SetData(floatArray, 0); 82 | source.clip = clip; 83 | } 84 | } 85 | */ 86 | 87 | IEnumerator LoadHelper(string uri) 88 | { 89 | statusText.text = "Loading..."; 90 | UnityWebRequest www = UnityWebRequest.Get(uri); 91 | yield return www.SendWebRequest(); 92 | 93 | if(www.isNetworkError || www.isHttpError) 94 | { 95 | statusText.text = www.error; 96 | } 97 | else 98 | { 99 | statusText.text = "loaded"; 100 | // Or retrieve results as binary data 101 | byte[] results = www.downloadHandler.data; 102 | var memStream = new System.IO.MemoryStream(results); 103 | var mpgFile = new NLayer.MpegFile(memStream); 104 | var samples = new float[mpgFile.Length]; 105 | mpgFile.ReadSamples(samples, 0, (int)mpgFile.Length); 106 | 107 | var clip = AudioClip.Create("foo", samples.Length, mpgFile.Channels, mpgFile.SampleRate, false); 108 | clip.SetData(samples, 0); 109 | source.clip = clip; 110 | } 111 | } 112 | 113 | void Update() 114 | { 115 | if (!source.isPlaying && source.clip != null && source.clip.loadState == AudioDataLoadState.Loaded) 116 | { 117 | source.Play(); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Assets/AudioLoader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 747f6edd312a7954ea7875b42d5e23d8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 05818d71f24bdb74a8c8f8121396bc61 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ba4b012216b5534aa82e8485d0621f1 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/BitReservoir.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NLayer.Decoder 4 | { 5 | class BitReservoir 6 | { 7 | // Per the spec, the maximum buffer size for layer III is 7680 bits, which is 960 bytes. 8 | // The only catch is if we're decoding a "free" frame, which could be a lot more (since 9 | // some encoders allow higher bitrates to maintain audio transparency). 10 | byte[] _buf = new byte[8192]; 11 | int _start = 0, _end = -1, _bitsLeft = 0; 12 | long _bitsRead = 0L; 13 | 14 | static int GetSlots(IMpegFrame frame) 15 | { 16 | var cnt = frame.FrameLength - 4; 17 | if (frame.HasCrc) cnt -= 2; 18 | 19 | if (frame.Version == MpegVersion.Version1 && frame.ChannelMode != MpegChannelMode.Mono) return cnt - 32; 20 | if (frame.Version > MpegVersion.Version1 && frame.ChannelMode == MpegChannelMode.Mono) return cnt - 9; 21 | return cnt - 17; 22 | 23 | } 24 | 25 | public bool AddBits(IMpegFrame frame, int overlap) 26 | { 27 | var originalEnd = _end; 28 | 29 | var slots = GetSlots(frame); 30 | while (--slots >= 0) 31 | { 32 | var temp = frame.ReadBits(8); 33 | if (temp == -1) throw new System.IO.InvalidDataException("Frame did not have enough bytes!"); 34 | _buf[++_end] = (byte)temp; 35 | if (_end == _buf.Length - 1) _end = -1; 36 | } 37 | 38 | _bitsLeft = 8; 39 | if (originalEnd == -1) 40 | { 41 | // it's either the start of the stream or we've reset... only return true if overlap says this frame is enough 42 | return overlap == 0; 43 | } 44 | else 45 | { 46 | // it's not the start of the stream so calculate _start based on whether we have enough bytes left 47 | 48 | // if we have enough bytes, reset start to match overlap 49 | if ((originalEnd + 1 - _start + _buf.Length) % _buf.Length >= overlap) 50 | { 51 | _start = (originalEnd + 1 - overlap + _buf.Length) % _buf.Length; 52 | return true; 53 | } 54 | // otherwise, just set start to match the start of the frame (we probably skipped a frame) 55 | else 56 | { 57 | _start = originalEnd; 58 | return false; 59 | } 60 | } 61 | } 62 | 63 | public int GetBits(int count) 64 | { 65 | int bitsRead; 66 | var bits = TryPeekBits(count, out bitsRead); 67 | if (bitsRead < count) throw new System.IO.InvalidDataException("Reservoir did not have enough bytes!"); 68 | 69 | SkipBits(count); 70 | 71 | return bits; 72 | } 73 | 74 | public int Get1Bit() 75 | { 76 | // this is an optimized single-bit reader 77 | if (_bitsLeft == 0) throw new System.IO.InvalidDataException("Reservoir did not have enough bytes!"); 78 | 79 | --_bitsLeft; 80 | ++_bitsRead; 81 | var val = (_buf[_start] >> _bitsLeft) & 1; 82 | 83 | if (_bitsLeft == 0 && (_start = (_start + 1) % _buf.Length) != _end + 1) 84 | { 85 | _bitsLeft = 8; 86 | } 87 | 88 | return val; 89 | } 90 | 91 | public int TryPeekBits(int count, out int readCount) 92 | { 93 | if (count < 0 || count > 32) throw new ArgumentOutOfRangeException("count", "Must return between 0 and 32 bits!"); 94 | 95 | // if we don't have any bits left, just return no bits read 96 | if (_bitsLeft == 0 || count == 0) 97 | { 98 | readCount = 0; 99 | return 0; 100 | } 101 | 102 | // get bits from the current start of the reservoir 103 | var bits = (int)_buf[_start]; 104 | if (count < _bitsLeft) 105 | { 106 | // just grab the bits, adjust the "left" count, and return 107 | bits >>= _bitsLeft - count; 108 | bits &= ((1 << count) - 1); 109 | readCount = count; 110 | return bits; 111 | } 112 | 113 | // we have to do it the hard way... 114 | bits &= ((1 << _bitsLeft) - 1); 115 | count -= _bitsLeft; 116 | readCount = _bitsLeft; 117 | 118 | var resStart = _start; 119 | 120 | // arg... gotta grab some more bits... 121 | while (count > 0) 122 | { 123 | // advance the start marker, and if we just advanced it past the end of the buffer, bail 124 | if ((resStart = (resStart + 1) % _buf.Length) == _end + 1) 125 | { 126 | break; 127 | } 128 | 129 | // figure out how many bits to pull from it 130 | var bitsToRead = Math.Min(count, 8); 131 | 132 | // move the existing bits over 133 | bits <<= bitsToRead; 134 | bits |= (_buf[resStart] >> ((8 - bitsToRead) % 8)); 135 | 136 | // update our count 137 | count -= bitsToRead; 138 | 139 | // update our remaining bits 140 | readCount += bitsToRead; 141 | } 142 | 143 | return bits; 144 | } 145 | 146 | public int BitsAvailable 147 | { 148 | get 149 | { 150 | if (_bitsLeft > 0) 151 | { 152 | return (((_end + _buf.Length) - _start) % _buf.Length) * 8 + _bitsLeft; 153 | } 154 | return 0; 155 | } 156 | } 157 | 158 | public long BitsRead 159 | { 160 | get { return _bitsRead; } 161 | } 162 | 163 | public void SkipBits(int count) 164 | { 165 | if (count > 0) 166 | { 167 | // make sure we have enough bits to skip 168 | if (count > BitsAvailable) throw new ArgumentOutOfRangeException("count"); 169 | 170 | // now calculate the new positions 171 | var offset = (8 - _bitsLeft) + count; 172 | _start = ((offset / 8) + _start) % _buf.Length; 173 | _bitsLeft = 8 - (offset % 8); 174 | 175 | _bitsRead += count; 176 | } 177 | } 178 | 179 | public void RewindBits(int count) 180 | { 181 | _bitsLeft += count; 182 | _bitsRead -= count; 183 | while (_bitsLeft > 8) 184 | { 185 | --_start; 186 | _bitsLeft -= 8; 187 | } 188 | while (_start < 0) 189 | { 190 | _start += _buf.Length; 191 | } 192 | } 193 | 194 | public void FlushBits() 195 | { 196 | if (_bitsLeft < 8) 197 | { 198 | SkipBits(_bitsLeft); 199 | } 200 | } 201 | 202 | public void Reset() 203 | { 204 | _start = 0; 205 | _end = -1; 206 | _bitsLeft = 0; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/BitReservoir.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2a5750753867bb14cbfcba4c6e10ad02 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/FrameBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NLayer.Decoder 4 | { 5 | abstract class FrameBase 6 | { 7 | static int _totalAllocation = 0; 8 | static internal int TotalAllocation 9 | { 10 | get { return System.Threading.Interlocked.CompareExchange(ref _totalAllocation, 0, 0); } 11 | } 12 | 13 | internal long Offset { get; private set; } 14 | internal int Length { get; set; } 15 | 16 | MpegStreamReader _reader; 17 | 18 | byte[] _savedBuffer; 19 | 20 | protected FrameBase() { } 21 | 22 | internal bool Validate(long offset, MpegStreamReader reader) 23 | { 24 | Offset = offset; 25 | _reader = reader; 26 | 27 | var len = Validate(); 28 | 29 | if (len > 0) 30 | { 31 | Length = len; 32 | return true; 33 | } 34 | return false; 35 | } 36 | 37 | protected int Read(int offset, byte[] buffer) 38 | { 39 | return Read(offset, buffer, 0, buffer.Length); 40 | } 41 | 42 | protected int Read(int offset, byte[] buffer, int index, int count) 43 | { 44 | if (_savedBuffer != null) 45 | { 46 | if (index < 0 || index + count > buffer.Length) return 0; // check against caller's buffer 47 | if (offset < 0 || offset >= _savedBuffer.Length) return 0; // check against saved buffer 48 | if (offset + count > _savedBuffer.Length) count = _savedBuffer.Length - index; // twiddle the size as needed 49 | 50 | Array.Copy(_savedBuffer, offset, buffer, index, count); 51 | return count; 52 | } 53 | else 54 | { 55 | return _reader.Read(Offset + offset, buffer, index, count); 56 | } 57 | } 58 | 59 | protected int ReadByte(int offset) 60 | { 61 | if (_savedBuffer != null) 62 | { 63 | if (offset < 0) throw new ArgumentOutOfRangeException(); 64 | if (offset >= _savedBuffer.Length) return -1; 65 | 66 | return (int)_savedBuffer[offset]; 67 | } 68 | else 69 | { 70 | return _reader.ReadByte(Offset + offset); 71 | } 72 | } 73 | 74 | /// 75 | /// Called to validate the frame header 76 | /// 77 | /// The length of the frame, or -1 if frame is invalid 78 | abstract protected int Validate(); 79 | 80 | internal void SaveBuffer() 81 | { 82 | _savedBuffer = new byte[Length]; 83 | _reader.Read(Offset, _savedBuffer, 0, Length); 84 | System.Threading.Interlocked.Add(ref _totalAllocation, Length); 85 | } 86 | 87 | internal void ClearBuffer() 88 | { 89 | System.Threading.Interlocked.Add(ref _totalAllocation, -Length); 90 | _savedBuffer = null; 91 | } 92 | 93 | /// 94 | /// Called when the stream is not "seek-able" 95 | /// 96 | virtual internal void Parse() { } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/FrameBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6d6c9072454d4d749b51b86496ed7373 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/Huffman.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae03d571c79ef1c4684a2d22ccee9aa5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/ID3Frame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NLayer.Decoder 7 | { 8 | class ID3Frame : FrameBase 9 | { 10 | internal static ID3Frame TrySync(uint syncMark) 11 | { 12 | if ((syncMark & 0xFFFFFF00U) == 0x49443300) 13 | { 14 | return new ID3Frame { _version = 2 }; 15 | } 16 | 17 | if ((syncMark & 0xFFFFFF00U) == 0x54414700) 18 | { 19 | if ((syncMark & 0xFF) == 0x2B) 20 | { 21 | return new ID3Frame { _version = 1 }; 22 | } 23 | else 24 | { 25 | return new ID3Frame { _version = 0 }; 26 | } 27 | } 28 | 29 | return null; 30 | } 31 | 32 | int _version; 33 | 34 | ID3Frame() 35 | { 36 | 37 | } 38 | 39 | protected override int Validate() 40 | { 41 | switch (_version) 42 | { 43 | case 2: 44 | // v2, yay! 45 | var buf = new byte[7]; 46 | if (Read(3, buf) == 7) 47 | { 48 | byte flagsMask; 49 | switch (buf[0]) 50 | { 51 | case 3: 52 | flagsMask = 0x1F; 53 | break; 54 | case 4: 55 | flagsMask = 0x0F; 56 | break; 57 | default: 58 | return -1; 59 | } 60 | 61 | // ignore the flags (we don't need them for the validation) 62 | 63 | // get the size (7 bits per byte [MSB cleared]) 64 | var size = (buf[3] << 21) 65 | | (buf[4] << 14) 66 | | (buf[5] << 7) 67 | | (buf[6]); 68 | 69 | // finally, check to make sure that all the right bits are cleared 70 | if (!(((buf[2] & flagsMask) | (buf[3] & 0x80) | (buf[4] & 0x80) | (buf[5] & 0x80) | (buf[6] & 0x80)) != 0 || buf[1] == 0xFF)) 71 | { 72 | return size + 10; // don't forget the sync, flag & size bytes! 73 | } 74 | } 75 | break; 76 | case 1: 77 | return 227 + 128; 78 | case 0: 79 | return 128; 80 | } 81 | 82 | return -1; 83 | } 84 | 85 | internal override void Parse() 86 | { 87 | // assume we have to process it now or else... we can still read the whole frame, so no biggie 88 | switch (_version) 89 | { 90 | case 2: 91 | ParseV2(); 92 | break; 93 | case 1: 94 | ParseV1Enh(); 95 | break; 96 | case 0: 97 | ParseV1(3); 98 | break; 99 | } 100 | } 101 | 102 | void ParseV1(int offset) 103 | { 104 | //var buffer = new byte[125]; 105 | //if (Read(offset, buffer) == 125) 106 | //{ 107 | // // v1 tags use ASCII encoding... For now we'll use the built-in encoding, but for Win8 we'll have to build our own. 108 | // var encoding = Encoding.ASCII; 109 | // 110 | // // title (30) 111 | // Title = encoding.GetString(buffer, 0, 30); 112 | // 113 | // // artist (30) 114 | // Artist = encoding.GetString(buffer, 30, 30); 115 | // 116 | // // album (30) 117 | // Album = encoding.GetString(buffer, 60, 30); 118 | // 119 | // // year (4) 120 | // Year = encoding.GetString(buffer, 90, 30); 121 | // 122 | // // comment (30)* 123 | // Comment = encoding.GetString(buffer, 94, 30); 124 | // 125 | // if (buffer[122] == 0) 126 | // { 127 | // // track (1)* 128 | // Track = (int)buffer[123]; 129 | // } 130 | // 131 | // // genre (1) 132 | // // ignore for now 133 | // 134 | // // * if byte 29 of comment is 0, track is byte 30. Otherwise, track is unknown. 135 | //} 136 | } 137 | 138 | void ParseV1Enh() 139 | { 140 | ParseV1(230); 141 | 142 | //var buffer = new byte[223]; 143 | //if (Read(4, buffer) == 223) 144 | //{ 145 | // // v1 tags use ASCII encoding... For now we'll use the built-in encoding, but for Win8 we'll have to build our own. 146 | // var encoding = Encoding.ASCII; 147 | // 148 | // // title (60) 149 | // Title += encoding.GetString(buffer, 0, 60); 150 | // 151 | // // artist (60) 152 | // Artist += encoding.GetString(buffer, 60, 60); 153 | // 154 | // // album (60) 155 | // Album += encoding.GetString(buffer, 120, 60); 156 | // 157 | // // speed (1) 158 | // //var speed = buffer[180]; 159 | // 160 | // // genre (30) 161 | // Genre = encoding.GetString(buffer, 181, 30); 162 | // 163 | // // start-time (6) 164 | // // 211 165 | // 166 | // // end-time (6) 167 | // // 217 168 | //} 169 | } 170 | 171 | void ParseV2() 172 | { 173 | // v2 is much more complicated than v1... don't worry about it for now 174 | // look for any merged frames, as well 175 | } 176 | 177 | internal int Version 178 | { 179 | get 180 | { 181 | if (_version == 0) return 1; 182 | return _version; 183 | } 184 | } 185 | 186 | //internal string Title { get; private set; } 187 | //internal string Artist { get; private set; } 188 | //internal string Album { get; private set; } 189 | //internal string Year { get; private set; } 190 | //internal string Comment { get; private set; } 191 | //internal int Track { get; private set; } 192 | //internal string Genre { get; private set; } 193 | // speed 194 | //public TimeSpan StartTime { get; private set; } 195 | //public TimeSpan EndTime { get; private set; } 196 | 197 | internal void Merge(ID3Frame newFrame) 198 | { 199 | // just save off the frame for parsing later 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/ID3Frame.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a98c2c1103a937848a69259c99532070 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerDecoderBase.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * NLayer - A C# MPEG1/2/2.5 audio decoder 3 | * 4 | * Portions of this file are courtesy Fluendo, S.A. They are dual licensed as Ms-PL 5 | * and under the following license: 6 | * 7 | * Copyright <2005-2012> Fluendo S.A. 8 | * 9 | * Unless otherwise indicated, Source Code is licensed under MIT license. 10 | * See further explanation attached in License Statement (distributed in the file 11 | * LICENSE). 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 14 | * this software and associated documentation files (the "Software"), to deal in 15 | * the Software without restriction, including without limitation the rights to 16 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 17 | * of the Software, and to permit persons to whom the Software is furnished to do 18 | * so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | * 31 | */ 32 | 33 | using System.Collections.Generic; 34 | 35 | namespace NLayer.Decoder 36 | { 37 | abstract class LayerDecoderBase 38 | { 39 | protected const int SBLIMIT = 32; 40 | 41 | const float INV_SQRT_2 = 7.071067811865474617150084668537e-01f; 42 | 43 | #region Tables 44 | 45 | static float[] DEWINDOW_TABLE = { 46 | 0.000000000f, -0.000015259f, -0.000015259f, -0.000015259f, 47 | -0.000015259f, -0.000015259f, -0.000015259f, -0.000030518f, 48 | -0.000030518f, -0.000030518f, -0.000030518f, -0.000045776f, 49 | -0.000045776f, -0.000061035f, -0.000061035f, -0.000076294f, 50 | -0.000076294f, -0.000091553f, -0.000106812f, -0.000106812f, 51 | -0.000122070f, -0.000137329f, -0.000152588f, -0.000167847f, 52 | -0.000198364f, -0.000213623f, -0.000244141f, -0.000259399f, 53 | -0.000289917f, -0.000320435f, -0.000366211f, -0.000396729f, 54 | -0.000442505f, -0.000473022f, -0.000534058f, -0.000579834f, 55 | -0.000625610f, -0.000686646f, -0.000747681f, -0.000808716f, 56 | -0.000885010f, -0.000961304f, -0.001037598f, -0.001113892f, 57 | -0.001205444f, -0.001296997f, -0.001388550f, -0.001480103f, 58 | -0.001586914f, -0.001693726f, -0.001785278f, -0.001907349f, 59 | -0.002014160f, -0.002120972f, -0.002243042f, -0.002349854f, 60 | -0.002456665f, -0.002578735f, -0.002685547f, -0.002792358f, 61 | -0.002899170f, -0.002990723f, -0.003082275f, -0.003173828f, 62 | 0.003250122f, 0.003326416f, 0.003387451f, 0.003433228f, 63 | 0.003463745f, 0.003479004f, 0.003479004f, 0.003463745f, 64 | 0.003417969f, 0.003372192f, 0.003280640f, 0.003173828f, 65 | 0.003051758f, 0.002883911f, 0.002700806f, 0.002487183f, 66 | 0.002227783f, 0.001937866f, 0.001617432f, 0.001266479f, 67 | 0.000869751f, 0.000442505f, -0.000030518f, -0.000549316f, 68 | -0.001098633f, -0.001693726f, -0.002334595f, -0.003005981f, 69 | -0.003723145f, -0.004486084f, -0.005294800f, -0.006118774f, 70 | -0.007003784f, -0.007919312f, -0.008865356f, -0.009841919f, 71 | -0.010848999f, -0.011886597f, -0.012939453f, -0.014022827f, 72 | -0.015121460f, -0.016235352f, -0.017349243f, -0.018463135f, 73 | -0.019577026f, -0.020690918f, -0.021789551f, -0.022857666f, 74 | -0.023910522f, -0.024932861f, -0.025909424f, -0.026840210f, 75 | -0.027725220f, -0.028533936f, -0.029281616f, -0.029937744f, 76 | -0.030532837f, -0.031005859f, -0.031387329f, -0.031661987f, 77 | -0.031814575f, -0.031845093f, -0.031738281f, -0.031478882f, 78 | 0.031082153f, 0.030517578f, 0.029785156f, 0.028884888f, 79 | 0.027801514f, 0.026535034f, 0.025085449f, 0.023422241f, 80 | 0.021575928f, 0.019531250f, 0.017257690f, 0.014801025f, 81 | 0.012115479f, 0.009231567f, 0.006134033f, 0.002822876f, 82 | -0.000686646f, -0.004394531f, -0.008316040f, -0.012420654f, 83 | -0.016708374f, -0.021179199f, -0.025817871f, -0.030609131f, 84 | -0.035552979f, -0.040634155f, -0.045837402f, -0.051132202f, 85 | -0.056533813f, -0.061996460f, -0.067520142f, -0.073059082f, 86 | -0.078628540f, -0.084182739f, -0.089706421f, -0.095169067f, 87 | -0.100540161f, -0.105819702f, -0.110946655f, -0.115921021f, 88 | -0.120697021f, -0.125259399f, -0.129562378f, -0.133590698f, 89 | -0.137298584f, -0.140670776f, -0.143676758f, -0.146255493f, 90 | -0.148422241f, -0.150115967f, -0.151306152f, -0.151962280f, 91 | -0.152069092f, -0.151596069f, -0.150497437f, -0.148773193f, 92 | -0.146362305f, -0.143264771f, -0.139450073f, -0.134887695f, 93 | -0.129577637f, -0.123474121f, -0.116577148f, -0.108856201f, 94 | 0.100311279f, 0.090927124f, 0.080688477f, 0.069595337f, 95 | 0.057617187f, 0.044784546f, 0.031082153f, 0.016510010f, 96 | 0.001068115f, -0.015228271f, -0.032379150f, -0.050354004f, 97 | -0.069168091f, -0.088775635f, -0.109161377f, -0.130310059f, 98 | -0.152206421f, -0.174789429f, -0.198059082f, -0.221984863f, 99 | -0.246505737f, -0.271591187f, -0.297210693f, -0.323318481f, 100 | -0.349868774f, -0.376800537f, -0.404083252f, -0.431655884f, 101 | -0.459472656f, -0.487472534f, -0.515609741f, -0.543823242f, 102 | -0.572036743f, -0.600219727f, -0.628295898f, -0.656219482f, 103 | -0.683914185f, -0.711318970f, -0.738372803f, -0.765029907f, 104 | -0.791213989f, -0.816864014f, -0.841949463f, -0.866363525f, 105 | -0.890090942f, -0.913055420f, -0.935195923f, -0.956481934f, 106 | -0.976852417f, -0.996246338f, -1.014617920f, -1.031936646f, 107 | -1.048156738f, -1.063217163f, -1.077117920f, -1.089782715f, 108 | -1.101211548f, -1.111373901f, -1.120223999f, -1.127746582f, 109 | -1.133926392f, -1.138763428f, -1.142211914f, -1.144287109f, 110 | 1.144989014f, 1.144287109f, 1.142211914f, 1.138763428f, 111 | 1.133926392f, 1.127746582f, 1.120223999f, 1.111373901f, 112 | 1.101211548f, 1.089782715f, 1.077117920f, 1.063217163f, 113 | 1.048156738f, 1.031936646f, 1.014617920f, 0.996246338f, 114 | 0.976852417f, 0.956481934f, 0.935195923f, 0.913055420f, 115 | 0.890090942f, 0.866363525f, 0.841949463f, 0.816864014f, 116 | 0.791213989f, 0.765029907f, 0.738372803f, 0.711318970f, 117 | 0.683914185f, 0.656219482f, 0.628295898f, 0.600219727f, 118 | 0.572036743f, 0.543823242f, 0.515609741f, 0.487472534f, 119 | 0.459472656f, 0.431655884f, 0.404083252f, 0.376800537f, 120 | 0.349868774f, 0.323318481f, 0.297210693f, 0.271591187f, 121 | 0.246505737f, 0.221984863f, 0.198059082f, 0.174789429f, 122 | 0.152206421f, 0.130310059f, 0.109161377f, 0.088775635f, 123 | 0.069168091f, 0.050354004f, 0.032379150f, 0.015228271f, 124 | -0.001068115f, -0.016510010f, -0.031082153f, -0.044784546f, 125 | -0.057617187f, -0.069595337f, -0.080688477f, -0.090927124f, 126 | 0.100311279f, 0.108856201f, 0.116577148f, 0.123474121f, 127 | 0.129577637f, 0.134887695f, 0.139450073f, 0.143264771f, 128 | 0.146362305f, 0.148773193f, 0.150497437f, 0.151596069f, 129 | 0.152069092f, 0.151962280f, 0.151306152f, 0.150115967f, 130 | 0.148422241f, 0.146255493f, 0.143676758f, 0.140670776f, 131 | 0.137298584f, 0.133590698f, 0.129562378f, 0.125259399f, 132 | 0.120697021f, 0.115921021f, 0.110946655f, 0.105819702f, 133 | 0.100540161f, 0.095169067f, 0.089706421f, 0.084182739f, 134 | 0.078628540f, 0.073059082f, 0.067520142f, 0.061996460f, 135 | 0.056533813f, 0.051132202f, 0.045837402f, 0.040634155f, 136 | 0.035552979f, 0.030609131f, 0.025817871f, 0.021179199f, 137 | 0.016708374f, 0.012420654f, 0.008316040f, 0.004394531f, 138 | 0.000686646f, -0.002822876f, -0.006134033f, -0.009231567f, 139 | -0.012115479f, -0.014801025f, -0.017257690f, -0.019531250f, 140 | -0.021575928f, -0.023422241f, -0.025085449f, -0.026535034f, 141 | -0.027801514f, -0.028884888f, -0.029785156f, -0.030517578f, 142 | 0.031082153f, 0.031478882f, 0.031738281f, 0.031845093f, 143 | 0.031814575f, 0.031661987f, 0.031387329f, 0.031005859f, 144 | 0.030532837f, 0.029937744f, 0.029281616f, 0.028533936f, 145 | 0.027725220f, 0.026840210f, 0.025909424f, 0.024932861f, 146 | 0.023910522f, 0.022857666f, 0.021789551f, 0.020690918f, 147 | 0.019577026f, 0.018463135f, 0.017349243f, 0.016235352f, 148 | 0.015121460f, 0.014022827f, 0.012939453f, 0.011886597f, 149 | 0.010848999f, 0.009841919f, 0.008865356f, 0.007919312f, 150 | 0.007003784f, 0.006118774f, 0.005294800f, 0.004486084f, 151 | 0.003723145f, 0.003005981f, 0.002334595f, 0.001693726f, 152 | 0.001098633f, 0.000549316f, 0.000030518f, -0.000442505f, 153 | -0.000869751f, -0.001266479f, -0.001617432f, -0.001937866f, 154 | -0.002227783f, -0.002487183f, -0.002700806f, -0.002883911f, 155 | -0.003051758f, -0.003173828f, -0.003280640f, -0.003372192f, 156 | -0.003417969f, -0.003463745f, -0.003479004f, -0.003479004f, 157 | -0.003463745f, -0.003433228f, -0.003387451f, -0.003326416f, 158 | 0.003250122f, 0.003173828f, 0.003082275f, 0.002990723f, 159 | 0.002899170f, 0.002792358f, 0.002685547f, 0.002578735f, 160 | 0.002456665f, 0.002349854f, 0.002243042f, 0.002120972f, 161 | 0.002014160f, 0.001907349f, 0.001785278f, 0.001693726f, 162 | 0.001586914f, 0.001480103f, 0.001388550f, 0.001296997f, 163 | 0.001205444f, 0.001113892f, 0.001037598f, 0.000961304f, 164 | 0.000885010f, 0.000808716f, 0.000747681f, 0.000686646f, 165 | 0.000625610f, 0.000579834f, 0.000534058f, 0.000473022f, 166 | 0.000442505f, 0.000396729f, 0.000366211f, 0.000320435f, 167 | 0.000289917f, 0.000259399f, 0.000244141f, 0.000213623f, 168 | 0.000198364f, 0.000167847f, 0.000152588f, 0.000137329f, 169 | 0.000122070f, 0.000106812f, 0.000106812f, 0.000091553f, 170 | 0.000076294f, 0.000076294f, 0.000061035f, 0.000061035f, 171 | 0.000045776f, 0.000045776f, 0.000030518f, 0.000030518f, 172 | 0.000030518f, 0.000030518f, 0.000015259f, 0.000015259f, 173 | 0.000015259f, 0.000015259f, 0.000015259f, 0.000015259f 174 | }; 175 | 176 | static float[] SYNTH_COS64_TABLE = { 177 | 5.0060299823519627260e-01f, 5.0241928618815567820e-01f, 5.0547095989754364798e-01f, 5.0979557910415917998e-01f, 178 | 5.1544730992262455249e-01f, 5.2249861493968885462e-01f, 5.3104259108978413284e-01f, 5.4119610014619701222e-01f, 179 | 5.5310389603444454210e-01f, 5.6694403481635768927e-01f, 5.8293496820613388554e-01f, 6.0134488693504528634e-01f, 180 | 6.2250412303566482475e-01f, 6.4682178335999007679e-01f, 6.7480834145500567800e-01f, 7.0710678118654746172e-01f, 181 | 7.4453627100229857749e-01f, 7.8815462345125020249e-01f, 8.3934964541552681272e-01f, 8.9997622313641556513e-01f, 182 | 9.7256823786196078263e-01f, 1.0606776859903470633e+00f, 1.1694399334328846596e+00f, 1.3065629648763763537e+00f, 183 | 1.4841646163141661852e+00f, 1.7224470982383341955e+00f, 2.0577810099534108446e+00f, 2.5629154477415054814e+00f, 184 | 3.4076084184687189804e+00f, 5.1011486186891552563e+00f, 1.0190008123548032870e+01f 185 | }; 186 | 187 | #endregion 188 | 189 | List _synBuf = new List(2); 190 | List _bufOffset = new List(2); 191 | 192 | float[] _eq; 193 | 194 | internal LayerDecoderBase() 195 | { 196 | StereoMode = StereoMode.Both; 197 | } 198 | 199 | abstract internal int DecodeFrame(IMpegFrame frame, float[] ch0, float[] ch1); 200 | 201 | internal void SetEQ(float[] eq) 202 | { 203 | if (eq == null || eq.Length == 32) 204 | { 205 | _eq = eq; 206 | } 207 | } 208 | 209 | internal StereoMode StereoMode { get; set; } 210 | 211 | virtual internal void ResetForSeek() 212 | { 213 | _synBuf.Clear(); 214 | _bufOffset.Clear(); 215 | } 216 | 217 | float[] ippuv = new float[512]; 218 | 219 | protected void InversePolyPhase(int channel, float[] data) 220 | { 221 | float[] synBuf; 222 | int k; 223 | GetBufAndOffset(channel, out synBuf, out k); 224 | 225 | if (_eq != null) 226 | { 227 | for (int i = 0; i < 32; i++) 228 | { 229 | data[i] *= _eq[i]; 230 | } 231 | } 232 | 233 | DCT32(data, synBuf, k); 234 | 235 | BuildUVec(ippuv, synBuf, k); 236 | 237 | DewindowOutput(ippuv, data); 238 | } 239 | 240 | void GetBufAndOffset(int channel, out float[] synBuf, out int k) 241 | { 242 | while (_synBuf.Count <= channel) 243 | { 244 | _synBuf.Add(new float[1024]); 245 | } 246 | 247 | while (_bufOffset.Count <= channel) 248 | { 249 | _bufOffset.Add(0); 250 | } 251 | 252 | synBuf = _synBuf[channel]; 253 | k = _bufOffset[channel]; 254 | 255 | k = (k - 32) & 511; 256 | _bufOffset[channel] = k; 257 | } 258 | 259 | float[] ei32 = new float[16], eo32 = new float[16], oi32 = new float[16], oo32 = new float[16]; 260 | 261 | void DCT32(float[] _in, float[] _out, int k) 262 | { 263 | int i; 264 | 265 | for (i = 0; i < 16; i++) 266 | { 267 | ei32[i] = _in[i] + _in[31 - i]; 268 | oi32[i] = (_in[i] - _in[31 - i]) * SYNTH_COS64_TABLE[2 * i]; 269 | } 270 | 271 | DCT16(ei32, eo32); 272 | DCT16(oi32, oo32); 273 | 274 | for (i = 0; i < 15; i++) 275 | { 276 | _out[2 * i + k] = eo32[i]; 277 | _out[2 * i + 1 + k] = oo32[i] + oo32[i + 1]; 278 | } 279 | _out[30 + k] = eo32[15]; 280 | _out[31 + k] = oo32[15]; 281 | } 282 | 283 | float[] ei16 = new float[8], eo16 = new float[8], oi16 = new float[8], oo16 = new float[8]; 284 | 285 | void DCT16(float[] _in, float[] _out) 286 | { 287 | float a, b; 288 | 289 | a = _in[0]; b = _in[15]; 290 | ei16[0] = a + b; 291 | oi16[0] = (a - b) * SYNTH_COS64_TABLE[1]; 292 | a = _in[1]; b = _in[14]; 293 | ei16[1] = a + b; 294 | oi16[1] = (a - b) * SYNTH_COS64_TABLE[5]; 295 | a = _in[2]; b = _in[13]; 296 | ei16[2] = a + b; 297 | oi16[2] = (a - b) * SYNTH_COS64_TABLE[9]; 298 | a = _in[3]; b = _in[12]; 299 | ei16[3] = a + b; 300 | oi16[3] = (a - b) * SYNTH_COS64_TABLE[13]; 301 | a = _in[4]; b = _in[11]; 302 | ei16[4] = a + b; 303 | oi16[4] = (a - b) * SYNTH_COS64_TABLE[17]; 304 | a = _in[5]; b = _in[10]; 305 | ei16[5] = a + b; 306 | oi16[5] = (a - b) * SYNTH_COS64_TABLE[21]; 307 | a = _in[6]; b = _in[9]; 308 | ei16[6] = a + b; 309 | oi16[6] = (a - b) * SYNTH_COS64_TABLE[25]; 310 | a = _in[7]; b = _in[8]; 311 | ei16[7] = a + b; 312 | oi16[7] = (a - b) * SYNTH_COS64_TABLE[29]; 313 | 314 | DCT8(ei16, eo16); 315 | DCT8(oi16, oo16); 316 | 317 | _out[0] = eo16[0]; 318 | _out[1] = oo16[0] + oo16[1]; 319 | _out[2] = eo16[1]; 320 | _out[3] = oo16[1] + oo16[2]; 321 | _out[4] = eo16[2]; 322 | _out[5] = oo16[2] + oo16[3]; 323 | _out[6] = eo16[3]; 324 | _out[7] = oo16[3] + oo16[4]; 325 | _out[8] = eo16[4]; 326 | _out[9] = oo16[4] + oo16[5]; 327 | _out[10] = eo16[5]; 328 | _out[11] = oo16[5] + oo16[6]; 329 | _out[12] = eo16[6]; 330 | _out[13] = oo16[6] + oo16[7]; 331 | _out[14] = eo16[7]; 332 | _out[15] = oo16[7]; 333 | } 334 | 335 | float[] ei8 = new float[4], tmp8 = new float[6], oi8 = new float[4], oo8 = new float[4]; 336 | 337 | void DCT8(float[] _in, float[] _out) 338 | { 339 | /* Even indices */ 340 | ei8[0] = _in[0] + _in[7]; 341 | ei8[1] = _in[3] + _in[4]; 342 | ei8[2] = _in[1] + _in[6]; 343 | ei8[3] = _in[2] + _in[5]; 344 | 345 | tmp8[0] = ei8[0] + ei8[1]; 346 | tmp8[1] = ei8[2] + ei8[3]; 347 | tmp8[2] = (ei8[0] - ei8[1]) * SYNTH_COS64_TABLE[7]; 348 | tmp8[3] = (ei8[2] - ei8[3]) * SYNTH_COS64_TABLE[23]; 349 | tmp8[4] = (float)((tmp8[2] - tmp8[3]) * INV_SQRT_2); 350 | 351 | _out[0] = tmp8[0] + tmp8[1]; 352 | _out[2] = tmp8[2] + tmp8[3] + tmp8[4]; 353 | _out[4] = (float)((tmp8[0] - tmp8[1]) * INV_SQRT_2); 354 | _out[6] = tmp8[4]; 355 | 356 | /* Odd indices */ 357 | oi8[0] = (_in[0] - _in[7]) * SYNTH_COS64_TABLE[3]; 358 | oi8[1] = (_in[1] - _in[6]) * SYNTH_COS64_TABLE[11]; 359 | oi8[2] = (_in[2] - _in[5]) * SYNTH_COS64_TABLE[19]; 360 | oi8[3] = (_in[3] - _in[4]) * SYNTH_COS64_TABLE[27]; 361 | 362 | tmp8[0] = oi8[0] + oi8[3]; 363 | tmp8[1] = oi8[1] + oi8[2]; 364 | tmp8[2] = (oi8[0] - oi8[3]) * SYNTH_COS64_TABLE[7]; 365 | tmp8[3] = (oi8[1] - oi8[2]) * SYNTH_COS64_TABLE[23]; 366 | tmp8[4] = tmp8[2] + tmp8[3]; 367 | tmp8[5] = (float)((tmp8[2] - tmp8[3]) * INV_SQRT_2); 368 | 369 | oo8[0] = tmp8[0] + tmp8[1]; 370 | oo8[1] = tmp8[4] + tmp8[5]; 371 | oo8[2] = (float)((tmp8[0] - tmp8[1]) * INV_SQRT_2); 372 | oo8[3] = tmp8[5]; 373 | 374 | _out[1] = oo8[0] + oo8[1]; 375 | _out[3] = oo8[1] + oo8[2]; 376 | _out[5] = oo8[2] + oo8[3]; 377 | _out[7] = oo8[3]; 378 | } 379 | 380 | void BuildUVec(float[] u_vec, float[] cur_synbuf, int k) 381 | { 382 | int i, j, uvp = 0; 383 | 384 | for (j = 0; j < 8; j++) 385 | { 386 | for (i = 0; i < 16; i++) 387 | { 388 | /* Copy first 32 elements */ 389 | u_vec[uvp + i] = cur_synbuf[k + i + 16]; 390 | u_vec[uvp + i + 17] = -cur_synbuf[k + 31 - i]; 391 | } 392 | 393 | /* k wraps at the synthesis buffer boundary */ 394 | k = (k + 32) & 511; 395 | 396 | for (i = 0; i < 16; i++) 397 | { 398 | /* Copy next 32 elements */ 399 | u_vec[uvp + i + 32] = -cur_synbuf[k + 16 - i]; 400 | u_vec[uvp + i + 48] = -cur_synbuf[k + i]; 401 | } 402 | u_vec[uvp + 16] = 0; 403 | 404 | /* k wraps at the synthesis buffer boundary */ 405 | k = (k + 32) & 511; 406 | uvp += 64; 407 | } 408 | } 409 | 410 | void DewindowOutput(float[] u_vec, float[] samples) 411 | { 412 | for (int i = 0; i < 512; i++) 413 | { 414 | u_vec[i] *= DEWINDOW_TABLE[i]; 415 | } 416 | 417 | for (int i = 0; i < 32; i++) 418 | { 419 | float sum = u_vec[i]; 420 | sum += u_vec[i + (1 << 5)]; 421 | sum += u_vec[i + (2 << 5)]; 422 | sum += u_vec[i + (3 << 5)]; 423 | sum += u_vec[i + (4 << 5)]; 424 | sum += u_vec[i + (5 << 5)]; 425 | sum += u_vec[i + (6 << 5)]; 426 | sum += u_vec[i + (7 << 5)]; 427 | sum += u_vec[i + (8 << 5)]; 428 | sum += u_vec[i + (9 << 5)]; 429 | sum += u_vec[i + (10 << 5)]; 430 | sum += u_vec[i + (11 << 5)]; 431 | sum += u_vec[i + (12 << 5)]; 432 | sum += u_vec[i + (13 << 5)]; 433 | sum += u_vec[i + (14 << 5)]; 434 | sum += u_vec[i + (15 << 5)]; 435 | u_vec[i] = sum; 436 | } 437 | 438 | for (int i = 0; i < 32; i++) 439 | { 440 | samples[i] = u_vec[i]; 441 | } 442 | } 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerDecoderBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a5d192dc216711441815b262642d6a8d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerIDecoder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * NLayer - A C# MPEG1/2/2.5 audio decoder 3 | * 4 | */ 5 | 6 | using System; 7 | 8 | namespace NLayer.Decoder 9 | { 10 | // Layer I is really just a special case of Layer II... 1 granule, 4 allocation bits per subband, 1 scalefactor per active subband, no grouping 11 | // That (of course) means we literally have no logic here 12 | class LayerIDecoder : LayerIIDecoderBase 13 | { 14 | static internal bool GetCRC(MpegFrame frame, ref uint crc) 15 | { 16 | return LayerIIDecoderBase.GetCRC(frame, _rateTable, _allocLookupTable, false, ref crc); 17 | } 18 | 19 | // this is simple: all 32 subbands have a 4-bit allocations, and positive allocation values are {bits per sample} - 1 20 | static readonly int[] _rateTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 21 | 22 | static readonly int[][] _allocLookupTable = { new int[] { 4, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 } }; 23 | 24 | internal LayerIDecoder() : base(_allocLookupTable, 1) { } 25 | 26 | protected override int[] GetRateTable(IMpegFrame frame) 27 | { 28 | return _rateTable; 29 | } 30 | 31 | protected override void ReadScaleFactorSelection(IMpegFrame frame, int[][] scfsi, int channels) 32 | { 33 | // this is a no-op since the base logic uses "2" as the "has energy" marker 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerIDecoder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d3f842ceb8172bd40bf0b1a5ccab8705 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerIIDecoder.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * NLayer - A C# MPEG1/2/2.5 audio decoder 3 | * 4 | */ 5 | 6 | using System; 7 | 8 | namespace NLayer.Decoder 9 | { 10 | // there's not much we have to do here... table selection, granule count, scalefactor selection 11 | class LayerIIDecoder : LayerIIDecoderBase 12 | { 13 | static internal bool GetCRC(MpegFrame frame, ref uint crc) 14 | { 15 | return LayerIIDecoderBase.GetCRC(frame, SelectTable(frame), _allocLookupTable, true, ref crc); 16 | } 17 | 18 | // figure out which rate table to use... basically, high-rate full, high-rate limited, low-rate limited, low-rate minimal, and LSF. 19 | static int[] SelectTable(IMpegFrame frame) 20 | { 21 | var bitRatePerChannel = (frame.BitRate / (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2)) / 1000; 22 | 23 | if (frame.Version == MpegVersion.Version1) 24 | { 25 | if ((bitRatePerChannel >= 56 && bitRatePerChannel <= 80) || (frame.SampleRate == 48000 && bitRatePerChannel >= 56)) 26 | { 27 | return _rateLookupTable[0]; // high-rate, 27 subbands 28 | } 29 | else if (frame.SampleRate != 48000 && bitRatePerChannel >= 96) 30 | { 31 | return _rateLookupTable[1]; // high-rate, 30 subbands 32 | } 33 | else if (frame.SampleRate != 32000 && bitRatePerChannel <= 48) 34 | { 35 | return _rateLookupTable[2]; // low-rate, 8 subbands 36 | } 37 | else 38 | { 39 | return _rateLookupTable[3]; // low-rate, 12 subbands 40 | } 41 | } 42 | else 43 | { 44 | return _rateLookupTable[4]; // lsf, 30 subbands 45 | } 46 | } 47 | 48 | // this table tells us which allocation lookup list to use for each subband 49 | // note that each row has the same number of elements as there are subbands for that type... 50 | static readonly int[][] _rateLookupTable = { 51 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 52 | new int[] { 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, // high-rate, 27 subbands 53 | new int[] { 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // high-rate, 30 subbands 54 | new int[] { 4, 4, 5, 5, 5, 5, 5, 5 }, // low-rate, 7 subbands 55 | new int[] { 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }, // low-rate, 12 subbands 56 | new int[] { 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }, // lsf, 30 subbands 57 | }; 58 | 59 | // this tells the decode logic: a) how many bits per allocation, and b) how many bits per sample for the give allocation value 60 | // if negative, read -x bits and handle as a group 61 | static readonly int[][] _allocLookupTable = { 62 | // bits 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 63 | new int[] { 2, 0, -5, -7, 16 }, // 0 (II) 64 | new int[] { 3, 0, -5, -7, 3,-10, 4, 5, 16 }, // 1 (II) 65 | new int[] { 4, 0, -5, -7, 3,-10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16 }, // 2 (II) 66 | new int[] { 4, 0, -5, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, // 3 (II) 67 | new int[] { 4, 0, -5, -7,-10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, // 4 (II, 4, 4 bits per alloc) 68 | new int[] { 3, 0, -5, -7,-10, 4, 5, 6, 9 }, // 5 (II, 4, 3 bits per alloc) 69 | new int[] { 4, 0, -5, -7, 3,-10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }, // 6 (II) 70 | new int[] { 2, 0, -5, -7, 3 }, // 7 (II, 4, 2 bits per alloc) 71 | }; 72 | 73 | internal LayerIIDecoder() : base(_allocLookupTable, 3) { } 74 | 75 | protected override int[] GetRateTable(IMpegFrame frame) 76 | { 77 | return SelectTable(frame); 78 | } 79 | 80 | protected override void ReadScaleFactorSelection(IMpegFrame frame, int[][] scfsi, int channels) 81 | { 82 | // we'll never have more than 30 active subbands 83 | for (int sb = 0; sb < 30; sb++) 84 | { 85 | for (int ch = 0; ch < channels; ch++) 86 | { 87 | if (scfsi[ch][sb] == 2) 88 | { 89 | scfsi[ch][sb] = frame.ReadBits(2); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerIIDecoder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8929914c526417f4e8ca6a89c6a308af 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerIIDecoderBase.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * NLayer - A C# MPEG1/2/2.5 audio decoder 3 | * 4 | */ 5 | 6 | using System; 7 | 8 | namespace NLayer.Decoder 9 | { 10 | // Layers I & II are basically identical... Layer II adds sample grouping, per subband allocation schemes, and granules 11 | // Because of this fact, we can use the same decoder for both 12 | abstract class LayerIIDecoderBase : LayerDecoderBase 13 | { 14 | protected const int SSLIMIT = 12; 15 | 16 | static protected bool GetCRC(MpegFrame frame, int[] rateTable, int[][] allocLookupTable, bool readScfsiBits, ref uint crc) 17 | { 18 | // ugh... we basically have to re-implement the allocation logic here. 19 | 20 | // keep up with how many active subbands we need to read selection info for 21 | var scfsiBits = 0; 22 | 23 | // only read as many subbands as we actually need; pay attention to the intensity stereo subbands 24 | var subbandCount = rateTable.Length; 25 | var jsbound = subbandCount; 26 | if (frame.ChannelMode == MpegChannelMode.JointStereo) 27 | { 28 | jsbound = frame.ChannelModeExtension * 4 + 4; 29 | } 30 | 31 | // read the full stereo subbands 32 | var channels = frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2; 33 | var sb = 0; 34 | for (; sb < jsbound; sb++) 35 | { 36 | var bits = allocLookupTable[rateTable[sb]][0]; 37 | for (int ch = 0; ch < channels; ch++) 38 | { 39 | var alloc = frame.ReadBits(bits); 40 | if (alloc > 0) scfsiBits += 2; 41 | 42 | MpegFrame.UpdateCRC(alloc, bits, ref crc); 43 | } 44 | } 45 | 46 | // read the intensity stereo subbands 47 | for (; sb < subbandCount; sb++) 48 | { 49 | var bits = allocLookupTable[rateTable[sb]][0]; 50 | 51 | var alloc = frame.ReadBits(bits); 52 | if (alloc > 0) scfsiBits += channels * 2; 53 | 54 | MpegFrame.UpdateCRC(alloc, bits, ref crc); 55 | } 56 | 57 | // finally, read the scalefac selection bits 58 | if (readScfsiBits) 59 | { 60 | while (scfsiBits >= 2) 61 | { 62 | MpegFrame.UpdateCRC(frame.ReadBits(2), 2, ref crc); 63 | scfsiBits -= 2; 64 | } 65 | } 66 | 67 | return true; 68 | } 69 | 70 | #region Lookup Tables 71 | 72 | // this is from the formula: C = 1 / (1 / (1 << (Bits / 2 + Bits % 2 - 1)) + .5f) 73 | // index by real bits (Bits / 2 + Bits % 2 - 1) 74 | static readonly float[] _groupedC = { 0, 0, 1.33333333333f, 1.60000000000f, 1.77777777777f }; 75 | 76 | // these are always -0.5 77 | // index by real bits (Bits / 2 + Bits % 2 - 1) 78 | static readonly float[] _groupedD = { 0, 0, -0.5f, -0.5f, -0.5f }; 79 | 80 | // this is from the formula: 1 / (1 - (1f / (1 << Bits))) 81 | // index by bits 82 | static readonly float[] _C = { 83 | 0.00000000000f, 84 | 0.00000000000f, 1.33333333333f, 1.14285714286f, 1.06666666666f, 1.03225806452f, 1.01587301587f, 1.00787401575f, 1.00392156863f, 85 | 1.00195694716f, 1.00097751711f, 1.00048851979f, 1.00024420024f, 1.00012208522f, 1.00006103888f, 1.00003051851f, 1.00001525902f 86 | }; 87 | 88 | // this is from the formula: 1f / (1 << Bits - 1) - 1 89 | // index by bits 90 | static readonly float[] _D = { 91 | 0.00000000000f - 0f, 92 | 0.00000000000f - 0f, 0.50000000000f - 1f, 0.25000000000f - 1f, 0.12500000000f - 1f, 0.062500000000f - 1f, 0.03125000000f - 1f, 0.01562500000f - 1f, 0.00781250000f - 1f, 93 | 0.00390625000f - 1f, 0.00195312500f - 1f, 0.00097656250f - 1f, 0.00048828125f - 1f, 0.000244140630f - 1f, 0.00012207031f - 1f, 0.00006103516f - 1f, 0.00003051758f - 1f 94 | }; 95 | 96 | // this is from a (really annoying) formula: 97 | // x = Math.Pow(4, 1 / ((2 << (idx % 3) + 1) - (idx % 3))) / (1 << (idx / 3)) 98 | // Basically... 99 | // [0] = Math.Pow(4, 1 / 2), [1] = Math.Pow(4, 1 / 3), [2] = Math.Pow(4, 1 / 6) 100 | // For every remaining element, calculate (in order): [idx] = [idx - 3] / 2 101 | static readonly float[] _denormalMultiplier = { 102 | 2.00000000000000f, 1.58740105196820f, 1.25992104989487f, 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, 0.39685026299205f, 103 | 0.31498026247372f, 0.25000000000000f, 0.19842513149602f, 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, 0.06250000000000f, 104 | 0.04960628287401f, 0.03937253280921f, 0.03125000000000f, 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, 0.00984313320230f, 105 | 0.00781250000000f, 0.00620078535925f, 0.00492156660115f, 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, 0.00155019633981f, 106 | 0.00123039165029f, 0.00097656250000f, 0.00077509816991f, 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, 0.00024414062500f, 107 | 0.00019377454248f, 0.00015379895629f, 0.00012207031250f, 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, 0.00003844973907f, 108 | 0.00003051757813f, 0.00002422181781f, 0.00001922486954f, 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, 0.00000605545445f, 109 | 0.00000480621738f, 0.00000381469727f, 0.00000302772723f, 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f, 0.00000095367432f 110 | }; 111 | 112 | #endregion 113 | 114 | int _channels, _jsbound, _granuleCount; 115 | int[][] _allocLookupTable, _scfsi, _samples; 116 | int[][][] _scalefac; 117 | float[] _polyPhaseBuf; 118 | 119 | int[][] _allocation; 120 | 121 | protected LayerIIDecoderBase(int[][] allocLookupTable, int granuleCount) 122 | : base() 123 | { 124 | _allocLookupTable = allocLookupTable; 125 | _granuleCount = granuleCount; 126 | 127 | _allocation = new int[][] { new int[SBLIMIT], new int[SBLIMIT] }; 128 | _scfsi = new int[][] { new int[SBLIMIT], new int[SBLIMIT] }; 129 | _samples = new int[][] { new int[SBLIMIT * SSLIMIT * _granuleCount], new int[SBLIMIT * SSLIMIT * _granuleCount] }; 130 | 131 | // NB: ReadScaleFactors(...) requires all three granules, even in Layer I 132 | _scalefac = new int[][][] { new int[3][], new int[3][] }; 133 | for (int i = 0; i < 3; i++) 134 | { 135 | _scalefac[0][i] = new int[SBLIMIT]; 136 | _scalefac[1][i] = new int[SBLIMIT]; 137 | } 138 | 139 | _polyPhaseBuf = new float[SBLIMIT]; 140 | } 141 | 142 | internal override int DecodeFrame(IMpegFrame frame, float[] ch0, float[] ch1) 143 | { 144 | InitFrame(frame); 145 | 146 | var rateTable = GetRateTable(frame); 147 | 148 | ReadAllocation(frame, rateTable); 149 | 150 | for (int i = 0; i < _scfsi[0].Length; i++) 151 | { 152 | // Since Layer II has to know which subbands have energy, we use the "Layer I valid" selection to mark that energy is present. 153 | // That way Layer I doesn't have to do anything else. 154 | _scfsi[0][i] = _allocation[0][i] != 0 ? 2 : -1; 155 | _scfsi[1][i] = _allocation[1][i] != 0 ? 2 : -1; 156 | } 157 | ReadScaleFactorSelection(frame, _scfsi, _channels); 158 | 159 | ReadScaleFactors(frame); 160 | 161 | ReadSamples(frame); 162 | 163 | return DecodeSamples(ch0, ch1); 164 | } 165 | 166 | // this just reads the channel mode and set a few flags 167 | void InitFrame(IMpegFrame frame) 168 | { 169 | switch (frame.ChannelMode) 170 | { 171 | case MpegChannelMode.Mono: 172 | _channels = 1; 173 | _jsbound = SBLIMIT; 174 | break; 175 | case MpegChannelMode.JointStereo: 176 | _channels = 2; 177 | _jsbound = frame.ChannelModeExtension * 4 + 4; 178 | break; 179 | default: 180 | _channels = 2; 181 | _jsbound = SBLIMIT; 182 | break; 183 | } 184 | } 185 | 186 | abstract protected int[] GetRateTable(IMpegFrame frame); 187 | 188 | void ReadAllocation(IMpegFrame frame, int[] rateTable) 189 | { 190 | var _subBandCount = rateTable.Length; 191 | if (_jsbound > _subBandCount) _jsbound = _subBandCount; 192 | 193 | Array.Clear(_allocation[0], 0, SBLIMIT); 194 | Array.Clear(_allocation[1], 0, SBLIMIT); 195 | 196 | int sb = 0; 197 | for (; sb < _jsbound; sb++) 198 | { 199 | var table = _allocLookupTable[rateTable[sb]]; 200 | var bits = table[0]; 201 | for (int ch = 0; ch < _channels; ch++) 202 | { 203 | _allocation[ch][sb] = table[frame.ReadBits(bits) + 1]; 204 | } 205 | } 206 | for (; sb < _subBandCount; sb++) 207 | { 208 | var table = _allocLookupTable[rateTable[sb]]; 209 | _allocation[0][sb] = _allocation[1][sb] = table[frame.ReadBits(table[0]) + 1]; 210 | } 211 | } 212 | 213 | abstract protected void ReadScaleFactorSelection(IMpegFrame frame, int[][] scfsi, int channels); 214 | 215 | void ReadScaleFactors(IMpegFrame frame) 216 | { 217 | for (int sb = 0; sb < SBLIMIT; sb++) 218 | { 219 | for (int ch = 0; ch < _channels; ch++) 220 | { 221 | switch (_scfsi[ch][sb]) 222 | { 223 | case 0: 224 | // all three 225 | _scalefac[ch][0][sb] = frame.ReadBits(6); 226 | _scalefac[ch][1][sb] = frame.ReadBits(6); 227 | _scalefac[ch][2][sb] = frame.ReadBits(6); 228 | break; 229 | case 1: 230 | // only two (2 = 1) 231 | _scalefac[ch][0][sb] = 232 | _scalefac[ch][1][sb] = frame.ReadBits(6); 233 | _scalefac[ch][2][sb] = frame.ReadBits(6); 234 | break; 235 | case 2: 236 | // only one (3 = 2 = 1) 237 | _scalefac[ch][0][sb] = 238 | _scalefac[ch][1][sb] = 239 | _scalefac[ch][2][sb] = frame.ReadBits(6); 240 | break; 241 | case 3: 242 | // only two (3 = 2) 243 | _scalefac[ch][0][sb] = frame.ReadBits(6); 244 | _scalefac[ch][1][sb] = 245 | _scalefac[ch][2][sb] = frame.ReadBits(6); 246 | break; 247 | default: 248 | // none 249 | _scalefac[ch][0][sb] = 63; 250 | _scalefac[ch][1][sb] = 63; 251 | _scalefac[ch][2][sb] = 63; 252 | break; 253 | } 254 | } 255 | } 256 | } 257 | 258 | void ReadSamples(IMpegFrame frame) 259 | { 260 | // load in all the data for this frame (1152 samples in this case) 261 | // NB: we flatten these into output order 262 | for (int ss = 0, idx = 0; ss < SSLIMIT; ss++, idx += SBLIMIT * (_granuleCount - 1)) 263 | { 264 | for (int sb = 0; sb < SBLIMIT; sb++, idx++) 265 | { 266 | for (int ch = 0; ch < _channels; ch++) 267 | { 268 | if (ch == 0 || sb < _jsbound) 269 | { 270 | var alloc = _allocation[ch][sb]; 271 | if (alloc != 0) 272 | { 273 | if (alloc < 0) 274 | { 275 | // grouping (Layer II only, so we don't have to play with the granule count) 276 | var val = frame.ReadBits(-alloc); 277 | var levels = (1 << (-alloc / 2 + -alloc % 2 - 1)) + 1; 278 | 279 | _samples[ch][idx] = val % levels; 280 | val /= levels; 281 | _samples[ch][idx + SBLIMIT] = val % levels; 282 | _samples[ch][idx + SBLIMIT * 2] = val / levels; 283 | } 284 | else 285 | { 286 | // non-grouping 287 | for (int gr = 0; gr < _granuleCount; gr++) 288 | { 289 | _samples[ch][idx + SBLIMIT * gr] = frame.ReadBits(alloc); 290 | } 291 | } 292 | } 293 | else 294 | { 295 | // no energy... zero out the samples 296 | for (int gr = 0; gr < _granuleCount; gr++) 297 | { 298 | _samples[ch][idx + SBLIMIT * gr] = 0; 299 | } 300 | } 301 | } 302 | else 303 | { 304 | // copy chan 0 to chan 1 305 | for (int gr = 0; gr < _granuleCount; gr++) 306 | { 307 | _samples[1][idx + SBLIMIT * gr] = _samples[0][idx + SBLIMIT * gr]; 308 | } 309 | } 310 | } 311 | } 312 | } 313 | } 314 | 315 | int DecodeSamples(float[] ch0, float[] ch1) 316 | { 317 | // do our stereo mode setup 318 | var chanBufs = new float[2][]; 319 | var startChannel = 0; 320 | var endChannel = _channels - 1; 321 | if (_channels == 1 || StereoMode == StereoMode.LeftOnly) 322 | { 323 | chanBufs[0] = ch0; 324 | endChannel = 0; 325 | } 326 | else if (StereoMode == StereoMode.RightOnly) 327 | { 328 | chanBufs[1] = ch0; // this is correct... if there's only a single channel output, it goes in channel 0's buffer 329 | startChannel = 1; 330 | } 331 | else // MpegStereoMode.Both or StereoMode.DownmixToMono 332 | { 333 | chanBufs[0] = ch0; 334 | chanBufs[1] = ch1; 335 | } 336 | 337 | int idx = 0; 338 | for (int ch = startChannel; ch <= endChannel; ch++) 339 | { 340 | idx = 0; 341 | for (int gr = 0; gr < _granuleCount; gr++) 342 | { 343 | for (int ss = 0; ss < SSLIMIT; ss++) 344 | { 345 | for (int sb = 0; sb < SBLIMIT; sb++, idx++) 346 | { 347 | // do the dequant and the denorm; output to _polyPhaseBuf 348 | // NB: Layers I & II use the same algorithm here... Grouping changes the bit counts, but doesn't change the algo 349 | // - Up to 65534 possible values (65535 does not appear to be usable) 350 | // - All values can be handled with 16-bit logic as long as the correct C and D constants are used 351 | // - Make sure to normalize each sample to 16 bits! 352 | 353 | var alloc = _allocation[ch][sb]; 354 | if (alloc != 0) 355 | { 356 | float[] c, d; 357 | if (alloc < 0) 358 | { 359 | alloc = -alloc / 2 + -alloc % 2 - 1; 360 | c = _groupedC; 361 | d = _groupedD; 362 | } 363 | else 364 | { 365 | c = _C; 366 | d = _D; 367 | } 368 | 369 | // read sample; normalize, scale & center to [-0.999984741f..0.999984741f]; apply scalefactor 370 | _polyPhaseBuf[sb] = c[alloc] * ((_samples[ch][idx] << (16 - alloc)) / 32768f + d[alloc]) * _denormalMultiplier[_scalefac[ch][gr][sb]]; 371 | } 372 | else 373 | { 374 | // no transmitted energy... 375 | _polyPhaseBuf[sb] = 0f; 376 | } 377 | } 378 | 379 | // do the polyphase output for this channel, section, and granule 380 | base.InversePolyPhase(ch, _polyPhaseBuf); 381 | Array.Copy(_polyPhaseBuf, 0, chanBufs[ch], idx - SBLIMIT, SBLIMIT); 382 | } 383 | } 384 | } 385 | 386 | if (_channels == 2 && StereoMode == NLayer.StereoMode.DownmixToMono) 387 | { 388 | for (int i = 0; i < idx; i++) 389 | { 390 | ch0[i] = (ch0[i] + ch1[i]) / 2; 391 | } 392 | } 393 | 394 | return idx; 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerIIDecoderBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c2712fedc82689349a61da57e56203e5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/LayerIIIDecoder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 99407b136754a304a8077e17cf5fe083 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/MpegFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace NLayer.Decoder 6 | { 7 | class MpegFrame : FrameBase, IMpegFrame 8 | { 9 | static readonly int[][][] _bitRateTable = 10 | { 11 | new int[][] 12 | { 13 | new int[] { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 }, 14 | new int[] { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 }, 15 | new int[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 } 16 | }, 17 | new int[][] 18 | { 19 | new int[] { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 }, 20 | new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }, 21 | new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 } 22 | }, 23 | }; 24 | 25 | internal static MpegFrame TrySync(uint syncMark) 26 | { 27 | if ((syncMark & 0xFFE00000) == 0xFFE00000 // frame sync 28 | && (syncMark & 0x00180000) != 0x00080000 // MPEG version != reserved 29 | && (syncMark & 0x00060000) != 0x00000000 // layer version != reserved 30 | && (syncMark & 0x0000F000) != 0x0000F000 // bitrate != bad 31 | && (syncMark & 0x00000C00) != 0x00000C00 // sample rate != reserved 32 | ) 33 | { 34 | // now check the stereo modes 35 | switch ((syncMark >> 4) & 0xF) 36 | { 37 | case 0x0: // stereo 38 | case 0x4: // joint stereo 39 | case 0x5: // " 40 | case 0x6: // " 41 | case 0x7: // " 42 | case 0x8: // dual channel 43 | case 0xC: // mono 44 | return new MpegFrame { _syncBits = (int)syncMark }; 45 | } 46 | } 47 | 48 | return null; 49 | } 50 | 51 | // base: 2xIntPtr, 1x8, 1x4 52 | // total: 3xIntPtr, 4x8, 4x4 53 | // Long Mode: 72 bytes per instance 54 | // Prot Mode: 60 bytes per instance 55 | 56 | // IntPtr 57 | internal MpegFrame Next; 58 | // 4 59 | internal int Number; 60 | 61 | // 4 62 | int _syncBits; 63 | 64 | // 8 65 | int _readOffset, _bitsRead; 66 | // 8 67 | ulong _bitBucket = 0UL; 68 | // 8 69 | long _offset; 70 | // 4 71 | bool _isMuted; 72 | 73 | MpegFrame() 74 | { 75 | 76 | } 77 | 78 | protected override int Validate() 79 | { 80 | // TrySync has already validated version, layer, bitrate, and samplerate 81 | 82 | // if layer2, we have to verify the channel mode is valid for the bitrate selected 83 | if (Layer == MpegLayer.LayerII) 84 | { 85 | switch (BitRate) 86 | { 87 | case 32000: 88 | case 48000: 89 | case 56000: 90 | case 80000: 91 | // don't allow anything except mono 92 | if (ChannelMode != MpegChannelMode.Mono) return -1; 93 | break; 94 | case 224000: 95 | case 256000: 96 | case 320000: 97 | case 384000: 98 | // don't allow mono 99 | if (ChannelMode == MpegChannelMode.Mono) return -1; 100 | break; 101 | } 102 | } 103 | 104 | // calculate the frame's length 105 | int frameSize; 106 | if (BitRateIndex > 0) 107 | { 108 | if (Layer == MpegLayer.LayerI) 109 | { 110 | frameSize = (12 * BitRate / SampleRate + Padding) * 4; 111 | } 112 | else 113 | { 114 | frameSize = 144 * BitRate / SampleRate + Padding; 115 | } 116 | } 117 | else 118 | { 119 | // "free" frame... we have to calculate it later 120 | frameSize = _readOffset + GetSideDataSize() + Padding; // we know the frame will be at least this big... 121 | } 122 | 123 | // now check the crc if one is present 124 | if (HasCrc) 125 | { 126 | // prep for CRC reading 127 | _readOffset = 4 + (HasCrc ? 2 : 0); 128 | 129 | // check the CRC 130 | if (!ValidateCRC()) 131 | { 132 | // mute this frame 133 | _isMuted = true; 134 | return 6; // header + crc... force the reader to re-sync 135 | } 136 | } 137 | 138 | // prep for reading 139 | Reset(); 140 | 141 | // finally, let our caller know how big this frame is (including the sync header) 142 | return frameSize; 143 | } 144 | 145 | internal int GetSideDataSize() 146 | { 147 | switch (Layer) 148 | { 149 | case MpegLayer.LayerI: 150 | if (ChannelMode == MpegChannelMode.Mono) 151 | { 152 | // mono 153 | return 16; 154 | } 155 | else if (ChannelMode == MpegChannelMode.Stereo || ChannelMode == MpegChannelMode.DualChannel) 156 | { 157 | // full stereo / dual channel 158 | return 32; 159 | } 160 | else 161 | { 162 | // joint stereo... ugh... 163 | switch (ChannelModeExtension) 164 | { 165 | case 0: 166 | return 18; 167 | case 1: 168 | return 20; 169 | case 2: 170 | return 22; 171 | case 3: 172 | return 24; 173 | } 174 | } 175 | break; 176 | case MpegLayer.LayerII: 177 | return 0; 178 | case MpegLayer.LayerIII: 179 | if (ChannelMode == MpegChannelMode.Mono && Version >= MpegVersion.Version2) 180 | { 181 | return 9; 182 | } 183 | else if (ChannelMode != MpegChannelMode.Mono && Version < MpegVersion.Version2) 184 | { 185 | return 32; 186 | } 187 | else 188 | { 189 | return 17; 190 | } 191 | } 192 | 193 | return 0; 194 | } 195 | 196 | bool ValidateCRC() 197 | { 198 | var crc = 0xFFFFU; 199 | 200 | // process the common bits... 201 | UpdateCRC(_syncBits, 16, ref crc); 202 | 203 | var apply = false; 204 | switch (Layer) 205 | { 206 | case MpegLayer.LayerI: 207 | apply = LayerIDecoder.GetCRC(this, ref crc); 208 | break; 209 | case MpegLayer.LayerII: 210 | apply = LayerIIDecoder.GetCRC(this, ref crc); 211 | break; 212 | case MpegLayer.LayerIII: 213 | apply = LayerIIIDecoder.GetCRC(this, ref crc); 214 | break; 215 | } 216 | 217 | if (apply) 218 | { 219 | var checkCrc = ReadByte(4) << 8 | ReadByte(5); 220 | return checkCrc == crc; 221 | } 222 | 223 | return true; 224 | } 225 | 226 | static internal void UpdateCRC(int data, int length, ref uint crc) 227 | { 228 | var masking = 1U << length; 229 | while ((masking >>= 1) != 0) 230 | { 231 | var carry = crc & 0x8000; 232 | crc <<= 1; 233 | if ((carry == 0) ^ ((data & masking) == 0)) 234 | { 235 | crc ^= 0x8005; 236 | } 237 | } 238 | crc &= 0xFFFF; 239 | } 240 | 241 | internal VBRInfo ParseVBR() 242 | { 243 | var buf = new byte[4]; 244 | 245 | // Xing first 246 | int offset; 247 | if (Version == MpegVersion.Version1 && ChannelMode != MpegChannelMode.Mono) 248 | { 249 | offset = 32 + 4; 250 | } 251 | else if (Version > MpegVersion.Version1 && ChannelMode == MpegChannelMode.Mono) 252 | { 253 | offset = 9 + 4; 254 | } 255 | else 256 | { 257 | offset = 17 + 4; 258 | } 259 | 260 | if (Read(offset, buf) != 4) return null; 261 | if (buf[0] == 'X' && buf[1] == 'i' && buf[2] == 'n' && buf[3] == 'g' 262 | || buf[0] == 'I' && buf[1] == 'n' && buf[2] == 'f' && buf[3] == 'o') 263 | { 264 | return ParseXing(offset + 4); 265 | } 266 | 267 | // then VBRI (kinda rare) 268 | if (Read(36, buf) != 4) return null; 269 | if (buf[0] == 'V' && buf[1] == 'B' && buf[2] == 'R' && buf[3] == 'I') 270 | { 271 | return ParseVBRI(); 272 | } 273 | 274 | return null; 275 | } 276 | 277 | VBRInfo ParseXing(int offset) 278 | { 279 | VBRInfo info = new VBRInfo(); 280 | info.Channels = Channels; 281 | info.SampleRate = SampleRate; 282 | info.SampleCount = SampleCount; 283 | 284 | var buf = new byte[100]; 285 | if (Read(offset, buf, 0, 4) != 4) return null; 286 | offset += 4; 287 | 288 | var flags = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; 289 | 290 | // frame count 291 | if ((flags & 0x1) != 0) 292 | { 293 | if (Read(offset, buf, 0, 4) != 4) return null; 294 | offset += 4; 295 | info.VBRFrames = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; 296 | } 297 | 298 | // byte count 299 | if ((flags & 0x2) != 0) 300 | { 301 | if (Read(offset, buf, 0, 4) != 4) return null; 302 | offset += 4; 303 | info.VBRBytes = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; 304 | } 305 | 306 | // TOC 307 | if ((flags & 0x4) != 0) 308 | { 309 | // we're not using the TOC, so just discard it 310 | if (Read(offset, buf) != 100) return null; 311 | offset += 100; 312 | } 313 | 314 | // scale 315 | if ((flags & 0x8) != 0) 316 | { 317 | if (Read(offset, buf, 0, 4) != 4) return null; 318 | offset += 4; 319 | info.VBRQuality = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; 320 | } 321 | 322 | //// now look for a LAME header (note: if it isn't found, it doesn't fail the VBR parse) 323 | //do 324 | //{ 325 | // if (Read(offset, buf, 0, 20) != 20) break; 326 | // offset += 20; 327 | 328 | // // LAME tag revision: only 0 and 1 are valid 329 | // if ((buf[9] & 0xF0) > 0x10) break; 330 | 331 | // // VBR mode: 0-6, 8 & 9 are valid 332 | // var mode = buf[9] & 0xF; 333 | // if (mode == 7 || mode > 9) break; 334 | 335 | // // Lowpass filter value 336 | // var lowpass = buf[10] / 100.0; 337 | 338 | // // Replay Gain 339 | // var rgPeak = BitConverter.ToSingle(buf, 11); 340 | // var rgGainRadio = buf[15] << 8 | buf[16]; 341 | // var rgGain = buf[17] << 8 | buf[18]; 342 | //} while (false); 343 | 344 | return info; 345 | } 346 | 347 | VBRInfo ParseVBRI() 348 | { 349 | VBRInfo info = new VBRInfo(); 350 | info.Channels = Channels; 351 | info.SampleRate = SampleRate; 352 | info.SampleCount = SampleCount; 353 | 354 | // VBRI is "fixed" size... Yay. :) 355 | var buf = new byte[26]; 356 | if (Read(36, buf) != 26) return null; 357 | 358 | // Version 359 | var version = buf[4] << 8 | buf[5]; 360 | 361 | // Delay 362 | info.VBRDelay = buf[6] << 8 | buf[7]; 363 | 364 | // Quality 365 | info.VBRQuality = buf[8] << 8 | buf[9]; 366 | 367 | // Bytes 368 | info.VBRBytes = buf[10] << 24 | buf[11] << 16 | buf[12] << 8 | buf[13]; 369 | 370 | // Frames 371 | info.VBRFrames = buf[14] << 24 | buf[15] << 16 | buf[16] << 8 | buf[17]; 372 | 373 | // TOC 374 | // entries 375 | var tocEntries = buf[18] << 8 | buf[19]; 376 | var tocScale = buf[20] << 8 | buf[21]; 377 | var tocEntrySize = buf[22] << 8 | buf[23]; 378 | var tocFramesPerEntry = buf[24] << 8 | buf[25]; 379 | var tocSize = tocEntries * tocEntrySize; 380 | 381 | var toc = new byte[tocSize]; 382 | if (Read(62, toc) != tocSize) return null; 383 | 384 | return info; 385 | } 386 | 387 | public int FrameLength 388 | { 389 | get { return base.Length; } 390 | } 391 | 392 | public MpegVersion Version 393 | { 394 | get 395 | { 396 | switch ((_syncBits >> 19) & 3) 397 | { 398 | case 0: 399 | return MpegVersion.Version25; 400 | case 2: 401 | return MpegVersion.Version2; 402 | case 3: 403 | return MpegVersion.Version1; 404 | } 405 | return MpegVersion.Unknown; 406 | } 407 | } 408 | public MpegLayer Layer 409 | { 410 | get 411 | { 412 | // the order is backwards, and "0" is invalid 413 | return (MpegLayer)((4 - ((_syncBits >> 17) & 3)) % 4); 414 | } 415 | } 416 | public bool HasCrc 417 | { 418 | get { return (_syncBits & 0x10000) == 0; } 419 | } 420 | public int BitRate 421 | { 422 | get 423 | { 424 | if (BitRateIndex > 0) 425 | { 426 | return _bitRateTable[(int)Version / 10 - 1][(int)Layer - 1][BitRateIndex] * 1000; 427 | } 428 | else 429 | { 430 | // bitrate is always an even multiple of 1000, so round 431 | return ((((FrameLength * 8) * SampleRate) / SampleCount + 499) + 500) / 1000 * 1000; 432 | } 433 | } 434 | } 435 | public int BitRateIndex 436 | { 437 | get { return (_syncBits >> 12) & 0xF; } 438 | } 439 | public int SampleRate 440 | { 441 | get 442 | { 443 | int sr; 444 | switch (SampleRateIndex) 445 | { 446 | case 0: sr = 44100; break; 447 | case 1: sr = 48000; break; 448 | case 2: sr = 32000; break; 449 | default: sr = 0; break; 450 | } 451 | if (Version > MpegVersion.Version1) 452 | { 453 | if (Version == MpegVersion.Version25) 454 | { 455 | sr /= 4; 456 | } 457 | else 458 | { 459 | sr /= 2; 460 | } 461 | } 462 | return sr; 463 | } 464 | } 465 | public int SampleRateIndex 466 | { 467 | get { return (_syncBits >> 10) & 0x3; } 468 | } 469 | private int Padding 470 | { 471 | get { return (_syncBits >> 9) & 0x1; } 472 | } 473 | public MpegChannelMode ChannelMode 474 | { 475 | get { return (MpegChannelMode)((_syncBits >> 6) & 0x3); } 476 | } 477 | public int ChannelModeExtension 478 | { 479 | get { return (_syncBits >> 4) & 0x3; } 480 | } 481 | internal int Channels 482 | { 483 | get { return (ChannelMode == MpegChannelMode.Mono ? 1 : 2); } 484 | } 485 | public bool IsCopyrighted 486 | { 487 | get { return (_syncBits & 0x8) == 0x8; } 488 | } 489 | internal bool IsOriginal 490 | { 491 | get { return (_syncBits & 0x4) == 0x4; } 492 | } 493 | internal int EmphasisMode 494 | { 495 | get { return (_syncBits & 0x3); } 496 | } 497 | public bool IsCorrupted 498 | { 499 | get { return _isMuted; } 500 | } 501 | 502 | public int SampleCount 503 | { 504 | get 505 | { 506 | if (Layer == MpegLayer.LayerI) return 384; 507 | if (Layer == MpegLayer.LayerIII && Version > MpegVersion.Version1) return 576; 508 | return 1152; 509 | } 510 | } 511 | internal long SampleOffset 512 | { 513 | get { return _offset; } 514 | set { _offset = value; } 515 | } 516 | 517 | 518 | public void Reset() 519 | { 520 | _readOffset = 4 + (HasCrc ? 2 : 0); 521 | _bitBucket = 0UL; 522 | _bitsRead = 0; 523 | } 524 | 525 | public int ReadBits(int bitCount) 526 | { 527 | if (bitCount < 1 || bitCount > 32) throw new ArgumentOutOfRangeException("bitCount"); 528 | if (_isMuted) return 0; 529 | 530 | while (_bitsRead < bitCount) 531 | { 532 | var b = ReadByte(_readOffset); 533 | if (b == -1) throw new System.IO.EndOfStreamException(); 534 | 535 | ++_readOffset; 536 | 537 | _bitBucket <<= 8; 538 | _bitBucket |= (byte)(b & 0xFF); 539 | _bitsRead += 8; 540 | } 541 | 542 | var temp = (int)((_bitBucket >> (_bitsRead - bitCount)) & ((1UL << bitCount) - 1)); 543 | _bitsRead -= bitCount; 544 | return temp; 545 | } 546 | 547 | #if DEBUG 548 | public override string ToString() 549 | { 550 | // version 551 | var sb = new StringBuilder("MPEG"); 552 | switch (Version) 553 | { 554 | case MpegVersion.Version1: sb.Append("1"); break; 555 | case MpegVersion.Version2: sb.Append("2"); break; 556 | case MpegVersion.Version25: sb.Append("2.5"); break; 557 | } 558 | 559 | // layer 560 | sb.Append(" Layer "); 561 | switch (Layer) 562 | { 563 | case MpegLayer.LayerI: sb.Append("I"); break; 564 | case MpegLayer.LayerII: sb.Append("II"); break; 565 | case MpegLayer.LayerIII: sb.Append("III"); break; 566 | } 567 | 568 | // bitrate 569 | sb.AppendFormat(" {0} kbps ", BitRate / 1000); 570 | 571 | // channel mode 572 | switch (ChannelMode) 573 | { 574 | case MpegChannelMode.Stereo: 575 | sb.Append("Stereo"); 576 | break; 577 | case MpegChannelMode.JointStereo: 578 | sb.Append("Joint Stereo"); 579 | switch (ChannelModeExtension) 580 | { 581 | case 1: sb.Append(" (I)"); break; 582 | case 2: sb.Append(" (M/S)"); break; 583 | case 3: sb.Append(" (M/S,I)"); break; 584 | } 585 | break; 586 | case MpegChannelMode.DualChannel: 587 | sb.Append("Dual Channel"); 588 | break; 589 | case MpegChannelMode.Mono: 590 | sb.Append("Mono"); 591 | break; 592 | } 593 | 594 | // sample rate 595 | sb.AppendFormat(" {0} KHz", (float)SampleRate / 1000); 596 | 597 | var flagList = new List(); 598 | // protection 599 | if (HasCrc) flagList.Add("CRC"); 600 | 601 | // copyright 602 | if (IsCopyrighted) flagList.Add("Copyright"); 603 | 604 | // original 605 | if (IsOriginal) flagList.Add("Original"); 606 | 607 | // emphasis 608 | switch (EmphasisMode) 609 | { 610 | case 1: 611 | flagList.Add("50/15 ms"); 612 | break; 613 | case 2: 614 | flagList.Add("Invalid Emphasis"); 615 | break; 616 | case 3: 617 | flagList.Add("CCIT J.17"); 618 | break; 619 | } 620 | 621 | if (flagList.Count > 0) 622 | { 623 | sb.AppendFormat(" ({0})", string.Join(",", flagList.ToArray())); 624 | } 625 | 626 | return sb.ToString(); 627 | } 628 | #endif 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/MpegFrame.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ad9a27822246ed948990a8d6c4bbc456 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/MpegStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace NLayer.Decoder 5 | { 6 | class MpegStreamReader 7 | { 8 | ID3Frame _id3Frame, _id3v1Frame; 9 | RiffHeaderFrame _riffHeaderFrame; 10 | 11 | VBRInfo _vbrInfo; 12 | MpegFrame _first, _current, _last, _lastFree; 13 | 14 | long _readOffset, _eofOffset; 15 | Stream _source; 16 | bool _canSeek, _endFound, _mixedFrameSize; 17 | object _readLock = new object(); 18 | object _frameLock = new object(); 19 | 20 | internal MpegStreamReader(Stream source) 21 | { 22 | _source = source; 23 | _canSeek = source.CanSeek; 24 | _readOffset = 0L; 25 | _eofOffset = long.MaxValue; 26 | 27 | // find the first Mpeg frame 28 | var frame = FindNextFrame(); 29 | while (frame != null && !(frame is MpegFrame)) 30 | { 31 | frame = FindNextFrame(); 32 | } 33 | 34 | // if we still don't have a frame, we never sync'ed 35 | if (frame == null) throw new InvalidDataException("Not a valid MPEG file!"); 36 | 37 | // the very next frame "should be" an mpeg frame 38 | frame = FindNextFrame(); 39 | 40 | // if not, it's not a valid file 41 | if (frame == null || !(frame is MpegFrame)) throw new InvalidDataException("Not a valid MPEG file!"); 42 | 43 | // seek to the first frame 44 | _current = _first; 45 | } 46 | 47 | FrameBase FindNextFrame() 48 | { 49 | // if we've found the end, don't bother looking for anything else 50 | if (_endFound) return null; 51 | 52 | var freeFrame = _lastFree; 53 | var lastFrameStart = _readOffset; 54 | 55 | lock (_frameLock) 56 | { 57 | // read 3 bytes 58 | var syncBuf = new byte[4]; 59 | try 60 | { 61 | if (Read(_readOffset, syncBuf, 0, 4) == 4) 62 | { 63 | // now loop until a frame is found 64 | do 65 | { 66 | var sync = (uint)(syncBuf[0] << 24 | syncBuf[1] << 16 | syncBuf[2] << 8 | syncBuf[3]); 67 | 68 | lastFrameStart = _readOffset; 69 | 70 | // try ID3 first (for v2 frames) 71 | if (_id3Frame == null) 72 | { 73 | var f = ID3Frame.TrySync(sync); 74 | if (f != null) 75 | { 76 | if (f.Validate(_readOffset, this)) 77 | { 78 | if (!_canSeek) f.SaveBuffer(); 79 | 80 | _readOffset += f.Length; 81 | DiscardThrough(_readOffset, true); 82 | 83 | return _id3Frame = f; 84 | } 85 | } 86 | } 87 | 88 | // now look for a RIFF header 89 | if (_first == null && _riffHeaderFrame == null) 90 | { 91 | var f = RiffHeaderFrame.TrySync(sync); 92 | if (f != null) 93 | { 94 | if (f.Validate(_readOffset, this)) 95 | { 96 | _readOffset += f.Length; 97 | DiscardThrough(_readOffset, true); 98 | 99 | return _riffHeaderFrame = f; 100 | } 101 | } 102 | } 103 | 104 | // finally, just try for an MPEG frame 105 | var frame = MpegFrame.TrySync(sync); 106 | if (frame != null) 107 | { 108 | if (frame.Validate(_readOffset, this) 109 | && !(freeFrame != null 110 | && (frame.Layer != freeFrame.Layer 111 | || frame.Version != freeFrame.Version 112 | || frame.SampleRate != freeFrame.SampleRate 113 | || frame.BitRateIndex > 0 114 | ) 115 | ) 116 | ) 117 | { 118 | if (!_canSeek) 119 | { 120 | frame.SaveBuffer(); 121 | DiscardThrough(_readOffset + frame.FrameLength, true); 122 | } 123 | 124 | _readOffset += frame.FrameLength; 125 | 126 | if (_first == null) 127 | { 128 | if (_vbrInfo == null && (_vbrInfo = frame.ParseVBR()) != null) 129 | { 130 | return FindNextFrame(); 131 | } 132 | else 133 | { 134 | frame.Number = 0; 135 | _first = _last = frame; 136 | } 137 | } 138 | else 139 | { 140 | if (frame.SampleCount != _first.SampleCount) 141 | { 142 | _mixedFrameSize = true; 143 | } 144 | 145 | frame.SampleOffset = _last.SampleCount + _last.SampleOffset; 146 | frame.Number = _last.Number + 1; 147 | _last = (_last.Next = frame); 148 | } 149 | 150 | if (frame.BitRateIndex == 0) 151 | { 152 | _lastFree = frame; 153 | } 154 | 155 | return frame; 156 | } 157 | } 158 | 159 | // if we've read MPEG frames and can't figure out what frame type we have, try looking for a new ID3 tag 160 | if (_last != null) 161 | { 162 | var f = ID3Frame.TrySync(sync); 163 | if (f != null) 164 | { 165 | if (f.Validate(_readOffset, this)) 166 | { 167 | if (!_canSeek) f.SaveBuffer(); 168 | 169 | // if it's a v1 tag, go ahead and parse it 170 | if (f.Version == 1) 171 | { 172 | _id3v1Frame = f; 173 | } 174 | else 175 | { 176 | // grrr... the ID3 2.4 spec says tags can be anywhere in the file and that later tags can override earlier ones... boo 177 | _id3Frame.Merge(f); 178 | } 179 | 180 | _readOffset += f.Length; 181 | DiscardThrough(_readOffset, true); 182 | 183 | return f; 184 | } 185 | } 186 | } 187 | 188 | // well, we didn't find anything, so rinse and repeat with the next byte 189 | ++_readOffset; 190 | if (_first == null || !_canSeek) DiscardThrough(_readOffset, true); 191 | Buffer.BlockCopy(syncBuf, 1, syncBuf, 0, 3); 192 | } while (Read(_readOffset + 3, syncBuf, 3, 1) == 1); 193 | } 194 | 195 | // move the "end of frame" marker for the last free format frame (in case we have one) 196 | // this is because we don't include the last four bytes otherwise 197 | lastFrameStart += 4; 198 | 199 | _endFound = true; 200 | return null; 201 | } 202 | finally 203 | { 204 | if (freeFrame != null) 205 | { 206 | freeFrame.Length = (int)(lastFrameStart - freeFrame.Offset); 207 | 208 | if (!_canSeek) 209 | { 210 | // gotta finish filling the buffer!! 211 | throw new InvalidOperationException("Free frames cannot be read properly from forward-only streams!"); 212 | } 213 | 214 | // if _lastFree hasn't changed (we got a non-MPEG frame), clear it out 215 | if (_lastFree == freeFrame) 216 | { 217 | _lastFree = null; 218 | } 219 | } 220 | } 221 | } 222 | } 223 | 224 | class ReadBuffer 225 | { 226 | public byte[] Data; 227 | public long BaseOffset; 228 | public int End; 229 | public int DiscardCount; 230 | 231 | object _localLock = new object(); 232 | 233 | public ReadBuffer(int initialSize) 234 | { 235 | initialSize = 2 << (int)Math.Log(initialSize, 2); 236 | 237 | Data = new byte[initialSize]; 238 | } 239 | 240 | public int Read(MpegStreamReader reader, long offset, byte[] buffer, int index, int count) 241 | { 242 | lock (_localLock) 243 | { 244 | var startIdx = EnsureFilled(reader, offset, ref count); 245 | 246 | Buffer.BlockCopy(Data, startIdx, buffer, index, count); 247 | } 248 | return count; 249 | } 250 | 251 | public int ReadByte(MpegStreamReader reader, long offset) 252 | { 253 | lock (_localLock) 254 | { 255 | var count = 1; 256 | var startIdx = EnsureFilled(reader, offset, ref count); 257 | if (count == 1) 258 | { 259 | return Data[startIdx]; 260 | } 261 | } 262 | return -1; 263 | } 264 | 265 | int EnsureFilled(MpegStreamReader reader, long offset, ref int count) 266 | { 267 | // if the offset & count are inside our buffer's range, just return the appropriate index 268 | var startIdx = (int)(offset - BaseOffset); 269 | int endIdx = startIdx + count; 270 | if (startIdx < 0 || endIdx > End) 271 | { 272 | int readStart = 0, readCount = 0, moveCount = 0; 273 | long readOffset = 0; 274 | 275 | #region Decision-Making 276 | 277 | if (startIdx < 0) 278 | { 279 | // if we can't seek, there's nothing we can do 280 | if (!reader._source.CanSeek) throw new InvalidOperationException("Cannot seek backwards on a forward-only stream!"); 281 | 282 | // if there's data in the buffer, try to keep it (up to doubling the buffer size) 283 | if (End > 0) 284 | { 285 | // if doubling the buffer would push it past the max size, don't check it 286 | if ((startIdx + Data.Length > 0) || (Data.Length * 2 <= 16384 && startIdx + Data.Length * 2 > 0)) 287 | { 288 | endIdx = End; 289 | } 290 | } 291 | 292 | // we know we'll have to start reading here 293 | readOffset = offset; 294 | 295 | // if the end of the request is before the start of our buffer... 296 | if (endIdx < 0) 297 | { 298 | // ... just truncate and move on 299 | Truncate(); 300 | 301 | // set up our read parameters 302 | BaseOffset = offset; 303 | startIdx = 0; 304 | endIdx = count; 305 | 306 | // how much do we need to read? 307 | readCount = count; 308 | } 309 | else // i.e., endIdx >= 0 310 | { 311 | // we have overlap with existing data... save as much as possible 312 | moveCount = -endIdx; 313 | readCount = -startIdx; 314 | } 315 | } 316 | else // i.e., startIdx >= 0 317 | { 318 | // we only get to here if at least one byte of the request is past the end of the read data 319 | // start with the simplest scenario and work our way up 320 | 321 | // 1) We just need to fill the buffer a bit more 322 | if (endIdx < Data.Length) 323 | { 324 | readCount = endIdx - End; 325 | readStart = End; 326 | readOffset = BaseOffset + readStart; 327 | } 328 | // 2) We need to discard some bytes, then fill the buffer 329 | else if (endIdx - DiscardCount < Data.Length) 330 | { 331 | moveCount = DiscardCount; 332 | readStart = End; 333 | readCount = endIdx - readStart; 334 | readOffset = BaseOffset + readStart; 335 | } 336 | // 3) We need to expand the buffer to hold all the existing & requested data 337 | else if (Data.Length * 2 <= 16384) 338 | { 339 | // by definition, we discard 340 | moveCount = DiscardCount; 341 | readStart = End; 342 | readCount = endIdx - End; 343 | readOffset = BaseOffset + readStart; 344 | } 345 | // 4) We have to throw away some data that hasn't been discarded 346 | else 347 | { 348 | // just truncate 349 | Truncate(); 350 | 351 | // set up our read parameters 352 | BaseOffset = offset; 353 | readOffset = offset; 354 | startIdx = 0; 355 | endIdx = count; 356 | 357 | // how much do we have to read? 358 | readCount = count; 359 | } 360 | } 361 | 362 | #endregion 363 | 364 | #region Buffer Resizing & Data Moving 365 | 366 | if (endIdx - moveCount > Data.Length || readStart + readCount - moveCount > Data.Length) 367 | { 368 | var newSize = Data.Length * 2; 369 | while (newSize < endIdx - moveCount) 370 | { 371 | newSize *= 2; 372 | } 373 | 374 | var newBuf = new byte[newSize]; 375 | if (moveCount < 0) 376 | { 377 | // reverse copy 378 | Buffer.BlockCopy(Data, 0, newBuf, -moveCount, End + moveCount); 379 | 380 | DiscardCount = 0; 381 | } 382 | else 383 | { 384 | // forward or neutral copy 385 | Buffer.BlockCopy(Data, moveCount, newBuf, 0, End - moveCount); 386 | 387 | DiscardCount -= moveCount; 388 | } 389 | Data = newBuf; 390 | } 391 | else if (moveCount != 0) 392 | { 393 | if (moveCount > 0) 394 | { 395 | // forward move 396 | Buffer.BlockCopy(Data, moveCount, Data, 0, End - moveCount); 397 | 398 | DiscardCount -= moveCount; 399 | } 400 | else 401 | { 402 | // backward move 403 | for (int i = 0, srcIdx = Data.Length - 1, destIdx = Data.Length - 1 - moveCount; i < moveCount; i++, srcIdx--, destIdx--) 404 | { 405 | Data[destIdx] = Data[srcIdx]; 406 | } 407 | 408 | DiscardCount = 0; 409 | } 410 | } 411 | 412 | BaseOffset += moveCount; 413 | readStart -= moveCount; 414 | startIdx -= moveCount; 415 | endIdx -= moveCount; 416 | End -= moveCount; 417 | 418 | #endregion 419 | 420 | #region Buffer Filling 421 | 422 | lock (reader._readLock) 423 | { 424 | if (readCount > 0 && reader._source.Position != readOffset && readOffset < reader._eofOffset) 425 | { 426 | if (reader._canSeek) 427 | { 428 | try 429 | { 430 | reader._source.Position = readOffset; 431 | } 432 | catch (EndOfStreamException) 433 | { 434 | reader._eofOffset = reader._source.Length; 435 | readCount = 0; 436 | } 437 | } 438 | else 439 | { 440 | // ugh, gotta read bytes until we've reached the desired offset 441 | var seekCount = readOffset - reader._source.Position; 442 | while (--seekCount >= 0) 443 | { 444 | if (reader._source.ReadByte() == -1) 445 | { 446 | reader._eofOffset = reader._source.Position; 447 | readCount = 0; 448 | break; 449 | } 450 | } 451 | } 452 | } 453 | 454 | while (readCount > 0 && readOffset < reader._eofOffset) 455 | { 456 | var temp = reader._source.Read(Data, readStart, readCount); 457 | if (temp == 0) 458 | { 459 | break; 460 | } 461 | readStart += temp; 462 | readOffset += temp; 463 | readCount -= temp; 464 | } 465 | 466 | if (readStart > End) 467 | { 468 | End = readStart; 469 | } 470 | 471 | if (End < endIdx) 472 | { 473 | // we didn't get a full read... 474 | count = Math.Max(0, End - startIdx); 475 | } 476 | // NB: if desired, switch to "minimal reads" by commenting-out this clause 477 | else if (End < Data.Length) 478 | { 479 | // try to finish filling the buffer 480 | var temp = reader._source.Read(Data, End, Data.Length - End); 481 | End += temp; 482 | } 483 | } 484 | 485 | #endregion 486 | } 487 | 488 | return startIdx; 489 | } 490 | 491 | public void DiscardThrough(long offset) 492 | { 493 | lock (_localLock) 494 | { 495 | var count = (int)(offset - BaseOffset); 496 | DiscardCount = Math.Max(count, DiscardCount); 497 | 498 | if (DiscardCount >= Data.Length) CommitDiscard(); 499 | } 500 | } 501 | 502 | void Truncate() 503 | { 504 | End = 0; 505 | DiscardCount = 0; 506 | } 507 | 508 | void CommitDiscard() 509 | { 510 | if (DiscardCount >= Data.Length || DiscardCount >= End) 511 | { 512 | // we have been told to discard the entire buffer 513 | BaseOffset += DiscardCount; 514 | End = 0; 515 | } 516 | else 517 | { 518 | // just discard the first part... 519 | //Array.Copy(_readBuf, _readBufDiscardCount, _readBuf, 0, _readBufEnd - _readBufDiscardCount); 520 | Buffer.BlockCopy(Data, DiscardCount, Data, 0, End - DiscardCount); 521 | BaseOffset += DiscardCount; 522 | End -= DiscardCount; 523 | } 524 | DiscardCount = 0; 525 | } 526 | } 527 | 528 | ReadBuffer _readBuf = new ReadBuffer(2048); 529 | 530 | internal int Read(long offset, byte[] buffer, int index, int count) 531 | { 532 | // make sure the offset is at least positive 533 | if (offset < 0L) throw new ArgumentOutOfRangeException("offset"); 534 | 535 | // make sure the buffer is valid 536 | if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index"); 537 | 538 | return _readBuf.Read(this, offset, buffer, index, count); 539 | } 540 | 541 | internal int ReadByte(long offset) 542 | { 543 | if (offset < 0L) throw new ArgumentOutOfRangeException("offset"); 544 | 545 | return _readBuf.ReadByte(this, offset); 546 | } 547 | 548 | internal void DiscardThrough(long offset, bool minimalRead) 549 | { 550 | _readBuf.DiscardThrough(offset); 551 | } 552 | 553 | 554 | internal void ReadToEnd() 555 | { 556 | try 557 | { 558 | var maxAllocation = 40000; 559 | if (_id3Frame != null) 560 | { 561 | maxAllocation += _id3Frame.Length; 562 | } 563 | 564 | while (!_endFound) 565 | { 566 | FindNextFrame(); 567 | 568 | while (!_canSeek && FrameBase.TotalAllocation >= maxAllocation) 569 | { 570 | #if NET35 571 | System.Threading.Thread.Sleep(500); 572 | #else 573 | System.Threading.Tasks.Task.Delay(500).Wait(); // 574 | #endif 575 | } 576 | } 577 | } 578 | catch (ObjectDisposedException) 579 | { 580 | // in case the stream was disposed before we finished... 581 | } 582 | } 583 | 584 | 585 | internal bool CanSeek 586 | { 587 | get { return _canSeek; } 588 | } 589 | 590 | internal long SampleCount 591 | { 592 | get 593 | { 594 | if (_vbrInfo != null) return _vbrInfo.VBRStreamSampleCount; 595 | 596 | if (!_canSeek) return -1; 597 | 598 | ReadToEnd(); 599 | return _last.SampleCount + _last.SampleOffset; 600 | } 601 | } 602 | 603 | internal int SampleRate 604 | { 605 | get 606 | { 607 | if (_vbrInfo != null) return _vbrInfo.SampleRate; 608 | return _first.SampleRate; 609 | } 610 | } 611 | 612 | internal int Channels 613 | { 614 | get 615 | { 616 | if (_vbrInfo != null) return _vbrInfo.Channels; 617 | return _first.Channels; 618 | } 619 | } 620 | 621 | internal int FirstFrameSampleCount 622 | { 623 | get { return (_first != null ? _first.SampleCount : 0); } 624 | } 625 | 626 | 627 | internal long SeekTo(long sampleNumber) 628 | { 629 | if (!_canSeek) throw new InvalidOperationException("Cannot seek!"); 630 | 631 | // first try to "seek" by calculating the frame number 632 | var cnt = (int)(sampleNumber / _first.SampleCount); 633 | var frame = _first; 634 | if (_current != null && _current.Number <= cnt && _current.SampleOffset <= sampleNumber) 635 | { 636 | // if this fires, we can short-circuit things a bit... 637 | frame = _current; 638 | cnt -= frame.Number; 639 | } 640 | while (!_mixedFrameSize && --cnt >= 0 && frame != null) 641 | { 642 | // make sure we have more frames to look at 643 | if (frame == _last && !_endFound) 644 | { 645 | do 646 | { 647 | FindNextFrame(); 648 | } while (frame == _last && !_endFound); 649 | } 650 | 651 | // if we've found a different frame size, fall through... 652 | if (_mixedFrameSize) 653 | { 654 | break; 655 | } 656 | 657 | frame = frame.Next; 658 | } 659 | 660 | // this should not run unless we found mixed frames... 661 | while (frame != null && frame.SampleOffset + frame.SampleCount < sampleNumber) 662 | { 663 | if (frame == _last && !_endFound) 664 | { 665 | do 666 | { 667 | FindNextFrame(); 668 | } while (frame == _last && !_endFound); 669 | } 670 | 671 | frame = frame.Next; 672 | } 673 | if (frame == null) return -1; 674 | return (_current = frame).SampleOffset; 675 | } 676 | 677 | internal MpegFrame NextFrame() 678 | { 679 | // if _current is null, we've returned the last frame already 680 | var frame = _current; 681 | if (frame != null) 682 | { 683 | if (_canSeek) 684 | { 685 | frame.SaveBuffer(); 686 | DiscardThrough(frame.Offset + frame.FrameLength, false); 687 | } 688 | 689 | if (frame == _last && !_endFound) 690 | { 691 | do 692 | { 693 | FindNextFrame(); 694 | } while (frame == _last && !_endFound); 695 | } 696 | 697 | _current = frame.Next; 698 | 699 | if (!_canSeek) 700 | { 701 | // if we're in a forward-only stream, don't bother keeping the frames that have already been processed 702 | lock (_frameLock) 703 | { 704 | var temp = _first; 705 | _first = temp.Next; 706 | temp.Next = null; 707 | } 708 | } 709 | } 710 | return frame; 711 | } 712 | 713 | internal MpegFrame GetCurrentFrame() 714 | { 715 | return _current; 716 | } 717 | } 718 | } 719 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/MpegStreamReader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67d22a767a6116146987148f2fd497a7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/RiffHeaderFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NLayer.Decoder 7 | { 8 | /// 9 | /// RIFF header reader 10 | /// 11 | class RiffHeaderFrame : FrameBase 12 | { 13 | internal static RiffHeaderFrame TrySync(uint syncMark) 14 | { 15 | if (syncMark == 0x52494646U) 16 | { 17 | return new RiffHeaderFrame(); 18 | } 19 | 20 | return null; 21 | } 22 | 23 | RiffHeaderFrame() 24 | { 25 | 26 | } 27 | 28 | protected override int Validate() 29 | { 30 | var buf = new byte[4]; 31 | 32 | // we expect this to be the "WAVE" chunk 33 | if (Read(8, buf) != 4) return -1; 34 | if (buf[0] != 'W' || buf[1] != 'A' || buf[2] != 'V' || buf[3] != 'E') return -1; 35 | 36 | // now the "fmt " chunk 37 | if (Read(12, buf) != 4) return -1; 38 | if (buf[0] != 'f' || buf[1] != 'm' || buf[2] != 't' || buf[3] != ' ') return -1; 39 | 40 | // we've found the fmt chunk, so look for the data chunk 41 | var offset = 16; 42 | while (true) 43 | { 44 | // read the length and seek forward 45 | if (Read(offset, buf) != 4) return -1; 46 | offset += 4 + BitConverter.ToInt32(buf, 0); 47 | 48 | // get the chunk ID 49 | if (Read(offset, buf) != 4) return -1; 50 | offset += 4; 51 | 52 | // if it's not the data chunk, try again 53 | if (buf[0] == 'd' && buf[1] == 'a' && buf[2] == 't' && buf[3] == 'a') break; 54 | } 55 | 56 | // ... and now we know exactly where the frame ends 57 | return offset + 4; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/RiffHeaderFrame.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ef179b275bbcac4ea84513f0a807569 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/VBRInfo.cs: -------------------------------------------------------------------------------- 1 | namespace NLayer.Decoder 2 | { 3 | class VBRInfo 4 | { 5 | internal VBRInfo() { } 6 | 7 | internal int SampleCount { get; set; } 8 | internal int SampleRate { get; set; } 9 | internal int Channels { get; set; } 10 | internal int VBRFrames { get; set; } 11 | internal int VBRBytes { get; set; } 12 | internal int VBRQuality { get; set; } 13 | internal int VBRDelay { get; set; } 14 | 15 | internal long VBRStreamSampleCount 16 | { 17 | get 18 | { 19 | // we assume the entire stream is consistent wrt samples per frame 20 | return VBRFrames * SampleCount; 21 | } 22 | } 23 | 24 | internal int VBRAverageBitrate 25 | { 26 | get 27 | { 28 | return (int)((VBRBytes / (VBRStreamSampleCount / (double)SampleRate)) * 8); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Assets/NLayer/Decoder/VBRInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe1615e88db66374cbca2f9a65fd3db8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/Enums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NLayer 7 | { 8 | public enum MpegVersion 9 | { 10 | Unknown = 0, 11 | Version1 = 10, 12 | Version2 = 20, 13 | Version25 = 25, 14 | } 15 | 16 | public enum MpegLayer 17 | { 18 | Unknown = 0, 19 | LayerI = 1, 20 | LayerII = 2, 21 | LayerIII = 3, 22 | } 23 | 24 | public enum MpegChannelMode 25 | { 26 | Stereo, 27 | JointStereo, 28 | DualChannel, 29 | Mono, 30 | } 31 | 32 | public enum StereoMode 33 | { 34 | Both, 35 | LeftOnly, 36 | RightOnly, 37 | DownmixToMono, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Assets/NLayer/Enums.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e7ccac2ed335b342ab3bf9c22dc5b86 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/IMpegFrame.cs: -------------------------------------------------------------------------------- 1 | namespace NLayer 2 | { 3 | /// 4 | /// Defines a standard way of representing a MPEG frame to the decoder 5 | /// 6 | public interface IMpegFrame 7 | { 8 | /// 9 | /// Sample rate of this frame 10 | /// 11 | int SampleRate { get; } 12 | 13 | /// 14 | /// The samplerate index (directly from the header) 15 | /// 16 | int SampleRateIndex { get; } 17 | 18 | /// 19 | /// Frame length in bytes 20 | /// 21 | int FrameLength { get; } 22 | 23 | /// 24 | /// Bit Rate 25 | /// 26 | int BitRate { get; } 27 | 28 | /// 29 | /// MPEG Version 30 | /// 31 | MpegVersion Version { get; } 32 | 33 | /// 34 | /// MPEG Layer 35 | /// 36 | MpegLayer Layer { get; } 37 | 38 | /// 39 | /// Channel Mode 40 | /// 41 | MpegChannelMode ChannelMode { get; } 42 | 43 | /// 44 | /// The number of samples in this frame 45 | /// 46 | int ChannelModeExtension { get; } 47 | 48 | /// 49 | /// The channel extension bits 50 | /// 51 | int SampleCount { get; } 52 | 53 | /// 54 | /// The bitrate index (directly from the header) 55 | /// 56 | int BitRateIndex { get; } 57 | 58 | /// 59 | /// Whether the Copyright bit is set 60 | /// 61 | bool IsCopyrighted { get; } 62 | 63 | /// 64 | /// Whether a CRC is present 65 | /// 66 | bool HasCrc { get; } 67 | 68 | /// 69 | /// Whether the CRC check failed (use error concealment strategy) 70 | /// 71 | bool IsCorrupted { get; } 72 | 73 | /// 74 | /// Resets the bit reader so frames can be reused 75 | /// 76 | void Reset(); 77 | 78 | /// 79 | /// Provides sequential access to the bitstream in the frame (after the header and optional CRC) 80 | /// 81 | /// The number of bits to read 82 | /// -1 if the end of the frame has been encountered, otherwise the bits requested 83 | int ReadBits(int bitCount); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Assets/NLayer/IMpegFrame.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4ffa58092fc20d44971f3225962eb55 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mark Heath, Andrew Ward & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Assets/NLayer/MpegFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NLayer 4 | { 5 | public class MpegFile : IDisposable 6 | { 7 | System.IO.Stream _stream; 8 | bool _closeStream, _eofFound; 9 | 10 | Decoder.MpegStreamReader _reader; 11 | MpegFrameDecoder _decoder; 12 | 13 | object _seekLock = new object(); 14 | long _position; 15 | 16 | /// 17 | /// Construct Mpeg file representation from filename. 18 | /// 19 | /// The file which contains Mpeg data. 20 | public MpegFile(string fileName) 21 | { 22 | Init(System.IO.File.Open(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read), true); 23 | } 24 | 25 | /// 26 | /// Construct Mpeg file representation from stream. 27 | /// 28 | /// The input stream which contains Mpeg data. 29 | public MpegFile(System.IO.Stream stream) 30 | { 31 | Init(stream, false); 32 | } 33 | 34 | void Init(System.IO.Stream stream, bool closeStream) 35 | { 36 | _stream = stream; 37 | _closeStream = closeStream; 38 | 39 | _reader = new Decoder.MpegStreamReader(_stream); 40 | 41 | _decoder = new MpegFrameDecoder(); 42 | } 43 | 44 | /// 45 | /// Implements IDisposable.Dispose. 46 | /// 47 | public void Dispose() 48 | { 49 | if (_closeStream) 50 | { 51 | _stream.Dispose(); 52 | _closeStream = false; 53 | } 54 | } 55 | /// 56 | /// Sample rate of source Mpeg, in Hertz. 57 | /// 58 | public int SampleRate { get { return _reader.SampleRate; } } 59 | 60 | /// 61 | /// Channel count of source Mpeg. 62 | /// 63 | public int Channels { get { return _reader.Channels; } } 64 | 65 | /// 66 | /// Whether the Mpeg stream supports seek operation. 67 | /// 68 | public bool CanSeek { get { return _reader.CanSeek; } } 69 | 70 | /// 71 | /// Data length of decoded data, in PCM. 72 | /// 73 | public long Length { get { return _reader.SampleCount * _reader.Channels * sizeof(float); } } 74 | 75 | /// 76 | /// Media duration of the Mpeg file. 77 | /// 78 | public TimeSpan Duration 79 | { 80 | get 81 | { 82 | var len = _reader.SampleCount; 83 | if (len == -1) return TimeSpan.Zero; 84 | return TimeSpan.FromSeconds((double)len / _reader.SampleRate); 85 | } 86 | } 87 | 88 | /// 89 | /// Current decode position, in number of sample. Calling the setter will result in a seeking operation. 90 | /// 91 | public long Position 92 | { 93 | get { return _position; } 94 | set 95 | { 96 | if (!_reader.CanSeek) throw new InvalidOperationException("Cannot Seek!"); 97 | if (value < 0L) throw new ArgumentOutOfRangeException("value"); 98 | 99 | // we're thinking in 4-byte samples, pcmStep interleaved... adjust accordingly 100 | var samples = value / sizeof(float) / _reader.Channels; 101 | var sampleOffset = 0; 102 | 103 | // seek to the frame preceding the one we want (unless we're seeking to the first frame) 104 | if (samples >= _reader.FirstFrameSampleCount) 105 | { 106 | sampleOffset = _reader.FirstFrameSampleCount; 107 | samples -= sampleOffset; 108 | } 109 | 110 | lock (_seekLock) 111 | { 112 | // seek the stream 113 | var newPos = _reader.SeekTo(samples); 114 | if (newPos == -1) throw new ArgumentOutOfRangeException("value"); 115 | 116 | _decoder.Reset(); 117 | 118 | // if we have a sample offset, decode the next frame 119 | if (sampleOffset != 0) 120 | { 121 | _decoder.DecodeFrame(_reader.NextFrame(), _readBuf, 0); // throw away a frame (but allow the decoder to resync) 122 | newPos += sampleOffset; 123 | } 124 | 125 | _position = newPos * sizeof(float) * _reader.Channels; 126 | _eofFound = false; 127 | 128 | // clear the decoder & buffer 129 | _readBufOfs = _readBufLen = 0; 130 | } 131 | } 132 | } 133 | 134 | /// 135 | /// Current decode position, represented by time. Calling the setter will result in a seeking operation. 136 | /// 137 | public TimeSpan Time 138 | { 139 | get { return TimeSpan.FromSeconds((double)_position / sizeof(float) / _reader.Channels / _reader.SampleRate); } 140 | set { Position = (long)(value.TotalSeconds * _reader.SampleRate * _reader.Channels * sizeof(float)); } 141 | } 142 | 143 | /// 144 | /// Set the equalizer. 145 | /// 146 | /// The equalizer, represented by an array of 32 adjustments in dB. 147 | public void SetEQ(float[] eq) 148 | { 149 | _decoder.SetEQ(eq); 150 | } 151 | 152 | /// 153 | /// Stereo mode used in decoding. 154 | /// 155 | public StereoMode StereoMode 156 | { 157 | get { return _decoder.StereoMode; } 158 | set { _decoder.StereoMode = value; } 159 | } 160 | 161 | /// 162 | /// Read specified samples into provided buffer. Do exactly the same as 163 | /// except that the data is written in type of byte, while still representing single-precision float (in local endian). 164 | /// 165 | /// Buffer to write. Floating point data will be actually written into this byte array. 166 | /// Writing offset on the destination buffer. 167 | /// Length of samples to be read, in bytes. 168 | /// Sample size actually reads, in bytes. 169 | public int ReadSamples(byte[] buffer, int index, int count) 170 | { 171 | if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index"); 172 | 173 | // make sure we're asking for an even number of samples 174 | count -= (count % sizeof(float)); 175 | 176 | return ReadSamplesImpl(buffer, index, count); 177 | } 178 | 179 | /// 180 | /// Read specified samples into provided buffer, as PCM format. 181 | /// Result varies with diffirent : 182 | /// 183 | /// 184 | /// For , sample data on both two channels will occur in turn (left first). 185 | /// 186 | /// 187 | /// For and , only data on 188 | /// specified channel will occur. 189 | /// 190 | /// 191 | /// For , two channels will be down-mixed into single channel. 192 | /// 193 | /// 194 | /// 195 | /// Buffer to write. 196 | /// Writing offset on the destination buffer. 197 | /// Count of samples to be read. 198 | /// Sample count actually reads. 199 | public int ReadSamples(float[] buffer, int index, int count) 200 | { 201 | if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index"); 202 | 203 | // ReadSampleImpl "thinks" in bytes, so adjust accordingly 204 | return ReadSamplesImpl(buffer, index * sizeof(float), count * sizeof(float)) / sizeof(float); 205 | } 206 | 207 | float[] _readBuf = new float[1152 * 2]; 208 | int _readBufLen, _readBufOfs; 209 | 210 | int ReadSamplesImpl(Array buffer, int index, int count) 211 | { 212 | var cnt = 0; 213 | 214 | // lock around the entire read operation so seeking doesn't bork our buffers as we decode 215 | lock (_seekLock) 216 | { 217 | while (count > 0) 218 | { 219 | if (_readBufLen > _readBufOfs) 220 | { 221 | // we have bytes in the buffer, so copy them first 222 | var temp = _readBufLen - _readBufOfs; 223 | if (temp > count) temp = count; 224 | Buffer.BlockCopy(_readBuf, _readBufOfs, buffer, index, temp); 225 | 226 | // now update our counters... 227 | cnt += temp; 228 | 229 | count -= temp; 230 | index += temp; 231 | 232 | _position += temp; 233 | _readBufOfs += temp; 234 | 235 | // finally, mark the buffer as empty if we've read everything in it 236 | if (_readBufOfs == _readBufLen) 237 | { 238 | _readBufLen = 0; 239 | } 240 | } 241 | 242 | // if the buffer is empty, try to fill it 243 | // NB: If we've already satisfied the read request, we'll still try to fill the buffer. 244 | // This ensures there's data in the pipe on the next call 245 | if (_readBufLen == 0) 246 | { 247 | if (_eofFound) 248 | { 249 | break; 250 | } 251 | 252 | // decode the next frame (update _readBufXXX) 253 | var frame = _reader.NextFrame(); 254 | if (frame == null) 255 | { 256 | _eofFound = true; 257 | break; 258 | } 259 | 260 | try 261 | { 262 | _readBufLen = _decoder.DecodeFrame(frame, _readBuf, 0) * sizeof(float); 263 | _readBufOfs = 0; 264 | } 265 | catch (System.IO.InvalidDataException) 266 | { 267 | // bad frame... try again... 268 | _decoder.Reset(); 269 | _readBufOfs = _readBufLen = 0; 270 | continue; 271 | } 272 | catch (System.IO.EndOfStreamException) 273 | { 274 | // no more frames 275 | _eofFound = true; 276 | break; 277 | } 278 | finally 279 | { 280 | frame.ClearBuffer(); 281 | } 282 | } 283 | } 284 | } 285 | return cnt; 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /Assets/NLayer/MpegFile.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a7824182eed1d0449687336eb5f61ec 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/MpegFrameDecoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NLayer 4 | { 5 | public class MpegFrameDecoder 6 | { 7 | Decoder.LayerIDecoder _layerIDecoder; 8 | Decoder.LayerIIDecoder _layerIIDecoder; 9 | Decoder.LayerIIIDecoder _layerIIIDecoder; 10 | 11 | float[] _eqFactors; 12 | 13 | // channel buffers for getting data out of the decoders... 14 | // we do it this way so the stereo interleaving code is in one place: DecodeFrameImpl(...) 15 | // if we ever add support for multi-channel, we'll have to add a pass after the initial 16 | // stereo decode (since multi-channel basically uses the stereo channels as a reference) 17 | float[] _ch0, _ch1; 18 | 19 | public MpegFrameDecoder() 20 | { 21 | _ch0 = new float[1152]; 22 | _ch1 = new float[1152]; 23 | } 24 | 25 | /// 26 | /// Set the equalizer. 27 | /// 28 | /// The equalizer, represented by an array of 32 adjustments in dB. 29 | public void SetEQ(float[] eq) 30 | { 31 | if (eq != null) 32 | { 33 | var factors = new float[32]; 34 | for (int i = 0; i < eq.Length; i++) 35 | { 36 | // convert from dB -> scaling 37 | factors[i] = (float)Math.Pow(2, eq[i] / 6); 38 | } 39 | _eqFactors = factors; 40 | } 41 | else 42 | { 43 | _eqFactors = null; 44 | } 45 | } 46 | 47 | /// 48 | /// Stereo mode used in decoding. 49 | /// 50 | public StereoMode StereoMode { get; set; } 51 | 52 | /// 53 | /// Decode the Mpeg frame into provided buffer. Do exactly the same as 54 | /// except that the data is written in type as byte array, while still representing single-precision float (in local endian). 55 | /// 56 | /// The Mpeg frame to be decoded. 57 | /// Destination buffer. Decoded PCM (single-precision floating point array) will be written into it. 58 | /// Writing offset on the destination buffer. 59 | /// 60 | public int DecodeFrame(IMpegFrame frame, byte[] dest, int destOffset) 61 | { 62 | if (frame == null) throw new ArgumentNullException("frame"); 63 | if (dest == null) throw new ArgumentNullException("dest"); 64 | if (destOffset % 4 != 0) throw new ArgumentException("Must be an even multiple of 4", "destOffset"); 65 | 66 | var bufferAvailable = (dest.Length - destOffset) / 4; 67 | if (bufferAvailable < (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2) * frame.SampleCount) 68 | { 69 | throw new ArgumentException("Buffer not large enough! Must be big enough to hold the frame's entire output. This is up to 9,216 bytes.", "dest"); 70 | } 71 | 72 | return DecodeFrameImpl(frame, dest, destOffset / 4) * 4; 73 | } 74 | 75 | /// 76 | /// Decode the Mpeg frame into provided buffer. 77 | /// Result varies with diffirent : 78 | /// 79 | /// 80 | /// For , sample data on both two channels will occur in turn (left first). 81 | /// 82 | /// 83 | /// For and , only data on 84 | /// specified channel will occur. 85 | /// 86 | /// 87 | /// For , two channels will be down-mixed into single channel. 88 | /// 89 | /// 90 | /// 91 | /// The Mpeg frame to be decoded. 92 | /// Destination buffer. Decoded PCM (single-precision floating point array) will be written into it. 93 | /// Writing offset on the destination buffer. 94 | /// 95 | public int DecodeFrame(IMpegFrame frame, float[] dest, int destOffset) 96 | { 97 | if (frame == null) throw new ArgumentNullException("frame"); 98 | if (dest == null) throw new ArgumentNullException("dest"); 99 | 100 | if (dest.Length - destOffset < (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2) * frame.SampleCount) 101 | { 102 | throw new ArgumentException("Buffer not large enough! Must be big enough to hold the frame's entire output. This is up to 2,304 elements.", "dest"); 103 | } 104 | 105 | return DecodeFrameImpl(frame, dest, destOffset); 106 | } 107 | 108 | int DecodeFrameImpl(IMpegFrame frame, Array dest, int destOffset) 109 | { 110 | frame.Reset(); 111 | 112 | Decoder.LayerDecoderBase curDecoder = null; 113 | switch (frame.Layer) 114 | { 115 | case MpegLayer.LayerI: 116 | if (_layerIDecoder == null) 117 | { 118 | _layerIDecoder = new Decoder.LayerIDecoder(); 119 | } 120 | curDecoder = _layerIDecoder; 121 | break; 122 | case MpegLayer.LayerII: 123 | if (_layerIIDecoder == null) 124 | { 125 | _layerIIDecoder = new Decoder.LayerIIDecoder(); 126 | } 127 | curDecoder = _layerIIDecoder; 128 | break; 129 | case MpegLayer.LayerIII: 130 | if (_layerIIIDecoder == null) 131 | { 132 | _layerIIIDecoder = new Decoder.LayerIIIDecoder(); 133 | } 134 | curDecoder = _layerIIIDecoder; 135 | break; 136 | } 137 | 138 | if (curDecoder != null) 139 | { 140 | curDecoder.SetEQ(_eqFactors); 141 | curDecoder.StereoMode = StereoMode; 142 | 143 | var cnt = curDecoder.DecodeFrame(frame, _ch0, _ch1); 144 | 145 | if (frame.ChannelMode == MpegChannelMode.Mono) 146 | { 147 | Buffer.BlockCopy(_ch0, 0, dest, destOffset * sizeof(float), cnt * sizeof(float)); 148 | } 149 | else 150 | { 151 | // This is kinda annoying... if we're doing a downmix, we should technically only output a single channel 152 | // The problem is, our caller is probably expecting stereo output. Grrrr.... 153 | 154 | // We use Buffer.BlockCopy here because we don't know dest's type, but do know it's big enough to do the copy 155 | for (int i = 0; i < cnt; i++) 156 | { 157 | Buffer.BlockCopy(_ch0, i * sizeof(float), dest, destOffset * sizeof(float), sizeof(float)); 158 | ++destOffset; 159 | Buffer.BlockCopy(_ch1, i * sizeof(float), dest, destOffset * sizeof(float), sizeof(float)); 160 | ++destOffset; 161 | } 162 | cnt *= 2; 163 | } 164 | 165 | return cnt; 166 | } 167 | 168 | return 0; 169 | } 170 | 171 | /// 172 | /// Reset the decoder. 173 | /// 174 | public void Reset() 175 | { 176 | // the synthesis filters need to be cleared 177 | if (_layerIDecoder != null) 178 | { 179 | _layerIDecoder.ResetForSeek(); 180 | } 181 | if (_layerIIDecoder != null) 182 | { 183 | _layerIIDecoder.ResetForSeek(); 184 | } 185 | if (_layerIIIDecoder != null) 186 | { 187 | _layerIIIDecoder.ResetForSeek(); 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Assets/NLayer/MpegFrameDecoder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 59b74552279cc4a4ebc5e80c73c786a4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Assets/NLayer/README.md: -------------------------------------------------------------------------------- 1 | # NLayer 2 | 3 | NLayer is a fully managed MP3 to WAV decoder. The code was originally based 4 | on [JavaLayer](http://www.javazoom.net/javalayer/javalayer.html) (v1.0.1), 5 | which has been ported to C#. 6 | 7 | Was previously hosted at [nlayer.codeplex.com](http://nlayer.codeplex.com/). 8 | Please see the history there for full details of contributors. 9 | 10 | ## Usage 11 | 12 | To use NLayer for decoding MP3, first reference NLayer. 13 | 14 | ```cs 15 | using NLayer; 16 | ``` 17 | 18 | Then create an `MpegFile`, pass a file name or a stream to the constructor, and use `ReadSamples` for decoding the content: 19 | 20 | ```cs 21 | var fileName = "myMp3File.mp3"; 22 | var mpegFile = new MpegFile(filename); 23 | float[] samples = new float[44100]; 24 | mpegFile.ReadSamples(samples, 0, 44100); 25 | ``` 26 | 27 | More information could be found in code documents. 28 | 29 | ## Use with NAudio 30 | 31 | NLayer is capable of using in conjunction with [NAudio](https://github.com/naudio/NAudio/) 32 | for file conversion and real-time playback. 33 | 34 | You need to reference NAudio, NLayer and NLayer.NAudioSupport first. 35 | 36 | ```cs 37 | using NAudio.Wave; 38 | using NLayer.NAudioSupport; 39 | ``` 40 | 41 | Then create an `Mp3FileReader`, passing in a `FrameDecompressorBuilder` that uses the `Mp3FrameDecompressor` from NLayer.NAudioSupport: 42 | 43 | ```cs 44 | var fileName = "myMp3File.mp3"; 45 | var builder = new Mp3FileReader.FrameDecompressorBuilder(wf => new Mp3FrameDecompressor(wf)); 46 | var reader = new Mp3FileReader(fileName, builder); 47 | // play or process the file, e.g.: 48 | waveOut.Init(reader); 49 | waveOut.Play(); 50 | ``` 51 | -------------------------------------------------------------------------------- /Assets/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 23759f4280160a844be9f2637260278e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/Scenes/SampleScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2cda990e2423bbf4892e6590ba056729 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Packages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.unity.2d.sprite": "1.0.0", 4 | "com.unity.collab-proxy": "1.2.16", 5 | "com.unity.ide.rider": "1.1.4", 6 | "com.unity.ide.vscode": "1.2.0", 7 | "com.unity.test-framework": "1.1.14", 8 | "com.unity.textmeshpro": "2.0.1", 9 | "com.unity.timeline": "1.2.14", 10 | "com.unity.ugui": "1.0.0", 11 | "com.unity.modules.ai": "1.0.0", 12 | "com.unity.modules.androidjni": "1.0.0", 13 | "com.unity.modules.animation": "1.0.0", 14 | "com.unity.modules.assetbundle": "1.0.0", 15 | "com.unity.modules.audio": "1.0.0", 16 | "com.unity.modules.cloth": "1.0.0", 17 | "com.unity.modules.director": "1.0.0", 18 | "com.unity.modules.imageconversion": "1.0.0", 19 | "com.unity.modules.imgui": "1.0.0", 20 | "com.unity.modules.jsonserialize": "1.0.0", 21 | "com.unity.modules.particlesystem": "1.0.0", 22 | "com.unity.modules.physics": "1.0.0", 23 | "com.unity.modules.physics2d": "1.0.0", 24 | "com.unity.modules.screencapture": "1.0.0", 25 | "com.unity.modules.terrain": "1.0.0", 26 | "com.unity.modules.terrainphysics": "1.0.0", 27 | "com.unity.modules.tilemap": "1.0.0", 28 | "com.unity.modules.ui": "1.0.0", 29 | "com.unity.modules.uielements": "1.0.0", 30 | "com.unity.modules.umbra": "1.0.0", 31 | "com.unity.modules.unityanalytics": "1.0.0", 32 | "com.unity.modules.unitywebrequest": "1.0.0", 33 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 34 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 35 | "com.unity.modules.unitywebrequesttexture": "1.0.0", 36 | "com.unity.modules.unitywebrequestwww": "1.0.0", 37 | "com.unity.modules.vehicles": "1.0.0", 38 | "com.unity.modules.video": "1.0.0", 39 | "com.unity.modules.vr": "1.0.0", 40 | "com.unity.modules.wind": "1.0.0", 41 | "com.unity.modules.xr": "1.0.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!11 &1 4 | AudioManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Volume: 1 8 | Rolloff Scale: 1 9 | Doppler Factor: 1 10 | Default Speaker Mode: 2 11 | m_SampleRate: 0 12 | m_DSPBufferSize: 1024 13 | m_VirtualVoiceCount: 512 14 | m_RealVoiceCount: 32 15 | m_SpatializerPlugin: 16 | m_AmbisonicDecoderPlugin: 17 | m_DisableAudio: 0 18 | m_VirtualizeEffects: 1 19 | m_RequestedDSPBufferSize: 1024 20 | -------------------------------------------------------------------------------- /ProjectSettings/ClusterInputManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!236 &1 4 | ClusterInputManager: 5 | m_ObjectHideFlags: 0 6 | m_Inputs: [] 7 | -------------------------------------------------------------------------------- /ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!55 &1 4 | PhysicsManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 11 7 | m_Gravity: {x: 0, y: -9.81, z: 0} 8 | m_DefaultMaterial: {fileID: 0} 9 | m_BounceThreshold: 2 10 | m_SleepThreshold: 0.005 11 | m_DefaultContactOffset: 0.01 12 | m_DefaultSolverIterations: 6 13 | m_DefaultSolverVelocityIterations: 1 14 | m_QueriesHitBackfaces: 0 15 | m_QueriesHitTriggers: 1 16 | m_EnableAdaptiveForce: 0 17 | m_ClothInterCollisionDistance: 0 18 | m_ClothInterCollisionStiffness: 0 19 | m_ContactsGeneration: 1 20 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 21 | m_AutoSimulation: 1 22 | m_AutoSyncTransforms: 0 23 | m_ReuseCollisionCallbacks: 1 24 | m_ClothInterCollisionSettingsToggle: 0 25 | m_ContactPairsMode: 0 26 | m_BroadphaseType: 0 27 | m_WorldBounds: 28 | m_Center: {x: 0, y: 0, z: 0} 29 | m_Extent: {x: 250, y: 250, z: 250} 30 | m_WorldSubdivisions: 8 31 | m_FrictionType: 0 32 | m_EnableEnhancedDeterminism: 0 33 | m_EnableUnifiedHeightmaps: 1 34 | m_DefaultMaxAngluarSpeed: 7 35 | -------------------------------------------------------------------------------- /ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1045 &1 4 | EditorBuildSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Scenes: [] 8 | m_configObjects: {} 9 | -------------------------------------------------------------------------------- /ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!159 &1 4 | EditorSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 9 7 | m_ExternalVersionControlSupport: Visible Meta Files 8 | m_SerializationMode: 2 9 | m_LineEndingsForNewScripts: 0 10 | m_DefaultBehaviorMode: 1 11 | m_PrefabRegularEnvironment: {fileID: 0} 12 | m_PrefabUIEnvironment: {fileID: 0} 13 | m_SpritePackerMode: 4 14 | m_SpritePackerPaddingPower: 1 15 | m_EtcTextureCompressorBehavior: 1 16 | m_EtcTextureFastCompressor: 1 17 | m_EtcTextureNormalCompressor: 2 18 | m_EtcTextureBestCompressor: 4 19 | m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref 20 | m_ProjectGenerationRootNamespace: 21 | m_CollabEditorSettings: 22 | inProgressEnabled: 1 23 | m_EnableTextureStreamingInEditMode: 1 24 | m_EnableTextureStreamingInPlayMode: 1 25 | m_AsyncShaderCompilation: 1 26 | m_EnterPlayModeOptionsEnabled: 0 27 | m_EnterPlayModeOptions: 3 28 | m_ShowLightmapResolutionOverlay: 1 29 | m_UseLegacyProbeSampleCount: 1 30 | m_AssetPipelineMode: 1 31 | m_CacheServerMode: 0 32 | m_CacheServerEndpoint: 33 | m_CacheServerNamespacePrefix: default 34 | m_CacheServerEnableDownload: 1 35 | m_CacheServerEnableUpload: 1 36 | -------------------------------------------------------------------------------- /ProjectSettings/GraphicsSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!30 &1 4 | GraphicsSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 13 7 | m_Deferred: 8 | m_Mode: 1 9 | m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} 10 | m_DeferredReflections: 11 | m_Mode: 1 12 | m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} 13 | m_ScreenSpaceShadows: 14 | m_Mode: 1 15 | m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} 16 | m_LegacyDeferred: 17 | m_Mode: 1 18 | m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} 19 | m_DepthNormals: 20 | m_Mode: 1 21 | m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} 22 | m_MotionVectors: 23 | m_Mode: 1 24 | m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} 25 | m_LightHalo: 26 | m_Mode: 1 27 | m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} 28 | m_LensFlare: 29 | m_Mode: 1 30 | m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} 31 | m_AlwaysIncludedShaders: 32 | - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} 33 | - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} 34 | - {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0} 35 | - {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0} 36 | - {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0} 37 | m_PreloadedShaders: [] 38 | m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, 39 | type: 0} 40 | m_CustomRenderPipeline: {fileID: 0} 41 | m_TransparencySortMode: 0 42 | m_TransparencySortAxis: {x: 0, y: 0, z: 1} 43 | m_DefaultRenderingPath: 1 44 | m_DefaultMobileRenderingPath: 1 45 | m_TierSettings: [] 46 | m_LightmapStripping: 0 47 | m_FogStripping: 0 48 | m_InstancingStripping: 0 49 | m_LightmapKeepPlain: 1 50 | m_LightmapKeepDirCombined: 1 51 | m_LightmapKeepDynamicPlain: 1 52 | m_LightmapKeepDynamicDirCombined: 1 53 | m_LightmapKeepShadowMask: 1 54 | m_LightmapKeepSubtractive: 1 55 | m_FogKeepLinear: 1 56 | m_FogKeepExp: 1 57 | m_FogKeepExp2: 1 58 | m_AlbedoSwatchInfos: [] 59 | m_LightsUseLinearIntensity: 0 60 | m_LightsUseColorTemperature: 0 61 | m_LogWhenShaderIsCompiled: 0 62 | m_AllowEnlightenSupportForUpgradedProject: 1 63 | -------------------------------------------------------------------------------- /ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!13 &1 4 | InputManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Axes: 8 | - serializedVersion: 3 9 | m_Name: Horizontal 10 | descriptiveName: 11 | descriptiveNegativeName: 12 | negativeButton: left 13 | positiveButton: right 14 | altNegativeButton: a 15 | altPositiveButton: d 16 | gravity: 3 17 | dead: 0.001 18 | sensitivity: 3 19 | snap: 1 20 | invert: 0 21 | type: 0 22 | axis: 0 23 | joyNum: 0 24 | - serializedVersion: 3 25 | m_Name: Vertical 26 | descriptiveName: 27 | descriptiveNegativeName: 28 | negativeButton: down 29 | positiveButton: up 30 | altNegativeButton: s 31 | altPositiveButton: w 32 | gravity: 3 33 | dead: 0.001 34 | sensitivity: 3 35 | snap: 1 36 | invert: 0 37 | type: 0 38 | axis: 0 39 | joyNum: 0 40 | - serializedVersion: 3 41 | m_Name: Fire1 42 | descriptiveName: 43 | descriptiveNegativeName: 44 | negativeButton: 45 | positiveButton: left ctrl 46 | altNegativeButton: 47 | altPositiveButton: mouse 0 48 | gravity: 1000 49 | dead: 0.001 50 | sensitivity: 1000 51 | snap: 0 52 | invert: 0 53 | type: 0 54 | axis: 0 55 | joyNum: 0 56 | - serializedVersion: 3 57 | m_Name: Fire2 58 | descriptiveName: 59 | descriptiveNegativeName: 60 | negativeButton: 61 | positiveButton: left alt 62 | altNegativeButton: 63 | altPositiveButton: mouse 1 64 | gravity: 1000 65 | dead: 0.001 66 | sensitivity: 1000 67 | snap: 0 68 | invert: 0 69 | type: 0 70 | axis: 0 71 | joyNum: 0 72 | - serializedVersion: 3 73 | m_Name: Fire3 74 | descriptiveName: 75 | descriptiveNegativeName: 76 | negativeButton: 77 | positiveButton: left shift 78 | altNegativeButton: 79 | altPositiveButton: mouse 2 80 | gravity: 1000 81 | dead: 0.001 82 | sensitivity: 1000 83 | snap: 0 84 | invert: 0 85 | type: 0 86 | axis: 0 87 | joyNum: 0 88 | - serializedVersion: 3 89 | m_Name: Jump 90 | descriptiveName: 91 | descriptiveNegativeName: 92 | negativeButton: 93 | positiveButton: space 94 | altNegativeButton: 95 | altPositiveButton: 96 | gravity: 1000 97 | dead: 0.001 98 | sensitivity: 1000 99 | snap: 0 100 | invert: 0 101 | type: 0 102 | axis: 0 103 | joyNum: 0 104 | - serializedVersion: 3 105 | m_Name: Mouse X 106 | descriptiveName: 107 | descriptiveNegativeName: 108 | negativeButton: 109 | positiveButton: 110 | altNegativeButton: 111 | altPositiveButton: 112 | gravity: 0 113 | dead: 0 114 | sensitivity: 0.1 115 | snap: 0 116 | invert: 0 117 | type: 1 118 | axis: 0 119 | joyNum: 0 120 | - serializedVersion: 3 121 | m_Name: Mouse Y 122 | descriptiveName: 123 | descriptiveNegativeName: 124 | negativeButton: 125 | positiveButton: 126 | altNegativeButton: 127 | altPositiveButton: 128 | gravity: 0 129 | dead: 0 130 | sensitivity: 0.1 131 | snap: 0 132 | invert: 0 133 | type: 1 134 | axis: 1 135 | joyNum: 0 136 | - serializedVersion: 3 137 | m_Name: Mouse ScrollWheel 138 | descriptiveName: 139 | descriptiveNegativeName: 140 | negativeButton: 141 | positiveButton: 142 | altNegativeButton: 143 | altPositiveButton: 144 | gravity: 0 145 | dead: 0 146 | sensitivity: 0.1 147 | snap: 0 148 | invert: 0 149 | type: 1 150 | axis: 2 151 | joyNum: 0 152 | - serializedVersion: 3 153 | m_Name: Horizontal 154 | descriptiveName: 155 | descriptiveNegativeName: 156 | negativeButton: 157 | positiveButton: 158 | altNegativeButton: 159 | altPositiveButton: 160 | gravity: 0 161 | dead: 0.19 162 | sensitivity: 1 163 | snap: 0 164 | invert: 0 165 | type: 2 166 | axis: 0 167 | joyNum: 0 168 | - serializedVersion: 3 169 | m_Name: Vertical 170 | descriptiveName: 171 | descriptiveNegativeName: 172 | negativeButton: 173 | positiveButton: 174 | altNegativeButton: 175 | altPositiveButton: 176 | gravity: 0 177 | dead: 0.19 178 | sensitivity: 1 179 | snap: 0 180 | invert: 1 181 | type: 2 182 | axis: 1 183 | joyNum: 0 184 | - serializedVersion: 3 185 | m_Name: Fire1 186 | descriptiveName: 187 | descriptiveNegativeName: 188 | negativeButton: 189 | positiveButton: joystick button 0 190 | altNegativeButton: 191 | altPositiveButton: 192 | gravity: 1000 193 | dead: 0.001 194 | sensitivity: 1000 195 | snap: 0 196 | invert: 0 197 | type: 0 198 | axis: 0 199 | joyNum: 0 200 | - serializedVersion: 3 201 | m_Name: Fire2 202 | descriptiveName: 203 | descriptiveNegativeName: 204 | negativeButton: 205 | positiveButton: joystick button 1 206 | altNegativeButton: 207 | altPositiveButton: 208 | gravity: 1000 209 | dead: 0.001 210 | sensitivity: 1000 211 | snap: 0 212 | invert: 0 213 | type: 0 214 | axis: 0 215 | joyNum: 0 216 | - serializedVersion: 3 217 | m_Name: Fire3 218 | descriptiveName: 219 | descriptiveNegativeName: 220 | negativeButton: 221 | positiveButton: joystick button 2 222 | altNegativeButton: 223 | altPositiveButton: 224 | gravity: 1000 225 | dead: 0.001 226 | sensitivity: 1000 227 | snap: 0 228 | invert: 0 229 | type: 0 230 | axis: 0 231 | joyNum: 0 232 | - serializedVersion: 3 233 | m_Name: Jump 234 | descriptiveName: 235 | descriptiveNegativeName: 236 | negativeButton: 237 | positiveButton: joystick button 3 238 | altNegativeButton: 239 | altPositiveButton: 240 | gravity: 1000 241 | dead: 0.001 242 | sensitivity: 1000 243 | snap: 0 244 | invert: 0 245 | type: 0 246 | axis: 0 247 | joyNum: 0 248 | - serializedVersion: 3 249 | m_Name: Submit 250 | descriptiveName: 251 | descriptiveNegativeName: 252 | negativeButton: 253 | positiveButton: return 254 | altNegativeButton: 255 | altPositiveButton: joystick button 0 256 | gravity: 1000 257 | dead: 0.001 258 | sensitivity: 1000 259 | snap: 0 260 | invert: 0 261 | type: 0 262 | axis: 0 263 | joyNum: 0 264 | - serializedVersion: 3 265 | m_Name: Submit 266 | descriptiveName: 267 | descriptiveNegativeName: 268 | negativeButton: 269 | positiveButton: enter 270 | altNegativeButton: 271 | altPositiveButton: space 272 | gravity: 1000 273 | dead: 0.001 274 | sensitivity: 1000 275 | snap: 0 276 | invert: 0 277 | type: 0 278 | axis: 0 279 | joyNum: 0 280 | - serializedVersion: 3 281 | m_Name: Cancel 282 | descriptiveName: 283 | descriptiveNegativeName: 284 | negativeButton: 285 | positiveButton: escape 286 | altNegativeButton: 287 | altPositiveButton: joystick button 1 288 | gravity: 1000 289 | dead: 0.001 290 | sensitivity: 1000 291 | snap: 0 292 | invert: 0 293 | type: 0 294 | axis: 0 295 | joyNum: 0 296 | -------------------------------------------------------------------------------- /ProjectSettings/NavMeshAreas.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!126 &1 4 | NavMeshProjectSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | areas: 8 | - name: Walkable 9 | cost: 1 10 | - name: Not Walkable 11 | cost: 1 12 | - name: Jump 13 | cost: 2 14 | - name: 15 | cost: 1 16 | - name: 17 | cost: 1 18 | - name: 19 | cost: 1 20 | - name: 21 | cost: 1 22 | - name: 23 | cost: 1 24 | - name: 25 | cost: 1 26 | - name: 27 | cost: 1 28 | - name: 29 | cost: 1 30 | - name: 31 | cost: 1 32 | - name: 33 | cost: 1 34 | - name: 35 | cost: 1 36 | - name: 37 | cost: 1 38 | - name: 39 | cost: 1 40 | - name: 41 | cost: 1 42 | - name: 43 | cost: 1 44 | - name: 45 | cost: 1 46 | - name: 47 | cost: 1 48 | - name: 49 | cost: 1 50 | - name: 51 | cost: 1 52 | - name: 53 | cost: 1 54 | - name: 55 | cost: 1 56 | - name: 57 | cost: 1 58 | - name: 59 | cost: 1 60 | - name: 61 | cost: 1 62 | - name: 63 | cost: 1 64 | - name: 65 | cost: 1 66 | - name: 67 | cost: 1 68 | - name: 69 | cost: 1 70 | - name: 71 | cost: 1 72 | m_LastAgentTypeID: -887442657 73 | m_Settings: 74 | - serializedVersion: 2 75 | agentTypeID: 0 76 | agentRadius: 0.5 77 | agentHeight: 2 78 | agentSlope: 45 79 | agentClimb: 0.75 80 | ledgeDropHeight: 0 81 | maxJumpAcrossDistance: 0 82 | minRegionArea: 2 83 | manualCellSize: 0 84 | cellSize: 0.16666667 85 | manualTileSize: 0 86 | tileSize: 256 87 | accuratePlacement: 0 88 | debug: 89 | m_Flags: 0 90 | m_SettingNames: 91 | - Humanoid 92 | -------------------------------------------------------------------------------- /ProjectSettings/NetworkManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!149 &1 4 | NetworkManager: 5 | m_ObjectHideFlags: 0 6 | m_DebugLevel: 0 7 | m_Sendrate: 15 8 | m_AssetToPrefab: {} 9 | -------------------------------------------------------------------------------- /ProjectSettings/Physics2DSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!19 &1 4 | Physics2DSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 4 7 | m_Gravity: {x: 0, y: -9.81} 8 | m_DefaultMaterial: {fileID: 0} 9 | m_VelocityIterations: 8 10 | m_PositionIterations: 3 11 | m_VelocityThreshold: 1 12 | m_MaxLinearCorrection: 0.2 13 | m_MaxAngularCorrection: 8 14 | m_MaxTranslationSpeed: 100 15 | m_MaxRotationSpeed: 360 16 | m_BaumgarteScale: 0.2 17 | m_BaumgarteTimeOfImpactScale: 0.75 18 | m_TimeToSleep: 0.5 19 | m_LinearSleepTolerance: 0.01 20 | m_AngularSleepTolerance: 2 21 | m_DefaultContactOffset: 0.01 22 | m_JobOptions: 23 | serializedVersion: 2 24 | useMultithreading: 0 25 | useConsistencySorting: 0 26 | m_InterpolationPosesPerJob: 100 27 | m_NewContactsPerJob: 30 28 | m_CollideContactsPerJob: 100 29 | m_ClearFlagsPerJob: 200 30 | m_ClearBodyForcesPerJob: 200 31 | m_SyncDiscreteFixturesPerJob: 50 32 | m_SyncContinuousFixturesPerJob: 50 33 | m_FindNearestContactsPerJob: 100 34 | m_UpdateTriggerContactsPerJob: 100 35 | m_IslandSolverCostThreshold: 100 36 | m_IslandSolverBodyCostScale: 1 37 | m_IslandSolverContactCostScale: 10 38 | m_IslandSolverJointCostScale: 10 39 | m_IslandSolverBodiesPerJob: 50 40 | m_IslandSolverContactsPerJob: 50 41 | m_AutoSimulation: 1 42 | m_QueriesHitTriggers: 1 43 | m_QueriesStartInColliders: 1 44 | m_CallbacksOnDisable: 1 45 | m_ReuseCollisionCallbacks: 1 46 | m_AutoSyncTransforms: 0 47 | m_AlwaysShowColliders: 0 48 | m_ShowColliderSleep: 1 49 | m_ShowColliderContacts: 0 50 | m_ShowColliderAABB: 0 51 | m_ContactArrowScale: 0.2 52 | m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} 53 | m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} 54 | m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} 55 | m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} 56 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 57 | -------------------------------------------------------------------------------- /ProjectSettings/PresetManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1386491679 &1 4 | PresetManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_DefaultPresets: {} 8 | -------------------------------------------------------------------------------- /ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!129 &1 4 | PlayerSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 20 7 | productGUID: 2a7e8dc3a4869db43bd752865d444df7 8 | AndroidProfiler: 0 9 | AndroidFilterTouchesWhenObscured: 0 10 | AndroidEnableSustainedPerformanceMode: 0 11 | defaultScreenOrientation: 4 12 | targetDevice: 2 13 | useOnDemandResources: 0 14 | accelerometerFrequency: 60 15 | companyName: DefaultCompany 16 | productName: unity-audio-html 17 | defaultCursor: {fileID: 0} 18 | cursorHotspot: {x: 0, y: 0} 19 | m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} 20 | m_ShowUnitySplashScreen: 1 21 | m_ShowUnitySplashLogo: 1 22 | m_SplashScreenOverlayOpacity: 1 23 | m_SplashScreenAnimation: 1 24 | m_SplashScreenLogoStyle: 1 25 | m_SplashScreenDrawMode: 0 26 | m_SplashScreenBackgroundAnimationZoom: 1 27 | m_SplashScreenLogoAnimationZoom: 1 28 | m_SplashScreenBackgroundLandscapeAspect: 1 29 | m_SplashScreenBackgroundPortraitAspect: 1 30 | m_SplashScreenBackgroundLandscapeUvs: 31 | serializedVersion: 2 32 | x: 0 33 | y: 0 34 | width: 1 35 | height: 1 36 | m_SplashScreenBackgroundPortraitUvs: 37 | serializedVersion: 2 38 | x: 0 39 | y: 0 40 | width: 1 41 | height: 1 42 | m_SplashScreenLogos: [] 43 | m_VirtualRealitySplashScreen: {fileID: 0} 44 | m_HolographicTrackingLossScreen: {fileID: 0} 45 | defaultScreenWidth: 1024 46 | defaultScreenHeight: 768 47 | defaultScreenWidthWeb: 960 48 | defaultScreenHeightWeb: 600 49 | m_StereoRenderingPath: 0 50 | m_ActiveColorSpace: 0 51 | m_MTRendering: 1 52 | m_StackTraceTypes: 010000000100000001000000010000000100000001000000 53 | iosShowActivityIndicatorOnLoading: -1 54 | androidShowActivityIndicatorOnLoading: -1 55 | iosUseCustomAppBackgroundBehavior: 0 56 | iosAllowHTTPDownload: 1 57 | allowedAutorotateToPortrait: 1 58 | allowedAutorotateToPortraitUpsideDown: 1 59 | allowedAutorotateToLandscapeRight: 1 60 | allowedAutorotateToLandscapeLeft: 1 61 | useOSAutorotation: 1 62 | use32BitDisplayBuffer: 1 63 | preserveFramebufferAlpha: 0 64 | disableDepthAndStencilBuffers: 0 65 | androidStartInFullscreen: 1 66 | androidRenderOutsideSafeArea: 1 67 | androidUseSwappy: 0 68 | androidBlitType: 0 69 | defaultIsNativeResolution: 1 70 | macRetinaSupport: 1 71 | runInBackground: 1 72 | captureSingleScreen: 0 73 | muteOtherAudioSources: 0 74 | Prepare IOS For Recording: 0 75 | Force IOS Speakers When Recording: 0 76 | deferSystemGesturesMode: 0 77 | hideHomeButton: 0 78 | submitAnalytics: 1 79 | usePlayerLog: 1 80 | bakeCollisionMeshes: 0 81 | forceSingleInstance: 0 82 | useFlipModelSwapchain: 1 83 | resizableWindow: 0 84 | useMacAppStoreValidation: 0 85 | macAppStoreCategory: public.app-category.games 86 | gpuSkinning: 0 87 | xboxPIXTextureCapture: 0 88 | xboxEnableAvatar: 0 89 | xboxEnableKinect: 0 90 | xboxEnableKinectAutoTracking: 0 91 | xboxEnableFitness: 0 92 | visibleInBackground: 1 93 | allowFullscreenSwitch: 1 94 | fullscreenMode: 1 95 | xboxSpeechDB: 0 96 | xboxEnableHeadOrientation: 0 97 | xboxEnableGuest: 0 98 | xboxEnablePIXSampling: 0 99 | metalFramebufferOnly: 0 100 | xboxOneResolution: 0 101 | xboxOneSResolution: 0 102 | xboxOneXResolution: 3 103 | xboxOneMonoLoggingLevel: 0 104 | xboxOneLoggingLevel: 1 105 | xboxOneDisableEsram: 0 106 | xboxOneEnableTypeOptimization: 0 107 | xboxOnePresentImmediateThreshold: 0 108 | switchQueueCommandMemory: 0 109 | switchQueueControlMemory: 16384 110 | switchQueueComputeMemory: 262144 111 | switchNVNShaderPoolsGranularity: 33554432 112 | switchNVNDefaultPoolsGranularity: 16777216 113 | switchNVNOtherPoolsGranularity: 16777216 114 | stadiaPresentMode: 0 115 | stadiaTargetFramerate: 0 116 | vulkanNumSwapchainBuffers: 3 117 | vulkanEnableSetSRGBWrite: 0 118 | m_SupportedAspectRatios: 119 | 4:3: 1 120 | 5:4: 1 121 | 16:10: 1 122 | 16:9: 1 123 | Others: 1 124 | bundleVersion: 0.1 125 | preloadedAssets: [] 126 | metroInputSource: 0 127 | wsaTransparentSwapchain: 0 128 | m_HolographicPauseOnTrackingLoss: 1 129 | xboxOneDisableKinectGpuReservation: 1 130 | xboxOneEnable7thCore: 1 131 | vrSettings: 132 | cardboard: 133 | depthFormat: 0 134 | enableTransitionView: 0 135 | daydream: 136 | depthFormat: 0 137 | useSustainedPerformanceMode: 0 138 | enableVideoLayer: 0 139 | useProtectedVideoMemory: 0 140 | minimumSupportedHeadTracking: 0 141 | maximumSupportedHeadTracking: 1 142 | hololens: 143 | depthFormat: 1 144 | depthBufferSharingEnabled: 1 145 | lumin: 146 | depthFormat: 0 147 | frameTiming: 2 148 | enableGLCache: 0 149 | glCacheMaxBlobSize: 524288 150 | glCacheMaxFileSize: 8388608 151 | oculus: 152 | sharedDepthBuffer: 1 153 | dashSupport: 1 154 | lowOverheadMode: 0 155 | protectedContext: 0 156 | v2Signing: 1 157 | enable360StereoCapture: 0 158 | isWsaHolographicRemotingEnabled: 0 159 | enableFrameTimingStats: 0 160 | useHDRDisplay: 0 161 | D3DHDRBitDepth: 0 162 | m_ColorGamuts: 00000000 163 | targetPixelDensity: 30 164 | resolutionScalingMode: 0 165 | androidSupportedAspectRatio: 1 166 | androidMaxAspectRatio: 2.1 167 | applicationIdentifier: {} 168 | buildNumber: {} 169 | AndroidBundleVersionCode: 1 170 | AndroidMinSdkVersion: 19 171 | AndroidTargetSdkVersion: 0 172 | AndroidPreferredInstallLocation: 1 173 | aotOptions: 174 | stripEngineCode: 1 175 | iPhoneStrippingLevel: 0 176 | iPhoneScriptCallOptimization: 0 177 | ForceInternetPermission: 0 178 | ForceSDCardPermission: 0 179 | CreateWallpaper: 0 180 | APKExpansionFiles: 0 181 | keepLoadedShadersAlive: 0 182 | StripUnusedMeshComponents: 1 183 | VertexChannelCompressionMask: 4054 184 | iPhoneSdkVersion: 988 185 | iOSTargetOSVersionString: 10.0 186 | tvOSSdkVersion: 0 187 | tvOSRequireExtendedGameController: 0 188 | tvOSTargetOSVersionString: 10.0 189 | uIPrerenderedIcon: 0 190 | uIRequiresPersistentWiFi: 0 191 | uIRequiresFullScreen: 1 192 | uIStatusBarHidden: 1 193 | uIExitOnSuspend: 0 194 | uIStatusBarStyle: 0 195 | appleTVSplashScreen: {fileID: 0} 196 | appleTVSplashScreen2x: {fileID: 0} 197 | tvOSSmallIconLayers: [] 198 | tvOSSmallIconLayers2x: [] 199 | tvOSLargeIconLayers: [] 200 | tvOSLargeIconLayers2x: [] 201 | tvOSTopShelfImageLayers: [] 202 | tvOSTopShelfImageLayers2x: [] 203 | tvOSTopShelfImageWideLayers: [] 204 | tvOSTopShelfImageWideLayers2x: [] 205 | iOSLaunchScreenType: 0 206 | iOSLaunchScreenPortrait: {fileID: 0} 207 | iOSLaunchScreenLandscape: {fileID: 0} 208 | iOSLaunchScreenBackgroundColor: 209 | serializedVersion: 2 210 | rgba: 0 211 | iOSLaunchScreenFillPct: 100 212 | iOSLaunchScreenSize: 100 213 | iOSLaunchScreenCustomXibPath: 214 | iOSLaunchScreeniPadType: 0 215 | iOSLaunchScreeniPadImage: {fileID: 0} 216 | iOSLaunchScreeniPadBackgroundColor: 217 | serializedVersion: 2 218 | rgba: 0 219 | iOSLaunchScreeniPadFillPct: 100 220 | iOSLaunchScreeniPadSize: 100 221 | iOSLaunchScreeniPadCustomXibPath: 222 | iOSUseLaunchScreenStoryboard: 0 223 | iOSLaunchScreenCustomStoryboardPath: 224 | iOSDeviceRequirements: [] 225 | iOSURLSchemes: [] 226 | iOSBackgroundModes: 0 227 | iOSMetalForceHardShadows: 0 228 | metalEditorSupport: 1 229 | metalAPIValidation: 1 230 | iOSRenderExtraFrameOnPause: 0 231 | appleDeveloperTeamID: 232 | iOSManualSigningProvisioningProfileID: 233 | tvOSManualSigningProvisioningProfileID: 234 | iOSManualSigningProvisioningProfileType: 0 235 | tvOSManualSigningProvisioningProfileType: 0 236 | appleEnableAutomaticSigning: 0 237 | iOSRequireARKit: 0 238 | iOSAutomaticallyDetectAndAddCapabilities: 1 239 | appleEnableProMotion: 0 240 | clonedFromGUID: 5f34be1353de5cf4398729fda238591b 241 | templatePackageId: com.unity.template.2d@3.3.0 242 | templateDefaultScene: Assets/Scenes/SampleScene.unity 243 | AndroidTargetArchitectures: 1 244 | AndroidSplashScreenScale: 0 245 | androidSplashScreen: {fileID: 0} 246 | AndroidKeystoreName: 247 | AndroidKeyaliasName: 248 | AndroidBuildApkPerCpuArchitecture: 0 249 | AndroidTVCompatibility: 0 250 | AndroidIsGame: 1 251 | AndroidEnableTango: 0 252 | androidEnableBanner: 1 253 | androidUseLowAccuracyLocation: 0 254 | androidUseCustomKeystore: 0 255 | m_AndroidBanners: 256 | - width: 320 257 | height: 180 258 | banner: {fileID: 0} 259 | androidGamepadSupportLevel: 0 260 | AndroidValidateAppBundleSize: 1 261 | AndroidAppBundleSizeToValidate: 150 262 | m_BuildTargetIcons: [] 263 | m_BuildTargetPlatformIcons: [] 264 | m_BuildTargetBatching: [] 265 | m_BuildTargetGraphicsJobs: 266 | - m_BuildTarget: MacStandaloneSupport 267 | m_GraphicsJobs: 0 268 | - m_BuildTarget: Switch 269 | m_GraphicsJobs: 0 270 | - m_BuildTarget: MetroSupport 271 | m_GraphicsJobs: 0 272 | - m_BuildTarget: AppleTVSupport 273 | m_GraphicsJobs: 0 274 | - m_BuildTarget: BJMSupport 275 | m_GraphicsJobs: 0 276 | - m_BuildTarget: LinuxStandaloneSupport 277 | m_GraphicsJobs: 0 278 | - m_BuildTarget: PS4Player 279 | m_GraphicsJobs: 0 280 | - m_BuildTarget: iOSSupport 281 | m_GraphicsJobs: 0 282 | - m_BuildTarget: WindowsStandaloneSupport 283 | m_GraphicsJobs: 0 284 | - m_BuildTarget: XboxOnePlayer 285 | m_GraphicsJobs: 0 286 | - m_BuildTarget: LuminSupport 287 | m_GraphicsJobs: 0 288 | - m_BuildTarget: AndroidPlayer 289 | m_GraphicsJobs: 0 290 | - m_BuildTarget: WebGLSupport 291 | m_GraphicsJobs: 0 292 | m_BuildTargetGraphicsJobMode: 293 | - m_BuildTarget: PS4Player 294 | m_GraphicsJobMode: 0 295 | - m_BuildTarget: XboxOnePlayer 296 | m_GraphicsJobMode: 0 297 | m_BuildTargetGraphicsAPIs: 298 | - m_BuildTarget: AndroidPlayer 299 | m_APIs: 150000000b000000 300 | m_Automatic: 0 301 | m_BuildTargetVRSettings: [] 302 | openGLRequireES31: 0 303 | openGLRequireES31AEP: 0 304 | openGLRequireES32: 0 305 | m_TemplateCustomTags: {} 306 | mobileMTRendering: 307 | Android: 1 308 | iPhone: 1 309 | tvOS: 1 310 | m_BuildTargetGroupLightmapEncodingQuality: [] 311 | m_BuildTargetGroupLightmapSettings: [] 312 | playModeTestRunnerEnabled: 0 313 | runPlayModeTestAsEditModeTest: 0 314 | actionOnDotNetUnhandledException: 1 315 | enableInternalProfiler: 0 316 | logObjCUncaughtExceptions: 1 317 | enableCrashReportAPI: 0 318 | cameraUsageDescription: 319 | locationUsageDescription: 320 | microphoneUsageDescription: 321 | switchNetLibKey: 322 | switchSocketMemoryPoolSize: 6144 323 | switchSocketAllocatorPoolSize: 128 324 | switchSocketConcurrencyLimit: 14 325 | switchScreenResolutionBehavior: 2 326 | switchUseCPUProfiler: 0 327 | switchApplicationID: 0x01004b9000490000 328 | switchNSODependencies: 329 | switchTitleNames_0: 330 | switchTitleNames_1: 331 | switchTitleNames_2: 332 | switchTitleNames_3: 333 | switchTitleNames_4: 334 | switchTitleNames_5: 335 | switchTitleNames_6: 336 | switchTitleNames_7: 337 | switchTitleNames_8: 338 | switchTitleNames_9: 339 | switchTitleNames_10: 340 | switchTitleNames_11: 341 | switchTitleNames_12: 342 | switchTitleNames_13: 343 | switchTitleNames_14: 344 | switchPublisherNames_0: 345 | switchPublisherNames_1: 346 | switchPublisherNames_2: 347 | switchPublisherNames_3: 348 | switchPublisherNames_4: 349 | switchPublisherNames_5: 350 | switchPublisherNames_6: 351 | switchPublisherNames_7: 352 | switchPublisherNames_8: 353 | switchPublisherNames_9: 354 | switchPublisherNames_10: 355 | switchPublisherNames_11: 356 | switchPublisherNames_12: 357 | switchPublisherNames_13: 358 | switchPublisherNames_14: 359 | switchIcons_0: {fileID: 0} 360 | switchIcons_1: {fileID: 0} 361 | switchIcons_2: {fileID: 0} 362 | switchIcons_3: {fileID: 0} 363 | switchIcons_4: {fileID: 0} 364 | switchIcons_5: {fileID: 0} 365 | switchIcons_6: {fileID: 0} 366 | switchIcons_7: {fileID: 0} 367 | switchIcons_8: {fileID: 0} 368 | switchIcons_9: {fileID: 0} 369 | switchIcons_10: {fileID: 0} 370 | switchIcons_11: {fileID: 0} 371 | switchIcons_12: {fileID: 0} 372 | switchIcons_13: {fileID: 0} 373 | switchIcons_14: {fileID: 0} 374 | switchSmallIcons_0: {fileID: 0} 375 | switchSmallIcons_1: {fileID: 0} 376 | switchSmallIcons_2: {fileID: 0} 377 | switchSmallIcons_3: {fileID: 0} 378 | switchSmallIcons_4: {fileID: 0} 379 | switchSmallIcons_5: {fileID: 0} 380 | switchSmallIcons_6: {fileID: 0} 381 | switchSmallIcons_7: {fileID: 0} 382 | switchSmallIcons_8: {fileID: 0} 383 | switchSmallIcons_9: {fileID: 0} 384 | switchSmallIcons_10: {fileID: 0} 385 | switchSmallIcons_11: {fileID: 0} 386 | switchSmallIcons_12: {fileID: 0} 387 | switchSmallIcons_13: {fileID: 0} 388 | switchSmallIcons_14: {fileID: 0} 389 | switchManualHTML: 390 | switchAccessibleURLs: 391 | switchLegalInformation: 392 | switchMainThreadStackSize: 1048576 393 | switchPresenceGroupId: 394 | switchLogoHandling: 0 395 | switchReleaseVersion: 0 396 | switchDisplayVersion: 1.0.0 397 | switchStartupUserAccount: 0 398 | switchTouchScreenUsage: 0 399 | switchSupportedLanguagesMask: 0 400 | switchLogoType: 0 401 | switchApplicationErrorCodeCategory: 402 | switchUserAccountSaveDataSize: 0 403 | switchUserAccountSaveDataJournalSize: 0 404 | switchApplicationAttribute: 0 405 | switchCardSpecSize: -1 406 | switchCardSpecClock: -1 407 | switchRatingsMask: 0 408 | switchRatingsInt_0: 0 409 | switchRatingsInt_1: 0 410 | switchRatingsInt_2: 0 411 | switchRatingsInt_3: 0 412 | switchRatingsInt_4: 0 413 | switchRatingsInt_5: 0 414 | switchRatingsInt_6: 0 415 | switchRatingsInt_7: 0 416 | switchRatingsInt_8: 0 417 | switchRatingsInt_9: 0 418 | switchRatingsInt_10: 0 419 | switchRatingsInt_11: 0 420 | switchRatingsInt_12: 0 421 | switchLocalCommunicationIds_0: 422 | switchLocalCommunicationIds_1: 423 | switchLocalCommunicationIds_2: 424 | switchLocalCommunicationIds_3: 425 | switchLocalCommunicationIds_4: 426 | switchLocalCommunicationIds_5: 427 | switchLocalCommunicationIds_6: 428 | switchLocalCommunicationIds_7: 429 | switchParentalControl: 0 430 | switchAllowsScreenshot: 1 431 | switchAllowsVideoCapturing: 1 432 | switchAllowsRuntimeAddOnContentInstall: 0 433 | switchDataLossConfirmation: 0 434 | switchUserAccountLockEnabled: 0 435 | switchSystemResourceMemory: 16777216 436 | switchSupportedNpadStyles: 22 437 | switchNativeFsCacheSize: 32 438 | switchIsHoldTypeHorizontal: 0 439 | switchSupportedNpadCount: 8 440 | switchSocketConfigEnabled: 0 441 | switchTcpInitialSendBufferSize: 32 442 | switchTcpInitialReceiveBufferSize: 64 443 | switchTcpAutoSendBufferSizeMax: 256 444 | switchTcpAutoReceiveBufferSizeMax: 256 445 | switchUdpSendBufferSize: 9 446 | switchUdpReceiveBufferSize: 42 447 | switchSocketBufferEfficiency: 4 448 | switchSocketInitializeEnabled: 1 449 | switchNetworkInterfaceManagerInitializeEnabled: 1 450 | switchPlayerConnectionEnabled: 1 451 | ps4NPAgeRating: 12 452 | ps4NPTitleSecret: 453 | ps4NPTrophyPackPath: 454 | ps4ParentalLevel: 11 455 | ps4ContentID: ED1633-NPXX51362_00-0000000000000000 456 | ps4Category: 0 457 | ps4MasterVersion: 01.00 458 | ps4AppVersion: 01.00 459 | ps4AppType: 0 460 | ps4ParamSfxPath: 461 | ps4VideoOutPixelFormat: 0 462 | ps4VideoOutInitialWidth: 1920 463 | ps4VideoOutBaseModeInitialWidth: 1920 464 | ps4VideoOutReprojectionRate: 60 465 | ps4PronunciationXMLPath: 466 | ps4PronunciationSIGPath: 467 | ps4BackgroundImagePath: 468 | ps4StartupImagePath: 469 | ps4StartupImagesFolder: 470 | ps4IconImagesFolder: 471 | ps4SaveDataImagePath: 472 | ps4SdkOverride: 473 | ps4BGMPath: 474 | ps4ShareFilePath: 475 | ps4ShareOverlayImagePath: 476 | ps4PrivacyGuardImagePath: 477 | ps4NPtitleDatPath: 478 | ps4RemotePlayKeyAssignment: -1 479 | ps4RemotePlayKeyMappingDir: 480 | ps4PlayTogetherPlayerCount: 0 481 | ps4EnterButtonAssignment: 1 482 | ps4ApplicationParam1: 0 483 | ps4ApplicationParam2: 0 484 | ps4ApplicationParam3: 0 485 | ps4ApplicationParam4: 0 486 | ps4DownloadDataSize: 0 487 | ps4GarlicHeapSize: 2048 488 | ps4ProGarlicHeapSize: 2560 489 | playerPrefsMaxSize: 32768 490 | ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ 491 | ps4pnSessions: 1 492 | ps4pnPresence: 1 493 | ps4pnFriends: 1 494 | ps4pnGameCustomData: 1 495 | playerPrefsSupport: 0 496 | enableApplicationExit: 0 497 | resetTempFolder: 1 498 | restrictedAudioUsageRights: 0 499 | ps4UseResolutionFallback: 0 500 | ps4ReprojectionSupport: 0 501 | ps4UseAudio3dBackend: 0 502 | ps4UseLowGarlicFragmentationMode: 1 503 | ps4SocialScreenEnabled: 0 504 | ps4ScriptOptimizationLevel: 0 505 | ps4Audio3dVirtualSpeakerCount: 14 506 | ps4attribCpuUsage: 0 507 | ps4PatchPkgPath: 508 | ps4PatchLatestPkgPath: 509 | ps4PatchChangeinfoPath: 510 | ps4PatchDayOne: 0 511 | ps4attribUserManagement: 0 512 | ps4attribMoveSupport: 0 513 | ps4attrib3DSupport: 0 514 | ps4attribShareSupport: 0 515 | ps4attribExclusiveVR: 0 516 | ps4disableAutoHideSplash: 0 517 | ps4videoRecordingFeaturesUsed: 0 518 | ps4contentSearchFeaturesUsed: 0 519 | ps4attribEyeToEyeDistanceSettingVR: 0 520 | ps4IncludedModules: [] 521 | ps4attribVROutputEnabled: 0 522 | monoEnv: 523 | splashScreenBackgroundSourceLandscape: {fileID: 0} 524 | splashScreenBackgroundSourcePortrait: {fileID: 0} 525 | blurSplashScreenBackground: 1 526 | spritePackerPolicy: 527 | webGLMemorySize: 16 528 | webGLExceptionSupport: 1 529 | webGLNameFilesAsHashes: 0 530 | webGLDataCaching: 1 531 | webGLDebugSymbols: 0 532 | webGLEmscriptenArgs: 533 | webGLModulesDirectory: 534 | webGLTemplate: APPLICATION:Default 535 | webGLAnalyzeBuildSize: 0 536 | webGLUseEmbeddedResources: 0 537 | webGLCompressionFormat: 1 538 | webGLLinkerTarget: 1 539 | webGLThreadsSupport: 0 540 | webGLWasmStreaming: 0 541 | scriptingDefineSymbols: {} 542 | platformArchitecture: {} 543 | scriptingBackend: {} 544 | il2cppCompilerConfiguration: {} 545 | managedStrippingLevel: {} 546 | incrementalIl2cppBuild: {} 547 | allowUnsafeCode: 0 548 | additionalIl2CppArgs: 549 | scriptingRuntimeVersion: 1 550 | gcIncremental: 0 551 | gcWBarrierValidation: 0 552 | apiCompatibilityLevelPerPlatform: {} 553 | m_RenderingPath: 1 554 | m_MobileRenderingPath: 1 555 | metroPackageName: Template_2D 556 | metroPackageVersion: 557 | metroCertificatePath: 558 | metroCertificatePassword: 559 | metroCertificateSubject: 560 | metroCertificateIssuer: 561 | metroCertificateNotAfter: 0000000000000000 562 | metroApplicationDescription: Template_2D 563 | wsaImages: {} 564 | metroTileShortName: 565 | metroTileShowName: 0 566 | metroMediumTileShowName: 0 567 | metroLargeTileShowName: 0 568 | metroWideTileShowName: 0 569 | metroSupportStreamingInstall: 0 570 | metroLastRequiredScene: 0 571 | metroDefaultTileSize: 1 572 | metroTileForegroundText: 2 573 | metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} 574 | metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, 575 | a: 1} 576 | metroSplashScreenUseBackgroundColor: 0 577 | platformCapabilities: {} 578 | metroTargetDeviceFamilies: {} 579 | metroFTAName: 580 | metroFTAFileTypes: [] 581 | metroProtocolName: 582 | XboxOneProductId: 583 | XboxOneUpdateKey: 584 | XboxOneSandboxId: 585 | XboxOneContentId: 586 | XboxOneTitleId: 587 | XboxOneSCId: 588 | XboxOneGameOsOverridePath: 589 | XboxOnePackagingOverridePath: 590 | XboxOneAppManifestOverridePath: 591 | XboxOneVersion: 1.0.0.0 592 | XboxOnePackageEncryption: 0 593 | XboxOnePackageUpdateGranularity: 2 594 | XboxOneDescription: 595 | XboxOneLanguage: 596 | - enus 597 | XboxOneCapability: [] 598 | XboxOneGameRating: {} 599 | XboxOneIsContentPackage: 0 600 | XboxOneEnableGPUVariability: 1 601 | XboxOneSockets: {} 602 | XboxOneSplashScreen: {fileID: 0} 603 | XboxOneAllowedProductIds: [] 604 | XboxOnePersistentLocalStorageSize: 0 605 | XboxOneXTitleMemory: 8 606 | XboxOneOverrideIdentityName: 607 | XboxOneOverrideIdentityPublisher: 608 | vrEditorSettings: 609 | daydream: 610 | daydreamIconForeground: {fileID: 0} 611 | daydreamIconBackground: {fileID: 0} 612 | cloudServicesEnabled: 613 | UNet: 1 614 | luminIcon: 615 | m_Name: 616 | m_ModelFolderPath: 617 | m_PortalFolderPath: 618 | luminCert: 619 | m_CertPath: 620 | m_SignPackage: 1 621 | luminIsChannelApp: 0 622 | luminVersion: 623 | m_VersionCode: 1 624 | m_VersionName: 625 | apiCompatibilityLevel: 6 626 | cloudProjectId: 627 | framebufferDepthMemorylessMode: 0 628 | projectName: 629 | organizationId: 630 | cloudEnabled: 0 631 | enableNativePlatformBackendsForNewInputSystem: 0 632 | disableOldInputManagerSupport: 0 633 | legacyClampBlendShapeWeights: 0 634 | -------------------------------------------------------------------------------- /ProjectSettings/ProjectVersion.txt: -------------------------------------------------------------------------------- 1 | m_EditorVersion: 2019.4.0f1 2 | m_EditorVersionWithRevision: 2019.4.0f1 (0af376155913) 3 | -------------------------------------------------------------------------------- /ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!47 &1 4 | QualitySettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 5 7 | m_CurrentQuality: 3 8 | m_QualitySettings: 9 | - serializedVersion: 2 10 | name: Very Low 11 | pixelLightCount: 0 12 | shadows: 0 13 | shadowResolution: 0 14 | shadowProjection: 1 15 | shadowCascades: 1 16 | shadowDistance: 15 17 | shadowNearPlaneOffset: 3 18 | shadowCascade2Split: 0.33333334 19 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 20 | shadowmaskMode: 0 21 | blendWeights: 1 22 | textureQuality: 1 23 | anisotropicTextures: 0 24 | antiAliasing: 0 25 | softParticles: 0 26 | softVegetation: 0 27 | realtimeReflectionProbes: 0 28 | billboardsFaceCameraPosition: 0 29 | vSyncCount: 0 30 | lodBias: 0.3 31 | maximumLODLevel: 0 32 | particleRaycastBudget: 4 33 | asyncUploadTimeSlice: 2 34 | asyncUploadBufferSize: 16 35 | resolutionScalingFixedDPIFactor: 1 36 | excludedTargetPlatforms: [] 37 | - serializedVersion: 2 38 | name: Low 39 | pixelLightCount: 0 40 | shadows: 0 41 | shadowResolution: 0 42 | shadowProjection: 1 43 | shadowCascades: 1 44 | shadowDistance: 20 45 | shadowNearPlaneOffset: 3 46 | shadowCascade2Split: 0.33333334 47 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 48 | shadowmaskMode: 0 49 | blendWeights: 2 50 | textureQuality: 0 51 | anisotropicTextures: 0 52 | antiAliasing: 0 53 | softParticles: 0 54 | softVegetation: 0 55 | realtimeReflectionProbes: 0 56 | billboardsFaceCameraPosition: 0 57 | vSyncCount: 0 58 | lodBias: 0.4 59 | maximumLODLevel: 0 60 | particleRaycastBudget: 16 61 | asyncUploadTimeSlice: 2 62 | asyncUploadBufferSize: 16 63 | resolutionScalingFixedDPIFactor: 1 64 | excludedTargetPlatforms: [] 65 | - serializedVersion: 2 66 | name: Medium 67 | pixelLightCount: 1 68 | shadows: 0 69 | shadowResolution: 0 70 | shadowProjection: 1 71 | shadowCascades: 1 72 | shadowDistance: 20 73 | shadowNearPlaneOffset: 3 74 | shadowCascade2Split: 0.33333334 75 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 76 | shadowmaskMode: 0 77 | blendWeights: 2 78 | textureQuality: 0 79 | anisotropicTextures: 0 80 | antiAliasing: 0 81 | softParticles: 0 82 | softVegetation: 0 83 | realtimeReflectionProbes: 0 84 | billboardsFaceCameraPosition: 0 85 | vSyncCount: 1 86 | lodBias: 0.7 87 | maximumLODLevel: 0 88 | particleRaycastBudget: 64 89 | asyncUploadTimeSlice: 2 90 | asyncUploadBufferSize: 16 91 | resolutionScalingFixedDPIFactor: 1 92 | excludedTargetPlatforms: [] 93 | - serializedVersion: 2 94 | name: High 95 | pixelLightCount: 2 96 | shadows: 0 97 | shadowResolution: 1 98 | shadowProjection: 1 99 | shadowCascades: 2 100 | shadowDistance: 40 101 | shadowNearPlaneOffset: 3 102 | shadowCascade2Split: 0.33333334 103 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 104 | shadowmaskMode: 1 105 | blendWeights: 2 106 | textureQuality: 0 107 | anisotropicTextures: 0 108 | antiAliasing: 0 109 | softParticles: 0 110 | softVegetation: 1 111 | realtimeReflectionProbes: 0 112 | billboardsFaceCameraPosition: 0 113 | vSyncCount: 1 114 | lodBias: 1 115 | maximumLODLevel: 0 116 | particleRaycastBudget: 256 117 | asyncUploadTimeSlice: 2 118 | asyncUploadBufferSize: 16 119 | resolutionScalingFixedDPIFactor: 1 120 | excludedTargetPlatforms: [] 121 | - serializedVersion: 2 122 | name: Very High 123 | pixelLightCount: 3 124 | shadows: 0 125 | shadowResolution: 2 126 | shadowProjection: 1 127 | shadowCascades: 2 128 | shadowDistance: 70 129 | shadowNearPlaneOffset: 3 130 | shadowCascade2Split: 0.33333334 131 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 132 | shadowmaskMode: 1 133 | blendWeights: 4 134 | textureQuality: 0 135 | anisotropicTextures: 0 136 | antiAliasing: 0 137 | softParticles: 0 138 | softVegetation: 1 139 | realtimeReflectionProbes: 0 140 | billboardsFaceCameraPosition: 0 141 | vSyncCount: 1 142 | lodBias: 1.5 143 | maximumLODLevel: 0 144 | particleRaycastBudget: 1024 145 | asyncUploadTimeSlice: 2 146 | asyncUploadBufferSize: 16 147 | resolutionScalingFixedDPIFactor: 1 148 | excludedTargetPlatforms: [] 149 | - serializedVersion: 2 150 | name: Ultra 151 | pixelLightCount: 4 152 | shadows: 0 153 | shadowResolution: 0 154 | shadowProjection: 1 155 | shadowCascades: 4 156 | shadowDistance: 150 157 | shadowNearPlaneOffset: 3 158 | shadowCascade2Split: 0.33333334 159 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 160 | shadowmaskMode: 1 161 | blendWeights: 4 162 | textureQuality: 0 163 | anisotropicTextures: 0 164 | antiAliasing: 0 165 | softParticles: 0 166 | softVegetation: 1 167 | realtimeReflectionProbes: 0 168 | billboardsFaceCameraPosition: 0 169 | vSyncCount: 1 170 | lodBias: 2 171 | maximumLODLevel: 0 172 | particleRaycastBudget: 4096 173 | asyncUploadTimeSlice: 2 174 | asyncUploadBufferSize: 16 175 | resolutionScalingFixedDPIFactor: 1 176 | excludedTargetPlatforms: [] 177 | m_PerPlatformDefaultQuality: 178 | Android: 2 179 | Nintendo 3DS: 5 180 | Nintendo Switch: 5 181 | PS4: 5 182 | PSM: 5 183 | PSP2: 2 184 | Stadia: 5 185 | Standalone: 5 186 | Tizen: 2 187 | WebGL: 3 188 | WiiU: 5 189 | Windows Store Apps: 5 190 | XboxOne: 5 191 | iPhone: 2 192 | tvOS: 2 193 | -------------------------------------------------------------------------------- /ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!78 &1 4 | TagManager: 5 | serializedVersion: 2 6 | tags: [] 7 | layers: 8 | - Default 9 | - TransparentFX 10 | - Ignore Raycast 11 | - 12 | - Water 13 | - UI 14 | - 15 | - 16 | - 17 | - 18 | - 19 | - 20 | - 21 | - 22 | - 23 | - 24 | - 25 | - 26 | - 27 | - 28 | - 29 | - 30 | - 31 | - 32 | - 33 | - 34 | - 35 | - 36 | - 37 | - 38 | - 39 | - 40 | m_SortingLayers: 41 | - name: Default 42 | uniqueID: 0 43 | locked: 0 44 | -------------------------------------------------------------------------------- /ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!5 &1 4 | TimeManager: 5 | m_ObjectHideFlags: 0 6 | Fixed Timestep: 0.02 7 | Maximum Allowed Timestep: 0.1 8 | m_TimeScale: 1 9 | Maximum Particle Timestep: 0.03 10 | -------------------------------------------------------------------------------- /ProjectSettings/UnityConnectSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!310 &1 4 | UnityConnectSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 1 7 | m_Enabled: 0 8 | m_TestMode: 0 9 | m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events 10 | m_EventUrl: https://cdp.cloud.unity3d.com/v1/events 11 | m_ConfigUrl: https://config.uca.cloud.unity3d.com 12 | m_TestInitMode: 0 13 | CrashReportingSettings: 14 | m_EventUrl: https://perf-events.cloud.unity3d.com 15 | m_Enabled: 0 16 | m_LogBufferSize: 10 17 | m_CaptureEditorExceptions: 1 18 | UnityPurchasingSettings: 19 | m_Enabled: 0 20 | m_TestMode: 0 21 | UnityAnalyticsSettings: 22 | m_Enabled: 0 23 | m_TestMode: 0 24 | m_InitializeOnStartup: 1 25 | UnityAdsSettings: 26 | m_Enabled: 0 27 | m_InitializeOnStartup: 1 28 | m_TestMode: 0 29 | m_IosGameId: 30 | m_AndroidGameId: 31 | m_GameIds: {} 32 | m_GameId: 33 | PerformanceReportingSettings: 34 | m_Enabled: 0 35 | -------------------------------------------------------------------------------- /ProjectSettings/VFXManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!937362698 &1 4 | VFXManager: 5 | m_ObjectHideFlags: 0 6 | m_IndirectShader: {fileID: 0} 7 | m_CopyBufferShader: {fileID: 0} 8 | m_SortShader: {fileID: 0} 9 | m_StripUpdateShader: {fileID: 0} 10 | m_RenderPipeSettingsPath: 11 | m_FixedTimeStep: 0.016666668 12 | m_MaxDeltaTime: 0.05 13 | -------------------------------------------------------------------------------- /ProjectSettings/XRSettings.asset: -------------------------------------------------------------------------------- 1 | { 2 | "m_SettingKeys": [ 3 | "VR Device Disabled", 4 | "VR Device User Alert" 5 | ], 6 | "m_SettingValues": [ 7 | "False", 8 | "False" 9 | ] 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example of loading mp3 at runtime in Unity 2 | 3 | I am not a unity expert but apparently Unity doesn't 4 | load mp3s at runtime. 5 | 6 | This example uses the [NLayer library](https://github.com/naudio/NLayer) which is just enough to decode an mp3. 7 | 8 | Note: It does not stream mp3s. Instead, first the entire mp3 file will be downloaded. It will then be converted 9 | to raw audio data. That data is then passed to Unity. 10 | 11 | The relevant code is in `AudioLoader.cs` 12 | 13 | ``` 14 | IEnumerator LoadHelper(string uri) 15 | { 16 | UnityWebRequest www = UnityWebRequest.Get(uri); 17 | yield return www.SendWebRequest(); 18 | 19 | if(www.isNetworkError || www.isHttpError) 20 | { 21 | Debug.Log(www.error); 22 | } 23 | else 24 | { 25 | // Or retrieve results as binary data 26 | byte[] results = www.downloadHandler.data; 27 | var memStream = new System.IO.MemoryStream(results); 28 | var mpgFile = new NLayer.MpegFile(memStream); 29 | var samples = new float[mpgFile.Length]; 30 | mpgFile.ReadSamples(samples, 0, (int)mpgFile.Length); 31 | 32 | var clip = AudioClip.Create("foo", samples.Length, mpgFile.Channels, mpgFile.SampleRate, false); 33 | clip.SetData(samples, 0); 34 | source.clip = clip; 35 | } 36 | } 37 | ``` 38 | 39 | Tested on Windows and on [a WebGL build](https://greggman.github.io/unity-load-mp3-at-runtime/) in 2019.4.1. (man I want to punch Unity in the face for that stupid naming! 😆) 40 | 41 | ## Notes 42 | 43 | 1. I have no idea what the implications of mp3 licensing are 44 | 45 | 2. The NLayer code is licensed under the MIT License as is this example 46 | 47 | 3. I originally tried the [MP3Stream library](https://github.com/ZaneDubya/MP3Sharp). 48 | It works but it is [LGPL](https://github.com/ZaneDubya/MP3Sharp/blob/master/license.txt) 49 | which AFAICT is entirely incompatible with how Unity works. The LGPL requires 50 | linking at runtime via dynamic libraries. Unity doesn't support that. Instead 51 | it re-builds the library into your app. So, you can't ship a game made with 52 | Unity using the MP3Sharp library unless you either (a) license your game as 53 | LGPL or GPL... not sure that's even possible or (b) you find some creative way to keep 54 | the MP3Stream code separate and load it at runtime. 55 | --------------------------------------------------------------------------------