@@ -284,7 +284,8 @@ namespace Dreamteck.Splines
|
||||
splineLocalPosition.y = applyPositionY ? 0f : splineLocalPosition.y;
|
||||
splineLocalPosition.z = applyPositionZ ? 0f : splineLocalPosition.z;
|
||||
inputPosition = matrix.MultiplyPoint3x4(splineLocalPosition);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (applyPositionX) inputPosition.x = position.x;
|
||||
if (applyPositionY) inputPosition.y = position.y;
|
||||
@@ -295,7 +296,9 @@ namespace Dreamteck.Splines
|
||||
|
||||
private Quaternion GetRotation(Quaternion inputRotation)
|
||||
{
|
||||
|
||||
rotation = Quaternion.LookRotation(_splineResult.forward * (direction == Spline.Direction.Forward ? 1f : -1f), _splineResult.up);
|
||||
//Debug.Log(_splineResult.forward * (direction == Spline.Direction.Forward ? 1f : -1f) + " " + _splineResult.up);
|
||||
if (_2dMode)
|
||||
{
|
||||
if (applyRotation2D)
|
||||
@@ -321,7 +324,8 @@ namespace Dreamteck.Splines
|
||||
targetEuler.y = applyRotationY ? 0f : targetEuler.y;
|
||||
targetEuler.z = applyRotationZ ? 0f : targetEuler.z;
|
||||
inputRotation = rotation * Quaternion.Euler(targetEuler);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!applyRotationX || !applyRotationY || !applyRotationZ)
|
||||
{
|
||||
@@ -332,7 +336,7 @@ namespace Dreamteck.Splines
|
||||
if (!applyRotationZ) targetEuler.z = sourceEuler.z;
|
||||
inputRotation.eulerAngles = targetEuler;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
inputRotation = rotation;
|
||||
}
|
||||
@@ -351,7 +355,7 @@ namespace Dreamteck.Splines
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
|
||||
78448
Assets/FR2_Cache.asset
78448
Assets/FR2_Cache.asset
File diff suppressed because it is too large
Load Diff
8
Assets/NLayer.meta
Normal file
8
Assets/NLayer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2566d93840cc8da4ca2886d9a0b0465b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/NLayer/Decoder.meta
Normal file
8
Assets/NLayer/Decoder.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c0fb0fe987803141abcabe8c78915fe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
209
Assets/NLayer/Decoder/BitReservoir.cs
Normal file
209
Assets/NLayer/Decoder/BitReservoir.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
class BitReservoir
|
||||
{
|
||||
// Per the spec, the maximum buffer size for layer III is 7680 bits, which is 960 bytes.
|
||||
// The only catch is if we're decoding a "free" frame, which could be a lot more (since
|
||||
// some encoders allow higher bitrates to maintain audio transparency).
|
||||
byte[] _buf = new byte[8192];
|
||||
int _start = 0, _end = -1, _bitsLeft = 0;
|
||||
long _bitsRead = 0L;
|
||||
|
||||
static int GetSlots(IMpegFrame frame)
|
||||
{
|
||||
var cnt = frame.FrameLength - 4;
|
||||
if (frame.HasCrc) cnt -= 2;
|
||||
|
||||
if (frame.Version == MpegVersion.Version1 && frame.ChannelMode != MpegChannelMode.Mono) return cnt - 32;
|
||||
if (frame.Version > MpegVersion.Version1 && frame.ChannelMode == MpegChannelMode.Mono) return cnt - 9;
|
||||
return cnt - 17;
|
||||
|
||||
}
|
||||
|
||||
public bool AddBits(IMpegFrame frame, int overlap)
|
||||
{
|
||||
var originalEnd = _end;
|
||||
|
||||
var slots = GetSlots(frame);
|
||||
while (--slots >= 0)
|
||||
{
|
||||
var temp = frame.ReadBits(8);
|
||||
if (temp == -1) throw new System.IO.InvalidDataException("Frame did not have enough bytes!");
|
||||
_buf[++_end] = (byte)temp;
|
||||
if (_end == _buf.Length - 1) _end = -1;
|
||||
}
|
||||
|
||||
_bitsLeft = 8;
|
||||
if (originalEnd == -1)
|
||||
{
|
||||
// it's either the start of the stream or we've reset... only return true if overlap says this frame is enough
|
||||
return overlap == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's not the start of the stream so calculate _start based on whether we have enough bytes left
|
||||
|
||||
// if we have enough bytes, reset start to match overlap
|
||||
if ((originalEnd + 1 - _start + _buf.Length) % _buf.Length >= overlap)
|
||||
{
|
||||
_start = (originalEnd + 1 - overlap + _buf.Length) % _buf.Length;
|
||||
return true;
|
||||
}
|
||||
// otherwise, just set start to match the start of the frame (we probably skipped a frame)
|
||||
else
|
||||
{
|
||||
_start = originalEnd + overlap;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetBits(int count)
|
||||
{
|
||||
int bitsRead;
|
||||
var bits = TryPeekBits(count, out bitsRead);
|
||||
if (bitsRead < count) throw new System.IO.InvalidDataException("Reservoir did not have enough bytes!");
|
||||
|
||||
SkipBits(count);
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public int Get1Bit()
|
||||
{
|
||||
// this is an optimized single-bit reader
|
||||
if (_bitsLeft == 0) throw new System.IO.InvalidDataException("Reservoir did not have enough bytes!");
|
||||
|
||||
--_bitsLeft;
|
||||
++_bitsRead;
|
||||
var val = (_buf[_start] >> _bitsLeft) & 1;
|
||||
|
||||
if (_bitsLeft == 0 && (_start = (_start + 1) % _buf.Length) != _end + 1)
|
||||
{
|
||||
_bitsLeft = 8;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public int TryPeekBits(int count, out int readCount)
|
||||
{
|
||||
if (count < 0 || count > 32) throw new ArgumentOutOfRangeException("count", "Must return between 0 and 32 bits!");
|
||||
|
||||
// if we don't have any bits left, just return no bits read
|
||||
if (_bitsLeft == 0 || count == 0)
|
||||
{
|
||||
readCount = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get bits from the current start of the reservoir
|
||||
var bits = (int)_buf[_start];
|
||||
if (count < _bitsLeft)
|
||||
{
|
||||
// just grab the bits, adjust the "left" count, and return
|
||||
bits >>= _bitsLeft - count;
|
||||
bits &= ((1 << count) - 1);
|
||||
readCount = count;
|
||||
return bits;
|
||||
}
|
||||
|
||||
// we have to do it the hard way...
|
||||
bits &= ((1 << _bitsLeft) - 1);
|
||||
count -= _bitsLeft;
|
||||
readCount = _bitsLeft;
|
||||
|
||||
var resStart = _start;
|
||||
|
||||
// arg... gotta grab some more bits...
|
||||
while (count > 0)
|
||||
{
|
||||
// advance the start marker, and if we just advanced it past the end of the buffer, bail
|
||||
if ((resStart = (resStart + 1) % _buf.Length) == _end + 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// figure out how many bits to pull from it
|
||||
var bitsToRead = Math.Min(count, 8);
|
||||
|
||||
// move the existing bits over
|
||||
bits <<= bitsToRead;
|
||||
bits |= (_buf[resStart] >> ((8 - bitsToRead) % 8));
|
||||
|
||||
// update our count
|
||||
count -= bitsToRead;
|
||||
|
||||
// update our remaining bits
|
||||
readCount += bitsToRead;
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public int BitsAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_bitsLeft > 0)
|
||||
{
|
||||
return (((_end + _buf.Length) - _start) % _buf.Length) * 8 + _bitsLeft;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public long BitsRead
|
||||
{
|
||||
get { return _bitsRead; }
|
||||
}
|
||||
|
||||
public void SkipBits(int count)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
// make sure we have enough bits to skip
|
||||
if (count > BitsAvailable) throw new ArgumentOutOfRangeException("count");
|
||||
|
||||
// now calculate the new positions
|
||||
var offset = (8 - _bitsLeft) + count;
|
||||
_start = ((offset / 8) + _start) % _buf.Length;
|
||||
_bitsLeft = 8 - (offset % 8);
|
||||
|
||||
_bitsRead += count;
|
||||
}
|
||||
}
|
||||
|
||||
public void RewindBits(int count)
|
||||
{
|
||||
_bitsLeft += count;
|
||||
_bitsRead -= count;
|
||||
while (_bitsLeft > 8)
|
||||
{
|
||||
--_start;
|
||||
_bitsLeft -= 8;
|
||||
}
|
||||
while (_start < 0)
|
||||
{
|
||||
_start += _buf.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public void FlushBits()
|
||||
{
|
||||
if (_bitsLeft < 8)
|
||||
{
|
||||
SkipBits(_bitsLeft);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_start = 0;
|
||||
_end = -1;
|
||||
_bitsLeft = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/BitReservoir.cs.meta
Normal file
11
Assets/NLayer/Decoder/BitReservoir.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6509f5ab6766c1c4d89e1ba31de2cba0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
98
Assets/NLayer/Decoder/FrameBase.cs
Normal file
98
Assets/NLayer/Decoder/FrameBase.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
abstract class FrameBase
|
||||
{
|
||||
static int _totalAllocation = 0;
|
||||
static internal int TotalAllocation
|
||||
{
|
||||
get { return System.Threading.Interlocked.CompareExchange(ref _totalAllocation, 0, 0); }
|
||||
}
|
||||
|
||||
internal long Offset { get; private set; }
|
||||
internal int Length { get; set; }
|
||||
|
||||
MpegStreamReader _reader;
|
||||
|
||||
byte[] _savedBuffer;
|
||||
|
||||
protected FrameBase() { }
|
||||
|
||||
internal bool Validate(long offset, MpegStreamReader reader)
|
||||
{
|
||||
Offset = offset;
|
||||
_reader = reader;
|
||||
|
||||
var len = Validate();
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
Length = len;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected int Read(int offset, byte[] buffer)
|
||||
{
|
||||
return Read(offset, buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
protected int Read(int offset, byte[] buffer, int index, int count)
|
||||
{
|
||||
if (_savedBuffer != null)
|
||||
{
|
||||
if (index < 0 || index + count > buffer.Length) return 0; // check against caller's buffer
|
||||
if (offset < 0 || offset >= _savedBuffer.Length) return 0; // check against saved buffer
|
||||
if (offset + count > _savedBuffer.Length) count = _savedBuffer.Length - index; // twiddle the size as needed
|
||||
|
||||
Array.Copy(_savedBuffer, offset, buffer, index, count);
|
||||
return count;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _reader.Read(Offset + offset, buffer, index, count);
|
||||
}
|
||||
}
|
||||
|
||||
protected int ReadByte(int offset)
|
||||
{
|
||||
if (_savedBuffer != null)
|
||||
{
|
||||
if (offset < 0) throw new ArgumentOutOfRangeException();
|
||||
if (offset >= _savedBuffer.Length) return -1;
|
||||
|
||||
return (int)_savedBuffer[offset];
|
||||
}
|
||||
else
|
||||
{
|
||||
return _reader.ReadByte(Offset + offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to validate the frame header
|
||||
/// </summary>
|
||||
/// <returns>The length of the frame, or -1 if frame is invalid</returns>
|
||||
abstract protected int Validate();
|
||||
|
||||
internal void SaveBuffer()
|
||||
{
|
||||
_savedBuffer = new byte[Length];
|
||||
_reader.Read(Offset, _savedBuffer, 0, Length);
|
||||
System.Threading.Interlocked.Add(ref _totalAllocation, Length);
|
||||
}
|
||||
|
||||
internal void ClearBuffer()
|
||||
{
|
||||
System.Threading.Interlocked.Add(ref _totalAllocation, -Length);
|
||||
_savedBuffer = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the stream is not "seek-able"
|
||||
/// </summary>
|
||||
virtual internal void Parse() { }
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/FrameBase.cs.meta
Normal file
11
Assets/NLayer/Decoder/FrameBase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c67e024d60626842b00f2248877db3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1066
Assets/NLayer/Decoder/Huffman.cs
Normal file
1066
Assets/NLayer/Decoder/Huffman.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/NLayer/Decoder/Huffman.cs.meta
Normal file
11
Assets/NLayer/Decoder/Huffman.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e67d41da55d301a4681b6a496cf52dd9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
205
Assets/NLayer/Decoder/ID3Frame.cs
Normal file
205
Assets/NLayer/Decoder/ID3Frame.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
class ID3Frame : FrameBase
|
||||
{
|
||||
internal static ID3Frame TrySync(uint syncMark)
|
||||
{
|
||||
if ((syncMark & 0xFFFFFF00U) == 0x49443300)
|
||||
{
|
||||
return new ID3Frame { _version = 2 };
|
||||
}
|
||||
|
||||
if ((syncMark & 0xFFFFFF00U) == 0x54414700)
|
||||
{
|
||||
if ((syncMark & 0xFF) == 0x2B)
|
||||
{
|
||||
return new ID3Frame { _version = 1 };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ID3Frame { _version = 0 };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
int _version;
|
||||
|
||||
ID3Frame()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override int Validate()
|
||||
{
|
||||
switch (_version)
|
||||
{
|
||||
case 2:
|
||||
// v2, yay!
|
||||
var buf = new byte[7];
|
||||
if (Read(3, buf) == 7)
|
||||
{
|
||||
byte flagsMask;
|
||||
switch (buf[0])
|
||||
{
|
||||
case 2:
|
||||
flagsMask = 0x3F;
|
||||
break;
|
||||
case 3:
|
||||
flagsMask = 0x1F;
|
||||
break;
|
||||
case 4:
|
||||
flagsMask = 0x0F;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ignore the flags (we don't need them for the validation)
|
||||
|
||||
// get the size (7 bits per byte [MSB cleared])
|
||||
var size = (buf[3] << 21)
|
||||
| (buf[4] << 14)
|
||||
| (buf[5] << 7)
|
||||
| (buf[6]);
|
||||
|
||||
// finally, check to make sure that all the right bits are cleared
|
||||
if (!(((buf[2] & flagsMask) | (buf[3] & 0x80) | (buf[4] & 0x80) | (buf[5] & 0x80) | (buf[6] & 0x80)) != 0 || buf[1] == 0xFF))
|
||||
{
|
||||
return size + 10; // don't forget the sync, flag & size bytes!
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
return 227 + 128;
|
||||
case 0:
|
||||
return 128;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
internal override void Parse()
|
||||
{
|
||||
// assume we have to process it now or else... we can still read the whole frame, so no biggie
|
||||
switch (_version)
|
||||
{
|
||||
case 2:
|
||||
ParseV2();
|
||||
break;
|
||||
case 1:
|
||||
ParseV1Enh();
|
||||
break;
|
||||
case 0:
|
||||
ParseV1(3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ParseV1(int offset)
|
||||
{
|
||||
//var buffer = new byte[125];
|
||||
//if (Read(offset, buffer) == 125)
|
||||
//{
|
||||
// // v1 tags use ASCII encoding... For now we'll use the built-in encoding, but for Win8 we'll have to build our own.
|
||||
// var encoding = Encoding.ASCII;
|
||||
//
|
||||
// // title (30)
|
||||
// Title = encoding.GetString(buffer, 0, 30);
|
||||
//
|
||||
// // artist (30)
|
||||
// Artist = encoding.GetString(buffer, 30, 30);
|
||||
//
|
||||
// // album (30)
|
||||
// Album = encoding.GetString(buffer, 60, 30);
|
||||
//
|
||||
// // year (4)
|
||||
// Year = encoding.GetString(buffer, 90, 30);
|
||||
//
|
||||
// // comment (30)*
|
||||
// Comment = encoding.GetString(buffer, 94, 30);
|
||||
//
|
||||
// if (buffer[122] == 0)
|
||||
// {
|
||||
// // track (1)*
|
||||
// Track = (int)buffer[123];
|
||||
// }
|
||||
//
|
||||
// // genre (1)
|
||||
// // ignore for now
|
||||
//
|
||||
// // * if byte 29 of comment is 0, track is byte 30. Otherwise, track is unknown.
|
||||
//}
|
||||
}
|
||||
|
||||
void ParseV1Enh()
|
||||
{
|
||||
ParseV1(230);
|
||||
|
||||
//var buffer = new byte[223];
|
||||
//if (Read(4, buffer) == 223)
|
||||
//{
|
||||
// // v1 tags use ASCII encoding... For now we'll use the built-in encoding, but for Win8 we'll have to build our own.
|
||||
// var encoding = Encoding.ASCII;
|
||||
//
|
||||
// // title (60)
|
||||
// Title += encoding.GetString(buffer, 0, 60);
|
||||
//
|
||||
// // artist (60)
|
||||
// Artist += encoding.GetString(buffer, 60, 60);
|
||||
//
|
||||
// // album (60)
|
||||
// Album += encoding.GetString(buffer, 120, 60);
|
||||
//
|
||||
// // speed (1)
|
||||
// //var speed = buffer[180];
|
||||
//
|
||||
// // genre (30)
|
||||
// Genre = encoding.GetString(buffer, 181, 30);
|
||||
//
|
||||
// // start-time (6)
|
||||
// // 211
|
||||
//
|
||||
// // end-time (6)
|
||||
// // 217
|
||||
//}
|
||||
}
|
||||
|
||||
void ParseV2()
|
||||
{
|
||||
// v2 is much more complicated than v1... don't worry about it for now
|
||||
// look for any merged frames, as well
|
||||
}
|
||||
|
||||
internal int Version
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_version == 0) return 1;
|
||||
return _version;
|
||||
}
|
||||
}
|
||||
|
||||
//internal string Title { get; private set; }
|
||||
//internal string Artist { get; private set; }
|
||||
//internal string Album { get; private set; }
|
||||
//internal string Year { get; private set; }
|
||||
//internal string Comment { get; private set; }
|
||||
//internal int Track { get; private set; }
|
||||
//internal string Genre { get; private set; }
|
||||
// speed
|
||||
//public TimeSpan StartTime { get; private set; }
|
||||
//public TimeSpan EndTime { get; private set; }
|
||||
|
||||
internal void Merge(ID3Frame newFrame)
|
||||
{
|
||||
// just save off the frame for parsing later
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/ID3Frame.cs.meta
Normal file
11
Assets/NLayer/Decoder/ID3Frame.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c0263a912bb5e3409a00a5d44e91944
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
444
Assets/NLayer/Decoder/LayerDecoderBase.cs
Normal file
444
Assets/NLayer/Decoder/LayerDecoderBase.cs
Normal file
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* NLayer - A C# MPEG1/2/2.5 audio decoder
|
||||
*
|
||||
* Portions of this file are courtesy Fluendo, S.A. They are dual licensed as Ms-PL
|
||||
* and under the following license:
|
||||
*
|
||||
* Copyright <2005-2012> Fluendo S.A.
|
||||
*
|
||||
* Unless otherwise indicated, Source Code is licensed under MIT license.
|
||||
* See further explanation attached in License Statement (distributed in the file
|
||||
* LICENSE).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
abstract class LayerDecoderBase
|
||||
{
|
||||
protected const int SBLIMIT = 32;
|
||||
|
||||
const float INV_SQRT_2 = 7.071067811865474617150084668537e-01f;
|
||||
|
||||
#region Tables
|
||||
|
||||
static float[] DEWINDOW_TABLE = {
|
||||
0.000000000f, -0.000015259f, -0.000015259f, -0.000015259f,
|
||||
-0.000015259f, -0.000015259f, -0.000015259f, -0.000030518f,
|
||||
-0.000030518f, -0.000030518f, -0.000030518f, -0.000045776f,
|
||||
-0.000045776f, -0.000061035f, -0.000061035f, -0.000076294f,
|
||||
-0.000076294f, -0.000091553f, -0.000106812f, -0.000106812f,
|
||||
-0.000122070f, -0.000137329f, -0.000152588f, -0.000167847f,
|
||||
-0.000198364f, -0.000213623f, -0.000244141f, -0.000259399f,
|
||||
-0.000289917f, -0.000320435f, -0.000366211f, -0.000396729f,
|
||||
-0.000442505f, -0.000473022f, -0.000534058f, -0.000579834f,
|
||||
-0.000625610f, -0.000686646f, -0.000747681f, -0.000808716f,
|
||||
-0.000885010f, -0.000961304f, -0.001037598f, -0.001113892f,
|
||||
-0.001205444f, -0.001296997f, -0.001388550f, -0.001480103f,
|
||||
-0.001586914f, -0.001693726f, -0.001785278f, -0.001907349f,
|
||||
-0.002014160f, -0.002120972f, -0.002243042f, -0.002349854f,
|
||||
-0.002456665f, -0.002578735f, -0.002685547f, -0.002792358f,
|
||||
-0.002899170f, -0.002990723f, -0.003082275f, -0.003173828f,
|
||||
0.003250122f, 0.003326416f, 0.003387451f, 0.003433228f,
|
||||
0.003463745f, 0.003479004f, 0.003479004f, 0.003463745f,
|
||||
0.003417969f, 0.003372192f, 0.003280640f, 0.003173828f,
|
||||
0.003051758f, 0.002883911f, 0.002700806f, 0.002487183f,
|
||||
0.002227783f, 0.001937866f, 0.001617432f, 0.001266479f,
|
||||
0.000869751f, 0.000442505f, -0.000030518f, -0.000549316f,
|
||||
-0.001098633f, -0.001693726f, -0.002334595f, -0.003005981f,
|
||||
-0.003723145f, -0.004486084f, -0.005294800f, -0.006118774f,
|
||||
-0.007003784f, -0.007919312f, -0.008865356f, -0.009841919f,
|
||||
-0.010848999f, -0.011886597f, -0.012939453f, -0.014022827f,
|
||||
-0.015121460f, -0.016235352f, -0.017349243f, -0.018463135f,
|
||||
-0.019577026f, -0.020690918f, -0.021789551f, -0.022857666f,
|
||||
-0.023910522f, -0.024932861f, -0.025909424f, -0.026840210f,
|
||||
-0.027725220f, -0.028533936f, -0.029281616f, -0.029937744f,
|
||||
-0.030532837f, -0.031005859f, -0.031387329f, -0.031661987f,
|
||||
-0.031814575f, -0.031845093f, -0.031738281f, -0.031478882f,
|
||||
0.031082153f, 0.030517578f, 0.029785156f, 0.028884888f,
|
||||
0.027801514f, 0.026535034f, 0.025085449f, 0.023422241f,
|
||||
0.021575928f, 0.019531250f, 0.017257690f, 0.014801025f,
|
||||
0.012115479f, 0.009231567f, 0.006134033f, 0.002822876f,
|
||||
-0.000686646f, -0.004394531f, -0.008316040f, -0.012420654f,
|
||||
-0.016708374f, -0.021179199f, -0.025817871f, -0.030609131f,
|
||||
-0.035552979f, -0.040634155f, -0.045837402f, -0.051132202f,
|
||||
-0.056533813f, -0.061996460f, -0.067520142f, -0.073059082f,
|
||||
-0.078628540f, -0.084182739f, -0.089706421f, -0.095169067f,
|
||||
-0.100540161f, -0.105819702f, -0.110946655f, -0.115921021f,
|
||||
-0.120697021f, -0.125259399f, -0.129562378f, -0.133590698f,
|
||||
-0.137298584f, -0.140670776f, -0.143676758f, -0.146255493f,
|
||||
-0.148422241f, -0.150115967f, -0.151306152f, -0.151962280f,
|
||||
-0.152069092f, -0.151596069f, -0.150497437f, -0.148773193f,
|
||||
-0.146362305f, -0.143264771f, -0.139450073f, -0.134887695f,
|
||||
-0.129577637f, -0.123474121f, -0.116577148f, -0.108856201f,
|
||||
0.100311279f, 0.090927124f, 0.080688477f, 0.069595337f,
|
||||
0.057617187f, 0.044784546f, 0.031082153f, 0.016510010f,
|
||||
0.001068115f, -0.015228271f, -0.032379150f, -0.050354004f,
|
||||
-0.069168091f, -0.088775635f, -0.109161377f, -0.130310059f,
|
||||
-0.152206421f, -0.174789429f, -0.198059082f, -0.221984863f,
|
||||
-0.246505737f, -0.271591187f, -0.297210693f, -0.323318481f,
|
||||
-0.349868774f, -0.376800537f, -0.404083252f, -0.431655884f,
|
||||
-0.459472656f, -0.487472534f, -0.515609741f, -0.543823242f,
|
||||
-0.572036743f, -0.600219727f, -0.628295898f, -0.656219482f,
|
||||
-0.683914185f, -0.711318970f, -0.738372803f, -0.765029907f,
|
||||
-0.791213989f, -0.816864014f, -0.841949463f, -0.866363525f,
|
||||
-0.890090942f, -0.913055420f, -0.935195923f, -0.956481934f,
|
||||
-0.976852417f, -0.996246338f, -1.014617920f, -1.031936646f,
|
||||
-1.048156738f, -1.063217163f, -1.077117920f, -1.089782715f,
|
||||
-1.101211548f, -1.111373901f, -1.120223999f, -1.127746582f,
|
||||
-1.133926392f, -1.138763428f, -1.142211914f, -1.144287109f,
|
||||
1.144989014f, 1.144287109f, 1.142211914f, 1.138763428f,
|
||||
1.133926392f, 1.127746582f, 1.120223999f, 1.111373901f,
|
||||
1.101211548f, 1.089782715f, 1.077117920f, 1.063217163f,
|
||||
1.048156738f, 1.031936646f, 1.014617920f, 0.996246338f,
|
||||
0.976852417f, 0.956481934f, 0.935195923f, 0.913055420f,
|
||||
0.890090942f, 0.866363525f, 0.841949463f, 0.816864014f,
|
||||
0.791213989f, 0.765029907f, 0.738372803f, 0.711318970f,
|
||||
0.683914185f, 0.656219482f, 0.628295898f, 0.600219727f,
|
||||
0.572036743f, 0.543823242f, 0.515609741f, 0.487472534f,
|
||||
0.459472656f, 0.431655884f, 0.404083252f, 0.376800537f,
|
||||
0.349868774f, 0.323318481f, 0.297210693f, 0.271591187f,
|
||||
0.246505737f, 0.221984863f, 0.198059082f, 0.174789429f,
|
||||
0.152206421f, 0.130310059f, 0.109161377f, 0.088775635f,
|
||||
0.069168091f, 0.050354004f, 0.032379150f, 0.015228271f,
|
||||
-0.001068115f, -0.016510010f, -0.031082153f, -0.044784546f,
|
||||
-0.057617187f, -0.069595337f, -0.080688477f, -0.090927124f,
|
||||
0.100311279f, 0.108856201f, 0.116577148f, 0.123474121f,
|
||||
0.129577637f, 0.134887695f, 0.139450073f, 0.143264771f,
|
||||
0.146362305f, 0.148773193f, 0.150497437f, 0.151596069f,
|
||||
0.152069092f, 0.151962280f, 0.151306152f, 0.150115967f,
|
||||
0.148422241f, 0.146255493f, 0.143676758f, 0.140670776f,
|
||||
0.137298584f, 0.133590698f, 0.129562378f, 0.125259399f,
|
||||
0.120697021f, 0.115921021f, 0.110946655f, 0.105819702f,
|
||||
0.100540161f, 0.095169067f, 0.089706421f, 0.084182739f,
|
||||
0.078628540f, 0.073059082f, 0.067520142f, 0.061996460f,
|
||||
0.056533813f, 0.051132202f, 0.045837402f, 0.040634155f,
|
||||
0.035552979f, 0.030609131f, 0.025817871f, 0.021179199f,
|
||||
0.016708374f, 0.012420654f, 0.008316040f, 0.004394531f,
|
||||
0.000686646f, -0.002822876f, -0.006134033f, -0.009231567f,
|
||||
-0.012115479f, -0.014801025f, -0.017257690f, -0.019531250f,
|
||||
-0.021575928f, -0.023422241f, -0.025085449f, -0.026535034f,
|
||||
-0.027801514f, -0.028884888f, -0.029785156f, -0.030517578f,
|
||||
0.031082153f, 0.031478882f, 0.031738281f, 0.031845093f,
|
||||
0.031814575f, 0.031661987f, 0.031387329f, 0.031005859f,
|
||||
0.030532837f, 0.029937744f, 0.029281616f, 0.028533936f,
|
||||
0.027725220f, 0.026840210f, 0.025909424f, 0.024932861f,
|
||||
0.023910522f, 0.022857666f, 0.021789551f, 0.020690918f,
|
||||
0.019577026f, 0.018463135f, 0.017349243f, 0.016235352f,
|
||||
0.015121460f, 0.014022827f, 0.012939453f, 0.011886597f,
|
||||
0.010848999f, 0.009841919f, 0.008865356f, 0.007919312f,
|
||||
0.007003784f, 0.006118774f, 0.005294800f, 0.004486084f,
|
||||
0.003723145f, 0.003005981f, 0.002334595f, 0.001693726f,
|
||||
0.001098633f, 0.000549316f, 0.000030518f, -0.000442505f,
|
||||
-0.000869751f, -0.001266479f, -0.001617432f, -0.001937866f,
|
||||
-0.002227783f, -0.002487183f, -0.002700806f, -0.002883911f,
|
||||
-0.003051758f, -0.003173828f, -0.003280640f, -0.003372192f,
|
||||
-0.003417969f, -0.003463745f, -0.003479004f, -0.003479004f,
|
||||
-0.003463745f, -0.003433228f, -0.003387451f, -0.003326416f,
|
||||
0.003250122f, 0.003173828f, 0.003082275f, 0.002990723f,
|
||||
0.002899170f, 0.002792358f, 0.002685547f, 0.002578735f,
|
||||
0.002456665f, 0.002349854f, 0.002243042f, 0.002120972f,
|
||||
0.002014160f, 0.001907349f, 0.001785278f, 0.001693726f,
|
||||
0.001586914f, 0.001480103f, 0.001388550f, 0.001296997f,
|
||||
0.001205444f, 0.001113892f, 0.001037598f, 0.000961304f,
|
||||
0.000885010f, 0.000808716f, 0.000747681f, 0.000686646f,
|
||||
0.000625610f, 0.000579834f, 0.000534058f, 0.000473022f,
|
||||
0.000442505f, 0.000396729f, 0.000366211f, 0.000320435f,
|
||||
0.000289917f, 0.000259399f, 0.000244141f, 0.000213623f,
|
||||
0.000198364f, 0.000167847f, 0.000152588f, 0.000137329f,
|
||||
0.000122070f, 0.000106812f, 0.000106812f, 0.000091553f,
|
||||
0.000076294f, 0.000076294f, 0.000061035f, 0.000061035f,
|
||||
0.000045776f, 0.000045776f, 0.000030518f, 0.000030518f,
|
||||
0.000030518f, 0.000030518f, 0.000015259f, 0.000015259f,
|
||||
0.000015259f, 0.000015259f, 0.000015259f, 0.000015259f
|
||||
};
|
||||
|
||||
static float[] SYNTH_COS64_TABLE = {
|
||||
5.0060299823519627260e-01f, 5.0241928618815567820e-01f, 5.0547095989754364798e-01f, 5.0979557910415917998e-01f,
|
||||
5.1544730992262455249e-01f, 5.2249861493968885462e-01f, 5.3104259108978413284e-01f, 5.4119610014619701222e-01f,
|
||||
5.5310389603444454210e-01f, 5.6694403481635768927e-01f, 5.8293496820613388554e-01f, 6.0134488693504528634e-01f,
|
||||
6.2250412303566482475e-01f, 6.4682178335999007679e-01f, 6.7480834145500567800e-01f, 7.0710678118654746172e-01f,
|
||||
7.4453627100229857749e-01f, 7.8815462345125020249e-01f, 8.3934964541552681272e-01f, 8.9997622313641556513e-01f,
|
||||
9.7256823786196078263e-01f, 1.0606776859903470633e+00f, 1.1694399334328846596e+00f, 1.3065629648763763537e+00f,
|
||||
1.4841646163141661852e+00f, 1.7224470982383341955e+00f, 2.0577810099534108446e+00f, 2.5629154477415054814e+00f,
|
||||
3.4076084184687189804e+00f, 5.1011486186891552563e+00f, 1.0190008123548032870e+01f
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
List<float[]> _synBuf = new List<float[]>(2);
|
||||
List<int> _bufOffset = new List<int>(2);
|
||||
|
||||
float[] _eq;
|
||||
|
||||
internal LayerDecoderBase()
|
||||
{
|
||||
StereoMode = StereoMode.Both;
|
||||
}
|
||||
|
||||
abstract internal int DecodeFrame(IMpegFrame frame, float[] ch0, float[] ch1);
|
||||
|
||||
internal void SetEQ(float[] eq)
|
||||
{
|
||||
if (eq == null || eq.Length == 32)
|
||||
{
|
||||
_eq = eq;
|
||||
}
|
||||
}
|
||||
|
||||
internal StereoMode StereoMode { get; set; }
|
||||
|
||||
virtual internal void ResetForSeek()
|
||||
{
|
||||
_synBuf.Clear();
|
||||
_bufOffset.Clear();
|
||||
}
|
||||
|
||||
float[] ippuv = new float[512];
|
||||
|
||||
protected void InversePolyPhase(int channel, float[] data)
|
||||
{
|
||||
float[] synBuf;
|
||||
int k;
|
||||
GetBufAndOffset(channel, out synBuf, out k);
|
||||
|
||||
if (_eq != null)
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
data[i] *= _eq[i];
|
||||
}
|
||||
}
|
||||
|
||||
DCT32(data, synBuf, k);
|
||||
|
||||
BuildUVec(ippuv, synBuf, k);
|
||||
|
||||
DewindowOutput(ippuv, data);
|
||||
}
|
||||
|
||||
void GetBufAndOffset(int channel, out float[] synBuf, out int k)
|
||||
{
|
||||
while (_synBuf.Count <= channel)
|
||||
{
|
||||
_synBuf.Add(new float[1024]);
|
||||
}
|
||||
|
||||
while (_bufOffset.Count <= channel)
|
||||
{
|
||||
_bufOffset.Add(0);
|
||||
}
|
||||
|
||||
synBuf = _synBuf[channel];
|
||||
k = _bufOffset[channel];
|
||||
|
||||
k = (k - 32) & 511;
|
||||
_bufOffset[channel] = k;
|
||||
}
|
||||
|
||||
float[] ei32 = new float[16], eo32 = new float[16], oi32 = new float[16], oo32 = new float[16];
|
||||
|
||||
void DCT32(float[] _in, float[] _out, int k)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
ei32[i] = _in[i] + _in[31 - i];
|
||||
oi32[i] = (_in[i] - _in[31 - i]) * SYNTH_COS64_TABLE[2 * i];
|
||||
}
|
||||
|
||||
DCT16(ei32, eo32);
|
||||
DCT16(oi32, oo32);
|
||||
|
||||
for (i = 0; i < 15; i++)
|
||||
{
|
||||
_out[2 * i + k] = eo32[i];
|
||||
_out[2 * i + 1 + k] = oo32[i] + oo32[i + 1];
|
||||
}
|
||||
_out[30 + k] = eo32[15];
|
||||
_out[31 + k] = oo32[15];
|
||||
}
|
||||
|
||||
float[] ei16 = new float[8], eo16 = new float[8], oi16 = new float[8], oo16 = new float[8];
|
||||
|
||||
void DCT16(float[] _in, float[] _out)
|
||||
{
|
||||
float a, b;
|
||||
|
||||
a = _in[0]; b = _in[15];
|
||||
ei16[0] = a + b;
|
||||
oi16[0] = (a - b) * SYNTH_COS64_TABLE[1];
|
||||
a = _in[1]; b = _in[14];
|
||||
ei16[1] = a + b;
|
||||
oi16[1] = (a - b) * SYNTH_COS64_TABLE[5];
|
||||
a = _in[2]; b = _in[13];
|
||||
ei16[2] = a + b;
|
||||
oi16[2] = (a - b) * SYNTH_COS64_TABLE[9];
|
||||
a = _in[3]; b = _in[12];
|
||||
ei16[3] = a + b;
|
||||
oi16[3] = (a - b) * SYNTH_COS64_TABLE[13];
|
||||
a = _in[4]; b = _in[11];
|
||||
ei16[4] = a + b;
|
||||
oi16[4] = (a - b) * SYNTH_COS64_TABLE[17];
|
||||
a = _in[5]; b = _in[10];
|
||||
ei16[5] = a + b;
|
||||
oi16[5] = (a - b) * SYNTH_COS64_TABLE[21];
|
||||
a = _in[6]; b = _in[9];
|
||||
ei16[6] = a + b;
|
||||
oi16[6] = (a - b) * SYNTH_COS64_TABLE[25];
|
||||
a = _in[7]; b = _in[8];
|
||||
ei16[7] = a + b;
|
||||
oi16[7] = (a - b) * SYNTH_COS64_TABLE[29];
|
||||
|
||||
DCT8(ei16, eo16);
|
||||
DCT8(oi16, oo16);
|
||||
|
||||
_out[0] = eo16[0];
|
||||
_out[1] = oo16[0] + oo16[1];
|
||||
_out[2] = eo16[1];
|
||||
_out[3] = oo16[1] + oo16[2];
|
||||
_out[4] = eo16[2];
|
||||
_out[5] = oo16[2] + oo16[3];
|
||||
_out[6] = eo16[3];
|
||||
_out[7] = oo16[3] + oo16[4];
|
||||
_out[8] = eo16[4];
|
||||
_out[9] = oo16[4] + oo16[5];
|
||||
_out[10] = eo16[5];
|
||||
_out[11] = oo16[5] + oo16[6];
|
||||
_out[12] = eo16[6];
|
||||
_out[13] = oo16[6] + oo16[7];
|
||||
_out[14] = eo16[7];
|
||||
_out[15] = oo16[7];
|
||||
}
|
||||
|
||||
float[] ei8 = new float[4], tmp8 = new float[6], oi8 = new float[4], oo8 = new float[4];
|
||||
|
||||
void DCT8(float[] _in, float[] _out)
|
||||
{
|
||||
/* Even indices */
|
||||
ei8[0] = _in[0] + _in[7];
|
||||
ei8[1] = _in[3] + _in[4];
|
||||
ei8[2] = _in[1] + _in[6];
|
||||
ei8[3] = _in[2] + _in[5];
|
||||
|
||||
tmp8[0] = ei8[0] + ei8[1];
|
||||
tmp8[1] = ei8[2] + ei8[3];
|
||||
tmp8[2] = (ei8[0] - ei8[1]) * SYNTH_COS64_TABLE[7];
|
||||
tmp8[3] = (ei8[2] - ei8[3]) * SYNTH_COS64_TABLE[23];
|
||||
tmp8[4] = (float)((tmp8[2] - tmp8[3]) * INV_SQRT_2);
|
||||
|
||||
_out[0] = tmp8[0] + tmp8[1];
|
||||
_out[2] = tmp8[2] + tmp8[3] + tmp8[4];
|
||||
_out[4] = (float)((tmp8[0] - tmp8[1]) * INV_SQRT_2);
|
||||
_out[6] = tmp8[4];
|
||||
|
||||
/* Odd indices */
|
||||
oi8[0] = (_in[0] - _in[7]) * SYNTH_COS64_TABLE[3];
|
||||
oi8[1] = (_in[1] - _in[6]) * SYNTH_COS64_TABLE[11];
|
||||
oi8[2] = (_in[2] - _in[5]) * SYNTH_COS64_TABLE[19];
|
||||
oi8[3] = (_in[3] - _in[4]) * SYNTH_COS64_TABLE[27];
|
||||
|
||||
tmp8[0] = oi8[0] + oi8[3];
|
||||
tmp8[1] = oi8[1] + oi8[2];
|
||||
tmp8[2] = (oi8[0] - oi8[3]) * SYNTH_COS64_TABLE[7];
|
||||
tmp8[3] = (oi8[1] - oi8[2]) * SYNTH_COS64_TABLE[23];
|
||||
tmp8[4] = tmp8[2] + tmp8[3];
|
||||
tmp8[5] = (float)((tmp8[2] - tmp8[3]) * INV_SQRT_2);
|
||||
|
||||
oo8[0] = tmp8[0] + tmp8[1];
|
||||
oo8[1] = tmp8[4] + tmp8[5];
|
||||
oo8[2] = (float)((tmp8[0] - tmp8[1]) * INV_SQRT_2);
|
||||
oo8[3] = tmp8[5];
|
||||
|
||||
_out[1] = oo8[0] + oo8[1];
|
||||
_out[3] = oo8[1] + oo8[2];
|
||||
_out[5] = oo8[2] + oo8[3];
|
||||
_out[7] = oo8[3];
|
||||
}
|
||||
|
||||
void BuildUVec(float[] u_vec, float[] cur_synbuf, int k)
|
||||
{
|
||||
int i, j, uvp = 0;
|
||||
|
||||
for (j = 0; j < 8; j++)
|
||||
{
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
/* Copy first 32 elements */
|
||||
u_vec[uvp + i] = cur_synbuf[k + i + 16];
|
||||
u_vec[uvp + i + 17] = -cur_synbuf[k + 31 - i];
|
||||
}
|
||||
|
||||
/* k wraps at the synthesis buffer boundary */
|
||||
k = (k + 32) & 511;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
/* Copy next 32 elements */
|
||||
u_vec[uvp + i + 32] = -cur_synbuf[k + 16 - i];
|
||||
u_vec[uvp + i + 48] = -cur_synbuf[k + i];
|
||||
}
|
||||
u_vec[uvp + 16] = 0;
|
||||
|
||||
/* k wraps at the synthesis buffer boundary */
|
||||
k = (k + 32) & 511;
|
||||
uvp += 64;
|
||||
}
|
||||
}
|
||||
|
||||
void DewindowOutput(float[] u_vec, float[] samples)
|
||||
{
|
||||
for (int i = 0; i < 512; i++)
|
||||
{
|
||||
u_vec[i] *= DEWINDOW_TABLE[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
float sum = u_vec[i];
|
||||
sum += u_vec[i + (1 << 5)];
|
||||
sum += u_vec[i + (2 << 5)];
|
||||
sum += u_vec[i + (3 << 5)];
|
||||
sum += u_vec[i + (4 << 5)];
|
||||
sum += u_vec[i + (5 << 5)];
|
||||
sum += u_vec[i + (6 << 5)];
|
||||
sum += u_vec[i + (7 << 5)];
|
||||
sum += u_vec[i + (8 << 5)];
|
||||
sum += u_vec[i + (9 << 5)];
|
||||
sum += u_vec[i + (10 << 5)];
|
||||
sum += u_vec[i + (11 << 5)];
|
||||
sum += u_vec[i + (12 << 5)];
|
||||
sum += u_vec[i + (13 << 5)];
|
||||
sum += u_vec[i + (14 << 5)];
|
||||
sum += u_vec[i + (15 << 5)];
|
||||
u_vec[i] = sum;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
samples[i] = u_vec[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/LayerDecoderBase.cs.meta
Normal file
11
Assets/NLayer/Decoder/LayerDecoderBase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6acdad93d9a14f41835fd290220b0c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
36
Assets/NLayer/Decoder/LayerIDecoder.cs
Normal file
36
Assets/NLayer/Decoder/LayerIDecoder.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* NLayer - A C# MPEG1/2/2.5 audio decoder
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
// 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
|
||||
// That (of course) means we literally have no logic here
|
||||
class LayerIDecoder : LayerIIDecoderBase
|
||||
{
|
||||
static internal bool GetCRC(MpegFrame frame, ref uint crc)
|
||||
{
|
||||
return LayerIIDecoderBase.GetCRC(frame, _rateTable, _allocLookupTable, false, ref crc);
|
||||
}
|
||||
|
||||
// this is simple: all 32 subbands have a 4-bit allocations, and positive allocation values are {bits per sample} - 1
|
||||
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 };
|
||||
|
||||
static readonly int[][] _allocLookupTable = { new int[] { 4, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 } };
|
||||
|
||||
internal LayerIDecoder() : base(_allocLookupTable, 1) { }
|
||||
|
||||
protected override int[] GetRateTable(IMpegFrame frame)
|
||||
{
|
||||
return _rateTable;
|
||||
}
|
||||
|
||||
protected override void ReadScaleFactorSelection(IMpegFrame frame, int[][] scfsi, int channels)
|
||||
{
|
||||
// this is a no-op since the base logic uses "2" as the "has energy" marker
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/LayerIDecoder.cs.meta
Normal file
11
Assets/NLayer/Decoder/LayerIDecoder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b219f25e94951c6468591adda1c84cff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
95
Assets/NLayer/Decoder/LayerIIDecoder.cs
Normal file
95
Assets/NLayer/Decoder/LayerIIDecoder.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* NLayer - A C# MPEG1/2/2.5 audio decoder
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
// there's not much we have to do here... table selection, granule count, scalefactor selection
|
||||
class LayerIIDecoder : LayerIIDecoderBase
|
||||
{
|
||||
static internal bool GetCRC(MpegFrame frame, ref uint crc)
|
||||
{
|
||||
return LayerIIDecoderBase.GetCRC(frame, SelectTable(frame), _allocLookupTable, true, ref crc);
|
||||
}
|
||||
|
||||
// figure out which rate table to use... basically, high-rate full, high-rate limited, low-rate limited, low-rate minimal, and LSF.
|
||||
static int[] SelectTable(IMpegFrame frame)
|
||||
{
|
||||
var bitRatePerChannel = (frame.BitRate / (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2)) / 1000;
|
||||
|
||||
if (frame.Version == MpegVersion.Version1)
|
||||
{
|
||||
if ((bitRatePerChannel >= 56 && bitRatePerChannel <= 80) || (frame.SampleRate == 48000 && bitRatePerChannel >= 56))
|
||||
{
|
||||
return _rateLookupTable[0]; // high-rate, 27 subbands
|
||||
}
|
||||
else if (frame.SampleRate != 48000 && bitRatePerChannel >= 96)
|
||||
{
|
||||
return _rateLookupTable[1]; // high-rate, 30 subbands
|
||||
}
|
||||
else if (frame.SampleRate != 32000 && bitRatePerChannel <= 48)
|
||||
{
|
||||
return _rateLookupTable[2]; // low-rate, 8 subbands
|
||||
}
|
||||
else
|
||||
{
|
||||
return _rateLookupTable[3]; // low-rate, 12 subbands
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _rateLookupTable[4]; // lsf, 30 subbands
|
||||
}
|
||||
}
|
||||
|
||||
// this table tells us which allocation lookup list to use for each subband
|
||||
// note that each row has the same number of elements as there are subbands for that type...
|
||||
static readonly int[][] _rateLookupTable = {
|
||||
// 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
|
||||
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
|
||||
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
|
||||
new int[] { 4, 4, 5, 5, 5, 5, 5, 5 }, // low-rate, 7 subbands
|
||||
new int[] { 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }, // low-rate, 12 subbands
|
||||
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
|
||||
};
|
||||
|
||||
// this tells the decode logic: a) how many bits per allocation, and b) how many bits per sample for the give allocation value
|
||||
// if negative, read -x bits and handle as a group
|
||||
static readonly int[][] _allocLookupTable = {
|
||||
// bits 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
new int[] { 2, 0, -5, -7, 16 }, // 0 (II)
|
||||
new int[] { 3, 0, -5, -7, 3,-10, 4, 5, 16 }, // 1 (II)
|
||||
new int[] { 4, 0, -5, -7, 3,-10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16 }, // 2 (II)
|
||||
new int[] { 4, 0, -5, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, // 3 (II)
|
||||
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)
|
||||
new int[] { 3, 0, -5, -7,-10, 4, 5, 6, 9 }, // 5 (II, 4, 3 bits per alloc)
|
||||
new int[] { 4, 0, -5, -7, 3,-10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }, // 6 (II)
|
||||
new int[] { 2, 0, -5, -7, 3 }, // 7 (II, 4, 2 bits per alloc)
|
||||
};
|
||||
|
||||
internal LayerIIDecoder() : base(_allocLookupTable, 3) { }
|
||||
|
||||
protected override int[] GetRateTable(IMpegFrame frame)
|
||||
{
|
||||
return SelectTable(frame);
|
||||
}
|
||||
|
||||
protected override void ReadScaleFactorSelection(IMpegFrame frame, int[][] scfsi, int channels)
|
||||
{
|
||||
// we'll never have more than 30 active subbands
|
||||
for (int sb = 0; sb < 30; sb++)
|
||||
{
|
||||
for (int ch = 0; ch < channels; ch++)
|
||||
{
|
||||
if (scfsi[ch][sb] == 2)
|
||||
{
|
||||
scfsi[ch][sb] = frame.ReadBits(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/LayerIIDecoder.cs.meta
Normal file
11
Assets/NLayer/Decoder/LayerIIDecoder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dc13f162ae472a4b9c4fdeedeb745ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
397
Assets/NLayer/Decoder/LayerIIDecoderBase.cs
Normal file
397
Assets/NLayer/Decoder/LayerIIDecoderBase.cs
Normal file
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* NLayer - A C# MPEG1/2/2.5 audio decoder
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
// Layers I & II are basically identical... Layer II adds sample grouping, per subband allocation schemes, and granules
|
||||
// Because of this fact, we can use the same decoder for both
|
||||
abstract class LayerIIDecoderBase : LayerDecoderBase
|
||||
{
|
||||
protected const int SSLIMIT = 12;
|
||||
|
||||
static protected bool GetCRC(MpegFrame frame, int[] rateTable, int[][] allocLookupTable, bool readScfsiBits, ref uint crc)
|
||||
{
|
||||
// ugh... we basically have to re-implement the allocation logic here.
|
||||
|
||||
// keep up with how many active subbands we need to read selection info for
|
||||
var scfsiBits = 0;
|
||||
|
||||
// only read as many subbands as we actually need; pay attention to the intensity stereo subbands
|
||||
var subbandCount = rateTable.Length;
|
||||
var jsbound = subbandCount;
|
||||
if (frame.ChannelMode == MpegChannelMode.JointStereo)
|
||||
{
|
||||
jsbound = frame.ChannelModeExtension * 4 + 4;
|
||||
}
|
||||
|
||||
// read the full stereo subbands
|
||||
var channels = frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2;
|
||||
var sb = 0;
|
||||
for (; sb < jsbound; sb++)
|
||||
{
|
||||
var bits = allocLookupTable[rateTable[sb]][0];
|
||||
for (int ch = 0; ch < channels; ch++)
|
||||
{
|
||||
var alloc = frame.ReadBits(bits);
|
||||
if (alloc > 0) scfsiBits += 2;
|
||||
|
||||
MpegFrame.UpdateCRC(alloc, bits, ref crc);
|
||||
}
|
||||
}
|
||||
|
||||
// read the intensity stereo subbands
|
||||
for (; sb < subbandCount; sb++)
|
||||
{
|
||||
var bits = allocLookupTable[rateTable[sb]][0];
|
||||
|
||||
var alloc = frame.ReadBits(bits);
|
||||
if (alloc > 0) scfsiBits += channels * 2;
|
||||
|
||||
MpegFrame.UpdateCRC(alloc, bits, ref crc);
|
||||
}
|
||||
|
||||
// finally, read the scalefac selection bits
|
||||
if (readScfsiBits)
|
||||
{
|
||||
while (scfsiBits >= 2)
|
||||
{
|
||||
MpegFrame.UpdateCRC(frame.ReadBits(2), 2, ref crc);
|
||||
scfsiBits -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Lookup Tables
|
||||
|
||||
// this is from the formula: C = 1 / (1 / (1 << (Bits / 2 + Bits % 2 - 1)) + .5f)
|
||||
// index by real bits (Bits / 2 + Bits % 2 - 1)
|
||||
static readonly float[] _groupedC = { 0, 0, 1.33333333333f, 1.60000000000f, 1.77777777777f };
|
||||
|
||||
// these are always -0.5
|
||||
// index by real bits (Bits / 2 + Bits % 2 - 1)
|
||||
static readonly float[] _groupedD = { 0, 0, -0.5f, -0.5f, -0.5f };
|
||||
|
||||
// this is from the formula: 1 / (1 - (1f / (1 << Bits)))
|
||||
// index by bits
|
||||
static readonly float[] _C = {
|
||||
0.00000000000f,
|
||||
0.00000000000f, 1.33333333333f, 1.14285714286f, 1.06666666666f, 1.03225806452f, 1.01587301587f, 1.00787401575f, 1.00392156863f,
|
||||
1.00195694716f, 1.00097751711f, 1.00048851979f, 1.00024420024f, 1.00012208522f, 1.00006103888f, 1.00003051851f, 1.00001525902f
|
||||
};
|
||||
|
||||
// this is from the formula: 1f / (1 << Bits - 1) - 1
|
||||
// index by bits
|
||||
static readonly float[] _D = {
|
||||
0.00000000000f - 0f,
|
||||
0.00000000000f - 0f, 0.50000000000f - 1f, 0.25000000000f - 1f, 0.12500000000f - 1f, 0.062500000000f - 1f, 0.03125000000f - 1f, 0.01562500000f - 1f, 0.00781250000f - 1f,
|
||||
0.00390625000f - 1f, 0.00195312500f - 1f, 0.00097656250f - 1f, 0.00048828125f - 1f, 0.000244140630f - 1f, 0.00012207031f - 1f, 0.00006103516f - 1f, 0.00003051758f - 1f
|
||||
};
|
||||
|
||||
// this is from a (really annoying) formula:
|
||||
// x = Math.Pow(4, 1 / ((2 << (idx % 3) + 1) - (idx % 3))) / (1 << (idx / 3))
|
||||
// Basically...
|
||||
// [0] = Math.Pow(4, 1 / 2), [1] = Math.Pow(4, 1 / 3), [2] = Math.Pow(4, 1 / 6)
|
||||
// For every remaining element, calculate (in order): [idx] = [idx - 3] / 2
|
||||
static readonly float[] _denormalMultiplier = {
|
||||
2.00000000000000f, 1.58740105196820f, 1.25992104989487f, 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, 0.39685026299205f,
|
||||
0.31498026247372f, 0.25000000000000f, 0.19842513149602f, 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, 0.06250000000000f,
|
||||
0.04960628287401f, 0.03937253280921f, 0.03125000000000f, 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, 0.00984313320230f,
|
||||
0.00781250000000f, 0.00620078535925f, 0.00492156660115f, 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, 0.00155019633981f,
|
||||
0.00123039165029f, 0.00097656250000f, 0.00077509816991f, 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, 0.00024414062500f,
|
||||
0.00019377454248f, 0.00015379895629f, 0.00012207031250f, 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, 0.00003844973907f,
|
||||
0.00003051757813f, 0.00002422181781f, 0.00001922486954f, 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, 0.00000605545445f,
|
||||
0.00000480621738f, 0.00000381469727f, 0.00000302772723f, 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f, 0.00000095367432f
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
int _channels, _jsbound, _granuleCount;
|
||||
int[][] _allocLookupTable, _scfsi, _samples;
|
||||
int[][][] _scalefac;
|
||||
float[] _polyPhaseBuf;
|
||||
|
||||
int[][] _allocation;
|
||||
|
||||
protected LayerIIDecoderBase(int[][] allocLookupTable, int granuleCount)
|
||||
: base()
|
||||
{
|
||||
_allocLookupTable = allocLookupTable;
|
||||
_granuleCount = granuleCount;
|
||||
|
||||
_allocation = new int[][] { new int[SBLIMIT], new int[SBLIMIT] };
|
||||
_scfsi = new int[][] { new int[SBLIMIT], new int[SBLIMIT] };
|
||||
_samples = new int[][] { new int[SBLIMIT * SSLIMIT * _granuleCount], new int[SBLIMIT * SSLIMIT * _granuleCount] };
|
||||
|
||||
// NB: ReadScaleFactors(...) requires all three granules, even in Layer I
|
||||
_scalefac = new int[][][] { new int[3][], new int[3][] };
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
_scalefac[0][i] = new int[SBLIMIT];
|
||||
_scalefac[1][i] = new int[SBLIMIT];
|
||||
}
|
||||
|
||||
_polyPhaseBuf = new float[SBLIMIT];
|
||||
}
|
||||
|
||||
internal override int DecodeFrame(IMpegFrame frame, float[] ch0, float[] ch1)
|
||||
{
|
||||
InitFrame(frame);
|
||||
|
||||
var rateTable = GetRateTable(frame);
|
||||
|
||||
ReadAllocation(frame, rateTable);
|
||||
|
||||
for (int i = 0; i < _scfsi[0].Length; i++)
|
||||
{
|
||||
// Since Layer II has to know which subbands have energy, we use the "Layer I valid" selection to mark that energy is present.
|
||||
// That way Layer I doesn't have to do anything else.
|
||||
_scfsi[0][i] = _allocation[0][i] != 0 ? 2 : -1;
|
||||
_scfsi[1][i] = _allocation[1][i] != 0 ? 2 : -1;
|
||||
}
|
||||
ReadScaleFactorSelection(frame, _scfsi, _channels);
|
||||
|
||||
ReadScaleFactors(frame);
|
||||
|
||||
ReadSamples(frame);
|
||||
|
||||
return DecodeSamples(ch0, ch1);
|
||||
}
|
||||
|
||||
// this just reads the channel mode and set a few flags
|
||||
void InitFrame(IMpegFrame frame)
|
||||
{
|
||||
switch (frame.ChannelMode)
|
||||
{
|
||||
case MpegChannelMode.Mono:
|
||||
_channels = 1;
|
||||
_jsbound = SBLIMIT;
|
||||
break;
|
||||
case MpegChannelMode.JointStereo:
|
||||
_channels = 2;
|
||||
_jsbound = frame.ChannelModeExtension * 4 + 4;
|
||||
break;
|
||||
default:
|
||||
_channels = 2;
|
||||
_jsbound = SBLIMIT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected int[] GetRateTable(IMpegFrame frame);
|
||||
|
||||
void ReadAllocation(IMpegFrame frame, int[] rateTable)
|
||||
{
|
||||
var _subBandCount = rateTable.Length;
|
||||
if (_jsbound > _subBandCount) _jsbound = _subBandCount;
|
||||
|
||||
Array.Clear(_allocation[0], 0, SBLIMIT);
|
||||
Array.Clear(_allocation[1], 0, SBLIMIT);
|
||||
|
||||
int sb = 0;
|
||||
for (; sb < _jsbound; sb++)
|
||||
{
|
||||
var table = _allocLookupTable[rateTable[sb]];
|
||||
var bits = table[0];
|
||||
for (int ch = 0; ch < _channels; ch++)
|
||||
{
|
||||
_allocation[ch][sb] = table[frame.ReadBits(bits) + 1];
|
||||
}
|
||||
}
|
||||
for (; sb < _subBandCount; sb++)
|
||||
{
|
||||
var table = _allocLookupTable[rateTable[sb]];
|
||||
_allocation[0][sb] = _allocation[1][sb] = table[frame.ReadBits(table[0]) + 1];
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void ReadScaleFactorSelection(IMpegFrame frame, int[][] scfsi, int channels);
|
||||
|
||||
void ReadScaleFactors(IMpegFrame frame)
|
||||
{
|
||||
for (int sb = 0; sb < SBLIMIT; sb++)
|
||||
{
|
||||
for (int ch = 0; ch < _channels; ch++)
|
||||
{
|
||||
switch (_scfsi[ch][sb])
|
||||
{
|
||||
case 0:
|
||||
// all three
|
||||
_scalefac[ch][0][sb] = frame.ReadBits(6);
|
||||
_scalefac[ch][1][sb] = frame.ReadBits(6);
|
||||
_scalefac[ch][2][sb] = frame.ReadBits(6);
|
||||
break;
|
||||
case 1:
|
||||
// only two (2 = 1)
|
||||
_scalefac[ch][0][sb] =
|
||||
_scalefac[ch][1][sb] = frame.ReadBits(6);
|
||||
_scalefac[ch][2][sb] = frame.ReadBits(6);
|
||||
break;
|
||||
case 2:
|
||||
// only one (3 = 2 = 1)
|
||||
_scalefac[ch][0][sb] =
|
||||
_scalefac[ch][1][sb] =
|
||||
_scalefac[ch][2][sb] = frame.ReadBits(6);
|
||||
break;
|
||||
case 3:
|
||||
// only two (3 = 2)
|
||||
_scalefac[ch][0][sb] = frame.ReadBits(6);
|
||||
_scalefac[ch][1][sb] =
|
||||
_scalefac[ch][2][sb] = frame.ReadBits(6);
|
||||
break;
|
||||
default:
|
||||
// none
|
||||
_scalefac[ch][0][sb] = 63;
|
||||
_scalefac[ch][1][sb] = 63;
|
||||
_scalefac[ch][2][sb] = 63;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadSamples(IMpegFrame frame)
|
||||
{
|
||||
// load in all the data for this frame (1152 samples in this case)
|
||||
// NB: we flatten these into output order
|
||||
for (int ss = 0, idx = 0; ss < SSLIMIT; ss++, idx += SBLIMIT * (_granuleCount - 1))
|
||||
{
|
||||
for (int sb = 0; sb < SBLIMIT; sb++, idx++)
|
||||
{
|
||||
for (int ch = 0; ch < _channels; ch++)
|
||||
{
|
||||
if (ch == 0 || sb < _jsbound)
|
||||
{
|
||||
var alloc = _allocation[ch][sb];
|
||||
if (alloc != 0)
|
||||
{
|
||||
if (alloc < 0)
|
||||
{
|
||||
// grouping (Layer II only, so we don't have to play with the granule count)
|
||||
var val = frame.ReadBits(-alloc);
|
||||
var levels = (1 << (-alloc / 2 + -alloc % 2 - 1)) + 1;
|
||||
|
||||
_samples[ch][idx] = val % levels;
|
||||
val /= levels;
|
||||
_samples[ch][idx + SBLIMIT] = val % levels;
|
||||
_samples[ch][idx + SBLIMIT * 2] = val / levels;
|
||||
}
|
||||
else
|
||||
{
|
||||
// non-grouping
|
||||
for (int gr = 0; gr < _granuleCount; gr++)
|
||||
{
|
||||
_samples[ch][idx + SBLIMIT * gr] = frame.ReadBits(alloc);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no energy... zero out the samples
|
||||
for (int gr = 0; gr < _granuleCount; gr++)
|
||||
{
|
||||
_samples[ch][idx + SBLIMIT * gr] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy chan 0 to chan 1
|
||||
for (int gr = 0; gr < _granuleCount; gr++)
|
||||
{
|
||||
_samples[1][idx + SBLIMIT * gr] = _samples[0][idx + SBLIMIT * gr];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DecodeSamples(float[] ch0, float[] ch1)
|
||||
{
|
||||
// do our stereo mode setup
|
||||
var chanBufs = new float[2][];
|
||||
var startChannel = 0;
|
||||
var endChannel = _channels - 1;
|
||||
if (_channels == 1 || StereoMode == StereoMode.LeftOnly)
|
||||
{
|
||||
chanBufs[0] = ch0;
|
||||
endChannel = 0;
|
||||
}
|
||||
else if (StereoMode == StereoMode.RightOnly)
|
||||
{
|
||||
chanBufs[1] = ch0; // this is correct... if there's only a single channel output, it goes in channel 0's buffer
|
||||
startChannel = 1;
|
||||
}
|
||||
else // MpegStereoMode.Both or StereoMode.DownmixToMono
|
||||
{
|
||||
chanBufs[0] = ch0;
|
||||
chanBufs[1] = ch1;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
for (int ch = startChannel; ch <= endChannel; ch++)
|
||||
{
|
||||
idx = 0;
|
||||
for (int gr = 0; gr < _granuleCount; gr++)
|
||||
{
|
||||
for (int ss = 0; ss < SSLIMIT; ss++)
|
||||
{
|
||||
for (int sb = 0; sb < SBLIMIT; sb++, idx++)
|
||||
{
|
||||
// do the dequant and the denorm; output to _polyPhaseBuf
|
||||
// NB: Layers I & II use the same algorithm here... Grouping changes the bit counts, but doesn't change the algo
|
||||
// - Up to 65534 possible values (65535 does not appear to be usable)
|
||||
// - All values can be handled with 16-bit logic as long as the correct C and D constants are used
|
||||
// - Make sure to normalize each sample to 16 bits!
|
||||
|
||||
var alloc = _allocation[ch][sb];
|
||||
if (alloc != 0)
|
||||
{
|
||||
float[] c, d;
|
||||
if (alloc < 0)
|
||||
{
|
||||
alloc = -alloc / 2 + -alloc % 2 - 1;
|
||||
c = _groupedC;
|
||||
d = _groupedD;
|
||||
}
|
||||
else
|
||||
{
|
||||
c = _C;
|
||||
d = _D;
|
||||
}
|
||||
|
||||
// read sample; normalize, scale & center to [-0.999984741f..0.999984741f]; apply scalefactor
|
||||
_polyPhaseBuf[sb] = c[alloc] * ((_samples[ch][idx] << (16 - alloc)) / 32768f + d[alloc]) * _denormalMultiplier[_scalefac[ch][gr][sb]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// no transmitted energy...
|
||||
_polyPhaseBuf[sb] = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
// do the polyphase output for this channel, section, and granule
|
||||
base.InversePolyPhase(ch, _polyPhaseBuf);
|
||||
Array.Copy(_polyPhaseBuf, 0, chanBufs[ch], idx - SBLIMIT, SBLIMIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_channels == 2 && StereoMode == NLayer.StereoMode.DownmixToMono)
|
||||
{
|
||||
for (int i = 0; i < idx; i++)
|
||||
{
|
||||
ch0[i] = (ch0[i] + ch1[i]) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/LayerIIDecoderBase.cs.meta
Normal file
11
Assets/NLayer/Decoder/LayerIIDecoderBase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0edfa5e3af494c1438f3141a1392b091
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1950
Assets/NLayer/Decoder/LayerIIIDecoder.cs
Normal file
1950
Assets/NLayer/Decoder/LayerIIIDecoder.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/NLayer/Decoder/LayerIIIDecoder.cs.meta
Normal file
11
Assets/NLayer/Decoder/LayerIIIDecoder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 166e05065bf611649939e09ca44d4cfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
630
Assets/NLayer/Decoder/MpegFrame.cs
Normal file
630
Assets/NLayer/Decoder/MpegFrame.cs
Normal file
@@ -0,0 +1,630 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
class MpegFrame : FrameBase, IMpegFrame
|
||||
{
|
||||
static readonly int[][][] _bitRateTable =
|
||||
{
|
||||
new int[][]
|
||||
{
|
||||
new int[] { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 },
|
||||
new int[] { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 },
|
||||
new int[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }
|
||||
},
|
||||
new int[][]
|
||||
{
|
||||
new int[] { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 },
|
||||
new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 },
|
||||
new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }
|
||||
},
|
||||
};
|
||||
|
||||
internal static MpegFrame TrySync(uint syncMark)
|
||||
{
|
||||
if ((syncMark & 0xFFE00000) == 0xFFE00000 // frame sync
|
||||
&& (syncMark & 0x00180000) != 0x00080000 // MPEG version != reserved
|
||||
&& (syncMark & 0x00060000) != 0x00000000 // layer version != reserved
|
||||
&& (syncMark & 0x0000F000) != 0x0000F000 // bitrate != bad
|
||||
&& (syncMark & 0x00000C00) != 0x00000C00 // sample rate != reserved
|
||||
)
|
||||
{
|
||||
// now check the stereo modes
|
||||
switch ((syncMark >> 4) & 0xF)
|
||||
{
|
||||
case 0x0: // stereo
|
||||
case 0x4: // joint stereo
|
||||
case 0x5: // "
|
||||
case 0x6: // "
|
||||
case 0x7: // "
|
||||
case 0x8: // dual channel
|
||||
case 0xC: // mono
|
||||
return new MpegFrame { _syncBits = (int)syncMark };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// base: 2xIntPtr, 1x8, 1x4
|
||||
// total: 3xIntPtr, 4x8, 4x4
|
||||
// Long Mode: 72 bytes per instance
|
||||
// Prot Mode: 60 bytes per instance
|
||||
|
||||
// IntPtr
|
||||
internal MpegFrame Next;
|
||||
// 4
|
||||
internal int Number;
|
||||
|
||||
// 4
|
||||
int _syncBits;
|
||||
|
||||
// 8
|
||||
int _readOffset, _bitsRead;
|
||||
// 8
|
||||
ulong _bitBucket = 0UL;
|
||||
// 8
|
||||
long _offset;
|
||||
// 4
|
||||
bool _isMuted;
|
||||
|
||||
MpegFrame()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override int Validate()
|
||||
{
|
||||
// TrySync has already validated version, layer, bitrate, and samplerate
|
||||
|
||||
// if layer2, we have to verify the channel mode is valid for the bitrate selected
|
||||
if (Layer == MpegLayer.LayerII)
|
||||
{
|
||||
switch (BitRate)
|
||||
{
|
||||
case 32000:
|
||||
case 48000:
|
||||
case 56000:
|
||||
case 80000:
|
||||
// don't allow anything except mono
|
||||
if (ChannelMode != MpegChannelMode.Mono) return -1;
|
||||
break;
|
||||
case 224000:
|
||||
case 256000:
|
||||
case 320000:
|
||||
case 384000:
|
||||
// don't allow mono
|
||||
if (ChannelMode == MpegChannelMode.Mono) return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the frame's length
|
||||
int frameSize;
|
||||
if (BitRateIndex > 0)
|
||||
{
|
||||
if (Layer == MpegLayer.LayerI)
|
||||
{
|
||||
frameSize = (12 * BitRate / SampleRate + Padding) * 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
frameSize = 144 * BitRate / SampleRate + Padding;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// "free" frame... we have to calculate it later
|
||||
frameSize = _readOffset + GetSideDataSize() + Padding; // we know the frame will be at least this big...
|
||||
}
|
||||
|
||||
// now check the crc if one is present
|
||||
if (HasCrc)
|
||||
{
|
||||
// prep for CRC reading
|
||||
_readOffset = 4 + (HasCrc ? 2 : 0);
|
||||
|
||||
// check the CRC
|
||||
if (!ValidateCRC())
|
||||
{
|
||||
// mute this frame
|
||||
_isMuted = true;
|
||||
return 6; // header + crc... force the reader to re-sync
|
||||
}
|
||||
}
|
||||
|
||||
// prep for reading
|
||||
Reset();
|
||||
|
||||
// finally, let our caller know how big this frame is (including the sync header)
|
||||
return frameSize;
|
||||
}
|
||||
|
||||
internal int GetSideDataSize()
|
||||
{
|
||||
switch (Layer)
|
||||
{
|
||||
case MpegLayer.LayerI:
|
||||
if (ChannelMode == MpegChannelMode.Mono)
|
||||
{
|
||||
// mono
|
||||
return 16;
|
||||
}
|
||||
else if (ChannelMode == MpegChannelMode.Stereo || ChannelMode == MpegChannelMode.DualChannel)
|
||||
{
|
||||
// full stereo / dual channel
|
||||
return 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
// joint stereo... ugh...
|
||||
switch (ChannelModeExtension)
|
||||
{
|
||||
case 0:
|
||||
return 18;
|
||||
case 1:
|
||||
return 20;
|
||||
case 2:
|
||||
return 22;
|
||||
case 3:
|
||||
return 24;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MpegLayer.LayerII:
|
||||
return 0;
|
||||
case MpegLayer.LayerIII:
|
||||
if (ChannelMode == MpegChannelMode.Mono && Version >= MpegVersion.Version2)
|
||||
{
|
||||
return 9;
|
||||
}
|
||||
else if (ChannelMode != MpegChannelMode.Mono && Version < MpegVersion.Version2)
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 17;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ValidateCRC()
|
||||
{
|
||||
var crc = 0xFFFFU;
|
||||
|
||||
// process the common bits...
|
||||
UpdateCRC(_syncBits, 16, ref crc);
|
||||
|
||||
var apply = false;
|
||||
switch (Layer)
|
||||
{
|
||||
case MpegLayer.LayerI:
|
||||
apply = LayerIDecoder.GetCRC(this, ref crc);
|
||||
break;
|
||||
case MpegLayer.LayerII:
|
||||
apply = LayerIIDecoder.GetCRC(this, ref crc);
|
||||
break;
|
||||
case MpegLayer.LayerIII:
|
||||
apply = LayerIIIDecoder.GetCRC(this, ref crc);
|
||||
break;
|
||||
}
|
||||
|
||||
if (apply)
|
||||
{
|
||||
var checkCrc = ReadByte(4) << 8 | ReadByte(5);
|
||||
return checkCrc == crc;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static internal void UpdateCRC(int data, int length, ref uint crc)
|
||||
{
|
||||
var masking = 1U << length;
|
||||
while ((masking >>= 1) != 0)
|
||||
{
|
||||
var carry = crc & 0x8000;
|
||||
crc <<= 1;
|
||||
if ((carry == 0) ^ ((data & masking) == 0))
|
||||
{
|
||||
crc ^= 0x8005;
|
||||
}
|
||||
}
|
||||
crc &= 0xFFFF;
|
||||
}
|
||||
|
||||
internal VBRInfo ParseVBR()
|
||||
{
|
||||
var buf = new byte[4];
|
||||
|
||||
// Xing first
|
||||
int offset;
|
||||
if (Version == MpegVersion.Version1 && ChannelMode != MpegChannelMode.Mono)
|
||||
{
|
||||
offset = 32 + 4;
|
||||
}
|
||||
else if (Version > MpegVersion.Version1 && ChannelMode == MpegChannelMode.Mono)
|
||||
{
|
||||
offset = 9 + 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = 17 + 4;
|
||||
}
|
||||
|
||||
if (Read(offset, buf) != 4) return null;
|
||||
if (buf[0] == 'X' && buf[1] == 'i' && buf[2] == 'n' && buf[3] == 'g'
|
||||
|| buf[0] == 'I' && buf[1] == 'n' && buf[2] == 'f' && buf[3] == 'o')
|
||||
{
|
||||
return ParseXing(offset + 4);
|
||||
}
|
||||
|
||||
// then VBRI (kinda rare)
|
||||
if (Read(36, buf) != 4) return null;
|
||||
if (buf[0] == 'V' && buf[1] == 'B' && buf[2] == 'R' && buf[3] == 'I')
|
||||
{
|
||||
return ParseVBRI();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
VBRInfo ParseXing(int offset)
|
||||
{
|
||||
VBRInfo info = new VBRInfo();
|
||||
info.Channels = Channels;
|
||||
info.SampleRate = SampleRate;
|
||||
info.SampleCount = SampleCount;
|
||||
|
||||
var buf = new byte[100];
|
||||
if (Read(offset, buf, 0, 4) != 4) return null;
|
||||
offset += 4;
|
||||
|
||||
var flags = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||
|
||||
// frame count
|
||||
if ((flags & 0x1) != 0)
|
||||
{
|
||||
if (Read(offset, buf, 0, 4) != 4) return null;
|
||||
offset += 4;
|
||||
info.VBRFrames = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||
}
|
||||
|
||||
// byte count
|
||||
if ((flags & 0x2) != 0)
|
||||
{
|
||||
if (Read(offset, buf, 0, 4) != 4) return null;
|
||||
offset += 4;
|
||||
info.VBRBytes = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||
}
|
||||
|
||||
// TOC
|
||||
if ((flags & 0x4) != 0)
|
||||
{
|
||||
// we're not using the TOC, so just discard it
|
||||
if (Read(offset, buf) != 100) return null;
|
||||
offset += 100;
|
||||
}
|
||||
|
||||
// scale
|
||||
if ((flags & 0x8) != 0)
|
||||
{
|
||||
if (Read(offset, buf, 0, 4) != 4) return null;
|
||||
offset += 4;
|
||||
info.VBRQuality = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||
}
|
||||
|
||||
//// now look for a LAME header (note: if it isn't found, it doesn't fail the VBR parse)
|
||||
//do
|
||||
//{
|
||||
// if (Read(offset, buf, 0, 20) != 20) break;
|
||||
// offset += 20;
|
||||
|
||||
// // LAME tag revision: only 0 and 1 are valid
|
||||
// if ((buf[9] & 0xF0) > 0x10) break;
|
||||
|
||||
// // VBR mode: 0-6, 8 & 9 are valid
|
||||
// var mode = buf[9] & 0xF;
|
||||
// if (mode == 7 || mode > 9) break;
|
||||
|
||||
// // Lowpass filter value
|
||||
// var lowpass = buf[10] / 100.0;
|
||||
|
||||
// // Replay Gain
|
||||
// var rgPeak = BitConverter.ToSingle(buf, 11);
|
||||
// var rgGainRadio = buf[15] << 8 | buf[16];
|
||||
// var rgGain = buf[17] << 8 | buf[18];
|
||||
//} while (false);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
VBRInfo ParseVBRI()
|
||||
{
|
||||
VBRInfo info = new VBRInfo();
|
||||
info.Channels = Channels;
|
||||
info.SampleRate = SampleRate;
|
||||
info.SampleCount = SampleCount;
|
||||
|
||||
// VBRI is "fixed" size... Yay. :)
|
||||
var buf = new byte[26];
|
||||
if (Read(36, buf) != 26) return null;
|
||||
|
||||
// Version
|
||||
var version = buf[4] << 8 | buf[5];
|
||||
|
||||
// Delay
|
||||
info.VBRDelay = buf[6] << 8 | buf[7];
|
||||
|
||||
// Quality
|
||||
info.VBRQuality = buf[8] << 8 | buf[9];
|
||||
|
||||
// Bytes
|
||||
info.VBRBytes = buf[10] << 24 | buf[11] << 16 | buf[12] << 8 | buf[13];
|
||||
|
||||
// Frames
|
||||
info.VBRFrames = buf[14] << 24 | buf[15] << 16 | buf[16] << 8 | buf[17];
|
||||
|
||||
// TOC
|
||||
// entries
|
||||
var tocEntries = buf[18] << 8 | buf[19];
|
||||
var tocScale = buf[20] << 8 | buf[21];
|
||||
var tocEntrySize = buf[22] << 8 | buf[23];
|
||||
var tocFramesPerEntry = buf[24] << 8 | buf[25];
|
||||
var tocSize = tocEntries * tocEntrySize;
|
||||
|
||||
var toc = new byte[tocSize];
|
||||
if (Read(62, toc) != tocSize) return null;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public int FrameLength
|
||||
{
|
||||
get { return base.Length; }
|
||||
}
|
||||
|
||||
public MpegVersion Version
|
||||
{
|
||||
get
|
||||
{
|
||||
switch ((_syncBits >> 19) & 3)
|
||||
{
|
||||
case 0:
|
||||
return MpegVersion.Version25;
|
||||
case 2:
|
||||
return MpegVersion.Version2;
|
||||
case 3:
|
||||
return MpegVersion.Version1;
|
||||
}
|
||||
return MpegVersion.Unknown;
|
||||
}
|
||||
}
|
||||
public MpegLayer Layer
|
||||
{
|
||||
get
|
||||
{
|
||||
// the order is backwards, and "0" is invalid
|
||||
return (MpegLayer)((4 - ((_syncBits >> 17) & 3)) % 4);
|
||||
}
|
||||
}
|
||||
public bool HasCrc
|
||||
{
|
||||
get { return (_syncBits & 0x10000) == 0; }
|
||||
}
|
||||
public int BitRate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (BitRateIndex > 0)
|
||||
{
|
||||
return _bitRateTable[(int)Version / 10 - 1][(int)Layer - 1][BitRateIndex] * 1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// bitrate is always an even multiple of 1000, so round
|
||||
return ((((FrameLength * 8) * SampleRate) / SampleCount + 499) + 500) / 1000 * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
public int BitRateIndex
|
||||
{
|
||||
get { return (_syncBits >> 12) & 0xF; }
|
||||
}
|
||||
public int SampleRate
|
||||
{
|
||||
get
|
||||
{
|
||||
int sr;
|
||||
switch (SampleRateIndex)
|
||||
{
|
||||
case 0: sr = 44100; break;
|
||||
case 1: sr = 48000; break;
|
||||
case 2: sr = 32000; break;
|
||||
default: sr = 0; break;
|
||||
}
|
||||
if (Version > MpegVersion.Version1)
|
||||
{
|
||||
if (Version == MpegVersion.Version25)
|
||||
{
|
||||
sr /= 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
sr /= 2;
|
||||
}
|
||||
}
|
||||
return sr;
|
||||
}
|
||||
}
|
||||
public int SampleRateIndex
|
||||
{
|
||||
get { return (_syncBits >> 10) & 0x3; }
|
||||
}
|
||||
private int Padding
|
||||
{
|
||||
get { return (_syncBits >> 9) & 0x1; }
|
||||
}
|
||||
public MpegChannelMode ChannelMode
|
||||
{
|
||||
get { return (MpegChannelMode)((_syncBits >> 6) & 0x3); }
|
||||
}
|
||||
public int ChannelModeExtension
|
||||
{
|
||||
get { return (_syncBits >> 4) & 0x3; }
|
||||
}
|
||||
internal int Channels
|
||||
{
|
||||
get { return (ChannelMode == MpegChannelMode.Mono ? 1 : 2); }
|
||||
}
|
||||
public bool IsCopyrighted
|
||||
{
|
||||
get { return (_syncBits & 0x8) == 0x8; }
|
||||
}
|
||||
internal bool IsOriginal
|
||||
{
|
||||
get { return (_syncBits & 0x4) == 0x4; }
|
||||
}
|
||||
internal int EmphasisMode
|
||||
{
|
||||
get { return (_syncBits & 0x3); }
|
||||
}
|
||||
public bool IsCorrupted
|
||||
{
|
||||
get { return _isMuted; }
|
||||
}
|
||||
|
||||
public int SampleCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Layer == MpegLayer.LayerI) return 384;
|
||||
if (Layer == MpegLayer.LayerIII && Version > MpegVersion.Version1) return 576;
|
||||
return 1152;
|
||||
}
|
||||
}
|
||||
internal long SampleOffset
|
||||
{
|
||||
get { return _offset; }
|
||||
set { _offset = value; }
|
||||
}
|
||||
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_readOffset = 4 + (HasCrc ? 2 : 0);
|
||||
_bitBucket = 0UL;
|
||||
_bitsRead = 0;
|
||||
}
|
||||
|
||||
public int ReadBits(int bitCount)
|
||||
{
|
||||
if (bitCount < 1 || bitCount > 32) throw new ArgumentOutOfRangeException("bitCount");
|
||||
if (_isMuted) return 0;
|
||||
|
||||
while (_bitsRead < bitCount)
|
||||
{
|
||||
var b = ReadByte(_readOffset);
|
||||
if (b == -1) throw new System.IO.EndOfStreamException();
|
||||
|
||||
++_readOffset;
|
||||
|
||||
_bitBucket <<= 8;
|
||||
_bitBucket |= (byte)(b & 0xFF);
|
||||
_bitsRead += 8;
|
||||
}
|
||||
|
||||
var temp = (int)((_bitBucket >> (_bitsRead - bitCount)) & ((1UL << bitCount) - 1));
|
||||
_bitsRead -= bitCount;
|
||||
return temp;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override string ToString()
|
||||
{
|
||||
// version
|
||||
var sb = new StringBuilder("MPEG");
|
||||
switch (Version)
|
||||
{
|
||||
case MpegVersion.Version1: sb.Append("1"); break;
|
||||
case MpegVersion.Version2: sb.Append("2"); break;
|
||||
case MpegVersion.Version25: sb.Append("2.5"); break;
|
||||
}
|
||||
|
||||
// layer
|
||||
sb.Append(" Layer ");
|
||||
switch (Layer)
|
||||
{
|
||||
case MpegLayer.LayerI: sb.Append("I"); break;
|
||||
case MpegLayer.LayerII: sb.Append("II"); break;
|
||||
case MpegLayer.LayerIII: sb.Append("III"); break;
|
||||
}
|
||||
|
||||
// bitrate
|
||||
sb.AppendFormat(" {0} kbps ", BitRate / 1000);
|
||||
|
||||
// channel mode
|
||||
switch (ChannelMode)
|
||||
{
|
||||
case MpegChannelMode.Stereo:
|
||||
sb.Append("Stereo");
|
||||
break;
|
||||
case MpegChannelMode.JointStereo:
|
||||
sb.Append("Joint Stereo");
|
||||
switch (ChannelModeExtension)
|
||||
{
|
||||
case 1: sb.Append(" (I)"); break;
|
||||
case 2: sb.Append(" (M/S)"); break;
|
||||
case 3: sb.Append(" (M/S,I)"); break;
|
||||
}
|
||||
break;
|
||||
case MpegChannelMode.DualChannel:
|
||||
sb.Append("Dual Channel");
|
||||
break;
|
||||
case MpegChannelMode.Mono:
|
||||
sb.Append("Mono");
|
||||
break;
|
||||
}
|
||||
|
||||
// sample rate
|
||||
sb.AppendFormat(" {0} KHz", (float)SampleRate / 1000);
|
||||
|
||||
var flagList = new List<string>();
|
||||
// protection
|
||||
if (HasCrc) flagList.Add("CRC");
|
||||
|
||||
// copyright
|
||||
if (IsCopyrighted) flagList.Add("Copyright");
|
||||
|
||||
// original
|
||||
if (IsOriginal) flagList.Add("Original");
|
||||
|
||||
// emphasis
|
||||
switch (EmphasisMode)
|
||||
{
|
||||
case 1:
|
||||
flagList.Add("50/15 ms");
|
||||
break;
|
||||
case 2:
|
||||
flagList.Add("Invalid Emphasis");
|
||||
break;
|
||||
case 3:
|
||||
flagList.Add("CCIT J.17");
|
||||
break;
|
||||
}
|
||||
|
||||
if (flagList.Count > 0)
|
||||
{
|
||||
sb.AppendFormat(" ({0})", string.Join(",", flagList.ToArray()));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/MpegFrame.cs.meta
Normal file
11
Assets/NLayer/Decoder/MpegFrame.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e57cfee9f5052d14e9107fde87d08a77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
718
Assets/NLayer/Decoder/MpegStreamReader.cs
Normal file
718
Assets/NLayer/Decoder/MpegStreamReader.cs
Normal file
@@ -0,0 +1,718 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
class MpegStreamReader
|
||||
{
|
||||
ID3Frame _id3Frame, _id3v1Frame;
|
||||
RiffHeaderFrame _riffHeaderFrame;
|
||||
|
||||
VBRInfo _vbrInfo;
|
||||
MpegFrame _first, _current, _last, _lastFree;
|
||||
|
||||
long _readOffset, _eofOffset;
|
||||
Stream _source;
|
||||
bool _canSeek, _endFound, _mixedFrameSize;
|
||||
object _readLock = new object();
|
||||
object _frameLock = new object();
|
||||
|
||||
internal MpegStreamReader(Stream source)
|
||||
{
|
||||
_source = source;
|
||||
_canSeek = source.CanSeek;
|
||||
_readOffset = 0L;
|
||||
_eofOffset = long.MaxValue;
|
||||
|
||||
// find the first Mpeg frame
|
||||
var frame = FindNextFrame();
|
||||
while (frame != null && !(frame is MpegFrame))
|
||||
{
|
||||
frame = FindNextFrame();
|
||||
}
|
||||
|
||||
// if we still don't have a frame, we never sync'ed
|
||||
if (frame == null) throw new InvalidDataException("Not a valid MPEG file!");
|
||||
|
||||
// the very next frame "should be" an mpeg frame
|
||||
frame = FindNextFrame();
|
||||
|
||||
// if not, it's not a valid file
|
||||
if (frame == null || !(frame is MpegFrame)) throw new InvalidDataException("Not a valid MPEG file!");
|
||||
|
||||
// seek to the first frame
|
||||
_current = _first;
|
||||
}
|
||||
|
||||
FrameBase FindNextFrame()
|
||||
{
|
||||
// if we've found the end, don't bother looking for anything else
|
||||
if (_endFound) return null;
|
||||
|
||||
var freeFrame = _lastFree;
|
||||
var lastFrameStart = _readOffset;
|
||||
|
||||
lock (_frameLock)
|
||||
{
|
||||
// read 3 bytes
|
||||
var syncBuf = new byte[4];
|
||||
try
|
||||
{
|
||||
if (Read(_readOffset, syncBuf, 0, 4) == 4)
|
||||
{
|
||||
// now loop until a frame is found
|
||||
do
|
||||
{
|
||||
var sync = (uint)(syncBuf[0] << 24 | syncBuf[1] << 16 | syncBuf[2] << 8 | syncBuf[3]);
|
||||
|
||||
lastFrameStart = _readOffset;
|
||||
|
||||
// try ID3 first (for v2 frames)
|
||||
if (_id3Frame == null)
|
||||
{
|
||||
var f = ID3Frame.TrySync(sync);
|
||||
if (f != null)
|
||||
{
|
||||
if (f.Validate(_readOffset, this))
|
||||
{
|
||||
if (!_canSeek) f.SaveBuffer();
|
||||
|
||||
_readOffset += f.Length;
|
||||
DiscardThrough(_readOffset, true);
|
||||
|
||||
return _id3Frame = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now look for a RIFF header
|
||||
if (_first == null && _riffHeaderFrame == null)
|
||||
{
|
||||
var f = RiffHeaderFrame.TrySync(sync);
|
||||
if (f != null)
|
||||
{
|
||||
if (f.Validate(_readOffset, this))
|
||||
{
|
||||
_readOffset += f.Length;
|
||||
DiscardThrough(_readOffset, true);
|
||||
|
||||
return _riffHeaderFrame = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finally, just try for an MPEG frame
|
||||
var frame = MpegFrame.TrySync(sync);
|
||||
if (frame != null)
|
||||
{
|
||||
if (frame.Validate(_readOffset, this)
|
||||
&& !(freeFrame != null
|
||||
&& (frame.Layer != freeFrame.Layer
|
||||
|| frame.Version != freeFrame.Version
|
||||
|| frame.SampleRate != freeFrame.SampleRate
|
||||
|| frame.BitRateIndex > 0
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
if (!_canSeek)
|
||||
{
|
||||
frame.SaveBuffer();
|
||||
DiscardThrough(_readOffset + frame.FrameLength, true);
|
||||
}
|
||||
|
||||
_readOffset += frame.FrameLength;
|
||||
|
||||
if (_first == null)
|
||||
{
|
||||
if (_vbrInfo == null && (_vbrInfo = frame.ParseVBR()) != null)
|
||||
{
|
||||
return FindNextFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.Number = 0;
|
||||
_first = _last = frame;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (frame.SampleCount != _first.SampleCount)
|
||||
{
|
||||
_mixedFrameSize = true;
|
||||
}
|
||||
|
||||
frame.SampleOffset = _last.SampleCount + _last.SampleOffset;
|
||||
frame.Number = _last.Number + 1;
|
||||
_last = (_last.Next = frame);
|
||||
}
|
||||
|
||||
if (frame.BitRateIndex == 0)
|
||||
{
|
||||
_lastFree = frame;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
|
||||
// if we've read MPEG frames and can't figure out what frame type we have, try looking for a new ID3 tag
|
||||
if (_last != null)
|
||||
{
|
||||
var f = ID3Frame.TrySync(sync);
|
||||
if (f != null)
|
||||
{
|
||||
if (f.Validate(_readOffset, this))
|
||||
{
|
||||
if (!_canSeek) f.SaveBuffer();
|
||||
|
||||
// if it's a v1 tag, go ahead and parse it
|
||||
if (f.Version == 1)
|
||||
{
|
||||
_id3v1Frame = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// grrr... the ID3 2.4 spec says tags can be anywhere in the file and that later tags can override earlier ones... boo
|
||||
_id3Frame.Merge(f);
|
||||
}
|
||||
|
||||
_readOffset += f.Length;
|
||||
DiscardThrough(_readOffset, true);
|
||||
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// well, we didn't find anything, so rinse and repeat with the next byte
|
||||
++_readOffset;
|
||||
if (_first == null || !_canSeek) DiscardThrough(_readOffset, true);
|
||||
Buffer.BlockCopy(syncBuf, 1, syncBuf, 0, 3);
|
||||
} while (Read(_readOffset + 3, syncBuf, 3, 1) == 1);
|
||||
}
|
||||
|
||||
// move the "end of frame" marker for the last free format frame (in case we have one)
|
||||
// this is because we don't include the last four bytes otherwise
|
||||
lastFrameStart += 4;
|
||||
|
||||
_endFound = true;
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (freeFrame != null)
|
||||
{
|
||||
freeFrame.Length = (int)(lastFrameStart - freeFrame.Offset);
|
||||
|
||||
if (!_canSeek)
|
||||
{
|
||||
// gotta finish filling the buffer!!
|
||||
throw new InvalidOperationException("Free frames cannot be read properly from forward-only streams!");
|
||||
}
|
||||
|
||||
// if _lastFree hasn't changed (we got a non-MPEG frame), clear it out
|
||||
if (_lastFree == freeFrame)
|
||||
{
|
||||
_lastFree = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReadBuffer
|
||||
{
|
||||
public byte[] Data;
|
||||
public long BaseOffset;
|
||||
public int End;
|
||||
public int DiscardCount;
|
||||
|
||||
object _localLock = new object();
|
||||
|
||||
public ReadBuffer(int initialSize)
|
||||
{
|
||||
initialSize = 2 << (int)Math.Log(initialSize, 2);
|
||||
|
||||
Data = new byte[initialSize];
|
||||
}
|
||||
|
||||
public int Read(MpegStreamReader reader, long offset, byte[] buffer, int index, int count)
|
||||
{
|
||||
lock (_localLock)
|
||||
{
|
||||
var startIdx = EnsureFilled(reader, offset, ref count);
|
||||
|
||||
Buffer.BlockCopy(Data, startIdx, buffer, index, count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public int ReadByte(MpegStreamReader reader, long offset)
|
||||
{
|
||||
lock (_localLock)
|
||||
{
|
||||
var count = 1;
|
||||
var startIdx = EnsureFilled(reader, offset, ref count);
|
||||
if (count == 1)
|
||||
{
|
||||
return Data[startIdx];
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int EnsureFilled(MpegStreamReader reader, long offset, ref int count)
|
||||
{
|
||||
// if the offset & count are inside our buffer's range, just return the appropriate index
|
||||
var startIdx = (int)(offset - BaseOffset);
|
||||
int endIdx = startIdx + count;
|
||||
if (startIdx < 0 || endIdx > End)
|
||||
{
|
||||
int readStart = 0, readCount = 0, moveCount = 0;
|
||||
long readOffset = 0;
|
||||
|
||||
#region Decision-Making
|
||||
|
||||
if (startIdx < 0)
|
||||
{
|
||||
// if we can't seek, there's nothing we can do
|
||||
if (!reader._source.CanSeek) throw new InvalidOperationException("Cannot seek backwards on a forward-only stream!");
|
||||
|
||||
// if there's data in the buffer, try to keep it (up to doubling the buffer size)
|
||||
if (End > 0)
|
||||
{
|
||||
// if doubling the buffer would push it past the max size, don't check it
|
||||
if ((startIdx + Data.Length > 0) || (Data.Length * 2 <= 16384 && startIdx + Data.Length * 2 > 0))
|
||||
{
|
||||
endIdx = End;
|
||||
}
|
||||
}
|
||||
|
||||
// we know we'll have to start reading here
|
||||
readOffset = offset;
|
||||
|
||||
// if the end of the request is before the start of our buffer...
|
||||
if (endIdx < 0)
|
||||
{
|
||||
// ... just truncate and move on
|
||||
Truncate();
|
||||
|
||||
// set up our read parameters
|
||||
BaseOffset = offset;
|
||||
startIdx = 0;
|
||||
endIdx = count;
|
||||
|
||||
// how much do we need to read?
|
||||
readCount = count;
|
||||
}
|
||||
else // i.e., endIdx >= 0
|
||||
{
|
||||
// we have overlap with existing data... save as much as possible
|
||||
moveCount = -endIdx;
|
||||
readCount = -startIdx;
|
||||
}
|
||||
}
|
||||
else // i.e., startIdx >= 0
|
||||
{
|
||||
// we only get to here if at least one byte of the request is past the end of the read data
|
||||
// start with the simplest scenario and work our way up
|
||||
|
||||
// 1) We just need to fill the buffer a bit more
|
||||
if (endIdx < Data.Length)
|
||||
{
|
||||
readCount = endIdx - End;
|
||||
readStart = End;
|
||||
readOffset = BaseOffset + readStart;
|
||||
}
|
||||
// 2) We need to discard some bytes, then fill the buffer
|
||||
else if (endIdx - DiscardCount < Data.Length)
|
||||
{
|
||||
moveCount = DiscardCount;
|
||||
readStart = End;
|
||||
readCount = endIdx - readStart;
|
||||
readOffset = BaseOffset + readStart;
|
||||
}
|
||||
// 3) We need to expand the buffer to hold all the existing & requested data
|
||||
else if (Data.Length * 2 <= 16384)
|
||||
{
|
||||
// by definition, we discard
|
||||
moveCount = DiscardCount;
|
||||
readStart = End;
|
||||
readCount = endIdx - End;
|
||||
readOffset = BaseOffset + readStart;
|
||||
}
|
||||
// 4) We have to throw away some data that hasn't been discarded
|
||||
else
|
||||
{
|
||||
// just truncate
|
||||
Truncate();
|
||||
|
||||
// set up our read parameters
|
||||
BaseOffset = offset;
|
||||
readOffset = offset;
|
||||
startIdx = 0;
|
||||
endIdx = count;
|
||||
|
||||
// how much do we have to read?
|
||||
readCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Buffer Resizing & Data Moving
|
||||
|
||||
if (endIdx - moveCount > Data.Length || readStart + readCount - moveCount > Data.Length)
|
||||
{
|
||||
var newSize = Data.Length * 2;
|
||||
while (newSize < endIdx - moveCount)
|
||||
{
|
||||
newSize *= 2;
|
||||
}
|
||||
|
||||
var newBuf = new byte[newSize];
|
||||
if (moveCount < 0)
|
||||
{
|
||||
// reverse copy
|
||||
Buffer.BlockCopy(Data, 0, newBuf, -moveCount, End + moveCount);
|
||||
|
||||
DiscardCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// forward or neutral copy
|
||||
Buffer.BlockCopy(Data, moveCount, newBuf, 0, End - moveCount);
|
||||
|
||||
DiscardCount -= moveCount;
|
||||
}
|
||||
Data = newBuf;
|
||||
}
|
||||
else if (moveCount != 0)
|
||||
{
|
||||
if (moveCount > 0)
|
||||
{
|
||||
// forward move
|
||||
Buffer.BlockCopy(Data, moveCount, Data, 0, End - moveCount);
|
||||
|
||||
DiscardCount -= moveCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// backward move
|
||||
for (int i = 0, srcIdx = Data.Length - 1, destIdx = Data.Length - 1 - moveCount; i < moveCount; i++, srcIdx--, destIdx--)
|
||||
{
|
||||
Data[destIdx] = Data[srcIdx];
|
||||
}
|
||||
|
||||
DiscardCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
BaseOffset += moveCount;
|
||||
readStart -= moveCount;
|
||||
startIdx -= moveCount;
|
||||
endIdx -= moveCount;
|
||||
End -= moveCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Buffer Filling
|
||||
|
||||
lock (reader._readLock)
|
||||
{
|
||||
if (readCount > 0 && reader._source.Position != readOffset && readOffset < reader._eofOffset)
|
||||
{
|
||||
if (reader._canSeek)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader._source.Position = readOffset;
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
reader._eofOffset = reader._source.Length;
|
||||
readCount = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// ugh, gotta read bytes until we've reached the desired offset
|
||||
var seekCount = readOffset - reader._source.Position;
|
||||
while (--seekCount >= 0)
|
||||
{
|
||||
if (reader._source.ReadByte() == -1)
|
||||
{
|
||||
reader._eofOffset = reader._source.Position;
|
||||
readCount = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (readCount > 0 && readOffset < reader._eofOffset)
|
||||
{
|
||||
var temp = reader._source.Read(Data, readStart, readCount);
|
||||
if (temp == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
readStart += temp;
|
||||
readOffset += temp;
|
||||
readCount -= temp;
|
||||
}
|
||||
|
||||
if (readStart > End)
|
||||
{
|
||||
End = readStart;
|
||||
}
|
||||
|
||||
if (End < endIdx)
|
||||
{
|
||||
// we didn't get a full read...
|
||||
count = Math.Max(0, End - startIdx);
|
||||
}
|
||||
// NB: if desired, switch to "minimal reads" by commenting-out this clause
|
||||
else if (End < Data.Length)
|
||||
{
|
||||
// try to finish filling the buffer
|
||||
var temp = reader._source.Read(Data, End, Data.Length - End);
|
||||
End += temp;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
return startIdx;
|
||||
}
|
||||
|
||||
public void DiscardThrough(long offset)
|
||||
{
|
||||
lock (_localLock)
|
||||
{
|
||||
var count = (int)(offset - BaseOffset);
|
||||
DiscardCount = Math.Max(count, DiscardCount);
|
||||
|
||||
if (DiscardCount >= Data.Length) CommitDiscard();
|
||||
}
|
||||
}
|
||||
|
||||
void Truncate()
|
||||
{
|
||||
End = 0;
|
||||
DiscardCount = 0;
|
||||
}
|
||||
|
||||
void CommitDiscard()
|
||||
{
|
||||
if (DiscardCount >= Data.Length || DiscardCount >= End)
|
||||
{
|
||||
// we have been told to discard the entire buffer
|
||||
BaseOffset += DiscardCount;
|
||||
End = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// just discard the first part...
|
||||
//Array.Copy(_readBuf, _readBufDiscardCount, _readBuf, 0, _readBufEnd - _readBufDiscardCount);
|
||||
Buffer.BlockCopy(Data, DiscardCount, Data, 0, End - DiscardCount);
|
||||
BaseOffset += DiscardCount;
|
||||
End -= DiscardCount;
|
||||
}
|
||||
DiscardCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ReadBuffer _readBuf = new ReadBuffer(2048);
|
||||
|
||||
internal int Read(long offset, byte[] buffer, int index, int count)
|
||||
{
|
||||
// make sure the offset is at least positive
|
||||
if (offset < 0L) throw new ArgumentOutOfRangeException("offset");
|
||||
|
||||
// make sure the buffer is valid
|
||||
if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
return _readBuf.Read(this, offset, buffer, index, count);
|
||||
}
|
||||
|
||||
internal int ReadByte(long offset)
|
||||
{
|
||||
if (offset < 0L) throw new ArgumentOutOfRangeException("offset");
|
||||
|
||||
return _readBuf.ReadByte(this, offset);
|
||||
}
|
||||
|
||||
internal void DiscardThrough(long offset, bool minimalRead)
|
||||
{
|
||||
_readBuf.DiscardThrough(offset);
|
||||
}
|
||||
|
||||
|
||||
internal void ReadToEnd()
|
||||
{
|
||||
try
|
||||
{
|
||||
var maxAllocation = 40000;
|
||||
if (_id3Frame != null)
|
||||
{
|
||||
maxAllocation += _id3Frame.Length;
|
||||
}
|
||||
|
||||
while (!_endFound)
|
||||
{
|
||||
FindNextFrame();
|
||||
|
||||
while (!_canSeek && FrameBase.TotalAllocation >= maxAllocation)
|
||||
{
|
||||
#if NET35
|
||||
System.Threading.Thread.Sleep(500);
|
||||
#else
|
||||
System.Threading.Tasks.Task.Delay(500).Wait(); //
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// in case the stream was disposed before we finished...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal bool CanSeek
|
||||
{
|
||||
get { return _canSeek; }
|
||||
}
|
||||
|
||||
internal long SampleCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vbrInfo != null) return _vbrInfo.VBRStreamSampleCount;
|
||||
|
||||
if (!_canSeek) return -1;
|
||||
|
||||
ReadToEnd();
|
||||
return _last.SampleCount + _last.SampleOffset;
|
||||
}
|
||||
}
|
||||
|
||||
internal int SampleRate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vbrInfo != null) return _vbrInfo.SampleRate;
|
||||
return _first.SampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
internal int Channels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vbrInfo != null) return _vbrInfo.Channels;
|
||||
return _first.Channels;
|
||||
}
|
||||
}
|
||||
|
||||
internal int FirstFrameSampleCount
|
||||
{
|
||||
get { return (_first != null ? _first.SampleCount : 0); }
|
||||
}
|
||||
|
||||
|
||||
internal long SeekTo(long sampleNumber)
|
||||
{
|
||||
if (!_canSeek) throw new InvalidOperationException("Cannot seek!");
|
||||
|
||||
// first try to "seek" by calculating the frame number
|
||||
var cnt = (int)(sampleNumber / _first.SampleCount);
|
||||
var frame = _first;
|
||||
if (_current != null && _current.Number <= cnt && _current.SampleOffset <= sampleNumber)
|
||||
{
|
||||
// if this fires, we can short-circuit things a bit...
|
||||
frame = _current;
|
||||
cnt -= frame.Number;
|
||||
}
|
||||
while (!_mixedFrameSize && --cnt >= 0 && frame != null)
|
||||
{
|
||||
// make sure we have more frames to look at
|
||||
if (frame == _last && !_endFound)
|
||||
{
|
||||
do
|
||||
{
|
||||
FindNextFrame();
|
||||
} while (frame == _last && !_endFound);
|
||||
}
|
||||
|
||||
// if we've found a different frame size, fall through...
|
||||
if (_mixedFrameSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
frame = frame.Next;
|
||||
}
|
||||
|
||||
// this should not run unless we found mixed frames...
|
||||
while (frame != null && frame.SampleOffset + frame.SampleCount < sampleNumber)
|
||||
{
|
||||
if (frame == _last && !_endFound)
|
||||
{
|
||||
do
|
||||
{
|
||||
FindNextFrame();
|
||||
} while (frame == _last && !_endFound);
|
||||
}
|
||||
|
||||
frame = frame.Next;
|
||||
}
|
||||
if (frame == null) return -1;
|
||||
return (_current = frame).SampleOffset;
|
||||
}
|
||||
|
||||
internal MpegFrame NextFrame()
|
||||
{
|
||||
// if _current is null, we've returned the last frame already
|
||||
var frame = _current;
|
||||
if (frame != null)
|
||||
{
|
||||
if (_canSeek)
|
||||
{
|
||||
frame.SaveBuffer();
|
||||
DiscardThrough(frame.Offset + frame.FrameLength, false);
|
||||
}
|
||||
|
||||
if (frame == _last && !_endFound)
|
||||
{
|
||||
do
|
||||
{
|
||||
FindNextFrame();
|
||||
} while (frame == _last && !_endFound);
|
||||
}
|
||||
|
||||
_current = frame.Next;
|
||||
|
||||
if (!_canSeek)
|
||||
{
|
||||
// if we're in a forward-only stream, don't bother keeping the frames that have already been processed
|
||||
lock (_frameLock)
|
||||
{
|
||||
var temp = _first;
|
||||
_first = temp.Next;
|
||||
temp.Next = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
internal MpegFrame GetCurrentFrame()
|
||||
{
|
||||
return _current;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/MpegStreamReader.cs.meta
Normal file
11
Assets/NLayer/Decoder/MpegStreamReader.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 310b96f197b6c73448981d66c46231db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
60
Assets/NLayer/Decoder/RiffHeaderFrame.cs
Normal file
60
Assets/NLayer/Decoder/RiffHeaderFrame.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
/// <summary>
|
||||
/// RIFF header reader
|
||||
/// </summary>
|
||||
class RiffHeaderFrame : FrameBase
|
||||
{
|
||||
internal static RiffHeaderFrame TrySync(uint syncMark)
|
||||
{
|
||||
if (syncMark == 0x52494646U)
|
||||
{
|
||||
return new RiffHeaderFrame();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
RiffHeaderFrame()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override int Validate()
|
||||
{
|
||||
var buf = new byte[4];
|
||||
|
||||
// we expect this to be the "WAVE" chunk
|
||||
if (Read(8, buf) != 4) return -1;
|
||||
if (buf[0] != 'W' || buf[1] != 'A' || buf[2] != 'V' || buf[3] != 'E') return -1;
|
||||
|
||||
// now the "fmt " chunk
|
||||
if (Read(12, buf) != 4) return -1;
|
||||
if (buf[0] != 'f' || buf[1] != 'm' || buf[2] != 't' || buf[3] != ' ') return -1;
|
||||
|
||||
// we've found the fmt chunk, so look for the data chunk
|
||||
var offset = 16;
|
||||
while (true)
|
||||
{
|
||||
// read the length and seek forward
|
||||
if (Read(offset, buf) != 4) return -1;
|
||||
offset += 4 + BitConverter.ToInt32(buf, 0);
|
||||
|
||||
// get the chunk ID
|
||||
if (Read(offset, buf) != 4) return -1;
|
||||
offset += 4;
|
||||
|
||||
// if it's not the data chunk, try again
|
||||
if (buf[0] == 'd' && buf[1] == 'a' && buf[2] == 't' && buf[3] == 'a') break;
|
||||
}
|
||||
|
||||
// ... and now we know exactly where the frame ends
|
||||
return offset + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/RiffHeaderFrame.cs.meta
Normal file
11
Assets/NLayer/Decoder/RiffHeaderFrame.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 235a0e0ad3c05444586c72bfea1bb911
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/NLayer/Decoder/VBRInfo.cs
Normal file
32
Assets/NLayer/Decoder/VBRInfo.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace NLayer.Decoder
|
||||
{
|
||||
class VBRInfo
|
||||
{
|
||||
internal VBRInfo() { }
|
||||
|
||||
internal int SampleCount { get; set; }
|
||||
internal int SampleRate { get; set; }
|
||||
internal int Channels { get; set; }
|
||||
internal int VBRFrames { get; set; }
|
||||
internal int VBRBytes { get; set; }
|
||||
internal int VBRQuality { get; set; }
|
||||
internal int VBRDelay { get; set; }
|
||||
|
||||
internal long VBRStreamSampleCount
|
||||
{
|
||||
get
|
||||
{
|
||||
// we assume the entire stream is consistent wrt samples per frame
|
||||
return VBRFrames * SampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
internal int VBRAverageBitrate
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)((VBRBytes / (VBRStreamSampleCount / (double)SampleRate)) * 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Decoder/VBRInfo.cs.meta
Normal file
11
Assets/NLayer/Decoder/VBRInfo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00675fd6a1b19054687f4208b26035e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
Assets/NLayer/Enums.cs
Normal file
39
Assets/NLayer/Enums.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NLayer
|
||||
{
|
||||
public enum MpegVersion
|
||||
{
|
||||
Unknown = 0,
|
||||
Version1 = 10,
|
||||
Version2 = 20,
|
||||
Version25 = 25,
|
||||
}
|
||||
|
||||
public enum MpegLayer
|
||||
{
|
||||
Unknown = 0,
|
||||
LayerI = 1,
|
||||
LayerII = 2,
|
||||
LayerIII = 3,
|
||||
}
|
||||
|
||||
public enum MpegChannelMode
|
||||
{
|
||||
Stereo,
|
||||
JointStereo,
|
||||
DualChannel,
|
||||
Mono,
|
||||
}
|
||||
|
||||
public enum StereoMode
|
||||
{
|
||||
Both,
|
||||
LeftOnly,
|
||||
RightOnly,
|
||||
DownmixToMono,
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/Enums.cs.meta
Normal file
11
Assets/NLayer/Enums.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8421992dd2936b24e8b8e7c5988d90b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
85
Assets/NLayer/IMpegFrame.cs
Normal file
85
Assets/NLayer/IMpegFrame.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
namespace NLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a standard way of representing a MPEG frame to the decoder
|
||||
/// </summary>
|
||||
public interface IMpegFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample rate of this frame
|
||||
/// </summary>
|
||||
int SampleRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The samplerate index (directly from the header)
|
||||
/// </summary>
|
||||
int SampleRateIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Frame length in bytes
|
||||
/// </summary>
|
||||
int FrameLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bit Rate
|
||||
/// </summary>
|
||||
int BitRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// MPEG Version
|
||||
/// </summary>
|
||||
MpegVersion Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// MPEG Layer
|
||||
/// </summary>
|
||||
MpegLayer Layer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Channel Mode
|
||||
/// </summary>
|
||||
MpegChannelMode ChannelMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of samples in this frame
|
||||
/// </summary>
|
||||
int ChannelModeExtension { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The channel extension bits
|
||||
/// </summary>
|
||||
int SampleCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The bitrate index (directly from the header)
|
||||
/// </summary>
|
||||
int BitRateIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the Copyright bit is set
|
||||
/// </summary>
|
||||
bool IsCopyrighted { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether a CRC is present
|
||||
/// </summary>
|
||||
bool HasCrc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the CRC check failed (use error concealment strategy)
|
||||
/// </summary>
|
||||
bool IsCorrupted { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resets the bit reader so frames can be reused
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
/// <summary>
|
||||
/// Provides sequential access to the bitstream in the frame (after the header and optional CRC)
|
||||
/// </summary>
|
||||
/// <param name="bitCount">The number of bits to read</param>
|
||||
/// <returns>-1 if the end of the frame has been encountered, otherwise the bits requested</returns>
|
||||
int ReadBits(int bitCount);
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/IMpegFrame.cs.meta
Normal file
11
Assets/NLayer/IMpegFrame.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0056d4c5dca076040913bc2b925149b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
327
Assets/NLayer/MpegFile.cs
Normal file
327
Assets/NLayer/MpegFile.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
using System;
|
||||
|
||||
namespace NLayer
|
||||
{
|
||||
public class MpegFile : IDisposable
|
||||
{
|
||||
System.IO.Stream _stream;
|
||||
bool _closeStream, _eofFound;
|
||||
|
||||
Decoder.MpegStreamReader _reader;
|
||||
MpegFrameDecoder _decoder;
|
||||
|
||||
object _seekLock = new object();
|
||||
long _position;
|
||||
|
||||
/// <summary>
|
||||
/// Construct Mpeg file representation from filename.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The file which contains Mpeg data.</param>
|
||||
public MpegFile(string fileName)
|
||||
{
|
||||
Init(System.IO.File.Open(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct Mpeg file representation from stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The input stream which contains Mpeg data.</param>
|
||||
public MpegFile(System.IO.Stream stream)
|
||||
{
|
||||
Init(stream, false);
|
||||
}
|
||||
|
||||
void Init(System.IO.Stream stream, bool closeStream)
|
||||
{
|
||||
_stream = stream;
|
||||
_closeStream = closeStream;
|
||||
|
||||
_reader = new Decoder.MpegStreamReader(_stream);
|
||||
|
||||
_decoder = new MpegFrameDecoder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IDisposable.Dispose.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_closeStream)
|
||||
{
|
||||
_stream.Dispose();
|
||||
_closeStream = false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Sample rate of source Mpeg, in Hertz.
|
||||
/// </summary>
|
||||
public int SampleRate { get { return _reader.SampleRate; } }
|
||||
|
||||
/// <summary>
|
||||
/// Channel count of source Mpeg.
|
||||
/// </summary>
|
||||
public int Channels { get { return _reader.Channels; } }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the Mpeg stream supports seek operation.
|
||||
/// </summary>
|
||||
public bool CanSeek { get { return _reader.CanSeek; } }
|
||||
|
||||
/// <summary>
|
||||
/// Data length of decoded data, in PCM.
|
||||
/// </summary>
|
||||
public long Length { get { return _reader.SampleCount * _reader.Channels * sizeof(float); } }
|
||||
|
||||
/// <summary>
|
||||
/// Media duration of the Mpeg file.
|
||||
/// </summary>
|
||||
public TimeSpan Duration
|
||||
{
|
||||
get
|
||||
{
|
||||
var len = _reader.SampleCount;
|
||||
if (len == -1) return TimeSpan.Zero;
|
||||
return TimeSpan.FromSeconds((double)len / _reader.SampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current decode position, in number of sample. Calling the setter will result in a seeking operation.
|
||||
/// </summary>
|
||||
public long Position
|
||||
{
|
||||
get { return _position; }
|
||||
set
|
||||
{
|
||||
if (!_reader.CanSeek) throw new InvalidOperationException("Cannot Seek!");
|
||||
if (value < 0L) throw new ArgumentOutOfRangeException("value");
|
||||
|
||||
// we're thinking in 4-byte samples, pcmStep interleaved... adjust accordingly
|
||||
var samples = value / sizeof(float) / _reader.Channels;
|
||||
var sampleOffset = 0;
|
||||
|
||||
// seek to the frame preceding the one we want (unless we're seeking to the first frame)
|
||||
if (samples >= _reader.FirstFrameSampleCount)
|
||||
{
|
||||
sampleOffset = _reader.FirstFrameSampleCount;
|
||||
samples -= sampleOffset;
|
||||
}
|
||||
|
||||
lock (_seekLock)
|
||||
{
|
||||
// seek the stream
|
||||
var newPos = _reader.SeekTo(samples);
|
||||
if (newPos == -1) throw new ArgumentOutOfRangeException("value");
|
||||
|
||||
_decoder.Reset();
|
||||
|
||||
// if we have a sample offset, decode the next frame
|
||||
if (sampleOffset != 0)
|
||||
{
|
||||
_decoder.DecodeFrame(_reader.NextFrame(), _readBuf, 0); // throw away a frame (but allow the decoder to resync)
|
||||
newPos += sampleOffset;
|
||||
}
|
||||
|
||||
_position = newPos * sizeof(float) * _reader.Channels;
|
||||
_eofFound = false;
|
||||
|
||||
// clear the decoder & buffer
|
||||
_readBufOfs = _readBufLen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current decode position, represented by time. Calling the setter will result in a seeking operation.
|
||||
/// </summary>
|
||||
public TimeSpan Time
|
||||
{
|
||||
get { return TimeSpan.FromSeconds((double)_position / sizeof(float) / _reader.Channels / _reader.SampleRate); }
|
||||
set { Position = (long)(value.TotalSeconds * _reader.SampleRate * _reader.Channels * sizeof(float)); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the equalizer.
|
||||
/// </summary>
|
||||
/// <param name="eq">The equalizer, represented by an array of 32 adjustments in dB.</param>
|
||||
public void SetEQ(float[] eq)
|
||||
{
|
||||
_decoder.SetEQ(eq);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stereo mode used in decoding.
|
||||
/// </summary>
|
||||
public StereoMode StereoMode
|
||||
{
|
||||
get { return _decoder.StereoMode; }
|
||||
set { _decoder.StereoMode = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read specified samples into provided buffer. Do exactly the same as <see cref="ReadSamples(float[], int, int)"/>
|
||||
/// except that the data is written in type of byte, while still representing single-precision float (in local endian).
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer to write. Floating point data will be actually written into this byte array.</param>
|
||||
/// <param name="index">Writing offset on the destination buffer.</param>
|
||||
/// <param name="count">Length of samples to be read, in bytes.</param>
|
||||
/// <returns>Sample size actually reads, in bytes.</returns>
|
||||
public int ReadSamples(byte[] buffer, int index, int count)
|
||||
{
|
||||
if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
// make sure we're asking for an even number of samples
|
||||
count -= (count % sizeof(float));
|
||||
|
||||
return ReadSamplesImpl(buffer, index, count, 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read specified samples into provided buffer, as PCM format.
|
||||
/// Result varies with diffirent <see cref="StereoMode"/>:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>For <see cref="NLayer.StereoMode.Both"/>, sample data on both two channels will occur in turn (left first).</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>For <see cref="NLayer.StereoMode.LeftOnly"/> and <see cref="NLayer.StereoMode.RightOnly"/>, only data on
|
||||
/// specified channel will occur.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>For <see cref="NLayer.StereoMode.DownmixToMono"/>, two channels will be down-mixed into single channel.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="buffer">Buffer to write.</param>
|
||||
/// <param name="index">Writing offset on the destination buffer.</param>
|
||||
/// <param name="count">Count of samples to be read.</param>
|
||||
/// <returns>Sample count actually reads.</returns>
|
||||
public int ReadSamples(float[] buffer, int index, int count)
|
||||
{
|
||||
if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
// ReadSampleImpl "thinks" in bytes, so adjust accordingly
|
||||
return ReadSamplesImpl(buffer, index * sizeof(float), count * sizeof(float), 32) / sizeof(float);
|
||||
}
|
||||
|
||||
public int ReadSamplesInt16(byte[] buffer, int index, int count)
|
||||
{
|
||||
if (index < 0 || index + count > buffer.Length * sizeof(short)) throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
return ReadSamplesImpl(buffer, index, count, 16) * sizeof(short) / sizeof(float);
|
||||
}
|
||||
|
||||
public int ReadSamplesInt8(byte[] buffer, int index, int count)
|
||||
{
|
||||
if (index < 0 || index + count > buffer.Length * sizeof(float)) throw new ArgumentOutOfRangeException("index");
|
||||
|
||||
return ReadSamplesImpl(buffer, index, count, 8) * sizeof(byte) / sizeof(float);
|
||||
}
|
||||
|
||||
float[] _readBuf = new float[1152 * 2];
|
||||
int _readBufLen, _readBufOfs;
|
||||
|
||||
int ReadSamplesImpl(Array buffer, int index, int count, int bitDepth)
|
||||
{
|
||||
var cnt = 0;
|
||||
|
||||
// lock around the entire read operation so seeking doesn't bork our buffers as we decode
|
||||
lock (_seekLock)
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
if (_readBufLen > _readBufOfs)
|
||||
{
|
||||
// we have bytes in the buffer, so copy them first
|
||||
var temp = _readBufLen - _readBufOfs;
|
||||
if (temp > count) temp = count;
|
||||
if (bitDepth == 32)
|
||||
{
|
||||
Buffer.BlockCopy(_readBuf, _readBufOfs, buffer, index, temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < temp / sizeof(float); i++)
|
||||
{
|
||||
switch (bitDepth)
|
||||
{
|
||||
case 8:
|
||||
buffer.SetValue((byte)Math.Round(127.5f * _readBuf[_readBufOfs / sizeof(float) + i] + 127.5f), index / sizeof(float) + i);
|
||||
break;
|
||||
case 16:
|
||||
var value = (int)Math.Round(32767.5f * _readBuf[_readBufOfs / sizeof(float) + i] - 0.5f);
|
||||
if (value < 0) {
|
||||
value += 65536;
|
||||
}
|
||||
|
||||
buffer.SetValue((byte)(value % 256), 2 * (index / sizeof(float) + i));
|
||||
buffer.SetValue((byte)(value / 256), 2 * (index / sizeof(float) + i) + 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now update our counters...
|
||||
cnt += temp;
|
||||
|
||||
count -= temp;
|
||||
index += temp;
|
||||
|
||||
_position += temp;
|
||||
_readBufOfs += temp;
|
||||
|
||||
// finally, mark the buffer as empty if we've read everything in it
|
||||
if (_readBufOfs == _readBufLen)
|
||||
{
|
||||
_readBufLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// if the buffer is empty, try to fill it
|
||||
// NB: If we've already satisfied the read request, we'll still try to fill the buffer.
|
||||
// This ensures there's data in the pipe on the next call
|
||||
if (_readBufLen == 0)
|
||||
{
|
||||
if (_eofFound)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// decode the next frame (update _readBufXXX)
|
||||
var frame = _reader.NextFrame();
|
||||
if (frame == null)
|
||||
{
|
||||
_eofFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_readBufLen = _decoder.DecodeFrame(frame, _readBuf, 0) * sizeof(float);
|
||||
_readBufOfs = 0;
|
||||
}
|
||||
catch (System.IO.InvalidDataException)
|
||||
{
|
||||
// bad frame... try again...
|
||||
_decoder.Reset();
|
||||
_readBufOfs = _readBufLen = 0;
|
||||
continue;
|
||||
}
|
||||
catch (System.IO.EndOfStreamException)
|
||||
{
|
||||
// no more frames
|
||||
_eofFound = true;
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
frame.ClearBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/MpegFile.cs.meta
Normal file
11
Assets/NLayer/MpegFile.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88b61e579c5b92e4b9623ecf75f97c1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
191
Assets/NLayer/MpegFrameDecoder.cs
Normal file
191
Assets/NLayer/MpegFrameDecoder.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
|
||||
namespace NLayer
|
||||
{
|
||||
public class MpegFrameDecoder
|
||||
{
|
||||
Decoder.LayerIDecoder _layerIDecoder;
|
||||
Decoder.LayerIIDecoder _layerIIDecoder;
|
||||
Decoder.LayerIIIDecoder _layerIIIDecoder;
|
||||
|
||||
float[] _eqFactors;
|
||||
|
||||
// channel buffers for getting data out of the decoders...
|
||||
// we do it this way so the stereo interleaving code is in one place: DecodeFrameImpl(...)
|
||||
// if we ever add support for multi-channel, we'll have to add a pass after the initial
|
||||
// stereo decode (since multi-channel basically uses the stereo channels as a reference)
|
||||
float[] _ch0, _ch1;
|
||||
|
||||
public MpegFrameDecoder()
|
||||
{
|
||||
_ch0 = new float[1152];
|
||||
_ch1 = new float[1152];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the equalizer.
|
||||
/// </summary>
|
||||
/// <param name="eq">The equalizer, represented by an array of 32 adjustments in dB.</param>
|
||||
public void SetEQ(float[] eq)
|
||||
{
|
||||
if (eq != null)
|
||||
{
|
||||
var factors = new float[32];
|
||||
for (int i = 0; i < eq.Length; i++)
|
||||
{
|
||||
// convert from dB -> scaling
|
||||
factors[i] = (float)Math.Pow(2, eq[i] / 6);
|
||||
}
|
||||
_eqFactors = factors;
|
||||
}
|
||||
else
|
||||
{
|
||||
_eqFactors = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stereo mode used in decoding.
|
||||
/// </summary>
|
||||
public StereoMode StereoMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decode the Mpeg frame into provided buffer. Do exactly the same as <see cref="DecodeFrame(IMpegFrame, float[], int)"/>
|
||||
/// except that the data is written in type as byte array, while still representing single-precision float (in local endian).
|
||||
/// </summary>
|
||||
/// <param name="frame">The Mpeg frame to be decoded.</param>
|
||||
/// <param name="dest">Destination buffer. Decoded PCM (single-precision floating point array) will be written into it.</param>
|
||||
/// <param name="destOffset">Writing offset on the destination buffer.</param>
|
||||
/// <returns></returns>
|
||||
public int DecodeFrame(IMpegFrame frame, byte[] dest, int destOffset)
|
||||
{
|
||||
if (frame == null) throw new ArgumentNullException("frame");
|
||||
if (dest == null) throw new ArgumentNullException("dest");
|
||||
if (destOffset % 4 != 0) throw new ArgumentException("Must be an even multiple of 4", "destOffset");
|
||||
|
||||
var bufferAvailable = (dest.Length - destOffset) / 4;
|
||||
if (bufferAvailable < (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2) * frame.SampleCount)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
return DecodeFrameImpl(frame, dest, destOffset / 4) * 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode the Mpeg frame into provided buffer.
|
||||
/// Result varies with different <see cref="StereoMode"/>:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>For <see cref="NLayer.StereoMode.Both"/>, sample data on both two channels will occur in turn (left first).</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>For <see cref="NLayer.StereoMode.LeftOnly"/> and <see cref="NLayer.StereoMode.RightOnly"/>, only data on
|
||||
/// specified channel will occur.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>For <see cref="NLayer.StereoMode.DownmixToMono"/>, two channels will be down-mixed into single channel.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="frame">The Mpeg frame to be decoded.</param>
|
||||
/// <param name="dest">Destination buffer. Decoded PCM (single-precision floating point array) will be written into it.</param>
|
||||
/// <param name="destOffset">Writing offset on the destination buffer.</param>
|
||||
/// <returns></returns>
|
||||
public int DecodeFrame(IMpegFrame frame, float[] dest, int destOffset)
|
||||
{
|
||||
if (frame == null) throw new ArgumentNullException("frame");
|
||||
if (dest == null) throw new ArgumentNullException("dest");
|
||||
|
||||
if (dest.Length - destOffset < (frame.ChannelMode == MpegChannelMode.Mono ? 1 : 2) * frame.SampleCount)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
return DecodeFrameImpl(frame, dest, destOffset);
|
||||
}
|
||||
|
||||
int DecodeFrameImpl(IMpegFrame frame, Array dest, int destOffset)
|
||||
{
|
||||
frame.Reset();
|
||||
|
||||
Decoder.LayerDecoderBase curDecoder = null;
|
||||
switch (frame.Layer)
|
||||
{
|
||||
case MpegLayer.LayerI:
|
||||
if (_layerIDecoder == null)
|
||||
{
|
||||
_layerIDecoder = new Decoder.LayerIDecoder();
|
||||
}
|
||||
curDecoder = _layerIDecoder;
|
||||
break;
|
||||
case MpegLayer.LayerII:
|
||||
if (_layerIIDecoder == null)
|
||||
{
|
||||
_layerIIDecoder = new Decoder.LayerIIDecoder();
|
||||
}
|
||||
curDecoder = _layerIIDecoder;
|
||||
break;
|
||||
case MpegLayer.LayerIII:
|
||||
if (_layerIIIDecoder == null)
|
||||
{
|
||||
_layerIIIDecoder = new Decoder.LayerIIIDecoder();
|
||||
}
|
||||
curDecoder = _layerIIIDecoder;
|
||||
break;
|
||||
}
|
||||
|
||||
if (curDecoder != null)
|
||||
{
|
||||
curDecoder.SetEQ(_eqFactors);
|
||||
curDecoder.StereoMode = StereoMode;
|
||||
|
||||
var cnt = curDecoder.DecodeFrame(frame, _ch0, _ch1);
|
||||
|
||||
if (frame.ChannelMode == MpegChannelMode.Mono)
|
||||
{
|
||||
Buffer.BlockCopy(_ch0, 0, dest, destOffset * sizeof(float), cnt * sizeof(float));
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is kinda annoying... if we're doing a downmix, we should technically only output a single channel
|
||||
// The problem is, our caller is probably expecting stereo output. Grrrr....
|
||||
|
||||
// We use Buffer.BlockCopy here because we don't know dest's type, but do know it's big enough to do the copy
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
Buffer.BlockCopy(_ch0, i * sizeof(float), dest, destOffset * sizeof(float), sizeof(float));
|
||||
++destOffset;
|
||||
Buffer.BlockCopy(_ch1, i * sizeof(float), dest, destOffset * sizeof(float), sizeof(float));
|
||||
++destOffset;
|
||||
}
|
||||
cnt *= 2;
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the decoder.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
// the synthesis filters need to be cleared
|
||||
if (_layerIDecoder != null)
|
||||
{
|
||||
_layerIDecoder.ResetForSeek();
|
||||
}
|
||||
if (_layerIIDecoder != null)
|
||||
{
|
||||
_layerIIDecoder.ResetForSeek();
|
||||
}
|
||||
if (_layerIIIDecoder != null)
|
||||
{
|
||||
_layerIIIDecoder.ResetForSeek();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/NLayer/MpegFrameDecoder.cs.meta
Normal file
11
Assets/NLayer/MpegFrameDecoder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ca3a6fbd1d4b3a41bae1e8f1a1196f0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/NLayer/NLayer.csproj.meta
Normal file
7
Assets/NLayer/NLayer.csproj.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dbdd314784e80440b48088e6357c1ef
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -33,6 +33,7 @@ MonoBehaviour:
|
||||
- Assembly-CSharp
|
||||
- Assembly-CSharp-firstpass
|
||||
- CW.Common
|
||||
- IngameDebugConsole.Runtime
|
||||
- LeanCommon
|
||||
- LeanPool
|
||||
- Lofelt.NiceVibrations
|
||||
|
||||
File diff suppressed because one or more lines are too long
9
Assets/Plugins/IngameDebugConsole.meta
Normal file
9
Assets/Plugins/IngameDebugConsole.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c57523b63ddb094b835b6613da12763
|
||||
folderAsset: yes
|
||||
timeCreated: 1596819199
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Plugins/IngameDebugConsole/Android.meta
Normal file
9
Assets/Plugins/IngameDebugConsole/Android.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d7d7a61a5341904eb3c65af025b1d86
|
||||
folderAsset: yes
|
||||
timeCreated: 1510075633
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
#if (UNITY_EDITOR || UNITY_ANDROID) && UNITY_ANDROID_JNI
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
// Credit: https://stackoverflow.com/a/41018028/2373034
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugLogLogcatListener : AndroidJavaProxy
|
||||
{
|
||||
private Queue<string> queuedLogs;
|
||||
private AndroidJavaObject nativeObject;
|
||||
|
||||
public DebugLogLogcatListener() : base( "com.yasirkula.unity.DebugConsoleLogcatLogReceiver" )
|
||||
{
|
||||
queuedLogs = new Queue<string>( 16 );
|
||||
}
|
||||
|
||||
~DebugLogLogcatListener()
|
||||
{
|
||||
Stop();
|
||||
|
||||
if( nativeObject != null )
|
||||
nativeObject.Dispose();
|
||||
}
|
||||
|
||||
public void Start( string arguments )
|
||||
{
|
||||
if( nativeObject == null )
|
||||
nativeObject = new AndroidJavaObject( "com.yasirkula.unity.DebugConsoleLogcatLogger" );
|
||||
|
||||
nativeObject.Call( "Start", this, arguments );
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if( nativeObject != null )
|
||||
nativeObject.Call( "Stop" );
|
||||
}
|
||||
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
public void OnLogReceived( string log )
|
||||
{
|
||||
queuedLogs.Enqueue( log );
|
||||
}
|
||||
|
||||
public string GetLog()
|
||||
{
|
||||
if( queuedLogs.Count > 0 )
|
||||
return queuedLogs.Dequeue();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd3b7385882055d4a8c2b91deb6b2470
|
||||
timeCreated: 1510076185
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/IngameDebugConsole/Android/IngameDebugConsole.aar
Normal file
BIN
Assets/Plugins/IngameDebugConsole/Android/IngameDebugConsole.aar
Normal file
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf909fab1c14af446b0a854de42289b2
|
||||
timeCreated: 1510086220
|
||||
licenseType: Free
|
||||
PluginImporter:
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
data:
|
||||
first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
data:
|
||||
first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
data:
|
||||
first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Plugins/IngameDebugConsole/Editor.meta
Normal file
9
Assets/Plugins/IngameDebugConsole/Editor.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86f54622630720f4abe279acdbb8886f
|
||||
folderAsset: yes
|
||||
timeCreated: 1561217660
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,185 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
[CustomEditor( typeof( DebugLogManager ) )]
|
||||
public class DebugLogManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty singleton;
|
||||
private SerializedProperty minimumHeight;
|
||||
private SerializedProperty enableHorizontalResizing;
|
||||
private SerializedProperty resizeFromRight;
|
||||
private SerializedProperty minimumWidth;
|
||||
private SerializedProperty logWindowOpacity;
|
||||
private SerializedProperty popupOpacity;
|
||||
private SerializedProperty popupVisibility;
|
||||
private SerializedProperty popupVisibilityLogFilter;
|
||||
private SerializedProperty startMinimized;
|
||||
private SerializedProperty toggleWithKey;
|
||||
private SerializedProperty toggleKey;
|
||||
private SerializedProperty enableSearchbar;
|
||||
private SerializedProperty topSearchbarMinWidth;
|
||||
private SerializedProperty copyAllLogsOnResizeButtonClick;
|
||||
private SerializedProperty receiveLogsWhileInactive;
|
||||
private SerializedProperty receiveInfoLogs;
|
||||
private SerializedProperty receiveWarningLogs;
|
||||
private SerializedProperty receiveErrorLogs;
|
||||
private SerializedProperty receiveExceptionLogs;
|
||||
private SerializedProperty captureLogTimestamps;
|
||||
private SerializedProperty alwaysDisplayTimestamps;
|
||||
private SerializedProperty maxLogCount;
|
||||
private SerializedProperty logsToRemoveAfterMaxLogCount;
|
||||
private SerializedProperty queuedLogLimit;
|
||||
private SerializedProperty clearCommandAfterExecution;
|
||||
private SerializedProperty commandHistorySize;
|
||||
private SerializedProperty showCommandSuggestions;
|
||||
private SerializedProperty receiveLogcatLogsInAndroid;
|
||||
private SerializedProperty logcatArguments;
|
||||
private SerializedProperty avoidScreenCutout;
|
||||
private SerializedProperty popupAvoidsScreenCutout;
|
||||
private SerializedProperty autoFocusOnCommandInputField;
|
||||
|
||||
private readonly GUIContent popupVisibilityLogFilterLabel = new GUIContent( "Log Filter", "Determines which log types will show the popup on screen" );
|
||||
private readonly GUIContent receivedLogTypesLabel = new GUIContent( "Received Log Types", "Only these logs will be received by the console window, other logs will simply be skipped" );
|
||||
private readonly GUIContent receiveInfoLogsLabel = new GUIContent( "Info" );
|
||||
private readonly GUIContent receiveWarningLogsLabel = new GUIContent( "Warning" );
|
||||
private readonly GUIContent receiveErrorLogsLabel = new GUIContent( "Error" );
|
||||
private readonly GUIContent receiveExceptionLogsLabel = new GUIContent( "Exception" );
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
singleton = serializedObject.FindProperty( "singleton" );
|
||||
minimumHeight = serializedObject.FindProperty( "minimumHeight" );
|
||||
enableHorizontalResizing = serializedObject.FindProperty( "enableHorizontalResizing" );
|
||||
resizeFromRight = serializedObject.FindProperty( "resizeFromRight" );
|
||||
minimumWidth = serializedObject.FindProperty( "minimumWidth" );
|
||||
logWindowOpacity = serializedObject.FindProperty( "logWindowOpacity" );
|
||||
popupOpacity = serializedObject.FindProperty( "popupOpacity" );
|
||||
popupVisibility = serializedObject.FindProperty( "popupVisibility" );
|
||||
popupVisibilityLogFilter = serializedObject.FindProperty( "popupVisibilityLogFilter" );
|
||||
startMinimized = serializedObject.FindProperty( "startMinimized" );
|
||||
toggleWithKey = serializedObject.FindProperty( "toggleWithKey" );
|
||||
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
toggleKey = serializedObject.FindProperty( "toggleBinding" );
|
||||
#else
|
||||
toggleKey = serializedObject.FindProperty( "toggleKey" );
|
||||
#endif
|
||||
enableSearchbar = serializedObject.FindProperty( "enableSearchbar" );
|
||||
topSearchbarMinWidth = serializedObject.FindProperty( "topSearchbarMinWidth" );
|
||||
receiveLogsWhileInactive = serializedObject.FindProperty( "receiveLogsWhileInactive" );
|
||||
receiveInfoLogs = serializedObject.FindProperty( "receiveInfoLogs" );
|
||||
receiveWarningLogs = serializedObject.FindProperty( "receiveWarningLogs" );
|
||||
receiveErrorLogs = serializedObject.FindProperty( "receiveErrorLogs" );
|
||||
receiveExceptionLogs = serializedObject.FindProperty( "receiveExceptionLogs" );
|
||||
captureLogTimestamps = serializedObject.FindProperty( "captureLogTimestamps" );
|
||||
alwaysDisplayTimestamps = serializedObject.FindProperty( "alwaysDisplayTimestamps" );
|
||||
maxLogCount = serializedObject.FindProperty( "maxLogCount" );
|
||||
logsToRemoveAfterMaxLogCount = serializedObject.FindProperty( "logsToRemoveAfterMaxLogCount" );
|
||||
queuedLogLimit = serializedObject.FindProperty( "queuedLogLimit" );
|
||||
copyAllLogsOnResizeButtonClick = serializedObject.FindProperty("copyAllLogsOnResizeButtonClick");
|
||||
clearCommandAfterExecution = serializedObject.FindProperty( "clearCommandAfterExecution" );
|
||||
commandHistorySize = serializedObject.FindProperty( "commandHistorySize" );
|
||||
showCommandSuggestions = serializedObject.FindProperty( "showCommandSuggestions" );
|
||||
receiveLogcatLogsInAndroid = serializedObject.FindProperty( "receiveLogcatLogsInAndroid" );
|
||||
logcatArguments = serializedObject.FindProperty( "logcatArguments" );
|
||||
avoidScreenCutout = serializedObject.FindProperty( "avoidScreenCutout" );
|
||||
popupAvoidsScreenCutout = serializedObject.FindProperty( "popupAvoidsScreenCutout" );
|
||||
autoFocusOnCommandInputField = serializedObject.FindProperty( "autoFocusOnCommandInputField" );
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.PropertyField( singleton );
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField( minimumHeight );
|
||||
|
||||
EditorGUILayout.PropertyField( enableHorizontalResizing );
|
||||
if( enableHorizontalResizing.boolValue )
|
||||
{
|
||||
DrawSubProperty( resizeFromRight );
|
||||
DrawSubProperty( minimumWidth );
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField( avoidScreenCutout );
|
||||
DrawSubProperty( popupAvoidsScreenCutout );
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField( startMinimized );
|
||||
EditorGUILayout.PropertyField( logWindowOpacity );
|
||||
EditorGUILayout.PropertyField( popupOpacity );
|
||||
|
||||
EditorGUILayout.PropertyField( popupVisibility );
|
||||
if( popupVisibility.intValue == (int) PopupVisibility.WhenLogReceived )
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
Rect rect = EditorGUILayout.GetControlRect();
|
||||
EditorGUI.BeginProperty( rect, GUIContent.none, popupVisibilityLogFilter );
|
||||
popupVisibilityLogFilter.intValue = (int) (DebugLogFilter) EditorGUI.EnumFlagsField( rect, popupVisibilityLogFilterLabel, (DebugLogFilter) popupVisibilityLogFilter.intValue );
|
||||
EditorGUI.EndProperty();
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField( toggleWithKey );
|
||||
if( toggleWithKey.boolValue )
|
||||
DrawSubProperty( toggleKey );
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField( enableSearchbar );
|
||||
if( enableSearchbar.boolValue )
|
||||
DrawSubProperty( topSearchbarMinWidth );
|
||||
|
||||
EditorGUILayout.PropertyField(copyAllLogsOnResizeButtonClick);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField( receiveLogsWhileInactive );
|
||||
|
||||
EditorGUILayout.PrefixLabel( receivedLogTypesLabel );
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField( receiveInfoLogs, receiveInfoLogsLabel );
|
||||
EditorGUILayout.PropertyField( receiveWarningLogs, receiveWarningLogsLabel );
|
||||
EditorGUILayout.PropertyField( receiveErrorLogs, receiveErrorLogsLabel );
|
||||
EditorGUILayout.PropertyField( receiveExceptionLogs, receiveExceptionLogsLabel );
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.PropertyField( receiveLogcatLogsInAndroid );
|
||||
if( receiveLogcatLogsInAndroid.boolValue )
|
||||
DrawSubProperty( logcatArguments );
|
||||
|
||||
EditorGUILayout.PropertyField( captureLogTimestamps );
|
||||
if( captureLogTimestamps.boolValue )
|
||||
DrawSubProperty( alwaysDisplayTimestamps );
|
||||
|
||||
EditorGUILayout.PropertyField( maxLogCount );
|
||||
DrawSubProperty( logsToRemoveAfterMaxLogCount );
|
||||
|
||||
EditorGUILayout.PropertyField( queuedLogLimit );
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField( clearCommandAfterExecution );
|
||||
EditorGUILayout.PropertyField( commandHistorySize );
|
||||
EditorGUILayout.PropertyField( showCommandSuggestions );
|
||||
EditorGUILayout.PropertyField( autoFocusOnCommandInputField );
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawPropertiesExcluding( serializedObject, "m_Script" );
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawSubProperty( SerializedProperty property )
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField( property );
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c23e5c521cb0c54b9a638b2a653d1d3
|
||||
timeCreated: 1561217671
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "IngameDebugConsole.Editor",
|
||||
"references": [
|
||||
"IngameDebugConsole.Runtime"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 466e67dabd1db22468246c39eddb6c3f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "IngameDebugConsole.Runtime",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"Unity.InputSystem",
|
||||
"Unity.TextMeshPro"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.modules.androidjni",
|
||||
"expression": "",
|
||||
"define": "UNITY_ANDROID_JNI"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3de88c88fbbb8f944b9210d496af9762
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
5111
Assets/Plugins/IngameDebugConsole/IngameDebugConsole.prefab
Normal file
5111
Assets/Plugins/IngameDebugConsole/IngameDebugConsole.prefab
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67117722a812a2e46ab8cb8eafbf5f5e
|
||||
timeCreated: 1466014755
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Plugins/IngameDebugConsole/Prefabs.meta
Normal file
9
Assets/Plugins/IngameDebugConsole/Prefabs.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dbc36665bc0d684db9a4447e27a7a4b
|
||||
folderAsset: yes
|
||||
timeCreated: 1520417401
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1386426139070838
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 224955737853170496}
|
||||
- component: {fileID: 222541766812100524}
|
||||
- component: {fileID: 6838696818539158795}
|
||||
m_Layer: 5
|
||||
m_Name: CommandSuggestion
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &224955737853170496
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1386426139070838}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &222541766812100524
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1386426139070838}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &6838696818539158795
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1386426139070838}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: help
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4292335574
|
||||
m_fontColor: {r: 0.83823526, g: 0.84439874, b: 0.84439874, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 16
|
||||
m_fontSizeBase: 16
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 1
|
||||
m_fontSizeMax: 40
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 3
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 0
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e66896448428cf46a1854dbdc014914
|
||||
timeCreated: 1601390136
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
mainObjectFileID: 100100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
841
Assets/Plugins/IngameDebugConsole/Prefabs/DebugLogItem.prefab
Normal file
841
Assets/Plugins/IngameDebugConsole/Prefabs/DebugLogItem.prefab
Normal file
@@ -0,0 +1,841 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &104862
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 22461494}
|
||||
- component: {fileID: 22233942}
|
||||
- component: {fileID: 11411806}
|
||||
m_Layer: 5
|
||||
m_Name: LogCount
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &22461494
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 104862}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 22420350}
|
||||
m_Father: {fileID: 22479264}
|
||||
m_RootOrder: 2
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 1, y: 0.5}
|
||||
m_AnchorMax: {x: 1, y: 0.5}
|
||||
m_AnchoredPosition: {x: -20, y: 0}
|
||||
m_SizeDelta: {x: 30, y: 25}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &22233942
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 104862}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &11411806
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 104862}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.42647058, g: 0.42647058, b: 0.42647058, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: b3f0d976f6d6802479d6465d11b3aa68, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1.3
|
||||
--- !u!1 &151462
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 22420350}
|
||||
- component: {fileID: 22200920}
|
||||
- component: {fileID: 5450305048240168820}
|
||||
m_Layer: 5
|
||||
m_Name: LogCountText
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &22420350
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 151462}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 22461494}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -2, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &22200920
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 151462}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &5450305048240168820
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 151462}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: 1
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4292335574
|
||||
m_fontColor: {r: 0.83823526, g: 0.84439874, b: 0.84439874, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 14
|
||||
m_fontSizeBase: 36
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 1
|
||||
m_fontSizeMin: 8
|
||||
m_fontSizeMax: 14
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 0
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!1 &152362
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 22427300}
|
||||
- component: {fileID: 22262284}
|
||||
- component: {fileID: 11404142}
|
||||
m_Layer: 5
|
||||
m_Name: LogType
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &22427300
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 152362}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 22479264}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0.5}
|
||||
m_AnchorMax: {x: 0, y: 0.5}
|
||||
m_AnchoredPosition: {x: 15, y: 0}
|
||||
m_SizeDelta: {x: 25, y: 25}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &22262284
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 152362}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &11404142
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 152362}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 5a97d5afa6254804f81b7ba956296996, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &166880
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 22479264}
|
||||
- component: {fileID: 22288988}
|
||||
- component: {fileID: 11459012}
|
||||
- component: {fileID: 11408050}
|
||||
- component: {fileID: 11456372}
|
||||
- component: {fileID: 225819852034701160}
|
||||
m_Layer: 5
|
||||
m_Name: DebugLogItem
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &22479264
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 166880}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 22427300}
|
||||
- {fileID: 224737693311518052}
|
||||
- {fileID: 22461494}
|
||||
- {fileID: 224006190298411330}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 35}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!222 &22288988
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 166880}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &11459012
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 166880}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.23529412, g: 0.23529412, b: 0.23529412, a: 0.697}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 98e8e1cf8dc7dbf469617c2e40c8a944, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &11408050
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 166880}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d2ea291be9de70a4abfec595203c96c1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
transformComponent: {fileID: 22479264}
|
||||
imageComponent: {fileID: 11459012}
|
||||
canvasGroupComponent: {fileID: 225819852034701160}
|
||||
logText: {fileID: 3887244321031527211}
|
||||
logTypeImage: {fileID: 11404142}
|
||||
logCountParent: {fileID: 104862}
|
||||
logCountText: {fileID: 5450305048240168820}
|
||||
copyLogButton: {fileID: 114694923173451186}
|
||||
--- !u!114 &11456372
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 166880}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 0
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Highlighted
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 11459012}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!225 &225819852034701160
|
||||
CanvasGroup:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 166880}
|
||||
m_Enabled: 1
|
||||
m_Alpha: 1
|
||||
m_Interactable: 1
|
||||
m_BlocksRaycasts: 1
|
||||
m_IgnoreParentGroups: 0
|
||||
--- !u!1 &1396836967994216
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 224006190298411330}
|
||||
- component: {fileID: 222870443111501910}
|
||||
- component: {fileID: 114119781176956926}
|
||||
- component: {fileID: 114694923173451186}
|
||||
m_Layer: 5
|
||||
m_Name: CopyLogButton
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 0
|
||||
--- !u!224 &224006190298411330
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1396836967994216}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 224887990600088790}
|
||||
m_Father: {fileID: 22479264}
|
||||
m_RootOrder: 3
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 2}
|
||||
m_SizeDelta: {x: -80, y: 36}
|
||||
m_Pivot: {x: 0.5, y: 0}
|
||||
--- !u!222 &222870443111501910
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1396836967994216}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &114119781176956926
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1396836967994216}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.42647058, g: 0.42647058, b: 0.42647058, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 066d3840badf4d24dba1d42b4c59b888, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &114694923173451186
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1396836967994216}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Highlighted
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 114119781176956926}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!1 &1503640463151286
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 224887990600088790}
|
||||
- component: {fileID: 222313182602304162}
|
||||
- component: {fileID: 6497267641603342931}
|
||||
m_Layer: 5
|
||||
m_Name: Text
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &224887990600088790
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1503640463151286}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 224006190298411330}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &222313182602304162
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1503640463151286}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &6497267641603342931
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1503640463151286}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: Copy
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4292335574
|
||||
m_fontColor: {r: 0.83823526, g: 0.84439874, b: 0.84439874, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 16
|
||||
m_fontSizeBase: 16
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 1
|
||||
m_fontSizeMax: 40
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 0
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 0
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 0
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!1 &1785910143472904
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 224737693311518052}
|
||||
- component: {fileID: 222175805939703770}
|
||||
- component: {fileID: 3887244321031527211}
|
||||
m_Layer: 5
|
||||
m_Name: LogText
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &224737693311518052
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1785910143472904}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 22479264}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 12.5, y: 0}
|
||||
m_SizeDelta: {x: -35, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &222175805939703770
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1785910143472904}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &3887244321031527211
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1785910143472904}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: Debug.Log summary
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4292335574
|
||||
m_fontColor: {r: 0.83823526, g: 0.84439874, b: 0.84439874, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 13.5
|
||||
m_fontSizeBase: 13.5
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 1
|
||||
m_fontSizeMax: 40
|
||||
m_fontStyle: 0
|
||||
m_HorizontalAlignment: 1
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 1
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 0
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 391be5df5ef62f345bb76a1051c04da7
|
||||
timeCreated: 1465919887
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/Plugins/IngameDebugConsole/README.txt
Normal file
7
Assets/Plugins/IngameDebugConsole/README.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
= In-game Debug Console (v1.8.2) =
|
||||
|
||||
Documentation: https://github.com/yasirkula/UnityIngameDebugConsole
|
||||
FAQ: https://github.com/yasirkula/UnityIngameDebugConsole#faq
|
||||
E-mail: yasirkula@gmail.com
|
||||
|
||||
You can simply place the IngameDebugConsole prefab to your scene. Hovering the cursor over its properties in the Inspector will reveal explanatory tooltips.
|
||||
8
Assets/Plugins/IngameDebugConsole/README.txt.meta
Normal file
8
Assets/Plugins/IngameDebugConsole/README.txt.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edf2ac73f7bc3064c96d53009106dc53
|
||||
timeCreated: 1563307881
|
||||
licenseType: Free
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Plugins/IngameDebugConsole/Scripts.meta
Normal file
9
Assets/Plugins/IngameDebugConsole/Scripts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 860c08388401a6d4e858fe4910ea9337
|
||||
folderAsset: yes
|
||||
timeCreated: 1465930645
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7de74709c0f949d42853e89b41f0c939
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public abstract class ConsoleAttribute : Attribute
|
||||
{
|
||||
public MethodInfo Method { get; private set; }
|
||||
public abstract int Order { get; }
|
||||
|
||||
public void SetMethod(MethodInfo method)
|
||||
{
|
||||
if (Method != null)
|
||||
throw new Exception("Method was already initialized.");
|
||||
|
||||
Method = method;
|
||||
}
|
||||
|
||||
public abstract void Load();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efc4511f2eea8034ca3a0a29cac8f554
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
|
||||
public class ConsoleCustomTypeParserAttribute : ConsoleAttribute
|
||||
{
|
||||
public readonly Type type;
|
||||
public readonly string readableName;
|
||||
|
||||
public override int Order { get { return 0; } }
|
||||
|
||||
public ConsoleCustomTypeParserAttribute(Type type, string readableName = null)
|
||||
{
|
||||
this.type = type;
|
||||
this.readableName = readableName;
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
DebugLogConsole.AddCustomParameterType(type, (DebugLogConsole.ParseFunction)Delegate.CreateDelegate(typeof(DebugLogConsole.ParseFunction), Method), readableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b014aa072d9631848babd5dafb325d3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
[AttributeUsage( AttributeTargets.Method, Inherited = false, AllowMultiple = true )]
|
||||
public class ConsoleMethodAttribute : ConsoleAttribute
|
||||
{
|
||||
private string m_command;
|
||||
private string m_description;
|
||||
private string[] m_parameterNames;
|
||||
|
||||
public string Command { get { return m_command; } }
|
||||
public string Description { get { return m_description; } }
|
||||
public string[] ParameterNames { get { return m_parameterNames; } }
|
||||
|
||||
public override int Order { get { return 1; } }
|
||||
|
||||
public ConsoleMethodAttribute( string command, string description, params string[] parameterNames )
|
||||
{
|
||||
m_command = command;
|
||||
m_description = description;
|
||||
m_parameterNames = parameterNames;
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
DebugLogConsole.AddCommand(Command, Description, Method, null, ParameterNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 324bb39c0bff0f74fa42f83e91f07e3a
|
||||
timeCreated: 1520710946
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
313
Assets/Plugins/IngameDebugConsole/Scripts/CircularBuffer.cs
Normal file
313
Assets/Plugins/IngameDebugConsole/Scripts/CircularBuffer.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class CircularBuffer<T>
|
||||
{
|
||||
private readonly T[] array;
|
||||
private int startIndex;
|
||||
|
||||
public int Count { get; private set; }
|
||||
public T this[int index] { get { return array[( startIndex + index ) % array.Length]; } }
|
||||
|
||||
public CircularBuffer( int capacity )
|
||||
{
|
||||
array = new T[capacity];
|
||||
}
|
||||
|
||||
// Old elements are overwritten when capacity is reached
|
||||
public void Add( T value )
|
||||
{
|
||||
if( Count < array.Length )
|
||||
array[Count++] = value;
|
||||
else
|
||||
{
|
||||
array[startIndex] = value;
|
||||
if( ++startIndex >= array.Length )
|
||||
startIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
T[] result = new T[Count];
|
||||
for (int i = 0; i < Count; i++)
|
||||
result[i] = this[i];
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicCircularBuffer<T>
|
||||
{
|
||||
private T[] array;
|
||||
private int startIndex;
|
||||
|
||||
public int Count { get; private set; }
|
||||
public int Capacity { get { return array.Length; } }
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get { return array[( startIndex + index ) % array.Length]; }
|
||||
set { array[( startIndex + index ) % array.Length] = value; }
|
||||
}
|
||||
|
||||
public DynamicCircularBuffer( int initialCapacity = 2 )
|
||||
{
|
||||
array = new T[initialCapacity];
|
||||
}
|
||||
|
||||
private void SetCapacity( int capacity )
|
||||
{
|
||||
T[] newArray = new T[capacity];
|
||||
if( Count > 0 )
|
||||
{
|
||||
int elementsBeforeWrap = Mathf.Min( Count, array.Length - startIndex );
|
||||
Array.Copy( array, startIndex, newArray, 0, elementsBeforeWrap );
|
||||
if( elementsBeforeWrap < Count )
|
||||
Array.Copy( array, 0, newArray, elementsBeforeWrap, Count - elementsBeforeWrap );
|
||||
}
|
||||
|
||||
array = newArray;
|
||||
startIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>Inserts the value to the beginning of the collection.</summary>
|
||||
public void AddFirst( T value )
|
||||
{
|
||||
if( array.Length == Count )
|
||||
SetCapacity( Mathf.Max( array.Length * 2, 4 ) );
|
||||
|
||||
startIndex = ( startIndex > 0 ) ? ( startIndex - 1 ) : ( array.Length - 1 );
|
||||
array[startIndex] = value;
|
||||
Count++;
|
||||
}
|
||||
|
||||
/// <summary>Adds the value to the end of the collection.</summary>
|
||||
public void Add( T value )
|
||||
{
|
||||
if( array.Length == Count )
|
||||
SetCapacity( Mathf.Max( array.Length * 2, 4 ) );
|
||||
|
||||
this[Count++] = value;
|
||||
}
|
||||
|
||||
public void AddRange( DynamicCircularBuffer<T> other )
|
||||
{
|
||||
if( other.Count == 0 )
|
||||
return;
|
||||
|
||||
if( array.Length < Count + other.Count )
|
||||
SetCapacity( Mathf.Max( array.Length * 2, Count + other.Count ) );
|
||||
|
||||
int insertStartIndex = ( startIndex + Count ) % array.Length;
|
||||
int elementsBeforeWrap = Mathf.Min( other.Count, array.Length - insertStartIndex );
|
||||
int otherElementsBeforeWrap = Mathf.Min( other.Count, other.array.Length - other.startIndex );
|
||||
|
||||
Array.Copy( other.array, other.startIndex, array, insertStartIndex, Mathf.Min( elementsBeforeWrap, otherElementsBeforeWrap ) );
|
||||
if( elementsBeforeWrap < otherElementsBeforeWrap ) // This array wrapped before the other array
|
||||
Array.Copy( other.array, other.startIndex + elementsBeforeWrap, array, 0, otherElementsBeforeWrap - elementsBeforeWrap );
|
||||
else if( elementsBeforeWrap > otherElementsBeforeWrap ) // The other array wrapped before this array
|
||||
Array.Copy( other.array, 0, array, insertStartIndex + otherElementsBeforeWrap, elementsBeforeWrap - otherElementsBeforeWrap );
|
||||
|
||||
int copiedElements = Mathf.Max( elementsBeforeWrap, otherElementsBeforeWrap );
|
||||
if( copiedElements < other.Count ) // Both arrays wrapped and there's still some elements left to copy
|
||||
Array.Copy( other.array, copiedElements - otherElementsBeforeWrap, array, copiedElements - elementsBeforeWrap, other.Count - copiedElements );
|
||||
|
||||
Count += other.Count;
|
||||
}
|
||||
|
||||
public T RemoveFirst()
|
||||
{
|
||||
T element = array[startIndex];
|
||||
array[startIndex] = default( T );
|
||||
|
||||
if( ++startIndex == array.Length )
|
||||
startIndex = 0;
|
||||
|
||||
Count--;
|
||||
return element;
|
||||
}
|
||||
|
||||
public T RemoveLast()
|
||||
{
|
||||
int index = ( startIndex + Count - 1 ) % array.Length;
|
||||
T element = array[index];
|
||||
array[index] = default( T );
|
||||
|
||||
Count--;
|
||||
return element;
|
||||
}
|
||||
|
||||
public int RemoveAll( Predicate<T> shouldRemoveElement )
|
||||
{
|
||||
return RemoveAll<T>( shouldRemoveElement, null, null );
|
||||
}
|
||||
|
||||
public int RemoveAll<Y>( Predicate<T> shouldRemoveElement, Action<T, int> onElementIndexChanged, DynamicCircularBuffer<Y> synchronizedBuffer )
|
||||
{
|
||||
Y[] synchronizedArray = ( synchronizedBuffer != null ) ? synchronizedBuffer.array : null;
|
||||
int elementsBeforeWrap = Mathf.Min( Count, array.Length - startIndex );
|
||||
int removedElements = 0;
|
||||
int i = startIndex, newIndex = startIndex, endIndex = startIndex + elementsBeforeWrap;
|
||||
for( ; i < endIndex; i++ )
|
||||
{
|
||||
if( shouldRemoveElement( array[i] ) )
|
||||
removedElements++;
|
||||
else
|
||||
{
|
||||
if( removedElements > 0 )
|
||||
{
|
||||
T element = array[i];
|
||||
array[newIndex] = element;
|
||||
|
||||
if( synchronizedArray != null )
|
||||
synchronizedArray[newIndex] = synchronizedArray[i];
|
||||
|
||||
if( onElementIndexChanged != null )
|
||||
onElementIndexChanged( element, newIndex - startIndex );
|
||||
}
|
||||
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
endIndex = Count - elementsBeforeWrap;
|
||||
|
||||
if( newIndex < array.Length )
|
||||
{
|
||||
for( ; i < endIndex; i++ )
|
||||
{
|
||||
if( shouldRemoveElement( array[i] ) )
|
||||
removedElements++;
|
||||
else
|
||||
{
|
||||
T element = array[i];
|
||||
array[newIndex] = element;
|
||||
|
||||
if( synchronizedArray != null )
|
||||
synchronizedArray[newIndex] = synchronizedArray[i];
|
||||
|
||||
if( onElementIndexChanged != null )
|
||||
onElementIndexChanged( element, newIndex - startIndex );
|
||||
|
||||
if( ++newIndex == array.Length )
|
||||
{
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( newIndex == array.Length )
|
||||
{
|
||||
newIndex = 0;
|
||||
for( ; i < endIndex; i++ )
|
||||
{
|
||||
if( shouldRemoveElement( array[i] ) )
|
||||
removedElements++;
|
||||
else
|
||||
{
|
||||
if( removedElements > 0 )
|
||||
{
|
||||
T element = array[i];
|
||||
array[newIndex] = element;
|
||||
|
||||
if( synchronizedArray != null )
|
||||
synchronizedArray[newIndex] = synchronizedArray[i];
|
||||
|
||||
if( onElementIndexChanged != null )
|
||||
onElementIndexChanged( element, newIndex + elementsBeforeWrap );
|
||||
}
|
||||
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrimEnd( removedElements );
|
||||
if( synchronizedBuffer != null )
|
||||
synchronizedBuffer.TrimEnd( removedElements );
|
||||
|
||||
return removedElements;
|
||||
}
|
||||
|
||||
public void TrimStart( int trimCount, Action<T> perElementCallback = null )
|
||||
{
|
||||
TrimInternal( trimCount, startIndex, perElementCallback );
|
||||
startIndex = ( startIndex + trimCount ) % array.Length;
|
||||
}
|
||||
|
||||
public void TrimEnd( int trimCount, Action<T> perElementCallback = null )
|
||||
{
|
||||
TrimInternal( trimCount, ( startIndex + Count - trimCount ) % array.Length, perElementCallback );
|
||||
}
|
||||
|
||||
private void TrimInternal( int trimCount, int startIndex, Action<T> perElementCallback )
|
||||
{
|
||||
int elementsBeforeWrap = Mathf.Min( trimCount, array.Length - startIndex );
|
||||
if( perElementCallback == null )
|
||||
{
|
||||
Array.Clear( array, startIndex, elementsBeforeWrap );
|
||||
if( elementsBeforeWrap < trimCount )
|
||||
Array.Clear( array, 0, trimCount - elementsBeforeWrap );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int i = startIndex, endIndex = startIndex + elementsBeforeWrap; i < endIndex; i++ )
|
||||
{
|
||||
perElementCallback( array[i] );
|
||||
array[i] = default( T );
|
||||
}
|
||||
|
||||
for( int i = 0, endIndex = trimCount - elementsBeforeWrap; i < endIndex; i++ )
|
||||
{
|
||||
perElementCallback( array[i] );
|
||||
array[i] = default( T );
|
||||
}
|
||||
}
|
||||
|
||||
Count -= trimCount;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
int elementsBeforeWrap = Mathf.Min( Count, array.Length - startIndex );
|
||||
Array.Clear( array, startIndex, elementsBeforeWrap );
|
||||
if( elementsBeforeWrap < Count )
|
||||
Array.Clear( array, 0, Count - elementsBeforeWrap );
|
||||
|
||||
startIndex = 0;
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
public int IndexOf( T value )
|
||||
{
|
||||
int elementsBeforeWrap = Mathf.Min( Count, array.Length - startIndex );
|
||||
int index = Array.IndexOf( array, value, startIndex, elementsBeforeWrap );
|
||||
if( index >= 0 )
|
||||
return index - startIndex;
|
||||
|
||||
if( elementsBeforeWrap < Count )
|
||||
{
|
||||
index = Array.IndexOf( array, value, 0, Count - elementsBeforeWrap );
|
||||
if( index >= 0 )
|
||||
return index + elementsBeforeWrap;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void ForEach( Action<T> action )
|
||||
{
|
||||
int elementsBeforeWrap = Mathf.Min( Count, array.Length - startIndex );
|
||||
for( int i = startIndex, endIndex = startIndex + elementsBeforeWrap; i < endIndex; i++ )
|
||||
action( array[i] );
|
||||
for( int i = 0, endIndex = Count - elementsBeforeWrap; i < endIndex; i++ )
|
||||
action( array[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6136cb3c00eac0149901b8e7f2fecef8
|
||||
timeCreated: 1550943949
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class CopyLogsOnResizeButtonClick : MonoBehaviour, IPointerClickHandler
|
||||
{
|
||||
[SerializeField]
|
||||
private int maxLogCount = int.MaxValue;
|
||||
[SerializeField]
|
||||
private float maxElapsedTime = float.PositiveInfinity;
|
||||
|
||||
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (!eventData.dragging && eventData.eligibleForClick && DebugLogManager.Instance.copyAllLogsOnResizeButtonClick)
|
||||
{
|
||||
GUIUtility.systemCopyBuffer = DebugLogManager.Instance.GetAllLogs(maxLogCount, maxElapsedTime);
|
||||
StartCoroutine(ScaleAnimationCoroutine());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ScaleAnimationCoroutine()
|
||||
{
|
||||
for (float t = 0f; t < 1f; t += Time.unscaledDeltaTime * 3f)
|
||||
{
|
||||
transform.localScale = Vector3.one * (1f + Mathf.PingPong(t, 0.5f));
|
||||
yield return null;
|
||||
}
|
||||
|
||||
transform.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 298319a3c52d37442b63e30622b8c05d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1521
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogConsole.cs
Normal file
1521
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogConsole.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d15693a03d0d33b4892c6365a2a97e19
|
||||
timeCreated: 1472036503
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
187
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogEntry.cs
Normal file
187
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogEntry.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
// Container for a simple debug entry
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugLogEntry
|
||||
{
|
||||
private const int HASH_NOT_CALCULATED = -623218;
|
||||
|
||||
public string logString;
|
||||
public string stackTrace;
|
||||
private string completeLog;
|
||||
|
||||
// Sprite to show with this entry
|
||||
public LogType logType;
|
||||
|
||||
// Collapsed count
|
||||
public int count;
|
||||
|
||||
// Index of this entry among all collapsed entries
|
||||
public int collapsedIndex;
|
||||
|
||||
private int hashValue;
|
||||
|
||||
public void Initialize( string logString, string stackTrace )
|
||||
{
|
||||
this.logString = logString;
|
||||
this.stackTrace = stackTrace;
|
||||
|
||||
completeLog = null;
|
||||
count = 1;
|
||||
hashValue = HASH_NOT_CALCULATED;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
logString = null;
|
||||
stackTrace = null;
|
||||
completeLog = null;
|
||||
}
|
||||
|
||||
// Checks if logString or stackTrace contains the search term
|
||||
public bool MatchesSearchTerm( string searchTerm )
|
||||
{
|
||||
return ( logString != null && DebugLogConsole.caseInsensitiveComparer.IndexOf( logString, searchTerm, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) >= 0 ) ||
|
||||
( stackTrace != null && DebugLogConsole.caseInsensitiveComparer.IndexOf( stackTrace, searchTerm, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) >= 0 );
|
||||
}
|
||||
|
||||
// Return a string containing complete information about this debug entry
|
||||
public override string ToString()
|
||||
{
|
||||
if( completeLog == null )
|
||||
completeLog = string.Concat( logString, "\n", stackTrace );
|
||||
|
||||
return completeLog;
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/19250516/2373034
|
||||
public int GetContentHashCode()
|
||||
{
|
||||
if( hashValue == HASH_NOT_CALCULATED )
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
hashValue = 17;
|
||||
hashValue = hashValue * 23 + ( logString == null ? 0 : logString.GetHashCode() );
|
||||
hashValue = hashValue * 23 + ( stackTrace == null ? 0 : stackTrace.GetHashCode() );
|
||||
}
|
||||
}
|
||||
|
||||
return hashValue;
|
||||
}
|
||||
}
|
||||
|
||||
public struct QueuedDebugLogEntry
|
||||
{
|
||||
public readonly string logString;
|
||||
public readonly string stackTrace;
|
||||
public readonly LogType logType;
|
||||
|
||||
public QueuedDebugLogEntry( string logString, string stackTrace, LogType logType )
|
||||
{
|
||||
this.logString = logString;
|
||||
this.stackTrace = stackTrace;
|
||||
this.logType = logType;
|
||||
}
|
||||
|
||||
// Checks if logString or stackTrace contains the search term
|
||||
public bool MatchesSearchTerm( string searchTerm )
|
||||
{
|
||||
return ( logString != null && DebugLogConsole.caseInsensitiveComparer.IndexOf( logString, searchTerm, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) >= 0 ) ||
|
||||
( stackTrace != null && DebugLogConsole.caseInsensitiveComparer.IndexOf( stackTrace, searchTerm, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) >= 0 );
|
||||
}
|
||||
}
|
||||
|
||||
public struct DebugLogEntryTimestamp
|
||||
{
|
||||
public readonly System.DateTime dateTime;
|
||||
#if !IDG_OMIT_ELAPSED_TIME
|
||||
public readonly float elapsedSeconds;
|
||||
#endif
|
||||
#if !IDG_OMIT_FRAMECOUNT
|
||||
public readonly int frameCount;
|
||||
#endif
|
||||
|
||||
#if !IDG_OMIT_ELAPSED_TIME && !IDG_OMIT_FRAMECOUNT
|
||||
public DebugLogEntryTimestamp( System.DateTime dateTime, float elapsedSeconds, int frameCount )
|
||||
#elif !IDG_OMIT_ELAPSED_TIME
|
||||
public DebugLogEntryTimestamp( System.DateTime dateTime, float elapsedSeconds )
|
||||
#elif !IDG_OMIT_FRAMECOUNT
|
||||
public DebugLogEntryTimestamp( System.DateTime dateTime, int frameCount )
|
||||
#else
|
||||
public DebugLogEntryTimestamp( System.DateTime dateTime )
|
||||
#endif
|
||||
{
|
||||
this.dateTime = dateTime;
|
||||
#if !IDG_OMIT_ELAPSED_TIME
|
||||
this.elapsedSeconds = elapsedSeconds;
|
||||
#endif
|
||||
#if !IDG_OMIT_FRAMECOUNT
|
||||
this.frameCount = frameCount;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void AppendTime( StringBuilder sb )
|
||||
{
|
||||
// Add DateTime in format: [HH:mm:ss]
|
||||
sb.Append( "[" );
|
||||
|
||||
int hour = dateTime.Hour;
|
||||
if( hour >= 10 )
|
||||
sb.Append( hour );
|
||||
else
|
||||
sb.Append( "0" ).Append( hour );
|
||||
|
||||
sb.Append( ":" );
|
||||
|
||||
int minute = dateTime.Minute;
|
||||
if( minute >= 10 )
|
||||
sb.Append( minute );
|
||||
else
|
||||
sb.Append( "0" ).Append( minute );
|
||||
|
||||
sb.Append( ":" );
|
||||
|
||||
int second = dateTime.Second;
|
||||
if( second >= 10 )
|
||||
sb.Append( second );
|
||||
else
|
||||
sb.Append( "0" ).Append( second );
|
||||
|
||||
sb.Append( "]" );
|
||||
}
|
||||
|
||||
public void AppendFullTimestamp( StringBuilder sb )
|
||||
{
|
||||
AppendTime( sb );
|
||||
|
||||
#if !IDG_OMIT_ELAPSED_TIME && !IDG_OMIT_FRAMECOUNT
|
||||
// Append elapsed seconds and frame count in format: [1.0s at #Frame]
|
||||
sb.Append( "[" ).Append( elapsedSeconds.ToString( "F1" ) ).Append( "s at " ).Append( "#" ).Append( frameCount ).Append( "]" );
|
||||
#elif !IDG_OMIT_ELAPSED_TIME
|
||||
// Append elapsed seconds in format: [1.0s]
|
||||
sb.Append( "[" ).Append( elapsedSeconds.ToString( "F1" ) ).Append( "s]" );
|
||||
#elif !IDG_OMIT_FRAMECOUNT
|
||||
// Append frame count in format: [#Frame]
|
||||
sb.Append( "[#" ).Append( frameCount ).Append( "]" );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public class DebugLogEntryContentEqualityComparer : EqualityComparer<DebugLogEntry>
|
||||
{
|
||||
public override bool Equals( DebugLogEntry x, DebugLogEntry y )
|
||||
{
|
||||
return x.logString == y.logString && x.stackTrace == y.stackTrace;
|
||||
}
|
||||
|
||||
public override int GetHashCode( DebugLogEntry obj )
|
||||
{
|
||||
return obj.GetContentHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7b1a420c564be040bf73b8a377fc2c2
|
||||
timeCreated: 1466375168
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
260
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogItem.cs
Normal file
260
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogItem.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using System.Text.RegularExpressions;
|
||||
#endif
|
||||
|
||||
// A UI element to show information about a debug entry
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugLogItem : MonoBehaviour, IPointerClickHandler
|
||||
{
|
||||
#pragma warning disable 0649
|
||||
// Cached components
|
||||
[SerializeField]
|
||||
private RectTransform transformComponent;
|
||||
public RectTransform Transform { get { return transformComponent; } }
|
||||
|
||||
[SerializeField]
|
||||
private Image imageComponent;
|
||||
public Image Image { get { return imageComponent; } }
|
||||
|
||||
[SerializeField]
|
||||
private CanvasGroup canvasGroupComponent;
|
||||
public CanvasGroup CanvasGroup { get { return canvasGroupComponent; } }
|
||||
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI logText;
|
||||
[SerializeField]
|
||||
private Image logTypeImage;
|
||||
|
||||
// Objects related to the collapsed count of the debug entry
|
||||
[SerializeField]
|
||||
private GameObject logCountParent;
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI logCountText;
|
||||
|
||||
[SerializeField]
|
||||
private Button copyLogButton;
|
||||
#pragma warning restore 0649
|
||||
|
||||
// Debug entry to show with this log item
|
||||
private DebugLogEntry logEntry;
|
||||
public DebugLogEntry Entry { get { return logEntry; } }
|
||||
|
||||
private DebugLogEntryTimestamp? logEntryTimestamp;
|
||||
public DebugLogEntryTimestamp? Timestamp { get { return logEntryTimestamp; } }
|
||||
|
||||
// Index of the entry in the list of entries
|
||||
[System.NonSerialized] public int Index;
|
||||
|
||||
private bool isExpanded;
|
||||
public bool Expanded { get { return isExpanded; } }
|
||||
|
||||
private Vector2 logTextOriginalPosition;
|
||||
private Vector2 logTextOriginalSize;
|
||||
private float copyLogButtonHeight;
|
||||
|
||||
private DebugLogRecycledListView listView;
|
||||
|
||||
public void Initialize( DebugLogRecycledListView listView )
|
||||
{
|
||||
this.listView = listView;
|
||||
|
||||
logTextOriginalPosition = logText.rectTransform.anchoredPosition;
|
||||
logTextOriginalSize = logText.rectTransform.sizeDelta;
|
||||
copyLogButtonHeight = ( copyLogButton.transform as RectTransform ).anchoredPosition.y + ( copyLogButton.transform as RectTransform ).sizeDelta.y + 2f; // 2f: space between text and button
|
||||
|
||||
if (listView.manager.logItemFontOverride != null)
|
||||
logText.font = listView.manager.logItemFontOverride;
|
||||
|
||||
copyLogButton.onClick.AddListener( CopyLog );
|
||||
#if !UNITY_EDITOR && UNITY_WEBGL
|
||||
copyLogButton.gameObject.AddComponent<DebugLogItemCopyWebGL>().Initialize( this );
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SetContent( DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp, int entryIndex, bool isExpanded )
|
||||
{
|
||||
this.logEntry = logEntry;
|
||||
this.logEntryTimestamp = logEntryTimestamp;
|
||||
this.Index = entryIndex;
|
||||
this.isExpanded = isExpanded;
|
||||
|
||||
Vector2 size = transformComponent.sizeDelta;
|
||||
if( isExpanded )
|
||||
{
|
||||
size.y = listView.SelectedItemHeight;
|
||||
|
||||
if( !copyLogButton.gameObject.activeSelf )
|
||||
{
|
||||
copyLogButton.gameObject.SetActive( true );
|
||||
|
||||
logText.rectTransform.anchoredPosition = new Vector2( logTextOriginalPosition.x, logTextOriginalPosition.y + copyLogButtonHeight * 0.5f );
|
||||
logText.rectTransform.sizeDelta = logTextOriginalSize - new Vector2( 0f, copyLogButtonHeight );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size.y = listView.ItemHeight;
|
||||
|
||||
if( copyLogButton.gameObject.activeSelf )
|
||||
{
|
||||
copyLogButton.gameObject.SetActive( false );
|
||||
|
||||
logText.rectTransform.anchoredPosition = logTextOriginalPosition;
|
||||
logText.rectTransform.sizeDelta = logTextOriginalSize;
|
||||
}
|
||||
}
|
||||
|
||||
transformComponent.sizeDelta = size;
|
||||
|
||||
SetText( logEntry, logEntryTimestamp, isExpanded );
|
||||
logTypeImage.sprite = DebugLogManager.logSpriteRepresentations[(int) logEntry.logType];
|
||||
}
|
||||
|
||||
// Show the collapsed count of the debug entry
|
||||
public void ShowCount()
|
||||
{
|
||||
logCountText.SetText( "{0}", logEntry.count );
|
||||
|
||||
if( !logCountParent.activeSelf )
|
||||
logCountParent.SetActive( true );
|
||||
}
|
||||
|
||||
// Hide the collapsed count of the debug entry
|
||||
public void HideCount()
|
||||
{
|
||||
if( logCountParent.activeSelf )
|
||||
logCountParent.SetActive( false );
|
||||
}
|
||||
|
||||
// Update the debug entry's displayed timestamp
|
||||
public void UpdateTimestamp( DebugLogEntryTimestamp timestamp )
|
||||
{
|
||||
logEntryTimestamp = timestamp;
|
||||
|
||||
if( isExpanded || listView.manager.alwaysDisplayTimestamps )
|
||||
SetText( logEntry, timestamp, isExpanded );
|
||||
}
|
||||
|
||||
private void SetText(DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp, bool isExpanded)
|
||||
{
|
||||
string text = isExpanded ? logEntry.ToString() : logEntry.logString;
|
||||
int maxLogLength = isExpanded ? listView.manager.maxExpandedLogLength : listView.manager.maxCollapsedLogLength;
|
||||
|
||||
if (!logEntryTimestamp.HasValue || (!isExpanded && !listView.manager.alwaysDisplayTimestamps))
|
||||
{
|
||||
if (text.Length <= maxLogLength)
|
||||
logText.text = text;
|
||||
else
|
||||
{
|
||||
if (listView.manager.textBuffer.Length < maxLogLength)
|
||||
listView.manager.textBuffer = new char[maxLogLength];
|
||||
|
||||
text.CopyTo(0, listView.manager.textBuffer, 0, maxLogLength);
|
||||
logText.SetText(listView.manager.textBuffer, 0, maxLogLength);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder sb = listView.manager.sharedStringBuilder;
|
||||
sb.Length = 0;
|
||||
|
||||
if (isExpanded)
|
||||
{
|
||||
logEntryTimestamp.Value.AppendFullTimestamp(sb);
|
||||
sb.Append(": ").Append(text, 0, Mathf.Min(text.Length, maxLogLength - sb.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
logEntryTimestamp.Value.AppendTime(sb);
|
||||
sb.Append(" ").Append(text, 0, Mathf.Min(text.Length, maxLogLength - sb.Length));
|
||||
}
|
||||
|
||||
if (listView.manager.textBuffer.Length < sb.Length)
|
||||
listView.manager.textBuffer = new char[sb.Length];
|
||||
|
||||
sb.CopyTo(0, listView.manager.textBuffer, 0, sb.Length);
|
||||
logText.SetText(listView.manager.textBuffer, 0, sb.Length);
|
||||
}
|
||||
}
|
||||
|
||||
// This log item is clicked, show the debug entry's stack trace
|
||||
public void OnPointerClick( PointerEventData eventData )
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if( eventData.button == PointerEventData.InputButton.Right )
|
||||
{
|
||||
Match regex = Regex.Match( logEntry.stackTrace, @"\(at .*\.cs:[0-9]+\)$", RegexOptions.Multiline );
|
||||
if( regex.Success )
|
||||
{
|
||||
string line = logEntry.stackTrace.Substring( regex.Index + 4, regex.Length - 5 );
|
||||
int lineSeparator = line.IndexOf( ':' );
|
||||
MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>( line.Substring( 0, lineSeparator ) );
|
||||
if( script != null )
|
||||
AssetDatabase.OpenAsset( script, int.Parse( line.Substring( lineSeparator + 1 ) ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
listView.OnLogItemClicked( this );
|
||||
#else
|
||||
listView.OnLogItemClicked( this );
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CopyLog()
|
||||
{
|
||||
#if UNITY_EDITOR || !UNITY_WEBGL
|
||||
string log = GetCopyContent();
|
||||
if( !string.IsNullOrEmpty( log ) )
|
||||
GUIUtility.systemCopyBuffer = log;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal string GetCopyContent()
|
||||
{
|
||||
if( !logEntryTimestamp.HasValue )
|
||||
return logEntry.ToString();
|
||||
else
|
||||
{
|
||||
StringBuilder sb = listView.manager.sharedStringBuilder;
|
||||
sb.Length = 0;
|
||||
|
||||
logEntryTimestamp.Value.AppendFullTimestamp( sb );
|
||||
sb.Append( ": " ).Append( logEntry.ToString() );
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// Here, we're using <see cref="TMP_Text.GetRenderedValues(bool)"/> instead of <see cref="TMP_Text.preferredHeight"/> because the latter doesn't take
|
||||
/// <see cref="TMP_Text.maxVisibleCharacters"/> into account. However, for <see cref="TMP_Text.GetRenderedValues(bool)"/> to work, we need to give it
|
||||
/// enough space (increase log item's height) and let it regenerate its mesh <see cref="TMP_Text.ForceMeshUpdate"/>.
|
||||
public float CalculateExpandedHeight( DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp )
|
||||
{
|
||||
string text = logText.text;
|
||||
Vector2 size = ( transform as RectTransform ).sizeDelta;
|
||||
|
||||
( transform as RectTransform ).sizeDelta = new Vector2( size.x, 10000f );
|
||||
SetText( logEntry, logEntryTimestamp, true );
|
||||
logText.ForceMeshUpdate();
|
||||
float result = logText.GetRenderedValues( true ).y + copyLogButtonHeight;
|
||||
|
||||
( transform as RectTransform ).sizeDelta = size;
|
||||
logText.text = text;
|
||||
|
||||
return Mathf.Max( listView.ItemHeight, result );
|
||||
}
|
||||
|
||||
// Return a string containing complete information about the debug entry
|
||||
public override string ToString()
|
||||
{
|
||||
return logEntry.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2ea291be9de70a4abfec595203c96c1
|
||||
timeCreated: 1465919949
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
#if !UNITY_EDITOR && UNITY_WEBGL
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugLogItemCopyWebGL : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
||||
{
|
||||
[DllImport( "__Internal" )]
|
||||
private static extern void IngameDebugConsoleStartCopy( string textToCopy );
|
||||
[DllImport( "__Internal" )]
|
||||
private static extern void IngameDebugConsoleCancelCopy();
|
||||
|
||||
private DebugLogItem logItem;
|
||||
|
||||
public void Initialize( DebugLogItem logItem )
|
||||
{
|
||||
this.logItem = logItem;
|
||||
}
|
||||
|
||||
public void OnPointerDown( PointerEventData eventData )
|
||||
{
|
||||
string log = logItem.GetCopyContent();
|
||||
if( !string.IsNullOrEmpty( log ) )
|
||||
IngameDebugConsoleStartCopy( log );
|
||||
}
|
||||
|
||||
public void OnPointerUp( PointerEventData eventData )
|
||||
{
|
||||
if( eventData.dragging )
|
||||
IngameDebugConsoleCancelCopy();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a7d9d894141e704d8160fb4632121ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1877
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogManager.cs
Normal file
1877
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogManager.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a4f16ed905adcd4ab0d7c8c11f0d72c
|
||||
timeCreated: 1522092746
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -9869
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
282
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogPopup.cs
Normal file
282
Assets/Plugins/IngameDebugConsole/Scripts/DebugLogPopup.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
#if UNITY_EDITOR && UNITY_2021_1_OR_NEWER
|
||||
using Screen = UnityEngine.Device.Screen; // To support Device Simulator on Unity 2021.1+
|
||||
#endif
|
||||
|
||||
// Manager class for the debug popup
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugLogPopup : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
private RectTransform popupTransform;
|
||||
|
||||
// Dimensions of the popup divided by 2
|
||||
private Vector2 halfSize;
|
||||
|
||||
// Background image that will change color to indicate an alert
|
||||
private Image backgroundImage;
|
||||
|
||||
// Canvas group to modify visibility of the popup
|
||||
private CanvasGroup canvasGroup;
|
||||
|
||||
#pragma warning disable 0649
|
||||
[SerializeField]
|
||||
private DebugLogManager debugManager;
|
||||
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI newInfoCountText;
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI newWarningCountText;
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI newErrorCountText;
|
||||
|
||||
[SerializeField]
|
||||
private Color alertColorInfo;
|
||||
[SerializeField]
|
||||
private Color alertColorWarning;
|
||||
[SerializeField]
|
||||
private Color alertColorError;
|
||||
#pragma warning restore 0649
|
||||
|
||||
// Number of new debug entries since the log window has been closed
|
||||
private int newInfoCount = 0, newWarningCount = 0, newErrorCount = 0;
|
||||
|
||||
private Color normalColor;
|
||||
|
||||
private bool isPopupBeingDragged = false;
|
||||
private Vector2 normalizedPosition;
|
||||
|
||||
// Coroutines for simple code-based animations
|
||||
private IEnumerator moveToPosCoroutine = null;
|
||||
|
||||
public bool IsVisible { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
popupTransform = (RectTransform) transform;
|
||||
backgroundImage = GetComponent<Image>();
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
|
||||
normalColor = backgroundImage.color;
|
||||
|
||||
halfSize = popupTransform.sizeDelta * 0.5f;
|
||||
|
||||
Vector2 pos = popupTransform.anchoredPosition;
|
||||
if( pos.x != 0f || pos.y != 0f )
|
||||
normalizedPosition = pos.normalized; // Respect the initial popup position set in the prefab
|
||||
else
|
||||
normalizedPosition = new Vector2( 0.5f, 0f ); // Right edge by default
|
||||
}
|
||||
|
||||
public void NewLogsArrived( int newInfo, int newWarning, int newError )
|
||||
{
|
||||
if( newInfo > 0 )
|
||||
{
|
||||
newInfoCount += newInfo;
|
||||
newInfoCountText.text = newInfoCount.ToString();
|
||||
}
|
||||
|
||||
if( newWarning > 0 )
|
||||
{
|
||||
newWarningCount += newWarning;
|
||||
newWarningCountText.text = newWarningCount.ToString();
|
||||
}
|
||||
|
||||
if( newError > 0 )
|
||||
{
|
||||
newErrorCount += newError;
|
||||
newErrorCountText.text = newErrorCount.ToString();
|
||||
}
|
||||
|
||||
if( newErrorCount > 0 )
|
||||
backgroundImage.color = alertColorError;
|
||||
else if( newWarningCount > 0 )
|
||||
backgroundImage.color = alertColorWarning;
|
||||
else
|
||||
backgroundImage.color = alertColorInfo;
|
||||
}
|
||||
|
||||
private void ResetValues()
|
||||
{
|
||||
newInfoCount = 0;
|
||||
newWarningCount = 0;
|
||||
newErrorCount = 0;
|
||||
|
||||
newInfoCountText.text = "0";
|
||||
newWarningCountText.text = "0";
|
||||
newErrorCountText.text = "0";
|
||||
|
||||
backgroundImage.color = normalColor;
|
||||
}
|
||||
|
||||
// A simple smooth movement animation
|
||||
private IEnumerator MoveToPosAnimation( Vector2 targetPos )
|
||||
{
|
||||
float modifier = 0f;
|
||||
Vector2 initialPos = popupTransform.anchoredPosition;
|
||||
|
||||
while( modifier < 1f )
|
||||
{
|
||||
modifier += 4f * Time.unscaledDeltaTime;
|
||||
popupTransform.anchoredPosition = Vector2.Lerp( initialPos, targetPos, modifier );
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Popup is clicked
|
||||
public void OnPointerClick( PointerEventData data )
|
||||
{
|
||||
// Hide the popup and show the log window
|
||||
if( !isPopupBeingDragged )
|
||||
debugManager.ShowLogWindow();
|
||||
}
|
||||
|
||||
// Hides the log window and shows the popup
|
||||
public void Show()
|
||||
{
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
canvasGroup.alpha = debugManager.popupOpacity;
|
||||
IsVisible = true;
|
||||
|
||||
// Reset the counters
|
||||
ResetValues();
|
||||
|
||||
// Update position in case resolution was changed while the popup was hidden
|
||||
UpdatePosition( true );
|
||||
}
|
||||
|
||||
// Hide the popup
|
||||
public void Hide()
|
||||
{
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
canvasGroup.alpha = 0f;
|
||||
|
||||
IsVisible = false;
|
||||
isPopupBeingDragged = false;
|
||||
}
|
||||
|
||||
public void OnBeginDrag( PointerEventData data )
|
||||
{
|
||||
isPopupBeingDragged = true;
|
||||
|
||||
// If a smooth movement animation is in progress, cancel it
|
||||
if( moveToPosCoroutine != null )
|
||||
{
|
||||
StopCoroutine( moveToPosCoroutine );
|
||||
moveToPosCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Reposition the popup
|
||||
public void OnDrag( PointerEventData data )
|
||||
{
|
||||
Vector2 localPoint;
|
||||
if( RectTransformUtility.ScreenPointToLocalPointInRectangle( debugManager.canvasTR, data.position, data.pressEventCamera, out localPoint ) )
|
||||
popupTransform.anchoredPosition = localPoint;
|
||||
}
|
||||
|
||||
// Smoothly translate the popup to the nearest edge
|
||||
public void OnEndDrag( PointerEventData data )
|
||||
{
|
||||
isPopupBeingDragged = false;
|
||||
UpdatePosition( false );
|
||||
}
|
||||
|
||||
// There are 2 different spaces used in these calculations:
|
||||
// RectTransform space: raw anchoredPosition of the popup that's in range [-canvasSize/2, canvasSize/2]
|
||||
// Safe area space: Screen.safeArea space that's in range [safeAreaBottomLeft, safeAreaTopRight] where these corner positions
|
||||
// are all positive (calculated from bottom left corner of the screen instead of the center of the screen)
|
||||
public void UpdatePosition( bool immediately )
|
||||
{
|
||||
Vector2 canvasRawSize = debugManager.canvasTR.rect.size;
|
||||
|
||||
// Calculate safe area bounds
|
||||
float canvasWidth = canvasRawSize.x;
|
||||
float canvasHeight = canvasRawSize.y;
|
||||
|
||||
float canvasBottomLeftX = 0f;
|
||||
float canvasBottomLeftY = 0f;
|
||||
|
||||
if( debugManager.popupAvoidsScreenCutout )
|
||||
{
|
||||
#if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS
|
||||
Rect safeArea = Screen.safeArea;
|
||||
|
||||
int screenWidth = Screen.width;
|
||||
int screenHeight = Screen.height;
|
||||
|
||||
canvasWidth *= safeArea.width / screenWidth;
|
||||
canvasHeight *= safeArea.height / screenHeight;
|
||||
|
||||
canvasBottomLeftX = canvasRawSize.x * ( safeArea.x / screenWidth );
|
||||
canvasBottomLeftY = canvasRawSize.y * ( safeArea.y / screenHeight );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Calculate safe area position of the popup
|
||||
// normalizedPosition allows us to glue the popup to a specific edge of the screen. It becomes useful when
|
||||
// the popup is at the right edge and we switch from portrait screen orientation to landscape screen orientation.
|
||||
// Without normalizedPosition, popup could jump to bottom or top edges instead of staying at the right edge
|
||||
Vector2 pos = canvasRawSize * 0.5f + ( immediately ? new Vector2( normalizedPosition.x * canvasWidth, normalizedPosition.y * canvasHeight ) : ( popupTransform.anchoredPosition - new Vector2( canvasBottomLeftX, canvasBottomLeftY ) ) );
|
||||
|
||||
// Find distances to all four edges of the safe area
|
||||
float distToLeft = pos.x;
|
||||
float distToRight = canvasWidth - distToLeft;
|
||||
|
||||
float distToBottom = pos.y;
|
||||
float distToTop = canvasHeight - distToBottom;
|
||||
|
||||
float horDistance = Mathf.Min( distToLeft, distToRight );
|
||||
float vertDistance = Mathf.Min( distToBottom, distToTop );
|
||||
|
||||
// Find the nearest edge's safe area coordinates
|
||||
if( horDistance < vertDistance )
|
||||
{
|
||||
if( distToLeft < distToRight )
|
||||
pos = new Vector2( halfSize.x, pos.y );
|
||||
else
|
||||
pos = new Vector2( canvasWidth - halfSize.x, pos.y );
|
||||
|
||||
pos.y = Mathf.Clamp( pos.y, halfSize.y, canvasHeight - halfSize.y );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( distToBottom < distToTop )
|
||||
pos = new Vector2( pos.x, halfSize.y );
|
||||
else
|
||||
pos = new Vector2( pos.x, canvasHeight - halfSize.y );
|
||||
|
||||
pos.x = Mathf.Clamp( pos.x, halfSize.x, canvasWidth - halfSize.x );
|
||||
}
|
||||
|
||||
pos -= canvasRawSize * 0.5f;
|
||||
|
||||
normalizedPosition.Set( pos.x / canvasWidth, pos.y / canvasHeight );
|
||||
|
||||
// Safe area's bottom left coordinates are added to pos only after normalizedPosition's value
|
||||
// is set because normalizedPosition is in range [-canvasWidth / 2, canvasWidth / 2]
|
||||
pos += new Vector2( canvasBottomLeftX, canvasBottomLeftY );
|
||||
|
||||
// If another smooth movement animation is in progress, cancel it
|
||||
if( moveToPosCoroutine != null )
|
||||
{
|
||||
StopCoroutine( moveToPosCoroutine );
|
||||
moveToPosCoroutine = null;
|
||||
}
|
||||
|
||||
if( immediately )
|
||||
popupTransform.anchoredPosition = pos;
|
||||
else
|
||||
{
|
||||
// Smoothly translate the popup to the specified position
|
||||
moveToPosCoroutine = MoveToPosAnimation( pos );
|
||||
StartCoroutine( moveToPosCoroutine );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05cc4b1999716644c9308528e38e7081
|
||||
timeCreated: 1466533184
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,485 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
// Handles the log items in an optimized way such that existing log items are
|
||||
// recycled within the list instead of creating a new log item at each chance
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugLogRecycledListView : MonoBehaviour
|
||||
{
|
||||
#pragma warning disable 0649
|
||||
// Cached components
|
||||
[SerializeField]
|
||||
private RectTransform transformComponent;
|
||||
[SerializeField]
|
||||
private RectTransform viewportTransform;
|
||||
|
||||
[SerializeField]
|
||||
private Color logItemNormalColor1;
|
||||
[SerializeField]
|
||||
private Color logItemNormalColor2;
|
||||
[SerializeField]
|
||||
private Color logItemSelectedColor;
|
||||
#pragma warning restore 0649
|
||||
|
||||
internal DebugLogManager manager;
|
||||
private ScrollRect scrollView;
|
||||
|
||||
private float logItemHeight;
|
||||
|
||||
private DynamicCircularBuffer<DebugLogEntry> entriesToShow = null;
|
||||
private DynamicCircularBuffer<DebugLogEntryTimestamp> timestampsOfEntriesToShow = null;
|
||||
|
||||
private DebugLogEntry selectedLogEntry;
|
||||
private int indexOfSelectedLogEntry = int.MaxValue;
|
||||
private float heightOfSelectedLogEntry;
|
||||
private float DeltaHeightOfSelectedLogEntry { get { return heightOfSelectedLogEntry - logItemHeight; } }
|
||||
|
||||
/// These properties are used by <see cref="OnBeforeFilterLogs"/> and <see cref="OnAfterFilterLogs"/>.
|
||||
private int collapsedOrderOfSelectedLogEntry;
|
||||
private float scrollDistanceToSelectedLogEntry;
|
||||
|
||||
// Log items used to visualize the visible debug entries
|
||||
private readonly DynamicCircularBuffer<DebugLogItem> visibleLogItems = new DynamicCircularBuffer<DebugLogItem>( 32 );
|
||||
|
||||
private bool isCollapseOn = false;
|
||||
|
||||
// Current indices of debug entries shown on screen
|
||||
private int currentTopIndex = -1, currentBottomIndex = -1;
|
||||
|
||||
private System.Predicate<DebugLogItem> shouldRemoveLogItemPredicate;
|
||||
private System.Action<DebugLogItem> poolLogItemAction;
|
||||
|
||||
public float ItemHeight { get { return logItemHeight; } }
|
||||
public float SelectedItemHeight { get { return heightOfSelectedLogEntry; } }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
scrollView = viewportTransform.GetComponentInParent<ScrollRect>();
|
||||
scrollView.onValueChanged.AddListener( ( pos ) =>
|
||||
{
|
||||
if( manager.IsLogWindowVisible )
|
||||
UpdateItemsInTheList( false );
|
||||
} );
|
||||
}
|
||||
|
||||
public void Initialize( DebugLogManager manager, DynamicCircularBuffer<DebugLogEntry> entriesToShow, DynamicCircularBuffer<DebugLogEntryTimestamp> timestampsOfEntriesToShow, float logItemHeight )
|
||||
{
|
||||
this.manager = manager;
|
||||
this.entriesToShow = entriesToShow;
|
||||
this.timestampsOfEntriesToShow = timestampsOfEntriesToShow;
|
||||
this.logItemHeight = logItemHeight;
|
||||
|
||||
shouldRemoveLogItemPredicate = ShouldRemoveLogItem;
|
||||
poolLogItemAction = manager.PoolLogItem;
|
||||
}
|
||||
|
||||
public void SetCollapseMode( bool collapse )
|
||||
{
|
||||
isCollapseOn = collapse;
|
||||
}
|
||||
|
||||
// A log item is clicked, highlight it
|
||||
public void OnLogItemClicked( DebugLogItem item )
|
||||
{
|
||||
OnLogItemClickedInternal( item.Index, item );
|
||||
}
|
||||
|
||||
// Force expand the log item at specified index
|
||||
public void SelectAndFocusOnLogItemAtIndex( int itemIndex )
|
||||
{
|
||||
if( indexOfSelectedLogEntry != itemIndex ) // Make sure that we aren't deselecting the target log item
|
||||
OnLogItemClickedInternal( itemIndex );
|
||||
|
||||
float viewportHeight = viewportTransform.rect.height;
|
||||
float transformComponentCenterYAtTop = viewportHeight * 0.5f;
|
||||
float transformComponentCenterYAtBottom = transformComponent.sizeDelta.y - viewportHeight * 0.5f;
|
||||
float transformComponentTargetCenterY = itemIndex * logItemHeight + viewportHeight * 0.5f;
|
||||
if( transformComponentCenterYAtTop == transformComponentCenterYAtBottom )
|
||||
scrollView.verticalNormalizedPosition = 0.5f;
|
||||
else
|
||||
scrollView.verticalNormalizedPosition = Mathf.Clamp01( Mathf.InverseLerp( transformComponentCenterYAtBottom, transformComponentCenterYAtTop, transformComponentTargetCenterY ) );
|
||||
|
||||
manager.SnapToBottom = false;
|
||||
}
|
||||
|
||||
private void OnLogItemClickedInternal( int itemIndex, DebugLogItem referenceItem = null )
|
||||
{
|
||||
int indexOfPreviouslySelectedLogEntry = indexOfSelectedLogEntry;
|
||||
DeselectSelectedLogItem();
|
||||
|
||||
if( indexOfPreviouslySelectedLogEntry != itemIndex )
|
||||
{
|
||||
selectedLogEntry = entriesToShow[itemIndex];
|
||||
indexOfSelectedLogEntry = itemIndex;
|
||||
CalculateSelectedLogEntryHeight( referenceItem );
|
||||
|
||||
manager.SnapToBottom = false;
|
||||
}
|
||||
|
||||
CalculateContentHeight();
|
||||
UpdateItemsInTheList( true );
|
||||
|
||||
manager.ValidateScrollPosition();
|
||||
}
|
||||
|
||||
// Deselect the currently selected log item
|
||||
public void DeselectSelectedLogItem()
|
||||
{
|
||||
selectedLogEntry = null;
|
||||
indexOfSelectedLogEntry = int.MaxValue;
|
||||
heightOfSelectedLogEntry = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache the currently selected log item's properties so that its position can be restored after <see cref="OnAfterFilterLogs"/> is called.
|
||||
/// </summary>
|
||||
public void OnBeforeFilterLogs()
|
||||
{
|
||||
collapsedOrderOfSelectedLogEntry = 0;
|
||||
scrollDistanceToSelectedLogEntry = 0f;
|
||||
|
||||
if( selectedLogEntry != null )
|
||||
{
|
||||
if( !isCollapseOn )
|
||||
{
|
||||
for( int i = 0; i < indexOfSelectedLogEntry; i++ )
|
||||
{
|
||||
if( entriesToShow[i] == selectedLogEntry )
|
||||
collapsedOrderOfSelectedLogEntry++;
|
||||
}
|
||||
}
|
||||
|
||||
scrollDistanceToSelectedLogEntry = indexOfSelectedLogEntry * ItemHeight - transformComponent.anchoredPosition.y;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="OnBeforeFilterLogs"/>.
|
||||
/// </summary>
|
||||
public void OnAfterFilterLogs()
|
||||
{
|
||||
// Refresh selected log entry's index
|
||||
int newIndexOfSelectedLogEntry = -1;
|
||||
if( selectedLogEntry != null )
|
||||
{
|
||||
for( int i = 0; i < entriesToShow.Count; i++ )
|
||||
{
|
||||
if( entriesToShow[i] == selectedLogEntry && collapsedOrderOfSelectedLogEntry-- == 0 )
|
||||
{
|
||||
newIndexOfSelectedLogEntry = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( newIndexOfSelectedLogEntry < 0 )
|
||||
DeselectSelectedLogItem();
|
||||
else
|
||||
{
|
||||
indexOfSelectedLogEntry = newIndexOfSelectedLogEntry;
|
||||
transformComponent.anchoredPosition = new Vector2( 0f, newIndexOfSelectedLogEntry * ItemHeight - scrollDistanceToSelectedLogEntry );
|
||||
}
|
||||
}
|
||||
|
||||
// Number of debug entries may have changed, update the list
|
||||
public void OnLogEntriesUpdated( bool updateAllVisibleItemContents )
|
||||
{
|
||||
CalculateContentHeight();
|
||||
UpdateItemsInTheList( updateAllVisibleItemContents );
|
||||
}
|
||||
|
||||
// A single collapsed log entry at specified index is updated, refresh its item if visible
|
||||
public void OnCollapsedLogEntryAtIndexUpdated( int index )
|
||||
{
|
||||
if( index >= currentTopIndex && index <= currentBottomIndex )
|
||||
{
|
||||
DebugLogItem logItem = GetLogItemAtIndex( index );
|
||||
logItem.ShowCount();
|
||||
|
||||
if( timestampsOfEntriesToShow != null )
|
||||
logItem.UpdateTimestamp( timestampsOfEntriesToShow[index] );
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshCollapsedLogEntryCounts()
|
||||
{
|
||||
for( int i = 0; i < visibleLogItems.Count; i++ )
|
||||
visibleLogItems[i].ShowCount();
|
||||
}
|
||||
|
||||
public void OnLogEntriesRemoved( int removedLogCount )
|
||||
{
|
||||
if( selectedLogEntry != null )
|
||||
{
|
||||
bool isSelectedLogEntryRemoved = isCollapseOn ? ( selectedLogEntry.count == 0 ) : ( indexOfSelectedLogEntry < removedLogCount );
|
||||
if( isSelectedLogEntryRemoved )
|
||||
DeselectSelectedLogItem();
|
||||
else
|
||||
indexOfSelectedLogEntry = isCollapseOn ? FindIndexOfLogEntryInReverseDirection( selectedLogEntry, indexOfSelectedLogEntry ) : ( indexOfSelectedLogEntry - removedLogCount );
|
||||
}
|
||||
|
||||
if( !manager.IsLogWindowVisible && manager.SnapToBottom )
|
||||
{
|
||||
// When log window becomes visible, it refreshes all logs. So unless snap to bottom is disabled, we don't need to
|
||||
// keep track of either the scroll position or the visible log items' positions.
|
||||
visibleLogItems.TrimStart( visibleLogItems.Count, poolLogItemAction );
|
||||
}
|
||||
else if( !isCollapseOn )
|
||||
visibleLogItems.TrimStart( Mathf.Clamp( removedLogCount - currentTopIndex, 0, visibleLogItems.Count ), poolLogItemAction );
|
||||
else
|
||||
{
|
||||
visibleLogItems.RemoveAll( shouldRemoveLogItemPredicate );
|
||||
if( visibleLogItems.Count > 0 )
|
||||
removedLogCount = currentTopIndex - FindIndexOfLogEntryInReverseDirection( visibleLogItems[0].Entry, visibleLogItems[0].Index );
|
||||
}
|
||||
|
||||
if( visibleLogItems.Count == 0 )
|
||||
{
|
||||
currentTopIndex = -1;
|
||||
|
||||
if( !manager.SnapToBottom )
|
||||
transformComponent.anchoredPosition = Vector2.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentTopIndex = Mathf.Max( 0, currentTopIndex - removedLogCount );
|
||||
currentBottomIndex = currentTopIndex + visibleLogItems.Count - 1;
|
||||
|
||||
float firstVisibleLogItemInitialYPos = visibleLogItems[0].Transform.anchoredPosition.y;
|
||||
for( int i = 0; i < visibleLogItems.Count; i++ )
|
||||
{
|
||||
DebugLogItem logItem = visibleLogItems[i];
|
||||
logItem.Index = currentTopIndex + i;
|
||||
|
||||
// If log window is visible, we need to manually refresh the visible items' visual properties. Otherwise, all log items will be refreshed when log window is opened
|
||||
if( manager.IsLogWindowVisible )
|
||||
{
|
||||
RepositionLogItem( logItem );
|
||||
ColorLogItem( logItem );
|
||||
|
||||
// Update collapsed count of the log items in collapsed mode
|
||||
if( isCollapseOn )
|
||||
logItem.ShowCount();
|
||||
}
|
||||
}
|
||||
|
||||
// Shift the ScrollRect
|
||||
if( !manager.SnapToBottom )
|
||||
transformComponent.anchoredPosition = new Vector2( 0f, Mathf.Max( 0f, transformComponent.anchoredPosition.y - ( visibleLogItems[0].Transform.anchoredPosition.y - firstVisibleLogItemInitialYPos ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldRemoveLogItem( DebugLogItem logItem )
|
||||
{
|
||||
if( logItem.Entry.count == 0 )
|
||||
{
|
||||
poolLogItemAction( logItem );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int FindIndexOfLogEntryInReverseDirection( DebugLogEntry logEntry, int startIndex )
|
||||
{
|
||||
for( int i = Mathf.Min( startIndex, entriesToShow.Count - 1 ); i >= 0; i-- )
|
||||
{
|
||||
if( entriesToShow[i] == logEntry )
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Log window's width has changed, update the expanded (currently selected) log's height
|
||||
public void OnViewportWidthChanged()
|
||||
{
|
||||
if( indexOfSelectedLogEntry >= entriesToShow.Count )
|
||||
return;
|
||||
|
||||
CalculateSelectedLogEntryHeight();
|
||||
CalculateContentHeight();
|
||||
UpdateItemsInTheList( true );
|
||||
|
||||
manager.ValidateScrollPosition();
|
||||
}
|
||||
|
||||
// Log window's height has changed, update the list
|
||||
public void OnViewportHeightChanged()
|
||||
{
|
||||
UpdateItemsInTheList( false );
|
||||
}
|
||||
|
||||
private void CalculateContentHeight()
|
||||
{
|
||||
float newHeight = Mathf.Max( 1f, entriesToShow.Count * logItemHeight );
|
||||
if( selectedLogEntry != null )
|
||||
newHeight += DeltaHeightOfSelectedLogEntry;
|
||||
|
||||
transformComponent.sizeDelta = new Vector2( 0f, newHeight );
|
||||
}
|
||||
|
||||
private void CalculateSelectedLogEntryHeight( DebugLogItem referenceItem = null )
|
||||
{
|
||||
if( !referenceItem )
|
||||
{
|
||||
if( visibleLogItems.Count == 0 )
|
||||
{
|
||||
UpdateItemsInTheList( false ); // Try to generate some DebugLogItems, we need one DebugLogItem to calculate the text height
|
||||
if( visibleLogItems.Count == 0 ) // No DebugLogItems are generated, weird
|
||||
return;
|
||||
}
|
||||
|
||||
referenceItem = visibleLogItems[0];
|
||||
}
|
||||
|
||||
heightOfSelectedLogEntry = referenceItem.CalculateExpandedHeight( selectedLogEntry, ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[indexOfSelectedLogEntry] : (DebugLogEntryTimestamp?) null );
|
||||
}
|
||||
|
||||
// Calculate the indices of log entries to show
|
||||
// and handle log items accordingly
|
||||
private void UpdateItemsInTheList( bool updateAllVisibleItemContents )
|
||||
{
|
||||
if( entriesToShow.Count > 0 )
|
||||
{
|
||||
float contentPosTop = transformComponent.anchoredPosition.y - 1f;
|
||||
float contentPosBottom = contentPosTop + viewportTransform.rect.height + 2f;
|
||||
float positionOfSelectedLogEntry = indexOfSelectedLogEntry * logItemHeight;
|
||||
|
||||
if( positionOfSelectedLogEntry <= contentPosBottom )
|
||||
{
|
||||
if( positionOfSelectedLogEntry <= contentPosTop )
|
||||
{
|
||||
contentPosTop = Mathf.Max( contentPosTop - DeltaHeightOfSelectedLogEntry, positionOfSelectedLogEntry - 1f );
|
||||
contentPosBottom = Mathf.Max( contentPosBottom - DeltaHeightOfSelectedLogEntry, contentPosTop + 2f );
|
||||
}
|
||||
else
|
||||
contentPosBottom = Mathf.Max( contentPosBottom - DeltaHeightOfSelectedLogEntry, positionOfSelectedLogEntry + 1f );
|
||||
}
|
||||
|
||||
int newBottomIndex = Mathf.Min( (int) ( contentPosBottom / logItemHeight ), entriesToShow.Count - 1 );
|
||||
int newTopIndex = Mathf.Clamp( (int) ( contentPosTop / logItemHeight ), 0, newBottomIndex );
|
||||
|
||||
if( currentTopIndex == -1 )
|
||||
{
|
||||
// There are no log items visible on screen,
|
||||
// just create the new log items
|
||||
updateAllVisibleItemContents = true;
|
||||
for( int i = 0, count = newBottomIndex - newTopIndex + 1; i < count; i++ )
|
||||
visibleLogItems.Add( manager.PopLogItem() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// There are some log items visible on screen
|
||||
|
||||
if( newBottomIndex < currentTopIndex || newTopIndex > currentBottomIndex )
|
||||
{
|
||||
// If user scrolled a lot such that, none of the log items are now within
|
||||
// the bounds of the scroll view, pool all the previous log items and create
|
||||
// new log items for the new list of visible debug entries
|
||||
updateAllVisibleItemContents = true;
|
||||
|
||||
visibleLogItems.TrimStart( visibleLogItems.Count, poolLogItemAction );
|
||||
for( int i = 0, count = newBottomIndex - newTopIndex + 1; i < count; i++ )
|
||||
visibleLogItems.Add( manager.PopLogItem() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// User did not scroll a lot such that, there are still some log items within
|
||||
// the bounds of the scroll view. Don't destroy them but update their content,
|
||||
// if necessary
|
||||
if( newTopIndex > currentTopIndex )
|
||||
visibleLogItems.TrimStart( newTopIndex - currentTopIndex, poolLogItemAction );
|
||||
|
||||
if( newBottomIndex < currentBottomIndex )
|
||||
visibleLogItems.TrimEnd( currentBottomIndex - newBottomIndex, poolLogItemAction );
|
||||
|
||||
if( newTopIndex < currentTopIndex )
|
||||
{
|
||||
for( int i = 0, count = currentTopIndex - newTopIndex; i < count; i++ )
|
||||
visibleLogItems.AddFirst( manager.PopLogItem() );
|
||||
|
||||
// If it is not necessary to update all the log items,
|
||||
// then just update the newly created log items. Otherwise,
|
||||
// wait for the major update
|
||||
if( !updateAllVisibleItemContents )
|
||||
UpdateLogItemContentsBetweenIndices( newTopIndex, currentTopIndex - 1, newTopIndex );
|
||||
}
|
||||
|
||||
if( newBottomIndex > currentBottomIndex )
|
||||
{
|
||||
for( int i = 0, count = newBottomIndex - currentBottomIndex; i < count; i++ )
|
||||
visibleLogItems.Add( manager.PopLogItem() );
|
||||
|
||||
// If it is not necessary to update all the log items,
|
||||
// then just update the newly created log items. Otherwise,
|
||||
// wait for the major update
|
||||
if( !updateAllVisibleItemContents )
|
||||
UpdateLogItemContentsBetweenIndices( currentBottomIndex + 1, newBottomIndex, newTopIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentTopIndex = newTopIndex;
|
||||
currentBottomIndex = newBottomIndex;
|
||||
|
||||
if( updateAllVisibleItemContents )
|
||||
{
|
||||
// Update all the log items
|
||||
UpdateLogItemContentsBetweenIndices( currentTopIndex, currentBottomIndex, newTopIndex );
|
||||
}
|
||||
}
|
||||
else if( currentTopIndex != -1 )
|
||||
{
|
||||
// There is nothing to show but some log items are still visible; pool them
|
||||
visibleLogItems.TrimStart( visibleLogItems.Count, poolLogItemAction );
|
||||
currentTopIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private DebugLogItem GetLogItemAtIndex( int index )
|
||||
{
|
||||
return visibleLogItems[index - currentTopIndex];
|
||||
}
|
||||
|
||||
private void UpdateLogItemContentsBetweenIndices( int topIndex, int bottomIndex, int logItemOffset )
|
||||
{
|
||||
for( int i = topIndex; i <= bottomIndex; i++ )
|
||||
{
|
||||
DebugLogItem logItem = visibleLogItems[i - logItemOffset];
|
||||
logItem.SetContent( entriesToShow[i], ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[i] : (DebugLogEntryTimestamp?) null, i, i == indexOfSelectedLogEntry );
|
||||
|
||||
RepositionLogItem( logItem );
|
||||
ColorLogItem( logItem );
|
||||
|
||||
if( isCollapseOn )
|
||||
logItem.ShowCount();
|
||||
else
|
||||
logItem.HideCount();
|
||||
}
|
||||
}
|
||||
|
||||
private void RepositionLogItem( DebugLogItem logItem )
|
||||
{
|
||||
int index = logItem.Index;
|
||||
Vector2 anchoredPosition = new Vector2( 1f, -index * logItemHeight );
|
||||
if( index > indexOfSelectedLogEntry )
|
||||
anchoredPosition.y -= DeltaHeightOfSelectedLogEntry;
|
||||
|
||||
logItem.Transform.anchoredPosition = anchoredPosition;
|
||||
}
|
||||
|
||||
private void ColorLogItem( DebugLogItem logItem )
|
||||
{
|
||||
int index = logItem.Index;
|
||||
if( index == indexOfSelectedLogEntry )
|
||||
logItem.Image.color = logItemSelectedColor;
|
||||
else if( index % 2 == 0 )
|
||||
logItem.Image.color = logItemNormalColor1;
|
||||
else
|
||||
logItem.Image.color = logItemNormalColor2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce231987d32488f43b6fb798f7df43f6
|
||||
timeCreated: 1466373025
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
// Listens to drag event on the DebugLogManager's resize button
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugLogResizeListener : MonoBehaviour, IBeginDragHandler, IDragHandler
|
||||
{
|
||||
#pragma warning disable 0649
|
||||
[SerializeField]
|
||||
private DebugLogManager debugManager;
|
||||
#pragma warning restore 0649
|
||||
|
||||
// This interface must be implemented in order to receive drag events
|
||||
void IBeginDragHandler.OnBeginDrag( PointerEventData eventData )
|
||||
{
|
||||
}
|
||||
|
||||
void IDragHandler.OnDrag( PointerEventData eventData )
|
||||
{
|
||||
debugManager.Resize( eventData );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6565f2084f5aef44abe57c988745b9c3
|
||||
timeCreated: 1601221093
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
// Listens to scroll events on the scroll rect that debug items are stored
|
||||
// and decides whether snap to bottom should be true or not
|
||||
//
|
||||
// Procedure: if, after a user input (drag or scroll), scrollbar is at the bottom, then
|
||||
// snap to bottom shall be true, otherwise it shall be false
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
public class DebugsOnScrollListener : MonoBehaviour, IScrollHandler, IBeginDragHandler, IEndDragHandler
|
||||
{
|
||||
public ScrollRect debugsScrollRect;
|
||||
public DebugLogManager debugLogManager;
|
||||
|
||||
public void OnScroll( PointerEventData data )
|
||||
{
|
||||
debugLogManager.SnapToBottom = IsScrollbarAtBottom();
|
||||
}
|
||||
|
||||
public void OnBeginDrag( PointerEventData data )
|
||||
{
|
||||
debugLogManager.SnapToBottom = false;
|
||||
}
|
||||
|
||||
public void OnEndDrag( PointerEventData data )
|
||||
{
|
||||
debugLogManager.SnapToBottom = IsScrollbarAtBottom();
|
||||
}
|
||||
|
||||
public void OnScrollbarDragStart( BaseEventData data )
|
||||
{
|
||||
debugLogManager.SnapToBottom = false;
|
||||
}
|
||||
|
||||
public void OnScrollbarDragEnd( BaseEventData data )
|
||||
{
|
||||
debugLogManager.SnapToBottom = IsScrollbarAtBottom();
|
||||
}
|
||||
|
||||
private bool IsScrollbarAtBottom()
|
||||
{
|
||||
return debugsScrollRect.verticalNormalizedPosition <= 1E-6f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb564dcb180e586429c57456166a76b5
|
||||
timeCreated: 1466004663
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
using UnityEngine.InputSystem.UI;
|
||||
#endif
|
||||
|
||||
namespace IngameDebugConsole
|
||||
{
|
||||
// Avoid multiple EventSystems in the scene by activating the embedded EventSystem only if one doesn't already exist in the scene
|
||||
[DefaultExecutionOrder( 1000 )]
|
||||
public class EventSystemHandler : MonoBehaviour
|
||||
{
|
||||
#pragma warning disable 0649
|
||||
[SerializeField]
|
||||
private GameObject embeddedEventSystem;
|
||||
#pragma warning restore 0649
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
|
||||
private void Awake()
|
||||
{
|
||||
StandaloneInputModule legacyInputModule = embeddedEventSystem.GetComponent<StandaloneInputModule>();
|
||||
if( legacyInputModule )
|
||||
{
|
||||
DestroyImmediate( legacyInputModule );
|
||||
embeddedEventSystem.AddComponent<InputSystemUIInputModule>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
SceneManager.sceneUnloaded -= OnSceneUnloaded;
|
||||
SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||
|
||||
ActivateEventSystemIfNeeded();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
SceneManager.sceneUnloaded -= OnSceneUnloaded;
|
||||
|
||||
DeactivateEventSystem();
|
||||
}
|
||||
|
||||
private void OnSceneLoaded( Scene scene, LoadSceneMode mode )
|
||||
{
|
||||
DeactivateEventSystem();
|
||||
ActivateEventSystemIfNeeded();
|
||||
}
|
||||
|
||||
private void OnSceneUnloaded( Scene current )
|
||||
{
|
||||
// Deactivate the embedded EventSystem before changing scenes because the new scene might have its own EventSystem
|
||||
DeactivateEventSystem();
|
||||
}
|
||||
|
||||
private void ActivateEventSystemIfNeeded()
|
||||
{
|
||||
if( embeddedEventSystem && !EventSystem.current )
|
||||
embeddedEventSystem.SetActive( true );
|
||||
}
|
||||
|
||||
private void DeactivateEventSystem()
|
||||
{
|
||||
if( embeddedEventSystem )
|
||||
embeddedEventSystem.SetActive( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3cc1b407f337e641ad32a2e91d5b478
|
||||
timeCreated: 1658741613
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Plugins/IngameDebugConsole/Sprites.meta
Normal file
9
Assets/Plugins/IngameDebugConsole/Sprites.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb5d7b23a9e684a41a6a5d4f300eb1e6
|
||||
folderAsset: yes
|
||||
timeCreated: 1465925237
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/IngameDebugConsole/Sprites/IconClear.psd
LFS
Normal file
BIN
Assets/Plugins/IngameDebugConsole/Sprites/IconClear.psd
LFS
Normal file
Binary file not shown.
135
Assets/Plugins/IngameDebugConsole/Sprites/IconClear.psd.meta
Normal file
135
Assets/Plugins/IngameDebugConsole/Sprites/IconClear.psd.meta
Normal file
@@ -0,0 +1,135 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a9e374666ad6cc47807bb001844f3d8
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 5
|
||||
maxTextureSize: 32
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 16
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
cookieLightType: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
- serializedVersion: 3
|
||||
buildTarget: WebGL
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 1
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag: DebugLogUI
|
||||
pSDRemoveMatte: 1
|
||||
pSDShowRemoveMatteOption: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Plugins/IngameDebugConsole/Sprites/IconCollapse.psd
LFS
Normal file
BIN
Assets/Plugins/IngameDebugConsole/Sprites/IconCollapse.psd
LFS
Normal file
Binary file not shown.
123
Assets/Plugins/IngameDebugConsole/Sprites/IconCollapse.psd.meta
Normal file
123
Assets/Plugins/IngameDebugConsole/Sprites/IconCollapse.psd.meta
Normal file
@@ -0,0 +1,123 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1546f8db185caf4dafcfa58efa3ba2c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMasterTextureLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 5
|
||||
maxTextureSize: 32
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 16
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 1
|
||||
cookieLightType: 1
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
spritePackingTag: DebugLogUI
|
||||
pSDRemoveMatte: 1
|
||||
pSDShowRemoveMatteOption: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user