From 849527cb935fa5e4a44ab1ac7b40f25f5f6983c5 Mon Sep 17 00:00:00 2001 From: Scream034 Date: Sat, 7 Feb 2026 01:10:47 +0500 Subject: [PATCH 01/20] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BB=D0=B5=D0=B5?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B2=20=D1=8D=D0=BA=D0=BE=D0=BD=D0=BE=D0=BC?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=BD=D0=B5=D1=82?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/Models/Playlist.cs | 2 +- Core/Services/AudioEngine.cs | 66 +- Core/Services/MemoryFirstCachingStream.cs | 770 ++++++++++++++-------- Core/Services/YoutubeProvider.cs | 61 -- Core/Youtube/YoutubeClient.cs | 35 - Features/Search/SearchViewModel.cs | 10 - Features/Shell/MainWindow.axaml.cs | 22 - UI/Controls/QualityImage.cs | 5 - 8 files changed, 544 insertions(+), 427 deletions(-) diff --git a/Core/Models/Playlist.cs b/Core/Models/Playlist.cs index 6e9f729..3728ef5 100644 --- a/Core/Models/Playlist.cs +++ b/Core/Models/Playlist.cs @@ -41,7 +41,7 @@ public string Name public string? ThumbnailUrl { get; set; } public string? Author { get; set; } - public string? Description { get; set; } // Добавлено поле описания из YouTube + public string? Description { get; set; } public PlaylistSyncMode SyncMode { get; set; } = PlaylistSyncMode.LocalOnly; public string? YoutubeId { get; set; } diff --git a/Core/Services/AudioEngine.cs b/Core/Services/AudioEngine.cs index 16b8126..5a6a8ac 100644 --- a/Core/Services/AudioEngine.cs +++ b/Core/Services/AudioEngine.cs @@ -11,10 +11,6 @@ namespace LMP.Core.Services; -/// -/// High-performance audio playback engine. -/// Uses command queue pattern for thread-safe operations. -/// public sealed class AudioEngine : ViewModelBase, IDisposable { #region Constants @@ -387,6 +383,25 @@ public ValueTask SeekAsync(TimeSpan position) var ms = (long)Math.Clamp(position.TotalMilliseconds, 0, TotalDuration.TotalMilliseconds); Volatile.Write(ref _cachedTimeMs, ms); + Log.Info($"[AudioEngine] Seeking to {position.TotalSeconds:F1}s"); + + // Уведомляем stream + if (_currentStream != null) + { + try + { + _currentStream.NotifySeek(ms); + } + catch (Exception ex) + { + Log.Warn($"[AudioEngine] NotifySeek failed: {ex.Message}"); + } + } + else + { + Log.Warn($"[AudioEngine] Cannot notify seek - stream is null"); + } + _ = Task.Run(() => { try { _player.Time = ms; } catch { } }); return ValueTask.CompletedTask; @@ -422,6 +437,8 @@ private int CancelCurrentPlayback() SetFlag(StateFlags.SuppressAutoNext, true); var newSession = Interlocked.Increment(ref _session); + Log.Info($"[AudioEngine] Cancelling current playback. New session: {newSession}"); + // Отменяем текущую операцию загрузки try { _playbackCts?.Cancel(); } catch { } @@ -835,14 +852,23 @@ await Task.Run(async () => // 4. Инициализируем кэш-стрим if (size > 0) { + var trackDuration = track.Duration.TotalMilliseconds; + cacheStream = new MemoryFirstCachingStream( - cacheId, stream.Url, size, _httpClient, _cacheManager, _streamingConfig, + cacheId, + stream.Url, + size, + _httpClient, + _cacheManager, + _streamingConfig, urlRefresher: async token => { var s = await GetStreamAsync(track, forceRefresh: true, token); return s?.Url; }, - originalTrackId: track.Id); + originalTrackId: track.Id, + getPlaybackTimeMs: () => Volatile.Read(ref _cachedTimeMs), + totalDurationMs: (long)trackDuration); // Пребуферизация с проверкой отмены if (!await cacheStream.PreBufferAsync(ct)) @@ -1302,44 +1328,40 @@ private static void RaiseOnUI(Action action) InternetProfile.Low => new() { ChunkSize = 64 * 1024, - ReadAheadChunks = 2, - MaxConcurrentDownloads = 2, + ReadAheadChunks = 1, + MaxConcurrentDownloads = 2, // Было 1, стало 2 VlcNetworkCachingMs = 4000, MaxRamChunks = 150, - MaxBufferAheadChunks = 20, // ~20 сек буфер DownloadFullTrack = false }, InternetProfile.Medium => new() { - ChunkSize = 128 * 1024, - ReadAheadChunks = 4, - MaxConcurrentDownloads = 3, + ChunkSize = 256 * 1024, // Было 128KB, стало 256KB + ReadAheadChunks = 1, + MaxConcurrentDownloads = 4, // Было 2, стало 4 VlcNetworkCachingMs = 2000, MaxRamChunks = 100, - MaxBufferAheadChunks = 30, // ~30 сек буфер DownloadFullTrack = false }, InternetProfile.High => new() { - ChunkSize = 256 * 1024, - ReadAheadChunks = 6, - MaxConcurrentDownloads = 4, + ChunkSize = 512 * 1024, // Было 256KB + ReadAheadChunks = 2, + MaxConcurrentDownloads = 6, // Было 3, стало 6 VlcNetworkCachingMs = 1000, MaxRamChunks = 80, - MaxBufferAheadChunks = 50, // ~50 сек буфер DownloadFullTrack = false }, InternetProfile.Ultra => new() { - ChunkSize = 512 * 1024, + ChunkSize = 1024 * 1024, // 1MB чанки ReadAheadChunks = 10, - MaxConcurrentDownloads = 6, + MaxConcurrentDownloads = 8, VlcNetworkCachingMs = 500, MaxRamChunks = 60, - MaxBufferAheadChunks = 100, // Большой буфер - DownloadFullTrack = true // Качать полностью! + DownloadFullTrack = true }, - _ => new() { ChunkSize = 128 * 1024, MaxRamChunks = 100, MaxBufferAheadChunks = 30 } + _ => new() { ChunkSize = 256 * 1024, MaxRamChunks = 100 } }; public void NotifyAppMinimized() => _currentStream?.ReleaseRamBuffers(); diff --git a/Core/Services/MemoryFirstCachingStream.cs b/Core/Services/MemoryFirstCachingStream.cs index 6aa2e92..0f2bdb8 100644 --- a/Core/Services/MemoryFirstCachingStream.cs +++ b/Core/Services/MemoryFirstCachingStream.cs @@ -11,28 +11,52 @@ namespace LMP.Core.Services; public sealed class MemoryFirstCachingStream : Stream { - #region Constants + #region Configuration - private const int MaxFileOpenRetries = 10; - private const int FileOpenRetryDelayMs = 100; - private const int DiskSaveThresholdBytes = 128 * 1024; + private static class Config + { + public const int MaxOpenRetries = 10; + public const int RetryDelayBaseMs = 100; + public const int FlushTimeoutMs = 1000; + public const int SemaphoreTimeoutMs = 2000; + public const int SaveThresholdBytes = 128 * 1024; + public const int ChannelCapacity = 64; + public const int MaxRetries = 2; + public const int RetryDelayMs = 500; + public const int PriorityUrgent = 0; + public const int PriorityReadAhead = 50; + public const int TailChunksForCues = 3; + public const int ChunkWaitMs = 300; + public const int PauseCheckIntervalMs = 500; + public const int IdleLoopMs = 100; + public const int PreBufferCheckMs = 150; + public const int InitialBufferSeconds = 15; + public const int ReadAheadSeconds = 15; + public const int SeekBufferSeconds = 10; + public const int ExtendIntervalMs = 500; + public const int ChunksToKeepBehind = 2; + public const int ChunksToKeepMultiplier = 2; + + // Таймауты для ожидания чанков + public const int CriticalWaitMs = 30000; // 30 сек + public const int NonCriticalWaitMs = 60000; // 60 сек + + // НОВОЕ: Для срочных чанков - больше параллелизма + public const int UrgentBoostMultiplier = 2; + } #endregion - #region Configuration + #region Fields private readonly int _chunkSize; private readonly int _readAheadChunks; private readonly int _maxConcurrentDownloads; private readonly int _maxRamChunks; private readonly int _downloadTimeoutMs; - - // Максимальный буфер вперёд (в чанках) - private readonly int _maxBufferAhead; - - #endregion - - #region Identity + private readonly int _estimatedBitrate; + private readonly long _totalDurationMs; + private readonly Func? _getPlaybackTimeMs; private readonly string _cacheId; private readonly string _originalTrackId; @@ -40,19 +64,12 @@ public sealed class MemoryFirstCachingStream : Stream private readonly long _contentLength; private readonly string _cachePath; private readonly int _totalChunks; - - #endregion - - #region Dependencies + private readonly int _tailStartChunk; private readonly HttpClient _http; private readonly StreamCacheManager _cacheManager; private readonly Func>? _urlRefresher; - #endregion - - #region State - private readonly ConcurrentDictionary _chunks = new(); private readonly ConcurrentDictionary _pendingDownloads = new(); private readonly RangeMap _diskRanges; @@ -62,19 +79,20 @@ public sealed class MemoryFirstCachingStream : Stream private volatile bool _downloadComplete; private volatile bool _disposed; private volatile bool _disposing; - - // Флаг паузы и режим полного скачивания private volatile bool _isPaused; private volatile bool _downloadFullTrack; + private volatile int _targetBufferChunk = -1; + private volatile int _lastPlaybackChunk; + private volatile int _prevExtendPlayback = -1; // Для BufferExtender + private volatile bool _hasUrgentWork; - #endregion - - #region Synchronization - + // Добавляем второй семафор для срочных загрузок + private readonly SemaphoreSlim _urgentDownloadSemaphore; private readonly SemaphoreSlim _downloadSemaphore; private readonly SemaphoreSlim _fileSemaphore = new(1, 1); private readonly SemaphoreSlim _refreshLock = new(1, 1); private readonly Lock _queueLock = new(); + private readonly Lock _extendLock = new(); private readonly PriorityQueue _downloadQueue = new(); private readonly HashSet _queuedChunks = []; @@ -84,14 +102,15 @@ public sealed class MemoryFirstCachingStream : Stream private readonly CancellationTokenSource _disposeCts = new(); private readonly CancellationTokenSource _downloadCts; - #endregion - - #region Tasks - private readonly Task _diskWriterTask; + private readonly Task _bufferExtenderTask; private Task? _downloadLoop; private FileStream? _cacheFile; + private int _cacheHits; + private int _cacheMisses; + private int _vlcRequestsIgnored; + #endregion #region Stream Properties @@ -107,14 +126,8 @@ public override long Position set => Seek(value, SeekOrigin.Begin); } - public double DownloadProgress - { - get - { - if (_contentLength <= 0) return 0; - return Math.Min((double)Volatile.Read(ref _bytesDownloaded) / _contentLength * 100, 100); - } - } + public double DownloadProgress => _contentLength <= 0 ? 0 + : Math.Min((double)Volatile.Read(ref _bytesDownloaded) / _contentLength * 100, 100); public bool IsFullyDownloaded => _downloadComplete; @@ -123,14 +136,12 @@ public double DownloadProgress #region Constructor public MemoryFirstCachingStream( - string cacheId, - string url, - long contentLength, - HttpClient http, - StreamCacheManager cacheManager, - StreamingConfig config, + string cacheId, string url, long contentLength, HttpClient http, + StreamCacheManager cacheManager, StreamingConfig config, Func>? urlRefresher = null, - string? originalTrackId = null) + string? originalTrackId = null, + Func? getPlaybackTimeMs = null, + long totalDurationMs = 0) { _cacheId = cacheId; _originalTrackId = originalTrackId ?? cacheId; @@ -139,54 +150,130 @@ public MemoryFirstCachingStream( _http = http; _cacheManager = cacheManager; _urlRefresher = urlRefresher; + _getPlaybackTimeMs = getPlaybackTimeMs; + _totalDurationMs = totalDurationMs > 0 ? totalDurationMs : 1; - // Config _chunkSize = config.ChunkSize; _readAheadChunks = config.ReadAheadChunks; _maxConcurrentDownloads = config.MaxConcurrentDownloads; - _maxRamChunks = config.MaxRamChunks > 0 ? config.MaxRamChunks : 50; + _maxRamChunks = Math.Max(config.MaxRamChunks, 10); _downloadTimeoutMs = config.DownloadTimeoutMs; - - // Ограничение буфера (по умолчанию 30 секунд при 128kbps ≈ 30 чанков) - _maxBufferAhead = config.MaxBufferAheadChunks > 0 ? config.MaxBufferAheadChunks : 30; _downloadFullTrack = config.DownloadFullTrack; - // Derived _cachePath = StreamCacheManager.GetCachePath(_cacheId); _downloadCts = new CancellationTokenSource(); _downloadSemaphore = new SemaphoreSlim(_maxConcurrentDownloads); + // НОВОЕ: Для срочных чанков - в 2 раза больше слотов + _urgentDownloadSemaphore = new SemaphoreSlim( + Math.Max(_maxConcurrentDownloads * Config.UrgentBoostMultiplier, 4)); _totalChunks = (int)((_contentLength + _chunkSize - 1) / _chunkSize); + _tailStartChunk = Math.Max(0, _totalChunks - Config.TailChunksForCues); - // Metadata - var meta = StreamCacheManager.TryGetMetadata(cacheId) - ?? StreamCacheManager.LoadOrCreateMetadata(cacheId, url, contentLength); + var meta = StreamCacheManager.TryGetMetadata(cacheId); + _estimatedBitrate = meta?.Bitrate ?? 128; + Log.Debug($"[Buffer] Init: id={_cacheId}, chunks={_totalChunks}, " + + $"duration={totalDurationMs}ms, tail={_tailStartChunk}-{_totalChunks - 1}"); + + meta ??= StreamCacheManager.LoadOrCreateMetadata(cacheId, url, contentLength); _diskRanges = RangeMap.Deserialize(meta.RangesJson); _bytesDownloaded = _diskRanges.DownloadedBytes; - // Already complete? if (_diskRanges.IsFullyDownloaded(_contentLength)) { _downloadComplete = true; _cacheManager.TriggerCacheCompleted(_cacheId, _originalTrackId); - Log.Info($"[CacheStream] {_cacheId} already complete"); + Log.Debug($"[Buffer] Already fully cached"); } - // File _cacheFile = OpenCacheFile(_cachePath); if (_cacheFile != null && _cacheFile.Length < _contentLength) _cacheFile.SetLength(_contentLength); - // Disk writer channel _diskChannel = Channel.CreateBounded<(long, byte[], int)>( - new BoundedChannelOptions(64) { FullMode = BoundedChannelFullMode.Wait, SingleReader = true }); + new BoundedChannelOptions(Config.ChannelCapacity) + { + FullMode = BoundedChannelFullMode.Wait, + SingleReader = true + }); + _diskWriterTask = Task.Run(DiskWriterLoopAsync); + _bufferExtenderTask = Task.Run(BufferExtenderLoopAsync); - // Diagnostics MemoryDiagnostics.TrackInstance("Stream.Active"); MemoryDiagnostics.TrackBytes("Stream.TotalSize", _contentLength); + } + + #endregion + + #region Chunk Helpers + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasChunk(int idx) + { + if (_chunks.ContainsKey(idx)) return true; + long start = (long)idx * _chunkSize; + long end = Math.Min(start + _chunkSize, _contentLength); + return _diskRanges.IsRangeComplete(start, end); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CanEnqueue(int idx) => + !HasChunk(idx) && !_pendingDownloads.ContainsKey(idx) && !_queuedChunks.Contains(idx); + + /// Проверяет нужно ли качать чанк (между playback и target, или tail) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsInDownloadRange(int idx) + { + // Tail чанки - всегда + if (idx >= _tailStartChunk) return true; + + // Между playback и target + return idx >= _lastPlaybackChunk && idx <= _targetBufferChunk; + } + + private int TimeToChunk(long timeMs) + { + if (_totalDurationMs <= 0 || _contentLength <= 0) return 0; + if (timeMs <= 0) return 0; + if (timeMs >= _totalDurationMs) return _totalChunks - 1; + return Math.Clamp((int)((double)timeMs / _totalDurationMs * _contentLength / _chunkSize), 0, _totalChunks - 1); + } + + private int SecondsToChunks(int seconds) => + _estimatedBitrate <= 0 + ? Math.Max(1, seconds / 2) + : Math.Max(1, (int)Math.Ceiling(_estimatedBitrate * 1000.0 / 8.0 * seconds / _chunkSize)); + + private int GetPlaybackChunk() + { + if (_getPlaybackTimeMs == null || _totalDurationMs <= 0) + return _lastPlaybackChunk; + + long currentMs = _getPlaybackTimeMs(); + if (currentMs <= 0) return _lastPlaybackChunk; + + int chunk = Math.Clamp((int)((double)currentMs / _totalDurationMs * _contentLength / _chunkSize), 0, _totalChunks - 1); - Log.Info($"[CacheStream] Opened {_cacheId}: {contentLength / 1024 / 1024}MB, maxAhead={_maxBufferAhead} chunks"); + if (chunk > _lastPlaybackChunk) + _lastPlaybackChunk = chunk; + + return _lastPlaybackChunk; + } + + private int CountCachedInRange(int from, int to) + { + int count = 0; + for (int i = from; i <= to && i < _totalChunks; i++) + if (HasChunk(i)) count++; + return count; + } + + private bool AreTailChunksCached() + { + for (int i = _tailStartChunk; i < _totalChunks; i++) + if (!HasChunk(i)) return false; + return true; } #endregion @@ -197,6 +284,9 @@ public async ValueTask PreBufferAsync(CancellationToken ct) { if (_disposed || _disposing) return false; + var sw = Stopwatch.StartNew(); + Log.Debug($"[Buffer] PreBuffer start"); + try { using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct, _downloadCts.Token, _disposeCts.Token); @@ -204,88 +294,230 @@ public async ValueTask PreBufferAsync(CancellationToken ct) _downloadLoop ??= Task.Run(() => DownloadLoopAsync(token), token); - if (HasChunk(0)) return true; + // Минимальный начальный target - только для старта + UpdateTarget(0, 5, "Initial"); // 5 секунд вместо 15 + ScheduleChunks(0); - EnqueueUrgent(0); + if (HasChunk(0)) + { + Log.Debug($"[Buffer] Chunk 0 ready, target={_targetBufferChunk}"); + return true; + } - var sw = Stopwatch.StartNew(); while (!HasChunk(0)) { if (token.IsCancellationRequested) return false; - if (!_dataAvailable.Wait(200, token)) - { + if (!_dataAvailable.Wait(Config.PreBufferCheckMs, token)) if (sw.ElapsedMilliseconds > _downloadTimeoutMs) return false; - } if (!HasChunk(0)) _dataAvailable.Reset(); } + + Log.Debug($"[Buffer] PreBuffer complete in {sw.ElapsedMilliseconds}ms"); return true; } - catch { return false; } + catch (OperationCanceledException) { return false; } + } + + public void NotifySeek(long positionMs) + { + Log.Debug($"[Buffer] NotifySeek: {positionMs}ms (duration={_totalDurationMs}ms)"); + + int newChunk = TimeToChunk(positionMs); + Log.Debug($"[Buffer] NotifySeek → chunk {newChunk}"); + + _lastPlaybackChunk = newChunk; + _prevExtendPlayback = newChunk; + + // Устанавливаем target НАЧИНАЯ с новой позиции + lock (_extendLock) + { + // Сбрасываем target если seekнули ВПЕРЁД за него + if (newChunk > _targetBufferChunk) + { + _targetBufferChunk = newChunk - 1; // UpdateTarget расширит + } + } + + int newTarget = UpdateTarget(newChunk, Config.SeekBufferSeconds, "Seek"); + int rangeSize = Math.Max(1, newTarget - newChunk + 1); + int alreadyCached = CountCachedInRange(newChunk, newTarget); + bool allCached = alreadyCached >= rangeSize && AreTailChunksCached(); + + if (allCached) + { + _hasUrgentWork = false; + Log.Debug($"[Buffer] NotifySeek done: all cached ({alreadyCached}/{rangeSize}), target={_targetBufferChunk}"); + return; + } + + // Очищаем очередь + lock (_queueLock) + { + _downloadQueue.Clear(); + _queuedChunks.Clear(); + } + + int queued = ScheduleChunks(newChunk); + _hasUrgentWork = queued > 0; + + Log.Debug($"[Buffer] NotifySeek done: queue={queued}, target={_targetBufferChunk}, " + + $"pending={_pendingDownloads.Count}, cached={alreadyCached}/{rangeSize}, hasUrgent={_hasUrgentWork}"); } public void CancelPendingReads() { + Log.Debug($"[Buffer] CancelPendingReads"); try { _disposeCts.Cancel(); } catch { } } - /// - /// Уведомление о паузе воспроизведения - /// public void NotifyPaused(bool paused) { + if (_isPaused == paused) return; _isPaused = paused; - if (paused) - { - Log.Debug($"[CacheStream] Paused, stopping background download"); - } + Log.Debug($"[Buffer] Pause={paused}, playback={_lastPlaybackChunk}, target={_targetBufferChunk}"); } - /// - /// Включить полное скачивание (для "скачать трек") - /// public void EnableFullDownload() { + if (_downloadFullTrack) return; _downloadFullTrack = true; - // Добавляем все оставшиеся чанки в очередь - lock (_queueLock) - { - for (int i = 0; i < _totalChunks; i++) - { - TryEnqueue(i, 100 + i); - } - } - Log.Info($"[CacheStream] Full download enabled for {_cacheId}"); + _targetBufferChunk = _totalChunks - 1; + ScheduleChunks(0); + Log.Debug($"[Buffer] Full download enabled"); } public void ReleaseRamBuffers() { if (_disposed) return; - int removed = 0; long freed = 0; - foreach (var kvp in _chunks) { int idx = kvp.Key; long start = (long)idx * _chunkSize; long end = Math.Min(start + _chunkSize, _contentLength); - if (_diskRanges.IsRangeComplete(start, end)) + if (_diskRanges.IsRangeComplete(start, end) && _chunks.TryRemove(idx, out var buffer)) { - if (_chunks.TryRemove(idx, out var buffer)) + freed += buffer.Length; + ArrayPool.Shared.Return(buffer); + } + } + + if (freed > 0) + MemoryDiagnostics.UntrackBytes("Stream.RAMChunks", freed); + } + + #endregion + + #region Scheduling + + private int UpdateTarget(int fromChunk, int seconds, string reason) + { + if (_downloadFullTrack || _downloadComplete) + return _targetBufferChunk; + + lock (_extendLock) + { + int newTarget = Math.Min(fromChunk + SecondsToChunks(seconds), _tailStartChunk - 1); + + if (newTarget > _targetBufferChunk) + { + Log.Debug($"[Buffer] {reason}: from={fromChunk}, target {_targetBufferChunk} → {newTarget}"); + _targetBufferChunk = newTarget; + } + + return _targetBufferChunk; + } + } + + private int ScheduleChunks(int fromChunk) + { + int added = 0; + + lock (_queueLock) + { + // Tail-чанки (высший приоритет) + for (int i = _tailStartChunk; i < _totalChunks; i++) + { + if (CanEnqueue(i) && _queuedChunks.Add(i)) + { + _downloadQueue.Enqueue(i, Config.PriorityUrgent + 1); + added++; + } + } + + // Чанки от fromChunk до target + int limit = Math.Min(_targetBufferChunk + 1, _tailStartChunk); + + // ОПТИМИЗАЦИЯ: Первые 3 чанка - максимальный приоритет + int urgentCount = Math.Min(3, limit - fromChunk); + + for (int i = fromChunk; i < limit; i++) + { + if (CanEnqueue(i) && _queuedChunks.Add(i)) { - freed += buffer.Length; - removed++; - ArrayPool.Shared.Return(buffer); + int priority; + if (i < fromChunk + urgentCount) + { + // Первые чанки - срочные + priority = Config.PriorityUrgent + (i - fromChunk); + } + else + { + // Остальные - обычные + priority = Config.PriorityReadAhead + (i - fromChunk); + } + + _downloadQueue.Enqueue(i, priority); + added++; } } } - if (freed > 0) + return added; + } + + #endregion + + #region Buffer Extender + + private async Task BufferExtenderLoopAsync() + { + await Task.Delay(300, _disposeCts.Token).ConfigureAwait(false); + + try { - MemoryDiagnostics.UntrackBytes("Stream.RAMChunks", freed); - Log.Info($"[CacheStream] Released {removed} chunks ({freed / 1024 / 1024}MB) on minimize"); + while (!_disposeCts.IsCancellationRequested && !_downloadComplete && !_disposing) + { + if (!_isPaused && !_downloadFullTrack) + { + int currentPlayback = GetPlaybackChunk(); + + // Расширяем ТОЛЬКО если playback ПРОДВИНУЛСЯ ВПЕРЁД + if (currentPlayback > _prevExtendPlayback) + { + _prevExtendPlayback = currentPlayback; + + int margin = SecondsToChunks(3); + if (currentPlayback + margin >= _targetBufferChunk) + { + int oldTarget = _targetBufferChunk; + UpdateTarget(currentPlayback, Config.ReadAheadSeconds, "Timer"); + + if (_targetBufferChunk > oldTarget) + { + int queued = ScheduleChunks(currentPlayback); + if (queued > 0) _hasUrgentWork = true; + } + } + } + } + + await Task.Delay(Config.ExtendIntervalMs, _disposeCts.Token); + } } + catch (OperationCanceledException) { } } #endregion @@ -294,7 +526,8 @@ public void ReleaseRamBuffers() public override int Read(byte[] buffer, int offset, int count) { - if (_disposed || _disposing || _disposeCts.IsCancellationRequested) return 0; + if (_disposed || _disposing || _disposeCts.IsCancellationRequested) + return 0; long pos = Volatile.Read(ref _position); if (pos >= _contentLength) return 0; @@ -308,29 +541,82 @@ public override int Read(byte[] buffer, int offset, int count) try { - // Wait for chunk - while (!HasChunk(chunkIndex)) + // Быстрый путь + if (HasChunk(chunkIndex)) { - if (_disposed || _disposing || _disposeCts.IsCancellationRequested) return 0; - EnqueueUrgent(chunkIndex); - try { _dataAvailable.Wait(500, _disposeCts.Token); } catch { return 0; } - if (!HasChunk(chunkIndex)) _dataAvailable.Reset(); + Interlocked.Increment(ref _cacheHits); + return ReadAndAdvance(chunkIndex, offsetInChunk, buffer, offset, toRead); } - int bytesRead = ReadChunk(chunkIndex, offsetInChunk, buffer, offset, toRead); - if (bytesRead > 0) + Interlocked.Increment(ref _cacheMisses); + + // Проверяем находится ли чанк в диапазоне [playback, target] или tail + bool isInRange = IsInDownloadRange(chunkIndex); + + if (isInRange) { - Interlocked.Add(ref _position, bytesRead); - - // ИСПРАВЛЕНИЕ: Read-ahead только если не на паузе и буфер не полон - if (!_isPaused || _downloadFullTrack) + // В пределах нужного диапазона - качаем + EnqueueUrgentIfNeeded(chunkIndex); + Log.Debug($"[Buffer] Need chunk {chunkIndex} (playback={_lastPlaybackChunk}, target={_targetBufferChunk})"); + } + else + { + // VLC lookahead за пределами диапазона - НЕ качаем + Interlocked.Increment(ref _vlcRequestsIgnored); + if (_vlcRequestsIgnored % 20 == 1) { - EnqueueReadAheadLimited(chunkIndex); + Log.Debug($"[Buffer] Ignoring VLC chunk {chunkIndex} " + + $"(range={_lastPlaybackChunk}-{_targetBufferChunk})"); } } - return bytesRead; + + // Ждём чанк с разными таймаутами + int maxWaitMs = isInRange ? Config.CriticalWaitMs : Config.NonCriticalWaitMs; + var sw = Stopwatch.StartNew(); + + while (!HasChunk(chunkIndex)) + { + if (_disposed || _disposing) return 0; + + if (sw.ElapsedMilliseconds > maxWaitMs) + { + if (isInRange) + Log.Error($"[Buffer] Timeout waiting for chunk {chunkIndex}"); + return 0; + } + + try { _dataAvailable.Wait(Config.ChunkWaitMs, _disposeCts.Token); } + catch { return 0; } + + if (!HasChunk(chunkIndex)) _dataAvailable.Reset(); + } + + return ReadAndAdvance(chunkIndex, offsetInChunk, buffer, offset, toRead); + } + catch (Exception ex) + { + Log.Error($"[Buffer] Read error: {ex.Message}"); + return 0; } - catch { return 0; } + } + + private void EnqueueUrgentIfNeeded(int chunkIndex) + { + lock (_queueLock) + { + if (CanEnqueue(chunkIndex) && _queuedChunks.Add(chunkIndex)) + { + _downloadQueue.Enqueue(chunkIndex, Config.PriorityUrgent); + _hasUrgentWork = true; + } + } + } + + private int ReadAndAdvance(int idx, int off, byte[] buf, int bufOff, int count) + { + int bytesRead = ReadChunk(idx, off, buf, bufOff, count); + if (bytesRead > 0) Interlocked.Add(ref _position, bytesRead); + return bytesRead; } public override long Seek(long offset, SeekOrigin origin) @@ -346,10 +632,13 @@ public override long Seek(long offset, SeekOrigin origin) }; newPos = Math.Clamp(newPos, 0, _contentLength); - Volatile.Write(ref _position, newPos); + long oldPos = Interlocked.Exchange(ref _position, newPos); + int oldChunk = (int)(oldPos / _chunkSize); int newChunk = (int)(newPos / _chunkSize); - EnqueueUrgent(newChunk); + + if (Math.Abs(newChunk - oldChunk) > 2) + Log.Debug($"[Buffer] Stream.Seek: {oldChunk} → {newChunk}"); return newPos; } @@ -360,19 +649,10 @@ public override void Flush() { } #endregion - #region Chunk Operations - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool HasChunk(int idx) - { - if (_chunks.ContainsKey(idx)) return true; - long start = (long)idx * _chunkSize; - return _diskRanges.IsRangeComplete(start, Math.Min(start + _chunkSize, _contentLength)); - } + #region Chunk Reading private int ReadChunk(int idx, int off, byte[] buf, int bufOff, int count) { - // Try RAM first if (_chunks.TryGetValue(idx, out var chunk)) { int usefulLen = idx == _totalChunks - 1 @@ -383,7 +663,6 @@ private int ReadChunk(int idx, int off, byte[] buf, int bufOff, int count) return available; } - // Fall back to disk long start = (long)idx * _chunkSize; if (_diskRanges.IsRangeComplete(start, Math.Min(start + _chunkSize, _contentLength))) return ReadFromDisk(start + off, buf, bufOff, count); @@ -397,7 +676,9 @@ private int ReadFromDisk(long pos, byte[] buf, int off, int count) try { - _fileSemaphore.Wait(_disposeCts.Token); + if (!_fileSemaphore.Wait(Config.ChunkWaitMs, _disposeCts.Token)) + return 0; + try { if (_cacheFile == null) return 0; @@ -411,143 +692,80 @@ private int ReadFromDisk(long pos, byte[] buf, int off, int count) #endregion - #region Download Queue - - private void EnqueueUrgent(int idx) - { - lock (_queueLock) - { - TryEnqueue(idx, 0); - for (int i = 1; i <= 3 && idx + i < _totalChunks; i++) - TryEnqueue(idx + i, i); - } - } - - /// - /// Read-ahead с ограничением буфера - /// - private void EnqueueReadAheadLimited(int current) - { - if (_downloadComplete) return; - - lock (_queueLock) - { - // Считаем сколько чанков уже скачано/в процессе вперёд от текущей позиции - int bufferedAhead = 0; - for (int i = current + 1; i < _totalChunks && i <= current + _maxBufferAhead; i++) - { - if (HasChunk(i) || _pendingDownloads.ContainsKey(i) || _queuedChunks.Contains(i)) - bufferedAhead++; - else - break; // Первый пропуск - прекращаем подсчёт - } - - // Если буфер достаточен - не добавляем новые чанки - if (!_downloadFullTrack && bufferedAhead >= _maxBufferAhead) - { - return; - } - - // Добавляем только недостающие чанки до лимита - int toAdd = _downloadFullTrack ? _readAheadChunks : Math.Min(_readAheadChunks, _maxBufferAhead - bufferedAhead); - - for (int i = 1; i <= toAdd && current + i < _totalChunks; i++) - { - TryEnqueue(current + i, 50 + i); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void TryEnqueue(int idx, int priority) - { - if (HasChunk(idx) || _pendingDownloads.ContainsKey(idx)) return; - if (_queuedChunks.Add(idx)) - _downloadQueue.Enqueue(idx, priority); - } - - #endregion - #region Download Loop private async Task DownloadLoopAsync(CancellationToken ct) { + Log.Debug($"[Buffer] Download loop started"); + while (!ct.IsCancellationRequested && !_disposing) { - // Если на паузе и не в режиме полного скачивания - ждём - if (_isPaused && !_downloadFullTrack) + bool shouldWork = _hasUrgentWork || _downloadFullTrack || !_isPaused; + + if (!shouldWork) { - await Task.Delay(500, ct).ConfigureAwait(false); + await Task.Delay(Config.PauseCheckIntervalMs, ct).ConfigureAwait(false); continue; } - int chunk = -1; - int currentPosition = (int)(Volatile.Read(ref _position) / _chunkSize); - - lock (_queueLock) - { - // Проверяем нужно ли ещё качать - if (!_downloadFullTrack && !ShouldContinueDownloading(currentPosition)) - { - // Буфер полон - очищаем очередь - _downloadQueue.Clear(); - _queuedChunks.Clear(); - } - - while (_downloadQueue.Count > 0) - { - var c = _downloadQueue.Dequeue(); - _queuedChunks.Remove(c); - if (!HasChunk(c) && !_pendingDownloads.ContainsKey(c)) - { - chunk = c; - break; - } - } - } + (int chunk, int priority) = DequeueNextChunk(); if (chunk < 0) { if (IsAllDownloaded()) { _downloadComplete = true; + Log.Debug($"[Buffer] FULLY CACHED ({_totalChunks} chunks), ignored={_vlcRequestsIgnored}"); break; } - try { await Task.Delay(100, ct); } catch { break; } + + await Task.Delay(Config.IdleLoopMs, ct); continue; } - try { await _downloadSemaphore.WaitAsync(ct); } + if (HasChunk(chunk)) continue; + + Log.Debug($"[Buffer] Downloading chunk {chunk}"); + + // ОПТИМИЗАЦИЯ: Срочные чанки используют отдельный семафор + bool isUrgent = priority <= Config.PriorityUrgent + 3; + var semaphore = isUrgent ? _urgentDownloadSemaphore : _downloadSemaphore; + + try { await semaphore.WaitAsync(ct); } catch { break; } - _ = DownloadChunkAsync(chunk, ct); + _ = DownloadChunkAsync(chunk, semaphore, ct); } + + Log.Debug($"[Buffer] Download loop ended"); } - /// - /// Проверка нужно ли продолжать скачивание - /// - private bool ShouldContinueDownloading(int currentChunk) + private (int chunk, int priority) DequeueNextChunk() { - if (_downloadFullTrack || _downloadComplete) return true; - - // Считаем скачанные чанки впереди - int bufferedAhead = 0; - for (int i = currentChunk; i < _totalChunks && i <= currentChunk + _maxBufferAhead + 5; i++) + lock (_queueLock) { - if (HasChunk(i) || _pendingDownloads.ContainsKey(i)) - bufferedAhead++; - } + while (_downloadQueue.Count > 0) + { + if (!_downloadQueue.TryDequeue(out var chunk, out var priority)) + continue; + + _queuedChunks.Remove(chunk); + + if (!HasChunk(chunk) && !_pendingDownloads.ContainsKey(chunk)) + return (chunk, priority); + } + + if (_pendingDownloads.Count == 0) + _hasUrgentWork = false; - // Продолжаем только если буфер меньше лимита - return bufferedAhead < _maxBufferAhead; + return (-1, int.MaxValue); + } } - private async Task DownloadChunkAsync(int idx, CancellationToken ct) + private async Task DownloadChunkAsync(int idx, SemaphoreSlim semaphore, CancellationToken ct) { byte[]? buffer = null; int retry = 0; - const int maxRetries = 2; try { @@ -556,7 +774,7 @@ private async Task DownloadChunkAsync(int idx, CancellationToken ct) var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (!_pendingDownloads.TryAdd(idx, tcs.Task)) return; - while (retry <= maxRetries) + while (retry <= Config.MaxRetries) { try { @@ -571,7 +789,9 @@ private async Task DownloadChunkAsync(int idx, CancellationToken ct) using var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token); - if (resp.StatusCode == HttpStatusCode.Forbidden && retry < maxRetries && _urlRefresher != null) + if (resp.StatusCode == HttpStatusCode.Forbidden && + retry < Config.MaxRetries && + _urlRefresher != null) { await RefreshUrlAsync(cts.Token); retry++; @@ -584,8 +804,11 @@ private async Task DownloadChunkAsync(int idx, CancellationToken ct) using var netStream = await resp.Content.ReadAsStreamAsync(cts.Token); int totalRead = 0, bytesRead; - while ((bytesRead = await netStream.ReadAsync(buffer.AsMemory(totalRead, _chunkSize - totalRead), cts.Token)) > 0) + while ((bytesRead = await netStream.ReadAsync( + buffer.AsMemory(totalRead, _chunkSize - totalRead), cts.Token)) > 0) + { totalRead += bytesRead; + } if (!_chunks.ContainsKey(idx) && !_disposing) { @@ -595,7 +818,6 @@ private async Task DownloadChunkAsync(int idx, CancellationToken ct) MemoryDiagnostics.TrackBytes("Stream.RAMChunks", buffer.Length); - // Queue for disk write if (_cacheFile != null && !_disposing) { var diskBuf = ArrayPool.Shared.Rent(totalRead); @@ -603,7 +825,7 @@ private async Task DownloadChunkAsync(int idx, CancellationToken ct) await _diskChannel.Writer.WriteAsync((start, diskBuf, totalRead), cts.Token); } - buffer = null; // Ownership transferred + buffer = null; if (_chunks.Count > _maxRamChunks) TrimRamCache(); @@ -612,29 +834,30 @@ private async Task DownloadChunkAsync(int idx, CancellationToken ct) tcs.SetResult(); break; } - catch (Exception ex) when (retry < maxRetries && ex is not OperationCanceledException) + catch (Exception ex) when (retry < Config.MaxRetries && ex is not OperationCanceledException) { - await Task.Delay(500, ct); + Log.Warn($"[Buffer] Chunk {idx} retry {retry + 1}: {ex.Message}"); + await Task.Delay(Config.RetryDelayMs, ct); retry++; } } } + catch (OperationCanceledException) { } catch (Exception ex) { - if (ex is not OperationCanceledException) - Log.Warn($"[CacheStream] Chunk {idx} error: {ex.Message}"); + Log.Warn($"[Buffer] Chunk {idx} failed: {ex.Message}"); } finally { if (buffer != null) ArrayPool.Shared.Return(buffer); _pendingDownloads.TryRemove(idx, out _); - _downloadSemaphore.Release(); + semaphore.Release(); // ВАЖНО: Освобождаем правильный семафор } } private async ValueTask RefreshUrlAsync(CancellationToken ct) { - await _refreshLock.WaitAsync(ct); + if (!await _refreshLock.WaitAsync(Config.ChunkWaitMs, ct)) return; try { var newUrl = await _urlRefresher!(ct); @@ -647,19 +870,23 @@ private void TrimRamCache() { if (_chunks.Count <= _maxRamChunks) return; - int current = (int)(Volatile.Read(ref _position) / _chunkSize); - int keepStart = current - 2; - int keepEnd = current + _readAheadChunks * 2; + int playback = _lastPlaybackChunk; + int keepStart = playback - Config.ChunksToKeepBehind; + int keepEnd = playback + _readAheadChunks * Config.ChunksToKeepMultiplier; foreach (var key in _chunks.Keys) { - if (key < keepStart || key > keepEnd) + if (key >= _tailStartChunk) continue; + if (key >= keepStart && key <= keepEnd) continue; + + long start = (long)key * _chunkSize; + if (!_diskRanges.IsRangeComplete(start, Math.Min(start + _chunkSize, _contentLength))) + continue; + + if (_chunks.TryRemove(key, out var buf)) { - if (_chunks.TryRemove(key, out var buf)) - { - MemoryDiagnostics.UntrackBytes("Stream.RAMChunks", buf.Length); - ArrayPool.Shared.Return(buf); - } + MemoryDiagnostics.UntrackBytes("Stream.RAMChunks", buf.Length); + ArrayPool.Shared.Return(buf); } } } @@ -685,7 +912,11 @@ private async Task DiskWriterLoopAsync() { try { - if (_disposing || _cacheFile == null) continue; + if (_disposing || _cacheFile == null) + { + ArrayPool.Shared.Return(data); + continue; + } await _fileSemaphore.WaitAsync(_disposeCts.Token); try @@ -705,18 +936,17 @@ private async Task DiskWriterLoopAsync() { _downloadComplete = true; SaveRanges(); - bytesWritten = 0; - Log.Info($"[CacheStream] {_cacheId} fully cached!"); + Log.Debug($"[Buffer] FULLY CACHED ({_totalChunks} chunks)"); _cacheManager.TriggerCacheCompleted(_cacheId, _originalTrackId); } - else if (bytesWritten >= DiskSaveThresholdBytes) + else if (bytesWritten >= Config.SaveThresholdBytes) { SaveRanges(); bytesWritten = 0; } } catch (OperationCanceledException) { break; } - catch (Exception ex) { Log.Error($"[CacheStream] Disk write error: {ex.Message}"); } + catch (Exception ex) { Log.Error($"[Buffer] Disk write error: {ex.Message}"); } finally { ArrayPool.Shared.Return(data); } } } @@ -735,7 +965,8 @@ private async Task DiskWriterLoopAsync() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SaveRanges() { - try { StreamCacheManager.UpdateRanges(_cacheId, _diskRanges); } catch { } + try { StreamCacheManager.UpdateRanges(_cacheId, _diskRanges); } + catch { } } #endregion @@ -748,29 +979,21 @@ private void SaveRanges() if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { try { Directory.CreateDirectory(dir); } - catch (Exception ex) - { - Log.Error($"[CacheStream] Failed to create dir: {ex.Message}"); - return null; - } + catch { return null; } } - for (int attempt = 1; attempt <= MaxFileOpenRetries; attempt++) + for (int attempt = 1; attempt <= Config.MaxOpenRetries; attempt++) { try { return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 65536, FileOptions.Asynchronous | FileOptions.RandomAccess); } - catch (IOException) when (attempt < MaxFileOpenRetries) - { - Thread.Sleep(FileOpenRetryDelayMs * attempt); - } - catch (Exception ex) + catch (IOException) when (attempt < Config.MaxOpenRetries) { - Log.Error($"[CacheStream] File open error: {ex.Message}"); - return null; + Thread.Sleep(Config.RetryDelayBaseMs * attempt); } + catch { return null; } } return null; } @@ -785,6 +1008,8 @@ protected override void Dispose(bool disposing) _disposing = true; _disposed = true; + Log.Debug($"[Buffer] Disposing: hits={_cacheHits}, misses={_cacheMisses}, ignored={_vlcRequestsIgnored}"); + if (disposing) { MemoryDiagnostics.UntrackInstance("Stream.Active"); @@ -804,9 +1029,10 @@ protected override void Dispose(bool disposing) { try { - await Task.WhenAny(_diskWriterTask, Task.Delay(1000)); + await Task.WhenAny(_diskWriterTask, Task.Delay(Config.FlushTimeoutMs)); + await Task.WhenAny(_bufferExtenderTask, Task.Delay(500)); - await _fileSemaphore.WaitAsync(2000); + await _fileSemaphore.WaitAsync(Config.SemaphoreTimeoutMs); try { Try(() => _cacheFile?.Flush()); @@ -831,9 +1057,11 @@ protected override void Dispose(bool disposing) Try(_fileSemaphore.Dispose); Try(_downloadSemaphore.Dispose); + Try(_urgentDownloadSemaphore.Dispose); Try(_refreshLock.Dispose); Try(_downloadCts.Dispose); Try(_disposeCts.Dispose); + Try(_dataAvailable.Dispose); } }); } diff --git a/Core/Services/YoutubeProvider.cs b/Core/Services/YoutubeProvider.cs index 0d7e503..00c5fd2 100644 --- a/Core/Services/YoutubeProvider.cs +++ b/Core/Services/YoutubeProvider.cs @@ -47,9 +47,6 @@ private class StreamCacheEntry public event Action? OnStatusChanged; public event Action? OnError; - [GeneratedRegex("\"VISITOR_DATA\":\"([^\"]+)\"")] - private static partial Regex VisitorDataRegex(); - private static readonly Regex YoutubeVideoRegex = _YoutubeVideoRegex(); private static readonly Regex YoutubePlaylistRegex = _YoutubePlaylistRegex(); private static readonly Regex ValidYoutubeId = _ValidYoutubeId(); @@ -1060,65 +1057,7 @@ public async Task> GetTrendingAsync(int count = 20) } #endregion - #region Helpers - private static TrackInfo ConvertPlaylistToTrackInfo(Playlist playlist) - { - return new TrackInfo - { - Id = playlist.Id, - Title = playlist.Name, - Author = playlist.Author ?? "Unknown Playlist", - Url = playlist.Url, - Duration = TimeSpan.Zero, - ThumbnailUrl = playlist.ThumbnailUrl ?? "", - IsMusic = false, - }; - } - - private static TrackInfo ConvertToTrackInfo(Video video) - { - var thumb = video.Thumbnails.OrderByDescending(t => t.Resolution.Width).FirstOrDefault(); - return new TrackInfo - { - Id = $"yt_{video.Id.Value}", - Title = video.Title, - Author = video.Author.ChannelTitle, - Url = video.Url, - Duration = video.Duration ?? TimeSpan.Zero, - ThumbnailUrl = thumb?.Url ?? "" - }; - } - - private static TrackInfo ConvertSearchResultToTrackInfo(VideoSearchResult video) - { - var thumb = video.Thumbnails.OrderByDescending(t => t.Resolution.Width).Skip(1).FirstOrDefault(); - return new TrackInfo - { - Id = $"yt_{video.Id.Value}", - Title = video.Title, - Author = video.Author.ChannelTitle, - Url = video.Url, - Duration = video.Duration ?? TimeSpan.Zero, - ThumbnailUrl = thumb?.Url ?? "", - IsOfficialArtist = video.IsOfficialArtist, - IsMusic = video.IsMusic - }; - } - - private static TrackInfo ConvertPlaylistVideoToTrackInfo(PlaylistVideo video) - { - var thumb = video.Thumbnails.OrderByDescending(t => t.Resolution.Width).Skip(1).FirstOrDefault(); - return new TrackInfo - { - Id = $"yt_{video.Id.Value}", - Title = video.Title, - Author = video.Author.ChannelTitle, - Url = video.Url, - Duration = video.Duration ?? TimeSpan.Zero, - ThumbnailUrl = thumb?.Url ?? "" - }; - } private static string SanitizeFileName(string name) { diff --git a/Core/Youtube/YoutubeClient.cs b/Core/Youtube/YoutubeClient.cs index c89ff30..97bc4ac 100644 --- a/Core/Youtube/YoutubeClient.cs +++ b/Core/Youtube/YoutubeClient.cs @@ -62,39 +62,4 @@ public YoutubeClient() /// public void Dispose() => _youtubeHttp.Dispose(); - - // Вспомогательный метод для извлечения куки из контейнера в строку - private static string ConvertContainerToString(CookieContainer container) - { - // Собираем куки с основных доменов, чтобы сформировать полную строку - var uris = new[] - { - new Uri("https://youtube.com"), - new Uri("https://music.youtube.com"), - new Uri("https://google.com") - }; - - var uniqueCookies = new Dictionary(); - - foreach (var uri in uris) - { - var collection = container.GetCookies(uri); - foreach (Cookie cookie in collection) - { - if (!uniqueCookies.ContainsKey(cookie.Name)) - { - uniqueCookies[cookie.Name] = cookie.Value; - } - } - } - - var sb = new StringBuilder(); - foreach (var kvp in uniqueCookies) - { - if (sb.Length > 0) sb.Append("; "); - sb.Append($"{kvp.Key}={kvp.Value}"); - } - - return sb.ToString(); - } } \ No newline at end of file diff --git a/Features/Search/SearchViewModel.cs b/Features/Search/SearchViewModel.cs index 1d475ce..3f6c545 100644 --- a/Features/Search/SearchViewModel.cs +++ b/Features/Search/SearchViewModel.cs @@ -30,7 +30,6 @@ public sealed class SearchViewModel : PaginatedViewModel /// Конвертирует ContentSource в SearchFilter для YouTube API. /// @@ -303,7 +294,6 @@ private async Task ExecuteSearchAsync(bool forceNetwork) HasResults = false; _currentQuery = SearchQuery.Trim(); - _currentSource = Source; try { diff --git a/Features/Shell/MainWindow.axaml.cs b/Features/Shell/MainWindow.axaml.cs index 612b960..582441b 100644 --- a/Features/Shell/MainWindow.axaml.cs +++ b/Features/Shell/MainWindow.axaml.cs @@ -13,8 +13,6 @@ public partial class MainWindow : Window private Button? _maximizeButton; private Button? _closeButton; private Border? _dragArea; - private Border? _maximizeIcon; - private Grid? _restoreIcon; private CancellationTokenSource? _cleanupCts; @@ -39,8 +37,6 @@ private void InitializeComponent() _maximizeButton = this.FindControl - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - { - await Task.Delay(5000); // Подождать инициализации - // await Core.Dev.HlsFormatTester.RunAllTestsAsync(); - // await Core.Dev.HlsFormatTester.TestAllStreamsAsync(); - // await Core.Dev.HlsFormatTester.TestIosRangeLimitAsync(); - // await Core.Dev.HlsFormatTester.TestIosOpusRangeLimitAsync(); - // await Core.Dev.HlsFormatTester.TestHeadersBypassAsync(); - await Core.Dev.HlsFormatTester.TestSignatureRefreshAsync(); - Console.WriteLine("\n\n"); - - await Core.Dev.HlsFormatTester.TestCookieBypassAsync(); - Console.WriteLine("\n\n"); - - await Core.Dev.HlsFormatTester.TestParallelConnectionsAsync(); - }); -#endif - -#if AUDIO_TESTS - _ = Task.Run(async () => - { - await Task.Delay(5000); // Ждём инициализации - - var youtubeProvider = Services.GetRequiredService(); - - // ════════════════════════════════════════════════════════════════ - // ВСТАВЬ СЮДА ЛЮБУЮ YOUTUBE ССЫЛКУ ИЛИ VIDEO ID - // ════════════════════════════════════════════════════════════════ - - await Core.Audio.Tests.QuickAudioTester.PlayAsync( - "dQw4w9WgXcQ", // Rick Astley - Never Gonna Give You Up - youtubeProvider, - seconds: 30 // Играть 30 секунд - ); - - // Или полная ссылка: - // await QuickAudioTester.PlayAsync( - // "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - // youtubeProvider, - // seconds: 30 - // ); - - // Или YouTube Music: - // await QuickAudioTester.PlayAsync( - // "https://music.youtube.com/watch?v=dQw4w9WgXcQ", - // youtubeProvider, - // seconds: 30 - // ); - - // Или локальный файл: - // await QuickAudioTester.PlayFileAsync(@"D:\Music\song.webm", seconds: 30); - }); -#endif - BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } catch (Exception ex) From c9a0255a31b6c05140c7655f218ad3a98071c446 Mon Sep 17 00:00:00 2001 From: Scream034 Date: Wed, 18 Feb 2026 01:06:27 +0500 Subject: [PATCH 12/20] =?UTF-8?q?=D0=91=D0=B5=D0=B7=20VLC,=20=D0=B0=D1=83?= =?UTF-8?q?=D0=B4=D0=B8=D0=BE=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D0=B9=20NAudio=20(=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=20wnd-only),=20=D0=BF=D0=B0=D1=80=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D1=8B=20Webm/mp4/fmp4=20(Opus/AAC).=20SharpJaad.AA?= =?UTF-8?q?C,=20Concentus=20(Opus).=20HLS=20403.=20=D0=9D=D0=B5=D0=B1?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D1=88=D0=B8=D0=B5=20=D0=BA=D0=BE=D1=81=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D0=BD=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 13 + App.axaml.cs | 27 +- Core/Audio/AudioConstants.cs | 188 + Core/Audio/AudioPlayer.cs | 669 +- Core/Audio/AudioPlayerEvents.cs | 45 + Core/Audio/AudioSourceFactory.cs | 426 +- Core/Audio/AudioStreamInfo.cs | 26 + Core/Audio/Backends/NAudioBackend.cs | 185 +- Core/Audio/Backends/NullAudioBackend.cs | 5 + Core/Audio/Cache/AudioCacheManager.cs | 444 +- Core/Audio/Decoders/AacDecoder.cs | 295 +- Core/Audio/Decoders/OpusDecoder.cs | 31 +- Core/{ => Audio}/Helpers/CircularBuffer.cs | 33 +- Core/Audio/Helpers/ConcurrentBitArray.cs | 11 +- Core/{ => Audio}/Helpers/FrameBuffer.cs | 2 +- Core/{ => Audio}/Helpers/WebMParser.cs | 35 +- Core/Audio/Interfaces/IPlaybackBackend.cs | 36 +- Core/Audio/Parsers/Mp4ContainerParser.cs | 1942 ++-- Core/Audio/Parsers/WebMContainerParser.cs | 2 +- Core/Audio/Sources/CachingStreamSource.cs | 686 +- Core/Audio/Sources/HlsStreamSource.cs | 657 +- Core/Audio/Sources/LocalFileSource.cs | 175 +- Core/Audio/Sources/UniversalStreamSource.cs | 523 -- Core/Audio/Tests/QuickAudioTester.cs | 107 +- Core/{Services => Helpers}/LoggingHandler.cs | 17 +- Core/Models/RefCountedBitmap.cs | 9 +- Core/Models/TrackInfo.cs | 27 +- Core/Services/AudioEngine.cs | 1618 +--- Core/Services/CookieAuthService.cs | 1 - Core/Services/HlsStream.cs | 713 -- Core/Services/ImageCacheService.cs | 2 +- Core/Services/LibraryService.cs | 53 +- Core/Services/LocalizationService.cs | 14 +- Core/Services/MediaStreamBase.cs | 205 - Core/Services/MemoryDiagnostics.cs | 21 +- Core/Services/MemoryFirstCachingStream.cs | 804 -- Core/Services/SearchCacheService.cs | 3 +- Core/Services/StreamCacheManager.cs | 4 +- Core/Services/YoutubeProvider.cs | 13 +- Core/ViewModels/PaginatedViewModel.cs | 23 +- Core/ViewModels/ReorderableViewModel.cs | 90 +- Core/ViewModels/ViewModelBase.cs | 2 + .../Bridge/ChannelPlaylistsResponse.cs | 2 +- Core/Youtube/Bridge/SearchResponse.cs | 42 +- Core/Youtube/Bridge/VideoWatchPage.cs | 2 +- Core/Youtube/Channels/ChannelClient.cs | 2 +- Core/Youtube/Music/MusicModels.cs | 4 +- Core/Youtube/Utils/Http.cs | 4 +- Core/Youtube/YoutubeClient.cs | 1 - Features/Debug/DebugViewModel.cs | 36 +- Features/Player/PlayerBarView.axaml | 29 +- Features/Player/PlayerBarView.axaml.cs | 293 +- Features/Player/PlayerBarViewModel.cs | 170 +- Features/Settings/SettingsViewModel.cs | 4 +- Features/Shared/TrackItemViewModel.cs | 7 +- Features/Shell/MainWindow.axaml.cs | 2 +- Globals.cs | 14 +- LMP.csproj | 12 - Program.cs | 21 +- SharpJaad.AAC/AACException.cs | 24 + SharpJaad.AAC/ChannelConfiguration.cs | 15 + SharpJaad.AAC/Decoder.cs | 146 + SharpJaad.AAC/DecoderConfig.cs | 279 + SharpJaad.AAC/Error/BitsBuffer.cs | 124 + SharpJaad.AAC/Error/HCR.cs | 285 + SharpJaad.AAC/Error/RVLC.cs | 135 + SharpJaad.AAC/Error/RVLCTables.cs | 90 + SharpJaad.AAC/Filterbank/FFT.cs | 130 + SharpJaad.AAC/Filterbank/FFTables.cs | 1130 +++ SharpJaad.AAC/Filterbank/FilterBank.cs | 211 + SharpJaad.AAC/Filterbank/KBDWindows.cs | 2246 +++++ SharpJaad.AAC/Filterbank/MDCT.cs | 137 + SharpJaad.AAC/Filterbank/MDCTTables.cs | 1130 +++ SharpJaad.AAC/Filterbank/SineWindows.cs | 2246 +++++ SharpJaad.AAC/Gain/FFT.cs | 144 + SharpJaad.AAC/Gain/GCConstants.cs | 16 + SharpJaad.AAC/Gain/GainControl.cs | 360 + SharpJaad.AAC/Gain/IMDCT.cs | 232 + SharpJaad.AAC/Gain/IMDCTTables.cs | 304 + SharpJaad.AAC/Gain/IPQF.cs | 91 + SharpJaad.AAC/Gain/PQFTables.cs | 128 + SharpJaad.AAC/Gain/Windows.cs | 590 ++ SharpJaad.AAC/Huffman/Codebooks.cs | 1393 +++ SharpJaad.AAC/Huffman/HCB.cs | 13 + SharpJaad.AAC/Huffman/HuffmanDec.cs | 90 + SharpJaad.AAC/Profile.cs | 35 + SharpJaad.AAC/Ps/Filterbank.cs | 386 + SharpJaad.AAC/Ps/HuffmanTables.cs | 258 + SharpJaad.AAC/Ps/PS.cs | 1466 +++ SharpJaad.AAC/Ps/PSConstants.cs | 11 + SharpJaad.AAC/Ps/PSTables.cs | 620 ++ SharpJaad.AAC/SampleBuffer.cs | 68 + SharpJaad.AAC/SampleFrequency.cs | 280 + SharpJaad.AAC/Sbr/AnalysisFilterbank.cs | 105 + SharpJaad.AAC/Sbr/Constants.cs | 37 + SharpJaad.AAC/Sbr/DCT.cs | 408 + SharpJaad.AAC/Sbr/FBT.cs | 472 + SharpJaad.AAC/Sbr/FilterbankTable.cs | 328 + SharpJaad.AAC/Sbr/HFAdjustment.cs | 503 + SharpJaad.AAC/Sbr/HFGeneration.cs | 346 + SharpJaad.AAC/Sbr/HuffmanTables.cs | 629 ++ SharpJaad.AAC/Sbr/NoiseEnvelope.cs | 499 + SharpJaad.AAC/Sbr/NoiseTable.cs | 521 ++ SharpJaad.AAC/Sbr/SBR.cs | 1477 +++ SharpJaad.AAC/Sbr/SynthesisFilterbank.cs | 1044 +++ SharpJaad.AAC/Sbr/TFGrid.cs | 158 + SharpJaad.AAC/Syntax/BitStream.cs | 227 + SharpJaad.AAC/Syntax/CCE.cs | 191 + SharpJaad.AAC/Syntax/CPE.cs | 95 + SharpJaad.AAC/Syntax/Constants.cs | 30 + SharpJaad.AAC/Syntax/DSE.cs | 24 + SharpJaad.AAC/Syntax/Element.cs | 43 + SharpJaad.AAC/Syntax/FIL.cs | 173 + SharpJaad.AAC/Syntax/ICSInfo.cs | 206 + SharpJaad.AAC/Syntax/ICStream.cs | 359 + SharpJaad.AAC/Syntax/IQTable.cs | 8199 +++++++++++++++++ SharpJaad.AAC/Syntax/PCE.cs | 179 + SharpJaad.AAC/Syntax/SCE_LFE.cs | 23 + SharpJaad.AAC/Syntax/ScaleFactorBands.cs | 112 + SharpJaad.AAC/Syntax/ScaleFactorTable.cs | 94 + SharpJaad.AAC/Syntax/SyntacticElements.cs | 465 + SharpJaad.AAC/Tools/Arrays.cs | 18 + SharpJaad.AAC/Tools/ICPrediction.cs | 162 + SharpJaad.AAC/Tools/IS.cs | 56 + SharpJaad.AAC/Tools/ISScaleTable.cs | 263 + SharpJaad.AAC/Tools/LTPrediction.cs | 179 + SharpJaad.AAC/Tools/MS.cs | 42 + SharpJaad.AAC/Tools/MSMask.cs | 10 + SharpJaad.AAC/Tools/TNS.cs | 63 + SharpJaad.AAC/Tools/TNSTables.cs | 31 + SharpJaad.AAC/Transport/ADIFHeader.cs | 67 + UI/Controls/TrackListControl.axaml.cs | 44 +- 132 files changed, 37421 insertions(+), 6101 deletions(-) create mode 100644 .editorconfig create mode 100644 Core/Audio/AudioConstants.cs create mode 100644 Core/Audio/AudioPlayerEvents.cs create mode 100644 Core/Audio/AudioStreamInfo.cs rename Core/{ => Audio}/Helpers/CircularBuffer.cs (88%) rename Core/{ => Audio}/Helpers/FrameBuffer.cs (98%) rename Core/{ => Audio}/Helpers/WebMParser.cs (95%) delete mode 100644 Core/Audio/Sources/UniversalStreamSource.cs rename Core/{Services => Helpers}/LoggingHandler.cs (89%) delete mode 100644 Core/Services/HlsStream.cs delete mode 100644 Core/Services/MediaStreamBase.cs delete mode 100644 Core/Services/MemoryFirstCachingStream.cs create mode 100644 SharpJaad.AAC/AACException.cs create mode 100644 SharpJaad.AAC/ChannelConfiguration.cs create mode 100644 SharpJaad.AAC/Decoder.cs create mode 100644 SharpJaad.AAC/DecoderConfig.cs create mode 100644 SharpJaad.AAC/Error/BitsBuffer.cs create mode 100644 SharpJaad.AAC/Error/HCR.cs create mode 100644 SharpJaad.AAC/Error/RVLC.cs create mode 100644 SharpJaad.AAC/Error/RVLCTables.cs create mode 100644 SharpJaad.AAC/Filterbank/FFT.cs create mode 100644 SharpJaad.AAC/Filterbank/FFTables.cs create mode 100644 SharpJaad.AAC/Filterbank/FilterBank.cs create mode 100644 SharpJaad.AAC/Filterbank/KBDWindows.cs create mode 100644 SharpJaad.AAC/Filterbank/MDCT.cs create mode 100644 SharpJaad.AAC/Filterbank/MDCTTables.cs create mode 100644 SharpJaad.AAC/Filterbank/SineWindows.cs create mode 100644 SharpJaad.AAC/Gain/FFT.cs create mode 100644 SharpJaad.AAC/Gain/GCConstants.cs create mode 100644 SharpJaad.AAC/Gain/GainControl.cs create mode 100644 SharpJaad.AAC/Gain/IMDCT.cs create mode 100644 SharpJaad.AAC/Gain/IMDCTTables.cs create mode 100644 SharpJaad.AAC/Gain/IPQF.cs create mode 100644 SharpJaad.AAC/Gain/PQFTables.cs create mode 100644 SharpJaad.AAC/Gain/Windows.cs create mode 100644 SharpJaad.AAC/Huffman/Codebooks.cs create mode 100644 SharpJaad.AAC/Huffman/HCB.cs create mode 100644 SharpJaad.AAC/Huffman/HuffmanDec.cs create mode 100644 SharpJaad.AAC/Profile.cs create mode 100644 SharpJaad.AAC/Ps/Filterbank.cs create mode 100644 SharpJaad.AAC/Ps/HuffmanTables.cs create mode 100644 SharpJaad.AAC/Ps/PS.cs create mode 100644 SharpJaad.AAC/Ps/PSConstants.cs create mode 100644 SharpJaad.AAC/Ps/PSTables.cs create mode 100644 SharpJaad.AAC/SampleBuffer.cs create mode 100644 SharpJaad.AAC/SampleFrequency.cs create mode 100644 SharpJaad.AAC/Sbr/AnalysisFilterbank.cs create mode 100644 SharpJaad.AAC/Sbr/Constants.cs create mode 100644 SharpJaad.AAC/Sbr/DCT.cs create mode 100644 SharpJaad.AAC/Sbr/FBT.cs create mode 100644 SharpJaad.AAC/Sbr/FilterbankTable.cs create mode 100644 SharpJaad.AAC/Sbr/HFAdjustment.cs create mode 100644 SharpJaad.AAC/Sbr/HFGeneration.cs create mode 100644 SharpJaad.AAC/Sbr/HuffmanTables.cs create mode 100644 SharpJaad.AAC/Sbr/NoiseEnvelope.cs create mode 100644 SharpJaad.AAC/Sbr/NoiseTable.cs create mode 100644 SharpJaad.AAC/Sbr/SBR.cs create mode 100644 SharpJaad.AAC/Sbr/SynthesisFilterbank.cs create mode 100644 SharpJaad.AAC/Sbr/TFGrid.cs create mode 100644 SharpJaad.AAC/Syntax/BitStream.cs create mode 100644 SharpJaad.AAC/Syntax/CCE.cs create mode 100644 SharpJaad.AAC/Syntax/CPE.cs create mode 100644 SharpJaad.AAC/Syntax/Constants.cs create mode 100644 SharpJaad.AAC/Syntax/DSE.cs create mode 100644 SharpJaad.AAC/Syntax/Element.cs create mode 100644 SharpJaad.AAC/Syntax/FIL.cs create mode 100644 SharpJaad.AAC/Syntax/ICSInfo.cs create mode 100644 SharpJaad.AAC/Syntax/ICStream.cs create mode 100644 SharpJaad.AAC/Syntax/IQTable.cs create mode 100644 SharpJaad.AAC/Syntax/PCE.cs create mode 100644 SharpJaad.AAC/Syntax/SCE_LFE.cs create mode 100644 SharpJaad.AAC/Syntax/ScaleFactorBands.cs create mode 100644 SharpJaad.AAC/Syntax/ScaleFactorTable.cs create mode 100644 SharpJaad.AAC/Syntax/SyntacticElements.cs create mode 100644 SharpJaad.AAC/Tools/Arrays.cs create mode 100644 SharpJaad.AAC/Tools/ICPrediction.cs create mode 100644 SharpJaad.AAC/Tools/IS.cs create mode 100644 SharpJaad.AAC/Tools/ISScaleTable.cs create mode 100644 SharpJaad.AAC/Tools/LTPrediction.cs create mode 100644 SharpJaad.AAC/Tools/MS.cs create mode 100644 SharpJaad.AAC/Tools/MSMask.cs create mode 100644 SharpJaad.AAC/Tools/TNS.cs create mode 100644 SharpJaad.AAC/Tools/TNSTables.cs create mode 100644 SharpJaad.AAC/Transport/ADIFHeader.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4bc7be6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Пометить все файлы в папке как "сгенерированный код" +# Компилятор обычно игнорирует варнинги в таких файлах +[SharpJaad.AAC/**.cs] +generated_code = true + +# Или точечно отключить конкретные правила для этой папки +[SharpJaad.AAC/**.cs] +dotnet_diagnostic.CS8618.severity = none +dotnet_diagnostic.CS8600.severity = none +dotnet_diagnostic.CS8602.severity = none +dotnet_diagnostic.CS8604.severity = none +dotnet_diagnostic.CS8625.severity = none +dotnet_diagnostic.CS0420.severity = none \ No newline at end of file diff --git a/App.axaml.cs b/App.axaml.cs index ecc51a1..b50a9eb 100644 --- a/App.axaml.cs +++ b/App.axaml.cs @@ -5,6 +5,8 @@ using LMP.Features.Shell; using LMP.Core.Services; using AsyncImageLoader; +using LMP.Core.Audio; +using LMP.Core.Audio.Cache; namespace LMP; @@ -25,24 +27,26 @@ public override void OnFrameworkInitializationCompleted() var themeManager = Program.Services.GetRequiredService(); themeManager.LoadAndApplyThemeOnStartup(); - // 1. Get library service (don't await initialization here!) + // 1. Get library service var library = Program.Services.GetRequiredService(); var registry = Program.Services.GetRequiredService(); var cacheManager = Program.Services.GetRequiredService(); - - // Передаем CacheManager в Registry ДО начала загрузки данных (HydrateAsync) registry.CacheManager = cacheManager; - // 2. Initialize localization with default, will update after DB init + // 2. Initialize localization with default LocalizationService.Instance.Initialize("en"); - // 3. Start Memory Monitor + // 3. Initialize GLOBAL audio cache + var audioCacheManager = Program.Services.GetRequiredService(); + AudioSourceFactory.InitializeGlobalCache(audioCacheManager); + + // 4. Start Memory Monitor MemoryDiagnostics.Instance.OnMemoryWarning += Log.Warn; MemoryDiagnostics.Instance.WarningThresholdMb = 400; MemoryDiagnostics.Instance.CriticalThresholdMb = 450; - // 4. Create UI FIRST + // 5. Create UI var mainWindowVM = Program.Services.GetRequiredService(); desktop.MainWindow = new MainWindow { @@ -54,18 +58,20 @@ public override void OnFrameworkInitializationCompleted() var imageCache = Program.Services.GetRequiredService(); ImageLoader.AsyncImageLoader = new CachedImageLoader(imageCache); - // 5. Initialize library and other services IN BACKGROUND + // 6. Initialize services IN BACKGROUND _ = InitializeServicesAsync(library); - // 6. Cleanup on shutdown + // 7. Cleanup on shutdown desktop.ShutdownRequested += async (_, e) => { try { - // Логируем финальный отчёт MemoryDiagnostics.LogReport(); - MemoryDiagnostics.Instance.Dispose(); + + // Dispose audio cache + await audioCacheManager.DisposeAsync(); + await library.DisposeAsync(); } catch (Exception ex) @@ -77,7 +83,6 @@ public override void OnFrameworkInitializationCompleted() #if DEBUG desktop.MainWindow.AttachDevTools(); - // Debug window desktop.MainWindow.KeyDown += (s, e) => { if (e.Key == Avalonia.Input.Key.F9) diff --git a/Core/Audio/AudioConstants.cs b/Core/Audio/AudioConstants.cs new file mode 100644 index 0000000..8ba492b --- /dev/null +++ b/Core/Audio/AudioConstants.cs @@ -0,0 +1,188 @@ +namespace LMP.Core.Audio; + +/// +/// Централизованные константы аудио системы. +/// Все магические числа в одном месте для удобства настройки и zero duplication. +/// +public static class AudioConstants +{ + // CHUNK SETTINGS — Управление сегментацией данных + + /// Размер чанка для кэширования (64KB = оптимум для HTTP Range + минимум аллокаций). + public const int ChunkSize = 64 * 1024; + + /// Максимум чанков в RAM (32 × 64KB = 2MB RAM на трек). + public const int MaxRamChunks = 32; + + /// Расстояние от текущей позиции для eviction чанков из RAM. + public const int RamEvictionDistance = 10; + + // PRELOAD SETTINGS — Стратегия упреждающей загрузки + + /// Чанков загружать перед стартом воспроизведения (300ms @ 128kbps). + public const int InitialChunksToLoad = 3; + + /// Чанков держать впереди от текущей позиции (adaptive buffering). + public const int PreloadAheadChunks = 4; + + /// Чанков загружать при seek (instant seek UX). + public const int SeekPreloadChunks = 2; + + /// Интервал проверки preload loop (ms). + public const int PreloadIntervalMs = 1000; + + /// Максимум параллельных загрузок чанков (баланс скорость/RAM). + public const int MaxConcurrentDownloads = 3; + + // DOWNLOAD TIMEOUTS — HTTP операции + + /// Таймаут загрузки одного чанка (15s = mobile-friendly). + public const int DownloadTimeoutMs = 15000; + + /// Таймаут ожидания слота загрузки (500ms = non-blocking). + public const int DownloadSlotTimeoutMs = 500; + + // BACKGROUND DOWNLOAD LIMITS — Экономия сети + + /// Циклов простоя перед фоновой докачкой (5 × 1s = 5s idle). + public const int BackgroundFillIdleCycles = 5; + + /// Пауза между фоновыми загрузками (5s = gentle network usage). + public const int BackgroundFillIntervalMs = 5000; + + /// Максимум чанков для фоновой докачки за сессию (0 = unlimited). + public const int MaxBackgroundChunksPerSession = 50; + + /// Минимум буфера впереди для начала фоновой докачки. + public const int MinBufferAheadForBackgroundFill = 6; + + // DECODER SETTINGS — Параметры декодирования + + /// Sample rate по умолчанию (48kHz = industry standard). + public const int DefaultSampleRate = 48000; + + /// Количество каналов по умолчанию (stereo). + public const int DefaultChannels = 2; + + /// Буфер декодирования (samples, не байты). + public const int DecoderBufferFrames = 8192; + + /// Кадров пропустить после seek для Opus (pre-skip compensation). + public const int SkipFramesAfterSeekOpus = 2; + + /// Кадров пропустить после seek для AAC (encoder delay). + public const int SkipFramesAfterSeekAac = 5; + + /// Таймаут graceful shutdown декодера (ms). + public const int DecoderStopTimeoutMs = 500; + + // PLAYBACK BUFFER SETTINGS — PCM циклический буфер + + /// Размер PCM буфера в секундах (2s = smooth playback). + public const int BufferSizeSeconds = 2; + + /// Минимальный буфер для старта воспроизведения (ms). + public const int MinBufferMs = 300; + + /// Минимальный буфер для возобновления после seek (ms). + public const int MinSeekResumeBufferMs = 80; + + // POSITION REPORTING — UI обновления + + /// Интервал обновления позиции по умолчанию (ms). + public const int DefaultPositionUpdateIntervalMs = 200; + + /// Интервал проверки buffer state (ms). + public const int BufferStateUpdateIntervalMs = 500; + + // RETRY POLICY — Обработка ошибок + + /// Максимум попыток повтора операций. + public const int MaxRetryAttempts = 3; + + /// Задержка между попытками (ms). + public const int RetryDelayMs = 1000; + + // CACHE MANAGEMENT — Дисковый кэш + + /// Имя файла метаданных кэша. + public const string CacheMetadataFileName = "cache_index.json"; + + /// Расширение файлов кэша. + public const string CacheFileExtension = ".audio"; + + /// Интервал автосохранения индекса кэша (ms). + public const int CacheAutoSaveIntervalMs = 30000; + + /// Таймаут блокировки при сохранении индекса (ms). + public const int CacheSaveLockTimeoutMs = 100; + + /// Размер буфера для файловых операций (64KB). + public const int CacheFileBufferSize = 65536; + + /// Порог очистки кэша (80% от максимума). + public const double CacheCleanupThreshold = 0.8; + + /// Нормализация битрейта для кэш-ключей (kbps buckets). + public static int NormalizeBitrate(int bitrate) => bitrate switch + { + <= 0 => 0, // Неизвестный — не нормализуем + < 50 => 48, // ~48kbps (Opus low) + < 80 => 64, // ~64kbps (Opus medium-low) + < 110 => 96, // ~96kbps (AAC standard) + < 140 => 128, // ~128kbps (Opus standard) + < 180 => 160, // ~160kbps (Opus high / AAC high) + < 260 => 256, // ~256kbps (Opus very high) + _ => 320 // 320kbps+ + }; + + // HLS SETTINGS — Специфичные для HLS потоков + + /// Сегментов для упреждающей загрузки в HLS. + public const int HlsPrefetchSegments = 3; + + /// Длительность AAC фрейма (ms) для HLS (~1024 samples @ 44.1kHz). + public const int HlsAacFrameDurationMs = 23; + + /// Интервал проверки HLS prefetch (ms). + public const int HlsPrefetchIntervalMs = 500; + + // FORMAT DETECTION — Magic bytes для форматов + + /// Размер заголовка для определения формата (bytes). + public const int FormatDetectionHeaderSize = 12; + + // WebM: 0x1A 0x45 0xDF 0xA3 + public static ReadOnlySpan WebMMagic => [0x1A, 0x45, 0xDF, 0xA3]; + + // MP4: 'ftyp' at offset 4 + public static ReadOnlySpan Mp4FtypMagic => "ftyp"u8; + + // Ogg: 'OggS' + public static ReadOnlySpan OggMagic => "OggS"u8; + + // SAMPLE RATE TABLE — AAC decoder + + private static readonly int[] AacSampleRates = + [ + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000 + ]; + + /// Получить sample rate по индексу (AAC ASC). + public static int GetAacSampleRate(int index) => + (uint)index < (uint)AacSampleRates.Length ? AacSampleRates[index] : DefaultSampleRate; + + /// Получить количество каналов по конфигу (AAC ASC). + public static int GetAacChannels(int channelConfig) => channelConfig switch + { + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 6 => 6, + 7 => 8, + _ => DefaultChannels + }; +} \ No newline at end of file diff --git a/Core/Audio/AudioPlayer.cs b/Core/Audio/AudioPlayer.cs index e634869..e53f7f8 100644 --- a/Core/Audio/AudioPlayer.cs +++ b/Core/Audio/AudioPlayer.cs @@ -4,90 +4,64 @@ using LMP.Core.Audio.Backends; using LMP.Core.Audio.Cache; using LMP.Core.Audio.Decoders; +using LMP.Core.Audio.Helpers; using LMP.Core.Audio.Http; using LMP.Core.Audio.Interfaces; -using LMP.Core.Audio.Sources; using LMP.Core.Exceptions; -using LMP.Core.Helpers; +using static LMP.Core.Audio.AudioConstants; namespace LMP.Core.Audio; -/// -/// Опции конфигурации аудиоплеера. -/// public sealed class AudioPlayerOptions { - /// Callback для обновления протухших ссылок (YouTube/CDN). public Func>? UrlRefreshCallback { get; init; } - - /// Интервал обновления события PositionChanged. - public TimeSpan PositionUpdateInterval { get; init; } = TimeSpan.FromMilliseconds(200); - - /// Максимальное количество попыток переподключения при разрыве сети. - public int MaxRetryAttempts { get; init; } = 3; - - /// Задержка между попытками переподключения. - public TimeSpan RetryDelay { get; init; } = TimeSpan.FromSeconds(1); - - /// Использовать NullBackend (без звука) для тестов или headless режима. + public TimeSpan PositionUpdateInterval { get; init; } = TimeSpan.FromMilliseconds(DefaultPositionUpdateIntervalMs); + public int MaxRetryAttempts { get; init; } = AudioConstants.MaxRetryAttempts; + public TimeSpan RetryDelay { get; init; } = TimeSpan.FromMilliseconds(RetryDelayMs); public bool UseNullBackend { get; init; } } -/// -/// Высокопроизводительный аудио-плеер. -/// Координирует работу Source -> Decoder -> Buffer -> Backend. -/// -public sealed class AudioPlayer : IAudioPlayer +public sealed class AudioPlayer : IAsyncDisposable, IDisposable { - #region Constants - - private const int DefaultSampleRate = 48000; - private const int DefaultChannels = 2; - private const int BufferSizeSeconds = 2; // 2 секунды PCM буфера (достаточно для стабильности) - private const int MinBufferMs = 500; // Минимальный буфер для старта (0.5с) - private const int StopTimeoutMs = 2000; // Таймаут ожидания остановки потоков - private const int DecoderBufferFrames = 8192;// Размер буфера декодирования (фреймов) - - #endregion - #region Fields private readonly AudioPlayerOptions _options; - private readonly AudioCacheManager? _cacheManager; + private readonly AudioPlayerEvents _events = new(); private readonly SemaphoreSlim _stateLock = new(1, 1); + private readonly SemaphoreSlim _seekLock = new(1, 1); - // Pipeline Components private IAudioSource? _source; private IAudioDecoder? _decoder; private IPlaybackBackend? _backend; - // Data Buffers private CircularBuffer? _pcmBuffer; private float[]? _decodeBuffer; - // State private volatile PlaybackState _state = PlaybackState.Stopped; private volatile float _volume = 1.0f; private volatile bool _disposed; - - // Timekeeping - private long _durationMs; - private long _playedSamples; // Количество семплов, переданных в backend (basis for Position) - // Context + private int _seekVersion; + private int _skipFramesCounter; + + private AudioStreamInfo _currentStreamInfo = AudioStreamInfo.Empty; + private long _playedSamples; private string? _currentTrackId; - private string? _currentUrl; + private int _currentBitrateHint; - // Tasks & Timers private CancellationTokenSource? _playbackCts; + private CancellationTokenSource? _decoderCts; private Task? _decoderTask; private Timer? _positionTimer; + private Timer? _bufferTimer; #endregion #region Properties - /// + public AudioPlayerEvents Events => _events; + public AudioStreamInfo StreamInfo => _currentStreamInfo; + public float Volume { get => _volume; @@ -95,15 +69,10 @@ public float Volume { _volume = Math.Clamp(value, 0f, 2f); if (_backend != null) - { - // Передаем в backend громкость <= 1.0 (аппаратное/драйверное управление). - // Если volume > 1.0, усиление применяется программно в AudioCallback. _backend.Volume = Math.Min(_volume, 1f); - } } } - /// public TimeSpan Position { get @@ -111,95 +80,100 @@ public TimeSpan Position if (_decoder == null || _decoder.SampleRate <= 0 || _backend == null) return TimeSpan.Zero; - // Алгоритм точного времени: - // 1. Берем сколько всего семплов мы "скормили" бэкенду (_playedSamples). - // 2. Вычитаем то, что бэкенд еще держит в своем внутреннем буфере (BufferedSamples). - // 3. Получаем реальное количество семплов, ушедших на динамики. - long totalWritten = Volatile.Read(ref _playedSamples); int backendBuffered = _backend.BufferedSamples; long heardSamples = Math.Max(0, totalWritten - backendBuffered); double seconds = (double)heardSamples / (_decoder.SampleRate * _decoder.Channels); + + var duration = Duration; + if (duration.TotalSeconds > 0 && seconds > duration.TotalSeconds) + seconds = duration.TotalSeconds; + return TimeSpan.FromSeconds(seconds); } } - /// - public TimeSpan Duration => TimeSpan.FromMilliseconds(Volatile.Read(ref _durationMs)); - - /// + public TimeSpan Duration => TimeSpan.FromMilliseconds(_currentStreamInfo.DurationMs); public PlaybackState State => _state; - public double BufferProgress => _source?.BufferProgress ?? 0; - public bool IsFullyBuffered => _source?.IsFullyBuffered ?? false; - #endregion - #region Events + #region Legacy Events + + public event Action? PositionChanged + { + add => _events.PositionChanged += value; + remove => _events.PositionChanged -= value; + } + + public event Action? StateChanged + { + add => _events.StateChanged += value; + remove => _events.StateChanged -= value; + } + + public event Action? TrackEnded + { + add => _events.TrackEnded += value; + remove => _events.TrackEnded -= value; + } - public event Action? PositionChanged; - public event Action? StateChanged; - public event Action? TrackEnded; public event Action? ErrorOccurred; #endregion - public AudioPlayer(AudioPlayerOptions? options = null, AudioCacheManager? cacheManager = null) + public AudioPlayer(AudioPlayerOptions? options = null) { _options = options ?? new AudioPlayerOptions(); - _cacheManager = cacheManager; + _events.ErrorOccurred += err => ErrorOccurred?.Invoke(err.Exception ?? new Exception(err.Message)); } #region Public Methods - /// - public async Task PlayAsync(string url, string? trackId = null, CancellationToken ct = default) + /// + /// Начинает воспроизведение. + /// + /// URL потока. + /// ID трека. + /// Подсказка битрейта (kbps). 0 = автоопределение. + /// Токен отмены. + public async Task PlayAsync(string url, string? trackId = null, int bitrateHint = 0, CancellationToken ct = default) { ObjectDisposedException.ThrowIf(_disposed, this); await _stateLock.WaitAsync(ct); try { - // Сброс предыдущего состояния await StopInternalAsync(); - _currentUrl = url; _currentTrackId = trackId; + _currentBitrateHint = bitrateHint; + Interlocked.Exchange(ref _skipFramesCounter, 0); + _currentStreamInfo = AudioStreamInfo.Empty; SetState(PlaybackState.Loading); - // Инициализация пайплайна - await InitializePlaybackAsync(url, ct); + await InitializePipelineAsync(url, ct); - // Запуск декодера _playbackCts = CancellationTokenSource.CreateLinkedTokenSource(ct); - _decoderTask = Task.Run(() => DecoderLoopAsync(_playbackCts.Token), _playbackCts.Token); + StartDecoderLoop(); - // Ожидание предварительной буферизации SetState(PlaybackState.Buffering); - await WaitForBufferAsync(_playbackCts.Token); + await WaitForInitialBufferAsync(_playbackCts.Token); - // Старт воспроизведения _backend!.Start(); - // Таймер UI обновлений - _positionTimer = new Timer( - _ => PositionChanged?.Invoke(Position), - null, - 0, - (int)_options.PositionUpdateInterval.TotalMilliseconds); + StartTimers(); SetState(PlaybackState.Playing); - Log.Info($"[AudioPlayer] Started track: {trackId ?? "unknown"}"); + Log.Info($"[AudioPlayer] Started track: {trackId ?? "unknown"}, bitrate hint={bitrateHint}kbps"); } catch (Exception ex) { Log.Error($"[AudioPlayer] Play failed: {ex.Message}", ex); SetState(PlaybackState.Error); - ErrorOccurred?.Invoke(ex); - - // Чистим ресурсы при ошибке старта + _events.RaiseError(new AudioPlayerError(ex.Message, ex)); await StopInternalAsync(); throw; } @@ -209,7 +183,6 @@ public async Task PlayAsync(string url, string? trackId = null, CancellationToke } } - /// public void Pause() { if (_state != PlaybackState.Playing) return; @@ -217,7 +190,6 @@ public void Pause() SetState(PlaybackState.Paused); } - /// public void Resume() { if (_state != PlaybackState.Paused) return; @@ -225,21 +197,16 @@ public void Resume() SetState(PlaybackState.Playing); } - /// public void Stop() { if (_state == PlaybackState.Stopped) return; - // Fire-and-forget, но безопасно _ = StopAsync(); } - /// - /// Асинхронная остановка с ожиданием завершения потоков. - /// public async Task StopAsync() { if (_state == PlaybackState.Stopped) return; - + await _stateLock.WaitAsync(); try { @@ -252,100 +219,174 @@ public async Task StopAsync() Log.Info("[AudioPlayer] Stopped"); } - /// public async ValueTask SeekAsync(TimeSpan position, CancellationToken ct = default) { - ObjectDisposedException.ThrowIf(_disposed, this); + if (_disposed) return; - if (_source == null || !_source.CanSeek || _decoder == null) return; + if (_source == null || !_source.CanSeek || _decoder == null) + return; + + if (!await _seekLock.WaitAsync(0, ct)) + return; - await _stateLock.WaitAsync(ct); try { - var wasPlaying = _state == PlaybackState.Playing; - SetState(PlaybackState.Buffering); + int currentSeekVersion = Interlocked.Increment(ref _seekVersion); + bool wasPlaying = _state == PlaybackState.Playing; + long posMs = (long)position.TotalMilliseconds; + + await StopDecoderLoopGracefullyAsync(); + + if (_disposed || Interlocked.CompareExchange(ref _seekVersion, 0, 0) != currentSeekVersion) + return; - // 1. Останавливаем вывод звука и чистим буферы _backend?.Stop(); + await Task.Delay(20, ct); + _backend?.Flush(); _pcmBuffer?.Clear(); - // 2. Выполняем seek на источнике - long posMs = (long)position.TotalMilliseconds; + int skipFrames = _source.Codec == AudioCodec.Aac + ? SkipFramesAfterSeekAac + : SkipFramesAfterSeekOpus; + Interlocked.Exchange(ref _skipFramesCounter, skipFrames); + bool success = await _source.SeekAsync(posMs, ct); + if (_disposed || Interlocked.CompareExchange(ref _seekVersion, 0, 0) != currentSeekVersion) + return; + if (success) { - // 3. Атомарно обновляем счетчик семплов для корректного отображения времени - // Position = (PlayedSamples - Buffered) / Rate - // Устанавливаем PlayedSamples так, чтобы формула вернула запрошенное время - long newSampleCount = (long)(position.TotalSeconds * _decoder.SampleRate * _decoder.Channels); - Volatile.Write(ref _playedSamples, newSampleCount); + long targetSamples = (long)(posMs / 1000.0 * _decoder.SampleRate * _decoder.Channels); + Volatile.Write(ref _playedSamples, targetSamples); - Log.Debug($"[AudioPlayer] Seeked to {posMs}ms"); - } + StartDecoderLoop(); - // 4. Возобновляем воспроизведение - if (wasPlaying) - { - await WaitForBufferAsync(ct); - _backend?.Start(); - SetState(PlaybackState.Playing); - } - else - { - SetState(PlaybackState.Paused); - } + if (wasPlaying && !_disposed) + { + try + { + await WaitForMinimalBufferAsync(ct); + } + catch { } - // Мгновенное обновление UI - PositionChanged?.Invoke(position); + if (!_disposed) + { + _backend?.Start(); + SetState(PlaybackState.Playing); + } + } + else if (!_disposed) + { + SetState(PlaybackState.Paused); + } + + _events.RaiseSeekCompleted(position); + Log.Debug($"[AudioPlayer] Seeked to {posMs}ms"); + } + } + catch (OperationCanceledException) { } + catch (ObjectDisposedException) { } + catch (Exception ex) + { + Log.Warn($"[AudioPlayer] Seek error: {ex.Message}"); } finally { - _stateLock.Release(); + try { _seekLock.Release(); } + catch (ObjectDisposedException) { } } } #endregion - #region Internal Logic + #region Pipeline Initialization - private async Task InitializePlaybackAsync(string url, CancellationToken ct) + private async Task InitializePipelineAsync(string url, CancellationToken ct) { - // 1. Создание источника (с кэшем или без) - if (_cacheManager != null && !string.IsNullOrEmpty(_currentTrackId)) - { - _source = await AudioSourceFactory.CreateWithCacheAsync( - url, _currentTrackId, SharedHttpClient.Instance, _cacheManager, CreateUrlRefresher(), ct); - } - else - { - _source = await AudioSourceFactory.CreateAsync( - url, SharedHttpClient.Instance, CreateUrlRefresher(), _currentTrackId, ct); - } + _source = await AudioSourceFactory.CreateAsync( + url, + SharedHttpClient.Instance, + CreateUrlRefresher(), + _currentTrackId, + _currentBitrateHint, + ct); if (!await _source.InitializeAsync(ct)) throw new AudioSourceException("Failed to initialize audio source"); - // 2. Создание декодера _decoder = CreateDecoder(_source); - Volatile.Write(ref _durationMs, _source.DurationMs); - // 3. Создание буферов - // 2 секунды буфера достаточно для компенсации джиттера сети, не создавая большой задержки + _currentStreamInfo = BuildStreamInfo(_source, _currentTrackId); + _events.RaiseStreamInfo(_currentStreamInfo); + int bufferSize = _decoder.SampleRate * _decoder.Channels * BufferSizeSeconds; _pcmBuffer = new CircularBuffer(bufferSize); _decodeBuffer = ArrayPool.Shared.Rent(DecoderBufferFrames * _decoder.Channels); - // 4. Создание бэкенда _backend = CreateBackend(); - _backend.Volume = Math.Min(_volume, 1f); - - // Инициализация бэкенда с передачей callback-функции _backend.Initialize(_decoder.SampleRate, _decoder.Channels, AudioCallback); + _backend.Volume = Math.Min(_volume, 1f); - Log.Debug($"[AudioPlayer] Initialized: {_source.Codec} -> PCM {_decoder.SampleRate}Hz -> {_backend.Name}"); + Log.Debug($"[AudioPlayer] Initialized: {_currentStreamInfo.FormatDisplay}"); } + private AudioStreamInfo BuildStreamInfo(IAudioSource source, string? trackId) + { + CacheEntry? cacheEntry = null; + string container = ""; + int bitrate = 0; + + if (!string.IsNullOrEmpty(trackId)) + { + var cached = AudioSourceFactory.FindAnyCachedTrack(trackId); + if (cached != null) + { + cacheEntry = cached.Value.Entry; + container = cacheEntry.Format.ToString(); + bitrate = cacheEntry.Bitrate; + } + } + + if (source is Sources.CachingStreamSource cachingSource) + { + bitrate = cachingSource.Bitrate; + + if (string.IsNullOrEmpty(container)) + { + container = source.Codec switch + { + AudioCodec.Opus => "WebM", + AudioCodec.Aac => "Mp4", + _ => "Unknown" + }; + } + } + + // bitrateHint имеет приоритет + if (_currentBitrateHint > 0) + bitrate = _currentBitrateHint; + + return new AudioStreamInfo + { + TrackId = trackId ?? "", + Container = container, + Codec = source.Codec.ToString(), + Bitrate = bitrate > 0 ? bitrate : EstimateBitrate(source), + SampleRate = source.SampleRate > 0 ? source.SampleRate : DefaultSampleRate, + Channels = source.Channels > 0 ? source.Channels : DefaultChannels, + DurationMs = source.DurationMs, + IsFromCache = cacheEntry?.IsComplete ?? false + }; + } + + private static int EstimateBitrate(IAudioSource source) => source.Codec switch + { + AudioCodec.Opus => 128, + AudioCodec.Aac => 96, + _ => 128 + }; + private IAudioDecoder CreateDecoder(IAudioSource source) { int rate = source.SampleRate > 0 ? source.SampleRate : DefaultSampleRate; @@ -362,108 +403,183 @@ private IAudioDecoder CreateDecoder(IAudioSource source) private static AacDecoder CreateAacDecoder(IAudioSource source, int rate, int ch) { var dec = new AacDecoder(rate, ch); - if (source.DecoderConfig != null) dec.Initialize(source.DecoderConfig); + if (source.DecoderConfig != null) + dec.Initialize(source.DecoderConfig); return dec; } private IPlaybackBackend CreateBackend() { - if (_options.UseNullBackend) return new NullAudioBackend(); - - try - { - return new NAudioBackend(); + if (_options.UseNullBackend) + return new NullAudioBackend(); + + try + { + return new NAudioBackend(); } catch (Exception ex) { - Log.Warn($"[AudioPlayer] NAudio init failed: {ex.Message}, falling back to NullBackend"); + Log.Warn($"[AudioPlayer] NAudio init failed: {ex.Message}, using NullBackend"); return new NullAudioBackend(); } } + #endregion + + #region Decoder Loop + + private void StartDecoderLoop() + { + _decoderCts?.Dispose(); + + _decoderCts = _playbackCts != null + ? CancellationTokenSource.CreateLinkedTokenSource(_playbackCts.Token) + : new CancellationTokenSource(); + + _decoderTask = Task.Run( + () => DecoderLoopAsync(_decoderCts.Token), + _decoderCts.Token); + } + + private async Task StopDecoderLoopGracefullyAsync() + { + if (_decoderCts == null || _decoderTask == null) + return; + + _decoderCts.Cancel(); + + try + { + await _decoderTask.WaitAsync(TimeSpan.FromMilliseconds(DecoderStopTimeoutMs)); + } + catch (TimeoutException) + { + Log.Warn("[AudioPlayer] Decoder loop graceful stop timeout"); + } + catch (OperationCanceledException) { } + catch (Exception ex) + { + Log.Warn($"[AudioPlayer] Decoder loop stop error: {ex.Message}"); + } + + _decoderCts.Dispose(); + _decoderCts = null; + _decoderTask = null; + } + private async Task DecoderLoopAsync(CancellationToken ct) { - if (_source == null || _decoder == null || _pcmBuffer == null || _decodeBuffer == null) return; + if (_source == null || _decoder == null || _pcmBuffer == null || _decodeBuffer == null) + return; int retryCount = 0; + bool useResetDecode = false; try { while (!ct.IsCancellationRequested) { - // 1. Проверка места в буфере - // Если буфер полон, ждем. Используем Task.Delay для разгрузки CPU. + int skipCount = Interlocked.CompareExchange(ref _skipFramesCounter, 0, 0); + if (skipCount > 0) + useResetDecode = true; + if (_pcmBuffer.Available < _decoder.MaxFrameSize * _decoder.Channels) { - await Task.Delay(10, ct); + await Task.Delay(5, ct); continue; } - // 2. Чтение фрейма AudioFrame? frame; try { frame = await _source.ReadFrameAsync(ct); - retryCount = 0; // Сброс счетчика ошибок при успехе + retryCount = 0; + } + catch (OperationCanceledException) + { + break; } catch (UrlExpiredException) { if (await TryRefreshUrlAsync(ct)) continue; throw; } - catch (Exception) when (retryCount++ < _options.MaxRetryAttempts) + catch (Exception ex) when (retryCount++ < _options.MaxRetryAttempts) { - Log.Warn($"[AudioPlayer] Read retry {retryCount}/{_options.MaxRetryAttempts}"); + Log.Warn($"[AudioPlayer] Read retry {retryCount}/{_options.MaxRetryAttempts}: {ex.Message}"); await Task.Delay(_options.RetryDelay, ct); continue; } - // 3. Конец потока if (frame == null) { - // Ждем пока буфер опустеет (доиграет) - while (!_pcmBuffer.IsEmpty && !ct.IsCancellationRequested) - { - await Task.Delay(50, ct); - } + await DrainBufferAsync(ct); OnTrackEnded(); break; } - // 4. Декодирование + ct.ThrowIfCancellationRequested(); + try { - int samplesDecoded = _decoder.Decode(frame.Value.Data.Span, _decodeBuffer); - - if (samplesDecoded > 0) + int samplesDecoded; + + if (useResetDecode) + { + samplesDecoded = _decoder.DecodeWithReset( + frame.Value.Data.Span, _decodeBuffer); + useResetDecode = false; + } + else + { + samplesDecoded = _decoder.Decode( + frame.Value.Data.Span, _decodeBuffer); + } + + skipCount = Interlocked.CompareExchange(ref _skipFramesCounter, 0, 0); + if (skipCount > 0) + { + Interlocked.Decrement(ref _skipFramesCounter); + continue; + } + + if (samplesDecoded > 0 && !ct.IsCancellationRequested) { - _pcmBuffer.Write(_decodeBuffer.AsSpan(0, samplesDecoded * _decoder.Channels)); - // Примечание: Position не обновляется здесь, он зависит от AudioCallback + _pcmBuffer.Write( + _decodeBuffer.AsSpan(0, samplesDecoded * _decoder.Channels)); } } + catch (OperationCanceledException) + { + break; + } catch (Exception ex) { - Log.Warn($"[AudioPlayer] Decode frame failed: {ex.Message}"); - // Пропускаем битый фрейм + Log.Warn($"[AudioPlayer] Decode error: {ex.Message}"); } } } - catch (OperationCanceledException) - { - // Нормальная остановка - } + catch (OperationCanceledException) { } catch (Exception ex) { - Log.Error($"[AudioPlayer] Decoder loop fatal error: {ex.Message}", ex); + Log.Error($"[AudioPlayer] Decoder loop fatal: {ex.Message}", ex); SetState(PlaybackState.Error); - ErrorOccurred?.Invoke(ex); + _events.RaiseError(new AudioPlayerError(ex.Message, ex)); } } - /// - /// Callback, вызываемый бэкендом (аудио-потоком) для получения PCM данных. - /// Критический путь: никаких аллокаций, блокировок или тяжелых операций. - /// + private async Task DrainBufferAsync(CancellationToken ct) + { + while (_pcmBuffer is { IsEmpty: false } && !ct.IsCancellationRequested) + { + await Task.Delay(50, ct); + } + } + + #endregion + + #region Audio Callback + private int AudioCallback(Span buffer) { if (_state != PlaybackState.Playing || _pcmBuffer == null) @@ -472,77 +588,109 @@ private int AudioCallback(Span buffer) return 0; } - // Читаем данные из циклического буфера int read = _pcmBuffer.Read(buffer); if (read > 0) { - // Увеличиваем глобальный счетчик воспроизведенных семплов. - // Это основа для свойства Position. Interlocked.Add(ref _playedSamples, read); - // Применяем программную громкость (SIMD оптимизация) - // Backend применяет Hardware Volume (<= 1.0), мы применяем Software Boost (> 1.0) if (_volume > 1.0f) - { ApplyVolumeSimd(buffer[..read], _volume); - } } - // Заполняем остаток тишиной (для стабильности драйверов) if (read < buffer.Length) - { buffer[read..].Clear(); - } - // Возвращаем количество фреймов (семплы / каналы) return read / (_decoder?.Channels ?? 2); } - /// - /// Применяет громкость к буферу с использованием векторных инструкций (AVX/SSE). - /// private static void ApplyVolumeSimd(Span data, float volume) { + if (MathF.Abs(volume - 1.0f) < 0.001f) return; + int i = 0; + if (Vector.IsHardwareAccelerated) { var vecVol = new Vector(volume); - var min = new Vector(-1.0f); - var max = new Vector(1.0f); - int vecSize = Vector.Count; - - var span = MemoryMarshal.Cast>(data); - for (int j = 0; j < span.Length; j++) - { - var v = span[j] * vecVol; - // Clamp значения между -1.0 и 1.0 - v = Vector.Min(Vector.Max(v, min), max); - span[j] = v; - } - i = span.Length * vecSize; + var vecMin = new Vector(-1.0f); + var vecMax = new Vector(1.0f); + + var vectors = MemoryMarshal.Cast>(data); + for (int j = 0; j < vectors.Length; j++) + vectors[j] = Vector.Min(Vector.Max(vectors[j] * vecVol, vecMin), vecMax); + + i = vectors.Length * Vector.Count; } - // Обработка хвоста (или если SIMD недоступен) for (; i < data.Length; i++) - { data[i] = Math.Clamp(data[i] * volume, -1f, 1f); - } } - private async Task WaitForBufferAsync(CancellationToken ct) + #endregion + + #region Timers + + private void StartTimers() + { + _positionTimer = new Timer( + _ => _events.RaisePositionChanged(Position), + null, 0, + (int)_options.PositionUpdateInterval.TotalMilliseconds); + + _bufferTimer = new Timer( + _ => RaiseBufferState(), + null, 0, BufferStateUpdateIntervalMs); + } + + private void RaiseBufferState() + { + if (_source == null) return; + + var state = new BufferState( + _source.BufferProgress, + _source.IsFullyBuffered, + _source.GetBufferedRanges()); + + _events.RaiseBufferState(state); + } + + #endregion + + #region Buffering + + private async Task WaitForInitialBufferAsync(CancellationToken ct) { if (_decoder == null || _pcmBuffer == null) return; - - // Ждем заполнения минимум 500мс или полной загрузки трека + int threshold = _decoder.SampleRate * _decoder.Channels * MinBufferMs / 1000; - while (_pcmBuffer.Count < threshold && !ct.IsCancellationRequested && _source?.IsFullyBuffered == false) + while (_pcmBuffer.Count < threshold + && !ct.IsCancellationRequested + && _source?.IsFullyBuffered == false) + { + await Task.Delay(5, ct); + } + } + + private async Task WaitForMinimalBufferAsync(CancellationToken ct) + { + if (_decoder == null || _pcmBuffer == null) return; + + int minSamples = _decoder.SampleRate * _decoder.Channels * MinSeekResumeBufferMs / 1000; + int waited = 0; + + while (_pcmBuffer.Count < minSamples && waited < 300 && !ct.IsCancellationRequested && !_disposed) { await Task.Delay(10, ct); + waited += 10; } } + #endregion + + #region URL Refresh + private async Task TryRefreshUrlAsync(CancellationToken ct) { if (_options.UrlRefreshCallback == null || string.IsNullOrEmpty(_currentTrackId)) @@ -553,8 +701,7 @@ private async Task TryRefreshUrlAsync(CancellationToken ct) var newUrl = await _options.UrlRefreshCallback(_currentTrackId, ct); if (!string.IsNullOrEmpty(newUrl)) { - _currentUrl = newUrl; - Log.Info("[AudioPlayer] URL refreshed successfully"); + Log.Info("[AudioPlayer] URL refreshed"); return true; } } @@ -562,6 +709,7 @@ private async Task TryRefreshUrlAsync(CancellationToken ct) { Log.Warn($"[AudioPlayer] URL refresh failed: {ex.Message}"); } + return false; } @@ -575,19 +723,21 @@ private async Task TryRefreshUrlAsync(CancellationToken ct) return ct => callback(trackId, ct).AsTask(); } + #endregion + + #region Lifecycle + private async Task StopInternalAsync() { _playbackCts?.Cancel(); - - if (_decoderTask != null) - { - try { await _decoderTask.WaitAsync(TimeSpan.FromMilliseconds(StopTimeoutMs)); } - catch { /* Игнорируем ошибки ожидания */ } - } + await StopDecoderLoopGracefullyAsync(); _positionTimer?.Dispose(); _positionTimer = null; + _bufferTimer?.Dispose(); + _bufferTimer = null; + _backend?.Dispose(); _backend = null; @@ -606,18 +756,16 @@ private async Task StopInternalAsync() _decodeBuffer = null; } - _pcmBuffer = null; // GC соберет - - // Сброс счетчиков + _pcmBuffer = null; + Volatile.Write(ref _playedSamples, 0); - Volatile.Write(ref _durationMs, 0); - + Interlocked.Exchange(ref _skipFramesCounter, 0); _currentTrackId = null; - _currentUrl = null; - + _currentBitrateHint = 0; + _currentStreamInfo = AudioStreamInfo.Empty; + _playbackCts?.Dispose(); _playbackCts = null; - _decoderTask = null; SetState(PlaybackState.Stopped); } @@ -625,9 +773,7 @@ private async Task StopInternalAsync() private void OnTrackEnded() { if (_state != PlaybackState.Stopped) - { - TrackEnded?.Invoke(); - } + _events.RaiseTrackEnded(); SetState(PlaybackState.Stopped); } @@ -635,31 +781,60 @@ private void SetState(PlaybackState newState) { if (_state == newState) return; _state = newState; - StateChanged?.Invoke(newState); + _events.RaiseStateChanged(newState); } #endregion + #region Statistics + + public double BufferProgress => _source?.BufferProgress ?? 0; + public bool IsFullyBuffered => _source?.IsFullyBuffered ?? false; + + public long GetDownloadedBytes() + { + return _source switch + { + Sources.CachingStreamSource caching => caching.DownloadedBytes, + Sources.LocalFileSource => _source.IsFullyBuffered ? EstimateTotalBytes() : 0, + _ => (long)(_source?.BufferProgress / 100.0 * EstimateTotalBytes() ?? 0) + }; + } + + private long EstimateTotalBytes() + { + if (_currentStreamInfo.DurationMs <= 0) return 0; + return _currentStreamInfo.DurationMs * _currentStreamInfo.Bitrate / 8; + } + + public IReadOnlyList<(double Start, double End)> GetBufferedRanges() + { + return _source?.GetBufferedRanges() ?? []; + } + + public string CurrentCodec => _currentStreamInfo.Codec; + + #endregion + #region Dispose public void Dispose() { if (_disposed) return; - _disposed = true; // Fix CS0649 - - // Синхронный стоп + _disposed = true; Stop(); _stateLock.Dispose(); + _seekLock.Dispose(); GC.SuppressFinalize(this); } public async ValueTask DisposeAsync() { if (_disposed) return; - _disposed = true; // Fix CS0649 - + _disposed = true; await StopAsync(); _stateLock.Dispose(); + _seekLock.Dispose(); GC.SuppressFinalize(this); } diff --git a/Core/Audio/AudioPlayerEvents.cs b/Core/Audio/AudioPlayerEvents.cs new file mode 100644 index 0000000..60326e9 --- /dev/null +++ b/Core/Audio/AudioPlayerEvents.cs @@ -0,0 +1,45 @@ +// Core/Audio/AudioPlayerEvents.cs +namespace LMP.Core.Audio; + +/// +/// Все события аудио плеера. +/// +public sealed class AudioPlayerEvents +{ + /// Изменилась позиция воспроизведения. + public event Action? PositionChanged; + + /// Изменилось состояние (Playing, Paused, etc). + public event Action? StateChanged; + + /// Трек закончился естественным образом. + public event Action? TrackEnded; + + /// Произошла ошибка. + public event Action? ErrorOccurred; + + /// Информация о потоке стала доступна или обновилась. + public event Action? StreamInfoChanged; + + /// Изменился прогресс буферизации. + public event Action? BufferStateChanged; + + /// Seek завершён. + public event Action? SeekCompleted; + + // Internal raise methods + internal void RaisePositionChanged(TimeSpan pos) => PositionChanged?.Invoke(pos); + internal void RaiseStateChanged(PlaybackState state) => StateChanged?.Invoke(state); + internal void RaiseTrackEnded() => TrackEnded?.Invoke(); + internal void RaiseError(AudioPlayerError error) => ErrorOccurred?.Invoke(error); + internal void RaiseStreamInfo(AudioStreamInfo info) => StreamInfoChanged?.Invoke(info); + internal void RaiseBufferState(BufferState state) => BufferStateChanged?.Invoke(state); + internal void RaiseSeekCompleted(TimeSpan pos) => SeekCompleted?.Invoke(pos); +} + +public readonly record struct AudioPlayerError(string Message, Exception? Exception = null); + +public readonly record struct BufferState( + double Progress, // 0-100% + bool IsFullyBuffered, + IReadOnlyList<(double Start, double End)> Ranges); \ No newline at end of file diff --git a/Core/Audio/AudioSourceFactory.cs b/Core/Audio/AudioSourceFactory.cs index c091a50..5a17532 100644 --- a/Core/Audio/AudioSourceFactory.cs +++ b/Core/Audio/AudioSourceFactory.cs @@ -1,249 +1,297 @@ +using System.Net; using LMP.Core.Audio.Cache; using LMP.Core.Audio.Http; using LMP.Core.Audio.Interfaces; using LMP.Core.Audio.Sources; +using static LMP.Core.Audio.AudioConstants; namespace LMP.Core.Audio; -/// -/// Форматы аудио контейнеров. -/// -public enum AudioFormat -{ - Unknown, - WebM, - Mp4, - Hls, - Ogg, - Raw -} - -/// -/// Фабрика источников аудио. -/// public static class AudioSourceFactory { - private const long DefaultContentLength = 50 * 1024 * 1024; // 50MB fallback - - /// - /// Определяет формат по URL. - /// - public static AudioFormat DetectFormat(string url) - { - var lower = url.ToLowerInvariant(); - - if (lower.Contains(".m3u8")) - return AudioFormat.Hls; - - if (lower.Contains(".webm")) - return AudioFormat.WebM; - if (lower.Contains(".m4a") || lower.Contains(".mp4") || lower.Contains(".aac")) - return AudioFormat.Mp4; - if (lower.Contains(".ogg") || lower.Contains(".opus")) - return AudioFormat.Ogg; - - if (lower.Contains("mime=audio%2fwebm") || lower.Contains("mime=audio/webm")) - return AudioFormat.WebM; - if (lower.Contains("mime=audio%2fmp4") || lower.Contains("mime=audio/mp4")) - return AudioFormat.Mp4; - - if (TryParseItag(url, out int itag)) - return GetFormatByItag(itag); - - return AudioFormat.Unknown; - } - - /// - /// Определяет формат по Content-Type. - /// - public static AudioFormat DetectFormatByContentType(string? contentType) + private static AudioCacheManager? _globalCacheManager; + + public static void InitializeGlobalCache(AudioCacheManager cacheManager) { - if (string.IsNullOrEmpty(contentType)) - return AudioFormat.Unknown; - - var lower = contentType.ToLowerInvariant(); - - if (lower.Contains("webm")) return AudioFormat.WebM; - if (lower.Contains("mp4") || lower.Contains("m4a") || lower.Contains("aac")) return AudioFormat.Mp4; - if (lower.Contains("ogg") || lower.Contains("opus")) return AudioFormat.Ogg; - if (lower.Contains("mpegurl") || lower.Contains("m3u")) return AudioFormat.Hls; - - return AudioFormat.Unknown; + _globalCacheManager = cacheManager ?? throw new ArgumentNullException(nameof(cacheManager)); + Log.Info("[AudioSourceFactory] Global cache initialized"); } - + + public static AudioCacheManager? GlobalCache => _globalCacheManager; + /// - /// Определяет формат по magic bytes. + /// Строит уникальный ключ кэша: trackId + формат + нормализованный битрейт. /// - public static AudioFormat DetectFormatByMagic(ReadOnlySpan header) + public static string BuildCacheKey(string trackId, AudioFormat format, int bitrate) { - if (header.Length < 12) - return AudioFormat.Unknown; - - // WebM/Matroska: 1A 45 DF A3 - if (header[0] == 0x1A && header[1] == 0x45 && header[2] == 0xDF && header[3] == 0xA3) - return AudioFormat.WebM; - - // MP4/M4A: ....ftyp - if (header[4] == 'f' && header[5] == 't' && header[6] == 'y' && header[7] == 'p') - return AudioFormat.Mp4; - - // Ogg: OggS - if (header[0] == 'O' && header[1] == 'g' && header[2] == 'g' && header[3] == 'S') - return AudioFormat.Ogg; - - // ADTS AAC: FF Fx - if (header[0] == 0xFF && (header[1] & 0xF0) == 0xF0) - return AudioFormat.Raw; - - return AudioFormat.Unknown; + int normalizedBitrate = NormalizeBitrate(bitrate); + return $"{trackId}_{format}_{normalizedBitrate}"; } - + /// - /// Определяет кодек по формату. + /// Ищет полностью закэшированный трек любого формата/битрейта. /// - public static AudioCodec GetCodecForFormat(AudioFormat format) + public static (string Path, CacheEntry Entry)? FindAnyCachedTrack(string trackId) { - return format switch - { - AudioFormat.WebM => AudioCodec.Opus, - AudioFormat.Mp4 => AudioCodec.Aac, - AudioFormat.Hls => AudioCodec.Aac, - AudioFormat.Ogg => AudioCodec.Opus, - _ => AudioCodec.Unknown - }; + if (_globalCacheManager == null) return null; + + var entry = _globalCacheManager.FindBestCache(trackId); + if (entry == null) return null; + + var path = _globalCacheManager.GetCachePath(entry.CacheKey); + if (!File.Exists(path)) return null; + + return (path, entry); } - + /// - /// Создаёт источник для URL (без кэширования на диск). + /// Создаёт аудио источник. /// + /// URL потока. + /// HTTP клиент. + /// Функция обновления URL. + /// ID трека. + /// + /// Подсказка битрейта (kbps) от вызывающего кода. + /// Используется для точного кэш-ключа вместо угадывания из URL. + /// 0 = определять автоматически. + /// + /// Токен отмены. public static async Task CreateAsync( string url, - HttpClient? httpClient = null, + HttpClient httpClient, Func>? urlRefresher = null, string? trackId = null, + int bitrateHint = 0, CancellationToken ct = default) { - httpClient ??= SharedHttpClient.Instance; - - var format = DetectFormat(url); - - if (format == AudioFormat.Unknown) - { - format = await DetectFormatByHeadAsync(url, httpClient, ct); - } - - Log.Debug($"[AudioSourceFactory] Format: {format}"); - - if (format == AudioFormat.Hls) - { - return new HlsStreamSource(url, httpClient, urlRefresher); - } - - long contentLength = await SharedHttpClient.GetContentLengthAsync(url, ct); - - if (contentLength <= 0) + if (_globalCacheManager == null) { - contentLength = DefaultContentLength; - Log.Warn("[AudioSourceFactory] Unknown content length, assuming 50MB"); + throw new InvalidOperationException( + "AudioSourceFactory.InitializeGlobalCache() must be called before creating sources"); } - - return new UniversalStreamSource( - trackId ?? Guid.NewGuid().ToString(), - url, - contentLength, - GetCodecForFormat(format), - httpClient, - urlRefresher); - } - - /// - /// Создаёт источник с кэшированием. - /// - public static async Task CreateWithCacheAsync( - string url, - string trackId, - HttpClient? httpClient, - AudioCacheManager cacheManager, - Func>? urlRefresher = null, - CancellationToken ct = default) - { - httpClient ??= SharedHttpClient.Instance; - - // Проверяем полный кэш - if (cacheManager.IsFullyCached(trackId)) + + trackId ??= GenerateTrackIdFromUrl(url); + + // Пустой URL — ищем любой кэш для трека + if (string.IsNullOrEmpty(url)) { - Log.Info($"[AudioSourceFactory] Using fully cached: {trackId}"); - cacheManager.Touch(trackId); - var cachedPath = cacheManager.GetCachePath(trackId); - return new LocalFileSource(cachedPath); + var cached = FindAnyCachedTrack(trackId); + if (cached != null) + { + Log.Info($"[AudioSourceFactory] Using cached: {trackId} " + + $"({cached.Value.Entry.Format}/{cached.Value.Entry.Bitrate}kbps)"); + return new LocalFileSource(cached.Value.Path); + } + throw new ArgumentException("No URL provided and no cache available", nameof(url)); } - - var format = DetectFormat(url); - + + // Определяем формат + var format = await DetectFormatAsync(url, httpClient, ct); if (format == AudioFormat.Unknown) - { - format = await DetectFormatByHeadAsync(url, httpClient, ct); - } - - // HLS пока без кэширования на диск + throw new NotSupportedException($"Could not detect audio format for: {url}"); + + // HLS — не кэшируем if (format == AudioFormat.Hls) { - Log.Debug("[AudioSourceFactory] HLS stream, caching not supported"); - return new HlsStreamSource(url, httpClient, urlRefresher); + Log.Info($"[AudioSourceFactory] HLS stream (RAM-only): {trackId}"); + return new HlsStreamSource(url, httpClient); } - - long contentLength = await SharedHttpClient.GetContentLengthAsync(url, ct); - - if (contentLength <= 0) + + // Получаем метаданные потока + var (contentLength, codec, detectedBitrate) = await GetStreamInfoAsync(url, format, httpClient, ct); + + // bitrateHint имеет приоритет над автодетектом + int finalBitrate = bitrateHint > 0 ? bitrateHint : detectedBitrate; + + // Строим ключ кэша + string cacheKey = BuildCacheKey(trackId, format, finalBitrate); + + Log.Debug($"[AudioSourceFactory] Bitrate resolution: hint={bitrateHint}, detected={detectedBitrate}, " + + $"final={finalBitrate}, normalized={NormalizeBitrate(finalBitrate)}, key={cacheKey}"); + + // Проверяем точный кэш (формат + битрейт) + if (_globalCacheManager.IsFullyCached(cacheKey)) { - contentLength = DefaultContentLength; - Log.Warn("[AudioSourceFactory] Unknown content length, assuming 50MB"); + var cachePath = _globalCacheManager.GetCachePath(cacheKey); + if (File.Exists(cachePath)) + { + Log.Info($"[AudioSourceFactory] Exact cache hit: {cacheKey}"); + return new LocalFileSource(cachePath); + } } - + + Log.Info($"[AudioSourceFactory] Caching {format}/{codec}/{finalBitrate}kbps: {cacheKey}"); + return new CachingStreamSource( + cacheKey, trackId, url, contentLength, format, + codec, + finalBitrate, httpClient, - cacheManager, + _globalCacheManager, urlRefresher); } - - #region Private - - private static async Task DetectFormatByHeadAsync( - string url, HttpClient httpClient, CancellationToken ct) + + private static string GenerateTrackIdFromUrl(string url) + { + var hash = System.Security.Cryptography.SHA256.HashData( + System.Text.Encoding.UTF8.GetBytes(url)); + return "cache_" + Convert.ToHexString(hash)[..16]; + } + + public static CacheEntry? GetCacheInfo(string trackId) { + return _globalCacheManager?.FindBestCache(trackId); + } + + #region Format Detection + + public static AudioFormat DetectFormat(string url) + { + if (string.IsNullOrEmpty(url)) + return AudioFormat.Unknown; + + if (url.Contains(".m3u8", StringComparison.OrdinalIgnoreCase) || + url.Contains("/hls/", StringComparison.OrdinalIgnoreCase)) + return AudioFormat.Hls; + + if (url.Contains(".webm", StringComparison.OrdinalIgnoreCase)) + return AudioFormat.WebM; + + if (url.Contains(".mp4", StringComparison.OrdinalIgnoreCase) || + url.Contains(".m4a", StringComparison.OrdinalIgnoreCase)) + return AudioFormat.Mp4; + + if (url.Contains(".ogg", StringComparison.OrdinalIgnoreCase) || + url.Contains(".opus", StringComparison.OrdinalIgnoreCase)) + return AudioFormat.Ogg; + + return AudioFormat.Unknown; + } + + public static async Task DetectFormatAsync( + string url, HttpClient httpClient, CancellationToken ct = default) + { + var urlFormat = DetectFormat(url); + if (urlFormat != AudioFormat.Unknown) + return urlFormat; + try { - using var request = new HttpRequestMessage(HttpMethod.Head, url); - using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct); - var contentType = response.Content.Headers.ContentType?.MediaType; - return DetectFormatByContentType(contentType); + using var request = SharedHttpClient.CreateRangeRequest(url, 0, FormatDetectionHeaderSize - 1); + using var response = await httpClient.SendAsync(request, + HttpCompletionOption.ResponseHeadersRead, ct); + + if (response.StatusCode == HttpStatusCode.Forbidden) + return AudioFormat.Unknown; + + response.EnsureSuccessStatusCode(); + + var header = await response.Content.ReadAsByteArrayAsync(ct); + return DetectFormatByMagic(header); } - catch + catch (Exception ex) { + Log.Warn($"[AudioSourceFactory] Format detection failed: {ex.Message}"); return AudioFormat.Unknown; } } - - private static bool TryParseItag(string url, out int itag) + + public static AudioFormat DetectFormatByMagic(ReadOnlySpan header) + { + if (header.Length < 4) return AudioFormat.Unknown; + + if (header[..4].SequenceEqual(WebMMagic)) + return AudioFormat.WebM; + + if (header.Length >= 8 && header.Slice(4, 4).SequenceEqual(Mp4FtypMagic)) + return AudioFormat.Mp4; + + if (header[..4].SequenceEqual(OggMagic)) + return AudioFormat.Ogg; + + return AudioFormat.Unknown; + } + + private static async Task<(long ContentLength, AudioCodec Codec, int Bitrate)> GetStreamInfoAsync( + string url, AudioFormat format, HttpClient httpClient, CancellationToken ct) { - itag = 0; - var match = System.Text.RegularExpressions.Regex.Match(url, @"[?&]itag=(\d+)"); - return match.Success && int.TryParse(match.Groups[1].Value, out itag); + long contentLength = 0; + + try + { + using var headRequest = new HttpRequestMessage(HttpMethod.Head, url); + using var headResponse = await httpClient.SendAsync(headRequest, ct); + + if (headResponse.IsSuccessStatusCode) + contentLength = headResponse.Content.Headers.ContentLength ?? 0; + } + catch { } + + if (contentLength == 0) + { + try + { + using var rangeRequest = SharedHttpClient.CreateRangeRequest(url, 0, 0); + using var rangeResponse = await httpClient.SendAsync(rangeRequest, ct); + + if (rangeResponse.Content.Headers.ContentRange?.Length != null) + contentLength = rangeResponse.Content.Headers.ContentRange.Length.Value; + } + catch + { + contentLength = 100 * 1024 * 1024; + } + } + + if (contentLength <= 0) + contentLength = 100 * 1024 * 1024; + + var codec = GetCodecForFormat(format); + int bitrate = ExtractBitrateFromUrl(url); + if (bitrate == 0) + bitrate = codec == AudioCodec.Opus ? 128 : 96; + + return (contentLength, codec, bitrate); } - - private static AudioFormat GetFormatByItag(int itag) + + private static int ExtractBitrateFromUrl(string url) { - return itag switch + try { - 249 or 250 or 251 => AudioFormat.WebM, // Opus - 139 or 140 or 141 or 256 or 258 or 325 or 328 => AudioFormat.Mp4, // AAC - _ => AudioFormat.Unknown - }; + var uri = new Uri(url); + var query = System.Web.HttpUtility.ParseQueryString(uri.Query); + + var bitrateStr = query["bitrate"]; + if (!string.IsNullOrEmpty(bitrateStr) && int.TryParse(bitrateStr, out var br)) + return br / 1000; + } + catch { } + + return 0; } - + + public static AudioCodec GetCodecForFormat(AudioFormat format) => format switch + { + AudioFormat.WebM => AudioCodec.Opus, + AudioFormat.Ogg => AudioCodec.Opus, + AudioFormat.Mp4 => AudioCodec.Aac, + AudioFormat.Hls => AudioCodec.Aac, + _ => AudioCodec.Unknown + }; + #endregion +} + +public enum AudioFormat +{ + Unknown = 0, + WebM = 1, + Mp4 = 2, + Ogg = 3, + Hls = 4 } \ No newline at end of file diff --git a/Core/Audio/AudioStreamInfo.cs b/Core/Audio/AudioStreamInfo.cs new file mode 100644 index 0000000..507a505 --- /dev/null +++ b/Core/Audio/AudioStreamInfo.cs @@ -0,0 +1,26 @@ +// Core/Audio/AudioStreamInfo.cs +namespace LMP.Core.Audio; + +/// +/// Неизменяемая информация о текущем аудио потоке. +/// Единый источник правды о формате/кодеке/битрейте. +/// +public sealed record AudioStreamInfo +{ + public static readonly AudioStreamInfo Empty = new(); + + public string TrackId { get; init; } = ""; + public string Container { get; init; } = ""; // WebM, Mp4, Ogg + public string Codec { get; init; } = ""; // Opus, Aac + public int Bitrate { get; init; } // kbps + public int SampleRate { get; init; } // Hz + public int Channels { get; init; } + public long DurationMs { get; init; } + public bool IsFromCache { get; init; } + + public bool IsValid => !string.IsNullOrEmpty(Codec) && Bitrate > 0; + + public string FormatDisplay => IsValid + ? $"{Container}/{Codec}/{Bitrate}kbps" + : "Loading..."; +} \ No newline at end of file diff --git a/Core/Audio/Backends/NAudioBackend.cs b/Core/Audio/Backends/NAudioBackend.cs index cbed50f..20ef3fc 100644 --- a/Core/Audio/Backends/NAudioBackend.cs +++ b/Core/Audio/Backends/NAudioBackend.cs @@ -3,132 +3,175 @@ namespace LMP.Core.Audio.Backends; -/// -/// Бэкенд воспроизведения на базе NAudio (Windows/WaveOut). -/// Обеспечивает буферизацию и взаимодействие с аудио-драйвером. -/// public sealed class NAudioBackend : IPlaybackBackend { - // Размер внутреннего буфера NAudio. - // 1 секунда обеспечивает баланс между стабильностью (нет заиканий) и отзывчивостью (Position). - private const int InternalBufferSeconds = 1; + private const int InternalBufferSeconds = 1; private const int DesiredLatencyMs = 150; - + private WaveOutEvent? _waveOut; private BufferedWaveProvider? _provider; private AudioDataCallback? _callback; - + private int _channels; private float[]? _floatBuffer; private byte[]? _byteBuffer; - + private volatile bool _playing; + private volatile bool _flushing; private volatile bool _disposed; - + private volatile bool _fillLoopActive; private Task? _fillTask; private CancellationTokenSource? _cts; - + public string Name => "NAudio"; - - /// - public float Volume { get; set; } = 1.0f; - - /// + + public float Volume + { + get; + set + { + field = Math.Clamp(value, 0f, 1f); + if (_waveOut != null) + _waveOut.Volume = field; + } + } = 1.0f; + public bool IsPlaying => _playing; - - /// - // Возвращаем количество семплов (float), которые находятся в буфере драйвера, но еще не сыграны. - public int BufferedSamples => _provider != null - ? _provider.BufferedBytes / sizeof(float) - : 0; - + + public int BufferedSamples => + _provider != null ? _provider.BufferedBytes / sizeof(float) : 0; + public void Initialize(int sampleRate, int channels, AudioDataCallback dataCallback) { ObjectDisposedException.ThrowIf(_disposed, this); - + _callback = dataCallback ?? throw new ArgumentNullException(nameof(dataCallback)); _channels = channels; - + var format = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels); - + _provider = new BufferedWaveProvider(format) { BufferDuration = TimeSpan.FromSeconds(InternalBufferSeconds), - DiscardOnBufferOverflow = true // Защита от переполнения + DiscardOnBufferOverflow = true }; - + _waveOut = new WaveOutEvent { DesiredLatency = DesiredLatencyMs, - NumberOfBuffers = 2 // Double buffering + NumberOfBuffers = 2 }; - + _waveOut.Init(_provider); _waveOut.Volume = Volume; - - // Буфер для чтения из AudioPlayer (50мс за раз) - int samplesPerRead = sampleRate * channels / 20; + + int samplesPerRead = sampleRate * channels / 20; _floatBuffer = new float[samplesPerRead]; _byteBuffer = new byte[samplesPerRead * sizeof(float)]; - + _cts = new CancellationTokenSource(); - // Запускаем цикл заполнения буфера - _fillTask = Task.Factory.StartNew(() => FillBufferLoopAsync(_cts.Token), + _fillTask = Task.Factory.StartNew( + () => FillBufferLoopAsync(_cts.Token), TaskCreationOptions.LongRunning); - - Log.Info($"[NAudioBackend] Initialized: {sampleRate}Hz, {channels}ch"); + + Log.Info($"[NAudioBackend] Initialized: {sampleRate}Hz, {channels}ch, vol={Volume:P0}"); } - + public void Start() { ObjectDisposedException.ThrowIf(_disposed, this); if (_waveOut == null || _playing) return; - + _waveOut.Play(); _playing = true; } - + public void Stop() { if (_waveOut == null || !_playing) return; - + _waveOut.Pause(); _playing = false; } - + + public void Flush() + { + if (_provider == null || _disposed) return; + + _flushing = true; + + try + { + // Ждём пока fill loop завершит текущую итерацию + int waitCount = 0; + while (_fillLoopActive && waitCount < 50) + { + Thread.Sleep(5); + waitCount++; + } + + // Очищаем буфер + _provider.ClearBuffer(); + + // Дополнительная пауза для NAudio + Thread.Sleep(30); + } + finally + { + _flushing = false; + } + } + private async Task FillBufferLoopAsync(CancellationToken ct) { while (!ct.IsCancellationRequested && !_disposed) { try { - if (_provider == null || _callback == null || _floatBuffer == null || _byteBuffer == null) + if (_provider == null || _callback == null + || _floatBuffer == null || _byteBuffer == null) break; - // Если буфер драйвера почти полон (>80%), ждем. - // Это предотвращает высокий CPU usage при spin-wait. + // Пропускаем если flush или не играем + if (_flushing || !_playing) + { + _fillLoopActive = false; + await Task.Delay(10, ct); + continue; + } + + // Не переполняем буфер if (_provider.BufferedDuration.TotalSeconds > InternalBufferSeconds * 0.8) { + _fillLoopActive = false; await Task.Delay(20, ct); continue; } - - // Запрашиваем данные у AudioPlayer через callback + + _fillLoopActive = true; + + // Двойная проверка после установки флага + if (_flushing || !_playing) + { + _fillLoopActive = false; + continue; + } + int framesRead = _callback(_floatBuffer); - - if (framesRead > 0) + + // Проверяем ещё раз перед записью + if (framesRead > 0 && !_flushing && _playing) { - // Конвертация float[] -> byte[] (BlockCopy очень быстр) int totalSamples = framesRead * _channels; int bytes = totalSamples * sizeof(float); Buffer.BlockCopy(_floatBuffer, 0, _byteBuffer, 0, bytes); - - // Отправка в NAudio _provider.AddSamples(_byteBuffer, 0, bytes); } - else + + _fillLoopActive = false; + + if (framesRead <= 0) { - // Если данных нет (пауза или буферизация), ждем немного await Task.Delay(10, ct); } } @@ -138,41 +181,41 @@ private async Task FillBufferLoopAsync(CancellationToken ct) } catch (Exception ex) { - Log.Error($"[NAudioBackend] Loop error: {ex.Message}"); - await Task.Delay(100, ct); // Anti-spam delay + Log.Error($"[NAudioBackend] Fill loop error: {ex.Message}"); + _fillLoopActive = false; + await Task.Delay(100, ct); } } + + _fillLoopActive = false; } - + public void Dispose() { if (_disposed) return; _disposed = true; - + _playing = false; + _flushing = true; _cts?.Cancel(); - - // Корректное ожидание завершения задачи заполнения + if (_fillTask != null) { - try - { - _fillTask.Wait(TimeSpan.FromSeconds(1)); - } - catch { /* Игнорируем ошибки при остановке */ } + try { _fillTask.Wait(TimeSpan.FromSeconds(1)); } + catch { } } - + _waveOut?.Stop(); _waveOut?.Dispose(); _waveOut = null; - + _provider = null; _floatBuffer = null; _byteBuffer = null; - + _cts?.Dispose(); _cts = null; - + Log.Debug("[NAudioBackend] Disposed"); } } \ No newline at end of file diff --git a/Core/Audio/Backends/NullAudioBackend.cs b/Core/Audio/Backends/NullAudioBackend.cs index 46dcc1f..6c27409 100644 --- a/Core/Audio/Backends/NullAudioBackend.cs +++ b/Core/Audio/Backends/NullAudioBackend.cs @@ -83,4 +83,9 @@ public void Dispose() _cts?.Dispose(); } + + public void Flush() + { + // Ignore + } } \ No newline at end of file diff --git a/Core/Audio/Cache/AudioCacheManager.cs b/Core/Audio/Cache/AudioCacheManager.cs index d173f75..d9ee9c1 100644 --- a/Core/Audio/Cache/AudioCacheManager.cs +++ b/Core/Audio/Cache/AudioCacheManager.cs @@ -4,155 +4,157 @@ using System.Text.Json; using System.Text.Json.Serialization; using LMP.Core.Audio.Interfaces; +using static LMP.Core.Audio.AudioConstants; namespace LMP.Core.Audio.Cache; -/// -/// Менеджер кэша аудио файлов. -/// public sealed class AudioCacheManager : IAsyncDisposable, IDisposable { - private const string MetadataFileName = "cache_index.json"; - private const string AudioExtension = ".audio"; - private const int AutoSaveIntervalMs = 30000; - private const int SaveLockTimeoutMs = 100; - private readonly string _cacheDirectory; private readonly long _maxCacheSize; private readonly ConcurrentDictionary _entries = new(); private readonly SemaphoreSlim _saveLock = new(1, 1); private readonly CancellationTokenSource _timerCts = new(); private readonly Task _autoSaveTask; - + private volatile bool _disposed; - + public AudioCacheManager(string? cacheDirectory = null, long maxCacheSizeMb = 2048) { - _cacheDirectory = cacheDirectory ?? Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "LMP", "AudioCache"); - + _cacheDirectory = cacheDirectory ?? G.Folder.AudioCache; _maxCacheSize = maxCacheSizeMb * 1024 * 1024; - + Directory.CreateDirectory(_cacheDirectory); LoadIndex(); - + _autoSaveTask = AutoSaveLoopAsync(_timerCts.Token); - + Log.Info($"[AudioCache] Initialized: {_cacheDirectory}, max={maxCacheSizeMb}MB, entries={_entries.Count}"); } - + #region Public API - - public bool IsFullyCached(string trackId) + + public bool IsFullyCached(string cacheKey) { - if (!_entries.TryGetValue(trackId, out var entry)) + if (!_entries.TryGetValue(cacheKey, out var entry)) return false; - + if (!entry.IsComplete) return false; - - var filePath = GetCachePath(trackId); + + var filePath = GetCachePath(cacheKey); if (!File.Exists(filePath)) return false; - + var fileInfo = new FileInfo(filePath); return fileInfo.Length >= entry.TotalSize; } - - public bool HasFullCache(string trackId) => IsFullyCached(trackId); - - public bool HasPartialCache(string trackId) + + public CacheEntry? FindBestCache(string trackId) { - return _entries.TryGetValue(trackId, out var entry) && entry.DownloadedChunks > 0; + return _entries.Values + .Where(e => e.TrackId == trackId && e.IsComplete) + .OrderByDescending(e => e.Bitrate) + .FirstOrDefault(e => + { + var path = GetCachePath(e.CacheKey); + return File.Exists(path) && new FileInfo(path).Length >= e.TotalSize; + }); + } + + public bool HasPartialCache(string cacheKey) + { + return _entries.TryGetValue(cacheKey, out var entry) && entry.DownloadedChunks > 0; } - - public CacheEntry? GetCacheInfo(string trackId) + + public CacheEntry? GetCacheInfo(string cacheKey) { - return _entries.TryGetValue(trackId, out var entry) ? entry : null; + return _entries.TryGetValue(cacheKey, out var entry) ? entry : null; } - - public string GetCachePath(string trackId) + + public string GetCachePath(string cacheKey) { - var safeId = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(trackId)))[..16]; - return Path.Combine(_cacheDirectory, safeId + AudioExtension); + var safeId = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(cacheKey)))[..16]; + return Path.Combine(_cacheDirectory, safeId + CacheFileExtension); } - - public void Touch(string trackId) + + public void Touch(string cacheKey) { - if (_entries.TryGetValue(trackId, out var entry)) + if (_entries.TryGetValue(cacheKey, out var entry)) { entry.LastAccessedAt = DateTime.UtcNow; } } - + public CacheEntry CreateOrUpdate( + string cacheKey, string trackId, string url, long totalSize, AudioFormat format, AudioCodec codec, + int bitrate = 0, long durationMs = -1, - int chunkSize = 256 * 1024) + int chunkSize = ChunkSize) { - var entry = _entries.GetOrAdd(trackId, _ => new CacheEntry + var entry = _entries.GetOrAdd(cacheKey, _ => new CacheEntry { + CacheKey = cacheKey, TrackId = trackId, OriginalUrl = url, TotalSize = totalSize, Format = format, Codec = codec, + Bitrate = bitrate, DurationMs = durationMs, ChunkSize = chunkSize, TotalChunks = (int)Math.Ceiling((double)totalSize / chunkSize), CreatedAt = DateTime.UtcNow, LastAccessedAt = DateTime.UtcNow }); - + entry.OriginalUrl = url; entry.LastAccessedAt = DateTime.UtcNow; - + + if (bitrate > 0) + entry.Bitrate = bitrate; + if (durationMs > 0) entry.DurationMs = durationMs; - + return entry; } - - public CacheEntry GetOrCreateEntry( - string trackId, - string url, - long totalSize, - AudioFormat format, - int chunkSize = 256 * 1024) - { - return CreateOrUpdate(trackId, url, totalSize, format, - AudioSourceFactory.GetCodecForFormat(format), -1, chunkSize); - } - - public void MarkComplete(string trackId, long? durationMs = null) + + public void MarkComplete(string cacheKey, long? durationMs = null, int? bitrate = null) { - if (_entries.TryGetValue(trackId, out var entry)) + if (_entries.TryGetValue(cacheKey, out var entry)) { entry.IsComplete = true; entry.CompletedAt = DateTime.UtcNow; entry.LastAccessedAt = DateTime.UtcNow; - + if (durationMs.HasValue) entry.DurationMs = durationMs.Value; - - Log.Info($"[AudioCache] Marked complete: {trackId}"); + + if (bitrate.HasValue) + entry.Bitrate = bitrate.Value; + + Log.Info($"[AudioCache] Track fully cached: {cacheKey}"); _ = SaveIndexAsync(); } } - - public async Task WriteChunkAsync(string trackId, int chunkIndex, byte[] data, CancellationToken ct = default) + + public async Task WriteChunkAsync(string cacheKey, int chunkIndex, byte[] data, CancellationToken ct = default) { - if (!_entries.TryGetValue(trackId, out var entry)) + if (!_entries.TryGetValue(cacheKey, out var entry)) + return; + + if (entry.IsComplete) return; - - var filePath = GetCachePath(trackId); + + var filePath = GetCachePath(cacheKey); long offset = (long)chunkIndex * entry.ChunkSize; - + try { await using var fs = new FileStream( @@ -160,20 +162,20 @@ public async Task WriteChunkAsync(string trackId, int chunkIndex, byte[] data, C FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, - bufferSize: 65536, + bufferSize: CacheFileBufferSize, useAsync: true); - + fs.Seek(offset, SeekOrigin.Begin); await fs.WriteAsync(data, ct); - + entry.MarkChunkDownloaded(chunkIndex); entry.LastAccessedAt = DateTime.UtcNow; - - if (entry.DownloadedChunks >= entry.TotalChunks) + + if (!entry.IsComplete && entry.DownloadedChunks >= entry.TotalChunks) { entry.IsComplete = true; entry.CompletedAt = DateTime.UtcNow; - Log.Info($"[AudioCache] Track fully cached: {trackId}"); + Log.Info($"[AudioCache] Track fully cached: {cacheKey}"); } } catch (Exception ex) @@ -181,49 +183,49 @@ public async Task WriteChunkAsync(string trackId, int chunkIndex, byte[] data, C Log.Warn($"[AudioCache] Write chunk failed: {ex.Message}"); } } - - public async Task ReadChunkAsync(string trackId, int chunkIndex, CancellationToken ct = default) + + public async Task ReadChunkAsync(string cacheKey, int chunkIndex, CancellationToken ct = default) { - if (!_entries.TryGetValue(trackId, out var entry)) + if (!_entries.TryGetValue(cacheKey, out var entry)) return null; - + if (!entry.IsChunkDownloaded(chunkIndex)) return null; - - var filePath = GetCachePath(trackId); + + var filePath = GetCachePath(cacheKey); if (!File.Exists(filePath)) return null; - + long offset = (long)chunkIndex * entry.ChunkSize; int size = (int)Math.Min(entry.ChunkSize, entry.TotalSize - offset); - + if (size <= 0) return null; - + try { var buffer = new byte[size]; - + await using var fs = new FileStream( filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, - bufferSize: 65536, + bufferSize: CacheFileBufferSize, useAsync: true); - + fs.Seek(offset, SeekOrigin.Begin); int totalRead = 0; - + while (totalRead < size) { int read = await fs.ReadAsync(buffer.AsMemory(totalRead, size - totalRead), ct); if (read == 0) break; totalRead += read; } - + entry.LastAccessedAt = DateTime.UtcNow; - + return totalRead == size ? buffer : null; } catch @@ -231,102 +233,89 @@ public async Task WriteChunkAsync(string trackId, int chunkIndex, byte[] data, C return null; } } - - public Stream? OpenCachedStream(string trackId) + + public Stream? OpenCachedStream(string cacheKey) { - if (!IsFullyCached(trackId)) + if (!IsFullyCached(cacheKey)) return null; - - var filePath = GetCachePath(trackId); - - Touch(trackId); - + + var filePath = GetCachePath(cacheKey); + Touch(cacheKey); + return new FileStream( filePath, FileMode.Open, FileAccess.Read, FileShare.Read, - bufferSize: 65536); + bufferSize: CacheFileBufferSize); } - - public void RemoveCache(string trackId) + + public void RemoveCache(string cacheKey) { - if (_entries.TryRemove(trackId, out _)) + if (_entries.TryRemove(cacheKey, out _)) { - var filePath = GetCachePath(trackId); + var filePath = GetCachePath(cacheKey); try { if (File.Exists(filePath)) File.Delete(filePath); } catch { } - + _ = SaveIndexAsync(); } } - + public async Task CleanupAsync(CancellationToken ct = default) { - long totalSize = 0; - var entries = _entries.Values.OrderByDescending(e => e.LastAccessedAt).ToList(); - - foreach (var entry in entries) - { - var filePath = GetCachePath(entry.TrackId); - if (File.Exists(filePath)) - { - totalSize += new FileInfo(filePath).Length; - } - } - + long totalSize = CalculateTotalCacheSize(); + if (totalSize <= _maxCacheSize) return; - + Log.Info($"[AudioCache] Cleanup needed: {totalSize / 1024 / 1024}MB > {_maxCacheSize / 1024 / 1024}MB"); - - var toRemove = entries + + var entries = _entries.Values .OrderBy(e => e.LastAccessedAt) - .TakeWhile(e => - { - if (totalSize <= _maxCacheSize * 0.8) - return false; - - var filePath = GetCachePath(e.TrackId); - if (File.Exists(filePath)) - totalSize -= new FileInfo(filePath).Length; - - return true; - }) .ToList(); - - foreach (var entry in toRemove) + + foreach (var entry in entries) { - RemoveCache(entry.TrackId); + if (totalSize <= _maxCacheSize * CacheCleanupThreshold) + break; + + var filePath = GetCachePath(entry.CacheKey); + if (File.Exists(filePath)) + { + totalSize -= new FileInfo(filePath).Length; + } + + RemoveCache(entry.CacheKey); } - - Log.Info($"[AudioCache] Removed {toRemove.Count} old entries"); + + Log.Info($"[AudioCache] Cleanup complete, new size: {totalSize / 1024 / 1024}MB"); } - + public CacheStats GetStats() { long totalSize = 0; int completeCount = 0; int partialCount = 0; - + foreach (var entry in _entries.Values) { - var filePath = GetCachePath(entry.TrackId); + var filePath = GetCachePath(entry.CacheKey); if (File.Exists(filePath)) { totalSize += new FileInfo(filePath).Length; } - + if (entry.IsComplete) completeCount++; else if (entry.DownloadedChunks > 0) partialCount++; } - + return new CacheStats { TotalEntries = _entries.Count, @@ -336,36 +325,55 @@ public CacheStats GetStats() MaxSizeBytes = _maxCacheSize }; } - + #endregion - - #region Index Management - + + #region Private Methods + + private long CalculateTotalCacheSize() + { + long totalSize = 0; + + foreach (var entry in _entries.Values) + { + var filePath = GetCachePath(entry.CacheKey); + if (File.Exists(filePath)) + { + totalSize += new FileInfo(filePath).Length; + } + } + + return totalSize; + } + private void LoadIndex() { - var indexPath = Path.Combine(_cacheDirectory, MetadataFileName); - + var indexPath = Path.Combine(_cacheDirectory, CacheMetadataFileName); + if (!File.Exists(indexPath)) return; - + try { var json = File.ReadAllText(indexPath); var entries = JsonSerializer.Deserialize>(json); - + if (entries != null) { foreach (var entry in entries) { - var filePath = GetCachePath(entry.TrackId); + if (string.IsNullOrEmpty(entry.CacheKey)) + continue; + + var filePath = GetCachePath(entry.CacheKey); if (File.Exists(filePath)) { entry.RestoreChunkMask(); - _entries.TryAdd(entry.TrackId, entry); + _entries.TryAdd(entry.CacheKey, entry); } } } - + Log.Debug($"[AudioCache] Loaded {_entries.Count} entries"); } catch (Exception ex) @@ -373,29 +381,29 @@ private void LoadIndex() Log.Warn($"[AudioCache] Failed to load index: {ex.Message}"); } } - + private async Task SaveIndexAsync() { if (_disposed) return; - - if (!await _saveLock.WaitAsync(SaveLockTimeoutMs)) + + if (!await _saveLock.WaitAsync(CacheSaveLockTimeoutMs)) return; - + try { - var indexPath = Path.Combine(_cacheDirectory, MetadataFileName); + var indexPath = Path.Combine(_cacheDirectory, CacheMetadataFileName); var entries = _entries.Values.ToList(); - + foreach (var entry in entries) { entry.SaveChunkMask(); } - + var json = JsonSerializer.Serialize(entries, new JsonSerializerOptions { WriteIndented = true }); - + await File.WriteAllTextAsync(indexPath, json); } catch (Exception ex) @@ -407,14 +415,14 @@ private async Task SaveIndexAsync() _saveLock.Release(); } } - + private async Task AutoSaveLoopAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { try { - await Task.Delay(AutoSaveIntervalMs, ct); + await Task.Delay(CacheAutoSaveIntervalMs, ct); await SaveIndexAsync(); } catch (OperationCanceledException) @@ -427,156 +435,133 @@ private async Task AutoSaveLoopAsync(CancellationToken ct) } } } - + #endregion - + #region Dispose - + public void Dispose() { if (_disposed) return; _disposed = true; - + _timerCts.Cancel(); - - try - { - _autoSaveTask.Wait(TimeSpan.FromSeconds(2)); - } - catch - { - // Ignore - } - - try - { - SaveIndexAsync().Wait(TimeSpan.FromSeconds(2)); - } - catch - { - // Ignore - } - + + try { _autoSaveTask.Wait(TimeSpan.FromSeconds(2)); } + catch { } + + try { SaveIndexAsync().Wait(TimeSpan.FromSeconds(2)); } + catch { } + _timerCts.Dispose(); _saveLock.Dispose(); } - + public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; - + _timerCts.Cancel(); - - try - { - await _autoSaveTask.WaitAsync(TimeSpan.FromSeconds(2)); - } - catch - { - // Ignore - } - + + try { await _autoSaveTask.WaitAsync(TimeSpan.FromSeconds(2)); } + catch { } + await SaveIndexAsync(); - + _timerCts.Dispose(); _saveLock.Dispose(); } - + #endregion } -/// -/// Запись в кэше. -/// public sealed class CacheEntry { - public required string TrackId { get; init; } + public string CacheKey { get; init; } = ""; + public string TrackId { get; init; } = ""; public string OriginalUrl { get; set; } = ""; public long TotalSize { get; init; } public AudioFormat Format { get; init; } public AudioCodec Codec { get; set; } + public int Bitrate { get; set; } public long DurationMs { get; set; } = -1; public int ChunkSize { get; init; } public int TotalChunks { get; init; } - - // Используем backing field для Interlocked + private int _downloadedChunks; - + public int DownloadedChunks { get => Volatile.Read(ref _downloadedChunks); set => Volatile.Write(ref _downloadedChunks, value); } - + public DateTime CreatedAt { get; init; } public DateTime LastAccessedAt { get; set; } public DateTime? CompletedAt { get; set; } public bool IsComplete { get; set; } - + public string? ChunkMaskData { get; set; } - + [JsonIgnore] private int[]? _chunkBits; - + [JsonIgnore] public double DownloadProgress => TotalChunks == 0 ? 0 : (double)DownloadedChunks / TotalChunks * 100; - + public bool IsChunkDownloaded(int index) { EnsureChunkBits(); if (index < 0 || index >= TotalChunks) return false; return (Volatile.Read(ref _chunkBits![index >> 5]) & (1 << (index & 31))) != 0; } - + public void MarkChunkDownloaded(int index) { EnsureChunkBits(); if (index < 0 || index >= TotalChunks) return; - + int word = index >> 5; int bit = 1 << (index & 31); - + int current = Volatile.Read(ref _chunkBits![word]); if ((current & bit) == 0) { - // Атомарно устанавливаем бит int original; do { original = current; current = Interlocked.CompareExchange(ref _chunkBits![word], original | bit, original); } while (current != original); - - // Инкрементируем счётчик только если бит был установлен впервые + if ((original & bit) == 0) { Interlocked.Increment(ref _downloadedChunks); } } } - + public void SaveChunkMask() { if (_chunkBits == null) return; - + var bytes = new byte[_chunkBits.Length * 4]; Buffer.BlockCopy(_chunkBits, 0, bytes, 0, bytes.Length); ChunkMaskData = Convert.ToBase64String(bytes); } - + public void RestoreChunkMask() { if (string.IsNullOrEmpty(ChunkMaskData)) return; - + EnsureChunkBits(); - + try { var bytes = Convert.FromBase64String(ChunkMaskData); Buffer.BlockCopy(bytes, 0, _chunkBits!, 0, Math.Min(bytes.Length, _chunkBits!.Length * 4)); - - // Пересчитываем количество загруженных чанков + int count = 0; for (int i = 0; i < TotalChunks; i++) { @@ -587,16 +572,13 @@ public void RestoreChunkMask() } catch { } } - + private void EnsureChunkBits() { _chunkBits ??= new int[(TotalChunks + 31) / 32]; } } -/// -/// Статистика кэша. -/// public readonly struct CacheStats { public int TotalEntries { get; init; } @@ -604,12 +586,12 @@ public readonly struct CacheStats public int PartialEntries { get; init; } public long TotalSizeBytes { get; init; } public long MaxSizeBytes { get; init; } - + public double UsagePercent => MaxSizeBytes == 0 ? 0 : (double)TotalSizeBytes / MaxSizeBytes * 100; - + public string TotalSizeFormatted => FormatSize(TotalSizeBytes); public string MaxSizeFormatted => FormatSize(MaxSizeBytes); - + private static string FormatSize(long bytes) { if (bytes < 1024) return $"{bytes} B"; diff --git a/Core/Audio/Decoders/AacDecoder.cs b/Core/Audio/Decoders/AacDecoder.cs index a8e4221..35bd0e8 100644 --- a/Core/Audio/Decoders/AacDecoder.cs +++ b/Core/Audio/Decoders/AacDecoder.cs @@ -4,90 +4,231 @@ namespace LMP.Core.Audio.Decoders; -/// -/// AAC декодер на базе SharpJaad. -/// public sealed class AacDecoder : IAudioDecoder { private const int DefaultSampleRate = 44100; private const int DefaultChannels = 2; private const int DefaultMaxFrameSize = 2048; - + private const int MaxConsecutiveSilentErrors = 15; + private Decoder? _decoder; private DecoderConfig? _config; private SampleBuffer? _sampleBuffer; - + private byte[]? _originalAsc; + private readonly int _requestedSampleRate; private readonly int _requestedChannels; private bool _initialized; private bool _disposed; - + + private bool _isHeAac; + private int _outputSampleRate; + private int _consecutiveErrors; + public AacDecoder(int sampleRate = DefaultSampleRate, int channels = DefaultChannels) { _requestedSampleRate = sampleRate; _requestedChannels = channels; - + Log.Debug($"[AacDecoder] Created: requested {sampleRate}Hz, {channels}ch"); } - - public int SampleRate => _config?.GetSampleFrequency().GetFrequency() ?? _requestedSampleRate; + + public int SampleRate => _outputSampleRate > 0 ? _outputSampleRate : + _config?.GetSampleFrequency().GetFrequency() ?? _requestedSampleRate; public int Channels => (int?)_config?.GetChannelConfiguration() ?? _requestedChannels; - public int MaxFrameSize => _config?.GetFrameLength() ?? DefaultMaxFrameSize; + public int MaxFrameSize => _isHeAac ? DefaultMaxFrameSize * 2 : + _config?.GetFrameLength() ?? DefaultMaxFrameSize; public AudioCodec Codec => AudioCodec.Aac; public bool IsInitialized => _initialized; - - /// - /// Инициализирует декодер с MP4 Decoder Specific Info (Audio Specific Config). - /// + public void Initialize(byte[] decoderSpecificInfo) { if (_initialized) return; - + if (decoderSpecificInfo == null || decoderSpecificInfo.Length < 2) - { throw new ArgumentException("Invalid decoder specific info", nameof(decoderSpecificInfo)); - } - + try { - _config = DecoderConfig.ParseMP4DecoderSpecificInfo(decoderSpecificInfo); - _decoder = new Decoder(_config); - + _originalAsc = (byte[])decoderSpecificInfo.Clone(); + var ascInfo = ParseAudioSpecificConfig(decoderSpecificInfo); + + Log.Debug($"[AacDecoder] ASC analysis: objectType={ascInfo.ObjectType}, " + + $"baseRate={ascInfo.BaseSampleRate}, outputRate={ascInfo.OutputSampleRate}, " + + $"channels={ascInfo.Channels}, isHE-AAC={ascInfo.IsHeAac}"); + + if (ascInfo.IsHeAac) + InitializeHeAac(ascInfo); + else + InitializeStandard(decoderSpecificInfo); + _sampleBuffer = new SampleBuffer(); _sampleBuffer.SetBigEndian(false); - _initialized = true; - - Log.Debug($"[AacDecoder] Initialized with DSI: profile={_config.GetProfile()}, " + - $"sampleRate={_config.GetSampleFrequency()}, channels={_config.GetChannelConfiguration()}"); } catch (Exception ex) { throw new AudioDecoderException($"Failed to initialize AAC decoder: {ex.Message}"); } } - + + private void InitializeHeAac(AscInfo ascInfo) + { + _isHeAac = true; + _outputSampleRate = ascInfo.OutputSampleRate; + + byte[] modifiedAsc = CreateAacLcAsc(ascInfo); + + Log.Debug($"[AacDecoder] HE-AAC: using modified ASC for core decoder: " + + $"{BitConverter.ToString(modifiedAsc)}"); + + _config = DecoderConfig.ParseMP4DecoderSpecificInfo(modifiedAsc); + _config.SetSBRPresent(true); + _decoder = new Decoder(_config); + + Log.Info($"[AacDecoder] HE-AAC initialized: core={ascInfo.BaseSampleRate}Hz, " + + $"output={_outputSampleRate}Hz, channels={ascInfo.Channels}"); + } + + private void InitializeStandard(byte[] decoderSpecificInfo) + { + _isHeAac = false; + + _config = DecoderConfig.ParseMP4DecoderSpecificInfo(decoderSpecificInfo); + _decoder = new Decoder(_config); + _outputSampleRate = _config.GetSampleFrequency().GetFrequency(); + + Log.Debug($"[AacDecoder] AAC-LC initialized: profile={_config.GetProfile()}, " + + $"sampleRate={_config.GetSampleFrequency()}, channels={_config.GetChannelConfiguration()}"); + } + /// - /// Инициализирует декодер с явными параметрами. + /// Полностью пересоздаёт внутренний декодер из оригинального ASC. + /// Вызывается после seek или при неисправимых ошибках. /// + private void RecreateDecoder() + { + if (_originalAsc == null || _config == null) return; + + try + { + if (_isHeAac) + { + var ascInfo = ParseAudioSpecificConfig(_originalAsc); + byte[] modifiedAsc = CreateAacLcAsc(ascInfo); + _config = DecoderConfig.ParseMP4DecoderSpecificInfo(modifiedAsc); + _config.SetSBRPresent(true); + } + else + { + _config = DecoderConfig.ParseMP4DecoderSpecificInfo(_originalAsc); + } + + _decoder = new Decoder(_config); + _sampleBuffer = new SampleBuffer(); + _sampleBuffer.SetBigEndian(false); + _consecutiveErrors = 0; + + Log.Debug("[AacDecoder] Decoder recreated successfully"); + } + catch (Exception ex) + { + Log.Warn($"[AacDecoder] Recreate failed: {ex.Message}"); + } + } + + private static byte[] CreateAacLcAsc(AscInfo info) + { + int freqIndex = GetSampleFrequencyIndex(info.BaseSampleRate); + int objectType = 2; // AAC-LC + + byte byte0 = (byte)((objectType << 3) | ((freqIndex >> 1) & 0x07)); + byte byte1 = (byte)(((freqIndex & 0x01) << 7) | ((info.Channels & 0x0F) << 3)); + + return [byte0, byte1]; + } + + private static AscInfo ParseAudioSpecificConfig(byte[] asc) + { + if (asc.Length < 2) + { + return new AscInfo + { + ObjectType = 2, BaseSampleRate = 44100, + OutputSampleRate = 44100, Channels = 2, IsHeAac = false + }; + } + + int audioObjectType = (asc[0] >> 3) & 0x1F; + int samplingFrequencyIndex = ((asc[0] & 0x07) << 1) | ((asc[1] >> 7) & 0x01); + int channelConfig = (asc[1] >> 3) & 0x0F; + + int baseSampleRate = GetSampleRateFromIndex(samplingFrequencyIndex); + + bool isHeAac = audioObjectType == 5 || audioObjectType == 29; + int outputSampleRate = baseSampleRate; + if (isHeAac && baseSampleRate > 0) + outputSampleRate = baseSampleRate * 2; + + int channels = channelConfig switch + { + 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 8, _ => 2 + }; + + return new AscInfo + { + ObjectType = audioObjectType, + BaseSampleRate = baseSampleRate, + OutputSampleRate = outputSampleRate, + Channels = channels, + IsHeAac = isHeAac + }; + } + + private static int GetSampleRateFromIndex(int index) + { + int[] rates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0]; + return index >= 0 && index < rates.Length ? rates[index] : 44100; + } + + private static int GetSampleFrequencyIndex(int sampleRate) + { + return sampleRate switch + { + 96000 => 0, 88200 => 1, 64000 => 2, 48000 => 3, 44100 => 4, + 32000 => 5, 24000 => 6, 22050 => 7, 16000 => 8, 12000 => 9, + 11025 => 10, 8000 => 11, 7350 => 12, _ => 4 + }; + } + + private readonly record struct AscInfo + { + public int ObjectType { get; init; } + public int BaseSampleRate { get; init; } + public int OutputSampleRate { get; init; } + public int Channels { get; init; } + public bool IsHeAac { get; init; } + } + public void Initialize(Profile profile, SampleFrequency sampleFrequency, int channels) { if (_initialized) return; - + try { _config = new DecoderConfig(); _config.SetProfile(profile); _config.SetSampleFrequency(sampleFrequency); _config.SetChannelConfiguration((ChannelConfiguration)channels); - + _decoder = new Decoder(_config); - _sampleBuffer = new SampleBuffer(); _sampleBuffer.SetBigEndian(false); - + + _outputSampleRate = sampleFrequency.GetFrequency(); _initialized = true; - + Log.Debug($"[AacDecoder] Initialized: profile={profile}, rate={sampleFrequency}, ch={channels}"); } catch (Exception ex) @@ -95,95 +236,94 @@ public void Initialize(Profile profile, SampleFrequency sampleFrequency, int cha throw new AudioDecoderException($"Failed to initialize AAC decoder: {ex.Message}"); } } - - /// - /// Инициализирует декодер с int sample rate. - /// + public void Initialize(Profile profile, int sampleRate, int channels) { Initialize(profile, GetSampleFrequency(sampleRate), channels); } - - /// - /// Авто-инициализация с defaults если ещё не инициализирован. - /// + private void EnsureInitialized() { if (_initialized) return; - Initialize(Profile.AAC_LC, GetSampleFrequency(_requestedSampleRate), _requestedChannels); } - + public int Decode(ReadOnlySpan encodedData, Span outputBuffer) { ObjectDisposedException.ThrowIf(_disposed, this); - EnsureInitialized(); - + if (encodedData.IsEmpty) { - // PLC: возвращаем тишину int silenceSamples = MaxFrameSize; int totalSamples = silenceSamples * Channels; outputBuffer[..Math.Min(totalSamples, outputBuffer.Length)].Clear(); return silenceSamples; } - + try { byte[] frameData = encodedData.ToArray(); _decoder!.DecodeFrame(frameData, _sampleBuffer!); - + byte[] pcmData = _sampleBuffer!.Data; int sampleCount = pcmData.Length / (sizeof(short) * Channels); - + ConvertBytesToFloat(pcmData, outputBuffer, sampleCount * Channels); - + + _consecutiveErrors = 0; return sampleCount; } - catch (AACException ex) + catch (Exception ex) when (ex is AACException or InvalidCastException or IndexOutOfRangeException) { - Log.Warn($"[AacDecoder] Decode error: {ex.Message}"); - throw new AudioDecoderException($"AAC decode failed: {ex.Message}"); - } - catch (Exception ex) - { - Log.Warn($"[AacDecoder] Unexpected error: {ex.Message}"); - throw new AudioDecoderException($"AAC decode failed: {ex.Message}"); + _consecutiveErrors++; + + if (_consecutiveErrors <= MaxConsecutiveSilentErrors) + { + if (_consecutiveErrors <= 3) + Log.Debug($"[AacDecoder] Decode error (attempt {_consecutiveErrors}): {ex.Message}"); + + // Каждые 5 ошибок пересоздаём декодер + if (_consecutiveErrors % 5 == 0) + RecreateDecoder(); + + int silenceSamples = MaxFrameSize; + int totalSamples = silenceSamples * Channels; + outputBuffer[..Math.Min(totalSamples, outputBuffer.Length)].Clear(); + return silenceSamples; + } + + // Последняя попытка: полное пересоздание + RecreateDecoder(); + + Log.Warn($"[AacDecoder] Persistent decode error after {_consecutiveErrors} attempts: {ex.Message}"); + + // Возвращаем тишину вместо исключения — не крашим воспроизведение + int silence = MaxFrameSize; + int total = silence * Channels; + outputBuffer[..Math.Min(total, outputBuffer.Length)].Clear(); + return silence; } } - + public int DecodeWithReset(ReadOnlySpan encodedData, Span outputBuffer) { - // SharpJaad не имеет explicit reset, пересоздаём декодер - if (_initialized && _config != null) - { - try - { - _decoder = new Decoder(_config); - _sampleBuffer = new SampleBuffer(); - _sampleBuffer.SetBigEndian(false); - } - catch (Exception ex) - { - Log.Warn($"[AacDecoder] Reset failed: {ex.Message}"); - } - } + RecreateDecoder(); return Decode(encodedData, outputBuffer); } - + private static void ConvertBytesToFloat(byte[] pcmBytes, Span output, int sampleCount) { const float scale = 1f / 32768f; int count = Math.Min(sampleCount, output.Length); - + for (int i = 0; i < count; i++) { short sample = BitConverter.ToInt16(pcmBytes, i * sizeof(short)); output[i] = sample * scale; } } - + private static SampleFrequency GetSampleFrequency(int rate) { return rate switch @@ -203,14 +343,15 @@ private static SampleFrequency GetSampleFrequency(int rate) _ => SampleFrequency.SAMPLE_FREQUENCY_44100 }; } - + public void Dispose() { if (_disposed) return; _disposed = true; - + _decoder = null; _config = null; _sampleBuffer = null; + _originalAsc = null; } } \ No newline at end of file diff --git a/Core/Audio/Decoders/OpusDecoder.cs b/Core/Audio/Decoders/OpusDecoder.cs index 3b221d1..44a51f4 100644 --- a/Core/Audio/Decoders/OpusDecoder.cs +++ b/Core/Audio/Decoders/OpusDecoder.cs @@ -13,26 +13,23 @@ public sealed class OpusDecoder : IAudioDecoder private readonly IOpusDecoder _decoder; private readonly short[] _shortBuffer; - private readonly int _sampleRate; - private readonly int _channels; - private readonly int _maxFrameSize; private bool _disposed; public OpusDecoder(int sampleRate = 48000, int channels = 2) { - _sampleRate = sampleRate; - _channels = channels; - _maxFrameSize = sampleRate * MaxFrameDurationMs / 1000; - _shortBuffer = new short[_maxFrameSize * channels]; + SampleRate = sampleRate; + Channels = channels; + MaxFrameSize = sampleRate * MaxFrameDurationMs / 1000; + _shortBuffer = new short[MaxFrameSize * channels]; _decoder = OpusCodecFactory.CreateDecoder(sampleRate, channels); Log.Debug($"[OpusDecoder] Created: {sampleRate}Hz, {channels}ch"); } - - public int SampleRate => _sampleRate; - public int Channels => _channels; - public int MaxFrameSize => _maxFrameSize; + + public int SampleRate { get; } + public int Channels { get; } + public int MaxFrameSize { get; } public AudioCodec Codec => AudioCodec.Opus; public bool IsInitialized => true; // Opus не требует отдельной инициализации @@ -45,10 +42,10 @@ public int Decode(ReadOnlySpan encodedData, Span outputBuffer) try { - Span shortSpan = _shortBuffer.AsSpan(0, _maxFrameSize * _channels); - int samples = _decoder.Decode(encodedData, shortSpan, _maxFrameSize); + Span shortSpan = _shortBuffer.AsSpan(0, MaxFrameSize * Channels); + int samples = _decoder.Decode(encodedData, shortSpan, MaxFrameSize); - ConvertToFloat(shortSpan[..(samples * _channels)], outputBuffer); + ConvertToFloat(shortSpan[..(samples * Channels)], outputBuffer); return samples; } catch (Exception ex) @@ -69,15 +66,15 @@ private int DecodePLC(Span outputBuffer) try { Span shortSpan = _shortBuffer.AsSpan(); - int samples = _decoder.Decode(ReadOnlySpan.Empty, shortSpan, _maxFrameSize); - ConvertToFloat(shortSpan[..(samples * _channels)], outputBuffer); + int samples = _decoder.Decode(ReadOnlySpan.Empty, shortSpan, MaxFrameSize); + ConvertToFloat(shortSpan[..(samples * Channels)], outputBuffer); return samples; } catch { // Fallback: возвращаем тишину (960 samples = 20ms @ 48kHz) const int fallbackSamples = 960; - int totalSamples = fallbackSamples * _channels; + int totalSamples = fallbackSamples * Channels; outputBuffer[..Math.Min(totalSamples, outputBuffer.Length)].Clear(); return fallbackSamples; } diff --git a/Core/Helpers/CircularBuffer.cs b/Core/Audio/Helpers/CircularBuffer.cs similarity index 88% rename from Core/Helpers/CircularBuffer.cs rename to Core/Audio/Helpers/CircularBuffer.cs index ba51bac..2f0ccac 100644 --- a/Core/Helpers/CircularBuffer.cs +++ b/Core/Audio/Helpers/CircularBuffer.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace LMP.Core.Helpers; +namespace LMP.Core.Audio.Helpers; /// /// Lock-free циклический буфер для single-producer-single-consumer сценария. @@ -10,7 +10,6 @@ namespace LMP.Core.Helpers; public sealed class CircularBuffer where T : unmanaged { private readonly T[] _buffer; - private readonly int _capacity; private readonly int _mask; private int _head; // Позиция записи (producer) @@ -22,9 +21,9 @@ public sealed class CircularBuffer where T : unmanaged /// Минимальная ёмкость (округляется до степени 2) public CircularBuffer(int capacity) { - _capacity = RoundUpToPowerOf2(Math.Max(capacity, 16)); - _mask = _capacity - 1; - _buffer = new T[_capacity]; + Capacity = RoundUpToPowerOf2(Math.Max(capacity, 16)); + _mask = Capacity - 1; + _buffer = new T[Capacity]; } /// Текущее количество элементов в буфере @@ -37,18 +36,18 @@ public int Count int head = Volatile.Read(ref _head); // Затем tail (consumer пишет сюда) int tail = Volatile.Read(ref _tail); - return (head - tail + _capacity) & _mask; + return (head - tail + Capacity) & _mask; } } - + /// Ёмкость буфера - public int Capacity => _capacity; - + public int Capacity { get; } + /// Свободное место в буфере (оставляем 1 слот для различения full/empty) public int Available { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _capacity - Count - 1; + get => Capacity - Count - 1; } /// Буфер пуст @@ -70,12 +69,12 @@ public int Write(ReadOnlySpan data) int head = Volatile.Read(ref _head); int tail = Volatile.Read(ref _tail); - int available = (_capacity - 1) - ((head - tail + _capacity) & _mask); + int available = (Capacity - 1) - ((head - tail + Capacity) & _mask); int toWrite = Math.Min(data.Length, available); if (toWrite == 0) return 0; - int firstPart = Math.Min(toWrite, _capacity - head); + int firstPart = Math.Min(toWrite, Capacity - head); // Копируем первую часть (до конца массива) data[..firstPart].CopyTo(_buffer.AsSpan(head, firstPart)); @@ -105,12 +104,12 @@ public int Read(Span output) int tail = Volatile.Read(ref _tail); int head = Volatile.Read(ref _head); - int count = (head - tail + _capacity) & _mask; + int count = (head - tail + Capacity) & _mask; int toRead = Math.Min(output.Length, count); if (toRead == 0) return 0; - int firstPart = Math.Min(toRead, _capacity - tail); + int firstPart = Math.Min(toRead, Capacity - tail); // Копируем первую часть _buffer.AsSpan(tail, firstPart).CopyTo(output[..firstPart]); @@ -136,12 +135,12 @@ public int Peek(Span output) int tail = Volatile.Read(ref _tail); int head = Volatile.Read(ref _head); - int count = (head - tail + _capacity) & _mask; + int count = (head - tail + Capacity) & _mask; int toRead = Math.Min(output.Length, count); if (toRead == 0) return 0; - int firstPart = Math.Min(toRead, _capacity - tail); + int firstPart = Math.Min(toRead, Capacity - tail); _buffer.AsSpan(tail, firstPart).CopyTo(output[..firstPart]); if (toRead > firstPart) @@ -161,7 +160,7 @@ public int Skip(int count) int tail = Volatile.Read(ref _tail); int head = Volatile.Read(ref _head); - int available = (head - tail + _capacity) & _mask; + int available = (head - tail + Capacity) & _mask; int toSkip = Math.Min(count, available); if (toSkip > 0) diff --git a/Core/Audio/Helpers/ConcurrentBitArray.cs b/Core/Audio/Helpers/ConcurrentBitArray.cs index d8dd0a5..b58d602 100644 --- a/Core/Audio/Helpers/ConcurrentBitArray.cs +++ b/Core/Audio/Helpers/ConcurrentBitArray.cs @@ -9,21 +9,20 @@ namespace LMP.Core.Audio.Helpers; public sealed class ConcurrentBitArray(int length) { private readonly int[] _data = new int[(length + 31) / 32]; - private readonly int _length = length; - public int Length => _length; - + public int Length { get; } = length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Get(int index) { - if ((uint)index >= (uint)_length) return false; + if ((uint)index >= (uint)Length) return false; return (Volatile.Read(ref _data[index >> 5]) & (1 << (index & 31))) != 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Set(int index, bool value) { - if ((uint)index >= (uint)_length) return; + if ((uint)index >= (uint)Length) return; int word = index >> 5; int bit = 1 << (index & 31); @@ -45,7 +44,7 @@ public int PopCount() { count += BitOperations.PopCount((uint)Volatile.Read(ref _data[i])); } - return Math.Min(count, _length); + return Math.Min(count, Length); } public void Clear() diff --git a/Core/Helpers/FrameBuffer.cs b/Core/Audio/Helpers/FrameBuffer.cs similarity index 98% rename from Core/Helpers/FrameBuffer.cs rename to Core/Audio/Helpers/FrameBuffer.cs index 3664f9f..b269e67 100644 --- a/Core/Helpers/FrameBuffer.cs +++ b/Core/Audio/Helpers/FrameBuffer.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using LMP.Core.Audio.Interfaces; -namespace LMP.Core.Helpers; +namespace LMP.Core.Audio.Helpers; /// /// Буфер фреймов для producer-consumer паттерна. diff --git a/Core/Helpers/WebMParser.cs b/Core/Audio/Helpers/WebMParser.cs similarity index 95% rename from Core/Helpers/WebMParser.cs rename to Core/Audio/Helpers/WebMParser.cs index 9c695fa..c955d98 100644 --- a/Core/Helpers/WebMParser.cs +++ b/Core/Audio/Helpers/WebMParser.cs @@ -1,7 +1,7 @@ using System.Buffers; using System.Buffers.Binary; -namespace LMP.Core.Helpers; +namespace LMP.Core.Audio.Helpers; /// /// Парсер WebM/Matroska контейнера для извлечения OPUS пакетов. @@ -50,9 +50,6 @@ public sealed class WebMParser : IDisposable private long _timecodeScale = DEFAULT_TIMECODE_SCALE; private long _duration; private int _audioTrackNumber = 1; - private int _sampleRate; - private int _channels = 2; - private byte[]? _codecPrivate; private bool _headersParsed; public readonly record struct CuePoint(long TimeMs, long ClusterOffset); @@ -71,10 +68,10 @@ public WebMParser(Stream stream, int bufferSize = ReadBufferSize) public long DurationMs => _duration * _timecodeScale / 1_000_000; public IReadOnlyList CuePoints => _cuePoints; - public byte[]? CodecPrivate => _codecPrivate; - public int SampleRate => _sampleRate; - public int Channels => _channels; - + public byte[]? CodecPrivate { get; private set; } + public int SampleRate { get; private set; } + public int Channels { get; private set; } = 2; + public async ValueTask ParseHeadersAsync(CancellationToken ct = default) { if (_headersParsed) return true; @@ -149,7 +146,7 @@ public async ValueTask ParseHeadersAsync(CancellationToken ct = default) continue; case TIMECODE_ID: - // *** ИСПРАВЛЕНИЕ: ReadUnsignedInt вместо ReadVInt! *** + // ReadUnsignedInt вместо ReadVInt! var timecodeData = ArrayPool.Shared.Rent((int)size); try { @@ -394,7 +391,7 @@ private async ValueTask ParseInfoAsync(long size, CancellationToken ct) switch (id) { case TIMECODE_SCALE_ID: - // *** ИСПРАВЛЕНИЕ: ReadUnsignedInt! *** + // ReadUnsignedInt! var scaleData = ArrayPool.Shared.Rent((int)elementSize); try { @@ -459,7 +456,7 @@ private async ValueTask ParseTrackEntryAsync(long size, CancellationToken ct) switch (id) { case TRACK_NUMBER_ID: - // *** ИСПРАВЛЕНИЕ: ReadUnsignedInt! *** + // ReadUnsignedInt! var numData = ArrayPool.Shared.Rent((int)elementSize); try { @@ -473,7 +470,7 @@ private async ValueTask ParseTrackEntryAsync(long size, CancellationToken ct) break; case TRACK_TYPE_ID: - // *** ИСПРАВЛЕНИЕ: ReadUnsignedInt! *** + // ReadUnsignedInt! var typeData = ArrayPool.Shared.Rent((int)elementSize); try { @@ -487,8 +484,8 @@ private async ValueTask ParseTrackEntryAsync(long size, CancellationToken ct) break; case CODEC_PRIVATE_ID: - _codecPrivate = new byte[elementSize]; - await ReadExactAsync(_codecPrivate, ct); + CodecPrivate = new byte[elementSize]; + await ReadExactAsync(CodecPrivate, ct); break; case AUDIO_ID: @@ -522,7 +519,7 @@ private async ValueTask ParseAudioSettingsAsync(long size, CancellationToken ct) try { await ReadExactAsync(freqData.AsMemory(0, (int)elementSize), ct); - _sampleRate = (int)ReadFloat(freqData.AsSpan(0, (int)elementSize)); + SampleRate = (int)ReadFloat(freqData.AsSpan(0, (int)elementSize)); } finally { @@ -531,12 +528,12 @@ private async ValueTask ParseAudioSettingsAsync(long size, CancellationToken ct) break; case CHANNELS_ID: - // *** ИСПРАВЛЕНИЕ: ReadUnsignedInt! *** + // ReadUnsignedInt! var chData = ArrayPool.Shared.Rent((int)elementSize); try { await ReadExactAsync(chData.AsMemory(0, (int)elementSize), ct); - _channels = (int)ReadUnsignedInt(chData.AsSpan(0, (int)elementSize)); + Channels = (int)ReadUnsignedInt(chData.AsSpan(0, (int)elementSize)); } finally { @@ -587,7 +584,7 @@ private async ValueTask ParseCuesAsync(long size, CancellationToken ct) switch (id) { case CUE_TIME_ID: - // *** ИСПРАВЛЕНИЕ: ReadUnsignedInt! *** + // ReadUnsignedInt! var timeData = ArrayPool.Shared.Rent((int)elementSize); try { @@ -607,7 +604,7 @@ private async ValueTask ParseCuesAsync(long size, CancellationToken ct) var (posId, posSize) = await ReadElementHeaderAsync(ct); if (posId == CUE_CLUSTER_POSITION_ID) { - // *** ИСПРАВЛЕНИЕ: ReadUnsignedInt! *** + // ReadUnsignedInt! var posData = ArrayPool.Shared.Rent((int)posSize); try { diff --git a/Core/Audio/Interfaces/IPlaybackBackend.cs b/Core/Audio/Interfaces/IPlaybackBackend.cs index 1462f8e..c05f1a9 100644 --- a/Core/Audio/Interfaces/IPlaybackBackend.cs +++ b/Core/Audio/Interfaces/IPlaybackBackend.cs @@ -1,7 +1,6 @@ namespace LMP.Core.Audio.Interfaces; /// -/// Интерфейс бэкенда воспроизведения аудио. /// Абстракция над аудио API операционной системы (WaveOut, ALSA, PulseAudio, etc). /// public interface IPlaybackBackend : IDisposable @@ -9,30 +8,35 @@ public interface IPlaybackBackend : IDisposable /// /// Инициализирует аудио устройство. /// - /// Частота дискретизации - /// Количество каналов - /// Callback для запроса PCM данных + /// Частота дискретизации. + /// Количество каналов. + /// Callback для запроса PCM данных. void Initialize(int sampleRate, int channels, AudioDataCallback dataCallback); - + /// Запускает воспроизведение. void Start(); - - /// Останавливает (ставит на паузу) воспроизведение. + + /// Останавливает (приостанавливает) воспроизведение. void Stop(); - + + /// + /// Очищает внутренний буфер. Вызывается при seek для немедленного + /// перехода к новой позиции без проигрывания устаревших данных. + /// + void Flush(); + /// Громкость (0.0 - 1.0). float Volume { get; set; } - + /// Воспроизводится ли в данный момент. bool IsPlaying { get; } - + /// - /// Количество семплов (samples * channels), находящихся во внутреннем буфере бэкенда, - /// которые были переданы, но еще не воспроизведены динамиками. - /// Используется для точной коррекции отображаемого времени. + /// Количество семплов (samples × channels), находящихся во внутреннем буфере, + /// которые были переданы, но ещё не воспроизведены. /// int BufferedSamples { get; } - + /// Название бэкенда для диагностики. string Name { get; } } @@ -40,6 +44,6 @@ public interface IPlaybackBackend : IDisposable /// /// Callback для запроса PCM данных от бэкенда. /// -/// Буфер для заполнения PCM данными (float32, interleaved) -/// Количество заполненных фреймов (samples / channels) +/// Буфер для заполнения (float32, interleaved). +/// Количество заполненных фреймов (samples / channels). public delegate int AudioDataCallback(Span buffer); \ No newline at end of file diff --git a/Core/Audio/Parsers/Mp4ContainerParser.cs b/Core/Audio/Parsers/Mp4ContainerParser.cs index 0624eb0..df7ec70 100644 --- a/Core/Audio/Parsers/Mp4ContainerParser.cs +++ b/Core/Audio/Parsers/Mp4ContainerParser.cs @@ -5,110 +5,139 @@ namespace LMP.Core.Audio.Parsers; /// /// Парсер MP4/M4A контейнера для извлечения AAC фреймов. -/// Поддерживает обычный MP4 и базовую поддержку fragmented MP4 (fMP4). +/// Поддерживает обычный MP4 и Fragmented MP4 (fMP4). /// public sealed class Mp4ContainerParser : IContainerParser { + #region Fields + private readonly Stream _stream; - + private long _durationMs; private byte[]? _decoderConfig; private int _sampleRate; private int _channels; - + + // Для обычного MP4 private List _samples = []; private int _currentSampleIndex; - - private uint _timeScale = 1; + + // Для fMP4 private bool _isFragmented; + private uint _trackTimeScale = 1; + private uint _defaultSampleDuration; + private uint _defaultSampleSize; + private readonly Queue _fragmentSamples = new(); + + // Для seek в fMP4 + private readonly List _segments = []; + + // Для валидации смещений mdat + private long _lastMdatDataStart; + private long _lastMdatDataEnd; + private bool _disposed; - + + #endregion + + #region Properties + public long DurationMs => _durationMs; public AudioCodec Codec => AudioCodec.Aac; public byte[]? DecoderConfig => _decoderConfig; public int SampleRate => _sampleRate > 0 ? _sampleRate : 44100; public int Channels => _channels > 0 ? _channels : 2; - + + #endregion + public Mp4ContainerParser(Stream stream) { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); } - + + #region Header Parsing + public async ValueTask ParseHeadersAsync(CancellationToken ct = default) { try { _stream.Position = 0; - + bool foundMoov = false; - bool foundMdat = false; - + bool foundMoof = false; + bool foundSidx = false; + while (_stream.Position < _stream.Length) { ct.ThrowIfCancellationRequested(); - + if (_stream.Length - _stream.Position < 8) break; - - var (size, type) = await ReadBoxHeaderAsync(ct); - + + long boxStart = _stream.Position; + var (size, type, headerSize) = await ReadBoxHeaderAsync(ct); + if (size == 0) - size = _stream.Length - _stream.Position + 8; - - long boxEnd = _stream.Position - 8 + size; - + size = _stream.Length - boxStart; + + long boxEnd = boxStart + size; + switch (type) { case "moov": await ParseMoovAsync(boxEnd, ct); foundMoov = true; break; - + + case "sidx": + await ParseSidxAsync(boxStart, boxEnd, ct); + foundSidx = true; + break; + case "moof": _isFragmented = true; - // Для fMP4 без moov — логируем и продолжаем - if (!foundMoov) - { - Log.Debug("[Mp4Parser] Found moof before moov, fragmented MP4"); - } - await SkipToAsync(boxEnd, ct); - break; - - case "mdat": - foundMdat = true; - await SkipToAsync(boxEnd, ct); - break; - - case "sidx": - // Segment index для fMP4 — пропускаем - await SkipToAsync(boxEnd, ct); + foundMoof = true; + if (_decoderConfig != null) + await ParseMoofAsync(boxStart, boxEnd, headerSize, ct); + else + await SkipToAsync(boxEnd, ct); break; - + default: await SkipToAsync(boxEnd, ct); break; } - - // Для обычного MP4: нашли moov и mdat - if (foundMoov && foundMdat && _samples.Count > 0) + + // Для обычного MP4: хватает moov + if (foundMoov && _samples.Count > 0 && !_isFragmented) + break; + + // Для fMP4: нужен moov + (sidx или moof) + if (foundMoov && _isFragmented && (foundSidx || foundMoof)) break; } - + + if (_isFragmented && !foundSidx && _durationMs <= 0) + await EstimateDurationFromMoofsAsync(ct); + + if (_isFragmented) + { + Log.Info($"[Mp4Parser] Fragmented MP4: rate={_sampleRate}, channels={_channels}, " + + $"duration={_durationMs}ms, segments={_segments.Count}"); + + // Перемотка к первому moof для начала чтения + _stream.Position = 0; + await ScanToFirstMoofAsync(ct); + return _decoderConfig != null; + } + if (_samples.Count == 0) { - if (_isFragmented) - { - Log.Warn("[Mp4Parser] Fragmented MP4 detected, sequential reading only"); - return _decoderConfig != null; - } - Log.Error("[Mp4Parser] No samples found"); return false; } - - Log.Debug($"[Mp4Parser] Parsed: {_samples.Count} samples, duration={_durationMs}ms, " + - $"rate={_sampleRate}, channels={_channels}, fragmented={_isFragmented}"); - + + Log.Debug($"[Mp4Parser] Regular MP4: {_samples.Count} samples, duration={_durationMs}ms"); return true; } catch (Exception ex) @@ -117,530 +146,1394 @@ public async ValueTask ParseHeadersAsync(CancellationToken ct = default) return false; } } - + + #endregion + + #region Frame Reading + public async ValueTask ReadNextFrameAsync(CancellationToken ct = default) + { + return _isFragmented + ? await ReadNextFragmentedFrameAsync(ct) + : await ReadNextRegularFrameAsync(ct); + } + + private async ValueTask ReadNextRegularFrameAsync(CancellationToken ct) { if (_currentSampleIndex >= _samples.Count) return null; - + var sample = _samples[_currentSampleIndex++]; - - _stream.Position = sample.Offset; - - var data = ArrayPool.Shared.Rent(sample.Size); + return await ReadSampleAsFrameAsync(sample, ct); + } + + private async ValueTask ReadNextFragmentedFrameAsync(CancellationToken ct) + { + // Если очередь фреймов пуста — нужно распарсить следующий фрагмент + while (_fragmentSamples.Count == 0) + { + if (_stream.Position >= _stream.Length) + return null; + + if (!await ScanAndParseNextFragmentAsync(ct)) + return null; + } + + var sample = _fragmentSamples.Dequeue(); + + // Валидация: offset и size должны быть в пределах файла + if (!ValidateSample(sample)) + { + Log.Warn($"[Mp4Parser] Skipping invalid sample: offset={sample.Offset}, " + + $"size={sample.Size}, streamLen={_stream.Length}"); + // Пропускаем невалидный sample, пробуем следующий + return _fragmentSamples.Count > 0 + ? await ReadNextFragmentedFrameAsync(ct) + : null; + } + + return await ReadSampleAsFrameAsync(sample, ct); + } + + /// + /// Проверяет что sample offset и size находятся в допустимых границах. + /// + private bool ValidateSample(SampleInfo sample) + { + if (sample.Offset < 0 || sample.Size <= 0) + return false; + + if (sample.Offset + sample.Size > _stream.Length) + return false; + + return true; + } + + /// + /// Читает данные sample из stream и возвращает AudioFrame. + /// + private async ValueTask ReadSampleAsFrameAsync(SampleInfo sample, CancellationToken ct) + { try { - await ReadExactlyAsync(data.AsMemory(0, sample.Size), ct); - - // Копируем в новый массив нужного размера + _stream.Position = sample.Offset; + var frameData = new byte[sample.Size]; - Buffer.BlockCopy(data, 0, frameData, 0, sample.Size); - + int totalRead = 0; + + while (totalRead < sample.Size) + { + int read = await _stream.ReadAsync( + frameData.AsMemory(totalRead, sample.Size - totalRead), ct); + + if (read == 0) + { + // Неожиданный конец потока + Log.Warn($"[Mp4Parser] Unexpected EOF at offset {sample.Offset + totalRead}, " + + $"expected {sample.Size} bytes, got {totalRead}"); + return null; + } + + totalRead += read; + } + return new AudioFrame { Data = frameData, TimestampMs = sample.TimestampMs, DurationMs = sample.DurationMs, - IsKeyFrame = true + IsKeyFrame = sample.IsKeyFrame }; } - finally + catch (EndOfStreamException) { - ArrayPool.Shared.Return(data); + Log.Warn($"[Mp4Parser] EOF reading sample at offset={sample.Offset}, size={sample.Size}"); + return null; + } + catch (IOException ex) + { + Log.Warn($"[Mp4Parser] IO error reading sample: {ex.Message}"); + return null; } } - - public (long BytePosition, long TimestampMs)? FindSeekPosition(long targetMs) + + #endregion + + #region Fragment Scanning + + /// + /// Сканирует поток в поисках следующей пары moof+mdat. + /// + private async Task ScanAndParseNextFragmentAsync(CancellationToken ct) { - if (_samples.Count == 0) return null; - - int left = 0, right = _samples.Count - 1; - - while (left < right) + long moofStart = -1; + long moofEnd = -1; + + while (_stream.Position < _stream.Length) { - int mid = (left + right + 1) / 2; - if (_samples[mid].TimestampMs <= targetMs) - left = mid; + ct.ThrowIfCancellationRequested(); + + if (_stream.Length - _stream.Position < 8) + return false; + + long boxStart = _stream.Position; + var (size, type, headerSize) = await ReadBoxHeaderAsync(ct); + + if (size == 0) + size = _stream.Length - boxStart; + + long boxEnd = boxStart + size; + + // Защита от невалидного размера + if (boxEnd > _stream.Length) + { + Log.Warn($"[Mp4Parser] Box '{type}' extends past stream end: " + + $"start={boxStart}, size={size}, streamLen={_stream.Length}"); + return false; + } + + switch (type) + { + case "moof": + moofStart = boxStart; + moofEnd = boxEnd; + await ParseMoofAsync(boxStart, boxEnd, headerSize, ct); + break; + + case "mdat": + _lastMdatDataStart = _stream.Position; // сразу после header mdat + _lastMdatDataEnd = boxEnd; + + if (moofStart >= 0 && _fragmentSamples.Count > 0) + { + ValidateAndCorrectSampleOffsets(moofStart, moofEnd); + // Позиционируемся после mdat для следующего вызова + await SkipToAsync(boxEnd, ct); + return _fragmentSamples.Count > 0; + } + + await SkipToAsync(boxEnd, ct); + break; + + default: + await SkipToAsync(boxEnd, ct); + break; + } + } + + return false; + } + + /// + /// Проверяет и корректирует offsets samples относительно текущего mdat. + /// + private void ValidateAndCorrectSampleOffsets(long moofStart, long moofEnd) + { + if (_fragmentSamples.Count == 0) + return; + + var samples = _fragmentSamples.ToArray(); + _fragmentSamples.Clear(); + + long firstOffset = samples[0].Offset; + + // Case 1: offsets уже корректны (в пределах mdat) + if (firstOffset >= _lastMdatDataStart && firstOffset < _lastMdatDataEnd) + { + foreach (var s in samples) + { + if (s.Offset >= _lastMdatDataStart + && s.Offset + s.Size <= _lastMdatDataEnd) + { + _fragmentSamples.Enqueue(s); + } + else + { + Log.Warn($"[Mp4Parser] Dropping out-of-bounds sample: " + + $"offset={s.Offset}, size={s.Size}, " + + $"mdat=[{_lastMdatDataStart}..{_lastMdatDataEnd})"); + } + } + return; + } + + // Case 2: offsets относительны — корректируем + long correction = _lastMdatDataStart - firstOffset; + + // Дополнительная эвристика: если firstOffset == 0 или маленький, + // скорее всего data_offset от начала moof + if (firstOffset >= moofStart && firstOffset < moofEnd) + { + // data_offset относительно moof — пересчитываем + // (ничего не нужно, correction уже правильный) + } + + foreach (var sample in samples) + { + var corrected = sample with { Offset = sample.Offset + correction }; + + if (corrected.Offset >= _lastMdatDataStart + && corrected.Offset + corrected.Size <= _lastMdatDataEnd) + { + _fragmentSamples.Enqueue(corrected); + } else - right = mid - 1; + { + Log.Warn($"[Mp4Parser] Dropping corrected sample: " + + $"original={sample.Offset}, corrected={corrected.Offset}, " + + $"size={sample.Size}, mdat=[{_lastMdatDataStart}..{_lastMdatDataEnd})"); + } } - - var sample = _samples[left]; - _currentSampleIndex = left; - + + if (_fragmentSamples.Count == 0 && samples.Length > 0) + { + Log.Error($"[Mp4Parser] ALL samples dropped after correction! " + + $"firstOffset={firstOffset}, correction={correction}, " + + $"mdat=[{_lastMdatDataStart}..{_lastMdatDataEnd})"); + } + } + + #endregion + + #region Seek + + public (long BytePosition, long TimestampMs)? FindSeekPosition(long targetMs) + { + if (_isFragmented) + return FindSeekPositionFragmented(targetMs); + + if (_samples.Count == 0) + return null; + + int index = BinarySearchSample(targetMs); + var sample = _samples[index]; + _currentSampleIndex = index; + return (sample.Offset, sample.TimestampMs); } - + + /// + /// Находит позицию для seek в fMP4 по индексу сегментов (sidx). + /// + private (long BytePosition, long TimestampMs)? FindSeekPositionFragmented(long targetMs) + { + if (_segments.Count == 0) + { + Log.Warn("[Mp4Parser] No segment index for fMP4 seek"); + return (0, 0); + } + + long targetTime = targetMs * _trackTimeScale / 1000; + int segmentIndex = BinarySearchSegment(targetTime); + + if (segmentIndex < 0 || segmentIndex >= _segments.Count) + return (0, 0); + + var segment = _segments[segmentIndex]; + long timestampMs = segment.TimeOffset * 1000 / _trackTimeScale; + + Log.Debug($"[Mp4Parser] Seek to segment {segmentIndex}: " + + $"byteOffset={segment.ByteOffset}, timeMs={timestampMs} (target={targetMs}ms)"); + + return (segment.ByteOffset, timestampMs); + } + public void Reset() { _currentSampleIndex = 0; + _fragmentSamples.Clear(); + // Сбрасываем данные о последнем mdat — после seek они невалидны + _lastMdatDataStart = 0; + _lastMdatDataEnd = 0; } - - #region Exact Reading - - /// - /// Читает ровно указанное количество байт или выбрасывает исключение. - /// - private async ValueTask ReadExactlyAsync(Memory buffer, CancellationToken ct) + + #endregion + + #region sidx Parsing + + private async Task ParseSidxAsync(long boxStart, long boxEnd, CancellationToken ct) { - int totalRead = 0; - while (totalRead < buffer.Length) + int version = await ReadByteAsync(ct); + await SkipBytesAsync(3, ct); // flags + + await SkipBytesAsync(4, ct); // reference_ID + uint timescale = await ReadUInt32BEAsync(ct); + + if (timescale == 0) + timescale = _trackTimeScale > 0 ? _trackTimeScale : 44100; + + long firstOffset; + + if (version == 0) + { + await ReadUInt32BEAsync(ct); // earliest_presentation_time + firstOffset = await ReadUInt32BEAsync(ct); + } + else + { + await ReadUInt64BEAsync(ct); // earliest_presentation_time + firstOffset = (long)await ReadUInt64BEAsync(ct); + } + + await SkipBytesAsync(2, ct); // reserved + ushort referenceCount = await ReadUInt16BEAsync(ct); + + _segments.Clear(); + long totalDuration = 0; + // sidx указывает смещения от конца sidx бокса + firstOffset + long currentByteOffset = boxEnd + firstOffset; + + for (int i = 0; i < referenceCount; i++) + { + uint referenceInfo = await ReadUInt32BEAsync(ct); + uint referencedSize = referenceInfo & 0x7FFFFFFF; + + uint subsegmentDuration = await ReadUInt32BEAsync(ct); + await SkipBytesAsync(4, ct); // SAP info + + _segments.Add(new SegmentInfo + { + ByteOffset = currentByteOffset, + TimeOffset = totalDuration, + Duration = subsegmentDuration, + Size = referencedSize + }); + + currentByteOffset += referencedSize; + totalDuration += subsegmentDuration; + } + + long durationMs = totalDuration * 1000 / timescale; + + Log.Debug($"[Mp4Parser] sidx: {referenceCount} segments, timescale={timescale}, duration={durationMs}ms"); + + if (durationMs > _durationMs) + _durationMs = durationMs; + + if (_trackTimeScale == 1) + _trackTimeScale = timescale; + + await SkipToAsync(boxEnd, ct); + } + + #endregion + + #region moof/traf/trun Parsing + + private async Task ParseMoofAsync(long moofStart, long moofEnd, int moofHeaderSize, CancellationToken ct) + { + _fragmentSamples.Clear(); + + while (_stream.Position < moofEnd) + { + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + if (type == "traf") + await ParseTrafAsync(childEnd, moofStart, moofEnd, ct); + else + await SkipToAsync(childEnd, ct); + } + } + + private async Task ParseTrafAsync(long boxEnd, long moofStart, long moofEnd, CancellationToken ct) + { + uint sampleDuration = _defaultSampleDuration; + uint sampleSize = _defaultSampleSize; + long baseDataOffset = moofStart; + long baseMediaDecodeTime = 0; + + long savedPosition = _stream.Position; + + // First pass: find tfhd + while (_stream.Position < boxEnd) + { + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + if (type == "tfhd") + { + var tfhd = await ParseTfhdAsync(ct); + + if (tfhd.HasBaseDataOffset) + baseDataOffset = tfhd.BaseDataOffset; + else if (tfhd.DefaultBaseIsMoof) + baseDataOffset = moofStart; + + if (tfhd.HasDefaultSampleDuration) + sampleDuration = tfhd.DefaultSampleDuration; + if (tfhd.HasDefaultSampleSize) + sampleSize = tfhd.DefaultSampleSize; + break; + } + + await SkipToAsync(childEnd, ct); + } + + // Second pass: parse tfdt + trun + _stream.Position = savedPosition; + + while (_stream.Position < boxEnd) + { + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + switch (type) + { + case "tfdt": + baseMediaDecodeTime = await ParseTfdtAsync(ct); + break; + + case "trun": + await ParseTrunAsync(baseDataOffset, moofEnd, baseMediaDecodeTime, + sampleDuration, sampleSize, ct); + break; + } + + await SkipToAsync(childEnd, ct); + } + } + + private async Task ParseTrunAsync( + long baseDataOffset, + long moofEnd, + long baseMediaDecodeTime, + uint defaultDuration, + uint defaultSize, + CancellationToken ct) + { + uint versionFlags = await ReadUInt32BEAsync(ct); + uint version = versionFlags >> 24; + uint flags = versionFlags & 0xFFFFFF; + + uint sampleCount = await ReadUInt32BEAsync(ct); + var flagsInfo = new TrunFlags(flags); + + int dataOffset = 0; + if (flagsInfo.HasDataOffset) + dataOffset = await ReadInt32BEAsync(ct); + + if (flagsInfo.HasFirstSampleFlags) + await SkipBytesAsync(4, ct); + + long currentOffset = baseDataOffset + dataOffset; + long currentTime = baseMediaDecodeTime; + + for (uint i = 0; i < sampleCount; i++) + { + uint duration = defaultDuration; + uint size = defaultSize; + uint sampleFlags = 0; + int compositionTimeOffset = 0; + + if (flagsInfo.HasSampleDuration) duration = await ReadUInt32BEAsync(ct); + if (flagsInfo.HasSampleSize) size = await ReadUInt32BEAsync(ct); + if (flagsInfo.HasSampleFlags) sampleFlags = await ReadUInt32BEAsync(ct); + + if (flagsInfo.HasSampleCompositionTimeOffset) + { + compositionTimeOffset = version == 0 + ? (int)await ReadUInt32BEAsync(ct) + : await ReadInt32BEAsync(ct); + } + + long timestampMs = _trackTimeScale > 0 + ? (currentTime + compositionTimeOffset) * 1000 / _trackTimeScale + : 0; + + int durationMs = _trackTimeScale > 0 + ? (int)(duration * 1000 / _trackTimeScale) + : 23; + + bool isKeyFrame = (sampleFlags & 0x00010000) == 0; + + _fragmentSamples.Enqueue(new SampleInfo + { + Offset = currentOffset, + Size = (int)size, + TimestampMs = timestampMs, + DurationMs = durationMs, + IsKeyFrame = isKeyFrame + }); + + currentOffset += size; + currentTime += duration; + } + } + + #endregion + + #region moov Parsing + + private async Task ParseMoovAsync(long boxEnd, CancellationToken ct) + { + while (_stream.Position < boxEnd) + { + ct.ThrowIfCancellationRequested(); + + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + switch (type) + { + case "trak": + await ParseTrakAsync(childEnd, ct); + break; + case "mvhd": + await ParseMvhdAsync(childEnd, ct); + break; + case "mvex": + _isFragmented = true; + await ParseMvexAsync(childEnd, ct); + break; + default: + await SkipToAsync(childEnd, ct); + break; + } + } + } + + private async Task ParseMvhdAsync(long boxEnd, CancellationToken ct) + { + int version = await ReadByteAsync(ct); + await SkipBytesAsync(3, ct); + + uint timescale; + long duration; + + if (version == 1) + { + await SkipBytesAsync(16, ct); + timescale = await ReadUInt32BEAsync(ct); + duration = (long)await ReadUInt64BEAsync(ct); + } + else + { + await SkipBytesAsync(8, ct); + timescale = await ReadUInt32BEAsync(ct); + duration = await ReadUInt32BEAsync(ct); + } + + if (_trackTimeScale == 1) + _trackTimeScale = timescale; + + if (duration > 0 && timescale > 0) + { + _durationMs = duration * 1000 / timescale; + Log.Debug($"[Mp4Parser] mvhd: timescale={timescale}, duration={_durationMs}ms"); + } + + await SkipToAsync(boxEnd, ct); + } + + private async Task ParseMvexAsync(long boxEnd, CancellationToken ct) + { + while (_stream.Position < boxEnd) + { + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + if (type == "trex") + await ParseTrexAsync(ct); + + await SkipToAsync(childEnd, ct); + } + } + + private async Task ParseTrexAsync(CancellationToken ct) + { + await SkipBytesAsync(4, ct); // version + flags + await SkipBytesAsync(4, ct); // track_ID + await SkipBytesAsync(4, ct); // default_sample_description_index + _defaultSampleDuration = await ReadUInt32BEAsync(ct); + _defaultSampleSize = await ReadUInt32BEAsync(ct); + } + + private async Task ParseTrakAsync(long boxEnd, CancellationToken ct) + { + while (_stream.Position < boxEnd) + { + ct.ThrowIfCancellationRequested(); + + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + if (type == "mdia") + await ParseMdiaAsync(childEnd, ct); + else + await SkipToAsync(childEnd, ct); + } + } + + private async Task<(bool IsAudio, uint TimeScale)> ParseMdiaAsync(long boxEnd, CancellationToken ct) + { + bool isAudioTrack = false; + uint trackTimeScale = 1; + + while (_stream.Position < boxEnd) + { + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + switch (type) + { + case "hdlr": + isAudioTrack = await ParseHdlrAsync(ct); + break; + case "mdhd": + trackTimeScale = await ParseMdhdAsync(ct, isAudioTrack); + break; + case "minf" when isAudioTrack: + await ParseMinfAsync(childEnd, trackTimeScale, ct); + break; + } + + await SkipToAsync(childEnd, ct); + } + + return (isAudioTrack, trackTimeScale); + } + + private async Task ParseHdlrAsync(CancellationToken ct) + { + await SkipBytesAsync(8, ct); + var buf = new byte[4]; + await ReadExactlyAsync(buf, ct); + return buf[0] == 's' && buf[1] == 'o' && buf[2] == 'u' && buf[3] == 'n'; + } + + private async Task ParseMdhdAsync(CancellationToken ct, bool isAudioTrack) + { + int version = await ReadByteAsync(ct); + await SkipBytesAsync(3, ct); + + uint trackTimeScale; + + if (version == 1) + { + await SkipBytesAsync(16, ct); + trackTimeScale = await ReadUInt32BEAsync(ct); + } + else + { + await SkipBytesAsync(8, ct); + trackTimeScale = await ReadUInt32BEAsync(ct); + } + + if (isAudioTrack || _trackTimeScale == 1) + _trackTimeScale = trackTimeScale; + + return trackTimeScale; + } + + private async Task ParseMinfAsync(long boxEnd, uint timeScale, CancellationToken ct) + { + while (_stream.Position < boxEnd) + { + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + if (type == "stbl") + await ParseStblAsync(childEnd, timeScale, ct); + else + await SkipToAsync(childEnd, ct); + } + } + + #endregion + + #region stbl Parsing (Regular MP4) + + private async Task ParseStblAsync(long boxEnd, uint timeScale, CancellationToken ct) + { + List sampleSizes = []; + List<(uint firstChunk, uint samplesPerChunk, uint descriptionIndex)> stsc = []; + List chunkOffsets = []; + List<(uint count, uint delta)> stts = []; + + while (_stream.Position < boxEnd) + { + ct.ThrowIfCancellationRequested(); + + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + switch (type) + { + case "stsd": await ParseStsdAsync(childEnd, ct); break; + case "stsz": sampleSizes = await ParseStszAsync(ct); break; + case "stsc": stsc = await ParseStscAsync(ct); break; + case "stco": chunkOffsets = await ParseStcoAsync(ct); break; + case "co64": chunkOffsets = await ParseCo64Async(ct); break; + case "stts": stts = await ParseSttsAsync(ct); break; + } + + await SkipToAsync(childEnd, ct); + } + + BuildSampleTable(sampleSizes, stsc, chunkOffsets, stts, timeScale); + } + + private async Task> ParseStszAsync(CancellationToken ct) + { + await SkipBytesAsync(4, ct); + uint defaultSize = await ReadUInt32BEAsync(ct); + uint count = await ReadUInt32BEAsync(ct); + + var sizes = new List((int)count); + for (uint i = 0; i < count; i++) + sizes.Add(defaultSize == 0 ? await ReadUInt32BEAsync(ct) : defaultSize); + return sizes; + } + + private async Task> ParseStscAsync(CancellationToken ct) + { + await SkipBytesAsync(4, ct); + uint entryCount = await ReadUInt32BEAsync(ct); + + var result = new List<(uint, uint, uint)>((int)entryCount); + for (uint i = 0; i < entryCount; i++) + { + uint firstChunk = await ReadUInt32BEAsync(ct); + uint samplesPerChunk = await ReadUInt32BEAsync(ct); + uint descIndex = await ReadUInt32BEAsync(ct); + result.Add((firstChunk, samplesPerChunk, descIndex)); + } + return result; + } + + private async Task> ParseStcoAsync(CancellationToken ct) + { + await SkipBytesAsync(4, ct); + uint count = await ReadUInt32BEAsync(ct); + + var offsets = new List((int)count); + for (uint i = 0; i < count; i++) + offsets.Add(await ReadUInt32BEAsync(ct)); + return offsets; + } + + private async Task> ParseCo64Async(CancellationToken ct) + { + await SkipBytesAsync(4, ct); + uint count = await ReadUInt32BEAsync(ct); + + var offsets = new List((int)count); + for (uint i = 0; i < count; i++) + offsets.Add((long)await ReadUInt64BEAsync(ct)); + return offsets; + } + + private async Task> ParseSttsAsync(CancellationToken ct) + { + await SkipBytesAsync(4, ct); + uint count = await ReadUInt32BEAsync(ct); + + var result = new List<(uint, uint)>((int)count); + for (uint i = 0; i < count; i++) + { + uint sampleCount = await ReadUInt32BEAsync(ct); + uint sampleDelta = await ReadUInt32BEAsync(ct); + result.Add((sampleCount, sampleDelta)); + } + return result; + } + + private async Task ParseStsdAsync(long boxEnd, CancellationToken ct) + { + await SkipBytesAsync(4, ct); + uint entryCount = await ReadUInt32BEAsync(ct); + if (entryCount == 0) return; + + long entryStart = _stream.Position; + var (size, _, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) return; + + long entryEnd = entryStart + size; + + // Skip reserved + data_reference_index + await SkipBytesAsync(6 + 2 + 8, ct); + + _channels = await ReadUInt16BEAsync(ct); + await SkipBytesAsync(2 + 4, ct); // sampleSize + reserved + _sampleRate = (int)(await ReadUInt32BEAsync(ct) >> 16); + + // Parse nested boxes (esds) + while (_stream.Position < entryEnd) + { + long nestedStart = _stream.Position; + var (boxSize, boxType, _) = await ReadBoxHeaderAsync(ct); + if (boxSize == 0) break; + + long nestedEnd = nestedStart + boxSize; + + if (boxType == "esds") + { + await ParseEsdsAsync(ct); + break; + } + + await SkipToAsync(nestedEnd, ct); + } + + await SkipToAsync(entryEnd, ct); + } + + private async Task ParseEsdsAsync(CancellationToken ct) + { + await SkipBytesAsync(4, ct); // version + flags + + var tagBuf = new byte[1]; + + // ES_Descriptor tag (0x03) + await ReadExactlyAsync(tagBuf, ct); + if (tagBuf[0] != 0x03) return; + + await ReadExpandableLengthAsync(ct); + await SkipBytesAsync(2, ct); // ES_ID + + await ReadExactlyAsync(tagBuf, ct); + int flags = tagBuf[0]; + + if ((flags & 0x80) != 0) await SkipBytesAsync(2, ct); + if ((flags & 0x40) != 0) + { + await ReadExactlyAsync(tagBuf, ct); + await SkipBytesAsync(tagBuf[0], ct); + } + if ((flags & 0x20) != 0) await SkipBytesAsync(2, ct); + + // DecoderConfigDescriptor tag (0x04) + await ReadExactlyAsync(tagBuf, ct); + if (tagBuf[0] != 0x04) return; + + await ReadExpandableLengthAsync(ct); + await SkipBytesAsync(13, ct); + + // DecoderSpecificInfo tag (0x05) + await ReadExactlyAsync(tagBuf, ct); + if (tagBuf[0] != 0x05) return; + + int dsiLength = await ReadExpandableLengthAsync(ct); + if (dsiLength <= 0 || dsiLength > 64) return; + + var fullDsi = new byte[dsiLength]; + await ReadExactlyAsync(fullDsi, ct); + + // Trim trailing zeros + int realLength = dsiLength; + while (realLength > 2 && fullDsi[realLength - 1] == 0) + realLength--; + realLength = Math.Max(2, realLength); + + _decoderConfig = realLength < dsiLength ? fullDsi[..realLength] : fullDsi; + + if (realLength < dsiLength) + Log.Debug($"[Mp4Parser] Trimmed ASC: {dsiLength} → {realLength} bytes (removed padding)"); + + ParseAudioSpecificConfig(_decoderConfig); + Log.Debug($"[Mp4Parser] Decoder config ({_decoderConfig.Length} bytes): " + + $"{BitConverter.ToString(_decoderConfig)}"); + } + + private void ParseAudioSpecificConfig(byte[] asc) + { + if (asc.Length < 2) return; + + int audioObjectType = (asc[0] >> 3) & 0x1F; + int samplingFrequencyIndex = ((asc[0] & 0x07) << 1) | ((asc[1] >> 7) & 0x01); + int channelConfig = (asc[1] >> 3) & 0x0F; + + int[] sampleRates = + { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 + }; + + int baseSampleRate = samplingFrequencyIndex < sampleRates.Length + ? sampleRates[samplingFrequencyIndex] : 0; + int outputSampleRate = baseSampleRate; + + bool isHeAac = audioObjectType == 5 || audioObjectType == 29; + if (isHeAac && baseSampleRate > 0) + outputSampleRate = baseSampleRate * 2; + + Log.Debug($"[Mp4Parser] ASC parsed: objectType={audioObjectType}, " + + $"freqIndex={samplingFrequencyIndex}, baseRate={baseSampleRate}, " + + $"outputRate={outputSampleRate}, channels={channelConfig}, isHE-AAC={isHeAac}"); + + if (outputSampleRate > 0) _sampleRate = outputSampleRate; + + if (channelConfig > 0 && channelConfig <= 7) + { + int[] channelCounts = { 0, 1, 2, 3, 4, 5, 6, 8 }; + _channels = channelCounts[channelConfig]; + } + } + + private void BuildSampleTable( + List sampleSizes, + List<(uint firstChunk, uint samplesPerChunk, uint descriptionIndex)> stsc, + List chunkOffsets, + List<(uint count, uint delta)> stts, + uint timeScale) + { + if (sampleSizes.Count == 0 || chunkOffsets.Count == 0) return; + + _samples = new List(sampleSizes.Count); + var timestamps = BuildTimestamps(stts); + + int sampleIndex = 0; + int stscIndex = 0; + + for (int chunkIndex = 0; chunkIndex < chunkOffsets.Count && sampleIndex < sampleSizes.Count; chunkIndex++) + { + while (stscIndex + 1 < stsc.Count && stsc[stscIndex + 1].firstChunk - 1 <= chunkIndex) + stscIndex++; + + uint samplesInChunk = stsc.Count > 0 ? stsc[stscIndex].samplesPerChunk : 1; + long offset = chunkOffsets[chunkIndex]; + + for (uint i = 0; i < samplesInChunk && sampleIndex < sampleSizes.Count; i++) + { + long timestampMs = timestamps.Count > sampleIndex + ? timestamps[sampleIndex] * 1000 / timeScale + : sampleIndex * 20; + + int durationMs = sampleIndex + 1 < timestamps.Count + ? (int)((timestamps[sampleIndex + 1] - timestamps[sampleIndex]) * 1000 / timeScale) + : 20; + + _samples.Add(new SampleInfo + { + Offset = offset, + Size = (int)sampleSizes[sampleIndex], + TimestampMs = timestampMs, + DurationMs = durationMs, + IsKeyFrame = true + }); + + offset += sampleSizes[sampleIndex]; + sampleIndex++; + } + } + + if (_samples.Count > 0) + _durationMs = _samples[^1].TimestampMs + _samples[^1].DurationMs; + } + + private static List BuildTimestamps(List<(uint count, uint delta)> stts) + { + int totalSamples = stts.Sum(x => (int)x.count); + var timestamps = new List(totalSamples); + long time = 0; + + foreach (var (count, delta) in stts) + { + for (uint i = 0; i < count; i++) + { + timestamps.Add(time); + time += delta; + } + } + + return timestamps; + } + + #endregion + + #region Duration Estimation (fallback) + + private async Task EstimateDurationFromMoofsAsync(CancellationToken ct) + { + long savedPosition = _stream.Position; + _stream.Position = 0; + + long lastTimestamp = 0; + long lastDuration = 0; + + try + { + while (_stream.Position < _stream.Length) + { + if (_stream.Length - _stream.Position < 8) break; + + long boxStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long boxEnd = boxStart + size; + + if (type == "moof") + { + var (timestamp, duration) = await QuickParseMoofForTimingAsync(boxEnd, ct); + if (timestamp >= 0) + { + _segments.Add(new SegmentInfo + { + ByteOffset = boxStart, + TimeOffset = timestamp, + Duration = duration, + Size = (uint)(boxEnd - boxStart) + }); + + lastTimestamp = timestamp; + lastDuration = duration; + } + } + + await SkipToAsync(boxEnd, ct); + } + + if (lastTimestamp > 0 && _trackTimeScale > 0) + { + long estimatedMs = (lastTimestamp + lastDuration) * 1000 / _trackTimeScale; + if (estimatedMs > _durationMs) + { + _durationMs = estimatedMs; + Log.Debug($"[Mp4Parser] Estimated duration: {_durationMs}ms, " + + $"{_segments.Count} segments"); + } + } + } + finally + { + _stream.Position = savedPosition; + } + } + + private async Task<(long timestamp, long duration)> QuickParseMoofForTimingAsync( + long boxEnd, CancellationToken ct) + { + long timestamp = -1; + long totalDuration = 0; + + while (_stream.Position < boxEnd) + { + long childStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + if (size == 0) break; + + long childEnd = childStart + size; + + if (type == "traf") + { + while (_stream.Position < childEnd) + { + long trafChildStart = _stream.Position; + var (trafChildSize, trafChildType, _) = await ReadBoxHeaderAsync(ct); + if (trafChildSize == 0) break; + + long trafChildEnd = trafChildStart + trafChildSize; + + if (trafChildType == "tfdt") + timestamp = await ParseTfdtAsync(ct); + else if (trafChildType == "trun") + totalDuration = await QuickParseTrunDurationAsync(ct); + + await SkipToAsync(trafChildEnd, ct); + } + } + + await SkipToAsync(childEnd, ct); + } + + return (timestamp, totalDuration); + } + + private async Task QuickParseTrunDurationAsync(CancellationToken ct) + { + uint versionFlags = await ReadUInt32BEAsync(ct); + uint flags = versionFlags & 0xFFFFFF; + + uint sampleCount = await ReadUInt32BEAsync(ct); + + bool hasDataOffset = (flags & 0x000001) != 0; + bool hasFirstSampleFlags = (flags & 0x000004) != 0; + bool hasSampleDuration = (flags & 0x000100) != 0; + bool hasSampleSize = (flags & 0x000200) != 0; + bool hasSampleFlags = (flags & 0x000400) != 0; + bool hasSampleCTO = (flags & 0x000800) != 0; + + if (hasDataOffset) await SkipBytesAsync(4, ct); + if (hasFirstSampleFlags) await SkipBytesAsync(4, ct); + + long totalDuration = 0; + + for (uint i = 0; i < sampleCount; i++) + { + uint duration = _defaultSampleDuration; + if (hasSampleDuration) duration = await ReadUInt32BEAsync(ct); + if (hasSampleSize) await SkipBytesAsync(4, ct); + if (hasSampleFlags) await SkipBytesAsync(4, ct); + if (hasSampleCTO) await SkipBytesAsync(4, ct); + totalDuration += duration; + } + + return totalDuration; + } + + #endregion + + #region Helper Methods + + private async Task ScanToFirstMoofAsync(CancellationToken ct) + { + while (_stream.Position < _stream.Length) + { + if (_stream.Length - _stream.Position < 8) break; + + long boxStart = _stream.Position; + var (size, type, _) = await ReadBoxHeaderAsync(ct); + + if (type == "moof") + { + _stream.Position = boxStart; + return; + } + + if (size == 0) break; + await SkipToAsync(boxStart + size, ct); + } + } + + private readonly record struct TfhdResult( + bool HasBaseDataOffset, long BaseDataOffset, + bool DefaultBaseIsMoof, + bool HasDefaultSampleDuration, uint DefaultSampleDuration, + bool HasDefaultSampleSize, uint DefaultSampleSize); + + private async Task ParseTfhdAsync(CancellationToken ct) + { + uint versionFlags = await ReadUInt32BEAsync(ct); + uint flags = versionFlags & 0xFFFFFF; + + await SkipBytesAsync(4, ct); // track_ID + + long baseDataOffset = 0; + uint defaultSampleDuration = 0; + uint defaultSampleSize = 0; + + bool hasBaseDataOffset = (flags & 0x000001) != 0; + bool hasSampleDescriptionIndex = (flags & 0x000002) != 0; + bool hasDefaultSampleDuration = (flags & 0x000008) != 0; + bool hasDefaultSampleSize = (flags & 0x000010) != 0; + bool hasDefaultSampleFlags = (flags & 0x000020) != 0; + bool defaultBaseIsMoof = (flags & 0x020000) != 0; + + if (hasBaseDataOffset) baseDataOffset = (long)await ReadUInt64BEAsync(ct); + if (hasSampleDescriptionIndex) await SkipBytesAsync(4, ct); + if (hasDefaultSampleDuration) defaultSampleDuration = await ReadUInt32BEAsync(ct); + if (hasDefaultSampleSize) defaultSampleSize = await ReadUInt32BEAsync(ct); + if (hasDefaultSampleFlags) await SkipBytesAsync(4, ct); + + return new TfhdResult( + hasBaseDataOffset, baseDataOffset, + defaultBaseIsMoof, + hasDefaultSampleDuration, defaultSampleDuration, + hasDefaultSampleSize, defaultSampleSize); + } + + private async Task ParseTfdtAsync(CancellationToken ct) + { + int version = await ReadByteAsync(ct); + await SkipBytesAsync(3, ct); + + return version == 1 + ? (long)await ReadUInt64BEAsync(ct) + : await ReadUInt32BEAsync(ct); + } + + private int BinarySearchSegment(long targetTime) + { + int left = 0; + int right = _segments.Count - 1; + int result = 0; + + while (left <= right) + { + int mid = (left + right) / 2; + + if (_segments[mid].TimeOffset <= targetTime) + { + result = mid; + left = mid + 1; + } + else + { + right = mid - 1; + } + } + + return result; + } + + private int BinarySearchSample(long targetMs) + { + int left = 0, right = _samples.Count - 1; + + while (left < right) + { + int mid = (left + right + 1) / 2; + if (_samples[mid].TimestampMs <= targetMs) + left = mid; + else + right = mid - 1; + } + + return left; + } + + private async ValueTask ReadExpandableLengthAsync(CancellationToken ct) + { + int length = 0; + var buf = new byte[1]; + + for (int i = 0; i < 4; i++) { - int read = await _stream.ReadAsync(buffer[totalRead..], ct); - if (read == 0) - throw new EndOfStreamException($"Expected {buffer.Length} bytes, got {totalRead}"); - totalRead += read; + await ReadExactlyAsync(buf, ct); + int b = buf[0]; + length = (length << 7) | (b & 0x7F); + if ((b & 0x80) == 0) break; } + + return length; } - + #endregion - - #region Box Parsing - - private async ValueTask<(long size, string type)> ReadBoxHeaderAsync(CancellationToken ct) + + #region Binary Reading Helpers + + private async ValueTask<(long size, string type, int headerSize)> ReadBoxHeaderAsync( + CancellationToken ct) { var header = ArrayPool.Shared.Rent(8); try { - await ReadExactlyAsync(header.AsMemory(0, 8), ct); - + int read = await _stream.ReadAsync(header.AsMemory(0, 8), ct); + if (read < 8) + return (0, "", 0); + uint size = ReadUInt32BE(header); string type = System.Text.Encoding.ASCII.GetString(header, 4, 4); - + if (size == 1) { var extSize = ArrayPool.Shared.Rent(8); try { await ReadExactlyAsync(extSize.AsMemory(0, 8), ct); - return ((long)ReadUInt64BE(extSize), type); + return ((long)ReadUInt64BE(extSize), type, 16); } finally { ArrayPool.Shared.Return(extSize); } } - - return (size, type); + + return (size, type, 8); } catch (EndOfStreamException) { - return (0, ""); + return (0, "", 0); } finally { ArrayPool.Shared.Return(header); } } - - private async Task ParseMoovAsync(long boxEnd, CancellationToken ct) - { - while (_stream.Position < boxEnd) - { - ct.ThrowIfCancellationRequested(); - - var (size, type) = await ReadBoxHeaderAsync(ct); - if (size == 0) break; - - long childEnd = _stream.Position - 8 + size; - - if (type == "trak") - { - await ParseTrakAsync(childEnd, ct); - } - else if (type == "mvhd") - { - await ParseMvhdAsync(childEnd, ct); - } - else - { - await SkipToAsync(childEnd, ct); - } - } - } - - private async Task ParseMvhdAsync(long boxEnd, CancellationToken ct) - { - int version = _stream.ReadByte(); - await SkipBytesAsync(3, ct); // flags - - if (version == 1) - { - await SkipBytesAsync(16, ct); - _timeScale = await ReadUInt32BEAsync(ct); - long duration = (long)await ReadUInt64BEAsync(ct); - _durationMs = duration * 1000 / _timeScale; - } - else - { - await SkipBytesAsync(8, ct); - _timeScale = await ReadUInt32BEAsync(ct); - uint duration = await ReadUInt32BEAsync(ct); - _durationMs = duration * 1000 / _timeScale; - } - - await SkipToAsync(boxEnd, ct); - } - - private async Task ParseTrakAsync(long boxEnd, CancellationToken ct) - { - bool isAudioTrack = false; - uint trackTimeScale = 1; - - while (_stream.Position < boxEnd) - { - ct.ThrowIfCancellationRequested(); - - var (size, type) = await ReadBoxHeaderAsync(ct); - if (size == 0) break; - - long childEnd = _stream.Position - 8 + size; - - if (type == "mdia") - { - while (_stream.Position < childEnd) - { - var (mSize, mType) = await ReadBoxHeaderAsync(ct); - if (mSize == 0) break; - - long mChildEnd = _stream.Position - 8 + mSize; - - if (mType == "hdlr") - { - await SkipBytesAsync(8, ct); - var handlerBytes = ArrayPool.Shared.Rent(4); - try - { - await ReadExactlyAsync(handlerBytes.AsMemory(0, 4), ct); - string handlerType = System.Text.Encoding.ASCII.GetString(handlerBytes, 0, 4); - isAudioTrack = handlerType == "soun"; - } - finally - { - ArrayPool.Shared.Return(handlerBytes); - } - await SkipToAsync(mChildEnd, ct); - } - else if (mType == "mdhd") - { - int version = _stream.ReadByte(); - await SkipBytesAsync(3, ct); - - if (version == 1) - { - await SkipBytesAsync(16, ct); - trackTimeScale = await ReadUInt32BEAsync(ct); - } - else - { - await SkipBytesAsync(8, ct); - trackTimeScale = await ReadUInt32BEAsync(ct); - } - await SkipToAsync(mChildEnd, ct); - } - else if (mType == "minf" && isAudioTrack) - { - await ParseMinfAsync(mChildEnd, trackTimeScale, ct); - } - else - { - await SkipToAsync(mChildEnd, ct); - } - } - } - else - { - await SkipToAsync(childEnd, ct); - } - } - } - - private async Task ParseMinfAsync(long boxEnd, uint timeScale, CancellationToken ct) + + /// + /// Читает ровно buffer.Length байт. Бросает EndOfStreamException если невозможно. + /// + private async ValueTask ReadExactlyAsync(Memory buffer, CancellationToken ct) { - while (_stream.Position < boxEnd) + int totalRead = 0; + while (totalRead < buffer.Length) { - var (size, type) = await ReadBoxHeaderAsync(ct); - if (size == 0) break; - - long childEnd = _stream.Position - 8 + size; - - if (type == "stbl") - { - await ParseStblAsync(childEnd, timeScale, ct); - } - else - { - await SkipToAsync(childEnd, ct); - } + int read = await _stream.ReadAsync(buffer[totalRead..], ct); + if (read == 0) + throw new EndOfStreamException(); + totalRead += read; } } - - private async Task ParseStblAsync(long boxEnd, uint timeScale, CancellationToken ct) + + private async ValueTask ReadByteAsync(CancellationToken ct) { - List sampleSizes = []; - List<(uint firstChunk, uint samplesPerChunk, uint descriptionIndex)> stsc = []; - List chunkOffsets = []; - List<(uint count, uint delta)> stts = []; - - while (_stream.Position < boxEnd) + var buffer = ArrayPool.Shared.Rent(1); + try { - ct.ThrowIfCancellationRequested(); - - var (size, type) = await ReadBoxHeaderAsync(ct); - if (size == 0) break; - - long childEnd = _stream.Position - 8 + size; - - switch (type) - { - case "stsd": - await ParseStsdAsync(childEnd, ct); - break; - - case "stsz": - await SkipBytesAsync(4, ct); - uint defaultSize = await ReadUInt32BEAsync(ct); - uint count = await ReadUInt32BEAsync(ct); - - for (uint i = 0; i < count; i++) - { - sampleSizes.Add(defaultSize == 0 ? await ReadUInt32BEAsync(ct) : defaultSize); - } - break; - - case "stsc": - await SkipBytesAsync(4, ct); - uint entryCount = await ReadUInt32BEAsync(ct); - - for (uint i = 0; i < entryCount; i++) - { - uint firstChunk = await ReadUInt32BEAsync(ct); - uint samplesPerChunk = await ReadUInt32BEAsync(ct); - uint descIndex = await ReadUInt32BEAsync(ct); - stsc.Add((firstChunk, samplesPerChunk, descIndex)); - } - break; - - case "stco": - await SkipBytesAsync(4, ct); - uint offsetCount = await ReadUInt32BEAsync(ct); - - for (uint i = 0; i < offsetCount; i++) - { - chunkOffsets.Add(await ReadUInt32BEAsync(ct)); - } - break; - - case "co64": - await SkipBytesAsync(4, ct); - uint offset64Count = await ReadUInt32BEAsync(ct); - - for (uint i = 0; i < offset64Count; i++) - { - chunkOffsets.Add((long)await ReadUInt64BEAsync(ct)); - } - break; - - case "stts": - await SkipBytesAsync(4, ct); - uint sttsCount = await ReadUInt32BEAsync(ct); - - for (uint i = 0; i < sttsCount; i++) - { - uint sampleCount = await ReadUInt32BEAsync(ct); - uint sampleDelta = await ReadUInt32BEAsync(ct); - stts.Add((sampleCount, sampleDelta)); - } - break; - - default: - await SkipToAsync(childEnd, ct); - break; - } - - await SkipToAsync(childEnd, ct); + int read = await _stream.ReadAsync(buffer.AsMemory(0, 1), ct); + return read == 0 ? -1 : buffer[0]; } - - BuildSampleTable(sampleSizes, stsc, chunkOffsets, stts, timeScale); - } - - private async Task ParseStsdAsync(long boxEnd, CancellationToken ct) - { - await SkipBytesAsync(4, ct); - uint entryCount = await ReadUInt32BEAsync(ct); - - if (entryCount == 0) return; - - var (size, type) = await ReadBoxHeaderAsync(ct); - if (size == 0) return; - - long entryEnd = _stream.Position - 8 + size; - - await SkipBytesAsync(6, ct); // reserved - await SkipBytesAsync(2, ct); // data_reference_index - await SkipBytesAsync(8, ct); // reserved - - _channels = await ReadUInt16BEAsync(ct); - await SkipBytesAsync(2, ct); // sample_size - await SkipBytesAsync(4, ct); // pre_defined, reserved - _sampleRate = (int)(await ReadUInt32BEAsync(ct) >> 16); - - // Find esds box - while (_stream.Position < entryEnd) + finally { - var (boxSize, boxType) = await ReadBoxHeaderAsync(ct); - if (boxSize == 0) break; - - long nestedEnd = _stream.Position - 8 + boxSize; - - if (boxType == "esds") - { - await ParseEsdsAsync(ct); - break; - } - - await SkipToAsync(nestedEnd, ct); + ArrayPool.Shared.Return(buffer); } - - await SkipToAsync(entryEnd, ct); - } - - private async Task ParseEsdsAsync(CancellationToken ct) - { - await SkipBytesAsync(4, ct); // version, flags - - if (_stream.ReadByte() != 0x03) return; - await ReadDescriptorLengthAsync(ct); - await SkipBytesAsync(2, ct); // ES_ID - await SkipBytesAsync(1, ct); // flags - - if (_stream.ReadByte() != 0x04) return; - await ReadDescriptorLengthAsync(ct); - await SkipBytesAsync(1, ct); // objectTypeIndication - await SkipBytesAsync(4, ct); // streamType, bufferSizeDB - await SkipBytesAsync(4, ct); // maxBitrate - await SkipBytesAsync(4, ct); // avgBitrate - - if (_stream.ReadByte() != 0x05) return; - int dsiLength = await ReadDescriptorLengthAsync(ct); - - _decoderConfig = new byte[dsiLength]; - await ReadExactlyAsync(_decoderConfig, ct); - - Log.Debug($"[Mp4Parser] Decoder config: {BitConverter.ToString(_decoderConfig)}"); - } - - private async ValueTask ReadDescriptorLengthAsync(CancellationToken ct) - { - int length = 0; - int b; - - do - { - b = _stream.ReadByte(); - if (b < 0) break; - length = (length << 7) | (b & 0x7F); - } while ((b & 0x80) != 0); - - return length; } - - private void BuildSampleTable( - List sampleSizes, - List<(uint firstChunk, uint samplesPerChunk, uint descriptionIndex)> stsc, - List chunkOffsets, - List<(uint count, uint delta)> stts, - uint timeScale) + + private static uint ReadUInt32BE(ReadOnlySpan b) => + (uint)((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]); + + private static ulong ReadUInt64BE(ReadOnlySpan b) => + ((ulong)b[0] << 56) | ((ulong)b[1] << 48) | ((ulong)b[2] << 40) | ((ulong)b[3] << 32) | + ((ulong)b[4] << 24) | ((ulong)b[5] << 16) | ((ulong)b[6] << 8) | b[7]; + + private async ValueTask ReadUInt32BEAsync(CancellationToken ct) { - if (sampleSizes.Count == 0 || chunkOffsets.Count == 0) return; - - _samples = new List(sampleSizes.Count); - - var timestamps = new List(sampleSizes.Count); - long time = 0; - - foreach (var (count, delta) in stts) - { - for (uint i = 0; i < count; i++) - { - timestamps.Add(time); - time += delta; - } - } - - int sampleIndex = 0; - int stscIndex = 0; - - for (int chunkIndex = 0; chunkIndex < chunkOffsets.Count && sampleIndex < sampleSizes.Count; chunkIndex++) + var buffer = ArrayPool.Shared.Rent(4); + try { - while (stscIndex + 1 < stsc.Count && stsc[stscIndex + 1].firstChunk - 1 <= chunkIndex) - { - stscIndex++; - } - - uint samplesInChunk = stsc.Count > 0 ? stsc[stscIndex].samplesPerChunk : 1; - long offset = chunkOffsets[chunkIndex]; - - for (uint i = 0; i < samplesInChunk && sampleIndex < sampleSizes.Count; i++) - { - long timestampMs = timestamps.Count > sampleIndex - ? timestamps[sampleIndex] * 1000 / timeScale - : sampleIndex * 20; - - int durationMs = sampleIndex + 1 < timestamps.Count - ? (int)((timestamps[sampleIndex + 1] - timestamps[sampleIndex]) * 1000 / timeScale) - : 20; - - _samples.Add(new SampleInfo - { - Offset = offset, - Size = (int)sampleSizes[sampleIndex], - TimestampMs = timestampMs, - DurationMs = durationMs - }); - - offset += sampleSizes[sampleIndex]; - sampleIndex++; - } + await ReadExactlyAsync(buffer.AsMemory(0, 4), ct); + return ReadUInt32BE(buffer); } - - if (_samples.Count > 0) + finally { - _durationMs = _samples[^1].TimestampMs + _samples[^1].DurationMs; + ArrayPool.Shared.Return(buffer); } } - - #endregion - - #region Helpers - - private static uint ReadUInt32BE(ReadOnlySpan bytes) - { - return (uint)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]); - } - - private static ulong ReadUInt64BE(ReadOnlySpan bytes) - { - return ((ulong)bytes[0] << 56) | ((ulong)bytes[1] << 48) | - ((ulong)bytes[2] << 40) | ((ulong)bytes[3] << 32) | - ((ulong)bytes[4] << 24) | ((ulong)bytes[5] << 16) | - ((ulong)bytes[6] << 8) | bytes[7]; - } - - private async ValueTask ReadUInt32BEAsync(CancellationToken ct) + + private async ValueTask ReadInt32BEAsync(CancellationToken ct) { var buffer = ArrayPool.Shared.Rent(4); try { await ReadExactlyAsync(buffer.AsMemory(0, 4), ct); - return ReadUInt32BE(buffer); + return unchecked((int)ReadUInt32BE(buffer)); } finally { ArrayPool.Shared.Return(buffer); } } - + private async ValueTask ReadUInt64BEAsync(CancellationToken ct) { var buffer = ArrayPool.Shared.Rent(8); @@ -654,7 +1547,7 @@ private async ValueTask ReadUInt64BEAsync(CancellationToken ct) ArrayPool.Shared.Return(buffer); } } - + private async ValueTask ReadUInt16BEAsync(CancellationToken ct) { var buffer = ArrayPool.Shared.Rent(2); @@ -668,61 +1561,84 @@ private async ValueTask ReadUInt16BEAsync(CancellationToken ct) ArrayPool.Shared.Return(buffer); } } - + private async ValueTask SkipBytesAsync(long count, CancellationToken ct) { if (_stream.CanSeek) { _stream.Position += count; + return; } - else + + var buffer = ArrayPool.Shared.Rent(Math.Min((int)count, 8192)); + try { - var buffer = ArrayPool.Shared.Rent(Math.Min((int)count, 8192)); - try - { - while (count > 0) - { - int toRead = (int)Math.Min(count, buffer.Length); - int read = await _stream.ReadAsync(buffer.AsMemory(0, toRead), ct); - if (read == 0) break; - count -= read; - } - } - finally + while (count > 0) { - ArrayPool.Shared.Return(buffer); + int toRead = (int)Math.Min(count, buffer.Length); + int read = await _stream.ReadAsync(buffer.AsMemory(0, toRead), ct); + if (read == 0) break; + count -= read; } } + finally + { + ArrayPool.Shared.Return(buffer); + } } - + private async ValueTask SkipToAsync(long position, CancellationToken ct) { if (_stream.Position < position) - { await SkipBytesAsync(position - _stream.Position, ct); - } } - + #endregion - - private struct SampleInfo + + #region Types + + private readonly record struct TrunFlags(uint Flags) + { + public bool HasDataOffset => (Flags & 0x000001) != 0; + public bool HasFirstSampleFlags => (Flags & 0x000004) != 0; + public bool HasSampleDuration => (Flags & 0x000100) != 0; + public bool HasSampleSize => (Flags & 0x000200) != 0; + public bool HasSampleFlags => (Flags & 0x000400) != 0; + public bool HasSampleCompositionTimeOffset => (Flags & 0x000800) != 0; + } + + private record struct SegmentInfo + { + public long ByteOffset; + public long TimeOffset; + public long Duration; + public uint Size; + } + + private record struct SampleInfo { public long Offset; public int Size; public long TimestampMs; public int DurationMs; + public bool IsKeyFrame; } - + + #endregion + + #region Dispose + public void Dispose() { if (_disposed) return; _disposed = true; - // Stream управляется извне } - + public ValueTask DisposeAsync() { Dispose(); return ValueTask.CompletedTask; } + + #endregion } \ No newline at end of file diff --git a/Core/Audio/Parsers/WebMContainerParser.cs b/Core/Audio/Parsers/WebMContainerParser.cs index 618afe3..abf6051 100644 --- a/Core/Audio/Parsers/WebMContainerParser.cs +++ b/Core/Audio/Parsers/WebMContainerParser.cs @@ -1,5 +1,5 @@ using LMP.Core.Audio.Interfaces; -using LMP.Core.Helpers; +using LMP.Core.Audio.Helpers; namespace LMP.Core.Audio.Parsers; diff --git a/Core/Audio/Sources/CachingStreamSource.cs b/Core/Audio/Sources/CachingStreamSource.cs index b612421..b3494f4 100644 --- a/Core/Audio/Sources/CachingStreamSource.cs +++ b/Core/Audio/Sources/CachingStreamSource.cs @@ -1,51 +1,57 @@ -using System.Buffers; using System.Collections.Concurrent; using System.Net; using LMP.Core.Audio.Cache; using LMP.Core.Audio.Http; using LMP.Core.Audio.Interfaces; using LMP.Core.Audio.Parsers; +using static LMP.Core.Audio.AudioConstants; namespace LMP.Core.Audio.Sources; /// -/// Источник с полной поддержкой disk-кэширования. +/// Источник аудио с сегментным кэшированием. +/// Поддерживает seek, частичную загрузку и визуализацию прогресса буфера. /// public sealed class CachingStreamSource : IAudioSource { - private const int ChunkSize = 256 * 1024; - private const int MaxConcurrentDownloads = 4; - private const int DownloadTimeoutMs = 15000; - private const int InitialChunksToLoad = 4; - private const int PreloadAheadChunks = 8; - private const int MaxRamChunks = 32; - + #region Fields + + private readonly string _cacheKey; private readonly string _trackId; private readonly string _url; private readonly long _contentLength; private readonly AudioFormat _format; + private readonly int _bitrate; private readonly HttpClient _httpClient; private readonly AudioCacheManager _cacheManager; private readonly Func>? _urlRefresher; - + private CacheEntry? _cacheEntry; private IContainerParser? _parser; private AsyncCachingReadStream? _readStream; - + private readonly ConcurrentDictionary _ramChunks = new(); private readonly ConcurrentDictionary _activeDownloads = new(); - private readonly SemaphoreSlim _downloadSlots; - + private readonly SemaphoreSlim _downloadSlots = new(MaxConcurrentDownloads); + private readonly Lock _seekLock = new(); + private int _currentChunk; private long _positionMs; private string _currentUrl; + private int _backgroundChunksLoaded; + private volatile bool _initialized; private volatile bool _disposed; private volatile bool _isOfflineMode; - + private CancellationTokenSource? _operationCts; + private CancellationTokenSource? _seekCts; private Task? _preloadTask; - + + #endregion + + #region Properties + public long DurationMs => _parser?.DurationMs ?? _cacheEntry?.DurationMs ?? -1; public long PositionMs => Volatile.Read(ref _positionMs); public bool CanSeek => true; @@ -53,91 +59,86 @@ public sealed class CachingStreamSource : IAudioSource public byte[]? DecoderConfig => _parser?.DecoderConfig; public int SampleRate => _parser?.SampleRate ?? 0; public int Channels => _parser?.Channels ?? 0; - - public double BufferProgress => _cacheEntry?.DownloadProgress ?? 0; - public bool IsFullyBuffered => _cacheEntry?.IsComplete ?? false; + + public double BufferProgress => _isOfflineMode ? 100 : (_cacheEntry?.DownloadProgress ?? 0); + public bool IsFullyBuffered => _cacheEntry?.IsComplete ?? _isOfflineMode; public bool IsOfflineMode => _isOfflineMode; - + public long DownloadedBytes => (_cacheEntry?.DownloadedChunks ?? 0) * (long)ChunkSize; + public int Bitrate => _cacheEntry?.Bitrate ?? _bitrate; + + #endregion + + #region Constructor + public CachingStreamSource( + string cacheKey, string trackId, string url, long contentLength, AudioFormat format, + AudioCodec codec, + int bitrate, HttpClient httpClient, AudioCacheManager cacheManager, Func>? urlRefresher = null) { + _cacheKey = cacheKey; _trackId = trackId; _url = url; _currentUrl = url; _contentLength = contentLength; _format = format; + _bitrate = bitrate; _httpClient = httpClient; _cacheManager = cacheManager; _urlRefresher = urlRefresher; - - _downloadSlots = new SemaphoreSlim(MaxConcurrentDownloads); - - Codec = AudioSourceFactory.GetCodecForFormat(format); + Codec = codec; } - + + #endregion + + #region Initialization + public async ValueTask InitializeAsync(CancellationToken ct = default) { if (_initialized) return true; - + try { - // Проверяем полный кэш - if (_cacheManager.IsFullyCached(_trackId)) + if (_cacheManager.IsFullyCached(_cacheKey)) { - Log.Info($"[CachingSource] Using fully cached: {_trackId}"); + Log.Info($"[CachingSource] Using fully cached: {_cacheKey}"); _isOfflineMode = true; return await InitializeFromCacheAsync(ct); } - - // Создаём или получаем запись кэша + _cacheEntry = _cacheManager.CreateOrUpdate( - _trackId, _url, _contentLength, _format, - AudioSourceFactory.GetCodecForFormat(_format)); - + _cacheKey, _trackId, _url, _contentLength, _format, + AudioSourceFactory.GetCodecForFormat(_format), + _bitrate, + chunkSize: ChunkSize); + if (_cacheEntry.DownloadedChunks > 0) - { Log.Info($"[CachingSource] Resuming: {_cacheEntry.DownloadedChunks}/{_cacheEntry.TotalChunks} chunks"); - } - + _operationCts = CancellationTokenSource.CreateLinkedTokenSource(ct); - - // Загружаем начальные чанки - var initialTasks = new List(); - for (int i = 0; i < Math.Min(InitialChunksToLoad, _cacheEntry.TotalChunks); i++) - { - initialTasks.Add(EnsureChunkAsync(i, _operationCts.Token)); - } - await Task.WhenAll(initialTasks); - + + await LoadInitialChunksAsync(_operationCts.Token); + _readStream = new AsyncCachingReadStream(this); - - _parser = _format switch - { - AudioFormat.WebM or AudioFormat.Ogg => new WebMContainerParser(_readStream), - AudioFormat.Mp4 => new Mp4ContainerParser(_readStream), - _ => throw new NotSupportedException($"Format not supported: {_format}") - }; - + _parser = CreateParser(_readStream); + if (!await _parser.ParseHeadersAsync(ct)) - { - throw new InvalidOperationException("Failed to parse headers"); - } - + throw new InvalidOperationException("Failed to parse container headers"); + Codec = _parser.Codec; _cacheEntry.Codec = Codec; _cacheEntry.DurationMs = _parser.DurationMs; - + _cacheEntry.Bitrate = _bitrate; + _initialized = true; - - // Запускаем фоновую предзагрузку _preloadTask = Task.Run(() => PreloadLoopAsync(_operationCts.Token)); - + Log.Info($"[CachingSource] Initialized: duration={DurationMs}ms, cached={_cacheEntry.DownloadProgress:F0}%"); return true; } @@ -147,167 +148,233 @@ public async ValueTask InitializeAsync(CancellationToken ct = default) return false; } } - + private async Task InitializeFromCacheAsync(CancellationToken ct) { - var stream = _cacheManager.OpenCachedStream(_trackId); + var stream = _cacheManager.OpenCachedStream(_cacheKey); if (stream == null) { _isOfflineMode = false; return await InitializeAsync(ct); } - - // Wrap в async stream + + _cacheEntry = _cacheManager.GetCacheInfo(_cacheKey); _readStream = new AsyncCachingReadStream(this, stream); - - _parser = _format switch - { - AudioFormat.WebM or AudioFormat.Ogg => new WebMContainerParser(_readStream), - AudioFormat.Mp4 => new Mp4ContainerParser(_readStream), - _ => throw new NotSupportedException($"Format: {_format}") - }; - + _parser = CreateParser(_readStream); + if (!await _parser.ParseHeadersAsync(ct)) - { return false; - } - + Codec = _parser.Codec; _initialized = true; - return true; } - + + private IContainerParser CreateParser(Stream stream) => _format switch + { + AudioFormat.WebM or AudioFormat.Ogg => new WebMContainerParser(stream), + AudioFormat.Mp4 => new Mp4ContainerParser(stream), + _ => throw new NotSupportedException($"Format not supported: {_format}") + }; + + private async Task LoadInitialChunksAsync(CancellationToken ct) + { + if (_cacheEntry == null) return; + + int count = Math.Min(InitialChunksToLoad, _cacheEntry.TotalChunks); + var tasks = new Task[count]; + + for (int i = 0; i < count; i++) + tasks[i] = EnsureChunkAsync(i, ct); + + await Task.WhenAll(tasks); + } + + #endregion + + #region Reading + public async ValueTask ReadFrameAsync(CancellationToken ct = default) { ObjectDisposedException.ThrowIf(_disposed, this); - + if (!_initialized || _parser == null) throw new InvalidOperationException("Not initialized"); - + try { var frame = await _parser.ReadNextFrameAsync(ct); - - if (frame == null) - return null; - + if (frame == null) return null; + Volatile.Write(ref _positionMs, frame.Value.TimestampMs); - - if (!_isOfflineMode && _readStream != null) - { - _currentChunk = (int)(_readStream.Position / ChunkSize); - } - + UpdateCurrentChunk(); return frame; } + catch (OperationCanceledException) + { + throw; + } catch (IOException) when (!_disposed && !_isOfflineMode) { await EnsureChunkAsync(_currentChunk, ct); return await ReadFrameAsync(ct); } } - + + private void UpdateCurrentChunk() + { + if (!_isOfflineMode && _readStream != null) + _currentChunk = (int)(_readStream.Position / ChunkSize); + } + + #endregion + + #region Seeking + public async ValueTask SeekAsync(long positionMs, CancellationToken ct = default) { if (_parser == null) return false; - - var seekInfo = _parser.FindSeekPosition(positionMs); - - if (seekInfo == null && _contentLength > 0 && DurationMs > 0) + + lock (_seekLock) { - long approxPosition = (long)(_contentLength * ((double)positionMs / DurationMs)); - seekInfo = (approxPosition, positionMs); + _seekCts?.Cancel(); + _seekCts?.Dispose(); + _seekCts = CancellationTokenSource.CreateLinkedTokenSource(ct); } - - if (seekInfo == null) return false; - - int targetChunk = (int)(seekInfo.Value.BytePosition / ChunkSize); - - if (!_isOfflineMode && _cacheEntry != null) + + var localCts = _seekCts; + + try { - var loadTasks = new List(); - for (int i = targetChunk; i < Math.Min(targetChunk + 3, _cacheEntry.TotalChunks); i++) + var seekInfo = _parser.FindSeekPosition(positionMs); + if (seekInfo == null) { - if (!_cacheEntry.IsChunkDownloaded(i)) - { - loadTasks.Add(EnsureChunkAsync(i, ct)); - } + Log.Warn($"[CachingSource] No seek point for {positionMs}ms"); + return false; } - if (loadTasks.Count > 0) + + long targetBytePos = seekInfo.Value.BytePosition; + long segmentStartMs = seekInfo.Value.TimestampMs; + int targetChunk = (int)(targetBytePos / ChunkSize); + + Log.Debug($"[CachingSource] Seek: {positionMs}ms → chunk {targetChunk}/{_cacheEntry?.TotalChunks}"); + + if (!_isOfflineMode && _cacheEntry != null) { - await Task.WhenAll(loadTasks); + await PreloadChunksForSeekAsync(targetChunk, localCts!.Token); } + + _readStream!.Position = targetBytePos; + _currentChunk = targetChunk; + _parser.Reset(); + Volatile.Write(ref _positionMs, segmentStartMs); + + return true; + } + catch (OperationCanceledException) when (localCts!.Token.IsCancellationRequested) + { + return false; + } + finally + { + lock (_seekLock) + { + if (_seekCts == localCts) + _seekCts = null; + } + localCts?.Dispose(); } - - _readStream!.Position = seekInfo.Value.BytePosition; - _currentChunk = targetChunk; - _parser.Reset(); - - Volatile.Write(ref _positionMs, positionMs); - - return true; } - + + private async Task PreloadChunksForSeekAsync(int targetChunk, CancellationToken ct) + { + if (_cacheEntry == null) return; + + var tasks = new List(); + int end = Math.Min(targetChunk + SeekPreloadChunks, _cacheEntry.TotalChunks); + + for (int i = targetChunk; i < end; i++) + { + if (!IsChunkAvailable(i)) + tasks.Add(EnsureChunkAsync(i, ct)); + } + + if (tasks.Count > 0) + { + Log.Debug($"[CachingSource] Seek preloading {tasks.Count} chunks from {targetChunk}"); + await Task.WhenAll(tasks); + } + } + + #endregion + + #region Buffered Ranges + public IReadOnlyList<(double Start, double End)> GetBufferedRanges() { - if (_isOfflineMode || _cacheEntry == null) + if (_isOfflineMode) return [(0.0, 1.0)]; - + + if (_cacheEntry == null) + return []; + + int total = _cacheEntry.TotalChunks; + if (total == 0) + return []; + var ranges = new List<(double, double)>(); int? rangeStart = null; - - for (int i = 0; i < _cacheEntry.TotalChunks; i++) + + for (int i = 0; i < total; i++) { - if (_cacheEntry.IsChunkDownloaded(i)) + if (IsChunkAvailable(i)) { rangeStart ??= i; } else if (rangeStart.HasValue) { - ranges.Add(((double)rangeStart.Value / _cacheEntry.TotalChunks, - (double)i / _cacheEntry.TotalChunks)); + ranges.Add(((double)rangeStart.Value / total, (double)i / total)); rangeStart = null; } } - + if (rangeStart.HasValue) - { - ranges.Add(((double)rangeStart.Value / _cacheEntry.TotalChunks, 1.0)); - } - + ranges.Add(((double)rangeStart.Value / total, 1.0)); + return ranges; } - + + private bool IsChunkAvailable(int index) => + _cacheEntry!.IsChunkDownloaded(index) || _ramChunks.ContainsKey(index); + public void ReleaseRamBuffers() { int current = Volatile.Read(ref _currentChunk); - foreach (var idx in _ramChunks.Keys.Where(i => Math.Abs(i - current) > 5).ToList()) - { + foreach (var idx in _ramChunks.Keys.Where(i => Math.Abs(i - current) > RamEvictionDistance).ToList()) _ramChunks.TryRemove(idx, out _); - } } - + public void CancelPendingOperations() => _operationCts?.Cancel(); - + + #endregion + #region Chunk Management - + private async Task EnsureChunkAsync(int index, CancellationToken ct) { - if (_cacheEntry == null) return; - if (_cacheEntry.IsChunkDownloaded(index)) return; - if (_ramChunks.ContainsKey(index)) return; - + if (_cacheEntry == null || IsChunkAvailable(index)) + return; + if (_activeDownloads.TryGetValue(index, out var existingTask)) { await existingTask; return; } - - var downloadTask = DownloadChunkAsync(index, ct); + + var downloadTask = DownloadChunkCoreAsync(index, ct); _activeDownloads.TryAdd(index, downloadTask); - + try { await downloadTask; @@ -317,59 +384,53 @@ private async Task EnsureChunkAsync(int index, CancellationToken ct) _activeDownloads.TryRemove(index, out _); } } - - private async Task DownloadChunkAsync(int index, CancellationToken ct) + + private async Task DownloadChunkCoreAsync(int index, CancellationToken ct) { - if (_cacheEntry == null || _cacheEntry.IsChunkDownloaded(index)) return; - + if (_cacheEntry == null || _cacheEntry.IsChunkDownloaded(index)) + return; + bool gotSlot = false; - + try { - gotSlot = await _downloadSlots.WaitAsync(500, ct); + gotSlot = await _downloadSlots.WaitAsync(DownloadSlotTimeoutMs, ct); if (!gotSlot) return; - - // Double check after getting slot + if (_cacheEntry.IsChunkDownloaded(index)) return; - + long start = (long)index * ChunkSize; long end = Math.Min(start + ChunkSize - 1, _contentLength - 1); - + using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(DownloadTimeoutMs); - + using var request = SharedHttpClient.CreateRangeRequest(_currentUrl, start, end); - using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token); - + using var response = await _httpClient.SendAsync( + request, HttpCompletionOption.ResponseHeadersRead, cts.Token); + if (response.StatusCode == HttpStatusCode.Forbidden) { await RefreshUrlAsync(ct); return; } - + response.EnsureSuccessStatusCode(); - + var data = await response.Content.ReadAsByteArrayAsync(cts.Token); - - // Add to RAM cache _ramChunks.TryAdd(index, data); - - // Write to disk cache + try { - await _cacheManager.WriteChunkAsync(_trackId, index, data, ct); + await _cacheManager.WriteChunkAsync(_cacheKey, index, data, ct); } catch (IOException ex) { Log.Warn($"[CachingSource] Disk write failed: {ex.Message}"); - // Continue with RAM-only } - - // Evict old RAM chunks + if (_ramChunks.Count > MaxRamChunks) - { - EvictOldRamChunks(); - } + EvictDistantRamChunks(); } catch (OperationCanceledException) { } catch (Exception ex) @@ -380,114 +441,171 @@ private async Task DownloadChunkAsync(int index, CancellationToken ct) { if (gotSlot) { - try { _downloadSlots.Release(); } catch { } + try { _downloadSlots.Release(); } + catch { } } } } - - private void EvictOldRamChunks() + + private void EvictDistantRamChunks() { int current = Volatile.Read(ref _currentChunk); - + var toEvict = _ramChunks.Keys - .Where(i => Math.Abs(i - current) > 10) + .Where(i => Math.Abs(i - current) > RamEvictionDistance) .OrderByDescending(i => Math.Abs(i - current)) .Take(MaxRamChunks / 4) .ToList(); - + foreach (var idx in toEvict) - { _ramChunks.TryRemove(idx, out _); - } } - + internal async Task ReadAtAsync(long position, Memory buffer, CancellationToken ct) { if (position >= _contentLength) return 0; - + int chunkIndex = (int)(position / ChunkSize); int offsetInChunk = (int)(position % ChunkSize); - - // Try RAM cache first + if (_ramChunks.TryGetValue(chunkIndex, out var ramData)) - { - int available = Math.Min(buffer.Length, ramData.Length - offsetInChunk); - if (available > 0) - { - ramData.AsSpan(offsetInChunk, available).CopyTo(buffer.Span); - return available; - } - } - - // Try disk cache - var diskData = await _cacheManager.ReadChunkAsync(_trackId, chunkIndex, ct); + return CopyFromChunk(ramData, offsetInChunk, buffer); + + var diskData = await _cacheManager.ReadChunkAsync(_cacheKey, chunkIndex, ct); if (diskData != null) { _ramChunks.TryAdd(chunkIndex, diskData); - - int available = Math.Min(buffer.Length, diskData.Length - offsetInChunk); - if (available > 0) - { - diskData.AsSpan(offsetInChunk, available).CopyTo(buffer.Span); - return available; - } + return CopyFromChunk(diskData, offsetInChunk, buffer); } - - // Download + await EnsureChunkAsync(chunkIndex, ct); - + if (_ramChunks.TryGetValue(chunkIndex, out ramData)) - { - int available = Math.Min(buffer.Length, ramData.Length - offsetInChunk); - if (available > 0) - { - ramData.AsSpan(offsetInChunk, available).CopyTo(buffer.Span); - return available; - } - } - + return CopyFromChunk(ramData, offsetInChunk, buffer); + return 0; } - + + private static int CopyFromChunk(byte[] chunkData, int offset, Memory buffer) + { + int available = Math.Min(buffer.Length, chunkData.Length - offset); + if (available <= 0) return 0; + + chunkData.AsSpan(offset, available).CopyTo(buffer.Span); + return available; + } + #endregion - - #region Background Tasks - + + #region Background Preload + private async Task PreloadLoopAsync(CancellationToken ct) { + int lastReportedProgress = -1; + int idleCycles = 0; + _backgroundChunksLoaded = 0; + while (!ct.IsCancellationRequested && _cacheEntry != null) { try { - await Task.Delay(200, ct); - + await Task.Delay(PreloadIntervalMs, ct); + + if (_cacheEntry.IsComplete) + break; + int current = Volatile.Read(ref _currentChunk); - - // Preload ahead - for (int i = 0; i < PreloadAheadChunks && current + i < _cacheEntry.TotalChunks; i++) + int pending = _activeDownloads.Count; + + if (pending >= MaxConcurrentDownloads) + { + idleCycles = 0; + continue; + } + + bool activePreload = false; + int chunksAhead = 0; + + for (int i = 0; i <= PreloadAheadChunks && pending < MaxConcurrentDownloads; i++) { int idx = current + i; - if (!_cacheEntry.IsChunkDownloaded(idx) && !_activeDownloads.ContainsKey(idx)) + if (idx >= _cacheEntry.TotalChunks) break; + + if (IsChunkAvailable(idx)) + { + chunksAhead++; + } + else if (!_activeDownloads.ContainsKey(idx)) { _ = EnsureChunkAsync(idx, ct); + pending++; + activePreload = true; + await Task.Delay(50, ct); } } - - // Evict old RAM chunks periodically - if (_ramChunks.Count > MaxRamChunks) + + if (!activePreload) + idleCycles++; + else + idleCycles = 0; + + bool canBackgroundFill = + !activePreload + && idleCycles >= BackgroundFillIdleCycles + && pending < MaxConcurrentDownloads + && chunksAhead >= MinBufferAheadForBackgroundFill + && (MaxBackgroundChunksPerSession == 0 || _backgroundChunksLoaded < MaxBackgroundChunksPerSession); + + if (canBackgroundFill) { - ReleaseRamBuffers(); + int? target = FindNearestMissingChunk(current); + + if (target.HasValue && !IsChunkAvailable(target.Value) && !_activeDownloads.ContainsKey(target.Value)) + { + _ = EnsureChunkAsync(target.Value, ct); + _backgroundChunksLoaded++; + await Task.Delay(BackgroundFillIntervalMs, ct); + } + } + + int progress = (int)_cacheEntry.DownloadProgress; + if (progress / 25 > lastReportedProgress / 25) + { + Log.Debug($"[CachingSource] Progress: {progress}% ({_cacheEntry.DownloadedChunks}/{_cacheEntry.TotalChunks})"); + lastReportedProgress = progress; } + + if (_ramChunks.Count > MaxRamChunks) + ReleaseRamBuffers(); } catch (OperationCanceledException) { break; } catch { } } } - + + private int? FindNearestMissingChunk(int currentChunk) + { + if (_cacheEntry == null) return null; + int total = _cacheEntry.TotalChunks; + + for (int offset = 1; offset < total; offset++) + { + int forward = currentChunk + offset; + if (forward < total && !IsChunkAvailable(forward)) + return forward; + + int backward = currentChunk - offset; + if (backward >= 0 && !IsChunkAvailable(backward)) + return backward; + } + + return null; + } + private async Task RefreshUrlAsync(CancellationToken ct) { if (_urlRefresher == null) return; - + try { var newUrl = await _urlRefresher(ct); @@ -502,16 +620,22 @@ private async Task RefreshUrlAsync(CancellationToken ct) Log.Warn($"[CachingSource] URL refresh failed: {ex.Message}"); } } - + #endregion - + #region Dispose - + public void Dispose() { if (_disposed) return; _disposed = true; - + + lock (_seekLock) + { + _seekCts?.Cancel(); + _seekCts?.Dispose(); + } + _operationCts?.Cancel(); _operationCts?.Dispose(); _parser?.Dispose(); @@ -519,103 +643,91 @@ public void Dispose() _ramChunks.Clear(); _downloadSlots.Dispose(); } - + public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; - + + lock (_seekLock) + { + _seekCts?.Cancel(); + _seekCts?.Dispose(); + } + _operationCts?.Cancel(); - + if (_preloadTask != null) { try { await _preloadTask.WaitAsync(TimeSpan.FromSeconds(1)); } catch { } } - + _operationCts?.Dispose(); - + if (_parser != null) await _parser.DisposeAsync(); - + _readStream?.Dispose(); _ramChunks.Clear(); _downloadSlots.Dispose(); } - + #endregion - + #region AsyncCachingReadStream - - /// - /// Async read stream with caching support. - /// + private sealed class AsyncCachingReadStream : Stream { private readonly CachingStreamSource? _source; private readonly Stream? _fileStream; private long _position; - - public AsyncCachingReadStream(CachingStreamSource source) - { - _source = source; - } - + + public AsyncCachingReadStream(CachingStreamSource source) => _source = source; + public AsyncCachingReadStream(CachingStreamSource source, Stream fileStream) { _source = source; _fileStream = fileStream; } - + public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => false; public override long Length => _fileStream?.Length ?? _source?._contentLength ?? 0; - + public override long Position { get => _fileStream?.Position ?? Volatile.Read(ref _position); set { - if (_fileStream != null) - _fileStream.Position = value; - else - Volatile.Write(ref _position, value); + if (_fileStream != null) _fileStream.Position = value; + else Volatile.Write(ref _position, value); } } - - public override int Read(byte[] buffer, int offset, int count) - { - return ReadAsync(buffer.AsMemory(offset, count), CancellationToken.None).AsTask().GetAwaiter().GetResult(); - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) - { - return await ReadAsync(buffer.AsMemory(offset, count), ct); - } - + + public override int Read(byte[] buffer, int offset, int count) => + ReadAsync(buffer.AsMemory(offset, count), CancellationToken.None) + .AsTask().GetAwaiter().GetResult(); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) => + ReadAsync(buffer.AsMemory(offset, count), ct).AsTask(); + public override async ValueTask ReadAsync(Memory buffer, CancellationToken ct = default) { - if (_fileStream != null) - { - return await _fileStream.ReadAsync(buffer, ct); - } - + if (_fileStream != null) return await _fileStream.ReadAsync(buffer, ct); if (_source == null) return 0; - + long pos = Volatile.Read(ref _position); int read = await _source.ReadAtAsync(pos, buffer, ct); Volatile.Write(ref _position, pos + read); return read; } - + public override long Seek(long offset, SeekOrigin origin) { - if (_fileStream != null) - { - return _fileStream.Seek(offset, origin); - } - + if (_fileStream != null) return _fileStream.Seek(offset, origin); + long length = _source?._contentLength ?? 0; long newPos = origin switch { @@ -624,23 +736,21 @@ public override long Seek(long offset, SeekOrigin origin) SeekOrigin.End => length + offset, _ => Volatile.Read(ref _position) }; + Volatile.Write(ref _position, newPos); return newPos; } - + public override void Flush() { } public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - + protected override void Dispose(bool disposing) { - if (disposing) - { - _fileStream?.Dispose(); - } + if (disposing) _fileStream?.Dispose(); base.Dispose(disposing); } } - + #endregion } \ No newline at end of file diff --git a/Core/Audio/Sources/HlsStreamSource.cs b/Core/Audio/Sources/HlsStreamSource.cs index 8a943eb..8b5d2b0 100644 --- a/Core/Audio/Sources/HlsStreamSource.cs +++ b/Core/Audio/Sources/HlsStreamSource.cs @@ -1,63 +1,36 @@ -using System.Buffers; -using System.Net; using System.Text.RegularExpressions; using LMP.Core.Audio.Interfaces; +using static LMP.Core.Audio.AudioConstants; namespace LMP.Core.Audio.Sources; -/// -/// HLS источник для AAC аудио. -/// Парсит m3u8, загружает TS сегменты, извлекает AAC фреймы. -/// -public sealed partial class HlsStreamSource : IAudioSource +public sealed partial class HlsStreamSource(string masterUrl, HttpClient? httpClient = null) : IAudioSource { - private const int DefaultPrefetchSegments = 3; - private const int FrameDurationMs = 23; // ~1024 samples @ 44.1kHz - - private readonly string _masterUrl; - private readonly HttpClient _httpClient; - private readonly Func>? _urlRefresher; - private readonly int _prefetchSegments; - + private readonly string _masterUrl = masterUrl ?? throw new ArgumentNullException(nameof(masterUrl)); + private readonly HttpClient _httpClient = httpClient ?? Http.SharedHttpClient.Instance; + private string _currentPlaylistUrl = string.Empty; private List _segments = []; private int _currentSegmentIndex; private readonly Queue _frameBuffer = new(); - private readonly object _bufferLock = new(); - - private byte[]? _audioSpecificConfig; - private long _durationMs = -1; + private readonly Lock _bufferLock = new(); private long _positionMs; - private int _sampleRate = 44100; - private int _channels = 2; private bool _initialized; private volatile bool _disposed; - + private CancellationTokenSource? _operationCts; private Task? _prefetchTask; private readonly HashSet _downloadedSegments = []; private readonly HashSet _loadingSegments = []; - - public HlsStreamSource( - string masterUrl, - HttpClient? httpClient = null, - Func>? urlRefresher = null, - int prefetchSegments = DefaultPrefetchSegments) - { - _masterUrl = masterUrl ?? throw new ArgumentNullException(nameof(masterUrl)); - _httpClient = httpClient ?? Http.SharedHttpClient.Instance; - _urlRefresher = urlRefresher; - _prefetchSegments = prefetchSegments; - } - - public long DurationMs => _durationMs; + + public long DurationMs { get; private set; } = -1; public long PositionMs => Volatile.Read(ref _positionMs); public bool CanSeek => _segments.Count > 0; public AudioCodec Codec => AudioCodec.Aac; - public byte[]? DecoderConfig => _audioSpecificConfig; - public int SampleRate => _sampleRate; - public int Channels => _channels; - + public byte[]? DecoderConfig { get; private set; } + public int SampleRate { get; private set; } = 44100; + public int Channels { get; private set; } = 2; + public double BufferProgress { get @@ -70,7 +43,7 @@ public double BufferProgress } } } - + public bool IsFullyBuffered { get @@ -81,42 +54,33 @@ public bool IsFullyBuffered } } } - + public async ValueTask InitializeAsync(CancellationToken ct = default) { if (_initialized) return true; - + try { - Log.Debug($"[HLS] Initializing: {_masterUrl[..Math.Min(60, _masterUrl.Length)]}..."); - - var masterContent = await _httpClient.GetStringAsync(_masterUrl, ct); - var audioPlaylistUrl = ParseMasterPlaylist(masterContent, _masterUrl); - - if (string.IsNullOrEmpty(audioPlaylistUrl)) - { - audioPlaylistUrl = _masterUrl; - } - - _currentPlaylistUrl = audioPlaylistUrl; - - var mediaContent = await _httpClient.GetStringAsync(audioPlaylistUrl, ct); - _segments = ParseMediaPlaylist(mediaContent, audioPlaylistUrl); - + Log.Debug($"[HLS] Initializing: {TruncateUrl(_masterUrl)}"); + + _currentPlaylistUrl = await ResolvePlaylistUrlAsync(ct); + + var mediaContent = await _httpClient.GetStringAsync(_currentPlaylistUrl, ct); + _segments = ParseMediaPlaylist(mediaContent, _currentPlaylistUrl); + if (_segments.Count == 0) throw new InvalidOperationException("No segments found in HLS playlist"); - - _durationMs = (long)_segments.Sum(s => s.DurationMs); - - // Load first segment to get ASC + + DurationMs = (long)_segments.Sum(s => s.DurationMs); + await LoadSegmentAsync(0, ct); - + _initialized = true; - + _operationCts = CancellationTokenSource.CreateLinkedTokenSource(ct); - _prefetchTask = Task.Run(() => PrefetchLoopAsync(_operationCts.Token)); - - Log.Info($"[HLS] Initialized: {_segments.Count} segments, duration={_durationMs}ms"); + _prefetchTask = Task.Run(() => PrefetchLoopAsync(_operationCts.Token), ct); + + Log.Info($"[HLS] Initialized: {_segments.Count} segments, duration={DurationMs}ms"); return true; } catch (Exception ex) @@ -125,141 +89,164 @@ public async ValueTask InitializeAsync(CancellationToken ct = default) return false; } } - + + private async Task ResolvePlaylistUrlAsync(CancellationToken ct) + { + var masterContent = await _httpClient.GetStringAsync(_masterUrl, ct); + var audioPlaylistUrl = ParseMasterPlaylist(masterContent, _masterUrl); + return string.IsNullOrEmpty(audioPlaylistUrl) ? _masterUrl : audioPlaylistUrl; + } + public async ValueTask ReadFrameAsync(CancellationToken ct = default) { ObjectDisposedException.ThrowIf(_disposed, this); - + if (!_initialized) throw new InvalidOperationException("Not initialized"); - + while (true) { - // Try to get frame from buffer - lock (_bufferLock) + if (TryDequeueFrame(out var frame)) { - if (_frameBuffer.Count > 0) + Volatile.Write(ref _positionMs, frame.TimestampMs); + return new AudioFrame { - var frame = _frameBuffer.Dequeue(); - Volatile.Write(ref _positionMs, frame.TimestampMs); - - return new AudioFrame - { - Data = frame.Data, - TimestampMs = frame.TimestampMs, - DurationMs = frame.DurationMs, - IsKeyFrame = true - }; - } + Data = frame.Data, + TimestampMs = frame.TimestampMs, + DurationMs = frame.DurationMs, + IsKeyFrame = true + }; } - - // Check if we've reached end + if (_currentSegmentIndex >= _segments.Count) return null; - - // Load next segment - bool isDownloaded; - lock (_bufferLock) - { - isDownloaded = _downloadedSegments.Contains(_currentSegmentIndex); - } - - if (!isDownloaded) + + if (!IsSegmentDownloaded(_currentSegmentIndex)) { await LoadSegmentAsync(_currentSegmentIndex, ct); } - - // If still no frames, move to next segment - lock (_bufferLock) + + if (IsFrameBufferEmpty()) { - if (_frameBuffer.Count == 0) - { - _currentSegmentIndex++; - } + _currentSegmentIndex++; } } } - - public async ValueTask SeekAsync(long positionMs, CancellationToken ct = default) + + private bool TryDequeueFrame(out AacFrame frame) { - if (_segments.Count == 0) return false; - - // Find target segment by time - long accumulated = 0; - int targetSegment = 0; - - for (int i = 0; i < _segments.Count; i++) + lock (_bufferLock) { - if (accumulated + _segments[i].DurationMs > positionMs) + if (_frameBuffer.Count > 0) { - targetSegment = i; - break; + frame = _frameBuffer.Dequeue(); + return true; } - accumulated += (long)_segments[i].DurationMs; - targetSegment = i; + frame = default; + return false; + } + } + + private bool IsSegmentDownloaded(int index) + { + lock (_bufferLock) + { + return _downloadedSegments.Contains(index); + } + } + + private bool IsFrameBufferEmpty() + { + lock (_bufferLock) + { + return _frameBuffer.Count == 0; } - + } + + public async ValueTask SeekAsync(long positionMs, CancellationToken ct = default) + { + if (_segments.Count == 0) return false; + + var (targetSegment, segmentStartMs) = FindSegmentByTime(positionMs); + Log.Debug($"[HLS] Seek to {positionMs}ms → segment {targetSegment}"); - - // Clear buffer and reset state + lock (_bufferLock) { _frameBuffer.Clear(); - // Mark segment as not downloaded to force reload - _downloadedSegments.Remove(targetSegment); } - + _currentSegmentIndex = targetSegment; - Volatile.Write(ref _positionMs, accumulated); - - // Load the target segment - await LoadSegmentAsync(targetSegment, ct); - + Volatile.Write(ref _positionMs, segmentStartMs); + + if (!IsSegmentDownloaded(targetSegment)) + { + await LoadSegmentAsync(targetSegment, ct); + } + return true; } - + + private (int Index, long StartMs) FindSegmentByTime(long positionMs) + { + long accumulated = 0; + + for (int i = 0; i < _segments.Count; i++) + { + if (accumulated + _segments[i].DurationMs > positionMs) + { + return (i, accumulated); + } + accumulated += (long)_segments[i].DurationMs; + } + + return (_segments.Count - 1, accumulated); + } + public IReadOnlyList<(double Start, double End)> GetBufferedRanges() { - if (_segments.Count == 0 || _durationMs <= 0) return []; - + if (_segments.Count == 0 || DurationMs <= 0) return []; + var ranges = new List<(double, double)>(); - + lock (_bufferLock) { int? rangeStart = null; long startMs = 0; long currentMs = 0; - + for (int i = 0; i < _segments.Count; i++) { - if (_downloadedSegments.Contains(i)) + bool isDownloaded = _downloadedSegments.Contains(i); + + if (isDownloaded && !rangeStart.HasValue) { - if (rangeStart == null) - { - rangeStart = i; - startMs = currentMs; - } + rangeStart = i; + startMs = currentMs; } - else if (rangeStart.HasValue) + else if (!isDownloaded && rangeStart.HasValue) { - double start = (double)startMs / _durationMs; - double end = (double)currentMs / _durationMs; - ranges.Add((start, end)); + ranges.Add(ToNormalizedRange(startMs, currentMs)); rangeStart = null; } - + currentMs += (long)_segments[i].DurationMs; } - + if (rangeStart.HasValue) { - ranges.Add(((double)startMs / _durationMs, 1.0)); + ranges.Add(ToNormalizedRange(startMs, DurationMs)); } } - + return ranges; } - + + private (double Start, double End) ToNormalizedRange(long startMs, long endMs) + { + return ((double)startMs / DurationMs, (double)endMs / DurationMs); + } + public void ReleaseRamBuffers() { lock (_bufferLock) @@ -267,65 +254,59 @@ public void ReleaseRamBuffers() _frameBuffer.Clear(); } } - - public void CancelPendingOperations() - { - _operationCts?.Cancel(); - } - - #region Parsing - + + public void CancelPendingOperations() => _operationCts?.Cancel(); + + #region Playlist Parsing + private static string? ParseMasterPlaylist(string content, string baseUrl) { var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries); string? audioUrl = null; int maxBandwidth = 0; - + for (int i = 0; i < lines.Length; i++) { var line = lines[i].Trim(); - + if (line.StartsWith("#EXT-X-MEDIA:") && line.Contains("TYPE=AUDIO")) { var uriMatch = UriRegex().Match(line); if (uriMatch.Success) { - audioUrl = ResolveUrl(baseUrl, uriMatch.Groups[1].Value); + return ResolveUrl(baseUrl, uriMatch.Groups[1].Value); } } - - if (line.StartsWith("#EXT-X-STREAM-INF:")) + + if (line.StartsWith("#EXT-X-STREAM-INF:") && i + 1 < lines.Length && !lines[i + 1].StartsWith("#")) { var bwMatch = BandwidthRegex().Match(line); int bw = bwMatch.Success ? int.Parse(bwMatch.Groups[1].Value) : 0; - - if (i + 1 < lines.Length && !lines[i + 1].StartsWith("#")) + + var url = lines[i + 1].Trim(); + if (bw > maxBandwidth || audioUrl == null) { - var url = lines[i + 1].Trim(); - if (bw > maxBandwidth || audioUrl == null) - { - maxBandwidth = bw; - audioUrl = ResolveUrl(baseUrl, url); - } + maxBandwidth = bw; + audioUrl = ResolveUrl(baseUrl, url); } } } - + return audioUrl; } - + private static List ParseMediaPlaylist(string content, string baseUrl) { var segments = new List(); var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries); - + double duration = 0; long accumulatedMs = 0; - - for (int i = 0; i < lines.Length; i++) + + foreach (var rawLine in lines) { - var line = lines[i].Trim(); - + var line = rawLine.Trim(); + if (line.StartsWith("#EXTINF:")) { var durationStr = line[8..].Split(',')[0]; @@ -333,78 +314,73 @@ private static List ParseMediaPlaylist(string content, string baseUr } else if (!line.StartsWith("#") && !string.IsNullOrEmpty(line)) { - var url = ResolveUrl(baseUrl, line); var durationMs = duration * 1000; - - segments.Add(new HlsSegment - { - Url = url, - DurationMs = durationMs, - StartMs = accumulatedMs - }); - + + segments.Add(new HlsSegment( + Url: ResolveUrl(baseUrl, line), + DurationMs: durationMs, + StartMs: accumulatedMs)); + accumulatedMs += (long)durationMs; duration = 0; } } - + return segments; } - + private static string ResolveUrl(string baseUrl, string relativeUrl) { if (relativeUrl.StartsWith("http://") || relativeUrl.StartsWith("https://")) return relativeUrl; - - var baseUri = new Uri(baseUrl); - return new Uri(baseUri, relativeUrl).ToString(); + + return new Uri(new Uri(baseUrl), relativeUrl).ToString(); } - + + private static string TruncateUrl(string url) => url[..Math.Min(60, url.Length)] + "..."; + [GeneratedRegex(@"URI=""([^""]+)""")] private static partial Regex UriRegex(); - + [GeneratedRegex(@"BANDWIDTH=(\d+)")] private static partial Regex BandwidthRegex(); - + #endregion - + #region Segment Loading - + private async Task LoadSegmentAsync(int index, CancellationToken ct) { if (index < 0 || index >= _segments.Count) return; - - // Check if already downloaded or loading + lock (_bufferLock) { - if (_downloadedSegments.Contains(index)) return; - if (_loadingSegments.Contains(index)) return; + if (_downloadedSegments.Contains(index) || _loadingSegments.Contains(index)) + return; _loadingSegments.Add(index); } - + var segment = _segments[index]; - + try { - Log.Debug($"[HLS] Loading segment {index}: {segment.Url[..Math.Min(50, segment.Url.Length)]}..."); - + Log.Debug($"[HLS] Loading segment {index}"); + var data = await _httpClient.GetByteArrayAsync(segment.Url, ct); - - // Extract AAC frames from TS var frames = ExtractAacFramesFromTs(data, segment.StartMs); - + lock (_bufferLock) { foreach (var frame in frames) { _frameBuffer.Enqueue(frame); } - + _downloadedSegments.Add(index); _loadingSegments.Remove(index); } - - Log.Debug($"[HLS] Segment {index}: {frames.Count} frames extracted"); + + Log.Debug($"[HLS] Segment {index}: {frames.Count} frames"); } catch (Exception ex) when (ex is not OperationCanceledException) { @@ -416,160 +392,133 @@ private async Task LoadSegmentAsync(int index, CancellationToken ct) throw; } } - + private List ExtractAacFramesFromTs(byte[] tsData, long baseTimestampMs) { var frames = new List(); int pos = 0; long currentMs = baseTimestampMs; - + while (pos + 188 <= tsData.Length) { - // TS packet = 188 bytes, sync byte = 0x47 if (tsData[pos] != 0x47) { pos++; continue; } - - bool hasPayload = (tsData[pos + 3] & 0x10) != 0; - bool hasAdaptation = (tsData[pos + 3] & 0x20) != 0; - - int payloadStart = pos + 4; - if (hasAdaptation) + + var payload = ExtractTsPayload(tsData, pos); + if (payload.Length > 0) { - int adaptLength = tsData[payloadStart]; - payloadStart += 1 + adaptLength; + ExtractAdtsFrames(payload, ref currentMs, frames); } - - if (hasPayload && payloadStart < pos + 188) + + pos += 188; + } + + return frames; + } + + private static ReadOnlySpan ExtractTsPayload(byte[] tsData, int packetStart) + { + bool hasPayload = (tsData[packetStart + 3] & 0x10) != 0; + bool hasAdaptation = (tsData[packetStart + 3] & 0x20) != 0; + + if (!hasPayload) return []; + + int payloadStart = packetStart + 4; + if (hasAdaptation) + { + payloadStart += 1 + tsData[payloadStart]; + } + + if (payloadStart >= packetStart + 188) return []; + + return tsData.AsSpan(payloadStart, packetStart + 188 - payloadStart); + } + + private void ExtractAdtsFrames(ReadOnlySpan payload, ref long currentMs, List frames) + { + int pos = 0; + + while (pos + 7 <= payload.Length) + { + if (payload[pos] != 0xFF || (payload[pos + 1] & 0xF0) != 0xF0) { - int payloadLen = pos + 188 - payloadStart; - var payload = tsData.AsSpan(payloadStart, payloadLen); - - int adtsPos = 0; - while (adtsPos + 7 <= payload.Length) + pos++; + continue; + } + + int frameLength = ((payload[pos + 3] & 0x03) << 11) | + (payload[pos + 4] << 3) | + ((payload[pos + 5] & 0xE0) >> 5); + + if (frameLength <= 0 || pos + frameLength > payload.Length) + { + pos++; + continue; + } + + if (DecoderConfig == null) + { + DecoderConfig = ExtractAscFromAdts(payload.Slice(pos, 7)); + ParseAscForSampleInfo(); + } + + int headerLen = (payload[pos + 1] & 0x01) == 0 ? 9 : 7; + int dataLen = frameLength - headerLen; + + if (dataLen > 0 && pos + headerLen + dataLen <= payload.Length) + { + frames.Add(new AacFrame { - // ADTS sync: 0xFFF - if (payload[adtsPos] == 0xFF && (payload[adtsPos + 1] & 0xF0) == 0xF0) - { - int frameLength = ((payload[adtsPos + 3] & 0x03) << 11) | - (payload[adtsPos + 4] << 3) | - ((payload[adtsPos + 5] & 0xE0) >> 5); - - if (frameLength > 0 && adtsPos + frameLength <= payload.Length) - { - // Extract ASC if not yet - if (_audioSpecificConfig == null) - { - _audioSpecificConfig = ExtractAscFromAdts(payload.Slice(adtsPos, 7)); - ParseAscForSampleInfo(); - } - - // AAC raw data (without ADTS header) - int headerLen = (payload[adtsPos + 1] & 0x01) == 0 ? 9 : 7; - int dataLen = frameLength - headerLen; - - if (dataLen > 0 && adtsPos + headerLen + dataLen <= payload.Length) - { - var frameData = payload.Slice(adtsPos + headerLen, dataLen).ToArray(); - - frames.Add(new AacFrame - { - Data = frameData, - TimestampMs = currentMs, - DurationMs = FrameDurationMs - }); - - currentMs += FrameDurationMs; - } - - adtsPos += frameLength; - } - else - { - adtsPos++; - } - } - else - { - adtsPos++; - } - } + Data = payload.Slice(pos + headerLen, dataLen).ToArray(), + TimestampMs = currentMs, + DurationMs = HlsAacFrameDurationMs + }); + + currentMs += HlsAacFrameDurationMs; } - - pos += 188; + + pos += frameLength; } - - return frames; } - + private static byte[] ExtractAscFromAdts(ReadOnlySpan adtsHeader) { int profile = ((adtsHeader[2] & 0xC0) >> 6) + 1; int sampleRateIndex = (adtsHeader[2] & 0x3C) >> 2; int channelConfig = ((adtsHeader[2] & 0x01) << 2) | ((adtsHeader[3] & 0xC0) >> 6); - + int asc = (profile << 11) | (sampleRateIndex << 7) | (channelConfig << 3); - + return [(byte)(asc >> 8), (byte)(asc & 0xFF)]; } - + private void ParseAscForSampleInfo() { - if (_audioSpecificConfig == null || _audioSpecificConfig.Length < 2) return; - - int asc = (_audioSpecificConfig[0] << 8) | _audioSpecificConfig[1]; + if (DecoderConfig == null || DecoderConfig.Length < 2) return; + + int asc = (DecoderConfig[0] << 8) | DecoderConfig[1]; int sampleRateIndex = (asc >> 7) & 0x0F; int channelConfig = (asc >> 3) & 0x0F; - - _sampleRate = sampleRateIndex switch - { - 0 => 96000, 1 => 88200, 2 => 64000, 3 => 48000, - 4 => 44100, 5 => 32000, 6 => 24000, 7 => 22050, - 8 => 16000, 9 => 12000, 10 => 11025, 11 => 8000, - _ => 44100 - }; - - _channels = channelConfig switch - { - 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 8, - _ => 2 - }; + + SampleRate = GetAacSampleRate(sampleRateIndex); + Channels = GetAacChannels(channelConfig); } - + #endregion - + #region Prefetch - + private async Task PrefetchLoopAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { try { - await Task.Delay(500, ct); - - int current = _currentSegmentIndex; - - for (int i = 0; i < _prefetchSegments; i++) - { - int targetIndex = current + i; - - if (targetIndex >= _segments.Count) break; - - bool shouldLoad; - lock (_bufferLock) - { - shouldLoad = !_downloadedSegments.Contains(targetIndex) && - !_loadingSegments.Contains(targetIndex); - } - - if (shouldLoad) - { - await LoadSegmentAsync(targetIndex, ct); - } - } + await Task.Delay(HlsPrefetchIntervalMs, ct); + await PrefetchSegmentsAhead(ct); } catch (OperationCanceledException) { break; } catch (Exception ex) @@ -578,59 +527,71 @@ private async Task PrefetchLoopAsync(CancellationToken ct) } } } - + + private async Task PrefetchSegmentsAhead(CancellationToken ct) + { + int current = _currentSegmentIndex; + + for (int i = 0; i < HlsPrefetchSegments && current + i < _segments.Count; i++) + { + int targetIndex = current + i; + + bool shouldLoad; + lock (_bufferLock) + { + shouldLoad = !_downloadedSegments.Contains(targetIndex) && + !_loadingSegments.Contains(targetIndex); + } + + if (shouldLoad) + { + await LoadSegmentAsync(targetIndex, ct); + } + } + } + #endregion - + + #region Dispose + public void Dispose() { if (_disposed) return; _disposed = true; - + _operationCts?.Cancel(); _operationCts?.Dispose(); - + lock (_bufferLock) { _frameBuffer.Clear(); } } - + public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; - + _operationCts?.Cancel(); - + if (_prefetchTask != null) { try { await _prefetchTask.WaitAsync(TimeSpan.FromSeconds(1)); } catch { } } - + _operationCts?.Dispose(); - + lock (_bufferLock) { _frameBuffer.Clear(); } } - - #region Types - - private sealed class HlsSegment - { - public required string Url { get; init; } - public double DurationMs { get; init; } - public long StartMs { get; init; } - } - - private readonly struct AacFrame - { - public required byte[] Data { get; init; } - public long TimestampMs { get; init; } - public int DurationMs { get; init; } - } - + #endregion + + private sealed record HlsSegment(string Url, double DurationMs, long StartMs); + + private readonly record struct AacFrame(byte[] Data, long TimestampMs, int DurationMs); } \ No newline at end of file diff --git a/Core/Audio/Sources/LocalFileSource.cs b/Core/Audio/Sources/LocalFileSource.cs index 3b8b3d9..eb2ac1e 100644 --- a/Core/Audio/Sources/LocalFileSource.cs +++ b/Core/Audio/Sources/LocalFileSource.cs @@ -4,28 +4,25 @@ namespace LMP.Core.Audio.Sources; /// -/// Универсальный источник для локальных файлов (WebM, MP4, M4A, Ogg). +/// Источник для локальных аудио файлов. /// public sealed class LocalFileSource : IAudioSource { private readonly string _filePath; private FileStream? _fileStream; private IContainerParser? _parser; - - private long _durationMs = -1; private long _positionMs; private bool _initialized; private bool _disposed; - + public LocalFileSource(string filePath) { _filePath = filePath ?? throw new ArgumentNullException(nameof(filePath)); - if (!File.Exists(filePath)) throw new FileNotFoundException("Audio file not found", filePath); } - - public long DurationMs => _durationMs; + + public long DurationMs { get; private set; } = -1; public long PositionMs => Volatile.Read(ref _positionMs); public bool CanSeek => true; public AudioCodec Codec { get; private set; } = AudioCodec.Unknown; @@ -34,68 +31,32 @@ public LocalFileSource(string filePath) public byte[]? DecoderConfig => _parser?.DecoderConfig; public int SampleRate => _parser?.SampleRate ?? 0; public int Channels => _parser?.Channels ?? 0; - + public async ValueTask InitializeAsync(CancellationToken ct = default) { if (_initialized) return true; - + try { - Log.Debug($"[LocalFileSource] Opening: {_filePath}"); - _fileStream = new FileStream( - _filePath, - FileMode.Open, - FileAccess.Read, - FileShare.Read, - bufferSize: 64 * 1024, - useAsync: true); - - var header = new byte[12]; - int totalRead = 0; - while (totalRead < 12) - { - int read = await _fileStream.ReadAsync(header.AsMemory(totalRead, 12 - totalRead), ct); - if (read == 0) break; - totalRead += read; - } - _fileStream.Position = 0; - - var format = AudioSourceFactory.DetectFormatByMagic(header); - - if (format == AudioFormat.Unknown) - { - var ext = Path.GetExtension(_filePath).ToLowerInvariant(); - format = ext switch - { - ".webm" => AudioFormat.WebM, - ".m4a" or ".mp4" or ".aac" => AudioFormat.Mp4, - ".ogg" or ".opus" => AudioFormat.Ogg, - _ => AudioFormat.Unknown - }; - } - - Log.Debug($"[LocalFileSource] Format: {format}"); - - _parser = format switch - { - AudioFormat.WebM or AudioFormat.Ogg => new WebMContainerParser(_fileStream), - AudioFormat.Mp4 => new Mp4ContainerParser(_fileStream), - _ => throw new NotSupportedException($"Unsupported format: {format}") - }; - + _filePath, FileMode.Open, FileAccess.Read, FileShare.Read, + bufferSize: 64 * 1024, useAsync: true); + + var format = await DetectFormatAsync(ct); + _parser = CreateParser(format); + if (!await _parser.ParseHeadersAsync(ct)) { Log.Error("[LocalFileSource] Failed to parse headers"); return false; } - - _durationMs = _parser.DurationMs; + + DurationMs = _parser.DurationMs; Codec = _parser.Codec; _initialized = true; - - Log.Info($"[LocalFileSource] Initialized: duration={_durationMs}ms, codec={Codec}, size={_fileStream.Length / 1024}KB"); - + + Log.Info($"[LocalFileSource] Initialized: duration={DurationMs}ms, " + + $"codec={Codec}, size={_fileStream.Length / 1024}KB"); return true; } catch (Exception ex) @@ -104,70 +65,108 @@ public async ValueTask InitializeAsync(CancellationToken ct = default) return false; } } - + + /// + /// Reads next frame. NOT thread-safe — caller must ensure + /// no concurrent calls with SeekAsync. + /// public async ValueTask ReadFrameAsync(CancellationToken ct = default) { ObjectDisposedException.ThrowIf(_disposed, this); - + if (!_initialized || _parser == null) throw new InvalidOperationException("Not initialized"); - + var frame = await _parser.ReadNextFrameAsync(ct); - + if (frame == null) return null; - + Volatile.Write(ref _positionMs, frame.Value.TimestampMs); return frame; } - + + /// + /// Seeks to position. NOT thread-safe — caller must stop decoder loop first. + /// public async ValueTask SeekAsync(long positionMs, CancellationToken ct = default) { if (_parser == null || _fileStream == null) return false; - - var seekInfo = _parser.FindSeekPosition(positionMs); - - if (seekInfo == null && _fileStream.Length > 0 && _durationMs > 0) - { - long approxPosition = (long)(_fileStream.Length * ((double)positionMs / _durationMs)); - seekInfo = (approxPosition, positionMs); - } - + + var seekInfo = _parser.FindSeekPosition(positionMs) + ?? EstimateSeekPosition(positionMs); + if (seekInfo == null) return false; - - Log.Debug($"[LocalFileSource] Seek to {positionMs}ms (byte: {seekInfo.Value.BytePosition})"); - + + Log.Debug($"[LocalFileSource] Seek: target={positionMs}ms, segment={seekInfo.Value.TimestampMs}ms"); + _fileStream.Position = seekInfo.Value.BytePosition; _parser.Reset(); - - Volatile.Write(ref _positionMs, positionMs); + + Volatile.Write(ref _positionMs, seekInfo.Value.TimestampMs); + return true; } - + + private async Task DetectFormatAsync(CancellationToken ct) + { + var header = new byte[12]; + int totalRead = 0; + while (totalRead < 12) + { + int read = await _fileStream!.ReadAsync(header.AsMemory(totalRead, 12 - totalRead), ct); + if (read == 0) break; + totalRead += read; + } + _fileStream!.Position = 0; + + var format = AudioSourceFactory.DetectFormatByMagic(header); + + if (format == AudioFormat.Unknown) + { + format = Path.GetExtension(_filePath).ToLowerInvariant() switch + { + ".webm" => AudioFormat.WebM, + ".m4a" or ".mp4" or ".aac" => AudioFormat.Mp4, + ".ogg" or ".opus" => AudioFormat.Ogg, + _ => AudioFormat.Unknown + }; + } + + return format; + } + + private IContainerParser CreateParser(AudioFormat format) => format switch + { + AudioFormat.WebM or AudioFormat.Ogg => new WebMContainerParser(_fileStream!), + AudioFormat.Mp4 => new Mp4ContainerParser(_fileStream!), + _ => throw new NotSupportedException($"Unsupported format: {format}") + }; + + private (long BytePosition, long TimestampMs)? EstimateSeekPosition(long positionMs) + { + if (_fileStream == null || _fileStream.Length <= 0 || DurationMs <= 0) return null; + long approxPosition = (long)(_fileStream.Length * ((double)positionMs / DurationMs)); + return (approxPosition, positionMs); + } + public IReadOnlyList<(double Start, double End)> GetBufferedRanges() => [(0.0, 1.0)]; - public void ReleaseRamBuffers() { } - public void CancelPendingOperations() { } - + public void Dispose() { if (_disposed) return; _disposed = true; - _parser?.Dispose(); _fileStream?.Dispose(); } - + public async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; - - if (_parser != null) - await _parser.DisposeAsync(); - - if (_fileStream != null) - await _fileStream.DisposeAsync(); + if (_parser != null) await _parser.DisposeAsync(); + if (_fileStream != null) await _fileStream.DisposeAsync(); } } \ No newline at end of file diff --git a/Core/Audio/Sources/UniversalStreamSource.cs b/Core/Audio/Sources/UniversalStreamSource.cs deleted file mode 100644 index ca99db7..0000000 --- a/Core/Audio/Sources/UniversalStreamSource.cs +++ /dev/null @@ -1,523 +0,0 @@ -using System.Buffers; -using System.Collections.Concurrent; -using System.Net; -using LMP.Core.Audio.Helpers; -using LMP.Core.Audio.Http; -using LMP.Core.Audio.Interfaces; -using LMP.Core.Audio.Parsers; - -namespace LMP.Core.Audio.Sources; - -/// -/// Универсальный источник для WebM и MP4 (RAM-only кэш). -/// -public sealed class UniversalStreamSource : IAudioSource -{ - private const int ChunkSize = 256 * 1024; - private const int MaxRamChunks = 32; - private const int MaxConcurrentDownloads = 4; - private const int DownloadTimeoutMs = 15000; - private const int InitialChunksToLoad = 4; - private const int PreloadAheadChunks = 8; - - private readonly string _cacheId; - private readonly HttpClient _httpClient; - private readonly Func>? _urlRefresher; - private readonly AudioCodec _expectedCodec; - - private string _currentUrl; - private readonly long _contentLength; - private readonly int _totalChunks; - - private readonly ConcurrentDictionary _ramChunks = new(); - private readonly ConcurrentBitArray _downloadedChunks; - private readonly ConcurrentDictionary _activeDownloads = new(); - private readonly SemaphoreSlim _downloadSlots; - - private IContainerParser? _parser; - private AsyncChunkReadStream? _readStream; - - private long _durationMs = -1; - private long _positionMs; - private int _currentChunk; - private volatile bool _initialized; - private volatile bool _disposed; - - private CancellationTokenSource? _operationCts; - private Task? _preloadTask; - - public long DurationMs => _durationMs; - public long PositionMs => Volatile.Read(ref _positionMs); - public bool CanSeek => true; - public AudioCodec Codec { get; private set; } - public byte[]? DecoderConfig => _parser?.DecoderConfig; - public int SampleRate => _parser?.SampleRate ?? 0; - public int Channels => _parser?.Channels ?? 0; - - public double BufferProgress => _totalChunks == 0 ? 0 : - Math.Min((double)_downloadedChunks.PopCount() / _totalChunks * 100, 100); - - public bool IsFullyBuffered => _downloadedChunks.PopCount() >= _totalChunks; - - public UniversalStreamSource( - string cacheId, - string url, - long contentLength, - AudioCodec expectedCodec, - HttpClient httpClient, - Func>? urlRefresher = null) - { - if (contentLength <= 0) - throw new ArgumentException("Content length must be positive", nameof(contentLength)); - - _cacheId = cacheId; - _currentUrl = url; - _contentLength = contentLength; - _expectedCodec = expectedCodec; - _httpClient = httpClient; - _urlRefresher = urlRefresher; - - _totalChunks = (int)Math.Ceiling((double)contentLength / ChunkSize); - _downloadedChunks = new ConcurrentBitArray(_totalChunks); - _downloadSlots = new SemaphoreSlim(MaxConcurrentDownloads); - - Codec = expectedCodec; - } - - public async ValueTask InitializeAsync(CancellationToken ct = default) - { - if (_initialized) return true; - - try - { - _operationCts = CancellationTokenSource.CreateLinkedTokenSource(ct); - - // Загружаем начальные чанки - var initialTasks = new List(); - for (int i = 0; i < Math.Min(InitialChunksToLoad, _totalChunks); i++) - { - initialTasks.Add(DownloadChunkAsync(i, _operationCts.Token)); - } - await Task.WhenAll(initialTasks); - - _readStream = new AsyncChunkReadStream(this); - - // Определяем формат по magic bytes - var header = ArrayPool.Shared.Rent(12); - try - { - int bytesRead = await _readStream.ReadAsync(header.AsMemory(0, 12), ct); - _readStream.Position = 0; - - var format = AudioSourceFactory.DetectFormatByMagic(header.AsSpan(0, bytesRead)); - - _parser = format switch - { - AudioFormat.WebM or AudioFormat.Ogg => new WebMContainerParser(_readStream), - AudioFormat.Mp4 => new Mp4ContainerParser(_readStream), - _ => throw new NotSupportedException($"Unsupported format: {format}") - }; - } - finally - { - ArrayPool.Shared.Return(header); - } - - if (!await _parser.ParseHeadersAsync(ct)) - { - throw new InvalidOperationException("Failed to parse headers"); - } - - _durationMs = _parser.DurationMs; - Codec = _parser.Codec; - _initialized = true; - - // Запускаем фоновую предзагрузку - _preloadTask = Task.Run(() => PreloadLoopAsync(_operationCts.Token)); - - Log.Info($"[UniversalSource] Initialized: duration={_durationMs}ms, codec={Codec}"); - return true; - } - catch (Exception ex) - { - Log.Error($"[UniversalSource] Init failed: {ex.Message}", ex); - return false; - } - } - - public async ValueTask ReadFrameAsync(CancellationToken ct = default) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - if (!_initialized || _parser == null) - throw new InvalidOperationException("Not initialized"); - - try - { - var frame = await _parser.ReadNextFrameAsync(ct); - - if (frame == null) - return null; - - Volatile.Write(ref _positionMs, frame.Value.TimestampMs); - _currentChunk = (int)(_readStream!.Position / ChunkSize); - - return frame; - } - catch (IOException) when (!_disposed) - { - // Retry after ensuring chunk is loaded - await EnsureChunkAsync(_currentChunk, ct); - return await ReadFrameAsync(ct); - } - } - - public async ValueTask SeekAsync(long positionMs, CancellationToken ct = default) - { - if (_parser == null || _durationMs <= 0) return false; - - var seekInfo = _parser.FindSeekPosition(positionMs); - - if (seekInfo == null) - { - // Approximate seek by byte position - long approxPosition = (long)(_contentLength * ((double)positionMs / _durationMs)); - seekInfo = (approxPosition, positionMs); - } - - int targetChunk = (int)(seekInfo.Value.BytePosition / ChunkSize); - targetChunk = Math.Clamp(targetChunk, 0, _totalChunks - 1); - - // Preload chunks around target - var loadTasks = new List(); - for (int i = targetChunk; i < Math.Min(targetChunk + 3, _totalChunks); i++) - { - if (!_downloadedChunks.Get(i)) - { - loadTasks.Add(EnsureChunkAsync(i, ct)); - } - } - if (loadTasks.Count > 0) - { - await Task.WhenAll(loadTasks); - } - - _readStream!.Position = seekInfo.Value.BytePosition; - _currentChunk = targetChunk; - _parser.Reset(); - - Volatile.Write(ref _positionMs, positionMs); - - return true; - } - - public IReadOnlyList<(double Start, double End)> GetBufferedRanges() - { - if (_totalChunks == 0) return []; - - var ranges = new List<(double, double)>(); - int? rangeStart = null; - - for (int i = 0; i < _totalChunks; i++) - { - if (_downloadedChunks.Get(i)) - { - rangeStart ??= i; - } - else if (rangeStart.HasValue) - { - ranges.Add(((double)rangeStart.Value / _totalChunks, (double)i / _totalChunks)); - rangeStart = null; - } - } - - if (rangeStart.HasValue) - { - ranges.Add(((double)rangeStart.Value / _totalChunks, 1.0)); - } - - return ranges; - } - - public void ReleaseRamBuffers() - { - int current = Volatile.Read(ref _currentChunk); - - foreach (var idx in _ramChunks.Keys.Where(i => Math.Abs(i - current) > 5).ToList()) - { - _ramChunks.TryRemove(idx, out _); - } - } - - public void CancelPendingOperations() => _operationCts?.Cancel(); - - #region Chunk Management - - private async Task EnsureChunkAsync(int index, CancellationToken ct) - { - if (_downloadedChunks.Get(index)) return; - if (_ramChunks.ContainsKey(index)) return; - - if (_activeDownloads.TryGetValue(index, out var existingTask)) - { - await existingTask; - return; - } - - var downloadTask = DownloadChunkAsync(index, ct); - _activeDownloads.TryAdd(index, downloadTask); - - try - { - await downloadTask; - } - finally - { - _activeDownloads.TryRemove(index, out _); - } - } - - private async Task DownloadChunkAsync(int index, CancellationToken ct) - { - if (index < 0 || index >= _totalChunks) return; - if (_downloadedChunks.Get(index)) return; - - bool gotSlot = false; - - try - { - gotSlot = await _downloadSlots.WaitAsync(500, ct); - if (!gotSlot || _downloadedChunks.Get(index)) return; - - long start = (long)index * ChunkSize; - long end = Math.Min(start + ChunkSize - 1, _contentLength - 1); - - using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - cts.CancelAfter(DownloadTimeoutMs); - - using var request = SharedHttpClient.CreateRangeRequest(_currentUrl, start, end); - using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token); - - if (response.StatusCode == HttpStatusCode.Forbidden) - { - await RefreshUrlAsync(ct); - return; - } - - response.EnsureSuccessStatusCode(); - - var data = await response.Content.ReadAsByteArrayAsync(cts.Token); - - _downloadedChunks.Set(index, true); - _ramChunks.TryAdd(index, data); - - if (_ramChunks.Count > MaxRamChunks) - { - EvictOldChunks(); - } - } - catch (OperationCanceledException) { } - catch (Exception ex) - { - Log.Warn($"[UniversalSource] Chunk {index} failed: {ex.Message}"); - } - finally - { - if (gotSlot) - { - try { _downloadSlots.Release(); } catch { } - } - } - } - - private void EvictOldChunks() - { - int current = Volatile.Read(ref _currentChunk); - - var toEvict = _ramChunks.Keys - .Where(i => Math.Abs(i - current) > 10) - .OrderByDescending(i => Math.Abs(i - current)) - .Take(MaxRamChunks / 4) - .ToList(); - - foreach (var idx in toEvict) - { - _ramChunks.TryRemove(idx, out _); - } - } - - internal async Task ReadAtAsync(long position, Memory buffer, CancellationToken ct) - { - if (position >= _contentLength) return 0; - - int chunkIndex = (int)(position / ChunkSize); - int offsetInChunk = (int)(position % ChunkSize); - - if (!_downloadedChunks.Get(chunkIndex)) - { - await EnsureChunkAsync(chunkIndex, ct); - } - - if (!_ramChunks.TryGetValue(chunkIndex, out var chunkData)) - return 0; - - int available = Math.Min(buffer.Length, chunkData.Length - offsetInChunk); - if (available <= 0) return 0; - - chunkData.AsSpan(offsetInChunk, available).CopyTo(buffer.Span); - return available; - } - - #endregion - - #region Background Tasks - - private async Task PreloadLoopAsync(CancellationToken ct) - { - while (!ct.IsCancellationRequested) - { - try - { - await Task.Delay(200, ct); - - int current = Volatile.Read(ref _currentChunk); - - // Preload ahead - for (int i = 0; i < PreloadAheadChunks && current + i < _totalChunks; i++) - { - int idx = current + i; - if (!_downloadedChunks.Get(idx) && !_activeDownloads.ContainsKey(idx)) - { - _ = EnsureChunkAsync(idx, ct); - } - } - } - catch (OperationCanceledException) { break; } - catch { } - } - } - - private async Task RefreshUrlAsync(CancellationToken ct) - { - if (_urlRefresher == null) return; - - try - { - var newUrl = await _urlRefresher(ct); - if (!string.IsNullOrEmpty(newUrl)) - { - _currentUrl = newUrl; - Log.Info("[UniversalSource] URL refreshed"); - } - } - catch (Exception ex) - { - Log.Warn($"[UniversalSource] URL refresh failed: {ex.Message}"); - } - } - - #endregion - - #region Dispose - - public void Dispose() - { - if (_disposed) return; - _disposed = true; - - _operationCts?.Cancel(); - _operationCts?.Dispose(); - _parser?.Dispose(); - _readStream?.Dispose(); - _ramChunks.Clear(); - _downloadSlots.Dispose(); - } - - public async ValueTask DisposeAsync() - { - if (_disposed) return; - _disposed = true; - - _operationCts?.Cancel(); - - if (_preloadTask != null) - { - try { await _preloadTask.WaitAsync(TimeSpan.FromSeconds(1)); } - catch { } - } - - _operationCts?.Dispose(); - - if (_parser != null) - await _parser.DisposeAsync(); - - _readStream?.Dispose(); - _ramChunks.Clear(); - _downloadSlots.Dispose(); - } - - #endregion - - #region AsyncChunkReadStream - - /// - /// Async-compatible read stream over chunks. - /// - private sealed class AsyncChunkReadStream : Stream - { - private readonly UniversalStreamSource _source; - private long _position; - - public AsyncChunkReadStream(UniversalStreamSource source) => _source = source; - - public override bool CanRead => true; - public override bool CanSeek => true; - public override bool CanWrite => false; - public override long Length => _source._contentLength; - - public override long Position - { - get => Volatile.Read(ref _position); - set => Volatile.Write(ref _position, value); - } - - public override int Read(byte[] buffer, int offset, int count) - { - // Sync read - вызываем async версию - return ReadAsync(buffer.AsMemory(offset, count), CancellationToken.None) - .AsTask().GetAwaiter().GetResult(); - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) - { - return await ReadAsync(buffer.AsMemory(offset, count), ct); - } - - public override async ValueTask ReadAsync(Memory buffer, CancellationToken ct = default) - { - long pos = Volatile.Read(ref _position); - int read = await _source.ReadAtAsync(pos, buffer, ct); - Volatile.Write(ref _position, pos + read); - return read; - } - - public override long Seek(long offset, SeekOrigin origin) - { - long newPos = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => Volatile.Read(ref _position) + offset, - SeekOrigin.End => _source._contentLength + offset, - _ => Volatile.Read(ref _position) - }; - Volatile.Write(ref _position, newPos); - return newPos; - } - - public override void Flush() { } - public override void SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - } - - #endregion -} \ No newline at end of file diff --git a/Core/Audio/Tests/QuickAudioTester.cs b/Core/Audio/Tests/QuickAudioTester.cs index 5c69e0c..b9f266f 100644 --- a/Core/Audio/Tests/QuickAudioTester.cs +++ b/Core/Audio/Tests/QuickAudioTester.cs @@ -1,9 +1,10 @@ using LMP.Core.Audio.Backends; +using LMP.Core.Audio.Cache; using LMP.Core.Audio.Decoders; using LMP.Core.Audio.Http; using LMP.Core.Audio.Interfaces; using LMP.Core.Audio.Sources; -using LMP.Core.Helpers; +using LMP.Core.Audio.Helpers; using LMP.Core.Models; using LMP.Core.Services; @@ -11,7 +12,6 @@ namespace LMP.Core.Audio.Tests; /// /// Быстрый тестер аудио системы. -/// Использует NAudio как primary backend, NullAudioBackend как fallback. /// public static class QuickAudioTester { @@ -37,10 +37,10 @@ public static async Task PlayAsync( IAudioSource? source = null; IAudioDecoder? decoder = null; IPlaybackBackend? backend = null; + AudioCacheManager? testCache = null; try { - // STEP 1: Извлекаем Video ID var videoId = ExtractVideoId(youtubeUrlOrId); if (string.IsNullOrEmpty(videoId)) { @@ -49,12 +49,11 @@ public static async Task PlayAsync( } Console.WriteLine($"[1/5] Video ID: {videoId}"); - // STEP 2: Создаём TrackInfo Console.WriteLine("[2/5] Getting track info..."); var track = new TrackInfo { - Id = videoId, + Id = $"yt_{videoId}", Title = "Test Track", Author = "Unknown", Url = $"https://www.youtube.com/watch?v={videoId}" @@ -76,7 +75,6 @@ public static async Task PlayAsync( Console.WriteLine($" ⚠ Could not get track info: {ex.Message}"); } - // STEP 3: Получаем URL стрима Console.WriteLine("[3/5] Getting stream URL..."); var streamInfo = await youtubeProvider.RefreshStreamUrlAsync(track, forceRefresh: true, ct); @@ -96,11 +94,13 @@ public static async Task PlayAsync( Console.WriteLine($" ✓ HLS: {track.IsHlsOnly}"); Console.WriteLine($" ✓ URL: {url[..Math.Min(80, url.Length)]}..."); - // STEP 4: Создаём пайплайн Console.WriteLine("[4/5] Creating audio pipeline..."); + // Создаём временный кэш для теста + testCache = new AudioCacheManager(); + (source, decoder) = await CreateSourceAndDecoderAsync( - track, url, size, codec, container, ct); + track, url, size, codec, bitrate, container, testCache, ct); if (source == null || decoder == null) { @@ -110,7 +110,6 @@ public static async Task PlayAsync( Console.WriteLine($" ✓ Source initialized: duration={source.DurationMs}ms, codec={source.Codec}"); - // STEP 5: Создаём backend Console.WriteLine("[5/5] Creating backend..."); backend = CreateBackend(); Console.WriteLine($" ✓ {backend.Name} backend"); @@ -132,6 +131,8 @@ public static async Task PlayAsync( decoder?.Dispose(); if (source != null) await source.DisposeAsync(); + if (testCache != null) + await testCache.DisposeAsync(); } } @@ -169,7 +170,6 @@ public static async Task PlayFileAsync(string filePath, int seconds = 10) decoder = CreateDecoderForCodec(source.Codec, source.SampleRate, source.Channels); - // Инициализируем AAC декодер если есть config if (decoder is AacDecoder aacDecoder && source.DecoderConfig != null) { aacDecoder.Initialize(source.DecoderConfig); @@ -205,6 +205,7 @@ public static async Task PlayDirectUrlAsync(string url, int seconds = 10) IAudioSource? source = null; IAudioDecoder? decoder = null; IPlaybackBackend? backend = null; + AudioCacheManager? testCache = null; try { @@ -214,14 +215,30 @@ public static async Task PlayDirectUrlAsync(string url, int seconds = 10) long contentLength = await SharedHttpClient.GetContentLengthAsync(url); Console.WriteLine($" Size: {(contentLength > 0 ? $"{contentLength / 1024.0 / 1024.0:F1} MB" : "Unknown")}"); + // Создаём временный кэш + testCache = new AudioCacheManager(); var expectedCodec = AudioSourceFactory.GetCodecForFormat(format); + var trackId = "test_" + Guid.NewGuid().ToString()[..8]; - source = new UniversalStreamSource( - cacheId: Guid.NewGuid().ToString(), - url: url, - contentLength: contentLength > 0 ? contentLength : 50 * 1024 * 1024, - expectedCodec: expectedCodec, - httpClient: SharedHttpClient.Instance); + if (format == AudioFormat.Hls) + { + source = new HlsStreamSource(url, SharedHttpClient.Instance); + } + else + { + string cacheKey = AudioSourceFactory.BuildCacheKey(trackId, format, 0); + + source = new CachingStreamSource( + cacheKey, + trackId, + url, + contentLength > 0 ? contentLength : 50 * 1024 * 1024, + format, + expectedCodec, + 0, // TODO: Bitrate get + SharedHttpClient.Instance, + testCache); + } if (!await source.InitializeAsync()) { @@ -255,6 +272,8 @@ public static async Task PlayDirectUrlAsync(string url, int seconds = 10) decoder?.Dispose(); if (source != null) await source.DisposeAsync(); + if (testCache != null) + await testCache.DisposeAsync(); } } @@ -276,7 +295,6 @@ private static async Task PlayPipelineAsync( bool endOfStream = false; string? error = null; - // Настраиваем бэкенд (volume управляется только здесь!) backend.Initialize(decoder.SampleRate, decoder.Channels, buffer => { int read = pcmBuffer.Read(buffer); @@ -287,7 +305,6 @@ private static async Task PlayPipelineAsync( using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - // Декодирование в отдельном потоке var decodeTask = Task.Run(async () => { try @@ -333,7 +350,6 @@ private static async Task PlayPipelineAsync( } }, cts.Token); - // Буферизация Console.WriteLine(); Console.WriteLine("Buffering..."); int minBuffer = decoder.SampleRate * decoder.Channels * MinBufferMs / 1000; @@ -365,7 +381,6 @@ private static async Task PlayPipelineAsync( return; } - // Воспроизведение Console.WriteLine(); Console.WriteLine($"▶ Playing for {seconds} seconds..."); Console.WriteLine(); @@ -385,7 +400,7 @@ private static async Task PlayPipelineAsync( progress = Math.Clamp(progress, 0, 40); string bar = new string('█', progress) + new string('░', 40 - progress); - Console.Write($"\r ▶ [{bar}] {positionSec:F1}s / {durationSec:F1}s | Buf: {bufferMs:F0}ms | Fr: {framesRead} "); + Console.Write($"\r ▶ [{bar}] {positionSec:F1}s / {durationSec:F1}s | Buf: {bufferMs:F0}ms | Cache: {source.BufferProgress:F0}% "); if (endOfStream) { @@ -409,7 +424,6 @@ private static async Task PlayPipelineAsync( } catch { } - // Итоги PrintSummary(framesRead, bytesRead, lastTimestampMs, source.BufferProgress, endOfStream, error); } @@ -422,7 +436,9 @@ private static async Task PlayPipelineAsync( string url, long size, string codec, + int bitrate, string container, + AudioCacheManager cacheManager, CancellationToken ct) { IAudioSource source; @@ -430,30 +446,40 @@ private static async Task PlayPipelineAsync( if (track.IsHlsOnly || container == "m3u8") { - Console.WriteLine(" → Using HLS source"); + Console.WriteLine(" → Using HLS source (RAM-only)"); source = new HlsStreamSource(url, SharedHttpClient.Instance); decoder = new AacDecoder(44100, 2); } else if (container == "webm" || codec == "Opus") { - Console.WriteLine(" → Using WebM/Opus source"); - source = new UniversalStreamSource( - cacheId: track.Id, - url: url, - contentLength: size > 0 ? size : 50 * 1024 * 1024, - expectedCodec: AudioCodec.Opus, - httpClient: SharedHttpClient.Instance); + Console.WriteLine(" → Using CachingSource (WebM/Opus)"); + string cacheKey = AudioSourceFactory.BuildCacheKey(track.Id, AudioFormat.WebM, bitrate); + source = new CachingStreamSource( + cacheKey, + track.Id, + url, + size > 0 ? size : 50 * 1024 * 1024, + AudioFormat.WebM, + AudioCodec.Opus, + bitrate, + SharedHttpClient.Instance, + cacheManager); decoder = new OpusDecoder(48000, 2); } else { - Console.WriteLine(" → Using MP4/AAC source"); - source = new UniversalStreamSource( - cacheId: track.Id, - url: url, - contentLength: size > 0 ? size : 50 * 1024 * 1024, - expectedCodec: AudioCodec.Aac, - httpClient: SharedHttpClient.Instance); + Console.WriteLine(" → Using CachingSource (MP4/AAC)"); + string cacheKey = AudioSourceFactory.BuildCacheKey(track.Id, AudioFormat.Mp4, bitrate); + source = new CachingStreamSource( + cacheKey, + track.Id, + url, + size > 0 ? size : 50 * 1024 * 1024, + AudioFormat.Mp4, + AudioCodec.Aac, + bitrate, + SharedHttpClient.Instance, + cacheManager); decoder = new AacDecoder(44100, 2); } @@ -463,7 +489,6 @@ private static async Task PlayPipelineAsync( return (null, null); } - // Обновляем декодер если нужно if (source.Codec != decoder.Codec) { Console.WriteLine($" → Switching decoder to {source.Codec}"); @@ -471,7 +496,6 @@ private static async Task PlayPipelineAsync( decoder = CreateDecoderForCodec(source.Codec, source.SampleRate, source.Channels); } - // Инициализируем AAC декодер если есть config if (decoder is AacDecoder aacDecoder && source.DecoderConfig != null) { aacDecoder.Initialize(source.DecoderConfig); @@ -495,9 +519,6 @@ private static IAudioDecoder CreateDecoderForCodec(AudioCodec codec, int sampleR }; } - /// - /// Создаёт backend: NAudio primary, Null fallback. - /// private static IPlaybackBackend CreateBackend() { try @@ -530,7 +551,7 @@ private static void PrintSummary(int framesRead, long bytesRead, long lastTimest Console.WriteLine($" Frames decoded: {framesRead}"); Console.WriteLine($" Data processed: {bytesRead / 1024.0:F1} KB"); Console.WriteLine($" Last position: {lastTimestampMs / 1000.0:F1}s"); - Console.WriteLine($" Buffer progress: {bufferProgress:F0}%"); + Console.WriteLine($" Cache progress: {bufferProgress:F0}%"); Console.WriteLine($" Status: {(endOfStream ? "✓ Completed" : error != null ? $"❌ {error}" : "⏹ Stopped")}"); Console.WriteLine("════════════════════════════════════════════════════════════"); } diff --git a/Core/Services/LoggingHandler.cs b/Core/Helpers/LoggingHandler.cs similarity index 89% rename from Core/Services/LoggingHandler.cs rename to Core/Helpers/LoggingHandler.cs index cd03677..9da6eea 100644 --- a/Core/Services/LoggingHandler.cs +++ b/Core/Helpers/LoggingHandler.cs @@ -1,6 +1,4 @@ -using System.Diagnostics; - -namespace LMP.Core.Services.Streaming; +namespace LMP.Core.Helpers; /// /// HTTP handler для логирования всех запросов в DEBUG. @@ -20,17 +18,26 @@ protected override async Task SendAsync( #endif HttpResponseMessage response; +#if DEBUG && HTTP_LOG try { response = await base.SendAsync(request, cancellationToken); } catch (Exception ex) { -#if DEBUG && HTTP_LOG Log.Warn($"[HTTP] ✗ {request.Method} {request.RequestUri} - {ex.Message}"); -#endif throw; } +#else + try + { + response = await base.SendAsync(request, cancellationToken); + } + catch (Exception) + { + throw; + } +#endif #if DEBUG && HTTP_LOG sw.Stop(); diff --git a/Core/Models/RefCountedBitmap.cs b/Core/Models/RefCountedBitmap.cs index def5c5d..fd3e90b 100644 --- a/Core/Models/RefCountedBitmap.cs +++ b/Core/Models/RefCountedBitmap.cs @@ -10,19 +10,18 @@ namespace LMP.Core.Models; /// public sealed class RefCountedBitmap : IDisposable { - private readonly Bitmap _bitmap; private int _refCount; private bool _isDisposed; private readonly object _lock = new(); - public Bitmap Bitmap => _bitmap; + public Bitmap Bitmap { get; } public int RefCount => Volatile.Read(ref _refCount); public long EstimatedBytes { get; } public DateTime CachedAt { get; } public RefCountedBitmap(Bitmap bitmap) { - _bitmap = bitmap ?? throw new ArgumentNullException(nameof(bitmap)); + Bitmap = bitmap ?? throw new ArgumentNullException(nameof(bitmap)); _refCount = 1; // Начинаем с 1 (cache держит ссылку) long pixelCount = (long)bitmap.PixelSize.Width * bitmap.PixelSize.Height; @@ -73,8 +72,8 @@ private void DisposeInternal() try { - _bitmap.Dispose(); - Log.Trace($"[RefCountedBitmap] Disposed bitmap {_bitmap.PixelSize}"); + Bitmap.Dispose(); + Log.Trace($"[RefCountedBitmap] Disposed bitmap {Bitmap.PixelSize}"); } catch (Exception ex) { diff --git a/Core/Models/TrackInfo.cs b/Core/Models/TrackInfo.cs index 45926c3..ace5874 100644 --- a/Core/Models/TrackInfo.cs +++ b/Core/Models/TrackInfo.cs @@ -14,8 +14,6 @@ public sealed class TrackInfo : ReactiveObject, IBatchItem, ISearchResult { private static readonly ConditionalWeakTable _idCache = new(); - private string _id = string.Empty; - #region Identity /// @@ -23,30 +21,30 @@ public sealed class TrackInfo : ReactiveObject, IBatchItem, ISearchResult /// public string Id { - get => _id; + get; set { - if (_id == value) return; + if (field == value) return; if (!string.IsNullOrEmpty(value)) { if (value.StartsWith("yt_") || value.StartsWith("yt_pl_")) { - _id = value; + field = value; } else { - _id = GetCachedPrefixedId("yt_", value); + field = GetCachedPrefixedId("yt_", value); } } else { - _id = value ?? string.Empty; + field = value ?? string.Empty; } this.RaisePropertyChanged(); } - } + } = string.Empty; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetCachedPrefixedId(string prefix, string rawId) @@ -65,7 +63,7 @@ private static string GetCachedPrefixedId(string prefix, string rawId) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan GetRawIdSpan() { - var span = _id.AsSpan(); + var span = Id.AsSpan(); if (span.StartsWith("yt_pl_".AsSpan())) return span[6..]; if (span.StartsWith("yt_".AsSpan())) @@ -79,9 +77,9 @@ public ReadOnlySpan GetRawIdSpan() [MethodImpl(MethodImplOptions.AggressiveInlining)] public string GetRawId() { - if (_id.StartsWith("yt_pl_")) return _id.Substring(6); - if (_id.StartsWith("yt_")) return _id.Substring(3); - return _id; + if (Id.StartsWith("yt_pl_")) return Id.Substring(6); + if (Id.StartsWith("yt_")) return Id.Substring(3); + return Id; } #endregion @@ -152,12 +150,11 @@ public string GetRawId() #region Playlists - private HashSet? _inPlaylists; public HashSet InPlaylists { - get => _inPlaylists ??= new HashSet(StringComparer.Ordinal); - set => _inPlaylists = value; + get => field ??= new HashSet(StringComparer.Ordinal); + set; } #endregion diff --git a/Core/Services/AudioEngine.cs b/Core/Services/AudioEngine.cs index da762fc..5c1085f 100644 --- a/Core/Services/AudioEngine.cs +++ b/Core/Services/AudioEngine.cs @@ -1,14 +1,8 @@ -// Core/Services/AudioEngine.cs - -using System.Diagnostics; -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; using System.Threading.Channels; -using LibVLCSharp.Shared; +using LMP.Core.Audio; using LMP.Core.Models; -using LMP.Core.Services.Streaming; using LMP.Core.ViewModels; -using LMP.Core.Youtube; -using LMP.Core.Youtube.Utils; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -19,12 +13,7 @@ public sealed class AudioEngine : ViewModelBase, IDisposable #region Constants private const int MaxConsecutiveErrors = 3; - private const int ApiCooldownMs = 200; - private const int QualitySwitchTimeoutMs = 8000; private const int MaxHistorySize = 100; - private const int RefreshTimeoutMs = 60_000; - private const int CommandTimeoutMs = 5000; - private const int LockTimeoutMs = 3000; #endregion @@ -32,62 +21,26 @@ public sealed class AudioEngine : ViewModelBase, IDisposable private readonly YoutubeProvider _youtube; private readonly LibraryService _library; - private readonly StreamCacheManager _cacheManager; - private readonly HttpClient _httpClient; - - #endregion - - #region VLC Core - - private LibVLC? _libVLC; - private MediaPlayer? _player; - private Media? _currentMedia; - - /// - /// Унифицированный стрим (HLS или MemoryFirst) - /// - private MediaStreamBase? _currentStream; + private readonly AudioPlayer _player; #endregion #region Synchronization - private readonly SemaphoreSlim _playbackLock = new(1, 1); - private readonly SemaphoreSlim _apiLock = new(1, 1); - private readonly Channel> _commandQueue; - - private CancellationTokenSource? _playbackCts; - private TaskCompletionSource? _playbackStartedTcs; - private int _session; + private readonly Channel> _commandQueue; + private readonly CancellationTokenSource _lifetimeCts = new(); + private readonly Lock _queueLock = new(); + private readonly Lock _seekDebounce = new(); + private volatile int _session; #endregion #region State - [Flags] - private enum StateFlags - { - None = 0, - Playing = 1 << 0, - Paused = 1 << 1, - Loading = 1 << 2, - Ready = 1 << 3, - Navigating = 1 << 4, - Disposed = 1 << 5, - SuppressAutoNext = 1 << 6 - } - - private int _stateFlags; private int _consecutiveErrors; - private long _cachedTimeMs; - private long _cachedLengthMs; private int _volumePercent; - private DateTime _lastApiCall; - private DateTime _lastVolumeChange; - - private string _activeCodec = ""; - private int _activeBitrate; - private StreamingConfig _streamingConfig; + private bool _volumeInitialized; + private CancellationTokenSource? _lastSeekCts; #endregion @@ -95,10 +48,7 @@ private enum StateFlags private readonly List _queue = new(64); private readonly List _history = new(MaxHistorySize); - private readonly Lock _queueLock = new(); private int _currentIndex = -1; - private IReadOnlyList? _queueSnapshot; - private int _queueVersion; #endregion @@ -106,59 +56,35 @@ private enum StateFlags [Reactive] public TrackInfo? CurrentTrack { get; private set; } - public bool IsPlaying => HasFlag(StateFlags.Playing); - public bool IsPaused => HasFlag(StateFlags.Paused); - public bool IsLoading => HasFlag(StateFlags.Loading); + [Reactive] public AudioStreamInfo StreamInfo { get; private set; } = AudioStreamInfo.Empty; + + public bool IsPlaying => _player.State == PlaybackState.Playing; + public bool IsPaused => _player.State == PlaybackState.Paused; + public bool IsLoading => _player.State == PlaybackState.Loading || _player.State == PlaybackState.Buffering; + [AllowNull] public IReadOnlyList Queue { get { lock (_queueLock) { - _queueSnapshot ??= [.. _queue]; - return _queueSnapshot; + field ??= [.. _queue]; + return field; } } + private set; } public int CurrentQueueIndex => Volatile.Read(ref _currentIndex); public bool ShuffleEnabled { get; set; } public RepeatMode RepeatMode { get; set; } - public TimeSpan CurrentPosition => TimeSpan.FromMilliseconds(Volatile.Read(ref _cachedTimeMs)); - - public TimeSpan TotalDuration - { - get - { - var len = Volatile.Read(ref _cachedLengthMs); - return len > 0 ? TimeSpan.FromMilliseconds(len) : (CurrentTrack?.Duration ?? TimeSpan.Zero); - } - } - - /// - /// Прогресс буферизации (0-100). - /// - public double BufferProgress => _currentStream?.DownloadProgress ?? 0; - - /// - /// Количество загруженных байт. - /// - public long GetDownloadedBytes() => _currentStream?.BufferedBytes ?? 0; - - /// - /// Закэшированные диапазоны для визуализации. - /// - public IReadOnlyList<(double Start, double End)> GetBufferedRanges() => - _currentStream?.GetBufferedRanges() ?? []; - - /// - /// Полностью загружен. - /// - public bool IsFullyBuffered => _currentStream?.IsFullyDownloaded ?? false; + public TimeSpan CurrentPosition => _player.Position; + public TimeSpan TotalDuration => _player.Duration; - public string VlcStateString => _player?.State.ToString() ?? "None"; + public double BufferProgress => _player.BufferProgress; + public bool IsFullyBuffered => _player.IsFullyBuffered; #endregion @@ -168,1212 +94,603 @@ public TimeSpan TotalDuration public event Action? OnPlaybackStopped; public event Action? OnError; public event Action? OnPositionChanged; - public event Action? OnMaxVolumeChanged; - public event Action? OnStreamInfoReady; public event Action? OnPlaybackStateChanged; public event Action? OnQueueChanged; - public event Action? OnCriticalError; public event Action? OnLoadingStateChanged; + public event Action? OnCriticalError; + public event Action? OnMaxVolumeChanged; + public event Action? OnStreamInfoChanged; + public event Action? OnBufferStateChanged; #endregion - #region Constructor - - public AudioEngine(YoutubeProvider youtube, LibraryService library, StreamCacheManager cacheManager) + public AudioEngine(YoutubeProvider youtube, LibraryService library) { _youtube = youtube; _library = library; - _cacheManager = cacheManager; - _httpClient = CreateHttpClient(); - _commandQueue = Channel.CreateBounded>( - new BoundedChannelOptions(16) - { - SingleReader = true, - FullMode = BoundedChannelFullMode.DropOldest - }); + var options = new AudioPlayerOptions + { + UrlRefreshCallback = RefreshUrlCallback, + PositionUpdateInterval = TimeSpan.FromMilliseconds(200), + MaxRetryAttempts = 3, + UseNullBackend = false + }; + + _player = new AudioPlayer(options); + + _player.Events.PositionChanged += pos => RaiseOnUI(() => OnPositionChanged?.Invoke(pos)); + _player.Events.StateChanged += OnPlayerStateChanged; + _player.Events.TrackEnded += OnPlayerTrackEnded; + _player.Events.ErrorOccurred += err => RaiseOnUI(() => OnError?.Invoke(err.Message)); + _player.Events.StreamInfoChanged += OnStreamInfoReceived; + _player.Events.BufferStateChanged += state => + { + Log.Debug($"[AudioEngine] BufferStateChanged received: progress={state.Progress:F1}%, " + + $"ranges={state.Ranges.Count}"); + + RaiseOnUI(() => OnBufferStateChanged?.Invoke(state)); + }; ShuffleEnabled = library.Settings.ShuffleEnabled; RepeatMode = library.Settings.RepeatMode; - _volumePercent = NormalizeVolume(library.Settings.Volume); - _streamingConfig = GetStreamingConfig(library.Settings.InternetProfile); - InitializeVLC(); + var savedVolume = library.Settings.Volume; + _volumePercent = savedVolume > 0 ? NormalizeVolume(savedVolume) : 60; + + ApplyVolume(); + + _commandQueue = Channel.CreateBounded>(new BoundedChannelOptions(16) + { + SingleReader = true, + FullMode = BoundedChannelFullMode.DropOldest + }); _ = ProcessCommandsAsync(); _ = VolumeSaveLoopAsync(); - Log.Info($"[AudioEngine] Ready. Volume={_volumePercent}%"); + Log.Info($"[AudioEngine] Native Engine Ready. Volume={_volumePercent}%"); + } + + #region Stream Info + + private void OnStreamInfoReceived(AudioStreamInfo info) + { + RaiseOnUI(() => + { + StreamInfo = info; + OnStreamInfoChanged?.Invoke(info); + + Log.Debug($"[AudioEngine] Stream info: {info.FormatDisplay}"); + }); } - private static HttpClient CreateHttpClient() => new(new LoggingHandler(new SocketsHttpHandler + public (string Format, int Bitrate, bool IsReady) GetCurrentStreamInfo() { - PooledConnectionLifetime = TimeSpan.FromMinutes(15), - PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2), - MaxConnectionsPerServer = 8, - ConnectTimeout = TimeSpan.FromSeconds(4), - EnableMultipleHttp2Connections = true - })) + var info = StreamInfo; + return (info.Codec, info.Bitrate, info.IsValid); + } + + #endregion + + #region Public API + + public void EnqueueRange(IEnumerable tracks) { - Timeout = TimeSpan.FromMinutes(4), - DefaultRequestHeaders = + lock (_queueLock) { - { "User-Agent", YoutubeClientUtils.UserAgent }, - { "Accept-Language", YoutubeHttpHandler.GetHl() }, - { "Accept", "*/*" }, - { "Origin", "https://www.youtube.com/" }, - { "Referer", "https://www.youtube.com/" } + _queue.AddRange(tracks); + InvalidateQueueSnapshot(); } - }; + RaiseOnUI(() => OnQueueChanged?.Invoke()); + } - #endregion + public void ShuffleQueue() + { + lock (_queueLock) + { + if (_queue.Count < 2) return; + + var current = _currentIndex >= 0 && _currentIndex < _queue.Count ? _queue[_currentIndex] : null; + + var n = _queue.Count; + while (n > 1) + { + n--; + var k = Random.Shared.Next(n + 1); + (_queue[k], _queue[n]) = (_queue[n], _queue[k]); + } + + if (current != null) + { + var newIndex = _queue.IndexOf(current); + if (newIndex != 0) + { + _queue.RemoveAt(newIndex); + _queue.Insert(0, current); + _currentIndex = 0; + } + else + { + _currentIndex = 0; + } + } + else + { + _currentIndex = -1; + } - #region VLC Initialization + InvalidateQueueSnapshot(); + } + RaiseOnUI(() => OnQueueChanged?.Invoke()); + } - private void InitializeVLC() + public void UpdateAudioSettings() { - LibVLCSharp.Shared.Core.Initialize(); - var cache = _streamingConfig.VlcNetworkCachingMs; - - _libVLC = new LibVLC( - "--no-video", "--vout=none", "--no-spu", "--no-osd", "--no-stats", - $"--network-caching={cache}", - $"--file-caching={cache}", - $"--live-caching={cache}", - "--http-reconnect", "--http-continuous", - "--audio-resampler=speex", "--aout=wasapi", - "--clock-jitter=0", "--clock-synchro=0" - ); - - _player = new MediaPlayer(_libVLC); - AttachPlayerEvents(_player); ApplyVolume(); + RaiseOnUI(() => OnMaxVolumeChanged?.Invoke(_library.Settings.MaxVolumeLimit)); } - private void AttachPlayerEvents(MediaPlayer player) + public static Task ReinitializeWithProfileAsync(InternetProfile profile) { - player.Playing += (_, _) => OnVlcPlaying(); - player.Paused += (_, _) => OnVlcPaused(); - player.Stopped += (_, _) => OnVlcStopped(); - player.EndReached += (_, _) => OnVlcEndReached(); - player.EncounteredError += (_, _) => OnVlcError(); - player.TimeChanged += (_, e) => - { - Volatile.Write(ref _cachedTimeMs, e.Time); - RaiseOnUI(() => OnPositionChanged?.Invoke(TimeSpan.FromMilliseconds(e.Time))); - }; - player.LengthChanged += (_, e) => - { - Volatile.Write(ref _cachedLengthMs, e.Length); - RaiseOnUI(() => this.RaisePropertyChanged(nameof(TotalDuration))); - }; + Log.Info($"[AudioEngine] Profile switched to {profile}. (No-op in Native Engine)"); + return Task.CompletedTask; } - public async Task ReinitializeWithProfileAsync(InternetProfile profile) + public static void NotifyAppMinimized() + { + GC.Collect(1, GCCollectionMode.Optimized, false); + } + + /// + /// Переключает качество потока с сохранением позиции. + /// + public async Task SwitchQualityAsync(string container, int bitrate) { - Log.Info($"[AudioEngine] Switching to profile: {profile}"); - CancelCurrentPlayback(); + if (CurrentTrack == null) return; + + var pos = CurrentPosition; + var track = CurrentTrack; + + track.TransientContainer = container; + track.TransientBitrate = bitrate; + + if (_library.Settings.RememberTrackFormat) + { + track.PreferredContainer = container; + track.PreferredBitrate = bitrate; + } + + Log.Info($"[AudioEngine] Switching quality to {container}/{bitrate}kbps at {pos.TotalSeconds:F1}s"); + + var session = Interlocked.Increment(ref _session); await EnqueueCommandAsync(async () => { - await CleanupMediaAsync(); - _player?.Dispose(); - _player = null; - _libVLC?.Dispose(); + if (_session != session) return; + + try + { + await _player.StopAsync(); + + track.StreamUrl = ""; + + var streamInfo = await _youtube.RefreshStreamUrlAsync(track, true, CancellationToken.None); + if (streamInfo == null) + { + Log.Error("[AudioEngine] Failed to get new stream URL"); + RaiseOnUI(() => OnError?.Invoke("Failed to switch quality")); + return; + } - _streamingConfig = GetStreamingConfig(profile); - InitializeVLC(); - ClearState(); + // Пробрасываем bitrate hint для точного cacheKey + await _player.PlayAsync(streamInfo.Value.Url, track.Id, bitrate, CancellationToken.None); + + // Seek сразу после PlayAsync, не через delay + if (pos.TotalSeconds > 1) + { + // Ждём пока декодер начнёт работать + await WaitForPlayerReadyAsync(TimeSpan.FromSeconds(2)); + await _player.SeekAsync(pos); + } - RaiseOnUI(() => + Log.Info($"[AudioEngine] Quality switched to {container}/{bitrate}kbps at {pos.TotalSeconds:F1}s"); + } + catch (Exception ex) { - OnTrackChanged?.Invoke(null); - OnPlaybackStopped?.Invoke(); - }); + Log.Error($"[AudioEngine] Quality switch failed: {ex.Message}"); + RaiseOnUI(() => OnError?.Invoke("Failed to switch quality")); + } }); } + /// + /// Ждёт пока плеер будет в состоянии Playing или Paused. + /// + private async Task WaitForPlayerReadyAsync(TimeSpan timeout) + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + while (sw.Elapsed < timeout) + { + var state = _player.State; + if (state == PlaybackState.Playing || state == PlaybackState.Paused) + return; + + await Task.Delay(50); + } + } + + public long GetDownloadedBytes() => _player.GetDownloadedBytes(); + + public IReadOnlyList<(double Start, double End)> GetBufferedRanges() => _player.GetBufferedRanges(); + #endregion #region Playback Control public Task PlayTrackAsync(TrackInfo track) { - if (track == null || HasFlag(StateFlags.Disposed)) return Task.CompletedTask; - - var newSession = CancelCurrentPlayback(); + if (track == null) return Task.CompletedTask; + var session = Interlocked.Increment(ref _session); return EnqueueCommandAsync(async () => { - if (_session != newSession) return; - + if (_session != session) return; lock (_queueLock) { var idx = _queue.FindIndex(t => t.Id == track.Id); - if (idx >= 0) - { - _currentIndex = idx; - _queue[idx] = track; - } - else - { - _queue.Clear(); - _queue.Add(track); - _currentIndex = 0; - InvalidateQueueSnapshot(); - } + if (idx >= 0) { _currentIndex = idx; _queue[idx] = track; } + else { _queue.Clear(); _queue.Add(track); _currentIndex = 0; InvalidateQueueSnapshot(); } } - RaiseOnUI(() => OnQueueChanged?.Invoke()); - await PlayCurrentIndexAsync(newSession); + await PlayCurrentIndexAsync(session); }); } public Task StartQueueAsync(IEnumerable tracks, TrackInfo startTrack) { - if (HasFlag(StateFlags.Disposed)) return Task.CompletedTask; - - var newSession = CancelCurrentPlayback(); - + var session = Interlocked.Increment(ref _session); return EnqueueCommandAsync(async () => { - if (_session != newSession) return; - + if (_session != session) return; lock (_queueLock) { - _queue.Clear(); - _queue.AddRange(tracks); + _queue.Clear(); _queue.AddRange(tracks); _currentIndex = _queue.FindIndex(t => t.Id == startTrack.Id); if (_currentIndex == -1 && _queue.Count > 0) _currentIndex = 0; InvalidateQueueSnapshot(); } - RaiseOnUI(() => OnQueueChanged?.Invoke()); - await PlayCurrentIndexAsync(newSession); + await PlayCurrentIndexAsync(session); }); } public async Task SetPlaybackStateAsync(bool shouldPlay) { - if (HasFlag(StateFlags.Disposed) || _player == null) return; - - using var cts = new CancellationTokenSource(CommandTimeoutMs); - - await Task.Run(() => + await EnqueueCommandAsync(async () => { - var state = _player?.State ?? VLCState.Error; - if (shouldPlay) { - _currentStream?.NotifyPaused(false); - - if (state is VLCState.Paused or VLCState.Stopped) - _player?.Play(); - else if (state is VLCState.Ended) - { - var session = CancelCurrentPlayback(); - _ = EnqueueCommandAsync(() => PlayCurrentIndexAsync(session)); - } - else - _player?.Play(); - - SetFlag(StateFlags.Playing, true); - SetFlag(StateFlags.Paused, false); - } - else - { - if (state is VLCState.Playing or VLCState.Buffering or VLCState.Opening) - _player?.Pause(); - - _currentStream?.NotifyPaused(true); - - SetFlag(StateFlags.Playing, false); - SetFlag(StateFlags.Paused, true); + if (_player.State == PlaybackState.Paused) _player.Resume(); + else if (_player.State == PlaybackState.Stopped && CurrentTrack != null) await PlayCurrentIndexAsync(_session); } - }, cts.Token); - - NotifyPlaybackState(); + else _player.Pause(); + }); } - public ValueTask SeekAsync(TimeSpan position) + public async ValueTask SeekAsync(TimeSpan position) { - if (_player == null || !HasFlag(StateFlags.Ready) || HasFlag(StateFlags.Disposed)) - return ValueTask.CompletedTask; - - var ms = (long)Math.Clamp(position.TotalMilliseconds, 0, TotalDuration.TotalMilliseconds); - - Log.Debug($"[AudioEngine] Seek to {position.TotalSeconds:F1}s"); + lock (_seekDebounce) + { + _lastSeekCts?.Cancel(); + _lastSeekCts?.Dispose(); + _lastSeekCts = new CancellationTokenSource(); + } - SetFlag(StateFlags.SuppressAutoNext, true); + var localCts = _lastSeekCts; try { - _currentStream?.NotifySeek(ms); + await Task.Delay(50, localCts.Token); + await _player.SeekAsync(position, localCts.Token); } + catch (OperationCanceledException) { } + catch (ObjectDisposedException) { } catch (Exception ex) { - Log.Warn($"[AudioEngine] NotifySeek error: {ex.Message}"); + Log.Warn($"[AudioEngine] Seek error: {ex.Message}"); } - - Volatile.Write(ref _cachedTimeMs, ms); - - _ = Task.Run(async () => - { - try - { - if (_player == null || HasFlag(StateFlags.Disposed)) return; - - // Запоминаем что нужно возобновить воспроизведение - bool shouldPlay = IsPlaying || _player.State == VLCState.Paused; - - Log.Debug($"[AudioEngine] Setting VLC time to {ms}ms, state={_player.State}"); - - // Устанавливаем время - _player.Time = ms; - - await Task.Delay(50); - - if (HasFlag(StateFlags.Disposed)) return; - - if (shouldPlay) - { - // Пробуем несколько способов возобновить воспроизведение - for (int retry = 0; retry < 10; retry++) - { - if (HasFlag(StateFlags.Disposed)) return; - - var state = _player.State; - if (state == VLCState.Playing) - { - Log.Debug($"[AudioEngine] VLC playing after {retry} retries"); - break; - } - - Log.Debug($"[AudioEngine] Play attempt {retry + 1}, state={state}"); - - // Разные методы в зависимости от состояния - if (state == VLCState.Paused) - { - _player.SetPause(false); - } - else if (state == VLCState.Stopped || state == VLCState.Ended) - { - _player.Play(); - } - else - { - _player.Play(); - } - - await Task.Delay(50 + retry * 20); - } - - // Последняя попытка - if (_player.State != VLCState.Playing && !HasFlag(StateFlags.Disposed)) - { - Log.Warn($"[AudioEngine] Force play, state={_player.State}"); - _player.Play(); - } - } - - await Task.Delay(50); - SetFlag(StateFlags.SuppressAutoNext, false); - - Log.Debug($"[AudioEngine] Seek complete, VLC state={_player.State}"); - } - catch (Exception ex) - { - Log.Warn($"[AudioEngine] VLC seek error: {ex.Message}"); - SetFlag(StateFlags.SuppressAutoNext, false); - } - }); - - return ValueTask.CompletedTask; } public void Stop() { - CancelCurrentPlayback(); - - _ = EnqueueCommandAsync(async () => + Interlocked.Increment(ref _session); + _player.Stop(); + RaiseOnUI(() => { - await CleanupMediaAsync(); - ClearState(); - - RaiseOnUI(() => - { - OnTrackChanged?.Invoke(null); - OnPlaybackStopped?.Invoke(); - }); - NotifyPlaybackState(); + CurrentTrack = null; + StreamInfo = AudioStreamInfo.Empty; + OnTrackChanged?.Invoke(null); + OnPlaybackStopped?.Invoke(); }); } - public Task PlayNextAsync() => NavigateAsync(forward: true, userInitiated: true); - public Task PlayPreviousAsync() => NavigateAsync(forward: false, userInitiated: true); + public Task PlayNextAsync() => NavigateAsync(true, true); + public Task PlayPreviousAsync() => NavigateAsync(false, true); - private int CancelCurrentPlayback() - { - SetFlag(StateFlags.SuppressAutoNext, true); - var newSession = Interlocked.Increment(ref _session); + #endregion - Log.Debug($"[AudioEngine] Cancel session → {newSession}"); + #region Internal Logic - // Отменяем CTS - try + private async Task PlayCurrentIndexAsync(int session) + { + TrackInfo? track; + lock (_queueLock) { - _playbackCts?.Cancel(); - _playbackCts?.Dispose(); + if (_currentIndex < 0 || _currentIndex >= _queue.Count) return; + track = _queue[_currentIndex]; } - catch { } - _playbackCts = null; - - // Немедленно отменяем операции в стриме - try { _currentStream?.CancelPendingReads(); } - catch { } - - return newSession; - } + if (track == null) return; + RaiseOnUI(() => + { + CurrentTrack = track; + StreamInfo = AudioStreamInfo.Empty; + OnTrackChanged?.Invoke(track); + OnPositionChanged?.Invoke(TimeSpan.Zero); + }); - #endregion + try + { + string? streamUrl = track.StreamUrl; + int bitrateHint = track.TransientBitrate; - #region Volume + // Проверяем кэш + var cached = AudioSourceFactory.FindAnyCachedTrack(track.Id); - public void SaveVolumeNow() => _library.UpdateSettings(s => s.Volume = _volumePercent); - public float GetVolume() => _volumePercent; + if (cached != null && string.IsNullOrEmpty(streamUrl)) + { + Log.Debug($"[AudioEngine] Using cache: {cached.Value.Entry.Format}/{cached.Value.Entry.Bitrate}kbps"); + streamUrl = ""; + bitrateHint = cached.Value.Entry.Bitrate; + } + else if (string.IsNullOrEmpty(streamUrl)) + { + var streamInfo = await _youtube.RefreshStreamUrlAsync(track, false, CancellationToken.None); + if (streamInfo == null) throw new Exception("Failed to resolve URL"); + streamUrl = streamInfo.Value.Url; + // Используем битрейт из stream info если не задан явно + if (bitrateHint <= 0) + bitrateHint = streamInfo.Value.Bitrate; + } - public void SetVolumeInstant(float value) - { - _volumePercent = Math.Clamp((int)Math.Round(value), 0, 500); - _lastVolumeChange = DateTime.UtcNow; - ApplyVolume(); - } + if (_session != session) return; - public void ToggleMute() - { - if (_volumePercent > 0) - { - _library.UpdateSettings(s => s.LastVolume = _volumePercent); - SetVolumeInstant(0); + await _player.PlayAsync(streamUrl ?? "", track.Id, bitrateHint, CancellationToken.None); + AddToHistory(track); } - else + catch (Exception ex) { - var restore = _library.Settings.LastVolume > 0 ? _library.Settings.LastVolume : 50; - SetVolumeInstant(restore); + Log.Error($"[AudioEngine] Play error: {ex.Message}"); + RaiseOnUI(() => OnError?.Invoke(ex.Message)); + await HandlePlaybackErrorAsync(); } } - public void UpdateAudioSettings() + private async ValueTask RefreshUrlCallback(string trackId, CancellationToken ct) { - RaiseOnUI(() => OnMaxVolumeChanged?.Invoke(_library.Settings.MaxVolumeLimit)); - ApplyVolume(); + var track = await _library.GetTrackAsync(trackId); + if (track == null) return null; + var info = await _youtube.RefreshStreamUrlAsync(track, true, ct); + return info?.Url; } - private void ApplyVolume() + private async Task NavigateAsync(bool forward, bool userInitiated) { - if (_player == null || HasFlag(StateFlags.Disposed)) return; + var session = Interlocked.Increment(ref _session); + bool canMove; + lock (_queueLock) { canMove = forward ? TryMoveNext(userInitiated) : TryMovePrevious(); } - try - { - var gain = MathF.Pow(10f, Math.Clamp(_library.Settings.TargetGainDb, -20f, 20f) / 20f); - var final = Math.Clamp((int)Math.Round(_volumePercent * gain), 0, 500); - if (_player.Volume != final) _player.Volume = final; - } - catch { } - } - - private async Task VolumeSaveLoopAsync() - { - while (!HasFlag(StateFlags.Disposed)) - { - await Task.Delay(2000); - if ((DateTime.UtcNow - _lastVolumeChange).TotalSeconds >= 1.5) - _library.UpdateSettings(s => s.Volume = _volumePercent); - } - } - - private static int NormalizeVolume(float saved) => - Math.Clamp(saved is <= 1f and > 0 ? (int)(saved * 100) : (int)saved, 0, 500); - - #endregion - - #region Queue Management - - public void Enqueue(TrackInfo track) - { - lock (_queueLock) - { - if (_queue.Any(t => t.Id == track.Id)) return; - _queue.Add(track); - InvalidateQueueSnapshot(); - } - - RaiseOnUI(() => OnQueueChanged?.Invoke()); - - if (CurrentTrack == null && !IsPlaying && !IsLoading) - { - lock (_queueLock) _currentIndex = _queue.Count - 1; - var session = CancelCurrentPlayback(); - _ = EnqueueCommandAsync(() => PlayCurrentIndexAsync(session)); - } + if (canMove) await EnqueueCommandAsync(() => PlayCurrentIndexAsync(session)); + else if (!forward && _player.State != PlaybackState.Stopped) await _player.SeekAsync(TimeSpan.Zero); + else Stop(); } - public void EnqueueRange(IEnumerable tracks) + private bool TryMoveNext(bool userInitiated) { - int added = 0; - lock (_queueLock) - { - var existing = _queue.Select(t => t.Id).ToHashSet(); - foreach (var track in tracks) - { - if (existing.Add(track.Id)) - { - _queue.Add(track); - added++; - } - } - if (added > 0) InvalidateQueueSnapshot(); - } - - if (added > 0) - { - RaiseOnUI(() => OnQueueChanged?.Invoke()); - if (CurrentTrack == null && !IsPlaying && !IsLoading) - _ = PlayNextAsync(); - } + if (_queue.Count == 0) return false; + if (!userInitiated && RepeatMode == RepeatMode.RepeatOne) return true; + if (_currentIndex + 1 < _queue.Count) { _currentIndex++; return true; } + if (RepeatMode == RepeatMode.RepeatAll) { _currentIndex = 0; return true; } + return false; } - public void ClearQueue() + private bool TryMovePrevious() { - lock (_queueLock) - { - var current = CurrentTrack; - _queue.Clear(); - _currentIndex = -1; - - if (current != null) - { - _queue.Add(current); - _currentIndex = 0; - } - InvalidateQueueSnapshot(); - } - RaiseOnUI(() => OnQueueChanged?.Invoke()); + if (_queue.Count == 0) return false; + if (CurrentPosition.TotalSeconds > 3) return false; + if (_currentIndex > 0) { _currentIndex--; return true; } + if (RepeatMode == RepeatMode.RepeatAll) { _currentIndex = _queue.Count - 1; return true; } + return false; } - public void ShuffleQueue() + private async Task HandlePlaybackErrorAsync() { - lock (_queueLock) + if (++_consecutiveErrors >= MaxConsecutiveErrors) { - if (_queue.Count < 2) return; - - var current = _currentIndex >= 0 && _currentIndex < _queue.Count ? _queue[_currentIndex] : null; - Random.Shared.Shuffle(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_queue)); - - if (current != null) - { - _currentIndex = _queue.IndexOf(current); - if (_currentIndex == -1) - { - _queue.Insert(0, current); - _currentIndex = 0; - } - } - InvalidateQueueSnapshot(); + Stop(); + RaiseOnUI(() => OnCriticalError?.Invoke("Error", "Too many playback errors")); + _consecutiveErrors = 0; + return; } - RaiseOnUI(() => OnQueueChanged?.Invoke()); + await Task.Delay(1000); + await PlayNextAsync(); } - public void RemoveFromQueue(TrackInfo track) + private void OnPlayerStateChanged(PlaybackState state) { - bool needStop = false; - lock (_queueLock) + RaiseOnUI(() => { - var idx = _queue.FindIndex(t => t.Id == track.Id); - if (idx == -1) return; + this.RaisePropertyChanged(nameof(IsPlaying)); + this.RaisePropertyChanged(nameof(IsPaused)); + this.RaisePropertyChanged(nameof(IsLoading)); + this.RaisePropertyChanged(nameof(TotalDuration)); - if (idx == _currentIndex) - { - needStop = _queue.Count == 1; - if (idx == _queue.Count - 1) _currentIndex--; - } - else if (idx < _currentIndex) _currentIndex--; + OnPlaybackStateChanged?.Invoke(state == PlaybackState.Playing, state == PlaybackState.Paused); + OnLoadingStateChanged?.Invoke(state == PlaybackState.Loading || state == PlaybackState.Buffering); - _queue.RemoveAt(idx); - InvalidateQueueSnapshot(); - } - - RaiseOnUI(() => OnQueueChanged?.Invoke()); - if (needStop) Stop(); + if (state == PlaybackState.Playing) _consecutiveErrors = 0; + }); } - public void MoveQueueItem(int from, int to) + private void OnPlayerTrackEnded() { - lock (_queueLock) + var session = Interlocked.Increment(ref _session); + _ = Task.Run(async () => { - if (from < 0 || from >= _queue.Count || to < 0 || to >= _queue.Count || from == to) return; - - var item = _queue[from]; - _queue.RemoveAt(from); - _queue.Insert(to, item); - - if (_currentIndex == from) _currentIndex = to; - else if (from < _currentIndex && to >= _currentIndex) _currentIndex--; - else if (from > _currentIndex && to <= _currentIndex) _currentIndex++; - - InvalidateQueueSnapshot(); - } - RaiseOnUI(() => OnQueueChanged?.Invoke()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void InvalidateQueueSnapshot() - { - _queueSnapshot = null; - _queueVersion++; + bool canAdvance; + lock (_queueLock) { canAdvance = TryMoveNext(false); } + if (canAdvance) await EnqueueCommandAsync(() => PlayCurrentIndexAsync(session)); + else Stop(); + }); } #endregion - #region Quality Switch - - public async Task SwitchQualityAsync(string container, int targetBitrate = 0) - { - if (CurrentTrack == null) return; - - var position = CurrentPosition; - var track = CurrentTrack; - - track.TransientContainer = container; - track.TransientBitrate = targetBitrate; - track.StreamUrl = ""; - track.CachedCodec = ""; - track.CachedBitrate = 0; - track.CachedContainer = ""; - - if (_library.Settings.RememberTrackFormat) - { - track.PreferredContainer = container; - track.PreferredBitrate = targetBitrate; - _ = _library.AddOrUpdateTrackAsync(track); - } - - _playbackStartedTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); - - var session = CancelCurrentPlayback(); - await EnqueueCommandAsync(() => PlayCurrentIndexAsync(session)); - - try - { - using var cts = new CancellationTokenSource(QualitySwitchTimeoutMs); - await _playbackStartedTcs.Task.WaitAsync(cts.Token); - } - catch (OperationCanceledException) { } - finally { _playbackStartedTcs = null; } - - if (position.TotalSeconds > 1) - { - await Task.Delay(200); - await SeekAsync(position); - } - } - - #endregion + #region Volume - #region Stream Info + public void SaveVolumeNow() => _library.UpdateSettings(s => s.Volume = _volumePercent); + public float GetVolume() => _volumePercent; - public (string Format, int Bitrate, bool IsReady) GetCurrentStreamInfo() + public void SetVolumeInstant(float value) { - if (!string.IsNullOrEmpty(_activeCodec)) - return (_activeCodec, _activeBitrate, true); - - if (CurrentTrack?.IsDownloaded == true && !string.IsNullOrEmpty(CurrentTrack.LocalPath)) - { - var ext = Path.GetExtension(CurrentTrack.LocalPath)?.TrimStart('.').ToUpperInvariant() ?? "FILE"; - var bitrate = CurrentTrack.PreferredBitrate; - if (bitrate <= 0) - { - var meta = StreamCacheManager.TryGetMetadata(CurrentTrack.Id); - if (meta != null) bitrate = meta.Bitrate; - } - return (ext, bitrate, true); - } - - return ("", 0, false); + _volumePercent = Math.Clamp((int)Math.Round(value), 0, 100); + ApplyVolume(); } - #endregion - - #region Internal Playback - - private async ValueTask PlayCurrentIndexAsync(int? expectedSession = null) + private void ApplyVolume() { - if (HasFlag(StateFlags.Disposed)) return; - - TrackInfo? track; - lock (_queueLock) - { - if (_currentIndex < 0 || _currentIndex >= _queue.Count) return; - track = _queue[_currentIndex]; - } - - if (track == null) return; + float linearPercent = _volumePercent / 100f; + float perceivedVolume = linearPercent * linearPercent; - int currentSession = expectedSession ?? Interlocked.Increment(ref _session); - if (expectedSession.HasValue && _session != currentSession) return; + float gain = MathF.Pow(10f, Math.Clamp(_library.Settings.TargetGainDb, -20f, 20f) / 20f); - SetFlag(StateFlags.SuppressAutoNext, true); - - _playbackCts?.Cancel(); - _playbackCts = new CancellationTokenSource(); - var ct = _playbackCts.Token; - - await CleanupMediaAsync(); - - if (_session != currentSession || ct.IsCancellationRequested) return; - - ClearStreamInfo(); - SetLoadingState(true); - SetFlag(StateFlags.Ready, false); - CurrentTrack = track; - - RaiseOnUI(() => - { - OnTrackChanged?.Invoke(track); - OnQueueChanged?.Invoke(); - }); - - try - { - await LoadAndPlayAsync(track, currentSession, ct); - } - catch (OperationCanceledException) - { - Log.Debug($"[AudioEngine] Cancelled session {currentSession}"); - } - catch (Exception ex) - { - Log.Error($"[AudioEngine] Playback error: {ex.Message}"); - RaiseOnUI(() => OnError?.Invoke(ex.Message)); - await HandlePlaybackErrorAsync(); - } - finally - { - if (_session == currentSession) - SetLoadingState(false); - } + _player.Volume = perceivedVolume * gain; } - private async ValueTask LoadAndPlayAsync(TrackInfo track, int session, CancellationToken ct) + public void InitializeVolumeFromSettings() { - var sw = Stopwatch.StartNew(); - MediaStreamBase? stream = null; - - try - { - // Проверки с быстрым выходом - if (ct.IsCancellationRequested || _session != session) return; - - var streamInfo = await GetStreamAsync(track, forceRefresh: false, ct); - if (streamInfo == null) throw new Exception("Failed to get stream URL"); - - if (ct.IsCancellationRequested || _session != session) return; - - bool isHls = track.IsHlsOnly || streamInfo.Container == "m3u8"; - string cacheId = track.Id; - - if (!isHls) - { - var hasOverride = track.TransientBitrate > 0 || !string.IsNullOrEmpty(track.TransientContainer); - if (hasOverride) - cacheId = $"{track.Id}_{streamInfo.Container}_{streamInfo.Bitrate}"; - StreamCacheManager.UpdateStreamInfo(cacheId, streamInfo.Codec, streamInfo.Bitrate, streamInfo.Container); - } - - SetStreamInfo(streamInfo.Codec, streamInfo.Bitrate); - Log.Info($"[AudioEngine] Stream: {streamInfo.Codec}/{streamInfo.Bitrate}kbps, HLS={isHls}"); - - if (ct.IsCancellationRequested || _session != session) return; - - // Создаём стрим - if (isHls) - { - stream = CreateHlsStream(streamInfo, track, ct); - } - else - { - stream = await CreateMemoryStreamAsync(streamInfo, track, cacheId, ct); - } - - if (stream == null) - throw new Exception("Failed to create stream"); - - // Проверка перед инициализацией - if (ct.IsCancellationRequested || _session != session) - { - stream.Dispose(); - return; - } - - if (!await stream.InitializeAsync(ct)) - { - stream.Dispose(); - throw new Exception("Stream initialization failed"); - } - - if (ct.IsCancellationRequested || _session != session) - { - stream.Dispose(); - return; - } - - if (!await stream.PreBufferAsync(ct)) - { - stream.Dispose(); - throw new Exception("PreBuffer failed"); - } - - // Финальная проверка - if (ct.IsCancellationRequested || _session != session) - { - stream.Dispose(); - return; - } + if (_volumeInitialized) return; - // Запуск воспроизведения - var media = new Media(_libVLC!, new StreamMediaInput(stream)); - StartPlayback(media, stream, track); + var savedVolume = _library.Settings.Volume; - Log.Info($"[AudioEngine] Loaded in {sw.ElapsedMilliseconds}ms"); - } - catch (OperationCanceledException) + if (savedVolume > 0 && savedVolume <= 1.0f) { - stream?.Dispose(); - Log.Debug($"[AudioEngine] Load cancelled, session {session}"); + _volumePercent = (int)(savedVolume * 100); } - catch (Exception) + else if (savedVolume > 1) { - stream?.Dispose(); - throw; + _volumePercent = Math.Clamp((int)savedVolume, 0, 100); } - } - - private HlsStream CreateHlsStream(StreamInfo info, TrackInfo track, CancellationToken ct) - { - var headers = new Dictionary - { - ["User-Agent"] = YoutubeClientUtils.UserAgent, - ["Referer"] = "https://www.youtube.com/", - ["Origin"] = "https://www.youtube.com" - }; - - return new HlsStream( - info.Url, - _httpClient, - headers, - urlRefresher: async token => - { - var s = await GetStreamAsync(track, forceRefresh: true, token); - return s?.Url; - }); - } - - - private async Task CreateMemoryStreamAsync( - StreamInfo info, TrackInfo track, string cacheId, CancellationToken ct) - { - var size = info.Size; - if (size <= 0) - size = await GetContentLengthAsync(info.Url, ct); - - if (size <= 0) + else { - Log.Warn("[AudioEngine] Cannot determine content length"); - return null; + _volumePercent = 50; } - // Инициализируем метаданные кэша - StreamCacheManager.LoadOrCreateMetadata(cacheId, info.Url, size); - - var stream = new MemoryFirstCachingStream( - cacheId, - info.Url, - size, - _httpClient, - _cacheManager, - _streamingConfig, - urlRefresher: async token => - { - var s = await GetStreamAsync(track, forceRefresh: true, token); - return s?.Url; - }, - originalTrackId: track.Id, - getPlaybackTimeMs: () => Volatile.Read(ref _cachedTimeMs), - totalDurationMs: (long)track.Duration.TotalMilliseconds); - - return stream; - } - - private void StartPlayback(Media media, MediaStreamBase stream, TrackInfo track) - { - var (oldMedia, oldStream) = (_currentMedia, _currentStream); - _currentMedia = media; - _currentStream = stream; - - _ = Task.Run(() => - { - Thread.Sleep(100); - try { oldStream?.Dispose(); } catch { } - try { oldMedia?.Dispose(); } catch { } - }); - - if (_player == null) return; - - _player.Media = media; + _volumeInitialized = true; ApplyVolume(); - _player.Play(); - AddToHistory(track); - } - private async Task NavigateAsync(bool forward, bool userInitiated) - { - if (HasFlag(StateFlags.Disposed)) return; - - var newSession = CancelCurrentPlayback(); - SetFlag(StateFlags.Navigating, true); - - try - { - if (!forward && CurrentPosition.TotalSeconds > 3 && HasFlag(StateFlags.Ready)) - { - await EnqueueCommandAsync(() => PlayCurrentIndexAsync(newSession)); - return; - } - - bool canMove; - lock (_queueLock) - { - canMove = forward ? TryMoveNext(userInitiated) : TryMovePrevious(); - } - - if (canMove) - await EnqueueCommandAsync(() => PlayCurrentIndexAsync(newSession)); - else if (!forward && HasFlag(StateFlags.Ready) && _player != null) - _player.Time = 0; - else - Stop(); - } - finally - { - SetFlag(StateFlags.Navigating, false); - } - } - - private bool TryMoveNext(bool userInitiated) - { - if (_queue.Count == 0) return false; - if (!userInitiated && RepeatMode == RepeatMode.RepeatOne) return true; - - if (_currentIndex + 1 < _queue.Count) - { - _currentIndex++; - return true; - } - - if (RepeatMode == RepeatMode.RepeatAll) - { - _currentIndex = 0; - return true; - } - - return false; + Log.Info($"[AudioEngine] Volume initialized: {_volumePercent}%"); } - private bool TryMovePrevious() + private async Task VolumeSaveLoopAsync() { - if (_queue.Count == 0) return false; - - if (_currentIndex > 0) - { - _currentIndex--; - return true; - } - - if (RepeatMode == RepeatMode.RepeatAll) + while (!_lifetimeCts.IsCancellationRequested) { - _currentIndex = _queue.Count - 1; - return true; + await Task.Delay(2000, _lifetimeCts.Token); + _library.UpdateSettings(s => s.Volume = _volumePercent); } - - return false; } - private async Task HandlePlaybackErrorAsync() - { - if (++_consecutiveErrors >= MaxConsecutiveErrors) - { - Stop(); - RaiseOnUI(() => OnCriticalError?.Invoke( - SL["Player_Error_403_Title"] ?? "Error", - SL["Player_Error_403_Msg"] ?? "Too many errors")); - _consecutiveErrors = 0; - return; - } - - await Task.Delay(1000); - await PlayNextAsync(); - } + private static int NormalizeVolume(float saved) => + Math.Clamp((int)saved, 0, 100); #endregion - #region Stream Resolution - - private record StreamInfo(string Url, long Size, int Bitrate, string Codec, string Container); + #region Queue Management - private async Task GetStreamAsync(TrackInfo track, bool forceRefresh, CancellationToken ct) + public void Enqueue(TrackInfo track) { - var hasOverride = track.TransientBitrate > 0 || !string.IsNullOrEmpty(track.TransientContainer); - - if (!hasOverride && !forceRefresh && _cacheManager.IsFullyCached(track.Id)) + lock (_queueLock) { - var meta = StreamCacheManager.TryGetMetadata(track.Id); - if (meta is { ContentLength: > 0, Codec: not "" }) - { - Log.Debug($"[AudioEngine] Using cached: {track.Id}"); - if (!track.IsDownloaded && !_cacheManager.IsPromoted(track.Id)) - _cacheManager.TriggerCacheCompleted(track.Id, track.Id); - return new(meta.SourceUrl, meta.ContentLength, meta.Bitrate, meta.Codec, meta.Container); - } + if (_queue.Any(t => t.Id == track.Id)) return; + _queue.Add(track); + InvalidateQueueSnapshot(); } + RaiseOnUI(() => OnQueueChanged?.Invoke()); - if (!forceRefresh && !hasOverride && !string.IsNullOrEmpty(track.StreamUrl) && !string.IsNullOrEmpty(track.CachedCodec)) - return new(track.StreamUrl, -1, track.CachedBitrate, track.CachedCodec, track.CachedContainer); - - return await WithApiLockAsync(async () => - { - await ThrottleAsync(ct); - - using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - cts.CancelAfter(RefreshTimeoutMs); - - var result = await _youtube.RefreshStreamUrlAsync(track, forceRefresh, cts.Token); - _lastApiCall = DateTime.UtcNow; - - return result.HasValue - ? new StreamInfo(result.Value.Url, result.Value.Size, result.Value.Bitrate, result.Value.Codec, result.Value.Container) - : null; - }, ct); - } - - private async Task ThrottleAsync(CancellationToken ct) - { - var elapsed = (DateTime.UtcNow - _lastApiCall).TotalMilliseconds; - if (elapsed < ApiCooldownMs) - await Task.Delay(ApiCooldownMs - (int)elapsed, ct); - } - - private async Task GetContentLengthAsync(string url, CancellationToken ct) - { - try + if (CurrentTrack == null && !IsPlaying && !IsLoading) { - using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - cts.CancelAfter(1500); - using var req = new HttpRequestMessage(HttpMethod.Head, url); - using var resp = await _httpClient.SendAsync(req, cts.Token); - return resp.Content.Headers.ContentLength ?? -1; + lock (_queueLock) _currentIndex = _queue.Count - 1; + var session = Interlocked.Increment(ref _session); + _ = EnqueueCommandAsync(() => PlayCurrentIndexAsync(session)); } - catch { return -1; } } - #endregion - - #region VLC Events - - private void OnVlcPlaying() + public void ClearQueue() { - if (HasFlag(StateFlags.Disposed)) return; - - _consecutiveErrors = 0; - - _ = Task.Run(async () => - { - await Task.Delay(500); - if (!HasFlag(StateFlags.Disposed)) - SetFlag(StateFlags.SuppressAutoNext, false); - }); - - SetFlag(StateFlags.Ready, true); - SetFlag(StateFlags.Loading, false); - SetFlag(StateFlags.Playing, true); - SetFlag(StateFlags.Paused, false); - - _currentStream?.NotifyPlaybackStarted(); - _currentStream?.NotifyPaused(false); - - ApplyVolume(); - - _ = Task.Run(async () => + lock (_queueLock) { - await Task.Delay(250); - if (IsPlaying && !HasFlag(StateFlags.Disposed)) ApplyVolume(); - }); - - NotifyPlaybackState(); - _playbackStartedTcs?.TrySetResult(true); - } - - private void OnVlcPaused() - { - if (HasFlag(StateFlags.Disposed)) return; - SetFlag(StateFlags.Playing, false); - SetFlag(StateFlags.Paused, true); - _currentStream?.NotifyPaused(true); - NotifyPlaybackState(); - } - - private void OnVlcStopped() - { - if (HasFlag(StateFlags.Disposed)) return; - SetFlag(StateFlags.Ready, false); - SetFlag(StateFlags.Playing, false); - SetFlag(StateFlags.Paused, false); - NotifyPlaybackState(); + var current = CurrentTrack; + _queue.Clear(); + _currentIndex = -1; + if (current != null) { _queue.Add(current); _currentIndex = 0; } + InvalidateQueueSnapshot(); + } + RaiseOnUI(() => OnQueueChanged?.Invoke()); } - private void OnVlcEndReached() + public void RemoveFromQueue(TrackInfo track) { - long lastSeek = Volatile.Read(ref _cachedTimeMs); - long totalDur = Volatile.Read(ref _cachedLengthMs); - - if (totalDur > 0 && lastSeek < totalDur * 0.95) - { - Log.Warn($"[AudioEngine] Ignoring false EndReached"); - return; - } - - if (_player != null) - { - long vlcTime = _player.Time; - if (totalDur > 0 && vlcTime > 0 && vlcTime < totalDur * 0.95) - { - Log.Warn($"[AudioEngine] Ignoring false EndReached (VLC check)"); - return; - } - } - - if (HasFlag(StateFlags.Disposed | StateFlags.SuppressAutoNext)) return; - - SetFlag(StateFlags.Playing, false); - SetFlag(StateFlags.Paused, false); - SetFlag(StateFlags.Ready, false); - NotifyPlaybackState(); - - var session = Interlocked.Increment(ref _session); - _ = Task.Run(async () => + bool needStop = false; + lock (_queueLock) { - await Task.Delay(100); - - if (_session != session || HasFlag(StateFlags.Disposed)) return; + var idx = _queue.FindIndex(t => t.Id == track.Id); + if (idx == -1) return; - if (_player != null) + if (idx == _currentIndex) { - long finalPos = _player.Time; - long finalDur = _player.Length; - - if (finalDur > 0 && finalPos < finalDur * 0.95) - { - Log.Warn($"[AudioEngine] Cancelled auto-next"); - return; - } + needStop = _queue.Count == 1; + if (idx == _queue.Count - 1) _currentIndex--; } + else if (idx < _currentIndex) _currentIndex--; - bool canAdvance; - lock (_queueLock) { canAdvance = TryMoveNext(userInitiated: false); } - - if (canAdvance) - await EnqueueCommandAsync(() => PlayCurrentIndexAsync(session)); - else - Stop(); - }); - } - - private void OnVlcError() - { - SetLoadingState(false); - SetFlag(StateFlags.Playing, false); - SetFlag(StateFlags.Paused, false); - RaiseOnUI(() => OnError?.Invoke("VLC playback error")); - NotifyPlaybackState(); + _queue.RemoveAt(idx); + InvalidateQueueSnapshot(); + } + RaiseOnUI(() => OnQueueChanged?.Invoke()); + if (needStop) Stop(); } - #endregion - - #region Helpers - - private async Task CleanupMediaAsync() + public void MoveQueueItem(int from, int to) { - var (media, stream) = (_currentMedia, _currentStream); - _currentMedia = null; - _currentStream = null; - - if (stream == null && media == null) return; - - // Сначала отменяем операции стрима - try { stream?.CancelPendingReads(); } - catch { } - - // Останавливаем VLC если играет - if (_player?.State is VLCState.Playing or VLCState.Buffering) - { - try { _player?.Pause(); } - catch { } - } - - // Отсоединяем медиа от плеера - if (_player?.Media == media) - { - try - { - _player!.Media = null; - _player.Stop(); - } - catch { } - } - - // Dispose в фоне с небольшой задержкой - _ = Task.Run(async () => + lock (_queueLock) { - await Task.Delay(100); - try { stream?.Dispose(); } catch { } - try { media?.Dispose(); } catch { } - }); - } + if (from < 0 || from >= _queue.Count || to < 0 || to >= _queue.Count) return; + var item = _queue[from]; + _queue.RemoveAt(from); + _queue.Insert(to, item); - private void ClearState() - { - ClearStreamInfo(); - CurrentTrack = null; - SetLoadingState(false); - SetFlag(StateFlags.Playing, false); - SetFlag(StateFlags.Paused, false); - SetFlag(StateFlags.Ready, false); - } + if (_currentIndex == from) _currentIndex = to; + else if (from < _currentIndex && to >= _currentIndex) _currentIndex--; + else if (from > _currentIndex && to <= _currentIndex) _currentIndex++; - private void ClearStreamInfo() - { - _activeCodec = ""; - _activeBitrate = 0; - Volatile.Write(ref _cachedTimeMs, 0); - Volatile.Write(ref _cachedLengthMs, 0); + InvalidateQueueSnapshot(); + } + RaiseOnUI(() => OnQueueChanged?.Invoke()); } - private void SetStreamInfo(string codec, int bitrate) - { - _activeCodec = codec?.ToUpperInvariant() ?? ""; - _activeBitrate = bitrate; - RaiseOnUI(() => OnStreamInfoReady?.Invoke()); - } + private void InvalidateQueueSnapshot() { Queue = null; } private void AddToHistory(TrackInfo track) { @@ -1382,116 +699,45 @@ private void AddToHistory(TrackInfo track) if (_history.Count > MaxHistorySize) _history.RemoveAt(0); } - private void NotifyPlaybackState() => - RaiseOnUI(() => OnPlaybackStateChanged?.Invoke(IsPlaying, IsPaused)); - - private void SetLoadingState(bool value) - { - bool changed = HasFlag(StateFlags.Loading) != value; - SetFlag(StateFlags.Loading, value); - - if (changed) - { - RaiseOnUI(() => - { - this.RaisePropertyChanged(nameof(IsLoading)); - OnLoadingStateChanged?.Invoke(value); - }); - } - } - - public void NotifyAppMinimized() => _currentStream?.ReleaseRamBuffers(); - #endregion - #region Command Queue + #region Helpers private async Task ProcessCommandsAsync() { - await foreach (var cmd in _commandQueue.Reader.ReadAllAsync()) + try { - if (HasFlag(StateFlags.Disposed)) break; - try { await cmd(); } - catch (OperationCanceledException) { } - catch (Exception ex) { Log.Error($"[AudioEngine] Command error: {ex.Message}"); } + await foreach (var cmd in _commandQueue.Reader.ReadAllAsync(_lifetimeCts.Token)) + { + await cmd(); + } } + catch (OperationCanceledException) { } } - private async Task EnqueueCommandAsync(Func command) + private async Task EnqueueCommandAsync(Func command) { - if (HasFlag(StateFlags.Disposed)) return; await _commandQueue.Writer.WriteAsync(command); } - private async Task WithApiLockAsync(Func> action, CancellationToken ct) where T : class - { - if (!await _apiLock.WaitAsync(LockTimeoutMs, ct)) return null; - try { return await action(); } - finally { _apiLock.Release(); } - } - - #endregion - - #region State Flags - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool HasFlag(StateFlags flag) => - (Volatile.Read(ref _stateFlags) & (int)flag) != 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetFlag(StateFlags flag, bool value) - { - int current, desired; - do - { - current = Volatile.Read(ref _stateFlags); - desired = value ? current | (int)flag : current & ~(int)flag; - } while (Interlocked.CompareExchange(ref _stateFlags, desired, current) != current); - } - - #endregion - - #region UI Helpers - - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void RaiseOnUI(Action action) { - if (Avalonia.Threading.Dispatcher.UIThread.CheckAccess()) - action(); - else - Avalonia.Threading.Dispatcher.UIThread.Post(action); + if (Avalonia.Threading.Dispatcher.UIThread.CheckAccess()) action(); + else Avalonia.Threading.Dispatcher.UIThread.Post(action); } - #endregion - - #region Configuration - - private static StreamingConfig GetStreamingConfig(InternetProfile profile) => - StreamingProfiles.GetConfig(profile); - - #endregion - - #region Dispose - protected override void Dispose(bool disposing) { - if (HasFlag(StateFlags.Disposed)) return; - SetFlag(StateFlags.Disposed, true); - if (disposing) { - _library.UpdateSettings(s => s.Volume = _volumePercent); - _commandQueue.Writer.TryComplete(); - _playbackCts?.Cancel(); - - try { _currentStream?.Dispose(); } catch { } - try { _player?.Stop(); _player?.Dispose(); } catch { } - try { _libVLC?.Dispose(); } catch { } - try { _playbackLock.Dispose(); } catch { } - try { _apiLock.Dispose(); } catch { } - try { _httpClient.Dispose(); } catch { } + lock (_seekDebounce) + { + _lastSeekCts?.Cancel(); + _lastSeekCts?.Dispose(); + } - Log.Info("[AudioEngine] Disposed"); + _lifetimeCts.Cancel(); + _player.Dispose(); } base.Dispose(disposing); diff --git a/Core/Services/CookieAuthService.cs b/Core/Services/CookieAuthService.cs index 595663b..003353d 100644 --- a/Core/Services/CookieAuthService.cs +++ b/Core/Services/CookieAuthService.cs @@ -1,4 +1,3 @@ -using System.Buffers; using System.Collections.Frozen; using System.Runtime.CompilerServices; using System.Text; diff --git a/Core/Services/HlsStream.cs b/Core/Services/HlsStream.cs deleted file mode 100644 index 6494d8a..0000000 --- a/Core/Services/HlsStream.cs +++ /dev/null @@ -1,713 +0,0 @@ -// Core/Services/Streaming/HlsStream.cs - -using System.Collections.Concurrent; -using System.Net; -using System.Text.RegularExpressions; - -namespace LMP.Core.Services.Streaming; - -/// -/// HLS → Stream адаптер с поддержкой отмены. -/// -public sealed partial class HlsStream : MediaStreamBase -{ - #region Constants - - private const int MaxConcurrentDownloads = 3; - private const int PreloadSegmentCount = 3; - private const int DownloadTimeoutMs = 30_000; - private const int MaxRetries = 3; - private const int ManifestRefreshIntervalMs = 55_000; - private const int WaitPollIntervalMs = 50; - - #endregion - - #region Fields - - private readonly string _initialManifestUrl; - private readonly Dictionary _headers; - private readonly Func>? _urlRefresher; - - private readonly List _segments = []; - private readonly ConcurrentDictionary _downloadedData = new(); - private readonly ConcurrentDictionary _activeDownloads = new(); - private readonly SemaphoreSlim _downloadSlots = new(MaxConcurrentDownloads); - private readonly SemaphoreSlim _initLock = new(1, 1); - - // Локальный сигнал для ожидания данных - private readonly ManualResetEventSlim _dataAvailable = new(false); - - // Версионирование для отмены - private volatile int _sessionId; - private CancellationTokenSource _sessionCts; - private readonly Lock _sessionLock = new(); - - private string _baseUrl = ""; - private string _currentManifestUrl; - private DateTime _manifestFetchedAt; - private bool _initialized; - private Task? _preloadTask; - - #endregion - - #region Segment - - private sealed class HlsSegment - { - public required int Index { get; init; } - public required string Url { get; set; } - public required double Duration { get; init; } - public long StartOffset { get; set; } - public long EstimatedSize { get; set; } - public long ActualSize { get; set; } - public long EffectiveSize => ActualSize > 0 ? ActualSize : EstimatedSize; - } - - #endregion - - #region Properties - - public override double DownloadProgress => - _segments.Count == 0 ? 0 : Math.Min((double)_downloadedData.Count / _segments.Count * 100, 100); - - public override long BufferedBytes => - _downloadedData.Values.Sum(d => (long)d.Length); - - public override bool IsFullyDownloaded => - _segments.Count > 0 && _downloadedData.Count >= _segments.Count; - - #endregion - - #region Constructor - - public HlsStream( - string manifestUrl, - HttpClient http, - Dictionary? headers = null, - Func>? urlRefresher = null) - : base(http) - { - _initialManifestUrl = manifestUrl; - _currentManifestUrl = manifestUrl; - _headers = headers ?? new Dictionary - { - ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", - ["Referer"] = "https://www.youtube.com/", - ["Origin"] = "https://www.youtube.com" - }; - _urlRefresher = urlRefresher; - _sessionCts = CancellationTokenSource.CreateLinkedTokenSource(DisposeCts.Token); - - ExtractBaseUrl(manifestUrl); - Log.Debug($"[HLS] Created: {manifestUrl}"); - } - - #endregion - - #region Session Control - - private bool IsSessionValid(int expectedId) - { - return !IsDisposed && _sessionId == expectedId; - } - - private CancellationToken GetSessionToken() - { - lock (_sessionLock) - { - return _sessionCts.Token; - } - } - - /// - /// Отменяет все ожидающие операции. - /// - public override void CancelPendingReads() - { - lock (_sessionLock) - { - Interlocked.Increment(ref _sessionId); - - try - { - _sessionCts.Cancel(); - _sessionCts.Dispose(); - } - catch { } - - _sessionCts = CancellationTokenSource.CreateLinkedTokenSource(DisposeCts.Token); - _dataAvailable.Set(); - _activeDownloads.Clear(); - } - } - - #endregion - - #region Initialization - - public override async ValueTask InitializeAsync(CancellationToken ct = default) - { - if (_initialized) return true; - - await _initLock.WaitAsync(ct); - try - { - if (_initialized) return true; - - if (!await FetchAndParseManifestAsync(_currentManifestUrl, ct)) - return false; - - if (_segments.Count == 0) - { - Log.Error("[HLS] No segments"); - return false; - } - - await ProbeSegmentSizesAsync(ct); - RecalculateOffsets(); - - _initialized = true; - _manifestFetchedAt = DateTime.UtcNow; - - Log.Info($"[HLS] Ready: {_segments.Count} segments, ~{TotalLength / 1024}KB"); - - _preloadTask = Task.Run(() => BackgroundPreloadAsync(GetSessionToken()), GetSessionToken()); - return true; - } - finally - { - _initLock.Release(); - } - } - - public override async ValueTask PreBufferAsync(CancellationToken ct = default) - { - if (!await InitializeAsync(ct)) - return false; - - var sessionId = _sessionId; - ScheduleDownload(0); - - var deadline = DateTime.UtcNow.AddMilliseconds(DownloadTimeoutMs); - - while (!HasSegment(0)) - { - if (!IsSessionValid(sessionId) || ct.IsCancellationRequested) - return false; - - if (DateTime.UtcNow > deadline) - return false; - - _dataAvailable.Wait(100); - _dataAvailable.Reset(); - } - - return true; - } - - #endregion - - #region Manifest - - private void ExtractBaseUrl(string url) - { - int lastSlash = url.LastIndexOf('/'); - if (lastSlash > 0) _baseUrl = url[..(lastSlash + 1)]; - } - - private async Task FetchAndParseManifestAsync(string url, CancellationToken ct) - { - try - { - using var request = CreateRequest(HttpMethod.Get, url, _headers); - using var response = await Http.SendAsync(request, ct); - response.EnsureSuccessStatusCode(); - - var content = await response.Content.ReadAsStringAsync(ct); - - if (content.Contains("#EXT-X-STREAM-INF")) - { - var variantUrl = SelectBestVariant(content); - if (string.IsNullOrEmpty(variantUrl)) return false; - - variantUrl = MakeAbsoluteUrl(variantUrl); - ExtractBaseUrl(variantUrl); - return await FetchAndParseManifestAsync(variantUrl, ct); - } - - ParseMediaPlaylist(content); - return _segments.Count > 0; - } - catch (Exception ex) - { - Log.Error($"[HLS] Manifest failed: {ex.Message}"); - return false; - } - } - - private string? SelectBestVariant(string manifest) - { - var lines = manifest.Split('\n', StringSplitOptions.RemoveEmptyEntries); - string? bestUrl = null; - long bestBandwidth = long.MaxValue; - - for (int i = 0; i < lines.Length - 1; i++) - { - var line = lines[i].Trim(); - if (!line.StartsWith("#EXT-X-STREAM-INF:")) continue; - - var match = BandwidthRegex().Match(line); - if (!match.Success || !long.TryParse(match.Groups[1].Value, out var bw)) continue; - - var next = lines[i + 1].Trim(); - if (next.StartsWith('#')) continue; - - if (bw < bestBandwidth) - { - bestBandwidth = bw; - bestUrl = next; - } - } - - return bestUrl; - } - - private void ParseMediaPlaylist(string manifest) - { - _segments.Clear(); - var lines = manifest.Split('\n', StringSplitOptions.RemoveEmptyEntries); - double duration = 0; - int index = 0; - - foreach (var rawLine in lines) - { - var line = rawLine.Trim(); - - if (line.StartsWith("#EXTINF:")) - { - var val = line[8..]; - int comma = val.IndexOf(','); - if (comma > 0) val = val[..comma]; - double.TryParse(val, System.Globalization.NumberStyles.Float, - System.Globalization.CultureInfo.InvariantCulture, out duration); - } - else if (!line.StartsWith('#') && !string.IsNullOrWhiteSpace(line)) - { - _segments.Add(new HlsSegment - { - Index = index++, - Url = MakeAbsoluteUrl(line), - Duration = duration, - EstimatedSize = (long)(duration * 16_000) - }); - duration = 0; - } - } - } - - private string MakeAbsoluteUrl(string url) => - url.StartsWith("http://") || url.StartsWith("https://") ? url : _baseUrl + url; - - private async Task ProbeSegmentSizesAsync(CancellationToken ct) - { - var probes = _segments.Take(3).Select(s => ProbeOneAsync(s, ct)); - await Task.WhenAll(probes); - - var known = _segments.Where(s => s.ActualSize > 0).Select(s => s.ActualSize).ToList(); - if (known.Count > 0) - { - var avg = (long)known.Average(); - foreach (var s in _segments.Where(s => s.ActualSize == 0)) - s.EstimatedSize = avg; - } - } - - private async Task ProbeOneAsync(HlsSegment seg, CancellationToken ct) - { - try - { - using var req = CreateRequest(HttpMethod.Head, seg.Url, _headers); - using var resp = await Http.SendAsync(req, ct); - if (resp.Content.Headers.ContentLength is { } len) - { - seg.ActualSize = len; - seg.EstimatedSize = len; - } - } - catch { } - } - - private void RecalculateOffsets() - { - long offset = 0; - foreach (var seg in _segments) - { - seg.StartOffset = offset; - offset += seg.EffectiveSize; - } - TotalLength = offset; - } - - [GeneratedRegex(@"BANDWIDTH=(\d+)")] - private static partial Regex BandwidthRegex(); - - #endregion - - #region Read - - public override int Read(byte[] buffer, int offset, int count) - { - if (IsDisposed || !_initialized) return 0; - - int sessionId = _sessionId; - - long pos = CurrentPosition; - if (pos >= TotalLength) return 0; - - count = (int)Math.Min(count, TotalLength - pos); - if (count <= 0) return 0; - - int segIdx = FindSegmentAt(pos); - if (segIdx < 0) return 0; - - var seg = _segments[segIdx]; - long offsetInSeg = pos - seg.StartOffset; - - if (!WaitForSegment(segIdx, sessionId)) return 0; - if (!_downloadedData.TryGetValue(segIdx, out var data)) return 0; - - int available = (int)Math.Min(count, data.Length - offsetInSeg); - if (available <= 0) - { - if (segIdx + 1 < _segments.Count) - { - CurrentPosition = _segments[segIdx + 1].StartOffset; - return Read(buffer, offset, count); - } - return 0; - } - - Buffer.BlockCopy(data, (int)offsetInSeg, buffer, offset, available); - CurrentPosition = pos + available; - - for (int i = 1; i <= PreloadSegmentCount; i++) - ScheduleDownload(segIdx + i); - - return available; - } - - private bool WaitForSegment(int index, int sessionId) - { - if (HasSegment(index)) return true; - - ScheduleDownload(index); - var deadline = DateTime.UtcNow.AddMilliseconds(DownloadTimeoutMs); - - while (!HasSegment(index)) - { - // Проверяем сессию первым делом - if (!IsSessionValid(sessionId)) return false; - if (IsDisposed) return false; - if (DateTime.UtcNow > deadline) return false; - - _dataAvailable.Wait(WaitPollIntervalMs); - _dataAvailable.Reset(); - } - - return IsSessionValid(sessionId); - } - - /// - /// Вызывается при seek (позиция в миллисекундах для HLS конвертируется в байты). - /// - protected override void OnSeekInternal(long positionMs) - { - // Для HLS конвертируем время в байтовую позицию - if (_segments.Count == 0) return; - - double totalDur = _segments.Sum(s => s.Duration); - if (totalDur <= 0) return; - - double progress = positionMs / 1000.0 / totalDur; - long bytePos = (long)(progress * TotalLength); - bytePos = Math.Clamp(bytePos, 0, TotalLength); - - CurrentPosition = bytePos; - - int idx = FindSegmentAt(bytePos); - for (int i = 0; i <= PreloadSegmentCount; i++) - ScheduleDownload(idx + i); - } - - #endregion - - #region Download - - private bool HasSegment(int i) => _downloadedData.ContainsKey(i); - - private int FindSegmentAt(long pos) - { - for (int i = 0; i < _segments.Count; i++) - { - var s = _segments[i]; - if (pos >= s.StartOffset && pos < s.StartOffset + s.EffectiveSize) - return i; - } - return _segments.Count - 1; - } - - private void ScheduleDownload(int index) - { - if (index < 0 || index >= _segments.Count) return; - if (HasSegment(index) || _activeDownloads.ContainsKey(index)) return; - - var ct = GetSessionToken(); - var task = Task.Run(() => DownloadSegmentAsync(index, ct), ct); - _activeDownloads.TryAdd(index, task); - } - - private async Task DownloadSegmentAsync(int index, CancellationToken ct) - { - if (HasSegment(index)) - { - _activeDownloads.TryRemove(index, out _); - return; - } - - bool gotSlot = false; - try - { - gotSlot = await _downloadSlots.WaitAsync(1000, ct); - if (!gotSlot) - { - _activeDownloads.TryRemove(index, out _); - return; - } - - if (HasSegment(index) || ct.IsCancellationRequested) return; - - var seg = _segments[index]; - - for (int retry = 0; retry <= MaxRetries; retry++) - { - if (ct.IsCancellationRequested) return; - - try - { - // Проверяем свежесть URL - if (DateTime.UtcNow - _manifestFetchedAt > TimeSpan.FromMilliseconds(ManifestRefreshIntervalMs)) - await RefreshManifestAsync(ct); - - using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - cts.CancelAfter(DownloadTimeoutMs); - - using var request = CreateRequest(HttpMethod.Get, seg.Url, _headers); - using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token); - - if (response.StatusCode == HttpStatusCode.Forbidden) - { - Log.Warn($"[HLS] Segment {index} got 403"); - await RefreshManifestAsync(ct); - continue; - } - - response.EnsureSuccessStatusCode(); - - if (ct.IsCancellationRequested) return; - - var data = await response.Content.ReadAsByteArrayAsync(cts.Token); - - if (ct.IsCancellationRequested) return; - - _downloadedData[index] = data; - - if (seg.ActualSize != data.Length) - { - seg.ActualSize = data.Length; - RecalculateOffsetsFrom(index); - } - - _dataAvailable.Set(); - Log.Debug($"[HLS] Segment {index}: {data.Length} bytes"); - return; - } - catch (OperationCanceledException) - { - return; - } - catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) - { - Log.Warn($"[HLS] Segment {index} got 403, refreshing..."); - await RefreshManifestAsync(ct); - } - catch (Exception ex) when (retry < MaxRetries && !ct.IsCancellationRequested) - { - Log.Warn($"[HLS] Segment {index} retry {retry + 1}: {ex.Message}"); - try { await Task.Delay(500 * (retry + 1), ct); } - catch (OperationCanceledException) { return; } - } - } - } - catch (OperationCanceledException) - { - // Нормальная отмена - } - finally - { - _activeDownloads.TryRemove(index, out _); - if (gotSlot) - { - try { _downloadSlots.Release(); } catch { } - } - } - } - - private async Task RefreshManifestAsync(CancellationToken ct) - { - try - { - string? newUrl = null; - - if (_urlRefresher != null) - newUrl = await _urlRefresher(ct); - - newUrl ??= _initialManifestUrl; - - ExtractBaseUrl(newUrl); - - int oldCount = _segments.Count; - if (await FetchAndParseManifestAsync(newUrl, ct) && _segments.Count == oldCount) - { - _manifestFetchedAt = DateTime.UtcNow; - _currentManifestUrl = newUrl; - Log.Debug("[HLS] Manifest refreshed"); - } - } - catch (OperationCanceledException) { } - catch (Exception ex) - { - Log.Warn($"[HLS] Refresh failed: {ex.Message}"); - } - } - - private void RecalculateOffsetsFrom(int from) - { - if (from >= _segments.Count) return; - - long offset = from > 0 - ? _segments[from - 1].StartOffset + _segments[from - 1].EffectiveSize - : 0; - - for (int i = from; i < _segments.Count; i++) - { - _segments[i].StartOffset = offset; - offset += _segments[i].EffectiveSize; - } - - TotalLength = offset; - } - - private async Task BackgroundPreloadAsync(CancellationToken ct) - { - while (!ct.IsCancellationRequested) - { - try - { - if (!IsPaused) - { - int cur = FindSegmentAt(CurrentPosition); - for (int i = 0; i <= PreloadSegmentCount; i++) - ScheduleDownload(cur + i); - } - - await Task.Delay(200, ct); - } - catch (OperationCanceledException) { break; } - catch (Exception ex) - { - Log.Warn($"[HLS] Preload error: {ex.Message}"); - } - } - } - - #endregion - - #region Buffered Ranges - - public override IReadOnlyList<(double Start, double End)> GetBufferedRanges() - { - if (_segments.Count == 0 || TotalLength == 0) return []; - - var ranges = new List<(double, double)>(); - int? start = null, end = null; - - for (int i = 0; i < _segments.Count; i++) - { - if (HasSegment(i)) - { - start ??= i; - end = i; - } - else if (start != null) - { - AddRange(start.Value, end!.Value); - start = null; - } - } - - if (start != null) AddRange(start.Value, end!.Value); - - return ranges; - - void AddRange(int s, int e) - { - double sp = (double)_segments[s].StartOffset / TotalLength; - double ep = (double)(_segments[e].StartOffset + _segments[e].EffectiveSize) / TotalLength; - ranges.Add((sp, Math.Min(ep, 1.0))); - } - } - - #endregion - - #region Seek by Time (public convenience) - - /// - /// Seek по времени в миллисекундах (публичный метод). - /// - public void SeekToTime(long milliseconds) - { - if (_segments.Count == 0) return; - - double totalDur = _segments.Sum(s => s.Duration); - if (totalDur <= 0) return; - - double progress = milliseconds / 1000.0 / totalDur; - Seek((long)(progress * TotalLength), SeekOrigin.Begin); - } - - #endregion - - #region Dispose - - protected override void OnDispose() - { - // Отменяем все операции - lock (_sessionLock) - { - try { _sessionCts.Cancel(); } catch { } - try { _sessionCts.Dispose(); } catch { } - } - - try { _preloadTask?.Wait(500); } catch { } - - _downloadedData.Clear(); - - try { _downloadSlots.Dispose(); } catch { } - try { _initLock.Dispose(); } catch { } - try { _dataAvailable.Dispose(); } catch { } - - Log.Debug("[HLS] Disposed"); - } - - #endregion -} \ No newline at end of file diff --git a/Core/Services/ImageCacheService.cs b/Core/Services/ImageCacheService.cs index 68a1af3..cf8e006 100644 --- a/Core/Services/ImageCacheService.cs +++ b/Core/Services/ImageCacheService.cs @@ -555,7 +555,7 @@ private async Task DownloadAndProcessImageAsync(string url, string diskPath, Can /// /// Конвертирует изображение в WebP с ресайзом до MaxDiskImageSize. /// - private async Task ProcessAndSaveAsWebPAsync(Stream sourceStream, string diskPath, CancellationToken ct) + private static async Task ProcessAndSaveAsWebPAsync(Stream sourceStream, string diskPath, CancellationToken ct) { return await Task.Run(() => { diff --git a/Core/Services/LibraryService.cs b/Core/Services/LibraryService.cs index 21110ec..7b0efa3 100644 --- a/Core/Services/LibraryService.cs +++ b/Core/Services/LibraryService.cs @@ -28,12 +28,9 @@ public sealed class LibraryService : IAsyncDisposable private readonly Subject _saveSettingsSignal = new(); private readonly IDisposable _saveSubscription; - private AppSettings _appSettings = new(); - public AppSettings Settings => _appSettings; + public AppSettings Settings { get; private set; } = new(); // Fake Account cache - private string? _fakeAccountName; - private string? _fakeAccountAvatarUrl; public event Action? OnInitialized; public event Action? OnDataChanged; @@ -61,7 +58,7 @@ public LibraryService( .ObserveOn(RxApp.TaskpoolScheduler) .Subscribe(async _ => { - try { await _settings.SetAsync("AppSettings", _appSettings); } + try { await _settings.SetAsync("AppSettings", Settings); } catch (Exception ex) { Log.Error($"[LibraryService] Settings save failed: {ex.Message}"); } }); } @@ -86,9 +83,9 @@ public async Task InitializeAsync(CancellationToken ct = default) } // Load settings - _appSettings = await _settings.GetOrDefaultAsync("AppSettings", new AppSettings(), ct); + Settings = await _settings.GetOrDefaultAsync("AppSettings", new AppSettings(), ct); // ИНИЦИАЛИЗИРУЕМ СТАТИКУ - YoutubeClientUtils.CurrentProfile = _appSettings.YoutubeClient; + YoutubeClientUtils.CurrentProfile = Settings.YoutubeClient; // Hydrate cache await _registry.HydrateAsync(ct); @@ -207,8 +204,8 @@ private async Task MigrateFromJsonAsync(string path, CancellationToken ct) } // Step 4: Migrate settings - _appSettings = MapLegacySettings(legacy); - await _settings.SetAsync("AppSettings", _appSettings, ct); + Settings = MapLegacySettings(legacy); + await _settings.SetAsync("AppSettings", Settings, ct); // Backup old file var backup = path + $".migrated.{DateTime.Now:yyyyMMddHHmmss}"; @@ -675,13 +672,13 @@ public async Task IsTrackInPlaylistAsync(string trackId, string playlistId public string DownloadPath { - get => string.IsNullOrEmpty(_appSettings.DownloadPath) ? G.Folder.Downloads : _appSettings.DownloadPath; - set { _appSettings.DownloadPath = value; SaveSettings(); } + get => string.IsNullOrEmpty(Settings.DownloadPath) ? G.Folder.Downloads : Settings.DownloadPath; + set { Settings.DownloadPath = value; SaveSettings(); } } public void UpdateSettings(Action update) { - update(_appSettings); + update(Settings); SaveSettings(); } @@ -691,16 +688,16 @@ public void UpdateSettings(Action update) #region Fake Account - public bool HasFakeAccount => !string.IsNullOrEmpty(_appSettings.FakeAccountChannelUrl); - public string? FakeAccountUrl => _appSettings.FakeAccountChannelUrl; - public string? FakeAccountName => _fakeAccountName; - public string? FakeAccountAvatarUrl => _fakeAccountAvatarUrl; + public bool HasFakeAccount => !string.IsNullOrEmpty(Settings.FakeAccountChannelUrl); + public string? FakeAccountUrl => Settings.FakeAccountChannelUrl; + public string? FakeAccountName { get; private set; } + public string? FakeAccountAvatarUrl { get; private set; } public void SetFakeAccount(string url, string name, string avatar) { - _appSettings.FakeAccountChannelUrl = url; - _fakeAccountName = name; - _fakeAccountAvatarUrl = avatar; + Settings.FakeAccountChannelUrl = url; + FakeAccountName = name; + FakeAccountAvatarUrl = avatar; SaveSettings(); OnFakeAccountChanged?.Invoke(); OnDataChanged?.Invoke(); @@ -708,16 +705,16 @@ public void SetFakeAccount(string url, string name, string avatar) public void UpdateFakeAccountCache(string name, string avatar) { - _fakeAccountName = name; - _fakeAccountAvatarUrl = avatar; + FakeAccountName = name; + FakeAccountAvatarUrl = avatar; OnFakeAccountChanged?.Invoke(); } public void ClearFakeAccount() { - _appSettings.FakeAccountChannelUrl = null; - _fakeAccountName = null; - _fakeAccountAvatarUrl = null; + Settings.FakeAccountChannelUrl = null; + FakeAccountName = null; + FakeAccountAvatarUrl = null; SaveSettings(); OnFakeAccountChanged?.Invoke(); OnDataChanged?.Invoke(); @@ -739,8 +736,8 @@ private void OnLanguageChanged(object? _, string __) public async Task ResetAsync(CancellationToken ct = default) { _registry.Clear(); - _fakeAccountName = null; - _fakeAccountAvatarUrl = null; + FakeAccountName = null; + FakeAccountAvatarUrl = null; await using var ctx = await _dbFactory.CreateDbContextAsync(ct); await ctx.Database.EnsureDeletedAsync(ct); @@ -748,7 +745,7 @@ public async Task ResetAsync(CancellationToken ct = default) await ctx.OptimizeAsync(ct); await ctx.EnsureFtsTablesAsync(ct); - _appSettings = new AppSettings(); + Settings = new AppSettings(); await EnsureLikedPlaylistAsync(ct); OnDataChanged?.Invoke(); } @@ -761,7 +758,7 @@ public async ValueTask DisposeAsync() // Final flush await _registry.FlushAsync(); - await _settings.SetAsync("AppSettings", _appSettings); + await _settings.SetAsync("AppSettings", Settings); GC.SuppressFinalize(this); } diff --git a/Core/Services/LocalizationService.cs b/Core/Services/LocalizationService.cs index 7b8e877..9ce4013 100644 --- a/Core/Services/LocalizationService.cs +++ b/Core/Services/LocalizationService.cs @@ -8,8 +8,6 @@ namespace LMP.Core.Services; public sealed class LocalizationService : INotifyPropertyChanged { public readonly static LocalizationService Instance = new(); - - private string _currentLanguage = "en"; // Дефолт - английский private Dictionary _resources = []; private bool _isInitialized; @@ -25,17 +23,17 @@ public sealed class LocalizationService : INotifyPropertyChanged /// /// Публичное свойство для доступа к коду языка (hl) /// - public string CurrentLanguageCode => _currentLanguage; + public string CurrentLanguageCode { get; private set; } = "en"; public string CurrentLanguage { - get => _currentLanguage; + get => CurrentLanguageCode; set { - if (_currentLanguage != value && AvailableLanguages.Any(l => l.Code == value)) + if (CurrentLanguageCode != value && AvailableLanguages.Any(l => l.Code == value)) { - Log.Info($"Changing language: {_currentLanguage} → {value}"); - _currentLanguage = value; + Log.Info($"Changing language: {CurrentLanguageCode} → {value}"); + CurrentLanguageCode = value; LoadLanguage(value); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); @@ -83,7 +81,7 @@ public void Initialize(string? savedLanguageCode) } } - _currentLanguage = langToUse; + CurrentLanguageCode = langToUse; LoadLanguage(langToUse); _isInitialized = true; } diff --git a/Core/Services/MediaStreamBase.cs b/Core/Services/MediaStreamBase.cs deleted file mode 100644 index fb7f81b..0000000 --- a/Core/Services/MediaStreamBase.cs +++ /dev/null @@ -1,205 +0,0 @@ -// Core/Services/Streaming/MediaStreamBase.cs - -using System.Net; -using System.Runtime.CompilerServices; - -namespace LMP.Core.Services.Streaming; - -/// -/// Базовый класс для потоковых медиа-источников с поддержкой отмены. -/// -public abstract class MediaStreamBase : Stream -{ - #region Fields - - protected readonly HttpClient Http; - protected readonly CancellationTokenSource DisposeCts = new(); - - private long _position; - private long _totalLength; - private volatile bool _disposed; - private volatile bool _isPaused; - private volatile bool _playbackStarted; - - #endregion - - #region Stream Properties - - public sealed override bool CanRead => !_disposed; - public sealed override bool CanSeek => !_disposed; - public sealed override bool CanWrite => false; - - public sealed override long Length => Volatile.Read(ref _totalLength); - - public sealed override long Position - { - get => Volatile.Read(ref _position); - set => Seek(value, SeekOrigin.Begin); - } - - #endregion - - #region Protected Accessors - - protected long TotalLength - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Volatile.Read(ref _totalLength); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Volatile.Write(ref _totalLength, value); - } - - protected long CurrentPosition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Volatile.Read(ref _position); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => Volatile.Write(ref _position, value); - } - - protected bool IsPaused => _isPaused; - protected bool PlaybackStarted => _playbackStarted; - public bool IsDisposed => _disposed; - - #endregion - - #region Abstract Members - - public abstract ValueTask InitializeAsync(CancellationToken ct = default); - public abstract ValueTask PreBufferAsync(CancellationToken ct = default); - public abstract double DownloadProgress { get; } - public abstract long BufferedBytes { get; } - public abstract bool IsFullyDownloaded { get; } - public abstract IReadOnlyList<(double Start, double End)> GetBufferedRanges(); - - #endregion - - #region Virtual Members - - protected virtual void OnSeekInternal(long newBytePosition) { } - protected virtual void OnDispose() { } - protected virtual void OnPauseStateChanged(bool paused) { } - protected virtual void OnPlaybackStarted() { } - public virtual void ReleaseRamBuffers() { } - - /// - /// Вызывается для немедленной отмены всех операций (seek, stop, dispose). - /// - public virtual void CancelPendingReads() { } - - #endregion - - #region Constructor - - protected MediaStreamBase(HttpClient http) - { - Http = http ?? throw new ArgumentNullException(nameof(http)); - } - - #endregion - - #region Public Control Methods - - public void NotifyPaused(bool paused) - { - if (_isPaused == paused) return; - _isPaused = paused; - OnPauseStateChanged(paused); - } - - /// - /// Уведомление о seek. Вызывает немедленную отмену текущих операций. - /// - public void NotifySeek(long positionMs) - { - // Сначала отмена — потом расчёт позиции - CancelPendingReads(); - OnSeekInternal(positionMs); - } - - public void NotifyPlaybackStarted() - { - if (_playbackStarted) return; - _playbackStarted = true; - OnPlaybackStarted(); - } - - #endregion - - #region Seek Implementation - - public sealed override long Seek(long offset, SeekOrigin origin) - { - if (_disposed) return 0; - - long newPos = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => CurrentPosition + offset, - SeekOrigin.End => TotalLength + offset, - _ => throw new ArgumentOutOfRangeException(nameof(origin)) - }; - - newPos = Math.Clamp(newPos, 0, Math.Max(0, TotalLength)); - CurrentPosition = newPos; - - return newPos; - } - - #endregion - - #region Not Supported - - public sealed override void Flush() { } - public sealed override void SetLength(long value) => throw new NotSupportedException(); - public sealed override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - - #endregion - - #region HTTP Helpers - - protected HttpRequestMessage CreateRequest( - HttpMethod method, - string url, - Dictionary? headers = null, - (long From, long To)? range = null) - { - var request = new HttpRequestMessage(method, url); - - if (headers != null) - { - foreach (var (key, value) in headers) - request.Headers.TryAddWithoutValidation(key, value); - } - - if (range.HasValue) - { - request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue( - range.Value.From, range.Value.To); - } - - return request; - } - - #endregion - - #region Dispose - - protected sealed override void Dispose(bool disposing) - { - if (_disposed) return; - _disposed = true; - - if (disposing) - { - try { DisposeCts.Cancel(); } catch { } - CancelPendingReads(); - OnDispose(); - try { DisposeCts.Dispose(); } catch { } - } - - base.Dispose(disposing); - } - - #endregion -} \ No newline at end of file diff --git a/Core/Services/MemoryDiagnostics.cs b/Core/Services/MemoryDiagnostics.cs index 3dc5d2d..8ef3198 100644 --- a/Core/Services/MemoryDiagnostics.cs +++ b/Core/Services/MemoryDiagnostics.cs @@ -13,8 +13,7 @@ public sealed class MemoryDiagnostics : IDisposable { #region Singleton - private static MemoryDiagnostics? _instance; - public static MemoryDiagnostics Instance => _instance ??= new MemoryDiagnostics(); + public static MemoryDiagnostics Instance => field ??= new MemoryDiagnostics(); #endregion @@ -23,8 +22,6 @@ public sealed class MemoryDiagnostics : IDisposable private readonly ConcurrentDictionary _counters = new(); private readonly ConcurrentDictionary _instanceCounts = new(); private readonly Timer _monitorTimer; - - private MemoryStats _lastStats = new(); private bool _disposed; #endregion @@ -38,7 +35,7 @@ public sealed class MemoryDiagnostics : IDisposable #region Properties - public MemoryStats CurrentStats => _lastStats; + public MemoryStats CurrentStats { get; private set; } = new(); /// Порог предупреждения (MB) public long WarningThresholdMb { get; set; } = 330; @@ -146,7 +143,7 @@ private void UpdateStats(object? state) var process = Process.GetCurrentProcess(); var gcInfo = GC.GetGCMemoryInfo(); - _lastStats = new MemoryStats + CurrentStats = new MemoryStats { WorkingSetMb = process.WorkingSet64 / (1024 * 1024), PrivateMemoryMb = process.PrivateMemorySize64 / (1024 * 1024), @@ -161,12 +158,12 @@ private void UpdateStats(object? state) TrackedCategories = GetTrackedSummary() }; - OnStatsUpdated?.Invoke(_lastStats); + OnStatsUpdated?.Invoke(CurrentStats); // Проверяем пороги - if (_lastStats.WorkingSetMb > CriticalThresholdMb) + if (CurrentStats.WorkingSetMb > CriticalThresholdMb) { - var msg = $"CRITICAL: Memory {_lastStats.WorkingSetMb}MB > {CriticalThresholdMb}MB!"; + var msg = $"CRITICAL: Memory {CurrentStats.WorkingSetMb}MB > {CriticalThresholdMb}MB!"; OnMemoryWarning?.Invoke(msg); Log.Warn(msg); @@ -175,9 +172,9 @@ private void UpdateStats(object? state) ForceCleanup(); } } - else if (_lastStats.WorkingSetMb > WarningThresholdMb) + else if (CurrentStats.WorkingSetMb > WarningThresholdMb) { - var msg = $"WARNING: Memory {_lastStats.WorkingSetMb}MB > {WarningThresholdMb}MB"; + var msg = $"WARNING: Memory {CurrentStats.WorkingSetMb}MB > {WarningThresholdMb}MB"; OnMemoryWarning?.Invoke(msg); } } @@ -264,7 +261,7 @@ public string GetFullReport() /// public string GetShortStatus() { - return $"RAM: {_lastStats.WorkingSetMb}MB | GC: {_lastStats.GcTotalMemoryMb}MB | Gen0/1/2: {_lastStats.Gen0Collections}/{_lastStats.Gen1Collections}/{_lastStats.Gen2Collections}"; + return $"RAM: {CurrentStats.WorkingSetMb}MB | GC: {CurrentStats.GcTotalMemoryMb}MB | Gen0/1/2: {CurrentStats.Gen0Collections}/{CurrentStats.Gen1Collections}/{CurrentStats.Gen2Collections}"; } /// diff --git a/Core/Services/MemoryFirstCachingStream.cs b/Core/Services/MemoryFirstCachingStream.cs deleted file mode 100644 index b10b264..0000000 --- a/Core/Services/MemoryFirstCachingStream.cs +++ /dev/null @@ -1,804 +0,0 @@ -// Core/Services/Streaming/MemoryFirstCachingStream.cs - -using System.Collections.Concurrent; -using System.Net; -using System.Numerics; -using System.Runtime.CompilerServices; -using LMP.Core.Models; - -namespace LMP.Core.Services.Streaming; - -/// -/// ФИНАЛЬНАЯ АРХИТЕКТУРА: -/// - Read() ВСЕГДА неблокирующий — отдаёт данные если есть, планирует загрузку если нет -/// - Throttling ТОЛЬКО в PreloadLoop — качает строго [playback .. playback+ahead] -/// - Если VLC запросил чанк вне окна — планируем ТОЛЬКО ЕГО (urgent), но НЕ соседей -/// - _activeDownloads ограничен MaxConcurrentDownloads слотами -/// - При смене трека — инвалидируем сессию (FullStop) -/// - При seek — НЕ инвалидируем (просто меняем позиции) -/// -public sealed class MemoryFirstCachingStream : MediaStreamBase -{ - #region Session (только для смены трека) - - private sealed class LoadSession : IDisposable - { - public int Id { get; } - public CancellationTokenSource Cts { get; } - public CancellationToken Token => Cts.Token; - - public LoadSession(int id, CancellationToken parentToken) - { - Id = id; - Cts = CancellationTokenSource.CreateLinkedTokenSource(parentToken); - } - - public void Cancel() { try { Cts.Cancel(); } catch { } } - public void Dispose() { try { Cts.Dispose(); } catch { } } - } - - #endregion - - #region Fields - - private readonly string _cacheId; - private readonly string _originalTrackId; - private readonly StreamCacheManager? _cacheManager; - private readonly StreamingConfig _config; - private readonly Func>? _urlRefresher; - private readonly Func? _getPlaybackTimeMs; - private readonly long _totalDurationMs; - - private string _currentUrl; - private DateTime _urlFetchedAt; - private readonly SemaphoreSlim _urlLock = new(1, 1); - - private readonly int _chunkSize; - private readonly int _totalChunks; - private readonly ConcurrentDictionary _ramChunks = new(); - private readonly ConcurrentBitArray _downloadedChunks; - private readonly ConcurrentDictionary _activeDownloads = new(); - private readonly SemaphoreSlim _downloadSlots; - - private readonly SemaphoreSlim _initLock = new(1, 1); - private volatile bool _initialized; - - private Task? _preloadTask; - private Task? _flushTask; - - private long _bytesDownloaded; - private int _ramChunkCount; - private volatile int _playbackChunk = -1; - private volatile int _readHead; - - private volatile int _sessionId; - private LoadSession? _currentSession; - private readonly Lock _sessionLock = new(); - - private readonly ManualResetEventSlim _dataSignal = new(false); - - private const int UrlRefreshIntervalMs = 300_000; - private const int WaitPollIntervalMs = 50; - private const int SlotTimeoutMs = 500; - - #endregion - - #region Properties - - public override double DownloadProgress => - _totalChunks == 0 ? 0 : Math.Min((double)_downloadedChunks.PopCount() / _totalChunks * 100, 100); - - public override long BufferedBytes => Volatile.Read(ref _bytesDownloaded); - public override bool IsFullyDownloaded => _downloadedChunks.PopCount() >= _totalChunks; - - #endregion - - #region Constructor - - public MemoryFirstCachingStream( - string cacheId, string url, long contentLength, HttpClient http, - StreamCacheManager? cacheManager, StreamingConfig config, - Func>? urlRefresher = null, - string? originalTrackId = null, - Func? getPlaybackTimeMs = null, - long totalDurationMs = 0) - : base(http) - { - if (contentLength <= 0) - throw new ArgumentException("Content length must be positive", nameof(contentLength)); - - _cacheId = cacheId; - _originalTrackId = originalTrackId ?? cacheId; - _currentUrl = url; - _cacheManager = cacheManager; - _config = config; - _urlRefresher = urlRefresher; - _getPlaybackTimeMs = getPlaybackTimeMs; - _totalDurationMs = totalDurationMs; - - TotalLength = contentLength; - _urlFetchedAt = DateTime.UtcNow; - - _chunkSize = config.ChunkSizeBytes; - _totalChunks = (int)Math.Ceiling((double)contentLength / _chunkSize); - _downloadedChunks = new ConcurrentBitArray(_totalChunks); - _downloadSlots = new SemaphoreSlim(config.MaxConcurrentDownloads); - - _currentSession = new LoadSession(0, DisposeCts.Token); - - Log.Debug($"[Stream] Created: {_totalChunks} chunks × {_chunkSize / 1024}KB, ahead={config.MaxDownloadAheadChunks}, fullTrack={config.DownloadFullTrack}"); - } - - #endregion - - #region Session Control - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private LoadSession? GetSession() => _currentSession?.Token.IsCancellationRequested == false && !IsDisposed ? _currentSession : null; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsSessionValid(int expectedId) => - !IsDisposed && _sessionId == expectedId && !(_currentSession?.Token.IsCancellationRequested ?? true); - - public override void CancelPendingReads() - { - // Seek — только разблокируем - _dataSignal.Set(); - } - - public void FullStop() - { - lock (_sessionLock) - { - Interlocked.Increment(ref _sessionId); - _currentSession?.Cancel(); - _dataSignal.Set(); - _activeDownloads.Clear(); - } - } - - #endregion - - #region Initialization - - public override async ValueTask InitializeAsync(CancellationToken ct = default) - { - if (_initialized) return true; - - await _initLock.WaitAsync(ct); - try - { - if (_initialized) return true; - - if (_cacheManager != null && await TryLoadFromCacheAsync(ct)) - { - Log.Info($"[Stream] Loaded from cache: {_cacheId}"); - _initialized = true; - return true; - } - - _initialized = true; - - var session = GetSession(); - if (session != null) - { - _preloadTask = Task.Run(() => PreloadLoopAsync(session), session.Token); - _flushTask = Task.Run(() => FlushLoopAsync(session), session.Token); - } - - Log.Debug($"[Stream] Initialized: {_cacheId}"); - return true; - } - finally { _initLock.Release(); } - } - - public override async ValueTask PreBufferAsync(CancellationToken ct = default) - { - if (!await InitializeAsync(ct)) return false; - - var session = GetSession(); - if (session == null) return false; - - for (int i = 0; i < Math.Min(_config.InitialReadAheadChunks, _totalChunks); i++) - StartDownload(i, session); - - var deadline = DateTime.UtcNow.AddMilliseconds(_config.DownloadTimeoutMs); - - while (!HasChunk(0)) - { - if (!IsSessionValid(session.Id) || ct.IsCancellationRequested) return false; - if (DateTime.UtcNow > deadline) { Log.Warn("[Stream] PreBuffer timeout"); return false; } - _dataSignal.Wait(100); - _dataSignal.Reset(); - } - - Log.Debug($"[Stream] PreBuffer complete: {_downloadedChunks.PopCount()}/{_config.InitialReadAheadChunks} chunks"); - return true; - } - - #endregion - - #region Read — Always non-blocking - - public override int Read(byte[] buffer, int offset, int count) - { - if (IsDisposed || !_initialized) return 0; - - var session = GetSession(); - if (session == null) return 0; - - long pos = CurrentPosition; - if (pos >= TotalLength) return 0; - - count = (int)Math.Min(count, TotalLength - pos); - if (count <= 0) return 0; - - int chunkIndex = (int)(pos / _chunkSize); - int offsetInChunk = (int)(pos % _chunkSize); - - if (chunkIndex > _readHead) - _readHead = chunkIndex; - - // Если есть — отдаём мгновенно - if (!HasChunk(chunkIndex)) - { - // Планируем ТОЛЬКО этот чанк (urgent) - StartDownload(chunkIndex, session); - - // Ждём с таймаутом - if (!WaitForChunkData(chunkIndex, _config.DownloadTimeoutMs)) - return 0; - } - - var data = GetChunkData(chunkIndex); - if (data == null) return 0; - - int available = Math.Min(count, data.Length - offsetInChunk); - if (available <= 0) - { - if (chunkIndex + 1 < _totalChunks) - { - CurrentPosition = (long)(chunkIndex + 1) * _chunkSize; - return Read(buffer, offset, count); - } - return 0; - } - - Buffer.BlockCopy(data, offsetInChunk, buffer, offset, available); - CurrentPosition = pos + available; - return available; - } - - private bool WaitForChunkData(int chunkIndex, int timeoutMs) - { - if (HasChunk(chunkIndex)) return true; - - var deadline = DateTime.UtcNow.AddMilliseconds(timeoutMs); - - while (!HasChunk(chunkIndex)) - { - if (IsDisposed) return false; - if (DateTime.UtcNow > deadline) return false; - - _dataSignal.Wait(WaitPollIntervalMs); - _dataSignal.Reset(); - } - - return true; - } - - #endregion - - #region Seek — NO session invalidation - - protected override void OnSeekInternal(long positionMs) - { - long bytePosition = 0; - if (_totalDurationMs > 0 && TotalLength > 0) - { - double progress = Math.Clamp((double)positionMs / _totalDurationMs, 0, 1); - bytePosition = (long)(progress * TotalLength); - } - - int newChunk = Math.Clamp((int)(bytePosition / _chunkSize), 0, Math.Max(0, _totalChunks - 1)); - - _readHead = newChunk; - _playbackChunk = newChunk; - CurrentPosition = bytePosition; - - _dataSignal.Set(); - - Log.Debug($"[Stream] Seek → chunk {newChunk}"); - - var session = GetSession(); - if (session != null) - Task.Run(() => PlanDownloadsFromPlayback(session)); - } - - #endregion - - #region Playback Tracking & Preload - - private void RefreshPlaybackChunk() - { - if (_getPlaybackTimeMs == null || _totalDurationMs <= 0) return; - - long ms = _getPlaybackTimeMs(); - if (ms <= 0) return; - - double progress = (double)ms / _totalDurationMs; - long bytePos = (long)(progress * TotalLength); - int chunk = Math.Clamp((int)(bytePos / _chunkSize), 0, _totalChunks - 1); - - if (chunk != _playbackChunk) - _playbackChunk = chunk; - } - - /// - /// Планирует загрузки СТРОГО в окне [playback .. playback+ahead]. - /// - private void PlanDownloadsFromPlayback(LoadSession session) - { - if (!IsSessionValid(session.Id)) return; - - RefreshPlaybackChunk(); - - int start = _playbackChunk >= 0 ? _playbackChunk : 0; - int end = Math.Min(start + _config.MaxDownloadAheadChunks, _totalChunks - 1); - - for (int i = start; i <= end; i++) - { - if (!IsSessionValid(session.Id)) return; - if (!HasChunk(i) && !_activeDownloads.ContainsKey(i)) - StartDownload(i, session); - } - } - - private void StartDownload(int index, LoadSession session) - { - if (index < 0 || index >= _totalChunks) return; - if (HasChunk(index) || _activeDownloads.ContainsKey(index)) return; - if (!IsSessionValid(session.Id)) return; - - var task = Task.Run(() => DownloadChunkAsync(index, session)); - _activeDownloads.TryAdd(index, task); - } - - private async Task PreloadLoopAsync(LoadSession session) - { - while (IsSessionValid(session.Id)) - { - try - { - await Task.Delay(_config.BufferExtendIntervalMs, session.Token); - - if (IsPaused || !PlaybackStarted || !IsSessionValid(session.Id)) continue; - - RefreshPlaybackChunk(); - - if (_config.DownloadFullTrack) - { - // Качаем всё - int scheduled = 0; - for (int i = 0; i < _totalChunks && scheduled < _config.MaxConcurrentDownloads * 2; i++) - { - if (!HasChunk(i) && !_activeDownloads.ContainsKey(i)) - { - StartDownload(i, session); - scheduled++; - } - } - } - else - { - // Качаем ТОЛЬКО окно - PlanDownloadsFromPlayback(session); - - // Отменяем загрузки ВНЕ окна - CancelDownloadsOutsideWindow(); - } - } - catch (OperationCanceledException) { break; } - catch (Exception ex) { Log.Warn($"[Stream] Preload error: {ex.Message}"); } - } - } - - /// - /// Отменяет активные загрузки которые слишком далеко от playback. - /// НЕ трогает загрузки в окне [playback-behind .. playback+ahead*2]. - /// - private void CancelDownloadsOutsideWindow() - { - int pb = _playbackChunk; - if (pb < 0) return; - - int minAllowed = pb - _config.ChunksToKeepBehind; - int maxAllowed = pb + _config.MaxDownloadAheadChunks * 2; - - var toCancel = _activeDownloads.Keys - .Where(i => i < minAllowed || i > maxAllowed) - .ToList(); - - foreach (var idx in toCancel) - { - if (_activeDownloads.TryRemove(idx, out _)) - Log.Trace($"[Stream] Cancelled download: chunk {idx} (outside window)"); - } - } - - #endregion - - #region Chunks - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool HasChunk(int index) => _downloadedChunks.Get(index); - - private byte[]? GetChunkData(int index) - { - if (_ramChunks.TryGetValue(index, out var data)) return data; - if (_cacheManager != null) - { - data = ReadChunkFromDisk(index); - if (data != null) - { - TryPromoteToRam(index, data); - return data; - } - } - return null; - } - - private byte[]? ReadChunkFromDisk(int index) - { - try - { - var cachePath = StreamCacheManager.GetCachePath(_cacheId); - if (!File.Exists(cachePath)) return null; - - long start = (long)index * _chunkSize; - int size = (int)Math.Min(_chunkSize, TotalLength - start); - - using var fs = new FileStream(cachePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - fs.Seek(start, SeekOrigin.Begin); - var buf = new byte[size]; - return fs.Read(buf, 0, size) == size ? buf : null; - } - catch { return null; } - } - - private void TryPromoteToRam(int index, byte[] data) - { - if (Volatile.Read(ref _ramChunkCount) < _config.MaxRamChunks && _ramChunks.TryAdd(index, data)) - Interlocked.Increment(ref _ramChunkCount); - } - - private void StoreChunk(int index, byte[] data) - { - _downloadedChunks.Set(index, true); - Interlocked.Add(ref _bytesDownloaded, data.Length); - if (_ramChunks.TryAdd(index, data)) - Interlocked.Increment(ref _ramChunkCount); - WriteChunkToDisk(index, data); - _dataSignal.Set(); - } - - private void WriteChunkToDisk(int index, byte[] data) - { - if (_cacheManager == null) return; - try - { - var cachePath = StreamCacheManager.GetCachePath(_cacheId); - var dir = Path.GetDirectoryName(cachePath); - if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - long off = (long)index * _chunkSize; - using var fs = new FileStream(cachePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); - fs.Seek(off, SeekOrigin.Begin); - fs.Write(data, 0, data.Length); - } - catch (Exception ex) { Log.Warn($"[Stream] WriteChunk {index} failed: {ex.Message}"); } - } - - #endregion - - #region Download - - private async Task DownloadChunkAsync(int index, LoadSession session) - { - var ct = session.Token; - if (HasChunk(index) || !IsSessionValid(session.Id)) - { - _activeDownloads.TryRemove(index, out _); - return; - } - - bool gotSlot = false; - try - { - gotSlot = await _downloadSlots.WaitAsync(SlotTimeoutMs, ct); - if (!gotSlot || HasChunk(index) || !IsSessionValid(session.Id)) return; - - long start = (long)index * _chunkSize; - long end = Math.Min(start + _chunkSize - 1, TotalLength - 1); - - for (int retry = 0; retry <= _config.MaxRetries; retry++) - { - if (!IsSessionValid(session.Id)) return; - - try - { - await EnsureFreshUrlAsync(ct); - - using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - cts.CancelAfter(_config.DownloadTimeoutMs); - - using var request = CreateRequest(HttpMethod.Get, _currentUrl, range: (start, end)); - request.Headers.TryAddWithoutValidation("Referer", "https://www.youtube.com/"); - request.Headers.TryAddWithoutValidation("Origin", "https://www.youtube.com"); - - using var response = await Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token); - - if (response.StatusCode == HttpStatusCode.Forbidden) - { - Log.Warn($"[Stream] Chunk {index} got 403"); - await RefreshUrlAsync(ct); - continue; - } - - response.EnsureSuccessStatusCode(); - if (!IsSessionValid(session.Id)) return; - - var data = await response.Content.ReadAsByteArrayAsync(cts.Token); - if (!IsSessionValid(session.Id)) return; - - StoreChunk(index, data); - Log.Debug($"[Stream] Chunk {index}: {data.Length} bytes"); - return; - } - catch (OperationCanceledException) { return; } - catch (Exception ex) when (retry < _config.MaxRetries) - { - if (!IsSessionValid(session.Id)) return; - Log.Warn($"[Stream] Chunk {index} retry {retry + 1}: {ex.Message}"); - try { await Task.Delay(_config.RetryDelayMs * (retry + 1), ct); } - catch (OperationCanceledException) { return; } - } - } - } - finally - { - _activeDownloads.TryRemove(index, out _); - if (gotSlot) try { _downloadSlots.Release(); } catch { } - } - } - - #endregion - - #region URL & Flush - - private async Task EnsureFreshUrlAsync(CancellationToken ct) - { - if ((DateTime.UtcNow - _urlFetchedAt).TotalMilliseconds < UrlRefreshIntervalMs) return; - await RefreshUrlAsync(ct); - } - - private async Task RefreshUrlAsync(CancellationToken ct) - { - if (_urlRefresher == null) return; - bool gotLock = false; - try - { - gotLock = await _urlLock.WaitAsync(1000, ct); - if (!gotLock || (DateTime.UtcNow - _urlFetchedAt).TotalMilliseconds < 10_000) return; - - var newUrl = await _urlRefresher(ct); - if (!string.IsNullOrEmpty(newUrl)) - { - _currentUrl = newUrl; - _urlFetchedAt = DateTime.UtcNow; - Log.Debug("[Stream] URL refreshed"); - } - } - catch (OperationCanceledException) { } - catch (Exception ex) { Log.Warn($"[Stream] URL refresh failed: {ex.Message}"); } - finally { if (gotLock) try { _urlLock.Release(); } catch { } } - } - - private async Task FlushLoopAsync(LoadSession session) - { - while (IsSessionValid(session.Id)) - { - try - { - await Task.Delay(5000, session.Token); - if (Volatile.Read(ref _ramChunkCount) > _config.MaxRamChunks) - FlushOldChunksFromRam(); - if (IsFullyDownloaded) { await FinalizeAsync(); break; } - } - catch (OperationCanceledException) { break; } - catch (Exception ex) { Log.Warn($"[Stream] Flush error: {ex.Message}"); } - } - } - - private void FlushOldChunksFromRam() - { - int center = Math.Max(_readHead, _playbackChunk); - var toFlush = _ramChunks.Keys - .Where(i => i < center - _config.ChunksToKeepBehind || i > center + _config.MaxDownloadAheadChunks * 2) - .OrderByDescending(i => Math.Abs(i - center)) - .Take(_config.MaxRamChunks / 4).ToList(); - - int flushed = 0; - foreach (var idx in toFlush) - if (_ramChunks.TryRemove(idx, out _)) { Interlocked.Decrement(ref _ramChunkCount); flushed++; } - - if (flushed > 0) Log.Trace($"[Stream] Flushed {flushed} RAM chunks"); - } - - private async Task FinalizeAsync() - { - if (_cacheManager == null) return; - try - { - var meta = StreamCacheManager.TryGetMetadata(_cacheId); - if (meta != null) - { - var ranges = new RangeMap(); - ranges.MarkComplete(0, TotalLength); - meta.RangesJson = ranges.Serialize(); - meta.LastAccessedAt = DateTime.UtcNow; - StreamCacheManager.SaveMetadata(_cacheId, meta); - } - _cacheManager.TriggerCacheCompleted(_cacheId, _originalTrackId); - Log.Info($"[Stream] Finalized: {_cacheId}"); - } - catch (Exception ex) { Log.Warn($"[Stream] Finalize failed: {ex.Message}"); } - } - - private async Task TryLoadFromCacheAsync(CancellationToken ct) - { - if (_cacheManager == null) return false; - try - { - var meta = StreamCacheManager.TryGetMetadata(_cacheId); - if (meta?.ContentLength != TotalLength) return false; - - var cachePath = StreamCacheManager.GetCachePath(_cacheId); - if (!File.Exists(cachePath) || new FileInfo(cachePath).Length != TotalLength) return false; - - var ranges = RangeMap.Deserialize(meta.RangesJson); - if (!ranges.IsFullyDownloaded(TotalLength)) return false; - - for (int i = 0; i < _totalChunks; i++) - _downloadedChunks.Set(i, true); - - Volatile.Write(ref _bytesDownloaded, TotalLength); - return true; - } - catch (Exception ex) { Log.Warn($"[Stream] Cache load failed: {ex.Message}"); return false; } - } - - public override IReadOnlyList<(double Start, double End)> GetBufferedRanges() - { - if (_totalChunks == 0 || TotalLength == 0) return []; - - var ranges = new List<(double, double)>(); - int? rs = null, re = null; - - for (int i = 0; i < _totalChunks; i++) - { - if (HasChunk(i)) { rs ??= i; re = i; } - else if (rs != null) { AddRange(rs.Value, re!.Value); rs = null; } - } - - if (rs != null) AddRange(rs.Value, re!.Value); - return ranges; - - void AddRange(int s, int e) - { - double sp = (double)((long)s * _chunkSize) / TotalLength; - double ep = (double)Math.Min((long)(e + 1) * _chunkSize, TotalLength) / TotalLength; - ranges.Add((sp, Math.Min(ep, 1.0))); - } - } - - #endregion - - #region Lifecycle - - protected override void OnPlaybackStarted() => Log.Debug("[Stream] Playback started"); - - protected override void OnPauseStateChanged(bool paused) - { - if (!paused) - { - RefreshPlaybackChunk(); - var session = GetSession(); - if (session != null) PlanDownloadsFromPlayback(session); - } - } - - public override void ReleaseRamBuffers() - { - int center = Math.Max(_readHead, _playbackChunk); - int released = 0; - - foreach (var idx in _ramChunks.Keys.Where(i => Math.Abs(i - center) > 3).ToList()) - if (_ramChunks.TryRemove(idx, out _)) { Interlocked.Decrement(ref _ramChunkCount); released++; } - - if (released > 0) { GC.Collect(1, GCCollectionMode.Optimized, false); Log.Debug($"[Stream] Released {released} RAM chunks"); } - } - - protected override void OnDispose() - { - FullStop(); - - if (_cacheManager != null && IsFullyDownloaded) - { - try - { - var meta = StreamCacheManager.TryGetMetadata(_cacheId); - if (meta != null) - { - var ranges = new RangeMap(); - ranges.MarkComplete(0, TotalLength); - meta.RangesJson = ranges.Serialize(); - StreamCacheManager.SaveMetadata(_cacheId, meta); - } - } - catch { } - } - - _ramChunks.Clear(); - try { _downloadSlots.Dispose(); } catch { } - try { _urlLock.Dispose(); } catch { } - try { _initLock.Dispose(); } catch { } - try { _dataSignal.Dispose(); } catch { } - - lock (_sessionLock) { try { _currentSession?.Dispose(); } catch { } _currentSession = null; } - - Log.Debug($"[Stream] Disposed: {_cacheId}"); - } - - #endregion -} - -#region ConcurrentBitArray -internal sealed class ConcurrentBitArray -{ - private readonly int[] _data; - private readonly int _length; - - public ConcurrentBitArray(int length) - { - _length = length; - _data = new int[(length + 31) / 32]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Get(int index) => - (uint)index < (uint)_length && (Volatile.Read(ref _data[index >> 5]) & (1 << (index & 31))) != 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(int index, bool value) - { - if ((uint)index >= (uint)_length) return; - int word = index >> 5, bit = 1 << (index & 31), current, desired; - do - { - current = Volatile.Read(ref _data[word]); - desired = value ? (current | bit) : (current & ~bit); - if (desired == current) return; - } while (Interlocked.CompareExchange(ref _data[word], desired, current) != current); - } - - public int PopCount() - { - int count = 0; - for (int i = 0; i < _data.Length; i++) - count += BitOperations.PopCount((uint)Volatile.Read(ref _data[i])); - return Math.Min(count, _length); - } -} -#endregion \ No newline at end of file diff --git a/Core/Services/SearchCacheService.cs b/Core/Services/SearchCacheService.cs index dcecfdc..0d2739f 100644 --- a/Core/Services/SearchCacheService.cs +++ b/Core/Services/SearchCacheService.cs @@ -24,8 +24,7 @@ public class SearchCacheService private readonly LinkedList _lruOrder = new(); private const int MaxMemoryCacheItems = 15; - private LibraryService? _libService; - private LibraryService LibService => _libService ??= Program.Services.GetRequiredService(); + private LibraryService LibService => field ??= Program.Services.GetRequiredService(); private TimeSpan CacheTtl => TimeSpan.FromMinutes( LibService.Settings.SearchCacheTtlMinutes > 0 diff --git a/Core/Services/StreamCacheManager.cs b/Core/Services/StreamCacheManager.cs index 26acecf..1178504 100644 --- a/Core/Services/StreamCacheManager.cs +++ b/Core/Services/StreamCacheManager.cs @@ -304,7 +304,7 @@ public bool IsFormatCached(string trackId, string container, int bitrate) /// /// Возвращает список закэшированных форматов для трека. /// - public List<(string Container, int Bitrate)> GetCachedFormats(string trackId) + public static List<(string Container, int Bitrate)> GetCachedFormats(string trackId) { var result = new List<(string, int)>(); @@ -544,7 +544,7 @@ public async Task ClearAllAsync() /// /// Очищает папку Downloads. /// - public async Task ClearDownloadsAsync() + public static async Task ClearDownloadsAsync() { try { diff --git a/Core/Services/YoutubeProvider.cs b/Core/Services/YoutubeProvider.cs index 3890ccb..7dbf710 100644 --- a/Core/Services/YoutubeProvider.cs +++ b/Core/Services/YoutubeProvider.cs @@ -1,5 +1,4 @@ -using System.Buffers; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Diagnostics; using System.Net; using System.Runtime.CompilerServices; @@ -932,7 +931,6 @@ public sealed class SearchSession : IDisposable private readonly TrackRegistry _registry; private readonly string _query; private readonly int _maxResults; - private readonly SearchFilter _filter; private readonly HashSet _seenIds; private IAsyncEnumerator>? _enumerator; private bool _hasMore = true; @@ -943,7 +941,7 @@ public sealed class SearchSession : IDisposable public bool HasMore => (_hasMore || _buffer.Count > 0) && !_disposed && _seenIds.Count < _maxResults; public int LoadedCount => _seenIds.Count; - public SearchFilter Filter => _filter; + public SearchFilter Filter { get; } internal SearchSession( YoutubeClient youtube, @@ -957,7 +955,7 @@ internal SearchSession( _registry = registry; _query = query; _maxResults = maxResults; - _filter = filter; + Filter = filter; _seenIds = new HashSet(64, StringComparer.Ordinal); if (skipTrackIds != null) @@ -988,7 +986,7 @@ public async Task> FetchNextBatchAsync(int count = 50, Cancellat try { _enumerator ??= _youtube.Search - .GetResultBatchesAsync(_query, _filter, ct) + .GetResultBatchesAsync(_query, Filter, ct) .GetAsyncEnumerator(ct); if (!await _enumerator.MoveNextAsync()) @@ -1426,8 +1424,7 @@ public void Dispose() if (_disposed) return; _disposed = true; - if (AuthService != null) - AuthService.OnAuthStateChanged -= ReloadClient; + AuthService?.OnAuthStateChanged -= ReloadClient; DisposeCurrentClient(); diff --git a/Core/ViewModels/PaginatedViewModel.cs b/Core/ViewModels/PaginatedViewModel.cs index aa6f4b7..6f3595b 100644 --- a/Core/ViewModels/PaginatedViewModel.cs +++ b/Core/ViewModels/PaginatedViewModel.cs @@ -28,9 +28,6 @@ public abstract class PaginatedViewModel : ViewModelBase, I private CancellationTokenSource? _loadCts; private bool _canFetchMore; private bool _isDisposed; - private int _totalSourceCount; - - private string _filterQuery = string.Empty; #endregion @@ -50,12 +47,12 @@ public abstract class PaginatedViewModel : ViewModelBase, I public string FilterQuery { - get => _filterQuery; - set => this.RaiseAndSetIfChanged(ref _filterQuery, value); - } + get; + set => this.RaiseAndSetIfChanged(ref field, value); + } = string.Empty; public ReadOnlyObservableCollection Items => _items; - protected int TotalCount => _totalSourceCount; + protected int TotalCount { get; private set; } public ReactiveCommand LoadMoreCommand { get; } @@ -72,7 +69,7 @@ protected PaginatedViewModel() .Throttle(TimeSpan.FromMilliseconds(200)) .ObserveOn(RxApp.TaskpoolScheduler) .Select(query => BuildFilterPredicate(query)) - .StartWith(BuildFilterPredicate(_filterQuery)); + .StartWith(BuildFilterPredicate(FilterQuery)); _sourceList.Connect() .Filter(filterPredicate) @@ -170,10 +167,10 @@ protected async Task InitializeItemsAsync(IEnumerable items, bool canFe innerList.AddRange(itemsList); }); - _totalSourceCount = _sourceList.Count; + TotalCount = _sourceList.Count; UpdateState(); - if (_totalSourceCount == 0 && canFetchMore) + if (TotalCount == 0 && canFetchMore) { await LoadNextBatchAsync(); } @@ -182,7 +179,7 @@ protected async Task InitializeItemsAsync(IEnumerable items, bool canFe protected void ClearItems() { _sourceList.Clear(); - _totalSourceCount = 0; + TotalCount = 0; _canFetchMore = false; UpdateState(); } @@ -207,7 +204,7 @@ protected void CancelLoading() private void UpdateState() { HasMoreItems = _canFetchMore; - ReachedEnd = !_canFetchMore && _totalSourceCount > 0; + ReachedEnd = !_canFetchMore && TotalCount > 0; } protected void SetCanFetchMore(bool value) @@ -242,7 +239,7 @@ private async Task LoadNextBatchAsync() if (!existingIds.Contains(GetItemId(item))) { list.Add(item); - _totalSourceCount++; + TotalCount++; } } }); diff --git a/Core/ViewModels/ReorderableViewModel.cs b/Core/ViewModels/ReorderableViewModel.cs index 949c7ce..3fa01ce 100644 --- a/Core/ViewModels/ReorderableViewModel.cs +++ b/Core/ViewModels/ReorderableViewModel.cs @@ -19,12 +19,8 @@ public abstract class ReorderableViewModel : ViewModelBase, private List _masterIds = []; private readonly Dictionary _loadedSources = []; private readonly Dictionary _vmCache = []; - private readonly ObservableCollection _visibleItems = []; - private CancellationTokenSource? _loadCts; - private int _loadedCount; private bool _isDisposed; - private string _filterQuery = string.Empty; #endregion @@ -42,21 +38,21 @@ public abstract class ReorderableViewModel : ViewModelBase, public string FilterQuery { - get => _filterQuery; + get; set { - if (_filterQuery == value) return; - this.RaiseAndSetIfChanged(ref _filterQuery, value); + if (field == value) return; + this.RaiseAndSetIfChanged(ref field, value); RebuildVisibleItems(); } - } + } = string.Empty; - public ObservableCollection Items => _visibleItems; + public ObservableCollection Items { get; } = []; protected int TotalCount => _masterIds.Count; - protected int LoadedCount => _loadedCount; + protected int LoadedCount { get; private set; } - public bool CanReorder => string.IsNullOrWhiteSpace(_filterQuery); + public bool CanReorder => string.IsNullOrWhiteSpace(FilterQuery); public ReactiveCommand LoadMoreCommand { get; } @@ -102,12 +98,12 @@ protected async Task InitializeAsync(List allIds, CancellationToken ct = _masterIds = [.. allIds]; _loadedSources.Clear(); - _loadedCount = 0; + LoadedCount = 0; foreach (var vm in _vmCache.Values) vm.Dispose(); _vmCache.Clear(); - _visibleItems.Clear(); + Items.Clear(); UpdateState(); @@ -131,12 +127,12 @@ protected void InitializeWithData(IEnumerable items) { _loadedSources[GetItemId(item)] = item; } - _loadedCount = itemsList.Count; + LoadedCount = itemsList.Count; foreach (var vm in _vmCache.Values) vm.Dispose(); _vmCache.Clear(); - _visibleItems.Clear(); + Items.Clear(); RebuildVisibleItems(); UpdateState(); @@ -148,7 +144,7 @@ protected void InitializeWithData(IEnumerable items) private async Task LoadNextBatchAsync() { - if (_isDisposed || IsLoadingMore || _loadedCount >= _masterIds.Count) + if (_isDisposed || IsLoadingMore || LoadedCount >= _masterIds.Count) return; IsLoadingMore = true; @@ -158,14 +154,14 @@ private async Task LoadNextBatchAsync() var ct = _loadCts?.Token ?? CancellationToken.None; var idsToLoad = _masterIds - .Skip(_loadedCount) + .Skip(LoadedCount) .Take(BatchSize) .Where(id => !_loadedSources.ContainsKey(id)) .ToList(); if (idsToLoad.Count == 0) { - _loadedCount = _masterIds.Count; + LoadedCount = _masterIds.Count; UpdateState(); return; } @@ -179,9 +175,9 @@ private async Task LoadNextBatchAsync() _loadedSources[id] = item; } - _loadedCount += BatchSize; - if (_loadedCount > _masterIds.Count) - _loadedCount = _masterIds.Count; + LoadedCount += BatchSize; + if (LoadedCount > _masterIds.Count) + LoadedCount = _masterIds.Count; AppendNewItemsToVisible(loaded); UpdateState(); @@ -198,7 +194,7 @@ private async Task LoadNextBatchAsync() private void AppendNewItemsToVisible(IEnumerable newItems) { - var query = _filterQuery; + var query = FilterQuery; foreach (var item in newItems) { if (!MatchesFilter(item, query)) continue; @@ -210,28 +206,28 @@ private void AppendNewItemsToVisible(IEnumerable newItems) _vmCache[id] = vm; int insertIndex = FindInsertIndex(id); - if (insertIndex >= 0 && insertIndex <= _visibleItems.Count) - _visibleItems.Insert(insertIndex, vm); + if (insertIndex >= 0 && insertIndex <= Items.Count) + Items.Insert(insertIndex, vm); else - _visibleItems.Add(vm); + Items.Add(vm); } } private int FindInsertIndex(string itemId) { int masterIndex = _masterIds.IndexOf(itemId); - if (masterIndex < 0) return _visibleItems.Count; + if (masterIndex < 0) return Items.Count; - for (int i = 0; i < _visibleItems.Count; i++) + for (int i = 0; i < Items.Count; i++) { - var existingId = GetVmId(_visibleItems[i]); + var existingId = GetVmId(Items[i]); int existingMasterIndex = _masterIds.IndexOf(existingId); if (existingMasterIndex > masterIndex) return i; } - return _visibleItems.Count; + return Items.Count; } private string GetVmId(TViewModel vm) @@ -250,7 +246,7 @@ private string GetVmId(TViewModel vm) protected void RebuildVisibleItems() { - var query = _filterQuery; + var query = FilterQuery; var newVisible = new List(); foreach (var id in _masterIds) @@ -275,19 +271,19 @@ protected void RebuildVisibleItems() private void SyncVisibleItems(List newItems) { - while (_visibleItems.Count > newItems.Count) - _visibleItems.RemoveAt(_visibleItems.Count - 1); + while (Items.Count > newItems.Count) + Items.RemoveAt(Items.Count - 1); for (int i = 0; i < newItems.Count; i++) { - if (i < _visibleItems.Count) + if (i < Items.Count) { - if (!ReferenceEquals(_visibleItems[i], newItems[i])) - _visibleItems[i] = newItems[i]; + if (!ReferenceEquals(Items[i], newItems[i])) + Items[i] = newItems[i]; } else { - _visibleItems.Add(newItems[i]); + Items.Add(newItems[i]); } } } @@ -299,8 +295,8 @@ private void SyncVisibleItems(List newItems) public void MoveItem(int oldIndex, int newIndex) { if (oldIndex == newIndex) return; - if (oldIndex < 0 || oldIndex >= _visibleItems.Count) return; - if (newIndex < 0 || newIndex >= _visibleItems.Count) return; + if (oldIndex < 0 || oldIndex >= Items.Count) return; + if (newIndex < 0 || newIndex >= Items.Count) return; if (!CanReorder) { @@ -308,7 +304,7 @@ public void MoveItem(int oldIndex, int newIndex) return; } - var movingVm = _visibleItems[oldIndex]; + var movingVm = Items[oldIndex]; var movingId = GetVmId(movingVm); if (string.IsNullOrEmpty(movingId)) return; @@ -317,14 +313,14 @@ public void MoveItem(int oldIndex, int newIndex) _masterIds.RemoveAt(oldIndex); _masterIds.Insert(newIndex, movingId); - _visibleItems.Move(oldIndex, newIndex); + Items.Move(oldIndex, newIndex); } public async Task MoveItemAsync(int oldIndex, int newIndex) { if (oldIndex == newIndex) return; - if (oldIndex < 0 || oldIndex >= _visibleItems.Count) return; - if (newIndex < 0 || newIndex >= _visibleItems.Count) return; + if (oldIndex < 0 || oldIndex >= Items.Count) return; + if (newIndex < 0 || newIndex >= Items.Count) return; if (!CanReorder) { @@ -332,7 +328,7 @@ public async Task MoveItemAsync(int oldIndex, int newIndex) return; } - var movingVm = _visibleItems[oldIndex]; + var movingVm = Items[oldIndex]; var movingId = GetVmId(movingVm); if (string.IsNullOrEmpty(movingId)) return; @@ -340,7 +336,7 @@ public async Task MoveItemAsync(int oldIndex, int newIndex) _masterIds.RemoveAt(oldIndex); _masterIds.Insert(newIndex, movingId); - _visibleItems.Move(oldIndex, newIndex); + Items.Move(oldIndex, newIndex); try { @@ -352,7 +348,7 @@ public async Task MoveItemAsync(int oldIndex, int newIndex) Log.Error($"[Move] Save failed: {ex.Message}"); _masterIds.RemoveAt(newIndex); _masterIds.Insert(oldIndex, movingId); - _visibleItems.Move(newIndex, oldIndex); + Items.Move(newIndex, oldIndex); } } @@ -371,7 +367,7 @@ protected void CancelLoading() private void UpdateState() { - HasMoreItems = _loadedCount < _masterIds.Count; + HasMoreItems = LoadedCount < _masterIds.Count; ReachedEnd = !HasMoreItems && _masterIds.Count > 0; } @@ -399,7 +395,7 @@ protected override void Dispose(bool disposing) CancelLoading(); _vmCache.Clear(); - _visibleItems.Clear(); + Items.Clear(); _loadedSources.Clear(); _masterIds.Clear(); } diff --git a/Core/ViewModels/ViewModelBase.cs b/Core/ViewModels/ViewModelBase.cs index 4cba43a..d9f78b3 100644 --- a/Core/ViewModels/ViewModelBase.cs +++ b/Core/ViewModels/ViewModelBase.cs @@ -92,7 +92,9 @@ private static List CollectAlive() public static LocalizationService SL => LocalizationService.Instance; /// Нестатическое для XAML биндинга (через DataContext). +#pragma warning disable CA1822 // Пометьте члены как статические public LocalizationService L => LocalizationService.Instance; +#pragma warning restore CA1822 // Пометьте члены как статические #endregion diff --git a/Core/Youtube/Bridge/ChannelPlaylistsResponse.cs b/Core/Youtube/Bridge/ChannelPlaylistsResponse.cs index 64cb2f0..68bcaf0 100644 --- a/Core/Youtube/Bridge/ChannelPlaylistsResponse.cs +++ b/Core/Youtube/Bridge/ChannelPlaylistsResponse.cs @@ -104,7 +104,7 @@ private static IEnumerable ParseSectionList(JsonElement sectionList) } } - private IEnumerable ParseRichGrid(JsonElement richGrid) + private static IEnumerable ParseRichGrid(JsonElement richGrid) { var contents = richGrid.GetPropertyOrNull("contents"); if (contents == null) yield break; diff --git a/Core/Youtube/Bridge/SearchResponse.cs b/Core/Youtube/Bridge/SearchResponse.cs index e2a6b0e..628942c 100644 --- a/Core/Youtube/Bridge/SearchResponse.cs +++ b/Core/Youtube/Bridge/SearchResponse.cs @@ -318,17 +318,11 @@ public static async ValueTask ParseAsync(Stream stream, Cancella // VideoData с кэшированием свойств при первом доступе internal sealed class VideoData(JsonElement content, bool isYtm) { - private string? _id; private bool _idComputed; - private string? _title; private bool _titleComputed; - private string? _author; private bool _authorComputed; - private string? _channelId; private bool _channelIdComputed; - private TimeSpan? _duration; private bool _durationComputed; - private IReadOnlyList? _thumbnails; private bool? _isPlaylistContext; private bool? _isArtistContext; @@ -338,10 +332,10 @@ public string? Id { get { - if (_idComputed) return _id; - _id = ComputeId(); + if (_idComputed) return field; + field = ComputeId(); _idComputed = true; - return _id; + return field; } } @@ -373,10 +367,10 @@ public string? Title { get { - if (_titleComputed) return _title; - _title = ComputeTitle(); + if (_titleComputed) return field; + field = ComputeTitle(); _titleComputed = true; - return _title; + return field; } } @@ -405,10 +399,10 @@ public string? Author { get { - if (_authorComputed) return _author; - _author = ComputeAuthor(); + if (_authorComputed) return field; + field = ComputeAuthor(); _authorComputed = true; - return _author; + return field; } } @@ -454,10 +448,10 @@ public string? ChannelId { get { - if (_channelIdComputed) return _channelId; - _channelId = ComputeChannelId(); + if (_channelIdComputed) return field; + field = ComputeChannelId(); _channelIdComputed = true; - return _channelId; + return field; } } @@ -528,10 +522,10 @@ public TimeSpan? Duration { get { - if (_durationComputed) return _duration; - _duration = ComputeDuration(); + if (_durationComputed) return field; + field = ComputeDuration(); _durationComputed = true; - return _duration; + return field; } } @@ -563,9 +557,9 @@ public IReadOnlyList Thumbnails { get { - if (_thumbnails != null) return _thumbnails; - _thumbnails = ComputeThumbnails(); - return _thumbnails; + if (field != null) return field; + field = ComputeThumbnails(); + return field; } } diff --git a/Core/Youtube/Bridge/VideoWatchPage.cs b/Core/Youtube/Bridge/VideoWatchPage.cs index a299072..84b59c5 100644 --- a/Core/Youtube/Bridge/VideoWatchPage.cs +++ b/Core/Youtube/Bridge/VideoWatchPage.cs @@ -40,7 +40,7 @@ public long? LikeCount } // То же самое для дизлайков (обычно 0 или скрыты) - public long? DislikeCount => 0; + public static long? DislikeCount => 0; public PlayerResponse? PlayerResponse { diff --git a/Core/Youtube/Channels/ChannelClient.cs b/Core/Youtube/Channels/ChannelClient.cs index 80cf2c8..404540e 100644 --- a/Core/Youtube/Channels/ChannelClient.cs +++ b/Core/Youtube/Channels/ChannelClient.cs @@ -14,7 +14,7 @@ public partial class ChannelClient(HttpClient http) { private readonly ChannelController _controller = new(http); - private Channel Get(ChannelPage channelPage) + private static Channel Get(ChannelPage channelPage) { var channelId = channelPage.Id diff --git a/Core/Youtube/Music/MusicModels.cs b/Core/Youtube/Music/MusicModels.cs index 0962364..0a5091f 100644 --- a/Core/Youtube/Music/MusicModels.cs +++ b/Core/Youtube/Music/MusicModels.cs @@ -196,7 +196,7 @@ private void ParseGridContent(JsonElement grid, string title) if (items != null) ParseMixedContent(items.Value, title); } - private MusicItem? ParseMusicItem(JsonElement json) + private static MusicItem? ParseMusicItem(JsonElement json) { var id = json.GetPropertyOrNull("playlistItemData")?.GetPropertyOrNull("videoId")?.GetStringOrNull(); if (id == null) @@ -280,7 +280,7 @@ private void ParseGridContent(JsonElement grid, string title) }; } - private MusicItem? ParseTwoRowItem(JsonElement json) + private static MusicItem? ParseTwoRowItem(JsonElement json) { var title = json.GetPropertyOrNull("title") ?.GetPropertyOrNull("runs") diff --git a/Core/Youtube/Utils/Http.cs b/Core/Youtube/Utils/Http.cs index 3035096..79484bb 100644 --- a/Core/Youtube/Utils/Http.cs +++ b/Core/Youtube/Utils/Http.cs @@ -2,7 +2,5 @@ internal static class Http { - private static readonly HttpClient HttpClientLazy = new(); - - public static HttpClient Client => HttpClientLazy; + public static HttpClient Client { get; } = new(); } diff --git a/Core/Youtube/YoutubeClient.cs b/Core/Youtube/YoutubeClient.cs index 47aa47c..9dee3e9 100644 --- a/Core/Youtube/YoutubeClient.cs +++ b/Core/Youtube/YoutubeClient.cs @@ -4,7 +4,6 @@ using LMP.Core.Youtube.Music; using LMP.Core.Youtube.Playlists; using LMP.Core.Youtube.Search; -using LMP.Core.Youtube.Utils; using LMP.Core.Youtube.Videos; namespace LMP.Core.Youtube; diff --git a/Features/Debug/DebugViewModel.cs b/Features/Debug/DebugViewModel.cs index 420fd0c..afed26a 100644 --- a/Features/Debug/DebugViewModel.cs +++ b/Features/Debug/DebugViewModel.cs @@ -7,6 +7,8 @@ using ReactiveUI.Fody.Helpers; using Microsoft.Extensions.DependencyInjection; using LMP.Features.Shared; +using LMP.Core.Audio.Helpers; + #if DEBUG using LMP.Core.Audio; @@ -146,7 +148,6 @@ private async Task PlayAudioTestAsync(bool useCache) try { - // Extract video ID var videoId = ExtractVideoId(AudioTestInput); if (string.IsNullOrEmpty(videoId)) { @@ -155,7 +156,6 @@ private async Task PlayAudioTestAsync(bool useCache) } AppendLog($" Video ID: {videoId}"); - // Get track info AppendLog($" → Getting stream URL..."); var track = new TrackInfo { @@ -177,7 +177,6 @@ private async Task PlayAudioTestAsync(bool useCache) AppendLog($" ⚠️ Track info error: {ex.Message}"); } - // Get stream URL var streamInfo = await _youtube.RefreshStreamUrlAsync(track, forceRefresh: true, _audioTestCts.Token); if (streamInfo == null) { @@ -190,13 +189,13 @@ private async Task PlayAudioTestAsync(bool useCache) AppendLog($" ✓ Container: {container}, Size: {size / 1024.0 / 1024.0:F1}MB"); AppendLog($" ✓ HLS: {track.IsHlsOnly}"); - // Create player AppendLog($" → Creating AudioPlayer..."); - // *** ИСПРАВЛЕНИЕ: используем поле класса для cacheManager *** + // Инициализируем глобальный кэш если включен if (useCache) { _testCacheManager = new AudioCacheManager(); + AudioSourceFactory.InitializeGlobalCache(_testCacheManager); AppendLog($" ✓ Cache enabled"); } @@ -209,9 +208,9 @@ private async Task PlayAudioTestAsync(bool useCache) } }; - _testPlayer = new AudioPlayer(options, _testCacheManager); + // AudioPlayer теперь принимает только options + _testPlayer = new AudioPlayer(options); - // Subscribe to events _testPlayer.StateChanged += state => Avalonia.Threading.Dispatcher.UIThread.Post(() => AppendLog($" State: {state}")); @@ -227,13 +226,11 @@ private async Task PlayAudioTestAsync(bool useCache) IsAudioPlaying = false; }); - // Start playback AppendLog($" → Starting playback..."); - await _testPlayer.PlayAsync(url, track.Id, _audioTestCts.Token); + await _testPlayer.PlayAsync(url, track.Id, ct: _audioTestCts.Token); AppendLog($" ▶️ Playing for {AudioTestDuration}s..."); - // Progress updates var startTime = DateTime.Now; while ((DateTime.Now - startTime).TotalSeconds < AudioTestDuration && !_audioTestCts.Token.IsCancellationRequested && @@ -243,12 +240,12 @@ private async Task PlayAudioTestAsync(bool useCache) var pos = _testPlayer.Position.TotalSeconds; var dur = _testPlayer.Duration.TotalSeconds; var buf = _testPlayer.BufferProgress; - AppendLog($" ⏱️ {pos:F1}s / {dur:F1}s | Buffer: {buf:F0}%"); + var downloaded = _testPlayer.GetDownloadedBytes() / 1024.0; + AppendLog($" ⏱️ {pos:F1}s / {dur:F1}s | Buffer: {buf:F0}% | Downloaded: {downloaded:F0}KB"); } AppendLog($" ✓ Test completed"); - // *** ИСПРАВЛЕНИЕ: показываем статистику из ТОГО ЖЕ cacheManager *** if (_testCacheManager != null) { var stats = _testCacheManager.GetStats(); @@ -671,11 +668,16 @@ private void AppendLog(string text) LogOutput += text + "\n"; } - public void Dispose() + protected override void Dispose(bool disposing) { - _audioTestCts?.Cancel(); - _audioTestCts?.Dispose(); - _testPlayer?.Dispose(); - _testCacheManager?.Dispose(); + if (disposing) + { + _audioTestCts?.Cancel(); + _audioTestCts?.Dispose(); + _testPlayer?.Dispose(); + _testCacheManager?.Dispose(); + } + + base.Dispose(disposing); } } \ No newline at end of file diff --git a/Features/Player/PlayerBarView.axaml b/Features/Player/PlayerBarView.axaml index 8fa9105..4f4b245 100644 --- a/Features/Player/PlayerBarView.axaml +++ b/Features/Player/PlayerBarView.axaml @@ -298,9 +298,7 @@ - - @@ -324,10 +322,13 @@ Classes="track-container" RenderTransformOrigin="0.5,0.5"> - - + + - + - + + VerticalAlignment="Stretch" + ClipToBounds="True" + IsHitTestVisible="False" /> - + - + - + + /// Минимальная ширина сегмента буфера в пикселях. + /// Нужна чтобы даже маленький чанк был виден. + /// + private const double MinSegmentWidthPx = 2.0; + #endregion #region State @@ -42,14 +49,24 @@ public partial class PlayerBarView : UserControl private double _seekDragRatio; private double _sparkPosition = -SparkWidth; + + /// + /// Пул Border'ов для сегментов буфера. + /// Используем абсолютное позиционирование внутри Grid через Margin. + /// private readonly List _bufferSegments = []; + /// + /// Кэшированная кисть для сегментов буфера. + /// Создаётся один раз, не пересоздаётся каждый кадр. + /// + private IBrush? _bufferBrushCache; + private DispatcherTimer? _volumePopupCloseTimer; private DispatcherTimer? _volumeTooltipHideTimer; private DispatcherTimer? _sparkAnimationTimer; private FlyoutBase? _formatFlyout; - // Для отписки от старого ViewModel private PlayerBarViewModel? _currentViewModel; #endregion @@ -84,7 +101,6 @@ private void SetupEventHandlers() private void SetupTimers() { - // Volume popup close timer _volumePopupCloseTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(VolumePopupCloseDelayMs) @@ -92,13 +108,10 @@ private void SetupTimers() _volumePopupCloseTimer.Tick += (_, _) => { if (!_isVolumePopupHovered && !_isVolumeButtonHovered && !_isDraggingVolume) - { VolumePopup.IsOpen = false; - } _volumePopupCloseTimer?.Stop(); }; - // Volume tooltip hide timer _volumeTooltipHideTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(VolumeTooltipHideDelayMs) @@ -109,7 +122,6 @@ private void SetupTimers() _volumeTooltipHideTimer?.Stop(); }; - // Spark animation timer _sparkAnimationTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(SparkAnimationIntervalMs) @@ -151,12 +163,10 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e VolumeButton.PointerExited -= OnVolumeButtonExited; VolumePopup.Opened -= OnVolumePopupOpened; - // Отписываемся от ViewModel - if (_currentViewModel != null) _currentViewModel.PropertyChanged -= OnViewModelPropertyChanged; + _currentViewModel?.PropertyChanged -= OnViewModelPropertyChanged; _currentViewModel = null; - _bufferSegments.Clear(); - BufferSegmentsCanvas.Children.Clear(); + ClearBufferSegments(); } private void OnWindowActivated(object? sender, EventArgs e) => _isWindowActive = true; @@ -171,29 +181,23 @@ protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); - // Отписываемся от старого ViewModel - if (_currentViewModel != null) - { - _currentViewModel.PropertyChanged -= OnViewModelPropertyChanged; - _currentViewModel = null; - } + _currentViewModel?.PropertyChanged -= OnViewModelPropertyChanged; + _currentViewModel = null; - // Подписываемся на новый if (DataContext is PlayerBarViewModel vm) { _currentViewModel = vm; vm.PropertyChanged += OnViewModelPropertyChanged; - // Инициализируем размеры ДО открытия любых Popup InitializeVolumeSlider(vm); - // Синхронизируем начальное состояние анимации if (vm.IsLoading) StartSparkAnimation(); else StopSparkAnimation(); - // Инициализируем буфер + // Инициализируем буфер — ВАЖНО сделать после присвоения DataContext + InvalidateBufferBrushCache(); UpdateBufferVisual(); } } @@ -202,21 +206,21 @@ private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.Pr { if (sender is not PlayerBarViewModel vm) return; - // Critical properties always update + // *** ДИАГНОСТИКА для буфера *** + if (e.PropertyName == nameof(PlayerBarViewModel.BufferedRanges)) + { + Log.Debug($"[PlayerBarView] PropertyChanged: BufferedRanges, count={vm.BufferedRanges.Count}"); + } + if (e.PropertyName == nameof(PlayerBarViewModel.IsLoading)) { if (vm.IsLoading) - { StartSparkAnimation(); - } else - { StopSparkAnimation(); - } return; } - // Skip non-critical updates when window inactive if (!_isWindowActive) return; switch (e.PropertyName) @@ -230,9 +234,9 @@ private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.Pr } break; - case nameof(PlayerBarViewModel.BufferProgressPercent): case nameof(PlayerBarViewModel.BufferedRanges): case nameof(PlayerBarViewModel.IsFullyBuffered): + Log.Debug($"[PlayerBarView] Calling UpdateBufferVisual, ranges={vm.BufferedRanges.Count}"); UpdateBufferVisual(); break; @@ -253,12 +257,9 @@ private void InitializeVolumeSlider(PlayerBarViewModel vm) try { int maxVolume = vm.MaxVolume > 0 ? vm.MaxVolume : 100; - - // Устанавливаем высоту панели double height = Math.Max(VolumeSliderMinHeight, maxVolume * VolumeSliderHeightPerPercent); VolumeSliderPanel.Height = height; - // Устанавливаем начальное положение ползунка int volume = Math.Clamp(vm.Volume, 0, maxVolume); double ratio = (double)volume / maxVolume; @@ -269,7 +270,6 @@ private void InitializeVolumeSlider(PlayerBarViewModel vm) catch (Exception ex) { Log.Warn($"[PlayerBar] InitializeVolumeSlider error: {ex.Message}"); - // Fallback к безопасным значениям VolumeSliderPanel.Height = VolumeSliderMinHeight; VolumeBar.Height = 0; VolumeThumb.Margin = new Thickness(0); @@ -282,19 +282,13 @@ private void InitializeVolumeSlider(PlayerBarViewModel vm) private void StartSparkAnimation() { - if (_sparkAnimationTimer == null) - { - Log.Warn("[PlayerBar] Spark animation timer is null!"); - return; - } + if (_sparkAnimationTimer == null) return; _sparkPosition = -SparkWidth; SparkRunner.Margin = new Thickness(_sparkPosition, 0, 0, 0); if (!_sparkAnimationTimer.IsEnabled) - { _sparkAnimationTimer.Start(); - } } private void StopSparkAnimation() @@ -308,7 +302,6 @@ private void StopSparkAnimation() private void OnSparkAnimationTick(object? sender, EventArgs e) { - // Не анимируем когда окно не видно if (!_isWindowActive) return; double containerWidth = SeekContainer.Bounds.Width; @@ -344,110 +337,148 @@ private void OnVolumeSliderPropertyChanged(object? sender, AvaloniaPropertyChang private void OnVolumePopupOpened(object? sender, EventArgs e) { - // Только обновляем визуал, высота уже установлена if (DataContext is PlayerBarViewModel vm) { - // Высота уже должна быть установлена в InitializeVolumeSlider - // Здесь только синхронизируем визуал если что-то изменилось if (VolumeSliderPanel.Height <= 0) - { UpdateVolumeSliderHeight(vm.MaxVolume); - } UpdateVolumeVisual(); } } #endregion - #region Visual Updates - - /// - /// Обновляет визуал seek-слайдера (позиция воспроизведения). - /// - private void UpdateSeekVisual() - { - if (DataContext is not PlayerBarViewModel vm) return; - - double width = SeekContainer.Bounds.Width; - double duration = vm.DurationSeconds; - - if (width <= 0 || duration <= 0) return; - - double ratio = Math.Clamp(vm.PositionSeconds / duration, 0, 1); - double position = width * ratio; - - ProgressBar.Width = position; - Canvas.SetLeft(SeekThumb, position - SeekThumbRadius); - } + #region Buffer Segments Visual /// - /// Обновляет визуал буфера. - /// Поддерживает сегментную визуализацию для прерывистого кэша. + /// Обновляет визуал сегментированного буфера. /// private void UpdateBufferVisual() { if (DataContext is not PlayerBarViewModel vm) return; + // Берём ширину из SeekContainer — он всегда имеет правильные размеры double width = SeekContainer.Bounds.Width; if (width <= 0) return; - IReadOnlyList<(double Start, double End)> ranges = vm.BufferedRanges; + var ranges = vm.BufferedRanges; - // Если нет диапазонов, но трек полностью загружен - показываем полную полосу - if (ranges.Count == 0 && vm.IsFullyBuffered) - { + // Полностью загружено, но ranges пустые + if ((ranges == null || ranges.Count == 0) && vm.IsFullyBuffered) ranges = [(0.0, 1.0)]; + + if (ranges == null || ranges.Count == 0) + { + HideAllBufferSegments(); + return; } - // Синхронизируем количество сегментов EnsureBufferSegmentCount(ranges.Count); - // Позиционируем каждый сегмент + var brush = GetBufferBrush(); + for (int i = 0; i < ranges.Count; i++) { var (start, end) = ranges[i]; var segment = _bufferSegments[i]; - // Защита от NaN/Infinity - start = double.IsNaN(start) || double.IsInfinity(start) ? 0 : Math.Clamp(start, 0, 1); - end = double.IsNaN(end) || double.IsInfinity(end) ? 0 : Math.Clamp(end, 0, 1); + if (double.IsNaN(start) || double.IsInfinity(start)) start = 0; + if (double.IsNaN(end) || double.IsInfinity(end)) end = 0; + start = Math.Clamp(start, 0, 1); + end = Math.Clamp(end, start, 1); + + double left = start * width; + double segWidth = (end - start) * width; - double left = width * start; - double segmentWidth = width * (end - start); + if (segWidth < MinSegmentWidthPx && segWidth > 0) + segWidth = MinSegmentWidthPx; + if (left + segWidth > width) + segWidth = width - left; + // Canvas абсолютное позиционирование Canvas.SetLeft(segment, left); - segment.Width = Math.Max(1, segmentWidth); // Минимум 1px чтобы было видно + Canvas.SetTop(segment, 0); + segment.Width = Math.Max(0, segWidth); + segment.Height = 4; + segment.Background = brush; + segment.IsVisible = segWidth > 0; + segment.Opacity = vm.IsFullyBuffered ? 0.6 : 0.45; } + + for (int i = ranges.Count; i < _bufferSegments.Count; i++) + _bufferSegments[i].IsVisible = false; } - /// - /// Обеспечивает нужное количество Border'ов для сегментов буфера. - /// - private void EnsureBufferSegmentCount(int count) + private void EnsureBufferSegmentCount(int needed) { - // Добавляем недостающие - while (_bufferSegments.Count < count) + while (_bufferSegments.Count < needed) { var segment = new Border { Height = 4, CornerRadius = new CornerRadius(2), - // Применяем те же классы что были у BufferBar + IsHitTestVisible = false, + Opacity = 0.45, + Background = GetBufferBrush() }; - segment.Classes.Add("slider-buffer"); - segment.Classes.Add("seek-track"); _bufferSegments.Add(segment); BufferSegmentsCanvas.Children.Add(segment); } + } + + private void HideAllBufferSegments() + { + foreach (var seg in _bufferSegments) + seg.IsVisible = false; + } - // Удаляем лишние - while (_bufferSegments.Count > count) + private void ClearBufferSegments() + { + _bufferSegments.Clear(); + BufferSegmentsCanvas.Children.Clear(); + _bufferBrushCache = null; + } + + private IBrush GetBufferBrush() + { + if (_bufferBrushCache != null) + return _bufferBrushCache; + + var app = Application.Current; + if (app?.Resources.TryGetResource("TextSecondaryBrush", app.ActualThemeVariant, out var res) == true + && res is IBrush b) { - var toRemove = _bufferSegments[^1]; - _bufferSegments.RemoveAt(_bufferSegments.Count - 1); - BufferSegmentsCanvas.Children.Remove(toRemove); + _bufferBrushCache = b; + return b; } + + _bufferBrushCache = new SolidColorBrush(Color.FromArgb(100, 180, 180, 180)); + return _bufferBrushCache; + } + + private void InvalidateBufferBrushCache() => _bufferBrushCache = null; + + #endregion + + #region Seek Visual + + /// + /// Обновляет визуал seek-слайдера (позиция воспроизведения). + /// + private void UpdateSeekVisual() + { + if (DataContext is not PlayerBarViewModel vm) return; + + double width = SeekContainer.Bounds.Width; + double duration = vm.DurationSeconds; + + if (width <= 0 || duration <= 0) return; + + double ratio = Math.Clamp(vm.PositionSeconds / duration, 0, 1); + double position = width * ratio; + + ProgressBar.Width = position; + Canvas.SetLeft(SeekThumb, position - SeekThumbRadius); } /// @@ -466,28 +497,40 @@ private void UpdatePlayingGlow() PlayingGlow.Width = Math.Max(20, width * ratio); } - /// - /// Обновляет высоту слайдера громкости. - /// + private void UpdateSeekCursor(double x) => + Canvas.SetLeft(SeekCursor, x - SeekCursorHalfWidth); + + private void UpdateSeekTooltip(double x, double seconds) + { + var time = TimeSpan.FromSeconds(Math.Max(0, seconds)); + HoverTimeText.Text = time.TotalHours >= 1 + ? time.ToString(@"h\:mm\:ss") + : time.ToString(@"m\:ss"); + + SeekTooltipBorder.Measure(Size.Infinity); + double tooltipWidth = SeekTooltipBorder.DesiredSize.Width; + SeekTooltipPopup.HorizontalOffset = x - (tooltipWidth / 2); + } + + private void UpdateSeekPreview(double x) => + PreviewFill.Width = Math.Max(0, x); + + #endregion + + #region Volume Visual + private void UpdateVolumeSliderHeight(int maxVolume) { - // Защита от невалидных значений if (maxVolume <= 0) maxVolume = 100; double height = Math.Max(VolumeSliderMinHeight, maxVolume * VolumeSliderHeightPerPercent); - // Проверка на NaN/Infinity if (double.IsNaN(height) || double.IsInfinity(height)) - { height = VolumeSliderMinHeight; - } VolumeSliderPanel.Height = height; } - /// - /// Обновляет визуал слайдера громкости. - /// private void UpdateVolumeVisual() { if (DataContext is not PlayerBarViewModel vm) return; @@ -495,7 +538,6 @@ private void UpdateVolumeVisual() double height = VolumeSliderPanel.Height; int maxVolume = vm.MaxVolume; - // Защита от деления на ноль и невалидных значений if (height <= 0 || double.IsNaN(height)) { height = VolumeSliderMinHeight; @@ -508,12 +550,8 @@ private void UpdateVolumeVisual() UpdateVolumeVisualInternal(ratio, height); } - /// - /// Внутренний метод обновления визуала громкости. - /// private void UpdateVolumeVisualInternal(double ratio, double height) { - // Защита от невалидных значений if (double.IsNaN(ratio) || double.IsInfinity(ratio)) ratio = 0; if (double.IsNaN(height) || double.IsInfinity(height) || height <= 0) height = VolumeSliderMinHeight; @@ -532,36 +570,6 @@ private void UpdateVolumeVisualInternal(double ratio, double height) VolumeThumb.Margin = new Thickness(0, thumbTop, 0, 0); } - /// - /// Обновляет позицию курсора seek. - /// - private void UpdateSeekCursor(double x) => - Canvas.SetLeft(SeekCursor, x - SeekCursorHalfWidth); - - /// - /// Обновляет тултип времени при наведении на seek-слайдер. - /// - private void UpdateSeekTooltip(double x, double seconds) - { - var time = TimeSpan.FromSeconds(Math.Max(0, seconds)); - HoverTimeText.Text = time.TotalHours >= 1 - ? time.ToString(@"h\:mm\:ss") - : time.ToString(@"m\:ss"); - - SeekTooltipBorder.Measure(Size.Infinity); - double tooltipWidth = SeekTooltipBorder.DesiredSize.Width; - SeekTooltipPopup.HorizontalOffset = x - (tooltipWidth / 2); - } - - /// - /// Обновляет превью seek при наведении. - /// - private void UpdateSeekPreview(double x) => - PreviewFill.Width = Math.Max(0, x); - - /// - /// Обновляет тултип громкости. - /// private void UpdateVolumeTooltip(int currentVolume, int maxVolume) { VolumeTooltipText.Text = $"{currentVolume}% / {maxVolume}%"; @@ -595,16 +603,12 @@ private void OnVolumeButtonEntered(object? sender, PointerEventArgs e) _isVolumeButtonHovered = true; _volumePopupCloseTimer?.Stop(); - // Проверяем валидность перед открытием if (DataContext is PlayerBarViewModel vm) { try { - // Убеждаемся что размеры валидны if (VolumeSliderPanel.Height <= 0 || double.IsNaN(VolumeSliderPanel.Height)) - { InitializeVolumeSlider(vm); - } VolumePopup.IsOpen = true; } @@ -781,7 +785,6 @@ private void OnVolumeScroll(object? sender, PointerWheelEventArgs e) { if (DataContext is not PlayerBarViewModel vm) return; - // Показываем тултип при скролле _volumeTooltipHideTimer?.Stop(); VolumeTooltipPopup.IsOpen = true; @@ -794,15 +797,12 @@ private void OnVolumeScroll(object? sender, PointerWheelEventArgs e) vm.OnVolumeChangeComplete(); } - // Обновляем текст и позицию тултипа UpdateVolumeTooltip(newVolume, vm.MaxVolume); - // Позиционируем относительно курсора мыши double height = VolumeSliderPanel.Height; double ratio = Math.Clamp((double)newVolume / vm.MaxVolume, 0, 1); double yOffset = height * (1 - ratio) - (height / 2); - // Принудительно ставим рядом с ползунком VolumeTooltipPopup.VerticalOffset = yOffset; e.Handled = true; @@ -831,7 +831,6 @@ private void OnVolumeAreaMoved(object? sender, PointerEventArgs e) UpdateVolumeTooltip(volumePercent, vm.MaxVolume); - // Тултип всегда слева от курсора (через Placement="Left" в XAML + VerticalOffset) double yOffset = height * (1 - ratio) - (height / 2); VolumeTooltipPopup.VerticalOffset = yOffset; diff --git a/Features/Player/PlayerBarViewModel.cs b/Features/Player/PlayerBarViewModel.cs index d47f0fb..612b04a 100644 --- a/Features/Player/PlayerBarViewModel.cs +++ b/Features/Player/PlayerBarViewModel.cs @@ -6,7 +6,7 @@ using Avalonia; using Avalonia.Media; using Avalonia.Threading; -using DynamicData.Binding; +using LMP.Core.Audio; using LMP.Core.Models; using LMP.Core.Services; using LMP.Core.ViewModels; @@ -35,7 +35,6 @@ public sealed class PlayerBarViewModel : ViewModelBase private readonly AudioEngine _audio; private readonly LibraryService _library; - private readonly DownloadService _downloads; private readonly IClipboardService _clipboard; private readonly YoutubeProvider _youtube; private readonly MusicLibraryManager _musicManager; @@ -292,7 +291,6 @@ public string DurationTooltip public PlayerBarViewModel( AudioEngine audio, LibraryService library, - DownloadService downloads, IClipboardService clipboard, YoutubeProvider youtube, MusicLibraryManager musicManager, @@ -300,7 +298,6 @@ public PlayerBarViewModel( { _audio = audio; _library = library; - _downloads = downloads; _clipboard = clipboard; _youtube = youtube; _musicManager = musicManager; @@ -383,20 +380,21 @@ public PlayerBarViewModel( .Subscribe(HandleTrackChanged) .DisposeWith(Disposables); - Observable.FromEvent( - h => _audio.OnStreamInfoReady += h, - h => _audio.OnStreamInfoReady -= h) + Observable.FromEvent, AudioStreamInfo>( + h => _audio.OnStreamInfoChanged += h, + h => _audio.OnStreamInfoChanged -= h) .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => UpdateStreamInfo()) + .Subscribe(UpdateStreamInfo) .DisposeWith(Disposables); - // BUFFER PROGRESS - Observable.Interval(TimeSpan.FromMilliseconds(BufferUpdateIntervalMs)) + // BUFFER PROGRESS — через событие, не через интервал + Observable.FromEvent, BufferState>( + h => _audio.OnBufferStateChanged += h, + h => _audio.OnBufferStateChanged -= h) + .Where(_ => !_isSuspended) + .Throttle(TimeSpan.FromMilliseconds(BufferUpdateIntervalMs)) .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => - { - if (!_isSuspended) UpdateBufferProgress(); - }) + .Subscribe(HandleBufferStateChanged) .DisposeWith(Disposables); // CACHE & LIBRARY EVENTS @@ -416,7 +414,7 @@ public PlayerBarViewModel( .Subscribe(t => { IsLiked = t.IsLiked; - if (CurrentTrack != null) CurrentTrack.IsLiked = t.IsLiked; + CurrentTrack?.IsLiked = t.IsLiked; this.RaisePropertyChanged(nameof(LikeTooltip)); }) .DisposeWith(Disposables); @@ -596,26 +594,87 @@ public PlayerBarViewModel( #region Buffer Progress /// - /// Обновляет прогресс буферизации из AudioEngine. + /// Обрабатывает обновление состояния буфера от AudioEngine. + /// + private void HandleBufferStateChanged(BufferState state) + { + Log.Debug($"[PlayerBarVM] HandleBufferStateChanged: progress={state.Progress:F1}%, " + + $"ranges={state.Ranges.Count}, suspended={_isSuspended}"); + + // Если трек загружен локально — всегда 100% + if (CurrentTrack?.IsDownloaded == true) + { + if (!IsFullyBuffered) + { + BufferProgressPercent = 100; + BufferedRanges = [(0.0, 1.0)]; + IsFullyBuffered = true; + this.RaisePropertyChanged(nameof(UseSegmentedBuffer)); + } + return; + } + + BufferProgressPercent = state.Progress; + IsFullyBuffered = state.IsFullyBuffered; + + // Обновляем ranges только если они реально изменились + var newRanges = state.Ranges; + + if (!RangesEqual(BufferedRanges, newRanges)) + { + Log.Debug($"[PlayerBarVM] Updating BufferedRanges: {newRanges.Count} ranges"); + BufferedRanges = newRanges; + this.RaisePropertyChanged(nameof(UseSegmentedBuffer)); + this.RaisePropertyChanged(nameof(BufferedRanges)); // Явно! + } + else + { + Log.Debug("[PlayerBarVM] Ranges unchanged, skipping update"); + } + } + + /// + /// Сравнивает два списка диапазонов для предотвращения лишних перерисовок. + /// + private static bool RangesEqual( + IReadOnlyList<(double Start, double End)> a, + IReadOnlyList<(double Start, double End)> b) + { + if (ReferenceEquals(a, b)) return true; + if (a.Count != b.Count) return false; + + for (int i = 0; i < a.Count; i++) + { + // Используем epsilon для сравнения double + if (Math.Abs(a[i].Start - b[i].Start) > 0.001 || + Math.Abs(a[i].End - b[i].End) > 0.001) + { + return false; + } + } + + return true; + } + + /// + /// Принудительное обновление буфера (для resume и т.д.). /// - private void UpdateBufferProgress() + private void ForceUpdateBufferProgress() { if (!HasTrack || CurrentTrack == null) return; - // Если трек загружен локально - 100% if (CurrentTrack.IsDownloaded) { BufferProgressPercent = 100; - BufferedRanges = [(0, 1)]; + BufferedRanges = [(0.0, 1.0)]; IsFullyBuffered = true; - this.RaisePropertyChanged(nameof(UseSegmentedBuffer)); - return; } - - // Получаем реальные данные из AudioEngine - BufferProgressPercent = _audio.BufferProgress; - BufferedRanges = _audio.GetBufferedRanges(); - IsFullyBuffered = _audio.IsFullyBuffered; + else + { + BufferProgressPercent = _audio.BufferProgress; + BufferedRanges = _audio.GetBufferedRanges(); + IsFullyBuffered = _audio.IsFullyBuffered; + } this.RaisePropertyChanged(nameof(UseSegmentedBuffer)); } @@ -720,7 +779,7 @@ private async Task LoadFormatsAsync() var formats = await _youtube.GetStreamOptionsAsync(videoId); var (currentFormat, currentBitrate, _) = _audio.GetCurrentStreamInfo(); - var cachedFormats = _cacheManager.GetCachedFormats(CurrentTrack.Id); + var cachedFormats = StreamCacheManager.GetCachedFormats(CurrentTrack.Id); AvailableFormats.Clear(); @@ -769,12 +828,6 @@ private void OnFormatCached(string trackId, string container, int bitrate, bool { _ = LoadFormatsAsync(); } - - if (isDownloaded) - { - UpdateStreamInfo(); - UpdateBufferProgress(); - } } #endregion @@ -846,7 +899,6 @@ private void HandleTrackChanged(TrackInfo? track) { CurrentTrack = track; HasTrack = track != null; - IsNavigating = false; this.RaisePropertyChanged(nameof(SafeTitle)); this.RaisePropertyChanged(nameof(SafeAuthor)); @@ -859,6 +911,9 @@ private void HandleTrackChanged(TrackInfo? track) AvailableFormats.Clear(); + Position = TimeSpan.Zero; + PositionSeconds = 0; + if (track != null) { Duration = track.Duration; @@ -867,14 +922,11 @@ private void HandleTrackChanged(TrackInfo? track) var storedTrack = _library.GetTrack(track.Id); IsLiked = storedTrack?.IsLiked ?? track.IsLiked; - Position = TimeSpan.Zero; - PositionSeconds = 0; - - // Инициализируем буфер + // Сбрасываем буфер для нового трека if (track.IsDownloaded) { BufferProgressPercent = 100; - BufferedRanges = [(0, 1)]; + BufferedRanges = [(0.0, 1.0)]; IsFullyBuffered = true; } else @@ -889,11 +941,15 @@ private void HandleTrackChanged(TrackInfo? track) } else { + Duration = TimeSpan.Zero; DurationSeconds = 1; PositionSeconds = 0; + + // Полный сброс буфера BufferProgressPercent = 0; BufferedRanges = []; IsFullyBuffered = false; + ShowStreamInfo = false; StreamInfo = ""; IsLiked = false; @@ -929,7 +985,7 @@ private void SyncPlaybackState(bool isPlaying, bool isPaused) IsPaused = isPaused; } - private void UpdateStreamInfo() + private void UpdateStreamInfo(AudioStreamInfo info) { if (CurrentTrack == null) { @@ -938,33 +994,21 @@ private void UpdateStreamInfo() return; } - var (format, bitrate, isReady) = _audio.GetCurrentStreamInfo(); - - if (!isReady || string.IsNullOrEmpty(format)) - { - StreamInfo = L.Get("Player_StreamInfo_Loading", "Loading..."); - ShowStreamInfo = true; - return; - } - - foreach (var f in AvailableFormats) + if (info.IsValid) { - f.IsActive = string.Equals(f.Codec, format, StringComparison.OrdinalIgnoreCase) && - (int)f.Bitrate == bitrate; - } + StreamInfo = info.FormatDisplay; + Duration = TimeSpan.FromMilliseconds(info.DurationMs); + DurationSeconds = Duration.TotalSeconds > 0 ? Duration.TotalSeconds : 1; - if (bitrate > 0) - { - StreamInfo = string.Format(L.Get("Stream_Format_Bitrate", "{0} • {1} kbps"), format, bitrate); + foreach (var f in AvailableFormats) + { + f.IsActive = string.Equals(f.Codec, info.Codec, StringComparison.OrdinalIgnoreCase) && + (int)f.Bitrate == info.Bitrate; + } } else { - StreamInfo = format; - } - - if (CurrentTrack.IsDownloaded && !string.IsNullOrEmpty(CurrentTrack.LocalPath)) - { - StreamInfo += " " + L.Get("Stream_Downloaded_Mark", "✓"); + StreamInfo = L.Get("Player_StreamInfo_Loading", "Loading..."); } ShowStreamInfo = true; @@ -1124,7 +1168,7 @@ protected override void OnResume() // Обновляем данные которые могли измениться FallbackPositionUpdate(); - UpdateBufferProgress(); + ForceUpdateBufferProgress(); Log.Debug("[PlayerBar] Resumed"); } diff --git a/Features/Settings/SettingsViewModel.cs b/Features/Settings/SettingsViewModel.cs index beda3f5..388d93c 100644 --- a/Features/Settings/SettingsViewModel.cs +++ b/Features/Settings/SettingsViewModel.cs @@ -180,7 +180,7 @@ public SettingsViewModel( YoutubeClientUtils.CurrentProfile = c.Value; // 3. Перезагружаем AudioEngine (чтобы обновить HttpClient внутри него) - await _audio.ReinitializeWithProfileAsync(_library.Settings.InternetProfile); + await AudioEngine.ReinitializeWithProfileAsync(_library.Settings.InternetProfile); // 4. Сбрасываем кэш стримов, так как старые ссылки могут быть невалидны для нового клиента _youtube.ClearCache(); @@ -691,7 +691,7 @@ private async Task ClearDownloadsAsync() if (!await _dialog.ConfirmAsync(SL["Dialog_Confirm_Title"], SL["Settings_ClearDownloadsConfirm"])) return; - await _streamCache.ClearDownloadsAsync(); + await StreamCacheManager.ClearDownloadsAsync(); UpdateCacheStats(); } diff --git a/Features/Shared/TrackItemViewModel.cs b/Features/Shared/TrackItemViewModel.cs index 8d74eff..946d00c 100644 --- a/Features/Shared/TrackItemViewModel.cs +++ b/Features/Shared/TrackItemViewModel.cs @@ -23,14 +23,13 @@ public sealed class TrackItemViewModel : ViewModelBase private readonly ObservableAsPropertyHelper _isCached; private Action? _onPlay; - private bool _isDisposed; #endregion #region Properties - Core public TrackInfo Track { get; } - public bool IsDisposed => _isDisposed; + public bool IsDisposed { get; private set; } public string Id => Track.Id; public string Title => Track.Title; @@ -265,7 +264,7 @@ public void SetActive(bool isActive) protected override void Dispose(bool disposing) { - if (_isDisposed) return; + if (IsDisposed) return; if (disposing) { @@ -280,7 +279,7 @@ protected override void Dispose(bool disposing) } base.Dispose(disposing); // Это вызовет Disposables.Dispose() - _isDisposed = true; + IsDisposed = true; } #endregion diff --git a/Features/Shell/MainWindow.axaml.cs b/Features/Shell/MainWindow.axaml.cs index a075830..6993a81 100644 --- a/Features/Shell/MainWindow.axaml.cs +++ b/Features/Shell/MainWindow.axaml.cs @@ -146,7 +146,7 @@ private static void PerformCleanup(bool aggressive) // 3. Буферы аудио var audioEngine = Program.Services.GetRequiredService(); - audioEngine.NotifyAppMinimized(); + AudioEngine.NotifyAppMinimized(); // 4. Мёртвые ссылки в TrackRegistry var registry = Program.Services.GetRequiredService(); diff --git a/Globals.cs b/Globals.cs index df3de9f..3ea9d1f 100644 --- a/Globals.cs +++ b/Globals.cs @@ -13,18 +13,26 @@ public static class Folder public static readonly string Data = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppId); + public static readonly string Cache = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + AppId); + public static readonly string Logs = Path.Combine(Data, "Logs"); public static readonly string Downloads = Path.Combine(Data, "Downloads"); - public static readonly string ImageCache = Path.Combine(Data, "ImageCache"); - public static readonly string StreamCache = Path.Combine(Data, "StreamCache"); - public static readonly string SearchCache = Path.Combine(Data, "SearchCache"); + public static readonly string ImageCache = Path.Combine(Cache, "ImageCache"); + public static readonly string StreamCache = Path.Combine(Cache, "StreamCache"); + public static readonly string SearchCache = Path.Combine(Cache, "SearchCache"); + public static readonly string AudioCache = Path.Combine(Cache, "AudioCache"); public static void Create() { Directory.CreateDirectory(Data); + Directory.CreateDirectory(Cache); Directory.CreateDirectory(Downloads); Directory.CreateDirectory(ImageCache); Directory.CreateDirectory(StreamCache); Directory.CreateDirectory(SearchCache); + Directory.CreateDirectory(AudioCache); + Directory.CreateDirectory(Logs); } } diff --git a/LMP.csproj b/LMP.csproj index 747f1ae..6e993b9 100644 --- a/LMP.csproj +++ b/LMP.csproj @@ -28,11 +28,6 @@ $(NoWarn);CS0436 - - $(DefineConstants);_HLS_FORMAT_TEST;_HTTP_LOG;AUDIO_TESTS;AUDIO_TESTS_OFFLINE - - - @@ -66,12 +61,5 @@ - - - - - - - \ No newline at end of file diff --git a/Program.cs b/Program.cs index f4ae0f8..0ae7b37 100644 --- a/Program.cs +++ b/Program.cs @@ -14,7 +14,7 @@ using Avalonia; using AsyncImageLoader; using LMP.UI.Dialogs; -using LMP.Core.Audio; +using LMP.Core.Audio.Cache; namespace LMP; @@ -105,21 +105,10 @@ private static void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - // === Audio & Downloads === - // services.AddSingleton(sp => - // { - // var youtubeProvider = sp.GetRequiredService(); - // var trackRegistry = sp.GetRequiredService(); - - // return new MusicPlayerService(youtubeProvider, trackRegistry, new AudioPlayerOptions - // { - // PositionUpdateInterval = TimeSpan.FromMilliseconds(200), - // MaxRetryAttempts = 3, - // UseNullBackend = false // true для тестов - // }); - // }); - services.AddSingleton(); - services.AddSingleton(); + // === Audio & Downloads ==== + services.AddSingleton(); // Новый менеджер кэша (диск) + services.AddSingleton(); // Наш новый движок + services.AddSingleton(); // Сервис загрузок // === ViewModels === services.AddTransient(); diff --git a/SharpJaad.AAC/AACException.cs b/SharpJaad.AAC/AACException.cs new file mode 100644 index 0000000..708d0ec --- /dev/null +++ b/SharpJaad.AAC/AACException.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace SharpJaad.AAC +{ + /// + /// Standard exception, thrown when decoding of an AAC frame fails. + /// The message gives more detailed information about the error. + /// @author in-somnia + /// + public class AACException : IOException + { + private readonly bool _eos; + + public bool IsEndOfStream { get { return _eos; } } + + public AACException(string message) : this(message, false) + { } + + public AACException(string message, bool eos) : base(message) + { + _eos = eos; + } + } +} diff --git a/SharpJaad.AAC/ChannelConfiguration.cs b/SharpJaad.AAC/ChannelConfiguration.cs new file mode 100644 index 0000000..67486b6 --- /dev/null +++ b/SharpJaad.AAC/ChannelConfiguration.cs @@ -0,0 +1,15 @@ +namespace SharpJaad.AAC +{ + public enum ChannelConfiguration : int + { + CHANNEL_CONFIG_UNSUPPORTED = -1, + CHANNEL_CONFIG_NONE = 0, + CHANNEL_CONFIG_MONO = 1, + CHANNEL_CONFIG_STEREO = 2, + CHANNEL_CONFIG_STEREO_PLUS_CENTER = 3, + CHANNEL_CONFIG_STEREO_PLUS_CENTER_PLUS_REAR_MONO = 4, + CHANNEL_CONFIG_FIVE = 5, + CHANNEL_CONFIG_FIVE_PLUS_ONE = 6, + CHANNEL_CONFIG_SEVEN_PLUS_ONE = 7 + } +} diff --git a/SharpJaad.AAC/Decoder.cs b/SharpJaad.AAC/Decoder.cs new file mode 100644 index 0000000..3472ef8 --- /dev/null +++ b/SharpJaad.AAC/Decoder.cs @@ -0,0 +1,146 @@ +using SharpJaad.AAC.Filterbank; +using SharpJaad.AAC.Syntax; +using SharpJaad.AAC.Transport; +using System; +using System.ComponentModel; + +namespace SharpJaad.AAC +{ + /// + /// AAC Decoder ported from JAAD: https://sourceforge.net/projects/jaadec/ + /// + public class Decoder + { + /* + static Decoder() + { + foreach (Handler h in LOGGER.getHandlers()) + { + LOGGER.removeHandler(h); + } + LOGGER.setLevel(Level.WARNING); + + ConsoleHandler h = new ConsoleHandler(); + h.setLevel(Level.ALL); + LOGGER.addHandler(h); + } + */ + + private DecoderConfig _config; + private SyntacticElements _syntacticElements; + private FilterBank _filterBank; + private BitStream _input; + private ADIFHeader _adifHeader; + + /// + /// The methods returns true, if a profile is supported by the decoder. + /// + /// An AAC profile. + /// true if the specified profile can be decoded + public static bool CanDecode(Profile profile) + { + return profile.IsDecodingSupported(); + } + + /// + /// Initializes the decoder with a MP4 decoder specific info. After this the MP4 frames can be passed to the decodeFrame(byte[], SampleBuffer) method to decode them. + /// + /// A byte array containing the decoder specific info from an MP4 container. + /// + /// If the specified profile is not supported. + public Decoder(byte[] decoderSpecificInfo) + { + _config = DecoderConfig.ParseMP4DecoderSpecificInfo(decoderSpecificInfo); + if (_config == null) throw new InvalidEnumArgumentException("illegal MP4 decoder specific info"); + + if (!CanDecode(_config.GetProfile())) throw new AACException("unsupported profile: " + _config.GetProfile()); + + _syntacticElements = new SyntacticElements(_config); + _filterBank = new FilterBank(_config.IsSmallFrameUsed(), (int)_config.GetChannelConfiguration()); + + _input = new BitStream(); + + //LOGGER.log(Level.FINE, "profile: {0}", config.getProfile()); + //LOGGER.log(Level.FINE, "sf: {0}", config.getSampleFrequency().getFrequency()); + //LOGGER.log(Level.FINE, "channels: {0}", config.getChannelConfiguration().getDescription()); + } + + public Decoder(DecoderConfig cfg) + { + _config = cfg ?? throw new InvalidEnumArgumentException("illegal MP4 decoder specific info"); + + if (!CanDecode(_config.GetProfile())) throw new AACException("unsupported profile: " + _config.GetProfile()); + + _syntacticElements = new SyntacticElements(_config); + _filterBank = new FilterBank(_config.IsSmallFrameUsed(), (int)_config.GetChannelConfiguration()); + + _input = new BitStream(); + + //LOGGER.log(Level.FINE, "profile: {0}", config.getProfile()); + //LOGGER.log(Level.FINE, "sf: {0}", config.getSampleFrequency().getFrequency()); + //LOGGER.log(Level.FINE, "channels: {0}", config.getChannelConfiguration().getDescription()); + } + + public DecoderConfig GetConfig() + { + return _config; + } + + /// + /// Decodes one frame of AAC data in frame mode and returns the raw PCM. + /// + /// The AAC frame. + /// A buffer to hold the decoded PCM data. + /// if decoding fails + public void DecodeFrame(byte[] frame, SampleBuffer buffer) + { + if (frame != null) _input.SetData(frame); + try + { + Decode(buffer); + } + catch (AACException e) + { + if (!e.IsEndOfStream) + throw; + //else LOGGER.warning("unexpected end of frame"); + } + } + + private void Decode(SampleBuffer buffer) + { + if (ADIFHeader.IsPresent(_input)) + { + _adifHeader = ADIFHeader.ReadHeader(_input); + PCE pce = _adifHeader.GetFirstPCE(); + _config.SetProfile(pce.GetProfile()); + _config.SetSampleFrequency(pce.GetSampleFrequency()); + _config.SetChannelConfiguration((ChannelConfiguration)pce.GetChannelCount()); + } + + if (!CanDecode(_config.GetProfile())) throw new AACException("unsupported profile: " + _config.GetProfile()); + + _syntacticElements.StartNewFrame(); + + try + { + //1: bitstream parsing and noiseless coding + _syntacticElements.Decode(_input); + //2: spectral processing + _syntacticElements.Process(_filterBank); + //3: send to output buffer + _syntacticElements.SendToOutput(buffer); + } + catch (AACException) + { + buffer.SetData(new byte[0], 0, 0, 0, 0); + throw; + } + catch (Exception e) + { + buffer.SetData(new byte[0], 0, 0, 0, 0); + throw new AACException(e.Message); + } + } + } +} diff --git a/SharpJaad.AAC/DecoderConfig.cs b/SharpJaad.AAC/DecoderConfig.cs new file mode 100644 index 0000000..650a2ff --- /dev/null +++ b/SharpJaad.AAC/DecoderConfig.cs @@ -0,0 +1,279 @@ +using SharpJaad.AAC.Syntax; + +namespace SharpJaad.AAC +{ + public class DecoderConfig + { + private Profile _profile, _extProfile; + private SampleFrequency _sampleFrequency; + private ChannelConfiguration _channelConfiguration; + private bool _frameLengthFlag; + private bool _dependsOnCoreCoder; + private int _coreCoderDelay; + private bool _extensionFlag; + //extension: SBR + private bool _sbrPresent, _downSampledSBR, _sbrEnabled; + //extension: error resilience + private bool _sectionDataResilience, _scalefactorResilience, _spectralDataResilience; + + public DecoderConfig() + { + _profile = Profile.AAC_MAIN; + _extProfile = Profile.UNKNOWN; + _sampleFrequency = SampleFrequency.SAMPLE_FREQUENCY_NONE; + _channelConfiguration = ChannelConfiguration.CHANNEL_CONFIG_UNSUPPORTED; + _frameLengthFlag = false; + _sbrPresent = false; + _downSampledSBR = false; + _sbrEnabled = true; + _sectionDataResilience = false; + _scalefactorResilience = false; + _spectralDataResilience = false; + } + + public void SetSBRPresent(bool sbr) + { + _sbrPresent = sbr; + } + + public void SetSBRDownsampled(bool sbr) + { + _downSampledSBR = sbr; + } + + /* ========== gets/sets ========== */ + public ChannelConfiguration GetChannelConfiguration() + { + return _channelConfiguration; + } + + public void SetChannelConfiguration(ChannelConfiguration channelConfiguration) + { + _channelConfiguration = channelConfiguration; + } + + public int GetCoreCoderDelay() + { + return _coreCoderDelay; + } + + public void SetCoreCoderDelay(int coreCoderDelay) + { + _coreCoderDelay = coreCoderDelay; + } + + public bool IsDependsOnCoreCoder() + { + return _dependsOnCoreCoder; + } + + public void SetDependsOnCoreCoder(bool dependsOnCoreCoder) + { + _dependsOnCoreCoder = dependsOnCoreCoder; + } + + public Profile GetExtObjectType() + { + return _extProfile; + } + + public void SetExtObjectType(Profile extObjectType) + { + _extProfile = extObjectType; + } + + public int GetFrameLength() + { + return _frameLengthFlag ? Constants.WINDOW_SMALL_LEN_LONG : Constants.WINDOW_LEN_LONG; + } + + public bool IsSmallFrameUsed() + { + return _frameLengthFlag; + } + + public void SetSmallFrameUsed(bool shortFrame) + { + _frameLengthFlag = shortFrame; + } + + public Profile GetProfile() + { + return _profile; + } + + public void SetProfile(Profile profile) + { + _profile = profile; + } + + public SampleFrequency GetSampleFrequency() + { + return _sampleFrequency; + } + + public void SetSampleFrequency(SampleFrequency sampleFrequency) + { + _sampleFrequency = sampleFrequency; + } + + //=========== SBR ============= + public bool IsSBRPresent() + { + return _sbrPresent; + } + + public bool IsSBRDownSampled() + { + return _downSampledSBR; + } + + public bool IsSBREnabled() + { + return _sbrEnabled; + } + + public void IetSBREnabled(bool enabled) + { + _sbrEnabled = enabled; + } + + //=========== ER ============= + public bool IsScalefactorResilienceUsed() + { + return _scalefactorResilience; + } + + public bool IsSectionDataResilienceUsed() + { + return _sectionDataResilience; + } + + public bool IsSpectralDataResilienceUsed() + { + return _spectralDataResilience; + } + + /* ======== static builder ========= */ + + /// + /// Parses the input arrays as a DecoderSpecificInfo, as used in MP4 containers. + /// + /// Data. + /// a DecoderConfig + /// + public static DecoderConfig ParseMP4DecoderSpecificInfo(byte[] data) + { + BitStream input = new BitStream(data); + DecoderConfig config = new DecoderConfig(); + + try + { + config._profile = ReadProfile(input); + + int sf = input.ReadBits(4); + if (sf == 0xF) config._sampleFrequency = SampleFrequencyExtensions.FromFrequency(input.ReadBits(24)); + else config._sampleFrequency = (SampleFrequency)sf; + config._channelConfiguration = (ChannelConfiguration)input.ReadBits(4); + + switch (config._profile) + { + case Profile.AAC_SBR: + config._extProfile = config._profile; + config._sbrPresent = true; + sf = input.ReadBits(4); + //TODO: 24 bits already read; read again? + //if(sf==0xF) config.sampleFrequency = SampleFrequency.forFrequency(in.readBits(24)); + //if sample frequencies are the same: downsample SBR + config._downSampledSBR = (int)config._sampleFrequency == sf; + config._sampleFrequency = (SampleFrequency)sf; + config._profile = ReadProfile(input); + break; + case Profile.AAC_MAIN: + case Profile.AAC_LC: + case Profile.AAC_SSR: + case Profile.AAC_LTP: + case Profile.ER_AAC_LC: + case Profile.ER_AAC_LTP: + case Profile.ER_AAC_LD: + //ga-specific info: + config._frameLengthFlag = input.ReadBool(); + if (config._frameLengthFlag) throw new AACException("config uses 960-sample frames, not yet supported"); //TODO: are 960-frames working yet? + config._dependsOnCoreCoder = input.ReadBool(); + if (config._dependsOnCoreCoder) config._coreCoderDelay = input.ReadBits(14); + else config._coreCoderDelay = 0; + config._extensionFlag = input.ReadBool(); + + if (config._extensionFlag) + { + if (config._profile.IsErrorResilientProfile()) + { + config._sectionDataResilience = input.ReadBool(); + config._scalefactorResilience = input.ReadBool(); + config._spectralDataResilience = input.ReadBool(); + } + //extensionFlag3 + input.SkipBit(); + } + + if (config._channelConfiguration == ChannelConfiguration.CHANNEL_CONFIG_NONE) + { + //TODO: is this working correct? -> ISO 14496-3 part 1: 1.A.4.3 + input.SkipBits(3); //PCE + PCE pce = new PCE(); + pce.Decode(input); + config._profile = pce.GetProfile(); + config._sampleFrequency = pce.GetSampleFrequency(); + config._channelConfiguration = (ChannelConfiguration)pce.GetChannelCount(); + } + + if (input.GetBitsLeft() > 10) ReadSyncExtension(input, config); + break; + default: + throw new AACException("profile not supported: " + (int)config._profile); + } + return config; + } + finally + { + input.Destroy(); + } + } + + private static Profile ReadProfile(BitStream input) + { + int i = input.ReadBits(5); + if (i == 31) i = 32 + input.ReadBits(6); + return (Profile)i; + } + + private static void ReadSyncExtension(BitStream input, DecoderConfig config) + { + int type = input.ReadBits(11); + switch (type) + { + case 0x2B7: + Profile profile = (Profile)input.ReadBits(5); + + if (profile.Equals(Profile.AAC_SBR)) + { + config._sbrPresent = input.ReadBool(); + if (config._sbrPresent) + { + config._profile = profile; + + int tmp = input.ReadBits(4); + + if (tmp == (int)config._sampleFrequency) config._downSampledSBR = true; + if (tmp == 15) + { + throw new AACException("sample rate specified explicitly, not supported yet!"); + //tmp = in.readBits(24); + } + } + } + break; + } + } + } +} diff --git a/SharpJaad.AAC/Error/BitsBuffer.cs b/SharpJaad.AAC/Error/BitsBuffer.cs new file mode 100644 index 0000000..3f53550 --- /dev/null +++ b/SharpJaad.AAC/Error/BitsBuffer.cs @@ -0,0 +1,124 @@ +using SharpJaad.AAC.Syntax; + +namespace SharpJaad.AAC.Error +{ + public class BitsBuffer + { + public int _bufa; + + public int _bufb; + + public int _len; + + public BitsBuffer() + { + _len = 0; + } + + public int GetLength() + { + return _len; + } + + public int ShowBits(int bits) + { + if (bits == 0) return 0; + if (_len <= 32) + { + //huffman_spectral_data_2 needs to read more than may be available, + //bits maybe > len, deliver 0 than + if (_len >= bits) return (int)(_bufa >> _len - bits & 0xFFFFFFFF >> 32 - bits); + else return (int)(_bufa << bits - _len & 0xFFFFFFFF >> 32 - bits); + } + else + { + if (_len - bits < 32) return (int)((_bufb & 0xFFFFFFFF >> 64 - _len) << bits - _len + 32) | _bufa >> _len - bits; + else return (int)(_bufb >> _len - bits - 32 & 0xFFFFFFFF >> 32 - bits); + } + } + + public bool FlushBits(int bits) + { + _len -= bits; + + bool b; + if (_len < 0) + { + _len = 0; + b = false; + } + else b = true; + return b; + } + + public int GetBits(int n) + { + int i = ShowBits(n); + if (!FlushBits(n)) i = -1; + return i; + } + + public int GetBit() + { + int i = ShowBits(1); + if (!FlushBits(1)) i = -1; + return i; + } + + public void RewindReverse() + { + if (_len == 0) return; + int[] i = HCR.RewindReverse64(_bufb, _bufa, _len); + _bufb = i[0]; + _bufa = i[1]; + } + + //merge bits of a to b + public void ConcatBits(BitsBuffer a) + { + if (a._len == 0) return; + int al = a._bufa; + int ah = a._bufb; + + int bl, bh; + if (_len > 32) + { + //mask off superfluous high b bits + bl = _bufa; + bh = _bufb & (1 << _len - 32) - 1; + //left shift a len bits + ah = al << _len - 32; + al = 0; + } + else + { + bl = _bufa & (1 << _len) - 1; + bh = 0; + ah = ah << _len | al >> 32 - _len; + al = al << _len; + } + + //merge + _bufa = bl | al; + _bufb = bh | ah; + + _len += a._len; + } + + public void ReadSegment(int segwidth, BitStream input) + { + _len = segwidth; + + if (segwidth > 32) + { + _bufb = input.ReadBits(segwidth - 32); + _bufa = input.ReadBits(32); + } + else + { + _bufa = input.ReadBits(segwidth); + _bufb = 0; + } + } + } +} diff --git a/SharpJaad.AAC/Error/HCR.cs b/SharpJaad.AAC/Error/HCR.cs new file mode 100644 index 0000000..2f87d97 --- /dev/null +++ b/SharpJaad.AAC/Error/HCR.cs @@ -0,0 +1,285 @@ +using SharpJaad.AAC; +using SharpJaad.AAC.Huffman; +using SharpJaad.AAC.Syntax; +using System; + +namespace SharpJaad.AAC.Error +{ + public class HCR + { + private class Codeword + { + public int _cb; + public int _decoded; + public int _sp_offset; + public BitsBuffer _bits; + + public void Fill(int sp, int cb) + { + _sp_offset = sp; + _cb = cb; + _decoded = 0; + _bits = new BitsBuffer(); + } + } + + private static readonly int NUM_CB = 6; + private static readonly int NUM_CB_ER = 22; + //private static readonly int MAX_CB = 32; + private static readonly int VCB11_FIRST = 16; + private static readonly int VCB11_LAST = 31; + private static readonly int[] PRE_SORT_CB_STD = { 11, 9, 7, 5, 3, 1 }; + private static readonly int[] PRE_SORT_CB_ER = { 11, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 9, 7, 5, 3, 1 }; + private static readonly int[] MAX_CW_LEN = {0, 11, 9, 20, 16, 13, 11, 14, 12, 17, 14, 49, + 0, 0, 0, 0, 14, 17, 21, 21, 25, 25, 29, 29, 29, 29, 33, 33, 33, 37, 37, 41}; + //bit-twiddling helpers + private static readonly int[] S = { 1, 2, 4, 8, 16 }; + private static readonly int[] B = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF }; + + //32 bit rewind and reverse + private static int RewindReverse(int v, int len) + { + v = v >> S[0] & B[0] | v << S[0] & ~B[0]; + v = v >> S[1] & B[1] | v << S[1] & ~B[1]; + v = v >> S[2] & B[2] | v << S[2] & ~B[2]; + v = v >> S[3] & B[3] | v << S[3] & ~B[3]; + v = v >> S[4] & B[4] | v << S[4] & ~B[4]; + + //shift off low bits + v >>= 32 - len; + + return v; + } + + //64 bit rewind and reverse + public static int[] RewindReverse64(int hi, int lo, int len) + { + int[] i = new int[2]; + if (len <= 32) + { + i[0] = 0; + i[1] = RewindReverse(lo, len); + } + else + { + lo = lo >> S[0] & B[0] | lo << S[0] & ~B[0]; + hi = hi >> S[0] & B[0] | hi << S[0] & ~B[0]; + lo = lo >> S[1] & B[1] | lo << S[1] & ~B[1]; + hi = hi >> S[1] & B[1] | hi << S[1] & ~B[1]; + lo = lo >> S[2] & B[2] | lo << S[2] & ~B[2]; + hi = hi >> S[2] & B[2] | hi << S[2] & ~B[2]; + lo = lo >> S[3] & B[3] | lo << S[3] & ~B[3]; + hi = hi >> S[3] & B[3] | hi << S[3] & ~B[3]; + lo = lo >> S[4] & B[4] | lo << S[4] & ~B[4]; + hi = hi >> S[4] & B[4] | hi << S[4] & ~B[4]; + + //shift off low bits + i[1] = hi >> 64 - len | lo << len - 32; + i[1] = lo >> 64 - len; + } + return i; + } + + private static bool IsGoodCB(int cb, int sectCB) + { + bool b = false; + if (sectCB > HCB.ZERO_HCB && sectCB <= HCB.ESCAPE_HCB || sectCB >= VCB11_FIRST && sectCB <= VCB11_LAST) + { + if (cb < HCB.ESCAPE_HCB) b = sectCB == cb || sectCB == cb + 1; + else b = sectCB == cb; + } + return b; + } + + //sectionDataResilience = hDecoder->aacSectionDataResilienceFlag + public static void DecodeReorderedSpectralData(ICStream ics, BitStream input, short[] spectralData, bool sectionDataResilience) + { + ICSInfo info = ics.GetInfo(); + int windowGroupCount = info.GetWindowGroupCount(); + int maxSFB = info.GetMaxSFB(); + int[] swbOffsets = info.GetSWBOffsets(); + int swbOffsetMax = info.GetSWBOffsetMax(); + //TODO: + //final SectionData sectData = ics.getSectionData(); + int[][] sectStart = null; //sectData.getSectStart(); + int[][] sectEnd = null; //sectData.getSectEnd(); + int[] numSec = null; //sectData.getNumSec(); + int[][] sectCB = null; //sectData.getSectCB(); + int[][] sectSFBOffsets = null; //info.getSectSFBOffsets(); + + //check parameter + int spDataLen = ics.GetReorderedSpectralDataLength(); + if (spDataLen == 0) return; + + int longestLen = ics.GetLongestCodewordLength(); + if (longestLen == 0 || longestLen >= spDataLen) throw new AACException("length of longest HCR codeword out of range"); + + //create spOffsets + int[] spOffsets = new int[8]; + int shortFrameLen = spectralData.Length / 8; + spOffsets[0] = 0; + int g; + for (g = 1; g < windowGroupCount; g++) + { + spOffsets[g] = spOffsets[g - 1] + shortFrameLen * info.GetWindowGroupLength(g - 1); + } + + Codeword[] codeword = new Codeword[512]; + BitsBuffer[] segment = new BitsBuffer[512]; + + int lastCB; + int[] preSortCB; + if (sectionDataResilience) + { + preSortCB = PRE_SORT_CB_ER; + lastCB = NUM_CB_ER; + } + else + { + preSortCB = PRE_SORT_CB_STD; + lastCB = NUM_CB; + } + + int PCWs_done = 0; + int segmentsCount = 0; + int numberOfCodewords = 0; + int bitsread = 0; + + int sfb, w_idx, i, thisCB, thisSectCB, cws; + //step 1: decode PCW's (set 0), and stuff data in easier-to-use format + for (int sortloop = 0; sortloop < lastCB; sortloop++) + { + //select codebook to process this pass + thisCB = preSortCB[sortloop]; + + for (sfb = 0; sfb < maxSFB; sfb++) + { + for (w_idx = 0; 4 * w_idx < Math.Min(swbOffsets[sfb + 1], swbOffsetMax) - swbOffsets[sfb]; w_idx++) + { + for (g = 0; g < windowGroupCount; g++) + { + for (i = 0; i < numSec[g]; i++) + { + if (sectStart[g][i] <= sfb && sectEnd[g][i] > sfb) + { + /* check whether codebook used here is the one we want to process */ + thisSectCB = sectCB[g][i]; + + if (IsGoodCB(thisCB, thisSectCB)) + { + //precalculation + int sect_sfb_size = sectSFBOffsets[g][sfb + 1] - sectSFBOffsets[g][sfb]; + int inc = thisSectCB < HCB.FIRST_PAIR_HCB ? 4 : 2; + int group_cws_count = 4 * info.GetWindowGroupLength(g) / inc; + int segwidth = Math.Min(MAX_CW_LEN[thisSectCB], longestLen); + + //read codewords until end of sfb or end of window group + for (cws = 0; cws < group_cws_count && cws + w_idx * group_cws_count < sect_sfb_size; cws++) + { + int sp = spOffsets[g] + sectSFBOffsets[g][sfb] + inc * (cws + w_idx * group_cws_count); + + //read and decode PCW + if (PCWs_done == 0) + { + //read in normal segments + if (bitsread + segwidth <= spDataLen) + { + segment[segmentsCount].ReadSegment(segwidth, input); + bitsread += segwidth; + + //Huffman.decodeSpectralDataER(segment[segmentsCount], thisSectCB, spectralData, sp); + + //keep leftover bits + segment[segmentsCount].RewindReverse(); + + segmentsCount++; + } + else + { + //remaining after last segment + if (bitsread < spDataLen) + { + int additional_bits = spDataLen - bitsread; + + segment[segmentsCount].ReadSegment(additional_bits, input); + segment[segmentsCount]._len += segment[segmentsCount - 1]._len; + segment[segmentsCount].RewindReverse(); + + if (segment[segmentsCount - 1]._len > 32) + { + segment[segmentsCount - 1]._bufb = segment[segmentsCount]._bufb + + segment[segmentsCount - 1].ShowBits(segment[segmentsCount - 1]._len - 32); + segment[segmentsCount - 1]._bufa = segment[segmentsCount]._bufa + + segment[segmentsCount - 1].ShowBits(32); + } + else + { + segment[segmentsCount - 1]._bufa = segment[segmentsCount]._bufa + + segment[segmentsCount - 1].ShowBits(segment[segmentsCount - 1]._len); + segment[segmentsCount - 1]._bufb = segment[segmentsCount]._bufb; + } + segment[segmentsCount - 1]._len += additional_bits; + } + bitsread = spDataLen; + PCWs_done = 1; + + codeword[0].Fill(sp, thisSectCB); + } + } + else + { + codeword[numberOfCodewords - segmentsCount].Fill(sp, thisSectCB); + } + numberOfCodewords++; + } + } + } + } + } + } + } + } + + if (segmentsCount == 0) throw new AACException("no segments in HCR"); + + int numberOfSets = numberOfCodewords / segmentsCount; + + //step 2: decode nonPCWs + int trial, codewordBase, segmentID, codewordID; + for (int set = 1; set <= numberOfSets; set++) + { + for (trial = 0; trial < segmentsCount; trial++) + { + for (codewordBase = 0; codewordBase < segmentsCount; codewordBase++) + { + segmentID = (trial + codewordBase) % segmentsCount; + codewordID = codewordBase + set * segmentsCount - segmentsCount; + + //data up + if (codewordID >= numberOfCodewords - segmentsCount) break; + + if (codeword[codewordID]._decoded == 0 && segment[segmentID]._len > 0) + { + if (codeword[codewordID]._bits._len != 0) segment[segmentID].ConcatBits(codeword[codewordID]._bits); + + int tmplen = segment[segmentID]._len; + /*int ret = Huffman.decodeSpectralDataER(segment[segmentID], codeword[codewordID].cb, + spectralData, codeword[codewordID].sp_offset); + + if(ret>=0) codeword[codewordID].decoded = 1; + else { + codeword[codewordID].bits = segment[segmentID]; + codeword[codewordID].bits.len = tmplen; + }*/ + + } + } + } + for (i = 0; i < segmentsCount; i++) + { + segment[i].RewindReverse(); + } + } + } + } +} diff --git a/SharpJaad.AAC/Error/RVLC.cs b/SharpJaad.AAC/Error/RVLC.cs new file mode 100644 index 0000000..4340717 --- /dev/null +++ b/SharpJaad.AAC/Error/RVLC.cs @@ -0,0 +1,135 @@ +using SharpJaad.AAC.Huffman; +using SharpJaad.AAC.Syntax; +using System; + +namespace SharpJaad.AAC.Error +{ + public class RVLC + { + private const int ESCAPE_FLAG = 7; + + public void Decode(BitStream input, ICStream ics, int[][] scaleFactors) + { + int bits = ics.GetInfo().IsEightShortFrame() ? 11 : 9; + bool sfConcealment = input.ReadBool(); + int revGlobalGain = input.ReadBits(8); + int rvlcSFLen = input.ReadBits(bits); + + ICSInfo info = ics.GetInfo(); + int windowGroupCount = info.GetWindowGroupCount(); + int maxSFB = info.GetMaxSFB(); + int[][] sfbCB = null; //ics.getSectionData().getSfbCB(); + + int sf = ics.GetGlobalGain(); + int intensityPosition = 0; + int noiseEnergy = sf - 90 - 256; + bool intensityUsed = false, noiseUsed = false; + + int sfb; + for (int g = 0; g < windowGroupCount; g++) + { + for (sfb = 0; sfb < maxSFB; sfb++) + { + switch (sfbCB[g][sfb]) + { + case HCB.ZERO_HCB: + scaleFactors[g][sfb] = 0; + break; + case HCB.INTENSITY_HCB: + case HCB.INTENSITY_HCB2: + if (!intensityUsed) intensityUsed = true; + intensityPosition += DecodeHuffman(input); + scaleFactors[g][sfb] = intensityPosition; + break; + case HCB.NOISE_HCB: + if (noiseUsed) + { + noiseEnergy += DecodeHuffman(input); + scaleFactors[g][sfb] = noiseEnergy; + } + else + { + noiseUsed = true; + noiseEnergy = DecodeHuffman(input); + } + break; + default: + sf += DecodeHuffman(input); + scaleFactors[g][sfb] = sf; + break; + } + } + } + + int lastIntensityPosition = 0; + if (intensityUsed) lastIntensityPosition = DecodeHuffman(input); + noiseUsed = false; + if (input.ReadBool()) DecodeEscapes(input, ics, scaleFactors); + } + + private void DecodeEscapes(BitStream input, ICStream ics, int[][] scaleFactors) + { + ICSInfo info = ics.GetInfo(); + int windowGroupCount = info.GetWindowGroupCount(); + int maxSFB = info.GetMaxSFB(); + int[][] sfbCB = null; //ics.getSectionData().getSfbCB(); + + int escapesLen = input.ReadBits(8); + + bool noiseUsed = false; + + int sfb, val; + for (int g = 0; g < windowGroupCount; g++) + { + for (sfb = 0; sfb < maxSFB; sfb++) + { + if (sfbCB[g][sfb] == HCB.NOISE_HCB && !noiseUsed) noiseUsed = true; + else if (Math.Abs(sfbCB[g][sfb]) == ESCAPE_FLAG) + { + val = DecodeHuffmanEscape(input); + if (sfbCB[g][sfb] == -ESCAPE_FLAG) scaleFactors[g][sfb] -= val; + else scaleFactors[g][sfb] += val; + } + } + } + } + + private int DecodeHuffman(BitStream input) + { + int off = 0; + int i = RVLCTables.RVLC_BOOK[off][1]; + int cw = input.ReadBits(i); + + int j; + while (cw != RVLCTables.RVLC_BOOK[off][2] && i < 10) + { + off++; + j = RVLCTables.RVLC_BOOK[off][1] - i; + i += j; + cw <<= j; + cw |= input.ReadBits(j); + } + + return RVLCTables.RVLC_BOOK[off][0]; + } + + private int DecodeHuffmanEscape(BitStream input) + { + int off = 0; + int i = RVLCTables.ESCAPE_BOOK[off][1]; + int cw = input.ReadBits(i); + + int j; + while (cw != RVLCTables.ESCAPE_BOOK[off][2] && i < 21) + { + off++; + j = RVLCTables.ESCAPE_BOOK[off][1] - i; + i += j; + cw <<= j; + cw |= input.ReadBits(j); + } + + return RVLCTables.ESCAPE_BOOK[off][0]; + } + } +} diff --git a/SharpJaad.AAC/Error/RVLCTables.cs b/SharpJaad.AAC/Error/RVLCTables.cs new file mode 100644 index 0000000..78ea246 --- /dev/null +++ b/SharpJaad.AAC/Error/RVLCTables.cs @@ -0,0 +1,90 @@ +namespace SharpJaad.AAC.Error +{ + public static class RVLCTables + { + //index,length,codeword + public static int[][] RVLC_BOOK = new int[][] { + new int[] {0, 1, 0}, /* 0 */ + new int[] { -1, 3, 5 }, /* 101 */ + new int[] { 1, 3, 7 }, /* 111 */ + new int[] { -2, 4, 9 }, /* 1001 */ + new int[] { -3, 5, 17 }, /* 10001 */ + new int[] { 2, 5, 27 }, /* 11011 */ + new int[] { -4, 6, 33 }, /* 100001 */ + new int[] { 99, 6, 50 }, /* 110010 */ + new int[] { 3, 6, 51 }, /* 110011 */ + new int[] { 99, 6, 52 }, /* 110100 */ + new int[] { -7, 7, 65 }, /* 1000001 */ + new int[] { 99, 7, 96 }, /* 1100000 */ + new int[] { 99, 7, 98 }, /* 1100010 */ + new int[] { 7, 7, 99 }, /* 1100011 */ + new int[] { 4, 7, 107 }, /* 1101011 */ + new int[] { -5, 8, 129 }, /* 10000001 */ + new int[] { 99, 8, 194 }, /* 11000010 */ + new int[] { 5, 8, 195 }, /* 11000011 */ + new int[] { 99, 8, 212 }, /* 11010100 */ + new int[] { 99, 9, 256 }, /* 100000000 */ + new int[] { -6, 9, 257 }, /* 100000001 */ + new int[] { 99, 9, 426 }, /* 110101010 */ + new int[] { 6, 9, 427 }, /* 110101011 */ + new int[] { 99, 10, 0 } + }; + public static int[][] ESCAPE_BOOK = { + new int[] { 1, 2, 0 }, + new int[] { 0, 2, 2 }, + new int[] { 3, 3, 2 }, + new int[] { 2, 3, 6 }, + new int[] { 4, 4, 14 }, + new int[] { 7, 5, 13 }, + new int[] { 6, 5, 15 }, + new int[] { 5, 5, 31 }, + new int[] { 11, 6, 24 }, + new int[] { 10, 6, 25 }, + new int[] { 9, 6, 29 }, + new int[] { 8, 6, 61 }, + new int[] { 13, 7, 56 }, + new int[] { 12, 7, 120 }, + new int[] { 15, 8, 114 }, + new int[] { 14, 8, 242 }, + new int[] { 17, 9, 230 }, + new int[] { 16, 9, 486 }, + new int[] { 19, 10, 463 }, + new int[] { 18, 10, 974 }, + new int[] { 22, 11, 925 }, + new int[] { 20, 11, 1950 }, + new int[] { 21, 11, 1951 }, + new int[] { 23, 12, 1848 }, + new int[] { 25, 13, 3698 }, + new int[] { 24, 14, 7399 }, + new int[] { 26, 15, 14797 }, + new int[] { 49, 19, 236736 }, + new int[] { 50, 19, 236737 }, + new int[] { 51, 19, 236738 }, + new int[] { 52, 19, 236739 }, + new int[] { 53, 19, 236740 }, + new int[] { 27, 20, 473482 }, + new int[] { 28, 20, 473483 }, + new int[] { 29, 20, 473484 }, + new int[] { 30, 20, 473485 }, + new int[] { 31, 20, 473486 }, + new int[] { 32, 20, 473487 }, + new int[] { 33, 20, 473488 }, + new int[] { 34, 20, 473489 }, + new int[] { 35, 20, 473490 }, + new int[] { 36, 20, 473491 }, + new int[] { 37, 20, 473492 }, + new int[] { 38, 20, 473493 }, + new int[] { 39, 20, 473494 }, + new int[] { 40, 20, 473495 }, + new int[] { 41, 20, 473496 }, + new int[] { 42, 20, 473497 }, + new int[] { 43, 20, 473498 }, + new int[] { 44, 20, 473499 }, + new int[] { 45, 20, 473500 }, + new int[] { 46, 20, 473501 }, + new int[] { 47, 20, 473502 }, + new int[] { 48, 20, 473503 }, + new int[] { 99, 21, 0 } + }; + } +} diff --git a/SharpJaad.AAC/Filterbank/FFT.cs b/SharpJaad.AAC/Filterbank/FFT.cs new file mode 100644 index 0000000..3f39442 --- /dev/null +++ b/SharpJaad.AAC/Filterbank/FFT.cs @@ -0,0 +1,130 @@ +using SharpJaad.AAC; + +namespace SharpJaad.AAC.Filterbank +{ + public class FFT + { + private int _length; + private float[,] _roots; + private float[,] _rev; + private float[] _a, _b, _c, _d, _e1, _e2; + + public FFT(int length) + { + _length = length; + + switch (length) + { + case 64: + _roots = FFTables.FFT_TABLE_64; + break; + case 512: + _roots = FFTables.FFT_TABLE_512; + break; + case 60: + _roots = FFTables.FFT_TABLE_60; + break; + case 480: + _roots = FFTables.FFT_TABLE_480; + break; + default: + throw new AACException("unexpected FFT length: " + length); + } + + //processing buffers + _rev = new float[length, 2]; + _a = new float[2]; + _b = new float[2]; + _c = new float[2]; + _d = new float[2]; + _e1 = new float[2]; + _e2 = new float[2]; + } + + public void Process(float[,] input, bool forward) + { + int imOff = forward ? 2 : 1; + int scale = 1; + //bit-reversal + int ii = 0; + for (int i = 0; i < _length; i++) + { + _rev[i, 0] = input[ii, 0]; + _rev[i, 1] = input[ii, 1]; + int k = _length >> 1; + while (ii >= k && k > 0) + { + ii -= k; + k >>= 1; + } + ii += k; + } + for (int i = 0; i < _length; i++) + { + input[i, 0] = _rev[i, 0]; + input[i, 1] = _rev[i, 1]; + } + + //bottom base-4 round + for (int i = 0; i < _length; i += 4) + { + _a[0] = input[i, 0] + input[i + 1, 0]; + _a[1] = input[i, 1] + input[i + 1, 1]; + _b[0] = input[i + 2, 0] + input[i + 3, 0]; + _b[1] = input[i + 2, 1] + input[i + 3, 1]; + _c[0] = input[i, 0] - input[i + 1, 0]; + _c[1] = input[i, 1] - input[i + 1, 1]; + _d[0] = input[i + 2, 0] - input[i + 3, 0]; + _d[1] = input[i + 2, 1] - input[i + 3, 1]; + input[i, 0] = _a[0] + _b[0]; + input[i, 1] = _a[1] + _b[1]; + input[i + 2, 0] = _a[0] - _b[0]; + input[i + 2, 1] = _a[1] - _b[1]; + + _e1[0] = _c[0] - _d[1]; + _e1[1] = _c[1] + _d[0]; + _e2[0] = _c[0] + _d[1]; + _e2[1] = _c[1] - _d[0]; + if (forward) + { + input[i + 1, 0] = _e2[0]; + input[i + 1, 1] = _e2[1]; + input[i + 3, 0] = _e1[0]; + input[i + 3, 1] = _e1[1]; + } + else + { + input[i + 1, 0] = _e1[0]; + input[i + 1, 1] = _e1[1]; + input[i + 3, 0] = _e2[0]; + input[i + 3, 1] = _e2[1]; + } + } + + //iterations from bottom to top + int shift, m, km; + float rootRe, rootIm, zRe, zIm; + for (int i = 4; i < _length; i <<= 1) + { + shift = i << 1; + m = _length / shift; + for (int j = 0; j < _length; j += shift) + { + for (int k = 0; k < i; k++) + { + km = k * m; + rootRe = _roots[km, 0]; + rootIm = _roots[km, imOff]; + zRe = input[i + j + k, 0] * rootRe - input[i + j + k, 1] * rootIm; + zIm = input[i + j + k, 0] * rootIm + input[i + j + k, 1] * rootRe; + + input[i + j + k, 0] = (input[j + k, 0] - zRe) * scale; + input[i + j + k, 1] = (input[j + k, 1] - zIm) * scale; + input[j + k, 0] = (input[j + k, 0] + zRe) * scale; + input[j + k, 1] = (input[j + k, 1] + zIm) * scale; + } + } + } + } + } +} diff --git a/SharpJaad.AAC/Filterbank/FFTables.cs b/SharpJaad.AAC/Filterbank/FFTables.cs new file mode 100644 index 0000000..4c3afea --- /dev/null +++ b/SharpJaad.AAC/Filterbank/FFTables.cs @@ -0,0 +1,1130 @@ +namespace SharpJaad.AAC.Filterbank +{ + public static class FFTables + { + public static float[,] FFT_TABLE_512 = new float[,] { + {1.0f, 0.0f, 0.0f}, + { 0.9999247f, 0.012271538f, -0.012271538f }, + { 0.9996989f, 0.024541229f, -0.024541229f }, + { 0.9993224f, 0.036807224f, -0.036807224f }, + { 0.9987955f, 0.049067676f, -0.049067676f }, + { 0.9981182f, 0.061320737f, -0.061320737f }, + { 0.99729055f, 0.07356457f, -0.07356457f }, + { 0.9963127f, 0.08579732f, -0.08579732f }, + { 0.99518484f, 0.09801715f, -0.09801715f }, + { 0.9939071f, 0.11022222f, -0.11022222f }, + { 0.9924797f, 0.12241069f, -0.12241069f }, + { 0.9909028f, 0.13458073f, -0.13458073f }, + { 0.98917663f, 0.1467305f, -0.1467305f }, + { 0.9873016f, 0.15885818f, -0.15885818f }, + {0.98527783f, 0.17096192f, -0.17096192f}, + {0.9831057f, 0.18303992f, -0.18303992f}, + {0.9807855f, 0.19509035f, -0.19509035f}, + {0.97831756f, 0.2071114f, -0.2071114f}, + {0.9757023f, 0.21910128f, -0.21910128f}, + {0.97294015f, 0.23105815f, -0.23105815f}, + {0.97003144f, 0.24298023f, -0.24298023f}, + {0.9669767f, 0.2548657f, -0.2548657f}, + {0.96377635f, 0.2667128f, -0.2667128f}, + {0.96043086f, 0.27851975f, -0.27851975f}, + {0.9569407f, 0.29028472f, -0.29028472f}, + {0.95330644f, 0.302006f, -0.302006f}, + {0.9495286f, 0.3136818f, -0.3136818f}, + {0.9456077f, 0.32531038f, -0.32531038f}, + {0.9415445f, 0.33688995f, -0.33688995f}, + {0.9373394f, 0.3484188f, -0.3484188f}, + {0.93299323f, 0.35989517f, -0.35989517f}, + {0.92850655f, 0.37131733f, -0.37131733f}, + {0.92388f, 0.38268358f, -0.38268358f}, + {0.9191143f, 0.3939922f, -0.3939922f}, + {0.9142102f, 0.4052415f, -0.4052415f}, + {0.9091684f, 0.41642973f, -0.41642973f}, + {0.9039898f, 0.42755526f, -0.42755526f}, + {0.89867496f, 0.43861642f, -0.43861642f}, + {0.89322484f, 0.4496115f, -0.4496115f}, + {0.8876402f, 0.4605389f, -0.4605389f}, + {0.8819218f, 0.47139695f, -0.47139695f}, + {0.8760707f, 0.482184f, -0.482184f}, + {0.8700876f, 0.49289843f, -0.49289843f}, + {0.8639735f, 0.50353867f, -0.50353867f}, + {0.85772926f, 0.51410306f, -0.51410306f}, + {0.85135585f, 0.52459f, -0.52459f}, + {0.84485424f, 0.53499794f, -0.53499794f}, + {0.83822536f, 0.5453253f, -0.5453253f}, + {0.83147025f, 0.55557054f, -0.55557054f}, + {0.82458997f, 0.5657321f, -0.5657321f}, + {0.8175855f, 0.57580847f, -0.57580847f}, + {0.8104579f, 0.58579814f, -0.58579814f}, + {0.80320823f, 0.5956996f, -0.5956996f}, + {0.79583764f, 0.60551137f, -0.60551137f}, + {0.7883472f, 0.61523193f, -0.61523193f}, + {0.780738f, 0.62485987f, -0.62485987f}, + {0.7730112f, 0.6343937f, -0.6343937f}, + {0.7651681f, 0.64383197f, -0.64383197f}, + {0.75720966f, 0.6531733f, -0.6531733f}, + {0.7491372f, 0.6624163f, -0.6624163f}, + {0.74095196f, 0.67155945f, -0.67155945f}, + {0.7326551f, 0.68060154f, -0.68060154f}, + {0.72424793f, 0.6895411f, -0.6895411f}, + {0.7157317f, 0.69837683f, -0.69837683f}, + {0.70710766f, 0.70710737f, -0.70710737f}, + {0.69837713f, 0.71573144f, -0.71573144f}, + {0.68954146f, 0.7242477f, -0.7242477f}, + {0.6806019f, 0.73265487f, -0.73265487f}, + {0.6715598f, 0.7409518f, -0.7409518f}, + {0.66241664f, 0.74913704f, -0.74913704f}, + {0.6531737f, 0.75720954f, -0.75720954f}, + {0.6438324f, 0.765168f, -0.765168f}, + {0.6343941f, 0.77301127f, -0.77301127f}, + {0.62486035f, 0.78073806f, -0.78073806f}, + {0.61523247f, 0.7883473f, -0.7883473f}, + {0.6055119f, 0.79583776f, -0.79583776f}, + {0.59570014f, 0.8032084f, -0.8032084f}, + {0.58579874f, 0.8104581f, -0.8104581f}, + {0.57580906f, 0.81758577f, -0.81758577f}, + {0.5657327f, 0.82459027f, -0.82459027f}, + {0.5555711f, 0.8314706f, -0.8314706f}, + {0.5453258f, 0.8382257f, -0.8382257f}, + {0.5349984f, 0.8448546f, -0.8448546f}, + {0.52459043f, 0.85135627f, -0.85135627f}, + {0.5141035f, 0.85772973f, -0.85772973f}, + {0.50353914f, 0.86397403f, -0.86397403f}, + {0.49289894f, 0.8700882f, -0.8700882f}, + {0.48218453f, 0.87607133f, -0.87607133f}, + {0.4713975f, 0.88192254f, -0.88192254f}, + {0.46053946f, 0.8876409f, -0.8876409f}, + {0.44961208f, 0.8932256f, -0.8932256f}, + {0.43861696f, 0.8986758f, -0.8986758f}, + {0.4275558f, 0.9039906f, -0.9039906f}, + {0.41643026f, 0.9091693f, -0.9091693f}, + {0.405242f, 0.91421115f, -0.91421115f}, + {0.3939927f, 0.91911525f, -0.91911525f}, + {0.38268408f, 0.92388093f, -0.92388093f}, + {0.37131783f, 0.9285075f, -0.9285075f}, + {0.35989568f, 0.93299425f, -0.93299425f}, + {0.3484193f, 0.9373405f, -0.9373405f}, + {0.33689046f, 0.94154555f, -0.94154555f}, + {0.3253109f, 0.94560885f, -0.94560885f}, + {0.31368232f, 0.94952977f, -0.94952977f}, + {0.3020065f, 0.9533077f, -0.9533077f}, + {0.29028523f, 0.956942f, -0.956942f}, + {0.27852023f, 0.96043223f, -0.96043223f}, + {0.26671326f, 0.9637778f, -0.9637778f}, + {0.25486615f, 0.96697825f, -0.96697825f}, + {0.24298064f, 0.97003305f, -0.97003305f}, + {0.23105855f, 0.97294176f, -0.97294176f}, + {0.21910167f, 0.97570395f, -0.97570395f}, + {0.20711178f, 0.9783192f, -0.9783192f}, + {0.19509071f, 0.98078716f, -0.98078716f}, + {0.18304025f, 0.9831074f, -0.9831074f}, + {0.17096223f, 0.98527956f, -0.98527956f}, + {0.15885846f, 0.9873034f, -0.9873034f}, + {0.14673077f, 0.9891785f, -0.9891785f}, + {0.13458098f, 0.9909046f, -0.9909046f}, + {0.12241093f, 0.9924815f, -0.9924815f}, + {0.11022244f, 0.99390894f, -0.99390894f}, + {0.09801734f, 0.99518675f, -0.99518675f}, + {0.085797496f, 0.99631464f, -0.99631464f}, + {0.07356472f, 0.9972925f, -0.9972925f}, + {0.061320875f, 0.9981202f, -0.9981202f}, + {0.049067788f, 0.99879754f, -0.99879754f}, + {0.03680731f, 0.9993245f, -0.9993245f}, + {0.024541289f, 0.99970096f, -0.99970096f}, + {0.012271572f, 0.99992687f, -0.99992687f}, + {7.4505806E-9f, 1.0000021f, -1.0000021f}, + {-0.012271557f, 0.99992687f, -0.99992687f}, + {-0.024541274f, 0.999701f, -0.999701f}, + {-0.036807295f, 0.99932456f, -0.99932456f}, + {-0.049067773f, 0.99879766f, -0.99879766f}, + {-0.06132086f, 0.99812037f, -0.99812037f}, + {-0.073564716f, 0.9972927f, -0.9972927f}, + {-0.085797496f, 0.9963148f, -0.9963148f}, + {-0.09801735f, 0.995187f, -0.995187f}, + {-0.11022245f, 0.99390924f, -0.99390924f}, + {-0.122410946f, 0.9924818f, -0.9924818f}, + {-0.13458101f, 0.9909049f, -0.9909049f}, + {-0.14673081f, 0.9891788f, -0.9891788f}, + {-0.15885851f, 0.98730373f, -0.98730373f}, + {-0.17096227f, 0.98528f, -0.98528f}, + {-0.1830403f, 0.98310786f, -0.98310786f}, + {-0.19509077f, 0.98078763f, -0.98078763f}, + {-0.20711185f, 0.9783197f, -0.9783197f}, + {-0.21910176f, 0.97570443f, -0.97570443f}, + {-0.23105866f, 0.9729423f, -0.9729423f}, + {-0.24298076f, 0.9700336f, -0.9700336f}, + {-0.25486627f, 0.96697885f, -0.96697885f}, + {-0.2667134f, 0.9637785f, -0.9637785f}, + {-0.27852038f, 0.96043295f, -0.96043295f}, + {-0.29028538f, 0.9569428f, -0.9569428f}, + {-0.3020067f, 0.95330846f, -0.95330846f}, + {-0.31368253f, 0.9495306f, -0.9495306f}, + {-0.32531112f, 0.94560975f, -0.94560975f}, + {-0.33689073f, 0.9415465f, -0.9415465f}, + {-0.34841958f, 0.93734145f, -0.93734145f}, + {-0.35989597f, 0.93299526f, -0.93299526f}, + {-0.37131816f, 0.9285086f, -0.9285086f}, + {-0.38268444f, 0.923882f, -0.923882f}, + {-0.39399308f, 0.9191163f, -0.9191163f}, + {-0.40524238f, 0.9142122f, -0.9142122f}, + {-0.41643065f, 0.90917045f, -0.90917045f}, + {-0.42755622f, 0.90399176f, -0.90399176f}, + {-0.4386174f, 0.89867693f, -0.89867693f}, + {-0.44961253f, 0.89322674f, -0.89322674f}, + {-0.46053994f, 0.8876421f, -0.8876421f}, + {-0.471398f, 0.88192374f, -0.88192374f}, + {-0.48218507f, 0.8760726f, -0.8760726f}, + {-0.49289954f, 0.87008953f, -0.87008953f}, + {-0.50353974f, 0.8639754f, -0.8639754f}, + {-0.5141041f, 0.85773116f, -0.85773116f}, + {-0.52459115f, 0.85135776f, -0.85135776f}, + {-0.5349991f, 0.84485614f, -0.84485614f}, + {-0.5453265f, 0.8382273f, -0.8382273f}, + {-0.55557173f, 0.83147216f, -0.83147216f}, + {-0.5657333f, 0.8245919f, -0.8245919f}, + {-0.5758097f, 0.81758744f, -0.81758744f}, + {-0.58579946f, 0.8104598f, -0.8104598f}, + {-0.5957009f, 0.8032101f, -0.8032101f}, + {-0.60551274f, 0.7958395f, -0.7958395f}, + {-0.6152333f, 0.78834903f, -0.78834903f}, + {-0.62486124f, 0.7807398f, -0.7807398f}, + {-0.63439506f, 0.773013f, -0.773013f}, + {-0.6438334f, 0.7651698f, -0.7651698f}, + {-0.65317476f, 0.7572114f, -0.7572114f}, + {-0.6624177f, 0.74913895f, -0.74913895f}, + {-0.6715609f, 0.7409537f, -0.7409537f}, + {-0.68060297f, 0.73265684f, -0.73265684f}, + {-0.68954253f, 0.72424966f, -0.72424966f}, + {-0.69837826f, 0.71573335f, -0.71573335f}, + {-0.70710886f, 0.7071093f, -0.7071093f}, + {-0.71573293f, 0.69837874f, -0.69837874f}, + {-0.72424924f, 0.689543f, -0.689543f}, + {-0.7326565f, 0.68060344f, -0.68060344f}, + {-0.7409534f, 0.67156136f, -0.67156136f}, + {-0.7491387f, 0.6624182f, -0.6624182f}, + {-0.7572112f, 0.65317523f, -0.65317523f}, + {-0.7651697f, 0.64383394f, -0.64383394f}, + {-0.77301294f, 0.63439566f, -0.63439566f}, + {-0.7807398f, 0.62486184f, -0.62486184f}, + {-0.78834903f, 0.61523396f, -0.61523396f}, + {-0.79583955f, 0.6055134f, -0.6055134f}, + {-0.8032102f, 0.59570163f, -0.59570163f}, + {-0.8104599f, 0.5858002f, -0.5858002f}, + {-0.81758755f, 0.5758105f, -0.5758105f}, + {-0.82459205f, 0.5657341f, -0.5657341f}, + {-0.83147246f, 0.55557245f, -0.55557245f}, + {-0.8382276f, 0.5453272f, -0.5453272f}, + {-0.8448565f, 0.5349998f, -0.5349998f}, + {-0.8513582f, 0.5245918f, -0.5245918f}, + {-0.85773164f, 0.5141048f, -0.5141048f}, + {-0.86397594f, 0.5035404f, -0.5035404f}, + {-0.8700901f, 0.49290016f, -0.49290016f}, + {-0.87607324f, 0.48218572f, -0.48218572f}, + {-0.88192445f, 0.47139865f, -0.47139865f}, + {-0.88764286f, 0.4605406f, -0.4605406f}, + {-0.8932276f, 0.44961318f, -0.44961318f}, + {-0.89867777f, 0.43861806f, -0.43861806f}, + {-0.90399265f, 0.42755687f, -0.42755687f}, + {-0.90917134f, 0.4164313f, -0.4164313f}, + {-0.9142132f, 0.40524304f, -0.40524304f}, + {-0.9191173f, 0.3939937f, -0.3939937f}, + {-0.92388296f, 0.38268507f, -0.38268507f}, + {-0.92850953f, 0.3713188f, -0.3713188f}, + {-0.9329963f, 0.3598966f, -0.3598966f}, + {-0.9373425f, 0.3484202f, -0.3484202f}, + {-0.94154763f, 0.33689135f, -0.33689135f}, + {-0.94561094f, 0.32531175f, -0.32531175f}, + {-0.94953185f, 0.31368315f, -0.31368315f}, + {-0.9533098f, 0.30200732f, -0.30200732f}, + {-0.9569441f, 0.290286f, -0.290286f}, + {-0.9604343f, 0.27852097f, -0.27852097f}, + {-0.9637799f, 0.26671398f, -0.26671398f}, + {-0.9669804f, 0.25486684f, -0.25486684f}, + {-0.97003525f, 0.24298131f, -0.24298131f}, + {-0.972944f, 0.2310592f, -0.2310592f}, + {-0.9757062f, 0.21910228f, -0.21910228f}, + {-0.9783215f, 0.20711237f, -0.20711237f}, + {-0.9807894f, 0.19509128f, -0.19509128f}, + {-0.98310965f, 0.18304078f, -0.18304078f}, + {-0.9852818f, 0.17096274f, -0.17096274f}, + {-0.98730564f, 0.15885894f, -0.15885894f}, + {-0.98918074f, 0.14673121f, -0.14673121f}, + {-0.9909069f, 0.1345814f, -0.1345814f}, + {-0.9924838f, 0.12241132f, -0.12241132f}, + {-0.9939112f, 0.1102228f, -0.1102228f}, + {-0.995189f, 0.098017685f, -0.098017685f}, + {-0.9963169f, 0.08579781f, -0.08579781f}, + {-0.9972948f, 0.073565006f, -0.073565006f}, + {-0.99812245f, 0.06132113f, -0.06132113f}, + {-0.9987998f, 0.049068015f, -0.049068015f}, + {-0.99932677f, 0.036807507f, -0.036807507f}, + {-0.9997032f, 0.02454146f, -0.02454146f}, + {-0.99992913f, 0.012271715f, -0.012271715f}, + {-1.0000044f, 1.2293458E-7f, -1.2293458E-7f}, + {-0.99992913f, -0.012271469f, 0.012271469f}, + {-0.9997033f, -0.024541214f, 0.024541214f}, + {-0.9993268f, -0.03680726f, 0.03680726f}, + {-0.9987999f, -0.049067765f, 0.049067765f}, + {-0.99812263f, -0.061320882f, 0.061320882f}, + {-0.99729496f, -0.07356477f, 0.07356477f}, + {-0.9963171f, -0.08579758f, 0.08579758f}, + {-0.99518925f, -0.09801746f, 0.09801746f}, + {-0.9939115f, -0.110222585f, 0.110222585f}, + {-0.9924841f, -0.12241111f, 0.12241111f}, + {-0.9909072f, -0.1345812f, 0.1345812f}, + {-0.98918104f, -0.14673102f, 0.14673102f}, + {-0.987306f, -0.15885875f, 0.15885875f}, + {-0.98528224f, -0.17096254f, 0.17096254f}, + {-0.98311013f, -0.18304059f, 0.18304059f}, + {-0.9807899f, -0.19509108f, 0.19509108f}, + {-0.97832197f, -0.2071122f, 0.2071122f}, + {-0.9757067f, -0.21910211f, 0.21910211f}, + {-0.97294456f, -0.23105904f, 0.23105904f}, + {-0.97003585f, -0.24298118f, 0.24298118f}, + {-0.96698105f, -0.25486672f, 0.25486672f}, + {-0.96378064f, -0.26671386f, 0.26671386f}, + {-0.9604351f, -0.27852085f, 0.27852085f}, + {-0.95694494f, -0.2902859f, 0.2902859f}, + {-0.9533106f, -0.30200723f, 0.30200723f}, + {-0.94953275f, -0.31368306f, 0.31368306f}, + {-0.9456119f, -0.3253117f, 0.3253117f}, + {-0.94154865f, -0.3368913f, 0.3368913f}, + {-0.9373436f, -0.34842017f, 0.34842017f}, + {-0.93299735f, -0.3598966f, 0.3598966f}, + {-0.92851067f, -0.37131882f, 0.37131882f}, + {-0.9238841f, -0.38268512f, 0.38268512f}, + {-0.9191184f, -0.3939938f, 0.3939938f}, + {-0.9142143f, -0.40524313f, 0.40524313f}, + {-0.90917253f, -0.41643143f, 0.41643143f}, + {-0.90399384f, -0.42755702f, 0.42755702f}, + {-0.898679f, -0.43861824f, 0.43861824f}, + {-0.8932288f, -0.4496134f, 0.4496134f}, + {-0.8876442f, -0.46054083f, 0.46054083f}, + {-0.8819258f, -0.47139892f, 0.47139892f}, + {-0.8760746f, -0.48218602f, 0.48218602f}, + {-0.8700915f, -0.4929005f, 0.4929005f}, + {-0.8639774f, -0.50354075f, 0.50354075f}, + {-0.85773313f, -0.5141052f, 0.5141052f}, + {-0.8513597f, -0.5245922f, 0.5245922f}, + {-0.8448581f, -0.5350002f, 0.5350002f}, + {-0.83822924f, -0.5453276f, 0.5453276f}, + {-0.8314741f, -0.5555729f, 0.5555729f}, + {-0.8245938f, -0.56573457f, 0.56573457f}, + {-0.8175893f, -0.57581097f, 0.57581097f}, + {-0.81046164f, -0.5858007f, 0.5858007f}, + {-0.8032119f, -0.59570223f, 0.59570223f}, + {-0.7958413f, -0.60551405f, 0.60551405f}, + {-0.78835076f, -0.6152347f, 0.6152347f}, + {-0.7807415f, -0.6248626f, 0.6248626f}, + {-0.7730147f, -0.6343965f, 0.6343965f}, + {-0.7651715f, -0.6438348f, 0.6438348f}, + {-0.7572131f, -0.6531762f, 0.6531762f}, + {-0.7491407f, -0.6624192f, 0.6624192f}, + {-0.7409554f, -0.67156243f, 0.67156243f}, + {-0.7326585f, -0.6806046f, 0.6806046f}, + {-0.72425133f, -0.68954414f, 0.68954414f}, + {-0.715735f, -0.6983799f, 0.6983799f}, + {-0.70711094f, -0.70711046f, 0.70711046f}, + {-0.69838035f, -0.7157346f, 0.7157346f}, + {-0.6895446f, -0.7242509f, 0.7242509f}, + {-0.68060505f, -0.73265815f, 0.73265815f}, + {-0.67156297f, -0.7409551f, 0.7409551f}, + {-0.6624198f, -0.74914044f, 0.74914044f}, + {-0.6531768f, -0.75721294f, 0.75721294f}, + {-0.6438354f, -0.7651714f, 0.7651714f}, + {-0.63439715f, -0.77301466f, 0.77301466f}, + {-0.6248633f, -0.7807415f, 0.7807415f}, + {-0.6152354f, -0.78835076f, 0.78835076f}, + {-0.6055148f, -0.7958413f, 0.7958413f}, + {-0.595703f, -0.803212f, 0.803212f}, + {-0.58580154f, -0.81046176f, 0.81046176f}, + {-0.5758118f, -0.8175894f, 0.8175894f}, + {-0.5657354f, -0.8245939f, 0.8245939f}, + {-0.55557376f, -0.8314743f, 0.8314743f}, + {-0.54532844f, -0.8382295f, 0.8382295f}, + {-0.535001f, -0.84485835f, 0.84485835f}, + {-0.524593f, -0.85136f, 0.85136f}, + {-0.514106f, -0.8577335f, 0.8577335f}, + {-0.5035416f, -0.8639778f, 0.8639778f}, + {-0.49290136f, -0.870092f, 0.870092f}, + {-0.48218688f, -0.87607515f, 0.87607515f}, + {-0.47139978f, -0.8819264f, 0.8819264f}, + {-0.4605417f, -0.8876448f, 0.8876448f}, + {-0.44961426f, -0.89322954f, 0.89322954f}, + {-0.4386191f, -0.8986798f, 0.8986798f}, + {-0.42755792f, -0.9039947f, 0.9039947f}, + {-0.41643232f, -0.9091734f, 0.9091734f}, + {-0.40524402f, -0.91421527f, 0.91421527f}, + {-0.3939947f, -0.9191194f, 0.9191194f}, + {-0.38268602f, -0.92388517f, 0.92388517f}, + {-0.3713197f, -0.92851174f, 0.92851174f}, + {-0.3598975f, -0.9329985f, 0.9329985f}, + {-0.34842107f, -0.9373448f, 0.9373448f}, + {-0.3368922f, -0.9415499f, 0.9415499f}, + {-0.32531255f, -0.9456132f, 0.9456132f}, + {-0.31368393f, -0.9495341f, 0.9495341f}, + {-0.3020081f, -0.95331204f, 0.95331204f}, + {-0.29028675f, -0.9569464f, 0.9569464f}, + {-0.2785217f, -0.9604366f, 0.9604366f}, + {-0.26671466f, -0.9637822f, 0.9637822f}, + {-0.2548675f, -0.96698266f, 0.96698266f}, + {-0.24298194f, -0.9700375f, 0.9700375f}, + {-0.23105979f, -0.9729463f, 0.9729463f}, + {-0.21910286f, -0.9757085f, 0.9757085f}, + {-0.20711292f, -0.97832376f, 0.97832376f}, + {-0.1950918f, -0.9807917f, 0.9807917f}, + {-0.18304129f, -0.9831119f, 0.9831119f}, + {-0.17096321f, -0.9852841f, 0.9852841f}, + {-0.15885939f, -0.9873079f, 0.9873079f}, + {-0.14673163f, -0.989183f, 0.989183f}, + {-0.13458179f, -0.99090916f, 0.99090916f}, + {-0.122411676f, -0.99248606f, 0.99248606f}, + {-0.11022313f, -0.9939135f, 0.9939135f}, + {-0.09801798f, -0.9951913f, 0.9951913f}, + {-0.08579808f, -0.9963192f, 0.9963192f}, + {-0.073565245f, -0.99729705f, 0.99729705f}, + {-0.06132134f, -0.9981247f, 0.9981247f}, + {-0.049068198f, -0.99880207f, 0.99880207f}, + {-0.036807664f, -0.99932903f, 0.99932903f}, + {-0.024541587f, -0.9997055f, 0.9997055f}, + {-0.012271815f, -0.9999314f, 0.9999314f}, + {-1.9464642E-7f, -1.0000067f, 1.0000067f}, + {0.012271426f, -0.9999314f, 0.9999314f}, + {0.0245412f, -0.99970555f, 0.99970555f}, + {0.036807276f, -0.9993291f, 0.9993291f}, + {0.04906781f, -0.9988022f, 0.9988022f}, + {0.061320953f, -0.9981249f, 0.9981249f}, + {0.073564865f, -0.9972972f, 0.9972972f}, + {0.0857977f, -0.99631935f, 0.99631935f}, + {0.09801761f, -0.9951915f, 0.9951915f}, + {0.110222764f, -0.99391377f, 0.99391377f}, + {0.12241132f, -0.99248636f, 0.99248636f}, + {0.13458143f, -0.99090946f, 0.99090946f}, + {0.14673129f, -0.9891833f, 0.9891833f}, + {0.15885904f, -0.98730826f, 0.98730826f}, + {0.17096287f, -0.9852845f, 0.9852845f}, + {0.18304095f, -0.9831124f, 0.9831124f}, + {0.19509147f, -0.98079216f, 0.98079216f}, + {0.20711261f, -0.97832423f, 0.97832423f}, + {0.21910256f, -0.97570896f, 0.97570896f}, + {0.23105952f, -0.9729468f, 0.9729468f}, + {0.24298169f, -0.9700381f, 0.9700381f}, + {0.25486726f, -0.9669833f, 0.9669833f}, + {0.26671442f, -0.9637829f, 0.9637829f}, + {0.27852145f, -0.96043736f, 0.96043736f}, + {0.2902865f, -0.95694715f, 0.95694715f}, + {0.30200788f, -0.9533128f, 0.9533128f}, + {0.31368375f, -0.94953495f, 0.94953495f}, + {0.3253124f, -0.9456141f, 0.9456141f}, + {0.33689204f, -0.94155085f, 0.94155085f}, + {0.34842095f, -0.9373458f, 0.9373458f}, + {0.3598974f, -0.93299955f, 0.93299955f}, + {0.37131965f, -0.9285129f, 0.9285129f}, + {0.382686f, -0.9238863f, 0.9238863f}, + {0.3939947f, -0.9191206f, 0.9191206f}, + {0.40524405f, -0.91421646f, 0.91421646f}, + {0.41643238f, -0.9091746f, 0.9091746f}, + {0.427558f, -0.90399593f, 0.90399593f}, + {0.43861923f, -0.89868104f, 0.89868104f}, + {0.4496144f, -0.89323086f, 0.89323086f}, + {0.46054187f, -0.88764614f, 0.88764614f}, + {0.4714f, -0.8819278f, 0.8819278f}, + {0.48218712f, -0.8760766f, 0.8760766f}, + {0.49290162f, -0.87009346f, 0.87009346f}, + {0.5035419f, -0.8639793f, 0.8639793f}, + {0.51410633f, -0.85773504f, 0.85773504f}, + {0.52459335f, -0.85136163f, 0.85136163f}, + {0.53500134f, -0.84486f, 0.84486f}, + {0.5453288f, -0.83823115f, 0.83823115f}, + {0.5555741f, -0.831476f, 0.831476f}, + {0.56573576f, -0.82459563f, 0.82459563f}, + {0.5758122f, -0.81759113f, 0.81759113f}, + {0.58580196f, -0.8104634f, 0.8104634f}, + {0.5957035f, -0.8032137f, 0.8032137f}, + {0.6055153f, -0.79584306f, 0.79584306f}, + {0.6152359f, -0.78835255f, 0.78835255f}, + {0.6248639f, -0.7807433f, 0.7807433f}, + {0.6343978f, -0.7730165f, 0.7730165f}, + {0.64383614f, -0.7651733f, 0.7651733f}, + {0.65317756f, -0.7572149f, 0.7572149f}, + {0.6624206f, -0.7491424f, 0.7491424f}, + {0.6715638f, -0.7409571f, 0.7409571f}, + {0.68060595f, -0.7326602f, 0.7326602f}, + {0.6895456f, -0.72425294f, 0.72425294f}, + {0.69838136f, -0.7157366f, 0.7157366f}, + {0.70711195f, -0.70711255f, 0.70711255f}, + {0.7157361f, -0.69838196f, 0.69838196f}, + {0.7242524f, -0.6895462f, 0.6895462f}, + {0.73265964f, -0.6806066f, 0.6806066f}, + {0.7409566f, -0.67156446f, 0.67156446f}, + {0.74914193f, -0.6624212f, 0.6624212f}, + {0.7572145f, -0.6531782f, 0.6531782f}, + {0.765173f, -0.64383686f, 0.64383686f}, + {0.77301633f, -0.6343985f, 0.6343985f}, + {0.7807432f, -0.6248647f, 0.6248647f}, + {0.7883525f, -0.61523676f, 0.61523676f}, + {0.795843f, -0.60551614f, 0.60551614f}, + {0.8032137f, -0.5957043f, 0.5957043f}, + {0.8104635f, -0.58580285f, 0.58580285f}, + {0.81759113f, -0.5758131f, 0.5758131f}, + {0.8245957f, -0.56573665f, 0.56573665f}, + {0.8314761f, -0.55557495f, 0.55557495f}, + {0.83823127f, -0.54532963f, 0.54532963f}, + {0.8448602f, -0.5350022f, 0.5350022f}, + {0.8513619f, -0.5245941f, 0.5245941f}, + {0.8577354f, -0.5141071f, 0.5141071f}, + {0.86397976f, -0.50354266f, 0.50354266f}, + {0.87009394f, -0.4929024f, 0.4929024f}, + {0.8760771f, -0.4821879f, 0.4821879f}, + {0.8819284f, -0.4714008f, 0.4714008f}, + {0.8876468f, -0.46054268f, 0.46054268f}, + {0.8932316f, -0.44961524f, 0.44961524f}, + {0.8986818f, -0.43862006f, 0.43862006f}, + {0.9039967f, -0.42755884f, 0.42755884f}, + {0.90917546f, -0.41643322f, 0.41643322f}, + {0.9142173f, -0.4052449f, 0.4052449f}, + {0.91912144f, -0.39399552f, 0.39399552f}, + {0.9238872f, -0.38268682f, 0.38268682f}, + {0.92851377f, -0.3713205f, 0.3713205f}, + {0.9330005f, -0.35989824f, 0.35989824f}, + {0.9373468f, -0.3484218f, 0.3484218f}, + {0.9415519f, -0.3368929f, 0.3368929f}, + {0.94561523f, -0.32531324f, 0.32531324f}, + {0.94953614f, -0.31368458f, 0.31368458f}, + {0.95331407f, -0.30200872f, 0.30200872f}, + {0.9569484f, -0.29028735f, 0.29028735f}, + {0.9604386f, -0.27852228f, 0.27852228f}, + {0.9637842f, -0.26671523f, 0.26671523f}, + {0.9669847f, -0.25486803f, 0.25486803f}, + {0.97003955f, -0.24298245f, 0.24298245f}, + {0.9729483f, -0.23106027f, 0.23106027f}, + {0.9757105f, -0.2191033f, 0.2191033f}, + {0.9783258f, -0.20711334f, 0.20711334f}, + {0.9807937f, -0.19509219f, 0.19509219f}, + {0.98311394f, -0.18304165f, 0.18304165f}, + {0.9852861f, -0.17096354f, 0.17096354f}, + {0.98730993f, -0.15885969f, 0.15885969f}, + {0.98918504f, -0.14673191f, 0.14673191f}, + {0.9909112f, -0.13458204f, 0.13458204f}, + {0.9924881f, -0.12241191f, 0.12241191f}, + {0.9939155f, -0.11022334f, 0.11022334f}, + {0.9951933f, -0.09801817f, 0.09801817f}, + {0.9963212f, -0.08579824f, 0.08579824f}, + {0.9972991f, -0.073565386f, 0.073565386f}, + {0.99812675f, -0.061321456f, 0.061321456f}, + {0.9988041f, -0.04906829f, 0.04906829f}, + {0.99933106f, -0.03680773f, 0.03680773f}, + {0.9997075f, -0.02454163f, 0.02454163f}, + {0.9999334f, -0.012271833f, 0.012271833f} + }; + public static float[,] FFT_TABLE_64 = { + {1.0f, 0.0f}, + {0.9951847f, 0.09801714f}, + {0.98078525f, 0.19509032f}, + {0.9569403f, 0.2902847f}, + {0.9238795f, 0.38268346f}, + {0.88192123f, 0.47139674f}, + {0.83146954f, 0.55557024f}, + {0.7730104f, 0.6343933f}, + {0.7071067f, 0.70710677f}, + {0.6343932f, 0.77301043f}, + {0.5555701f, 0.8314696f}, + {0.47139663f, 0.88192123f}, + {0.38268334f, 0.92387944f}, + {0.29028457f, 0.95694023f}, + {0.19509023f, 0.9807852f}, + {0.09801706f, 0.9951846f}, + {-6.7055225E-8f, 0.9999999f}, + {-0.09801719f, 0.9951846f}, + {-0.19509035f, 0.98078513f}, + {-0.2902847f, 0.9569402f}, + {-0.38268346f, 0.9238794f}, + {-0.47139674f, 0.8819211f}, + {-0.55557024f, 0.8314694f}, + {-0.6343933f, 0.77301025f}, + {-0.7071067f, 0.7071066f}, + {-0.7730104f, 0.6343931f}, + {-0.83146954f, 0.55557f}, + {-0.8819212f, 0.4713965f}, + {-0.9238794f, 0.38268322f}, + {-0.9569402f, 0.29028445f}, + {-0.98078513f, 0.19509012f}, + {-0.99518454f, 0.09801695f}, + {-0.99999976f, -1.7881393E-7f}, + {-0.9951845f, -0.0980173f}, + {-0.980785f, -0.19509046f}, + {-0.95694005f, -0.29028478f}, + {-0.92387927f, -0.38268352f}, + {-0.881921f, -0.47139677f}, + {-0.8314693f, -0.55557024f}, + {-0.77301013f, -0.6343933f}, + {-0.7071065f, -0.7071067f}, + {-0.634393f, -0.7730104f}, + {-0.5555699f, -0.83146954f}, + {-0.4713964f, -0.8819212f}, + {-0.3826831f, -0.9238794f}, + {-0.29028434f, -0.9569402f}, + {-0.19509f, -0.9807851f}, + {-0.098016836f, -0.9951845f}, + {2.8312206E-7f, -0.9999997f}, + {0.098017395f, -0.9951844f}, + {0.19509055f, -0.98078495f}, + {0.29028487f, -0.95693994f}, + {0.3826836f, -0.92387915f}, + {0.47139686f, -0.8819209f}, + {0.55557036f, -0.8314692f}, + {0.63439333f, -0.77301f}, + {0.70710677f, -0.70710635f}, + {0.7730104f, -0.63439286f}, + {0.8314695f, -0.55556977f}, + {0.88192105f, -0.47139627f}, + {0.92387927f, -0.38268298f}, + {0.95694005f, -0.29028425f}, + {0.98078495f, -0.19508994f}, + {0.99518436f, -0.09801678f} + }; + public static float[,] FFT_TABLE_480 = { + {1.0f, 0.0f, 0.0f}, + {0.99991435f, 0.013089596f, -0.013089596f}, + {0.99965733f, 0.02617695f, -0.02617695f}, + {0.999229f, 0.039259817f, -0.039259817f}, + {0.9986295f, 0.05233596f, -0.05233596f}, + {0.99785894f, 0.06540313f, -0.06540313f}, + {0.99691737f, 0.0784591f, -0.0784591f}, + {0.99580497f, 0.09150162f, -0.09150162f}, + {0.994522f, 0.10452847f, -0.10452847f}, + {0.9930686f, 0.11753741f, -0.11753741f}, + {0.991445f, 0.13052621f, -0.13052621f}, + {0.98965156f, 0.14349265f, -0.14349265f}, + {0.98768854f, 0.1564345f, -0.1564345f}, + {0.9855563f, 0.16934955f, -0.16934955f}, + {0.9832552f, 0.18223558f, -0.18223558f}, + {0.9807856f, 0.1950904f, -0.1950904f}, + {0.978148f, 0.20791179f, -0.20791179f}, + {0.9753427f, 0.22069755f, -0.22069755f}, + {0.97237027f, 0.23344548f, -0.23344548f}, + {0.9692313f, 0.24615341f, -0.24615341f}, + {0.96592623f, 0.25881916f, -0.25881916f}, + {0.9624557f, 0.27144057f, -0.27144057f}, + {0.9588202f, 0.28401548f, -0.28401548f}, + {0.9550204f, 0.29654172f, -0.29654172f}, + {0.951057f, 0.30901715f, -0.30901715f}, + {0.94693065f, 0.32143965f, -0.32143965f}, + {0.94264203f, 0.33380705f, -0.33380705f}, + {0.9381919f, 0.3461173f, -0.3461173f}, + {0.933581f, 0.3583682f, -0.3583682f}, + {0.9288101f, 0.3705577f, -0.3705577f}, + {0.9238801f, 0.3826837f, -0.3826837f}, + {0.9187918f, 0.39474413f, -0.39474413f}, + {0.913546f, 0.40673694f, -0.40673694f}, + {0.90814376f, 0.41866004f, -0.41866004f}, + {0.90258586f, 0.43051142f, -0.43051142f}, + {0.89687335f, 0.44228902f, -0.44228902f}, + {0.8910071f, 0.45399085f, -0.45399085f}, + {0.88498825f, 0.46561489f, -0.46561489f}, + {0.87881774f, 0.47715914f, -0.47715914f}, + {0.87249666f, 0.48862165f, -0.48862165f}, + {0.86602604f, 0.5000004f, -0.5000004f}, + {0.85940707f, 0.51129353f, -0.51129353f}, + {0.8526408f, 0.522499f, -0.522499f}, + {0.8457285f, 0.533615f, -0.533615f}, + {0.83867127f, 0.5446395f, -0.5446395f}, + {0.8314703f, 0.5555707f, -0.5555707f}, + {0.8241269f, 0.5664068f, -0.5664068f}, + {0.8166423f, 0.57714576f, -0.57714576f}, + {0.8090177f, 0.58778584f, -0.58778584f}, + {0.8012545f, 0.5983252f, -0.5983252f}, + {0.7933541f, 0.608762f, -0.608762f}, + {0.7853177f, 0.61909455f, -0.61909455f}, + {0.77714676f, 0.629321f, -0.629321f}, + {0.76884264f, 0.63943964f, -0.63943964f}, + {0.7604068f, 0.6494487f, -0.6494487f}, + {0.75184065f, 0.6593465f, -0.6593465f}, + {0.7431457f, 0.66913134f, -0.66913134f}, + {0.7343234f, 0.6788015f, -0.6788015f}, + {0.72537524f, 0.6883554f, -0.6883554f}, + {0.7163028f, 0.6977913f, -0.6977913f}, + {0.70710766f, 0.7071076f, -0.7071076f}, + {0.69779134f, 0.7163028f, -0.7163028f}, + {0.68835545f, 0.7253753f, -0.7253753f}, + {0.67880166f, 0.7343235f, -0.7343235f}, + {0.6691315f, 0.7431459f, -0.7431459f}, + {0.6593467f, 0.7518409f, -0.7518409f}, + {0.64944893f, 0.7604071f, -0.7604071f}, + {0.6394399f, 0.768843f, -0.768843f}, + {0.6293213f, 0.7771471f, -0.7771471f}, + {0.61909485f, 0.7853181f, -0.7853181f}, + {0.6087623f, 0.7933545f, -0.7933545f}, + {0.5983255f, 0.801255f, -0.801255f}, + {0.58778614f, 0.8090182f, -0.8090182f}, + {0.57714605f, 0.81664276f, -0.81664276f}, + {0.56640714f, 0.8241274f, -0.8241274f}, + {0.55557114f, 0.83147085f, -0.83147085f}, + {0.54463995f, 0.8386718f, -0.8386718f}, + {0.5336154f, 0.8457291f, -0.8457291f}, + {0.52249944f, 0.8526415f, -0.8526415f}, + {0.51129395f, 0.85940784f, -0.85940784f}, + {0.50000083f, 0.8660269f, -0.8660269f}, + {0.48862207f, 0.87249756f, -0.87249756f}, + {0.4771596f, 0.8788187f, -0.8788187f}, + {0.46561536f, 0.88498926f, -0.88498926f}, + {0.45399132f, 0.89100814f, -0.89100814f}, + {0.4422895f, 0.8968744f, -0.8968744f}, + {0.4305119f, 0.902587f, -0.902587f}, + {0.41866052f, 0.9081449f, -0.9081449f}, + {0.40673742f, 0.9135472f, -0.9135472f}, + {0.3947446f, 0.91879296f, -0.91879296f}, + {0.38268417f, 0.92388135f, -0.92388135f}, + {0.37055814f, 0.9288114f, -0.9288114f}, + {0.35836864f, 0.93358225f, -0.93358225f}, + {0.34611773f, 0.93819314f, -0.93819314f}, + {0.3338075f, 0.94264334f, -0.94264334f}, + {0.3214401f, 0.94693196f, -0.94693196f}, + {0.3090176f, 0.9510583f, -0.9510583f}, + {0.29654217f, 0.95502174f, -0.95502174f}, + {0.28401592f, 0.9588216f, -0.9588216f}, + {0.271441f, 0.9624571f, -0.9624571f}, + {0.25881958f, 0.9659277f, -0.9659277f}, + {0.2461538f, 0.96923286f, -0.96923286f}, + {0.23344585f, 0.9723719f, -0.9723719f}, + {0.2206979f, 0.9753443f, -0.9753443f}, + {0.20791212f, 0.9781496f, -0.9781496f}, + {0.19509073f, 0.9807873f, -0.9807873f}, + {0.18223591f, 0.98325694f, -0.98325694f}, + {0.16934988f, 0.9855581f, -0.9855581f}, + {0.15643482f, 0.9876904f, -0.9876904f}, + {0.14349295f, 0.98965347f, -0.98965347f}, + {0.1305265f, 0.991447f, -0.991447f}, + {0.117537685f, 0.9930706f, -0.9930706f}, + {0.104528725f, 0.99452406f, -0.99452406f}, + {0.09150185f, 0.9958071f, -0.9958071f}, + {0.07845929f, 0.9969195f, -0.9969195f}, + {0.0654033f, 0.9978611f, -0.9978611f}, + {0.052336097f, 0.9986317f, -0.9986317f}, + {0.03925993f, 0.9992312f, -0.9992312f}, + {0.026177032f, 0.99965954f, -0.99965954f}, + {0.013089649f, 0.99991655f, -0.99991655f}, + {2.4214387E-8f, 1.0000023f, -1.0000023f}, + {-0.013089602f, 0.9999166f, -0.9999166f}, + {-0.026176985f, 0.9996596f, -0.9996596f}, + {-0.039259885f, 0.9992313f, -0.9992313f}, + {-0.052336056f, 0.9986318f, -0.9986318f}, + {-0.06540326f, 0.9978612f, -0.9978612f}, + {-0.078459255f, 0.99691963f, -0.99691963f}, + {-0.09150181f, 0.99580723f, -0.99580723f}, + {-0.10452869f, 0.99452424f, -0.99452424f}, + {-0.117537655f, 0.99307084f, -0.99307084f}, + {-0.13052648f, 0.99144727f, -0.99144727f}, + {-0.14349295f, 0.98965377f, -0.98965377f}, + {-0.15643483f, 0.98769075f, -0.98769075f}, + {-0.16934991f, 0.9855585f, -0.9855585f}, + {-0.18223597f, 0.9832574f, -0.9832574f}, + {-0.19509082f, 0.9807878f, -0.9807878f}, + {-0.20791222f, 0.9781502f, -0.9781502f}, + {-0.220698f, 0.9753449f, -0.9753449f}, + {-0.23344596f, 0.9723725f, -0.9723725f}, + {-0.24615392f, 0.9692335f, -0.9692335f}, + {-0.2588197f, 0.96592844f, -0.96592844f}, + {-0.27144113f, 0.96245784f, -0.96245784f}, + {-0.28401607f, 0.95882237f, -0.95882237f}, + {-0.29654235f, 0.9550226f, -0.9550226f}, + {-0.3090178f, 0.95105916f, -0.95105916f}, + {-0.3214403f, 0.9469328f, -0.9469328f}, + {-0.33380774f, 0.9426441f, -0.9426441f}, + {-0.34611797f, 0.9381939f, -0.9381939f}, + {-0.3583689f, 0.933583f, -0.933583f}, + {-0.37055844f, 0.92881215f, -0.92881215f}, + {-0.38268447f, 0.9238821f, -0.9238821f}, + {-0.39474493f, 0.9187938f, -0.9187938f}, + {-0.40673777f, 0.91354805f, -0.91354805f}, + {-0.4186609f, 0.9081458f, -0.9081458f}, + {-0.4305123f, 0.9025879f, -0.9025879f}, + {-0.44228995f, 0.8968753f, -0.8968753f}, + {-0.4539918f, 0.8910091f, -0.8910091f}, + {-0.46561587f, 0.8849902f, -0.8849902f}, + {-0.47716016f, 0.8788197f, -0.8788197f}, + {-0.4886227f, 0.8724986f, -0.8724986f}, + {-0.5000015f, 0.86602795f, -0.86602795f}, + {-0.5112946f, 0.859409f, -0.859409f}, + {-0.5225001f, 0.8526427f, -0.8526427f}, + {-0.53361607f, 0.84573036f, -0.84573036f}, + {-0.5446406f, 0.8386731f, -0.8386731f}, + {-0.5555718f, 0.83147216f, -0.83147216f}, + {-0.56640786f, 0.82412875f, -0.82412875f}, + {-0.5771468f, 0.81664413f, -0.81664413f}, + {-0.587787f, 0.80901957f, -0.80901957f}, + {-0.5983263f, 0.80125636f, -0.80125636f}, + {-0.6087632f, 0.7933559f, -0.7933559f}, + {-0.6190958f, 0.78531945f, -0.78531945f}, + {-0.6293223f, 0.7771484f, -0.7771484f}, + {-0.63944095f, 0.76884425f, -0.76884425f}, + {-0.64945006f, 0.76040834f, -0.76040834f}, + {-0.6593479f, 0.75184214f, -0.75184214f}, + {-0.66913277f, 0.7431472f, -0.7431472f}, + {-0.6788029f, 0.7343249f, -0.7343249f}, + {-0.6883568f, 0.7253767f, -0.7253767f}, + {-0.69779277f, 0.7163043f, -0.7163043f}, + {-0.7071091f, 0.70710915f, -0.70710915f}, + {-0.7163043f, 0.6977928f, -0.6977928f}, + {-0.7253768f, 0.68835694f, -0.68835694f}, + {-0.734325f, 0.6788031f, -0.6788031f}, + {-0.7431474f, 0.66913295f, -0.66913295f}, + {-0.7518424f, 0.65934813f, -0.65934813f}, + {-0.7604086f, 0.64945036f, -0.64945036f}, + {-0.7688445f, 0.6394413f, -0.6394413f}, + {-0.77714866f, 0.62932265f, -0.62932265f}, + {-0.7853197f, 0.6190962f, -0.6190962f}, + {-0.7933561f, 0.60876364f, -0.60876364f}, + {-0.80125666f, 0.59832674f, -0.59832674f}, + {-0.8090199f, 0.58778733f, -0.58778733f}, + {-0.8166445f, 0.57714725f, -0.57714725f}, + {-0.82412916f, 0.5664083f, -0.5664083f}, + {-0.83147264f, 0.5555722f, -0.5555722f}, + {-0.83867365f, 0.544641f, -0.544641f}, + {-0.84573096f, 0.5336164f, -0.5336164f}, + {-0.8526434f, 0.52250046f, -0.52250046f}, + {-0.8594097f, 0.51129496f, -0.51129496f}, + {-0.8660287f, 0.50000185f, -0.50000185f}, + {-0.8724994f, 0.48862305f, -0.48862305f}, + {-0.87882054f, 0.47716054f, -0.47716054f}, + {-0.8849911f, 0.4656163f, -0.4656163f}, + {-0.89101005f, 0.45399225f, -0.45399225f}, + {-0.89687634f, 0.4422904f, -0.4422904f}, + {-0.9025889f, 0.43051276f, -0.43051276f}, + {-0.90814686f, 0.41866136f, -0.41866136f}, + {-0.9135492f, 0.40673822f, -0.40673822f}, + {-0.918795f, 0.39474538f, -0.39474538f}, + {-0.9238834f, 0.38268492f, -0.38268492f}, + {-0.9288134f, 0.37055886f, -0.37055886f}, + {-0.9335843f, 0.35836932f, -0.35836932f}, + {-0.93819517f, 0.3461184f, -0.3461184f}, + {-0.9426454f, 0.33380815f, -0.33380815f}, + {-0.94693404f, 0.32144073f, -0.32144073f}, + {-0.9510605f, 0.3090182f, -0.3090182f}, + {-0.95502394f, 0.29654273f, -0.29654273f}, + {-0.9588238f, 0.28401646f, -0.28401646f}, + {-0.9624593f, 0.27144152f, -0.27144152f}, + {-0.9659299f, 0.25882006f, -0.25882006f}, + {-0.96923506f, 0.24615425f, -0.24615425f}, + {-0.9723741f, 0.23344627f, -0.23344627f}, + {-0.9753465f, 0.22069828f, -0.22069828f}, + {-0.9781518f, 0.20791247f, -0.20791247f}, + {-0.9807895f, 0.19509105f, -0.19509105f}, + {-0.98325914f, 0.18223621f, -0.18223621f}, + {-0.9855603f, 0.16935015f, -0.16935015f}, + {-0.9876926f, 0.15643506f, -0.15643506f}, + {-0.9896557f, 0.14349316f, -0.14349316f}, + {-0.9914492f, 0.13052668f, -0.13052668f}, + {-0.9930728f, 0.117537834f, -0.117537834f}, + {-0.99452627f, 0.104528844f, -0.104528844f}, + {-0.9958093f, 0.091501944f, -0.091501944f}, + {-0.9969217f, 0.07845937f, -0.07845937f}, + {-0.9978633f, 0.06540334f, -0.06540334f}, + {-0.9986339f, 0.05233611f, -0.05233611f}, + {-0.9992334f, 0.039259914f, -0.039259914f}, + {-0.99966174f, 0.02617699f, -0.02617699f}, + {-0.99991876f, 0.013089578f, -0.013089578f}, + {-1.0000044f, -7.636845E-8f, 7.636845E-8f}, + {-0.99991876f, -0.01308973f, 0.01308973f}, + {-0.99966174f, -0.026177142f, 0.026177142f}, + {-0.9992334f, -0.039260067f, 0.039260067f}, + {-0.9986339f, -0.052336264f, 0.052336264f}, + {-0.99786335f, -0.0654035f, 0.0654035f}, + {-0.9969218f, -0.07845952f, 0.07845952f}, + {-0.9958094f, -0.09150211f, 0.09150211f}, + {-0.9945263f, -0.10452901f, 0.10452901f}, + {-0.9930729f, -0.117538f, 0.117538f}, + {-0.99144936f, -0.13052686f, 0.13052686f}, + {-0.98965585f, -0.14349335f, 0.14349335f}, + {-0.98769283f, -0.15643525f, 0.15643525f}, + {-0.9855606f, -0.16935036f, 0.16935036f}, + {-0.98325944f, -0.18223645f, 0.18223645f}, + {-0.98078984f, -0.19509132f, 0.19509132f}, + {-0.9781522f, -0.20791276f, 0.20791276f}, + {-0.9753469f, -0.22069857f, 0.22069857f}, + {-0.9723745f, -0.23344655f, 0.23344655f}, + {-0.96923554f, -0.24615455f, 0.24615455f}, + {-0.96593046f, -0.25882035f, 0.25882035f}, + {-0.96245986f, -0.27144182f, 0.27144182f}, + {-0.95882434f, -0.2840168f, 0.2840168f}, + {-0.95502454f, -0.2965431f, 0.2965431f}, + {-0.9510611f, -0.30901858f, 0.30901858f}, + {-0.9469347f, -0.3214411f, 0.3214411f}, + {-0.942646f, -0.33380857f, 0.33380857f}, + {-0.9381958f, -0.34611884f, 0.34611884f}, + {-0.9335849f, -0.3583698f, 0.3583698f}, + {-0.928814f, -0.37055936f, 0.37055936f}, + {-0.923884f, -0.38268542f, 0.38268542f}, + {-0.91879565f, -0.39474592f, 0.39474592f}, + {-0.9135499f, -0.4067388f, 0.4067388f}, + {-0.9081476f, -0.41866195f, 0.41866195f}, + {-0.9025897f, -0.43051338f, 0.43051338f}, + {-0.8968771f, -0.44229105f, 0.44229105f}, + {-0.8910109f, -0.45399293f, 0.45399293f}, + {-0.884992f, -0.465617f, 0.465617f}, + {-0.87882143f, -0.47716132f, 0.47716132f}, + {-0.8725003f, -0.4886239f, 0.4886239f}, + {-0.8660297f, -0.50000274f, 0.50000274f}, + {-0.8594107f, -0.5112959f, 0.5112959f}, + {-0.85264444f, -0.52250147f, 0.52250147f}, + {-0.8457321f, -0.5336175f, 0.5336175f}, + {-0.83867484f, -0.5446421f, 0.5446421f}, + {-0.8314739f, -0.55557334f, 0.55557334f}, + {-0.8241304f, -0.5664094f, 0.5664094f}, + {-0.8166458f, -0.57714844f, 0.57714844f}, + {-0.8090212f, -0.5877886f, 0.5877886f}, + {-0.80125797f, -0.598328f, 0.598328f}, + {-0.7933575f, -0.6087649f, 0.6087649f}, + {-0.78532106f, -0.6190975f, 0.6190975f}, + {-0.77715003f, -0.62932396f, 0.62932396f}, + {-0.76884586f, -0.6394427f, 0.6394427f}, + {-0.76040995f, -0.6494518f, 0.6494518f}, + {-0.75184375f, -0.6593496f, 0.6593496f}, + {-0.74314874f, -0.6691345f, 0.6691345f}, + {-0.73432636f, -0.6788047f, 0.6788047f}, + {-0.7253782f, -0.6883586f, 0.6883586f}, + {-0.7163058f, -0.69779456f, 0.69779456f}, + {-0.7071106f, -0.70711094f, 0.70711094f}, + {-0.6977942f, -0.71630615f, 0.71630615f}, + {-0.68835825f, -0.72537863f, 0.72537863f}, + {-0.6788044f, -0.73432684f, 0.73432684f}, + {-0.66913426f, -0.7431492f, 0.7431492f}, + {-0.6593494f, -0.7518443f, 0.7518443f}, + {-0.6494516f, -0.76041055f, 0.76041055f}, + {-0.63944256f, -0.76884645f, 0.76884645f}, + {-0.6293239f, -0.77715063f, 0.77715063f}, + {-0.6190974f, -0.78532165f, 0.78532165f}, + {-0.6087648f, -0.7933581f, 0.7933581f}, + {-0.59832793f, -0.8012586f, 0.8012586f}, + {-0.5877885f, -0.8090219f, 0.8090219f}, + {-0.5771484f, -0.81664646f, 0.81664646f}, + {-0.5664094f, -0.82413113f, 0.82413113f}, + {-0.55557334f, -0.8314746f, 0.8314746f}, + {-0.5446421f, -0.8386756f, 0.8386756f}, + {-0.5336175f, -0.8457329f, 0.8457329f}, + {-0.52250147f, -0.85264534f, 0.85264534f}, + {-0.5112959f, -0.85941166f, 0.85941166f}, + {-0.50000274f, -0.8660307f, 0.8660307f}, + {-0.48862392f, -0.8725014f, 0.8725014f}, + {-0.47716138f, -0.8788225f, 0.8788225f}, + {-0.4656171f, -0.8849931f, 0.8849931f}, + {-0.45399302f, -0.891012f, 0.891012f}, + {-0.44229114f, -0.8968783f, 0.8968783f}, + {-0.4305135f, -0.9025909f, 0.9025909f}, + {-0.41866207f, -0.9081488f, 0.9081488f}, + {-0.4067389f, -0.91355115f, 0.91355115f}, + {-0.39474607f, -0.91879696f, 0.91879696f}, + {-0.38268557f, -0.92388535f, 0.92388535f}, + {-0.3705595f, -0.92881536f, 0.92881536f}, + {-0.35836995f, -0.93358624f, 0.93358624f}, + {-0.346119f, -0.9381972f, 0.9381972f}, + {-0.33380872f, -0.9426474f, 0.9426474f}, + {-0.32144126f, -0.9469361f, 0.9469361f}, + {-0.3090187f, -0.9510625f, 0.9510625f}, + {-0.2965432f, -0.955026f, 0.955026f}, + {-0.2840169f, -0.9588258f, 0.9588258f}, + {-0.27144194f, -0.96246135f, 0.96246135f}, + {-0.25882047f, -0.965932f, 0.965932f}, + {-0.24615464f, -0.96923715f, 0.96923715f}, + {-0.23344663f, -0.97237617f, 0.97237617f}, + {-0.22069862f, -0.97534865f, 0.97534865f}, + {-0.2079128f, -0.97815394f, 0.97815394f}, + {-0.19509135f, -0.9807916f, 0.9807916f}, + {-0.18223648f, -0.9832613f, 0.9832613f}, + {-0.16935039f, -0.98556244f, 0.98556244f}, + {-0.15643527f, -0.9876948f, 0.9876948f}, + {-0.14349334f, -0.9896579f, 0.9896579f}, + {-0.13052683f, -0.9914514f, 0.9914514f}, + {-0.11753795f, -0.993075f, 0.993075f}, + {-0.104528934f, -0.9945285f, 0.9945285f}, + {-0.091502f, -0.9958115f, 0.9958115f}, + {-0.0784594f, -0.9969239f, 0.9969239f}, + {-0.06540334f, -0.9978655f, 0.9978655f}, + {-0.05233608f, -0.9986361f, 0.9986361f}, + {-0.03925986f, -0.99923563f, 0.99923563f}, + {-0.026176903f, -0.99966395f, 0.99966395f}, + {-0.013089463f, -0.99992096f, 0.99992096f}, + {2.1979213E-7f, -1.0000067f, 1.0000067f}, + {0.013089904f, -0.999921f, 0.999921f}, + {0.026177345f, -0.999664f, 0.999664f}, + {0.0392603f, -0.9992357f, 0.9992357f}, + {0.05233653f, -0.9986362f, 0.9986362f}, + {0.06540379f, -0.9978656f, 0.9978656f}, + {0.078459844f, -0.99692404f, 0.99692404f}, + {0.09150246f, -0.99581164f, 0.99581164f}, + {0.104529396f, -0.9945286f, 0.9945286f}, + {0.117538415f, -0.9930752f, 0.9930752f}, + {0.1305273f, -0.9914516f, 0.9914516f}, + {0.14349383f, -0.9896581f, 0.9896581f}, + {0.15643576f, -0.9876951f, 0.9876951f}, + {0.16935089f, -0.98556286f, 0.98556286f}, + {0.18223701f, -0.9832617f, 0.9832617f}, + {0.19509192f, -0.98079205f, 0.98079205f}, + {0.20791338f, -0.97815436f, 0.97815436f}, + {0.22069922f, -0.97534907f, 0.97534907f}, + {0.23344724f, -0.97237664f, 0.97237664f}, + {0.24615526f, -0.9692376f, 0.9692376f}, + {0.2588211f, -0.96593255f, 0.96593255f}, + {0.2714426f, -0.96246195f, 0.96246195f}, + {0.2840176f, -0.9588264f, 0.9588264f}, + {0.29654393f, -0.9550266f, 0.9550266f}, + {0.30901945f, -0.9510632f, 0.9510632f}, + {0.321442f, -0.9469368f, 0.9469368f}, + {0.3338095f, -0.9426481f, 0.9426481f}, + {0.3461198f, -0.9381979f, 0.9381979f}, + {0.35837078f, -0.933587f, 0.933587f}, + {0.37056035f, -0.9288161f, 0.9288161f}, + {0.38268644f, -0.923886f, 0.923886f}, + {0.39474696f, -0.9187976f, 0.9187976f}, + {0.40673983f, -0.91355187f, 0.91355187f}, + {0.41866302f, -0.90814954f, 0.90814954f}, + {0.43051448f, -0.90259165f, 0.90259165f}, + {0.44229218f, -0.8968791f, 0.8968791f}, + {0.4539941f, -0.89101285f, 0.89101285f}, + {0.4656182f, -0.884994f, 0.884994f}, + {0.47716254f, -0.8788234f, 0.8788234f}, + {0.4886251f, -0.87250227f, 0.87250227f}, + {0.500004f, -0.86603165f, 0.86603165f}, + {0.51129717f, -0.85941267f, 0.85941267f}, + {0.5225027f, -0.8526464f, 0.8526464f}, + {0.53361875f, -0.84573406f, 0.84573406f}, + {0.54464334f, -0.8386768f, 0.8386768f}, + {0.5555746f, -0.83147585f, 0.83147585f}, + {0.5664107f, -0.8241324f, 0.8241324f}, + {0.57714975f, -0.8166477f, 0.8166477f}, + {0.58778995f, -0.8090231f, 0.8090231f}, + {0.59832937f, -0.8012598f, 0.8012598f}, + {0.60876626f, -0.79335934f, 0.79335934f}, + {0.61909884f, -0.7853229f, 0.7853229f}, + {0.62932533f, -0.7771519f, 0.7771519f}, + {0.63944405f, -0.7688477f, 0.7688477f}, + {0.64945316f, -0.7604118f, 0.7604118f}, + {0.65935105f, -0.7518456f, 0.7518456f}, + {0.669136f, -0.7431506f, 0.7431506f}, + {0.6788062f, -0.7343282f, 0.7343282f}, + {0.68836015f, -0.72538f, 0.72538f}, + {0.69779617f, -0.7163075f, 0.7163075f}, + {0.70711255f, -0.7071123f, 0.7071123f}, + {0.7163078f, -0.6977959f, 0.6977959f}, + {0.72538036f, -0.68836f, 0.68836f}, + {0.7343286f, -0.67880607f, 0.67880607f}, + {0.74315107f, -0.66913587f, 0.66913587f}, + {0.75184613f, -0.659351f, 0.659351f}, + {0.7604124f, -0.64945316f, 0.64945316f}, + {0.7688483f, -0.63944405f, 0.63944405f}, + {0.7771525f, -0.6293254f, 0.6293254f}, + {0.7853235f, -0.6190989f, 0.6190989f}, + {0.79335994f, -0.60876626f, 0.60876626f}, + {0.8012605f, -0.59832937f, 0.59832937f}, + {0.80902374f, -0.58778995f, 0.58778995f}, + {0.81664836f, -0.5771498f, 0.5771498f}, + {0.82413304f, -0.5664108f, 0.5664108f}, + {0.83147657f, -0.5555747f, 0.5555747f}, + {0.8386776f, -0.54464346f, 0.54464346f}, + {0.84573495f, -0.53361887f, 0.53361887f}, + {0.85264736f, -0.52250284f, 0.52250284f}, + {0.8594137f, -0.5112973f, 0.5112973f}, + {0.8660327f, -0.5000041f, 0.5000041f}, + {0.8725034f, -0.48862526f, 0.48862526f}, + {0.8788246f, -0.4771627f, 0.4771627f}, + {0.88499516f, -0.46561837f, 0.46561837f}, + {0.8910141f, -0.45399427f, 0.45399427f}, + {0.8968804f, -0.44229236f, 0.44229236f}, + {0.90259296f, -0.4305147f, 0.4305147f}, + {0.9081509f, -0.41866326f, 0.41866326f}, + {0.91355324f, -0.40674007f, 0.40674007f}, + {0.91879904f, -0.3947472f, 0.3947472f}, + {0.92388743f, -0.38268667f, 0.38268667f}, + {0.9288175f, -0.3705606f, 0.3705606f}, + {0.93358845f, -0.358371f, 0.358371f}, + {0.9381994f, -0.34612f, 0.34612f}, + {0.9426496f, -0.3338097f, 0.3338097f}, + {0.9469383f, -0.32144222f, 0.32144222f}, + {0.9510647f, -0.30901963f, 0.30901963f}, + {0.9550282f, -0.2965441f, 0.2965441f}, + {0.95882803f, -0.28401777f, 0.28401777f}, + {0.96246356f, -0.27144277f, 0.27144277f}, + {0.9659342f, -0.25882128f, 0.25882128f}, + {0.96923935f, -0.24615541f, 0.24615541f}, + {0.9723784f, -0.23344737f, 0.23344737f}, + {0.97535086f, -0.22069934f, 0.22069934f}, + {0.97815615f, -0.20791349f, 0.20791349f}, + {0.98079383f, -0.19509201f, 0.19509201f}, + {0.98326355f, -0.1822371f, 0.1822371f}, + {0.98556477f, -0.16935098f, 0.16935098f}, + {0.9876971f, -0.15643583f, 0.15643583f}, + {0.9896602f, -0.14349388f, 0.14349388f}, + {0.9914537f, -0.13052733f, 0.13052733f}, + {0.99307734f, -0.11753843f, 0.11753843f}, + {0.9945308f, -0.10452938f, 0.10452938f}, + {0.99581385f, -0.09150242f, 0.09150242f}, + {0.9969263f, -0.078459784f, 0.078459784f}, + {0.9978679f, -0.0654037f, 0.0654037f}, + {0.9986385f, -0.05233641f, 0.05233641f}, + {0.999238f, -0.039260153f, 0.039260153f}, + {0.99966633f, -0.026177168f, 0.026177168f}, + {0.99992335f, -0.013089697f, 0.013089697f} + }; + public static float[,] FFT_TABLE_60 = { + {1.0f, 0.0f}, + {0.9945219f, 0.104528464f}, + {0.9781476f, 0.2079117f}, + {0.95105654f, 0.309017f}, + {0.9135455f, 0.40673664f}, + {0.86602545f, 0.5f}, + {0.80901706f, 0.58778524f}, + {0.7431449f, 0.66913056f}, + {0.66913074f, 0.7431448f}, + {0.58778536f, 0.809017f}, + {0.5000001f, 0.86602545f}, + {0.40673676f, 0.9135455f}, + {0.30901712f, 0.9510566f}, + {0.2079118f, 0.9781477f}, + {0.104528576f, 0.994522f}, + {1.0430813E-7f, 1.0000001f}, + {-0.104528375f, 0.99452204f}, + {-0.20791161f, 0.97814775f}, + {-0.30901694f, 0.95105666f}, + {-0.4067366f, 0.9135456f}, + {-0.5f, 0.86602557f}, + {-0.5877853f, 0.8090172f}, + {-0.6691307f, 0.74314505f}, + {-0.7431449f, 0.66913086f}, + {-0.8090172f, 0.5877855f}, + {-0.8660256f, 0.5000002f}, + {-0.9135457f, 0.4067368f}, + {-0.95105684f, 0.30901712f}, + {-0.9781479f, 0.20791179f}, + {-0.9945222f, 0.10452853f}, + {-1.0000004f, 3.7252903E-8f}, + {-0.9945223f, -0.104528464f}, + {-0.978148f, -0.20791173f}, + {-0.9510569f, -0.3090171f}, + {-0.91354585f, -0.4067368f}, + {-0.8660258f, -0.5000002f}, + {-0.80901736f, -0.5877855f}, + {-0.7431452f, -0.66913086f}, + {-0.66913104f, -0.7431451f}, + {-0.58778566f, -0.80901736f}, + {-0.50000036f, -0.86602587f}, + {-0.40673697f, -0.91354597f}, + {-0.30901727f, -0.9510571f}, + {-0.20791191f, -0.9781482f}, + {-0.10452862f, -0.9945225f}, + {-9.685755E-8f, -1.0000006f}, + {0.10452843f, -0.9945225f}, + {0.20791173f, -0.9781482f}, + {0.30901712f, -0.95105714f}, + {0.40673685f, -0.9135461f}, + {0.5000003f, -0.86602604f}, + {0.5877856f, -0.8090176f}, + {0.669131f, -0.7431454f}, + {0.7431453f, -0.66913116f}, + {0.80901754f, -0.5877858f}, + {0.86602604f, -0.5000005f}, + {0.91354614f, -0.40673706f}, + {0.95105726f, -0.30901736f}, + {0.9781484f, -0.20791197f}, + {0.99452275f, -0.104528666f} + }; + } +} diff --git a/SharpJaad.AAC/Filterbank/FilterBank.cs b/SharpJaad.AAC/Filterbank/FilterBank.cs new file mode 100644 index 0000000..89f2f24 --- /dev/null +++ b/SharpJaad.AAC/Filterbank/FilterBank.cs @@ -0,0 +1,211 @@ +using SharpJaad.AAC.Syntax; +using static SharpJaad.AAC.Syntax.ICSInfo; + +namespace SharpJaad.AAC.Filterbank +{ + public class FilterBank + { + private float[][] _LONG_WINDOWS;// = {SINE_LONG, KBD_LONG}; + private float[][] _SHORT_WINDOWS;// = {SINE_SHORT, KBD_SHORT}; + private int _length; + private int _shortLen; + private int _mid; + private int _trans; + private MDCT _mdctShort, _mdctLong; + private float[] _buf; + private float[][] _overlaps; + + public FilterBank(bool smallFrames, int channels) + { + if (smallFrames) + { + _length = Constants.WINDOW_SMALL_LEN_LONG; + _shortLen = Constants.WINDOW_SMALL_LEN_SHORT; + _LONG_WINDOWS = new float[][] { SineWindows.SINE_960, KBDWindows.KBD_960 }; + _SHORT_WINDOWS = new float[][] { SineWindows.SINE_120, KBDWindows.KBD_120 }; + } + else + { + _length = Constants.WINDOW_LEN_LONG; + _shortLen = Constants.WINDOW_LEN_SHORT; + _LONG_WINDOWS = new float[][] { SineWindows.SINE_1024, KBDWindows.KBD_1024 }; + _SHORT_WINDOWS = new float[][] { SineWindows.SINE_128, KBDWindows.KBD_128 }; + } + _mid = (_length - _shortLen) / 2; + _trans = _shortLen / 2; + + _mdctShort = new MDCT(_shortLen * 2); + _mdctLong = new MDCT(_length * 2); + + _overlaps = new float[channels][]; + for (int i = 0; i < channels; i++) + { + _overlaps[i] = new float[_length]; + } + + _buf = new float[2 * _length]; + } + + public void Process(WindowSequence windowSequence, int windowShape, int windowShapePrev, float[] input, float[] output, int channel) + { + int i; + float[] overlap = _overlaps[channel]; + switch (windowSequence) + { + case WindowSequence.ONLY_LONG_SEQUENCE: + _mdctLong.Process(input, 0, _buf, 0); + //add second half output of previous frame to windowed output of current frame + for (i = 0; i < _length; i++) + { + output[i] = overlap[i] + _buf[i] * _LONG_WINDOWS[windowShapePrev][i]; + } + + //window the second half and save as overlap for next frame + for (i = 0; i < _length; i++) + { + overlap[i] = _buf[_length + i] * _LONG_WINDOWS[windowShape][_length - 1 - i]; + } + break; + case WindowSequence.LONG_START_SEQUENCE: + _mdctLong.Process(input, 0, _buf, 0); + //add second half output of previous frame to windowed output of current frame + for (i = 0; i < _length; i++) + { + output[i] = overlap[i] + _buf[i] * _LONG_WINDOWS[windowShapePrev][i]; + } + + //window the second half and save as overlap for next frame + for (i = 0; i < _mid; i++) + { + overlap[i] = _buf[_length + i]; + } + for (i = 0; i < _shortLen; i++) + { + overlap[_mid + i] = _buf[_length + _mid + i] * _SHORT_WINDOWS[windowShape][_shortLen - i - 1]; + } + for (i = 0; i < _mid; i++) + { + overlap[_mid + _shortLen + i] = 0; + } + break; + case WindowSequence.EIGHT_SHORT_SEQUENCE: + for (i = 0; i < 8; i++) + { + _mdctShort.Process(input, i * _shortLen, _buf, 2 * i * _shortLen); + } + + //add second half output of previous frame to windowed output of current frame + for (i = 0; i < _mid; i++) + { + output[i] = overlap[i]; + } + for (i = 0; i < _shortLen; i++) + { + output[_mid + i] = overlap[_mid + i] + _buf[i] * _SHORT_WINDOWS[windowShapePrev][i]; + output[_mid + 1 * _shortLen + i] = overlap[_mid + _shortLen * 1 + i] + _buf[_shortLen * 1 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 2 + i] * _SHORT_WINDOWS[windowShape][i]; + output[_mid + 2 * _shortLen + i] = overlap[_mid + _shortLen * 2 + i] + _buf[_shortLen * 3 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 4 + i] * _SHORT_WINDOWS[windowShape][i]; + output[_mid + 3 * _shortLen + i] = overlap[_mid + _shortLen * 3 + i] + _buf[_shortLen * 5 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 6 + i] * _SHORT_WINDOWS[windowShape][i]; + if (i < _trans) output[_mid + 4 * _shortLen + i] = overlap[_mid + _shortLen * 4 + i] + _buf[_shortLen * 7 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 8 + i] * _SHORT_WINDOWS[windowShape][i]; + } + + //window the second half and save as overlap for next frame + for (i = 0; i < _shortLen; i++) + { + if (i >= _trans) overlap[_mid + 4 * _shortLen + i - _length] = _buf[_shortLen * 7 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 8 + i] * _SHORT_WINDOWS[windowShape][i]; + overlap[_mid + 5 * _shortLen + i - _length] = _buf[_shortLen * 9 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 10 + i] * _SHORT_WINDOWS[windowShape][i]; + overlap[_mid + 6 * _shortLen + i - _length] = _buf[_shortLen * 11 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 12 + i] * _SHORT_WINDOWS[windowShape][i]; + overlap[_mid + 7 * _shortLen + i - _length] = _buf[_shortLen * 13 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i] + _buf[_shortLen * 14 + i] * _SHORT_WINDOWS[windowShape][i]; + overlap[_mid + 8 * _shortLen + i - _length] = _buf[_shortLen * 15 + i] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i]; + } + for (i = 0; i < _mid; i++) + { + overlap[_mid + _shortLen + i] = 0; + } + break; + case WindowSequence.LONG_STOP_SEQUENCE: + _mdctLong.Process(input, 0, _buf, 0); + //add second half output of previous frame to windowed output of current frame + //construct first half window using padding with 1's and 0's + for (i = 0; i < _mid; i++) + { + output[i] = overlap[i]; + } + for (i = 0; i < _shortLen; i++) + { + output[_mid + i] = overlap[_mid + i] + _buf[_mid + i] * _SHORT_WINDOWS[windowShapePrev][i]; + } + for (i = 0; i < _mid; i++) + { + output[_mid + _shortLen + i] = overlap[_mid + _shortLen + i] + _buf[_mid + _shortLen + i]; + } + //window the second half and save as overlap for next frame + for (i = 0; i < _length; i++) + { + overlap[i] = _buf[_length + i] * _LONG_WINDOWS[windowShape][_length - 1 - i]; + } + break; + } + } + + //only for LTP: no overlapping, no short blocks + public void ProcessLTP(WindowSequence windowSequence, int windowShape, int windowShapePrev, float[] input, float[] output) + { + int i; + + switch (windowSequence) + { + case WindowSequence.ONLY_LONG_SEQUENCE: + for (i = _length - 1; i >= 0; i--) + { + _buf[i] = input[i] * _LONG_WINDOWS[windowShapePrev][i]; + _buf[i + _length] = input[i + _length] * _LONG_WINDOWS[windowShape][_length - 1 - i]; + } + break; + + case WindowSequence.LONG_START_SEQUENCE: + for (i = 0; i < _length; i++) + { + _buf[i] = input[i] * _LONG_WINDOWS[windowShapePrev][i]; + } + for (i = 0; i < _mid; i++) + { + _buf[i + _length] = input[i + _length]; + } + for (i = 0; i < _shortLen; i++) + { + _buf[i + _length + _mid] = input[i + _length + _mid] * _SHORT_WINDOWS[windowShape][_shortLen - 1 - i]; + } + for (i = 0; i < _mid; i++) + { + _buf[i + _length + _mid + _shortLen] = 0; + } + break; + + case WindowSequence.LONG_STOP_SEQUENCE: + for (i = 0; i < _mid; i++) + { + _buf[i] = 0; + } + for (i = 0; i < _shortLen; i++) + { + _buf[i + _mid] = input[i + _mid] * _SHORT_WINDOWS[windowShapePrev][i]; + } + for (i = 0; i < _mid; i++) + { + _buf[i + _mid + _shortLen] = input[i + _mid + _shortLen]; + } + for (i = 0; i < _length; i++) + { + _buf[i + _length] = input[i + _length] * _LONG_WINDOWS[windowShape][_length - 1 - i]; + } + break; + } + _mdctLong.ProcessForward(_buf, output); + } + + public float[] GetOverlap(int channel) + { + return _overlaps[channel]; + } + } +} diff --git a/SharpJaad.AAC/Filterbank/KBDWindows.cs b/SharpJaad.AAC/Filterbank/KBDWindows.cs new file mode 100644 index 0000000..1824835 --- /dev/null +++ b/SharpJaad.AAC/Filterbank/KBDWindows.cs @@ -0,0 +1,2246 @@ +namespace SharpJaad.AAC.Filterbank +{ + public static class KBDWindows + { + public static float[] KBD_1024 = { + 0.00029256153896361f, + 0.00042998567353047f, + 0.00054674074589540f, + 0.00065482304299792f, + 0.00075870195068747f, + 0.00086059331713336f, + 0.00096177541439010f, + 0.0010630609410878f, + 0.0011650036308132f, + 0.0012680012194148f, + 0.0013723517232956f, + 0.0014782864109136f, + 0.0015859901976719f, + 0.0016956148252373f, + 0.0018072876903517f, + 0.0019211179405514f, + 0.0020372007924215f, + 0.0021556206591754f, + 0.0022764534599614f, + 0.0023997683540995f, + 0.0025256290631156f, + 0.0026540948920831f, + 0.0027852215281403f, + 0.0029190616715331f, + 0.0030556655443223f, + 0.0031950812943391f, + 0.0033373553240392f, + 0.0034825325586930f, + 0.0036306566699199f, + 0.0037817702604646f, + 0.0039359150179719f, + 0.0040931318437260f, + 0.0042534609610026f, + 0.0044169420066964f, + 0.0045836141091341f, + 0.0047535159544086f, + 0.0049266858431214f, + 0.0051031617390698f, + 0.0052829813111335f, + 0.0054661819693975f, + 0.0056528008963682f, + 0.0058428750739943f, + 0.0060364413070882f, + 0.0062335362436492f, + 0.0064341963925079f, + 0.0066384581386503f, + 0.0068463577565218f, + 0.0070579314215715f, + 0.0072732152202559f, + 0.0074922451586909f, + 0.0077150571701162f, + 0.0079416871213115f, + 0.0081721708180857f, + 0.0084065440099458f, + 0.0086448423940363f, + 0.0088871016184291f, + 0.0091333572848345f, + 0.0093836449507939f, + 0.0096380001314086f, + 0.0098964583006517f, + 0.010159054892306f, + 0.010425825300561f, + 0.010696804880310f, + 0.010972028947167f, + 0.011251532777236f, + 0.011535351606646f, + 0.011823520630897f, + 0.012116075003993f, + 0.012413049837429f, + 0.012714480198999f, + 0.013020401111478f, + 0.013330847551161f, + 0.013645854446288f, + 0.013965456675352f, + 0.014289689065314f, + 0.014618586389712f, + 0.014952183366697f, + 0.015290514656976f, + 0.015633614861688f, + 0.015981518520214f, + 0.016334260107915f, + 0.016691874033817f, + 0.017054394638241f, + 0.017421856190380f, + 0.017794292885832f, + 0.018171738844085f, + 0.018554228105962f, + 0.018941794631032f, + 0.019334472294980f, + 0.019732294886947f, + 0.020135296106839f, + 0.020543509562604f, + 0.020956968767488f, + 0.021375707137257f, + 0.021799757987407f, + 0.022229154530343f, + 0.022663929872540f, + 0.023104117011689f, + 0.023549748833816f, + 0.024000858110398f, + 0.024457477495451f, + 0.024919639522613f, + 0.025387376602207f, + 0.025860721018295f, + 0.026339704925726f, + 0.026824360347160f, + 0.027314719170100f, + 0.027810813143900f, + 0.028312673876775f, + 0.028820332832801f, + 0.029333821328905f, + 0.029853170531859f, + 0.030378411455255f, + 0.030909574956490f, + 0.031446691733739f, + 0.031989792322926f, + 0.032538907094693f, + 0.033094066251369f, + 0.033655299823935f, + 0.034222637668991f, + 0.034796109465717f, + 0.035375744712844f, + 0.035961572725616f, + 0.036553622632758f, + 0.037151923373446f, + 0.037756503694277f, + 0.038367392146243f, + 0.038984617081711f, + 0.039608206651398f, + 0.040238188801359f, + 0.040874591269976f, + 0.041517441584950f, + 0.042166767060301f, + 0.042822594793376f, + 0.043484951661852f, + 0.044153864320760f, + 0.044829359199509f, + 0.045511462498913f, + 0.046200200188234f, + 0.046895598002228f, + 0.047597681438201f, + 0.048306475753074f, + 0.049022005960455f, + 0.049744296827725f, + 0.050473372873129f, + 0.051209258362879f, + 0.051951977308273f, + 0.052701553462813f, + 0.053458010319350f, + 0.054221371107223f, + 0.054991658789428f, + 0.055768896059787f, + 0.056553105340134f, + 0.057344308777513f, + 0.058142528241393f, + 0.058947785320893f, + 0.059760101322019f, + 0.060579497264926f, + 0.061405993881180f, + 0.062239611611049f, + 0.063080370600799f, + 0.063928290700012f, + 0.064783391458919f, + 0.065645692125747f, + 0.066515211644086f, + 0.067391968650269f, + 0.068275981470777f, + 0.069167268119652f, + 0.070065846295935f, + 0.070971733381121f, + 0.071884946436630f, + 0.072805502201299f, + 0.073733417088896f, + 0.074668707185649f, + 0.075611388247794f, + 0.076561475699152f, + 0.077518984628715f, + 0.078483929788261f, + 0.079456325589986f, + 0.080436186104162f, + 0.081423525056808f, + 0.082418355827392f, + 0.083420691446553f, + 0.084430544593841f, + 0.085447927595483f, + 0.086472852422178f, + 0.087505330686900f, + 0.088545373642744f, + 0.089592992180780f, + 0.090648196827937f, + 0.091710997744919f, + 0.092781404724131f, + 0.093859427187640f, + 0.094945074185163f, + 0.096038354392069f, + 0.097139276107423f, + 0.098247847252041f, + 0.099364075366580f, + 0.10048796760965f, + 0.10161953075597f, + 0.10275877119451f, + 0.10390569492671f, + 0.10506030756469f, + 0.10622261432949f, + 0.10739262004941f, + 0.10857032915821f, + 0.10975574569357f, + 0.11094887329534f, + 0.11214971520402f, + 0.11335827425914f, + 0.11457455289772f, + 0.11579855315274f, + 0.11703027665170f, + 0.11826972461510f, + 0.11951689785504f, + 0.12077179677383f, + 0.12203442136263f, + 0.12330477120008f, + 0.12458284545102f, + 0.12586864286523f, + 0.12716216177615f, + 0.12846340009971f, + 0.12977235533312f, + 0.13108902455375f, + 0.13241340441801f, + 0.13374549116025f, + 0.13508528059173f, + 0.13643276809961f, + 0.13778794864595f, + 0.13915081676677f, + 0.14052136657114f, + 0.14189959174027f, + 0.14328548552671f, + 0.14467904075349f, + 0.14608024981336f, + 0.14748910466804f, + 0.14890559684750f, + 0.15032971744929f, + 0.15176145713790f, + 0.15320080614414f, + 0.15464775426459f, + 0.15610229086100f, + 0.15756440485987f, + 0.15903408475193f, + 0.16051131859170f, + 0.16199609399712f, + 0.16348839814917f, + 0.16498821779156f, + 0.16649553923042f, + 0.16801034833404f, + 0.16953263053270f, + 0.17106237081842f, + 0.17259955374484f, + 0.17414416342714f, + 0.17569618354193f, + 0.17725559732720f, + 0.17882238758238f, + 0.18039653666830f, + 0.18197802650733f, + 0.18356683858343f, + 0.18516295394233f, + 0.18676635319174f, + 0.18837701650148f, + 0.18999492360384f, + 0.19162005379380f, + 0.19325238592940f, + 0.19489189843209f, + 0.19653856928714f, + 0.19819237604409f, + 0.19985329581721f, + 0.20152130528605f, + 0.20319638069594f, + 0.20487849785865f, + 0.20656763215298f, + 0.20826375852540f, + 0.20996685149083f, + 0.21167688513330f, + 0.21339383310678f, + 0.21511766863598f, + 0.21684836451719f, + 0.21858589311922f, + 0.22033022638425f, + 0.22208133582887f, + 0.22383919254503f, + 0.22560376720111f, + 0.22737503004300f, + 0.22915295089517f, + 0.23093749916189f, + 0.23272864382838f, + 0.23452635346201f, + 0.23633059621364f, + 0.23814133981883f, + 0.23995855159925f, + 0.24178219846403f, + 0.24361224691114f, + 0.24544866302890f, + 0.24729141249740f, + 0.24914046059007f, + 0.25099577217522f, + 0.25285731171763f, + 0.25472504328019f, + 0.25659893052556f, + 0.25847893671788f, + 0.26036502472451f, + 0.26225715701781f, + 0.26415529567692f, + 0.26605940238966f, + 0.26796943845439f, + 0.26988536478190f, + 0.27180714189742f, + 0.27373472994256f, + 0.27566808867736f, + 0.27760717748238f, + 0.27955195536071f, + 0.28150238094021f, + 0.28345841247557f, + 0.28542000785059f, + 0.28738712458038f, + 0.28935971981364f, + 0.29133775033492f, + 0.29332117256704f, + 0.29530994257338f, + 0.29730401606034f, + 0.29930334837974f, + 0.30130789453132f, + 0.30331760916521f, + 0.30533244658452f, + 0.30735236074785f, + 0.30937730527195f, + 0.31140723343430f, + 0.31344209817583f, + 0.31548185210356f, + 0.31752644749341f, + 0.31957583629288f, + 0.32162997012390f, + 0.32368880028565f, + 0.32575227775738f, + 0.32782035320134f, + 0.32989297696566f, + 0.33197009908736f, + 0.33405166929523f, + 0.33613763701295f, + 0.33822795136203f, + 0.34032256116495f, + 0.34242141494820f, + 0.34452446094547f, + 0.34663164710072f, + 0.34874292107143f, + 0.35085823023181f, + 0.35297752167598f, + 0.35510074222129f, + 0.35722783841160f, + 0.35935875652060f, + 0.36149344255514f, + 0.36363184225864f, + 0.36577390111444f, + 0.36791956434930f, + 0.37006877693676f, + 0.37222148360070f, + 0.37437762881878f, + 0.37653715682603f, + 0.37870001161834f, + 0.38086613695607f, + 0.38303547636766f, + 0.38520797315322f, + 0.38738357038821f, + 0.38956221092708f, + 0.39174383740701f, + 0.39392839225157f, + 0.39611581767449f, + 0.39830605568342f, + 0.40049904808370f, + 0.40269473648218f, + 0.40489306229101f, + 0.40709396673153f, + 0.40929739083810f, + 0.41150327546197f, + 0.41371156127524f, + 0.41592218877472f, + 0.41813509828594f, + 0.42035022996702f, + 0.42256752381274f, + 0.42478691965848f, + 0.42700835718423f, + 0.42923177591866f, + 0.43145711524314f, + 0.43368431439580f, + 0.43591331247564f, + 0.43814404844658f, + 0.44037646114161f, + 0.44261048926688f, + 0.44484607140589f, + 0.44708314602359f, + 0.44932165147057f, + 0.45156152598727f, + 0.45380270770813f, + 0.45604513466581f, + 0.45828874479543f, + 0.46053347593880f, + 0.46277926584861f, + 0.46502605219277f, + 0.46727377255861f, + 0.46952236445718f, + 0.47177176532752f, + 0.47402191254100f, + 0.47627274340557f, + 0.47852419517009f, + 0.48077620502869f, + 0.48302871012505f, + 0.48528164755674f, + 0.48753495437962f, + 0.48978856761212f, + 0.49204242423966f, + 0.49429646121898f, + 0.49655061548250f, + 0.49880482394273f, + 0.50105902349665f, + 0.50331315103004f, + 0.50556714342194f, + 0.50782093754901f, + 0.51007447028990f, + 0.51232767852971f, + 0.51458049916433f, + 0.51683286910489f, + 0.51908472528213f, + 0.52133600465083f, + 0.52358664419420f, + 0.52583658092832f, + 0.52808575190648f, + 0.53033409422367f, + 0.53258154502092f, + 0.53482804148974f, + 0.53707352087652f, + 0.53931792048690f, + 0.54156117769021f, + 0.54380322992385f, + 0.54604401469766f, + 0.54828346959835f, + 0.55052153229384f, + 0.55275814053768f, + 0.55499323217338f, + 0.55722674513883f, + 0.55945861747062f, + 0.56168878730842f, + 0.56391719289930f, + 0.56614377260214f, + 0.56836846489188f, + 0.57059120836390f, + 0.57281194173835f, + 0.57503060386439f, + 0.57724713372458f, + 0.57946147043912f, + 0.58167355327012f, + 0.58388332162591f, + 0.58609071506528f, + 0.58829567330173f, + 0.59049813620770f, + 0.59269804381879f, + 0.59489533633802f, + 0.59708995413996f, + 0.59928183777495f, + 0.60147092797329f, + 0.60365716564937f, + 0.60584049190582f, + 0.60802084803764f, + 0.61019817553632f, + 0.61237241609393f, + 0.61454351160718f, + 0.61671140418155f, + 0.61887603613527f, + 0.62103735000336f, + 0.62319528854167f, + 0.62534979473088f, + 0.62750081178042f, + 0.62964828313250f, + 0.63179215246597f, + 0.63393236370030f, + 0.63606886099946f, + 0.63820158877577f, + 0.64033049169379f, + 0.64245551467413f, + 0.64457660289729f, + 0.64669370180740f, + 0.64880675711607f, + 0.65091571480603f, + 0.65302052113494f, + 0.65512112263906f, + 0.65721746613689f, + 0.65930949873289f, + 0.66139716782102f, + 0.66348042108842f, + 0.66555920651892f, + 0.66763347239664f, + 0.66970316730947f, + 0.67176824015260f, + 0.67382864013196f, + 0.67588431676768f, + 0.67793521989751f, + 0.67998129968017f, + 0.68202250659876f, + 0.68405879146403f, + 0.68609010541774f, + 0.68811639993588f, + 0.69013762683195f, + 0.69215373826012f, + 0.69416468671849f, + 0.69617042505214f, + 0.69817090645634f, + 0.70016608447958f, + 0.70215591302664f, + 0.70414034636163f, + 0.70611933911096f, + 0.70809284626630f, + 0.71006082318751f, + 0.71202322560554f, + 0.71398000962530f, + 0.71593113172842f, + 0.71787654877613f, + 0.71981621801195f, + 0.72175009706445f, + 0.72367814394990f, + 0.72560031707496f, + 0.72751657523927f, + 0.72942687763803f, + 0.73133118386457f, + 0.73322945391280f, + 0.73512164817975f, + 0.73700772746796f, + 0.73888765298787f, + 0.74076138636020f, + 0.74262888961827f, + 0.74449012521027f, + 0.74634505600152f, + 0.74819364527663f, + 0.75003585674175f, + 0.75187165452661f, + 0.75370100318668f, + 0.75552386770515f, + 0.75734021349500f, + 0.75915000640095f, + 0.76095321270137f, + 0.76274979911019f, + 0.76453973277875f, + 0.76632298129757f, + 0.76809951269819f, + 0.76986929545481f, + 0.77163229848604f, + 0.77338849115651f, + 0.77513784327849f, + 0.77688032511340f, + 0.77861590737340f, + 0.78034456122283f, + 0.78206625827961f, + 0.78378097061667f, + 0.78548867076330f, + 0.78718933170643f, + 0.78888292689189f, + 0.79056943022564f, + 0.79224881607494f, + 0.79392105926949f, + 0.79558613510249f, + 0.79724401933170f, + 0.79889468818046f, + 0.80053811833858f, + 0.80217428696334f, + 0.80380317168028f, + 0.80542475058405f, + 0.80703900223920f, + 0.80864590568089f, + 0.81024544041560f, + 0.81183758642175f, + 0.81342232415032f, + 0.81499963452540f, + 0.81656949894467f, + 0.81813189927991f, + 0.81968681787738f, + 0.82123423755821f, + 0.82277414161874f, + 0.82430651383076f, + 0.82583133844180f, + 0.82734860017528f, + 0.82885828423070f, + 0.83036037628369f, + 0.83185486248609f, + 0.83334172946597f, + 0.83482096432759f, + 0.83629255465130f, + 0.83775648849344f, + 0.83921275438615f, + 0.84066134133716f, + 0.84210223882952f, + 0.84353543682130f, + 0.84496092574524f, + 0.84637869650833f, + 0.84778874049138f, + 0.84919104954855f, + 0.85058561600677f, + 0.85197243266520f, + 0.85335149279457f, + 0.85472279013653f, + 0.85608631890295f, + 0.85744207377513f, + 0.85879004990298f, + 0.86013024290422f, + 0.86146264886346f, + 0.86278726433124f, + 0.86410408632306f, + 0.86541311231838f, + 0.86671434025950f, + 0.86800776855046f, + 0.86929339605590f, + 0.87057122209981f, + 0.87184124646433f, + 0.87310346938840f, + 0.87435789156650f, + 0.87560451414719f, + 0.87684333873173f, + 0.87807436737261f, + 0.87929760257204f, + 0.88051304728038f, + 0.88172070489456f, + 0.88292057925645f, + 0.88411267465117f, + 0.88529699580537f, + 0.88647354788545f, + 0.88764233649580f, + 0.88880336767692f, + 0.88995664790351f, + 0.89110218408260f, + 0.89223998355154f, + 0.89337005407600f, + 0.89449240384793f, + 0.89560704148345f, + 0.89671397602074f, + 0.89781321691786f, + 0.89890477405053f, + 0.89998865770993f, + 0.90106487860034f, + 0.90213344783689f, + 0.90319437694315f, + 0.90424767784873f, + 0.90529336288690f, + 0.90633144479201f, + 0.90736193669708f, + 0.90838485213119f, + 0.90940020501694f, + 0.91040800966776f, + 0.91140828078533f, + 0.91240103345685f, + 0.91338628315231f, + 0.91436404572173f, + 0.91533433739238f, + 0.91629717476594f, + 0.91725257481564f, + 0.91820055488334f, + 0.91914113267664f, + 0.92007432626589f, + 0.92100015408120f, + 0.92191863490944f, + 0.92282978789113f, + 0.92373363251740f, + 0.92463018862687f, + 0.92551947640245f, + 0.92640151636824f, + 0.92727632938624f, + 0.92814393665320f, + 0.92900435969727f, + 0.92985762037477f, + 0.93070374086684f, + 0.93154274367610f, + 0.93237465162328f, + 0.93319948784382f, + 0.93401727578443f, + 0.93482803919967f, + 0.93563180214841f, + 0.93642858899043f, + 0.93721842438279f, + 0.93800133327637f, + 0.93877734091223f, + 0.93954647281807f, + 0.94030875480458f, + 0.94106421296182f, + 0.94181287365556f, + 0.94255476352362f, + 0.94328990947213f, + 0.94401833867184f, + 0.94474007855439f, + 0.94545515680855f, + 0.94616360137644f, + 0.94686544044975f, + 0.94756070246592f, + 0.94824941610434f, + 0.94893161028248f, + 0.94960731415209f, + 0.95027655709525f, + 0.95093936872056f, + 0.95159577885924f, + 0.95224581756115f, + 0.95288951509097f, + 0.95352690192417f, + 0.95415800874314f, + 0.95478286643320f, + 0.95540150607863f, + 0.95601395895871f, + 0.95662025654373f, + 0.95722043049100f, + 0.95781451264084f, + 0.95840253501260f, + 0.95898452980058f, + 0.95956052937008f, + 0.96013056625336f, + 0.96069467314557f, + 0.96125288290073f, + 0.96180522852773f, + 0.96235174318622f, + 0.96289246018262f, + 0.96342741296604f, + 0.96395663512424f, + 0.96448016037959f, + 0.96499802258499f, + 0.96551025571985f, + 0.96601689388602f, + 0.96651797130376f, + 0.96701352230768f, + 0.96750358134269f, + 0.96798818295998f, + 0.96846736181297f, + 0.96894115265327f, + 0.96940959032667f, + 0.96987270976912f, + 0.97033054600270f, + 0.97078313413161f, + 0.97123050933818f, + 0.97167270687887f, + 0.97210976208030f, + 0.97254171033525f, + 0.97296858709871f, + 0.97339042788392f, + 0.97380726825843f, + 0.97421914384017f, + 0.97462609029350f, + 0.97502814332534f, + 0.97542533868127f, + 0.97581771214160f, + 0.97620529951759f, + 0.97658813664749f, + 0.97696625939282f, + 0.97733970363445f, + 0.97770850526884f, + 0.97807270020427f, + 0.97843232435704f, + 0.97878741364771f, + 0.97913800399743f, + 0.97948413132414f, + 0.97982583153895f, + 0.98016314054243f, + 0.98049609422096f, + 0.98082472844313f, + 0.98114907905608f, + 0.98146918188197f, + 0.98178507271438f, + 0.98209678731477f, + 0.98240436140902f, + 0.98270783068385f, + 0.98300723078342f, + 0.98330259730589f, + 0.98359396579995f, + 0.98388137176152f, + 0.98416485063031f, + 0.98444443778651f, + 0.98472016854752f, + 0.98499207816463f, + 0.98526020181980f, + 0.98552457462240f, + 0.98578523160609f, + 0.98604220772560f, + 0.98629553785362f, + 0.98654525677772f, + 0.98679139919726f, + 0.98703399972035f, + 0.98727309286089f, + 0.98750871303556f, + 0.98774089456089f, + 0.98796967165036f, + 0.98819507841154f, + 0.98841714884323f, + 0.98863591683269f, + 0.98885141615285f, + 0.98906368045957f, + 0.98927274328896f, + 0.98947863805473f, + 0.98968139804554f, + 0.98988105642241f, + 0.99007764621618f, + 0.99027120032501f, + 0.99046175151186f, + 0.99064933240208f, + 0.99083397548099f, + 0.99101571309153f, + 0.99119457743191f, + 0.99137060055337f, + 0.99154381435784f, + 0.99171425059582f, + 0.99188194086414f, + 0.99204691660388f, + 0.99220920909823f, + 0.99236884947045f, + 0.99252586868186f, + 0.99268029752989f, + 0.99283216664606f, + 0.99298150649419f, + 0.99312834736847f, + 0.99327271939167f, + 0.99341465251338f, + 0.99355417650825f, + 0.99369132097430f, + 0.99382611533130f, + 0.99395858881910f, + 0.99408877049612f, + 0.99421668923778f, + 0.99434237373503f, + 0.99446585249289f, + 0.99458715382906f, + 0.99470630587254f, + 0.99482333656229f, + 0.99493827364600f, + 0.99505114467878f, + 0.99516197702200f, + 0.99527079784214f, + 0.99537763410962f, + 0.99548251259777f, + 0.99558545988178f, + 0.99568650233767f, + 0.99578566614138f, + 0.99588297726783f, + 0.99597846149005f, + 0.99607214437834f, + 0.99616405129947f, + 0.99625420741595f, + 0.99634263768527f, + 0.99642936685928f, + 0.99651441948352f, + 0.99659781989663f, + 0.99667959222978f, + 0.99675976040620f, + 0.99683834814063f, + 0.99691537893895f, + 0.99699087609774f, + 0.99706486270391f, + 0.99713736163442f, + 0.99720839555593f, + 0.99727798692461f, + 0.99734615798589f, + 0.99741293077431f, + 0.99747832711337f, + 0.99754236861541f, + 0.99760507668158f, + 0.99766647250181f, + 0.99772657705478f, + 0.99778541110799f, + 0.99784299521785f, + 0.99789934972976f, + 0.99795449477828f, + 0.99800845028730f, + 0.99806123597027f, + 0.99811287133042f, + 0.99816337566108f, + 0.99821276804596f, + 0.99826106735952f, + 0.99830829226732f, + 0.99835446122649f, + 0.99839959248609f, + 0.99844370408765f, + 0.99848681386566f, + 0.99852893944805f, + 0.99857009825685f, + 0.99861030750869f, + 0.99864958421549f, + 0.99868794518504f, + 0.99872540702178f, + 0.99876198612738f, + 0.99879769870160f, + 0.99883256074295f, + 0.99886658804953f, + 0.99889979621983f, + 0.99893220065356f, + 0.99896381655254f, + 0.99899465892154f, + 0.99902474256924f, + 0.99905408210916f, + 0.99908269196056f, + 0.99911058634952f, + 0.99913777930986f, + 0.99916428468421f, + 0.99919011612505f, + 0.99921528709576f, + 0.99923981087174f, + 0.99926370054150f, + 0.99928696900779f, + 0.99930962898876f, + 0.99933169301910f, + 0.99935317345126f, + 0.99937408245662f, + 0.99939443202674f, + 0.99941423397457f, + 0.99943349993572f, + 0.99945224136972f, + 0.99947046956130f, + 0.99948819562171f, + 0.99950543049000f, + 0.99952218493439f, + 0.99953846955355f, + 0.99955429477803f, + 0.99956967087154f, + 0.99958460793242f, + 0.99959911589494f, + 0.99961320453077f, + 0.99962688345035f, + 0.99964016210433f, + 0.99965304978499f, + 0.99966555562769f, + 0.99967768861231f, + 0.99968945756473f, + 0.99970087115825f, + 0.99971193791510f, + 0.99972266620792f, + 0.99973306426121f, + 0.99974314015288f, + 0.99975290181568f, + 0.99976235703876f, + 0.99977151346914f, + 0.99978037861326f, + 0.99978895983845f, + 0.99979726437448f, + 0.99980529931507f, + 0.99981307161943f, + 0.99982058811377f, + 0.99982785549283f, + 0.99983488032144f, + 0.99984166903600f, + 0.99984822794606f, + 0.99985456323584f, + 0.99986068096572f, + 0.99986658707386f, + 0.99987228737764f, + 0.99987778757524f, + 0.99988309324717f, + 0.99988820985777f, + 0.99989314275675f, + 0.99989789718072f, + 0.99990247825468f, + 0.99990689099357f, + 0.99991114030376f, + 0.99991523098456f, + 0.99991916772971f, + 0.99992295512891f, + 0.99992659766930f, + 0.99993009973692f, + 0.99993346561824f, + 0.99993669950161f, + 0.99993980547870f, + 0.99994278754604f, + 0.99994564960642f, + 0.99994839547033f, + 0.99995102885747f, + 0.99995355339809f, + 0.99995597263451f, + 0.99995829002249f, + 0.99996050893264f, + 0.99996263265183f, + 0.99996466438460f, + 0.99996660725452f, + 0.99996846430558f, + 0.99997023850356f, + 0.99997193273736f, + 0.99997354982037f, + 0.99997509249183f, + 0.99997656341810f, + 0.99997796519400f, + 0.99997930034415f, + 0.99998057132421f, + 0.99998178052220f, + 0.99998293025975f, + 0.99998402279338f, + 0.99998506031574f, + 0.99998604495686f, + 0.99998697878536f, + 0.99998786380966f, + 0.99998870197921f, + 0.99998949518567f, + 0.99999024526408f, + 0.99999095399401f, + 0.99999162310077f, + 0.99999225425649f, + 0.99999284908128f, + 0.99999340914435f, + 0.99999393596510f, + 0.99999443101421f, + 0.99999489571473f, + 0.99999533144314f, + 0.99999573953040f, + 0.99999612126300f, + 0.99999647788395f, + 0.99999681059383f, + 0.99999712055178f, + 0.99999740887647f, + 0.99999767664709f, + 0.99999792490431f, + 0.99999815465123f, + 0.99999836685427f, + 0.99999856244415f, + 0.99999874231676f, + 0.99999890733405f, + 0.99999905832493f, + 0.99999919608613f, + 0.99999932138304f, + 0.99999943495056f, + 0.99999953749392f, + 0.99999962968950f, + 0.99999971218563f, + 0.99999978560337f, + 0.99999985053727f, + 0.99999990755616f, + 0.99999995720387f + }; + public static float[] KBD_128 = { + 4.3795702929468881e-005f, + 0.00011867384265436617f, + 0.0002307165763996192f, + 0.00038947282760568383f, + 0.00060581272288302553f, + 0.00089199695169487453f, + 0.0012617254423430522f, + 0.0017301724373162003f, + 0.0023140071937421476f, + 0.0030313989666022221f, + 0.0039020049735530842f, + 0.0049469401815512024f, + 0.0061887279335368318f, + 0.0076512306364647726f, + 0.0093595599562652423f, + 0.011339966208377799f, + 0.013619706891715299f, + 0.016226894586323766f, + 0.019190324717288168f, + 0.022539283975960878f, + 0.026303340480472455f, + 0.030512117046644357f, + 0.03519504922365594f, + 0.040381130021856941f, + 0.046098643518702249f, + 0.052374889768730587f, + 0.059235903660769147f, + 0.066706170556282418f, + 0.074808341703430481f, + 0.083562952548726227f, + 0.092988147159339674f, + 0.1030994120216919f, + 0.11390932249409955f, + 0.12542730516149531f, + 0.13765941926783826f, + 0.15060816028651081f, + 0.16427228853114245f, + 0.17864668550988483f, + 0.19372224048676889f, + 0.20948576943658073f, + 0.22591996826744942f, + 0.24300340184133981f, + 0.26071052995068139f, + 0.27901177101369551f, + 0.29787360383626599f, + 0.3172587073594233f, + 0.33712613787396362f, + 0.35743154274286698f, + 0.37812740923363009f, + 0.39916334663203618f, + 0.42048639939189658f, + 0.4420413886774246f, + 0.4637712792815169f, + 0.4856175685594023f, + 0.50752069370766872f, + 0.52942045344797806f, + 0.55125643994680196f, + 0.57296847662071559f, + 0.59449705734411495f, + 0.61578378249506627f, + 0.63677178724712891f, + 0.65740615754163356f, + 0.67763432925662526f, + 0.69740646622548552f, + 0.71667581294953808f, + 0.73539901809352737f, + 0.75353642514900732f, + 0.77105232699609816f, + 0.78791518148597028f, + 0.80409778560147072f, + 0.81957740622770781f, + 0.83433586607383625f, + 0.84835958382689225f, + 0.86163956818294229f, + 0.87417136598406997f, + 0.88595496528524853f, + 0.89699465477567619f, + 0.90729884157670959f, + 0.91687983002436779f, + 0.92575356460899649f, + 0.93393934077779084f, + 0.94145948779657318f, + 0.94833902830402828f, + 0.95460531956280026f, + 0.96028768170574896f, + 0.96541701848104766f, + 0.97002543610646474f, + 0.97414586584250062f, + 0.97781169577969584f, + 0.98105641710392333f, + 0.98391328975491177f, + 0.98641503193166202f, + 0.98859353733226141f, + 0.99047962335771556f, + 0.9921028127769449f, + 0.99349115056397752f, + 0.99467105680259038f, + 0.9956672157341897f, + 0.99650250022834352f, + 0.99719793020823266f, + 0.99777266288955657f, + 0.99824401211201486f, + 0.99862749357391212f, + 0.99893689243401962f, + 0.99918434952623147f, + 0.99938046234161726f, + 0.99953439696357238f, + 0.99965400728430465f, + 0.99974595807027455f, + 0.99981584876278362f, + 0.99986833527824281f, + 0.99990724749057802f, + 0.99993570051598468f, + 0.99995619835942084f, + 0.99997072890647543f, + 0.9999808496399144f, + 0.99998776381655818f, + 0.99999238714961569f, + 0.99999540529959718f, + 0.99999732268176988f, + 0.99999850325054862f, + 0.99999920402413744f, + 0.9999996021706401f, + 0.99999981649545566f, + 0.99999992415545547f, + 0.99999997338493041f, + 0.99999999295825959f, + 0.99999999904096815f + }; + public static float[] KBD_960 = { + 0.0003021562530949f, + 0.0004452267024786f, + 0.0005674947527496f, + 0.0006812465553466f, + 0.0007910496776387f, + 0.0008991655033895f, + 0.0010068978259384f, + 0.0011150758515751f, + 0.0012242653193642f, + 0.0013348735658205f, + 0.0014472068670273f, + 0.0015615039850448f, + 0.0016779568885263f, + 0.0017967241232412f, + 0.0019179397560955f, + 0.0020417195415393f, + 0.0021681652836642f, + 0.0022973679910599f, + 0.0024294102029937f, + 0.0025643677339078f, + 0.0027023110014772f, + 0.0028433060512612f, + 0.0029874153568025f, + 0.0031346984511728f, + 0.0032852124303662f, + 0.0034390123581190f, + 0.0035961515940931f, + 0.0037566820618961f, + 0.0039206544694386f, + 0.0040881184912194f, + 0.0042591229199617f, + 0.0044337157933972f, + 0.0046119445007641f, + 0.0047938558726415f, + 0.0049794962570131f, + 0.0051689115838900f, + 0.0053621474203763f, + 0.0055592490177131f, + 0.0057602613515573f, + 0.0059652291565289f, + 0.0061741969558843f, + 0.0063872090870253f, + 0.0066043097234387f, + 0.0068255428935640f, + 0.0070509524970088f, + 0.0072805823184660f, + 0.0075144760396340f, + 0.0077526772493942f, + 0.0079952294524673f, + 0.0082421760767325f, + 0.0084935604793733f, + 0.0087494259519870f, + 0.0090098157247792f, + 0.0092747729699467f, + 0.0095443408043399f, + 0.0098185622914832f, + 0.0100974804430226f, + 0.0103811382196612f, + 0.0106695785316351f, + 0.0109628442387771f, + 0.0112609781502091f, + 0.0115640230236993f, + 0.0118720215647169f, + 0.0121850164252137f, + 0.0125030502021561f, + 0.0128261654358321f, + 0.0131544046079532f, + 0.0134878101395681f, + 0.0138264243888068f, + 0.0141702896484671f, + 0.0145194481434592f, + 0.0148739420281182f, + 0.0152338133833959f, + 0.0155991042139432f, + 0.0159698564450882f, + 0.0163461119197227f, + 0.0167279123950996f, + 0.0171152995395520f, + 0.0175083149291368f, + 0.0179070000442104f, + 0.0183113962659409f, + 0.0187215448727609f, + 0.0191374870367659f, + 0.0195592638200623f, + 0.0199869161710679f, + 0.0204204849207691f, + 0.0208600107789370f, + 0.0213055343303066f, + 0.0217570960307201f, + 0.0222147362032386f, + 0.0226784950342228f, + 0.0231484125693867f, + 0.0236245287098244f, + 0.0241068832080138f, + 0.0245955156637973f, + 0.0250904655203431f, + 0.0255917720600868f, + 0.0260994744006559f, + 0.0266136114907790f, + 0.0271342221061795f, + 0.0276613448454576f, + 0.0281950181259587f, + 0.0287352801796329f, + 0.0292821690488833f, + 0.0298357225824074f, + 0.0303959784310299f, + 0.0309629740435296f, + 0.0315367466624615f, + 0.0321173333199732f, + 0.0327047708336193f, + 0.0332990958021720f, + 0.0339003446014307f, + 0.0345085533800302f, + 0.0351237580552491f, + 0.0357459943088193f, + 0.0363752975827358f, + 0.0370117030750704f, + 0.0376552457357870f, + 0.0383059602625614f, + 0.0389638810966056f, + 0.0396290424184964f, + 0.0403014781440112f, + 0.0409812219199691f, + 0.0416683071200799f, + 0.0423627668408009f, + 0.0430646338972016f, + 0.0437739408188385f, + 0.0444907198456388f, + 0.0452150029237951f, + 0.0459468217016708f, + 0.0466862075257170f, + 0.0474331914364021f, + 0.0481878041641539f, + 0.0489500761253148f, + 0.0497200374181119f, + 0.0504977178186404f, + 0.0512831467768636f, + 0.0520763534126273f, + 0.0528773665116913f, + 0.0536862145217772f, + 0.0545029255486345f, + 0.0553275273521232f, + 0.0561600473423164f, + 0.0570005125756209f, + 0.0578489497509179f, + 0.0587053852057233f, + 0.0595698449123695f, + 0.0604423544742077f, + 0.0613229391218317f, + 0.0622116237093247f, + 0.0631084327105284f, + 0.0640133902153352f, + 0.0649265199260043f, + 0.0658478451535027f, + 0.0667773888138695f, + 0.0677151734246072f, + 0.0686612211010977f, + 0.0696155535530446f, + 0.0705781920809429f, + 0.0715491575725758f, + 0.0725284704995383f, + 0.0735161509137906f, + 0.0745122184442388f, + 0.0755166922933461f, + 0.0765295912337720f, + 0.0775509336050437f, + 0.0785807373102561f, + 0.0796190198128044f, + 0.0806657981331473f, + 0.0817210888456026f, + 0.0827849080751753f, + 0.0838572714944183f, + 0.0849381943203265f, + 0.0860276913112652f, + 0.0871257767639319f, + 0.0882324645103534f, + 0.0893477679149177f, + 0.0904716998714418f, + 0.0916042728002747f, + 0.0927454986454381f, + 0.0938953888718020f, + 0.0950539544622996f, + 0.0962212059151784f, + 0.0973971532412897f, + 0.0985818059614169f, + 0.0997751731036425f, + 0.1009772632007537f, + 0.1021880842876888f, + 0.1034076438990227f, + 0.1046359490664932f, + 0.1058730063165681f, + 0.1071188216680533f, + 0.1083734006297428f, + 0.1096367481981100f, + 0.1109088688550422f, + 0.1121897665656167f, + 0.1134794447759207f, + 0.1147779064109143f, + 0.1160851538723372f, + 0.1174011890366591f, + 0.1187260132530751f, + 0.1200596273415457f, + 0.1214020315908810f, + 0.1227532257568719f, + 0.1241132090604651f, + 0.1254819801859856f, + 0.1268595372794049f, + 0.1282458779466558f, + 0.1296409992519942f, + 0.1310448977164081f, + 0.1324575693160745f, + 0.1338790094808633f, + 0.1353092130928902f, + 0.1367481744851168f, + 0.1381958874400010f, + 0.1396523451881945f, + 0.1411175404072910f, + 0.1425914652206223f, + 0.1440741111961058f, + 0.1455654693451402f, + 0.1470655301215526f, + 0.1485742834205956f, + 0.1500917185779945f, + 0.1516178243690463f, + 0.1531525890077689f, + 0.1546960001461024f, + 0.1562480448731608f, + 0.1578087097145364f, + 0.1593779806316558f, + 0.1609558430211876f, + 0.1625422817145027f, + 0.1641372809771871f, + 0.1657408245086070f, + 0.1673528954415270f, + 0.1689734763417811f, + 0.1706025492079969f, + 0.1722400954713725f, + 0.1738860959955082f, + 0.1755405310762898f, + 0.1772033804418275f, + 0.1788746232524467f, + 0.1805542381007349f, + 0.1822422030116404f, + 0.1839384954426268f, + 0.1856430922838810f, + 0.1873559698585756f, + 0.1890771039231862f, + 0.1908064696678625f, + 0.1925440417168546f, + 0.1942897941289937f, + 0.1960437003982277f, + 0.1978057334542116f, + 0.1995758656629525f, + 0.2013540688275098f, + 0.2031403141887507f, + 0.2049345724261595f, + 0.2067368136587033f, + 0.2085470074457521f, + 0.2103651227880538f, + 0.2121911281287646f, + 0.2140249913545346f, + 0.2158666797966480f, + 0.2177161602322188f, + 0.2195733988854414f, + 0.2214383614288963f, + 0.2233110129849106f, + 0.2251913181269740f, + 0.2270792408812093f, + 0.2289747447278976f, + 0.2308777926030592f, + 0.2327883469000885f, + 0.2347063694714437f, + 0.2366318216303919f, + 0.2385646641528076f, + 0.2405048572790267f, + 0.2424523607157545f, + 0.2444071336380283f, + 0.2463691346912334f, + 0.2483383219931741f, + 0.2503146531361985f, + 0.2522980851893767f, + 0.2542885747007335f, + 0.2562860776995335f, + 0.2582905496986215f, + 0.2603019456968142f, + 0.2623202201813464f, + 0.2643453271303700f, + 0.2663772200155053f, + 0.2684158518044454f, + 0.2704611749636135f, + 0.2725131414608710f, + 0.2745717027682799f, + 0.2766368098649151f, + 0.2787084132397296f, + 0.2807864628944707f, + 0.2828709083466482f, + 0.2849616986325523f, + 0.2870587823103237f, + 0.2891621074630737f, + 0.2912716217020546f, + 0.2933872721698803f, + 0.2955090055437973f, + 0.2976367680390041f, + 0.2997705054120213f, + 0.3019101629641097f, + 0.3040556855447379f, + 0.3062070175550981f, + 0.3083641029516701f, + 0.3105268852498334f, + 0.3126953075275265f, + 0.3148693124289546f, + 0.3170488421683428f, + 0.3192338385337370f, + 0.3214242428908514f, + 0.3236199961869606f, + 0.3258210389548392f, + 0.3280273113167459f, + 0.3302387529884521f, + 0.3324553032833160f, + 0.3346769011164010f, + 0.3369034850086373f, + 0.3391349930910280f, + 0.3413713631088974f, + 0.3436125324261830f, + 0.3458584380297697f, + 0.3481090165338656f, + 0.3503642041844199f, + 0.3526239368635820f, + 0.3548881500942010f, + 0.3571567790443668f, + 0.3594297585319891f, + 0.3617070230294185f, + 0.3639885066681048f, + 0.3662741432432950f, + 0.3685638662187693f, + 0.3708576087316147f, + 0.3731553035970366f, + 0.3754568833132069f, + 0.3777622800661488f, + 0.3800714257346570f, + 0.3823842518952546f, + 0.3847006898271841f, + 0.3870206705174334f, + 0.3893441246657958f, + 0.3916709826899639f, + 0.3940011747306560f, + 0.3963346306567764f, + 0.3986712800706062f, + 0.4010110523130271f, + 0.4033538764687756f, + 0.4056996813717284f, + 0.4080483956102172f, + 0.4103999475323736f, + 0.4127542652515031f, + 0.4151112766514873f, + 0.4174709093922143f, + 0.4198330909150365f, + 0.4221977484482556f, + 0.4245648090126334f, + 0.4269341994269293f, + 0.4293058463134616f, + 0.4316796761036958f, + 0.4340556150438547f, + 0.4364335892005536f, + 0.4388135244664580f, + 0.4411953465659639f, + 0.4435789810609000f, + 0.4459643533562509f, + 0.4483513887059016f, + 0.4507400122184019f, + 0.4531301488627497f, + 0.4555217234741947f, + 0.4579146607600593f, + 0.4603088853055777f, + 0.4627043215797521f, + 0.4651008939412254f, + 0.4674985266441709f, + 0.4698971438441951f, + 0.4722966696042580f, + 0.4746970279006055f, + 0.4770981426287164f, + 0.4794999376092619f, + 0.4819023365940778f, + 0.4843052632721476f, + 0.4867086412755978f, + 0.4891123941857028f, + 0.4915164455388997f, + 0.4939207188328126f, + 0.4963251375322855f, + 0.4987296250754225f, + 0.5011341048796359f, + 0.5035385003477012f, + 0.5059427348738168f, + 0.5083467318496706f, + 0.5107504146705106f, + 0.5131537067412193f, + 0.5155565314823923f, + 0.5179588123364193f, + 0.5203604727735667f, + 0.5227614362980630f, + 0.5251616264541841f, + 0.5275609668323384f, + 0.5299593810751532f, + 0.5323567928835578f, + 0.5347531260228663f, + 0.5371483043288580f, + 0.5395422517138538f, + 0.5419348921727899f, + 0.5443261497892862f, + 0.5467159487417104f, + 0.5491042133092364f, + 0.5514908678778958f, + 0.5538758369466227f, + 0.5562590451332913f, + 0.5586404171807443f, + 0.5610198779628133f, + 0.5633973524903286f, + 0.5657727659171199f, + 0.5681460435460047f, + 0.5705171108347663f, + 0.5728858934021188f, + 0.5752523170336598f, + 0.5776163076878088f, + 0.5799777915017323f, + 0.5823366947972535f, + 0.5846929440867458f, + 0.5870464660790119f, + 0.5893971876851449f, + 0.5917450360243719f, + 0.5940899384298793f, + 0.5964318224546208f, + 0.5987706158771039f, + 0.6011062467071583f, + 0.6034386431916822f, + 0.6057677338203681f, + 0.6080934473314057f, + 0.6104157127171639f, + 0.6127344592298474f, + 0.6150496163871310f, + 0.6173611139777690f, + 0.6196688820671789f, + 0.6219728510029997f, + 0.6242729514206247f, + 0.6265691142487051f, + 0.6288612707146283f, + 0.6311493523499663f, + 0.6334332909958958f, + 0.6357130188085891f, + 0.6379884682645743f, + 0.6402595721660647f, + 0.6425262636462578f, + 0.6447884761746012f, + 0.6470461435620266f, + 0.6492991999661505f, + 0.6515475798964411f, + 0.6537912182193508f, + 0.6560300501634142f, + 0.6582640113243098f, + 0.6604930376698862f, + 0.6627170655451516f, + 0.6649360316772256f, + 0.6671498731802533f, + 0.6693585275602818f, + 0.6715619327200959f, + 0.6737600269640164f, + 0.6759527490026566f, + 0.6781400379576392f, + 0.6803218333662715f, + 0.6824980751861787f, + 0.6846687037998949f, + 0.6868336600194123f, + 0.6889928850906855f, + 0.6911463206980928f, + 0.6932939089688525f, + 0.6954355924773949f, + 0.6975713142496884f, + 0.6997010177675195f, + 0.7018246469727265f, + 0.7039421462713862f, + 0.7060534605379528f, + 0.7081585351193496f, + 0.7102573158390105f, + 0.7123497490008750f, + 0.7144357813933307f, + 0.7165153602931092f, + 0.7185884334691287f, + 0.7206549491862871f, + 0.7227148562092042f, + 0.7247681038059106f, + 0.7268146417514855f, + 0.7288544203316418f, + 0.7308873903462577f, + 0.7329135031128549f, + 0.7349327104700221f, + 0.7369449647807855f, + 0.7389502189359237f, + 0.7409484263572271f, + 0.7429395410007016f, + 0.7449235173597176f, + 0.7469003104681008f, + 0.7488698759031670f, + 0.7508321697887005f, + 0.7527871487978728f, + 0.7547347701561059f, + 0.7566749916438754f, + 0.7586077715994560f, + 0.7605330689216074f, + 0.7624508430722016f, + 0.7643610540787891f, + 0.7662636625371070f, + 0.7681586296135255f, + 0.7700459170474343f, + 0.7719254871535672f, + 0.7737973028242671f, + 0.7756613275316875f, + 0.7775175253299340f, + 0.7793658608571425f, + 0.7812062993374951f, + 0.7830388065831744f, + 0.7848633489962533f, + 0.7866798935705233f, + 0.7884884078932579f, + 0.7902888601469138f, + 0.7920812191107668f, + 0.7938654541624850f, + 0.7956415352796368f, + 0.7974094330411343f, + 0.7991691186286133f, + 0.8009205638277465f, + 0.8026637410294932f, + 0.8043986232312831f, + 0.8061251840381346f, + 0.8078433976637077f, + 0.8095532389312917f, + 0.8112546832747255f, + 0.8129477067392539f, + 0.8146322859823164f, + 0.8163083982742698f, + 0.8179760214990457f, + 0.8196351341547393f, + 0.8212857153541345f, + 0.8229277448251595f, + 0.8245612029112778f, + 0.8261860705718113f, + 0.8278023293821971f, + 0.8294099615341773f, + 0.8310089498359212f, + 0.8325992777120815f, + 0.8341809292037831f, + 0.8357538889685445f, + 0.8373181422801330f, + 0.8388736750283521f, + 0.8404204737187619f, + 0.8419585254723335f, + 0.8434878180250348f, + 0.8450083397273509f, + 0.8465200795437368f, + 0.8480230270520029f, + 0.8495171724426350f, + 0.8510025065180464f, + 0.8524790206917633f, + 0.8539467069875448f, + 0.8554055580384357f, + 0.8568555670857525f, + 0.8582967279780043f, + 0.8597290351697464f, + 0.8611524837203691f, + 0.8625670692928198f, + 0.8639727881522599f, + 0.8653696371646555f, + 0.8667576137953045f, + 0.8681367161072958f, + 0.8695069427599065f, + 0.8708682930069319f, + 0.8722207666949527f, + 0.8735643642615368f, + 0.8748990867333771f, + 0.8762249357243662f, + 0.8775419134336067f, + 0.8788500226433579f, + 0.8801492667169208f, + 0.8814396495964587f, + 0.8827211758007560f, + 0.8839938504229149f, + 0.8852576791279895f, + 0.8865126681505587f, + 0.8877588242922386f, + 0.8889961549191320f, + 0.8902246679592184f, + 0.8914443718996848f, + 0.8926552757841945f, + 0.8938573892100969f, + 0.8950507223255798f, + 0.8962352858267605f, + 0.8974110909547198f, + 0.8985781494924783f, + 0.8997364737619142f, + 0.9008860766206249f, + 0.9020269714587307f, + 0.9031591721956235f, + 0.9042826932766591f, + 0.9053975496697941f, + 0.9065037568621681f, + 0.9076013308566311f, + 0.9086902881682180f, + 0.9097706458205682f, + 0.9108424213422940f, + 0.9119056327632955f, + 0.9129602986110235f, + 0.9140064379066919f, + 0.9150440701614393f, + 0.9160732153724396f, + 0.9170938940189634f, + 0.9181061270583908f, + 0.9191099359221748f, + 0.9201053425117579f, + 0.9210923691944400f, + 0.9220710387992010f, + 0.9230413746124764f, + 0.9240034003738882f, + 0.9249571402719298f, + 0.9259026189396085f, + 0.9268398614500427f, + 0.9277688933120170f, + 0.9286897404654957f, + 0.9296024292770939f, + 0.9305069865355076f, + 0.9314034394469048f, + 0.9322918156302762f, + 0.9331721431127471f, + 0.9340444503248519f, + 0.9349087660957711f, + 0.9357651196485313f, + 0.9366135405951697f, + 0.9374540589318637f, + 0.9382867050340261f, + 0.9391115096513655f, + 0.9399285039029165f, + 0.9407377192720349f, + 0.9415391876013639f, + 0.9423329410877687f, + 0.9431190122772415f, + 0.9438974340597782f, + 0.9446682396642262f, + 0.9454314626531054f, + 0.9461871369174033f, + 0.9469352966713429f, + 0.9476759764471278f, + 0.9484092110896616f, + 0.9491350357512457f, + 0.9498534858862532f, + 0.9505645972457831f, + 0.9512684058722927f, + 0.9519649480942105f, + 0.9526542605205314f, + 0.9533363800353921f, + 0.9540113437926313f, + 0.9546791892103320f, + 0.9553399539653500f, + 0.9559936759878265f, + 0.9566403934556893f, + 0.9572801447891388f, + 0.9579129686451244f, + 0.9585389039118085f, + 0.9591579897030224f, + 0.9597702653527108f, + 0.9603757704093711f, + 0.9609745446304828f, + 0.9615666279769324f, + 0.9621520606074324f, + 0.9627308828729358f, + 0.9633031353110477f, + 0.9638688586404335f, + 0.9644280937552258f, + 0.9649808817194311f, + 0.9655272637613366f, + 0.9660672812679171f, + 0.9666009757792454f, + 0.9671283889829055f, + 0.9676495627084089f, + 0.9681645389216160f, + 0.9686733597191652f, + 0.9691760673229058f, + 0.9696727040743406f, + 0.9701633124290767f, + 0.9706479349512860f, + 0.9711266143081750f, + 0.9715993932644684f, + 0.9720663146769026f, + 0.9725274214887337f, + 0.9729827567242596f, + 0.9734323634833574f, + 0.9738762849360358f, + 0.9743145643170059f, + 0.9747472449202687f, + 0.9751743700937215f, + 0.9755959832337850f, + 0.9760121277800496f, + 0.9764228472099433f, + 0.9768281850334235f, + 0.9772281847876897f, + 0.9776228900319223f, + 0.9780123443420448f, + 0.9783965913055132f, + 0.9787756745161313f, + 0.9791496375688939f, + 0.9795185240548578f, + 0.9798823775560431f, + 0.9802412416403639f, + 0.9805951598565897f, + 0.9809441757293399f, + 0.9812883327541090f, + 0.9816276743923267f, + 0.9819622440664515f, + 0.9822920851550995f, + 0.9826172409882086f, + 0.9829377548422400f, + 0.9832536699354163f, + 0.9835650294229984f, + 0.9838718763926001f, + 0.9841742538595437f, + 0.9844722047622547f, + 0.9847657719576983f, + 0.9850549982168574f, + 0.9853399262202529f, + 0.9856205985535073f, + 0.9858970577029519f, + 0.9861693460512790f, + 0.9864375058732389f, + 0.9867015793313820f, + 0.9869616084718489f, + 0.9872176352202061f, + 0.9874697013773301f, + 0.9877178486153397f, + 0.9879621184735767f, + 0.9882025523546365f, + 0.9884391915204485f, + 0.9886720770884069f, + 0.9889012500275530f, + 0.9891267511548089f, + 0.9893486211312621f, + 0.9895669004585049f, + 0.9897816294750255f, + 0.9899928483526520f, + 0.9902005970930525f, + 0.9904049155242876f, + 0.9906058432974180f, + 0.9908034198831690f, + 0.9909976845686489f, + 0.9911886764541239f, + 0.9913764344498495f, + 0.9915609972729590f, + 0.9917424034444086f, + 0.9919206912859797f, + 0.9920958989173397f, + 0.9922680642531603f, + 0.9924372250002933f, + 0.9926034186550070f, + 0.9927666825002789f, + 0.9929270536031491f, + 0.9930845688121325f, + 0.9932392647546895f, + 0.9933911778347579f, + 0.9935403442303433f, + 0.9936867998911693f, + 0.9938305805363887f, + 0.9939717216523539f, + 0.9941102584904481f, + 0.9942462260649764f, + 0.9943796591511174f, + 0.9945105922829353f, + 0.9946390597514524f, + 0.9947650956027824f, + 0.9948887336363228f, + 0.9950100074030103f, + 0.9951289502036336f, + 0.9952455950872091f, + 0.9953599748494155f, + 0.9954721220310890f, + 0.9955820689167787f, + 0.9956898475333619f, + 0.9957954896487196f, + 0.9958990267704713f, + 0.9960004901447701f, + 0.9960999107551559f, + 0.9961973193214694f, + 0.9962927462988245f, + 0.9963862218766388f, + 0.9964777759777242f, + 0.9965674382574342f, + 0.9966552381028704f, + 0.9967412046321465f, + 0.9968253666937095f, + 0.9969077528657186f, + 0.9969883914554805f, + 0.9970673104989413f, + 0.9971445377602348f, + 0.9972201007312871f, + 0.9972940266314749f, + 0.9973663424073412f, + 0.9974370747323638f, + 0.9975062500067785f, + 0.9975738943574574f, + 0.9976400336378379f, + 0.9977046934279079f, + 0.9977678990342401f, + 0.9978296754900812f, + 0.9978900475554902f, + 0.9979490397175296f, + 0.9980066761905056f, + 0.9980629809162593f, + 0.9981179775645063f, + 0.9981716895332257f, + 0.9982241399490979f, + 0.9982753516679893f, + 0.9983253472754841f, + 0.9983741490874634f, + 0.9984217791507299f, + 0.9984682592436778f, + 0.9985136108770075f, + 0.9985578552944850f, + 0.9986010134737439f, + 0.9986431061271304f, + 0.9986841537025921f, + 0.9987241763846056f, + 0.9987631940951476f, + 0.9988012264947044f, + 0.9988382929833222f, + 0.9988744127016956f, + 0.9989096045322947f, + 0.9989438871005292f, + 0.9989772787759494f, + 0.9990097976734847f, + 0.9990414616547146f, + 0.9990722883291779f, + 0.9991022950557125f, + 0.9991314989438310f, + 0.9991599168551279f, + 0.9991875654047181f, + 0.9992144609627068f, + 0.9992406196556911f, + 0.9992660573682882f, + 0.9992907897446957f, + 0.9993148321902777f, + 0.9993381998731797f, + 0.9993609077259696f, + 0.9993829704473038f, + 0.9994044025036201f, + 0.9994252181308537f, + 0.9994454313361775f, + 0.9994650558997651f, + 0.9994841053765757f, + 0.9995025930981609f, + 0.9995205321744921f, + 0.9995379354958073f, + 0.9995548157344778f, + 0.9995711853468930f, + 0.9995870565753632f, + 0.9996024414500382f, + 0.9996173517908444f, + 0.9996317992094352f, + 0.9996457951111574f, + 0.9996593506970310f, + 0.9996724769657434f, + 0.9996851847156547f, + 0.9996974845468164f, + 0.9997093868630000f, + 0.9997209018737374f, + 0.9997320395963699f, + 0.9997428098581069f, + 0.9997532222980933f, + 0.9997632863694836f, + 0.9997730113415246f, + 0.9997824063016426f, + 0.9997914801575380f, + 0.9998002416392840f, + 0.9998086993014300f, + 0.9998168615251084f, + 0.9998247365201450f, + 0.9998323323271717f, + 0.9998396568197407f, + 0.9998467177064404f, + 0.9998535225330116f, + 0.9998600786844637f, + 0.9998663933871905f, + 0.9998724737110845f, + 0.9998783265716498f, + 0.9998839587321121f, + 0.9998893768055266f, + 0.9998945872568815f, + 0.9998995964051983f, + 0.9999044104256269f, + 0.9999090353515359f, + 0.9999134770765971f, + 0.9999177413568642f, + 0.9999218338128448f, + 0.9999257599315647f, + 0.9999295250686255f, + 0.9999331344502529f, + 0.9999365931753376f, + 0.9999399062174669f, + 0.9999430784269460f, + 0.9999461145328103f, + 0.9999490191448277f, + 0.9999517967554878f, + 0.9999544517419835f, + 0.9999569883681778f, + 0.9999594107865607f, + 0.9999617230401926f, + 0.9999639290646355f, + 0.9999660326898712f, + 0.9999680376422052f, + 0.9999699475461585f, + 0.9999717659263435f, + 0.9999734962093266f, + 0.9999751417254756f, + 0.9999767057107922f, + 0.9999781913087290f, + 0.9999796015719915f, + 0.9999809394643231f, + 0.9999822078622751f, + 0.9999834095569596f, + 0.9999845472557860f, + 0.9999856235841805f, + 0.9999866410872889f, + 0.9999876022316609f, + 0.9999885094069193f, + 0.9999893649274085f, + 0.9999901710338274f, + 0.9999909298948430f, + 0.9999916436086862f, + 0.9999923142047299f, + 0.9999929436450469f, + 0.9999935338259505f, + 0.9999940865795161f, + 0.9999946036750835f, + 0.9999950868207405f, + 0.9999955376647868f, + 0.9999959577971798f, + 0.9999963487509599f, + 0.9999967120036571f, + 0.9999970489786785f, + 0.9999973610466748f, + 0.9999976495268890f, + 0.9999979156884846f, + 0.9999981607518545f, + 0.9999983858899099f, + 0.9999985922293493f, + 0.9999987808519092f, + 0.9999989527955938f, + 0.9999991090558848f, + 0.9999992505869332f, + 0.9999993783027293f, + 0.9999994930782556f, + 0.9999995957506171f, + 0.9999996871201549f, + 0.9999997679515386f, + 0.9999998389748399f, + 0.9999999008865869f, + 0.9999999543507984f + }; + public static float[] KBD_120 = { + 0.0000452320086910f, + 0.0001274564692111f, + 0.0002529398385345f, + 0.0004335140496648f, + 0.0006827100966952f, + 0.0010158708222246f, + 0.0014502162869659f, + 0.0020048865156264f, + 0.0027009618393178f, + 0.0035614590925043f, + 0.0046113018122711f, + 0.0058772627936484f, + 0.0073878776584103f, + 0.0091733284512589f, + 0.0112652966728373f, + 0.0136967855861945f, + 0.0165019120857793f, + 0.0197156688892217f, + 0.0233736582950619f, + 0.0275117992367496f, + 0.0321660098468534f, + 0.0373718682174417f, + 0.0431642544948834f, + 0.0495769778717676f, + 0.0566423924273392f, + 0.0643910061132260f, + 0.0728510874761729f, + 0.0820482749475221f, + 0.0920051937045235f, + 0.1027410852163450f, + 0.1142714546239370f, + 0.1266077410648368f, + 0.1397570159398145f, + 0.1537217139274270f, + 0.1684994012857075f, + 0.1840825856392944f, + 0.2004585710384133f, + 0.2176093615976121f, + 0.2355116164824983f, + 0.2541366584185075f, + 0.2734505372545160f, + 0.2934141494343369f, + 0.3139834135200387f, + 0.3351095011824163f, + 0.3567391223361566f, + 0.3788148623608774f, + 0.4012755686250732f, + 0.4240567828288110f, + 0.4470912150133537f, + 0.4703092544619664f, + 0.4936395121456694f, + 0.5170093888596962f, + 0.5403456627591340f, + 0.5635750896430154f, + 0.5866250090612892f, + 0.6094239491338723f, + 0.6319022228794100f, + 0.6539925088563087f, + 0.6756304090216887f, + 0.6967549769155277f, + 0.7173092095766250f, + 0.7372404969921184f, + 0.7565010233699827f, + 0.7750481150999984f, + 0.7928445309277697f, + 0.8098586906021583f, + 0.8260648390616000f, + 0.8414431440907889f, + 0.8559797262966709f, + 0.8696666212110165f, + 0.8825016743142358f, + 0.8944883707784486f, + 0.9056356027326216f, + 0.9159573778427816f, + 0.9254724739583072f, + 0.9342040454819434f, + 0.9421791879559176f, + 0.9494284680976784f, + 0.9559854271440150f, + 0.9618860658493898f, + 0.9671683198119525f, + 0.9718715339497299f, + 0.9760359449042233f, + 0.9797021798981759f, + 0.9829107801140203f, + 0.9857017559923277f, + 0.9881141809867999f, + 0.9901858292742826f, + 0.9919528617340944f, + 0.9934495632180476f, + 0.9947081327749199f, + 0.9957585271195989f, + 0.9966283562984427f, + 0.9973428292485683f, + 0.9979247458259197f, + 0.9983945309245774f, + 0.9987703055583410f, + 0.9990679892449266f, + 0.9993014277313617f, + 0.9994825400228521f, + 0.9996214788122335f, + 0.9997267987294857f, + 0.9998056273097539f, + 0.9998638341781910f, + 0.9999061946325793f, + 0.9999365445321382f, + 0.9999579241373735f, + 0.9999727092594598f, + 0.9999827287418790f, + 0.9999893678912771f, + 0.9999936579844555f, + 0.9999963523959187f, + 0.9999979902130101f, + 0.9999989484358076f, + 0.9999994840031031f, + 0.9999997669534347f, + 0.9999999060327799f, + 0.9999999680107184f, + 0.9999999918774242f, + 0.9999999989770326f + }; + } +} diff --git a/SharpJaad.AAC/Filterbank/MDCT.cs b/SharpJaad.AAC/Filterbank/MDCT.cs new file mode 100644 index 0000000..5d01e2c --- /dev/null +++ b/SharpJaad.AAC/Filterbank/MDCT.cs @@ -0,0 +1,137 @@ +using SharpJaad.AAC; + +namespace SharpJaad.AAC.Filterbank +{ + public class MDCT + { + private int _N, _N2, _N4, _N8; + private float[][] _sincos; + private FFT _fft; + private float[,] _buf; + private float[] _tmp; + + public MDCT(int length) + { + _N = length; + _N2 = length >> 1; + _N4 = length >> 2; + _N8 = length >> 3; + switch (length) + { + case 2048: + _sincos = MDCTTables.MDCT_TABLE_2048; + break; + case 256: + _sincos = MDCTTables.MDCT_TABLE_128; + break; + case 1920: + _sincos = MDCTTables.MDCT_TABLE_1920; + break; + case 240: + _sincos = MDCTTables.MDCT_TABLE_240; + break; + default: + throw new AACException("unsupported MDCT length: " + length); + } + _fft = new FFT(_N4); + _buf = new float[_N4, 2]; + _tmp = new float[2]; + } + + public void Process(float[] input, int inOff, float[] output, int outOff) + { + int k; + + //pre-IFFT complex multiplication + for (k = 0; k < _N4; k++) + { + _buf[k, 1] = input[inOff + 2 * k] * _sincos[k][0] + input[inOff + _N2 - 1 - 2 * k] * _sincos[k][1]; + _buf[k, 0] = input[inOff + _N2 - 1 - 2 * k] * _sincos[k][0] - input[inOff + 2 * k] * _sincos[k][1]; + } + + //complex IFFT, non-scaling + _fft.Process(_buf, false); + + //post-IFFT complex multiplication + for (k = 0; k < _N4; k++) + { + _tmp[0] = _buf[k, 0]; + _tmp[1] = _buf[k, 1]; + _buf[k, 1] = _tmp[1] * _sincos[k][0] + _tmp[0] * _sincos[k][1]; + _buf[k, 0] = _tmp[0] * _sincos[k][0] - _tmp[1] * _sincos[k][1]; + } + + //reordering + for (k = 0; k < _N8; k += 2) + { + output[outOff + 2 * k] = _buf[_N8 + k, 1]; + output[outOff + 2 + 2 * k] = _buf[_N8 + 1 + k, 1]; + + output[outOff + 1 + 2 * k] = -_buf[_N8 - 1 - k, 0]; + output[outOff + 3 + 2 * k] = -_buf[_N8 - 2 - k, 0]; + + output[outOff + _N4 + 2 * k] = _buf[k, 0]; + output[outOff + _N4 + 2 + 2 * k] = _buf[1 + k, 0]; + + output[outOff + _N4 + 1 + 2 * k] = -_buf[_N4 - 1 - k, 1]; + output[outOff + _N4 + 3 + 2 * k] = -_buf[_N4 - 2 - k, 1]; + + output[outOff + _N2 + 2 * k] = _buf[_N8 + k, 0]; + output[outOff + _N2 + 2 + 2 * k] = _buf[_N8 + 1 + k, 0]; + + output[outOff + _N2 + 1 + 2 * k] = -_buf[_N8 - 1 - k, 1]; + output[outOff + _N2 + 3 + 2 * k] = -_buf[_N8 - 2 - k, 1]; + + output[outOff + _N2 + _N4 + 2 * k] = -_buf[k, 1]; + output[outOff + _N2 + _N4 + 2 + 2 * k] = -_buf[1 + k, 1]; + + output[outOff + _N2 + _N4 + 1 + 2 * k] = _buf[_N4 - 1 - k, 0]; + output[outOff + _N2 + _N4 + 3 + 2 * k] = _buf[_N4 - 2 - k, 0]; + } + } + + public void ProcessForward(float[] input, float[] output) + { + int n, k; + //pre-FFT complex multiplication + for (k = 0; k < _N8; k++) + { + n = k << 1; + _tmp[0] = input[_N - _N4 - 1 - n] + input[_N - _N4 + n]; + _tmp[1] = input[_N4 + n] - input[_N4 - 1 - n]; + + _buf[k, 0] = _tmp[0] * _sincos[k][0] + _tmp[1] * _sincos[k][1]; + _buf[k, 1] = _tmp[1] * _sincos[k][0] - _tmp[0] * _sincos[k][1]; + + _buf[k, 0] *= _N; + _buf[k, 1] *= _N; + + _tmp[0] = input[_N2 - 1 - n] - input[n]; + _tmp[1] = input[_N2 + n] + input[_N - 1 - n]; + + _buf[k + _N8, 0] = _tmp[0] * _sincos[k + _N8][0] + _tmp[1] * _sincos[k + _N8][1]; + _buf[k + _N8, 1] = _tmp[1] * _sincos[k + _N8][0] - _tmp[0] * _sincos[k + _N8][1]; + + _buf[k + _N8, 0] *= _N; + _buf[k + _N8, 1] *= _N; + } + + //complex FFT, non-scaling + _fft.Process(_buf, true); + + //post-FFT complex multiplication + for (k = 0; k < _N4; k++) + { + n = k << 1; + + _tmp[0] = _buf[k, 0] * _sincos[k][0] + _buf[k, 1] * _sincos[k][1]; + _tmp[1] = _buf[k, 1] * _sincos[k][0] - _buf[k, 0] * _sincos[k][1]; + + output[n] = -_tmp[0]; + output[_N2 - 1 - n] = _tmp[1]; + output[_N2 + n] = -_tmp[1]; + output[_N - 1 - n] = _tmp[0]; + } + } + } +} diff --git a/SharpJaad.AAC/Filterbank/MDCTTables.cs b/SharpJaad.AAC/Filterbank/MDCTTables.cs new file mode 100644 index 0000000..56ad5c7 --- /dev/null +++ b/SharpJaad.AAC/Filterbank/MDCTTables.cs @@ -0,0 +1,1130 @@ +namespace SharpJaad.AAC.Filterbank +{ + public static class MDCTTables + { + public static float[][] MDCT_TABLE_2048 = { + new float[] {0.031249997702054f, 0.000011984224612f}, + new float[] {0.031249813866531f, 0.000107857810004f}, + new float[] {0.031249335895858f, 0.000203730380198f}, + new float[] {0.031248563794535f, 0.000299601032804f}, + new float[] {0.031247497569829f, 0.000395468865451f}, + new float[] {0.031246137231775f, 0.000491332975794f}, + new float[] {0.031244482793177f, 0.000587192461525f}, + new float[] {0.031242534269608f, 0.000683046420376f}, + new float[] {0.031240291679407f, 0.000778893950134f}, + new float[] {0.031237755043684f, 0.000874734148645f}, + new float[] {0.031234924386313f, 0.000970566113826f}, + new float[] {0.031231799733938f, 0.001066388943669f}, + new float[] {0.031228381115970f, 0.001162201736253f}, + new float[] {0.031224668564585f, 0.001258003589751f}, + new float[] {0.031220662114728f, 0.001353793602441f}, + new float[] {0.031216361804108f, 0.001449570872710f}, + new float[] {0.031211767673203f, 0.001545334499065f}, + new float[] {0.031206879765253f, 0.001641083580144f}, + new float[] {0.031201698126266f, 0.001736817214719f}, + new float[] {0.031196222805014f, 0.001832534501709f}, + new float[] {0.031190453853031f, 0.001928234540186f}, + new float[] {0.031184391324617f, 0.002023916429386f}, + new float[] {0.031178035276836f, 0.002119579268713f}, + new float[] {0.031171385769513f, 0.002215222157753f}, + new float[] {0.031164442865236f, 0.002310844196278f}, + new float[] {0.031157206629353f, 0.002406444484258f}, + new float[] {0.031149677129975f, 0.002502022121865f}, + new float[] {0.031141854437973f, 0.002597576209488f}, + new float[] {0.031133738626977f, 0.002693105847734f}, + new float[] {0.031125329773375f, 0.002788610137442f}, + new float[] {0.031116627956316f, 0.002884088179689f}, + new float[] {0.031107633257703f, 0.002979539075801f}, + new float[] {0.031098345762200f, 0.003074961927355f}, + new float[] {0.031088765557222f, 0.003170355836197f}, + new float[] {0.031078892732942f, 0.003265719904442f}, + new float[] {0.031068727382288f, 0.003361053234488f}, + new float[] {0.031058269600939f, 0.003456354929021f}, + new float[] {0.031047519487329f, 0.003551624091024f}, + new float[] {0.031036477142640f, 0.003646859823790f}, + new float[] {0.031025142670809f, 0.003742061230921f}, + new float[] {0.031013516178519f, 0.003837227416347f}, + new float[] {0.031001597775203f, 0.003932357484328f}, + new float[] {0.030989387573042f, 0.004027450539462f}, + new float[] {0.030976885686963f, 0.004122505686697f}, + new float[] {0.030964092234638f, 0.004217522031340f}, + new float[] {0.030951007336485f, 0.004312498679058f}, + new float[] {0.030937631115663f, 0.004407434735897f}, + new float[] {0.030923963698074f, 0.004502329308281f}, + new float[] {0.030910005212362f, 0.004597181503027f}, + new float[] {0.030895755789908f, 0.004691990427350f}, + new float[] {0.030881215564835f, 0.004786755188872f}, + new float[] {0.030866384674000f, 0.004881474895632f}, + new float[] {0.030851263256996f, 0.004976148656090f}, + new float[] {0.030835851456154f, 0.005070775579142f}, + new float[] {0.030820149416533f, 0.005165354774124f}, + new float[] {0.030804157285929f, 0.005259885350819f}, + new float[] {0.030787875214864f, 0.005354366419469f}, + new float[] {0.030771303356593f, 0.005448797090784f}, + new float[] {0.030754441867095f, 0.005543176475946f}, + new float[] {0.030737290905077f, 0.005637503686619f}, + new float[] {0.030719850631972f, 0.005731777834961f}, + new float[] {0.030702121211932f, 0.005825998033626f}, + new float[] {0.030684102811835f, 0.005920163395780f}, + new float[] {0.030665795601276f, 0.006014273035101f}, + new float[] {0.030647199752570f, 0.006108326065793f}, + new float[] {0.030628315440748f, 0.006202321602594f}, + new float[] {0.030609142843557f, 0.006296258760782f}, + new float[] {0.030589682141455f, 0.006390136656185f}, + new float[] {0.030569933517616f, 0.006483954405188f}, + new float[] {0.030549897157919f, 0.006577711124743f}, + new float[] {0.030529573250956f, 0.006671405932375f}, + new float[] {0.030508961988022f, 0.006765037946194f}, + new float[] {0.030488063563118f, 0.006858606284900f}, + new float[] {0.030466878172949f, 0.006952110067791f}, + new float[] {0.030445406016919f, 0.007045548414774f}, + new float[] {0.030423647297133f, 0.007138920446372f}, + new float[] {0.030401602218392f, 0.007232225283733f}, + new float[] {0.030379270988192f, 0.007325462048634f}, + new float[] {0.030356653816724f, 0.007418629863497f}, + new float[] {0.030333750916869f, 0.007511727851390f}, + new float[] {0.030310562504198f, 0.007604755136040f}, + new float[] {0.030287088796968f, 0.007697710841838f}, + new float[] {0.030263330016124f, 0.007790594093851f}, + new float[] {0.030239286385293f, 0.007883404017824f}, + new float[] {0.030214958130781f, 0.007976139740197f}, + new float[] {0.030190345481576f, 0.008068800388104f}, + new float[] {0.030165448669342f, 0.008161385089390f}, + new float[] {0.030140267928416f, 0.008253892972610f}, + new float[] {0.030114803495809f, 0.008346323167047f}, + new float[] {0.030089055611203f, 0.008438674802711f}, + new float[] {0.030063024516947f, 0.008530947010354f}, + new float[] {0.030036710458054f, 0.008623138921475f}, + new float[] {0.030010113682202f, 0.008715249668328f}, + new float[] {0.029983234439732f, 0.008807278383932f}, + new float[] {0.029956072983640f, 0.008899224202078f}, + new float[] {0.029928629569580f, 0.008991086257336f}, + new float[] {0.029900904455860f, 0.009082863685067f}, + new float[] {0.029872897903441f, 0.009174555621425f}, + new float[] {0.029844610175929f, 0.009266161203371f}, + new float[] {0.029816041539579f, 0.009357679568679f}, + new float[] {0.029787192263292f, 0.009449109855944f}, + new float[] {0.029758062618606f, 0.009540451204587f}, + new float[] {0.029728652879702f, 0.009631702754871f}, + new float[] {0.029698963323395f, 0.009722863647900f}, + new float[] {0.029668994229134f, 0.009813933025633f}, + new float[] {0.029638745879000f, 0.009904910030891f}, + new float[] {0.029608218557702f, 0.009995793807363f}, + new float[] {0.029577412552575f, 0.010086583499618f}, + new float[] {0.029546328153577f, 0.010177278253107f}, + new float[] {0.029514965653285f, 0.010267877214177f}, + new float[] {0.029483325346896f, 0.010358379530076f}, + new float[] {0.029451407532220f, 0.010448784348962f}, + new float[] {0.029419212509679f, 0.010539090819911f}, + new float[] {0.029386740582307f, 0.010629298092923f}, + new float[] {0.029353992055740f, 0.010719405318933f}, + new float[] {0.029320967238220f, 0.010809411649818f}, + new float[] {0.029287666440590f, 0.010899316238403f}, + new float[] {0.029254089976290f, 0.010989118238474f}, + new float[] {0.029220238161353f, 0.011078816804778f}, + new float[] {0.029186111314406f, 0.011168411093039f}, + new float[] {0.029151709756664f, 0.011257900259961f}, + new float[] {0.029117033811927f, 0.011347283463239f}, + new float[] {0.029082083806579f, 0.011436559861563f}, + new float[] {0.029046860069582f, 0.011525728614630f}, + new float[] {0.029011362932476f, 0.011614788883150f}, + new float[] {0.028975592729373f, 0.011703739828853f}, + new float[] {0.028939549796957f, 0.011792580614500f}, + new float[] {0.028903234474475f, 0.011881310403886f}, + new float[] {0.028866647103744f, 0.011969928361855f}, + new float[] {0.028829788029135f, 0.012058433654299f}, + new float[] {0.028792657597583f, 0.012146825448172f}, + new float[] {0.028755256158571f, 0.012235102911499f}, + new float[] {0.028717584064137f, 0.012323265213377f}, + new float[] {0.028679641668864f, 0.012411311523990f}, + new float[] {0.028641429329882f, 0.012499241014612f}, + new float[] {0.028602947406859f, 0.012587052857618f}, + new float[] {0.028564196262001f, 0.012674746226488f}, + new float[] {0.028525176260050f, 0.012762320295819f}, + new float[] {0.028485887768276f, 0.012849774241331f}, + new float[] {0.028446331156478f, 0.012937107239875f}, + new float[] {0.028406506796976f, 0.013024318469437f}, + new float[] {0.028366415064615f, 0.013111407109155f}, + new float[] {0.028326056336751f, 0.013198372339315f}, + new float[] {0.028285430993258f, 0.013285213341368f}, + new float[] {0.028244539416515f, 0.013371929297933f}, + new float[] {0.028203381991411f, 0.013458519392807f}, + new float[] {0.028161959105334f, 0.013544982810971f}, + new float[] {0.028120271148172f, 0.013631318738598f}, + new float[] {0.028078318512309f, 0.013717526363062f}, + new float[] {0.028036101592619f, 0.013803604872943f}, + new float[] {0.027993620786463f, 0.013889553458039f}, + new float[] {0.027950876493687f, 0.013975371309367f}, + new float[] {0.027907869116616f, 0.014061057619178f}, + new float[] {0.027864599060052f, 0.014146611580959f}, + new float[] {0.027821066731270f, 0.014232032389445f}, + new float[] {0.027777272540012f, 0.014317319240622f}, + new float[] {0.027733216898487f, 0.014402471331737f}, + new float[] {0.027688900221361f, 0.014487487861307f}, + new float[] {0.027644322925762f, 0.014572368029123f}, + new float[] {0.027599485431266f, 0.014657111036262f}, + new float[] {0.027554388159903f, 0.014741716085090f}, + new float[] {0.027509031536144f, 0.014826182379271f}, + new float[] {0.027463415986904f, 0.014910509123778f}, + new float[] {0.027417541941533f, 0.014994695524894f}, + new float[] {0.027371409831816f, 0.015078740790225f}, + new float[] {0.027325020091965f, 0.015162644128704f}, + new float[] {0.027278373158618f, 0.015246404750603f}, + new float[] {0.027231469470833f, 0.015330021867534f}, + new float[] {0.027184309470088f, 0.015413494692460f}, + new float[] {0.027136893600268f, 0.015496822439704f}, + new float[] {0.027089222307671f, 0.015580004324954f}, + new float[] {0.027041296040997f, 0.015663039565269f}, + new float[] {0.026993115251345f, 0.015745927379091f}, + new float[] {0.026944680392213f, 0.015828666986247f}, + new float[] {0.026895991919487f, 0.015911257607961f}, + new float[] {0.026847050291442f, 0.015993698466859f}, + new float[] {0.026797855968734f, 0.016075988786976f}, + new float[] {0.026748409414401f, 0.016158127793763f}, + new float[] {0.026698711093851f, 0.016240114714099f}, + new float[] {0.026648761474864f, 0.016321948776289f}, + new float[] {0.026598561027585f, 0.016403629210082f}, + new float[] {0.026548110224519f, 0.016485155246669f}, + new float[] {0.026497409540530f, 0.016566526118696f}, + new float[] {0.026446459452830f, 0.016647741060271f}, + new float[] {0.026395260440982f, 0.016728799306966f}, + new float[] {0.026343812986890f, 0.016809700095831f}, + new float[] {0.026292117574797f, 0.016890442665397f}, + new float[] {0.026240174691280f, 0.016971026255683f}, + new float[] {0.026187984825246f, 0.017051450108208f}, + new float[] {0.026135548467924f, 0.017131713465990f}, + new float[] {0.026082866112867f, 0.017211815573560f}, + new float[] {0.026029938255941f, 0.017291755676967f}, + new float[] {0.025976765395322f, 0.017371533023784f}, + new float[] {0.025923348031494f, 0.017451146863116f}, + new float[] {0.025869686667242f, 0.017530596445607f}, + new float[] {0.025815781807646f, 0.017609881023449f}, + new float[] {0.025761633960080f, 0.017688999850383f}, + new float[] {0.025707243634204f, 0.017767952181715f}, + new float[] {0.025652611341960f, 0.017846737274313f}, + new float[] {0.025597737597568f, 0.017925354386623f}, + new float[] {0.025542622917522f, 0.018003802778671f}, + new float[] {0.025487267820581f, 0.018082081712071f}, + new float[] {0.025431672827768f, 0.018160190450031f}, + new float[] {0.025375838462365f, 0.018238128257362f}, + new float[] {0.025319765249906f, 0.018315894400484f}, + new float[] {0.025263453718173f, 0.018393488147432f}, + new float[] {0.025206904397193f, 0.018470908767865f}, + new float[] {0.025150117819228f, 0.018548155533070f}, + new float[] {0.025093094518776f, 0.018625227715971f}, + new float[] {0.025035835032562f, 0.018702124591135f}, + new float[] {0.024978339899534f, 0.018778845434780f}, + new float[] {0.024920609660858f, 0.018855389524780f}, + new float[] {0.024862644859912f, 0.018931756140672f}, + new float[] {0.024804446042284f, 0.019007944563666f}, + new float[] {0.024746013755764f, 0.019083954076646f}, + new float[] {0.024687348550337f, 0.019159783964183f}, + new float[] {0.024628450978184f, 0.019235433512536f}, + new float[] {0.024569321593670f, 0.019310902009663f}, + new float[] {0.024509960953345f, 0.019386188745225f}, + new float[] {0.024450369615932f, 0.019461293010596f}, + new float[] {0.024390548142329f, 0.019536214098866f}, + new float[] {0.024330497095598f, 0.019610951304848f}, + new float[] {0.024270217040961f, 0.019685503925087f}, + new float[] {0.024209708545799f, 0.019759871257867f}, + new float[] {0.024148972179639f, 0.019834052603212f}, + new float[] {0.024088008514157f, 0.019908047262901f}, + new float[] {0.024026818123164f, 0.019981854540467f}, + new float[] {0.023965401582609f, 0.020055473741208f}, + new float[] {0.023903759470567f, 0.020128904172192f}, + new float[] {0.023841892367236f, 0.020202145142264f}, + new float[] {0.023779800854935f, 0.020275195962052f}, + new float[] {0.023717485518092f, 0.020348055943974f}, + new float[] {0.023654946943242f, 0.020420724402244f}, + new float[] {0.023592185719023f, 0.020493200652878f}, + new float[] {0.023529202436167f, 0.020565484013703f}, + new float[] {0.023465997687496f, 0.020637573804361f}, + new float[] {0.023402572067918f, 0.020709469346314f}, + new float[] {0.023338926174419f, 0.020781169962854f}, + new float[] {0.023275060606058f, 0.020852674979108f}, + new float[] {0.023210975963963f, 0.020923983722044f}, + new float[] {0.023146672851322f, 0.020995095520475f}, + new float[] {0.023082151873380f, 0.021066009705072f}, + new float[] {0.023017413637435f, 0.021136725608363f}, + new float[] {0.022952458752826f, 0.021207242564742f}, + new float[] {0.022887287830934f, 0.021277559910478f}, + new float[] {0.022821901485173f, 0.021347676983716f}, + new float[] {0.022756300330983f, 0.021417593124488f}, + new float[] {0.022690484985827f, 0.021487307674717f}, + new float[] {0.022624456069185f, 0.021556819978223f}, + new float[] {0.022558214202547f, 0.021626129380729f}, + new float[] {0.022491760009405f, 0.021695235229869f}, + new float[] {0.022425094115252f, 0.021764136875192f}, + new float[] {0.022358217147572f, 0.021832833668171f}, + new float[] {0.022291129735838f, 0.021901324962204f}, + new float[] {0.022223832511501f, 0.021969610112625f}, + new float[] {0.022156326107988f, 0.022037688476709f}, + new float[] {0.022088611160696f, 0.022105559413676f}, + new float[] {0.022020688306983f, 0.022173222284699f}, + new float[] {0.021952558186166f, 0.022240676452909f}, + new float[] {0.021884221439510f, 0.022307921283403f}, + new float[] {0.021815678710228f, 0.022374956143245f}, + new float[] {0.021746930643469f, 0.022441780401478f}, + new float[] {0.021677977886316f, 0.022508393429127f}, + new float[] {0.021608821087780f, 0.022574794599206f}, + new float[] {0.021539460898790f, 0.022640983286719f}, + new float[] {0.021469897972190f, 0.022706958868676f}, + new float[] {0.021400132962735f, 0.022772720724087f}, + new float[] {0.021330166527077f, 0.022838268233979f}, + new float[] {0.021259999323769f, 0.022903600781391f}, + new float[] {0.021189632013250f, 0.022968717751391f}, + new float[] {0.021119065257845f, 0.023033618531071f}, + new float[] {0.021048299721754f, 0.023098302509561f}, + new float[] {0.020977336071050f, 0.023162769078031f}, + new float[] {0.020906174973670f, 0.023227017629698f}, + new float[] {0.020834817099409f, 0.023291047559828f}, + new float[] {0.020763263119915f, 0.023354858265748f}, + new float[] {0.020691513708680f, 0.023418449146848f}, + new float[] {0.020619569541038f, 0.023481819604585f}, + new float[] {0.020547431294155f, 0.023544969042494f}, + new float[] {0.020475099647023f, 0.023607896866186f}, + new float[] {0.020402575280455f, 0.023670602483363f}, + new float[] {0.020329858877078f, 0.023733085303813f}, + new float[] {0.020256951121327f, 0.023795344739427f}, + new float[] {0.020183852699437f, 0.023857380204193f}, + new float[] {0.020110564299439f, 0.023919191114211f}, + new float[] {0.020037086611150f, 0.023980776887692f}, + new float[] {0.019963420326171f, 0.024042136944968f}, + new float[] {0.019889566137877f, 0.024103270708495f}, + new float[] {0.019815524741412f, 0.024164177602859f}, + new float[] {0.019741296833681f, 0.024224857054779f}, + new float[] {0.019666883113346f, 0.024285308493120f}, + new float[] {0.019592284280817f, 0.024345531348888f}, + new float[] {0.019517501038246f, 0.024405525055242f}, + new float[] {0.019442534089523f, 0.024465289047500f}, + new float[] {0.019367384140264f, 0.024524822763141f}, + new float[] {0.019292051897809f, 0.024584125641809f}, + new float[] {0.019216538071215f, 0.024643197125323f}, + new float[] {0.019140843371246f, 0.024702036657681f}, + new float[] {0.019064968510369f, 0.024760643685063f}, + new float[] {0.018988914202748f, 0.024819017655836f}, + new float[] {0.018912681164234f, 0.024877158020562f}, + new float[] {0.018836270112363f, 0.024935064232003f}, + new float[] {0.018759681766343f, 0.024992735745123f}, + new float[] {0.018682916847054f, 0.025050172017095f}, + new float[] {0.018605976077037f, 0.025107372507308f}, + new float[] {0.018528860180486f, 0.025164336677369f}, + new float[] {0.018451569883247f, 0.025221063991110f}, + new float[] {0.018374105912805f, 0.025277553914591f}, + new float[] {0.018296468998280f, 0.025333805916107f}, + new float[] {0.018218659870421f, 0.025389819466194f}, + new float[] {0.018140679261596f, 0.025445594037630f}, + new float[] {0.018062527905790f, 0.025501129105445f}, + new float[] {0.017984206538592f, 0.025556424146920f}, + new float[] {0.017905715897192f, 0.025611478641598f}, + new float[] {0.017827056720375f, 0.025666292071285f}, + new float[] {0.017748229748511f, 0.025720863920056f}, + new float[] {0.017669235723550f, 0.025775193674260f}, + new float[] {0.017590075389012f, 0.025829280822525f}, + new float[] {0.017510749489986f, 0.025883124855762f}, + new float[] {0.017431258773116f, 0.025936725267170f}, + new float[] {0.017351603986600f, 0.025990081552242f}, + new float[] {0.017271785880180f, 0.026043193208768f}, + new float[] {0.017191805205132f, 0.026096059736841f}, + new float[] {0.017111662714267f, 0.026148680638861f}, + new float[] {0.017031359161915f, 0.026201055419541f}, + new float[] {0.016950895303924f, 0.026253183585908f}, + new float[] {0.016870271897651f, 0.026305064647313f}, + new float[] {0.016789489701954f, 0.026356698115431f}, + new float[] {0.016708549477186f, 0.026408083504269f}, + new float[] {0.016627451985187f, 0.026459220330167f}, + new float[] {0.016546197989277f, 0.026510108111806f}, + new float[] {0.016464788254250f, 0.026560746370212f}, + new float[] {0.016383223546365f, 0.026611134628757f}, + new float[] {0.016301504633341f, 0.026661272413168f}, + new float[] {0.016219632284346f, 0.026711159251530f}, + new float[] {0.016137607269996f, 0.026760794674288f}, + new float[] {0.016055430362340f, 0.026810178214254f}, + new float[] {0.015973102334858f, 0.026859309406613f}, + new float[] {0.015890623962454f, 0.026908187788922f}, + new float[] {0.015807996021446f, 0.026956812901119f}, + new float[] {0.015725219289558f, 0.027005184285527f}, + new float[] {0.015642294545918f, 0.027053301486856f}, + new float[] {0.015559222571044f, 0.027101164052208f}, + new float[] {0.015476004146842f, 0.027148771531083f}, + new float[] {0.015392640056594f, 0.027196123475380f}, + new float[] {0.015309131084956f, 0.027243219439406f}, + new float[] {0.015225478017946f, 0.027290058979875f}, + new float[] {0.015141681642938f, 0.027336641655915f}, + new float[] {0.015057742748656f, 0.027382967029073f}, + new float[] {0.014973662125164f, 0.027429034663317f}, + new float[] {0.014889440563862f, 0.027474844125040f}, + new float[] {0.014805078857474f, 0.027520394983066f}, + new float[] {0.014720577800046f, 0.027565686808654f}, + new float[] {0.014635938186934f, 0.027610719175499f}, + new float[] {0.014551160814797f, 0.027655491659740f}, + new float[] {0.014466246481592f, 0.027700003839960f}, + new float[] {0.014381195986567f, 0.027744255297195f}, + new float[] {0.014296010130247f, 0.027788245614933f}, + new float[] {0.014210689714436f, 0.027831974379120f}, + new float[] {0.014125235542201f, 0.027875441178165f}, + new float[] {0.014039648417870f, 0.027918645602941f}, + new float[] {0.013953929147020f, 0.027961587246792f}, + new float[] {0.013868078536476f, 0.028004265705534f}, + new float[] {0.013782097394294f, 0.028046680577462f}, + new float[] {0.013695986529763f, 0.028088831463351f}, + new float[] {0.013609746753390f, 0.028130717966461f}, + new float[] {0.013523378876898f, 0.028172339692540f}, + new float[] {0.013436883713214f, 0.028213696249828f}, + new float[] {0.013350262076462f, 0.028254787249062f}, + new float[] {0.013263514781960f, 0.028295612303478f}, + new float[] {0.013176642646205f, 0.028336171028814f}, + new float[] {0.013089646486871f, 0.028376463043317f}, + new float[] {0.013002527122799f, 0.028416487967743f}, + new float[] {0.012915285373990f, 0.028456245425361f}, + new float[] {0.012827922061597f, 0.028495735041960f}, + new float[] {0.012740438007915f, 0.028534956445849f}, + new float[] {0.012652834036379f, 0.028573909267859f}, + new float[] {0.012565110971550f, 0.028612593141354f}, + new float[] {0.012477269639111f, 0.028651007702224f}, + new float[] {0.012389310865858f, 0.028689152588899f}, + new float[] {0.012301235479693f, 0.028727027442343f}, + new float[] {0.012213044309615f, 0.028764631906065f}, + new float[] {0.012124738185712f, 0.028801965626115f}, + new float[] {0.012036317939156f, 0.028839028251097f}, + new float[] {0.011947784402191f, 0.028875819432161f}, + new float[] {0.011859138408130f, 0.028912338823015f}, + new float[] {0.011770380791341f, 0.028948586079925f}, + new float[] {0.011681512387245f, 0.028984560861718f}, + new float[] {0.011592534032306f, 0.029020262829785f}, + new float[] {0.011503446564022f, 0.029055691648087f}, + new float[] {0.011414250820918f, 0.029090846983152f}, + new float[] {0.011324947642537f, 0.029125728504087f}, + new float[] {0.011235537869437f, 0.029160335882573f}, + new float[] {0.011146022343175f, 0.029194668792871f}, + new float[] {0.011056401906305f, 0.029228726911828f}, + new float[] {0.010966677402371f, 0.029262509918876f}, + new float[] {0.010876849675891f, 0.029296017496036f}, + new float[] {0.010786919572361f, 0.029329249327922f}, + new float[] {0.010696887938235f, 0.029362205101743f}, + new float[] {0.010606755620926f, 0.029394884507308f}, + new float[] {0.010516523468793f, 0.029427287237024f}, + new float[] {0.010426192331137f, 0.029459412985906f}, + new float[] {0.010335763058187f, 0.029491261451573f}, + new float[] {0.010245236501099f, 0.029522832334255f}, + new float[] {0.010154613511943f, 0.029554125336796f}, + new float[] {0.010063894943698f, 0.029585140164654f}, + new float[] {0.009973081650240f, 0.029615876525905f}, + new float[] {0.009882174486340f, 0.029646334131247f}, + new float[] {0.009791174307650f, 0.029676512694001f}, + new float[] {0.009700081970699f, 0.029706411930116f}, + new float[] {0.009608898332881f, 0.029736031558168f}, + new float[] {0.009517624252453f, 0.029765371299366f}, + new float[] {0.009426260588521f, 0.029794430877553f}, + new float[] {0.009334808201034f, 0.029823210019210f}, + new float[] {0.009243267950778f, 0.029851708453456f}, + new float[] {0.009151640699363f, 0.029879925912053f}, + new float[] {0.009059927309220f, 0.029907862129408f}, + new float[] {0.008968128643591f, 0.029935516842573f}, + new float[] {0.008876245566520f, 0.029962889791254f}, + new float[] {0.008784278942845f, 0.029989980717805f}, + new float[] {0.008692229638191f, 0.030016789367235f}, + new float[] {0.008600098518961f, 0.030043315487212f}, + new float[] {0.008507886452329f, 0.030069558828062f}, + new float[] {0.008415594306230f, 0.030095519142772f}, + new float[] {0.008323222949351f, 0.030121196186994f}, + new float[] {0.008230773251129f, 0.030146589719046f}, + new float[] {0.008138246081733f, 0.030171699499915f}, + new float[] {0.008045642312067f, 0.030196525293257f}, + new float[] {0.007952962813750f, 0.030221066865402f}, + new float[] {0.007860208459119f, 0.030245323985357f}, + new float[] {0.007767380121212f, 0.030269296424803f}, + new float[] {0.007674478673766f, 0.030292983958103f}, + new float[] {0.007581504991203f, 0.030316386362302f}, + new float[] {0.007488459948628f, 0.030339503417126f}, + new float[] {0.007395344421816f, 0.030362334904989f}, + new float[] {0.007302159287206f, 0.030384880610993f}, + new float[] {0.007208905421891f, 0.030407140322928f}, + new float[] {0.007115583703613f, 0.030429113831278f}, + new float[] {0.007022195010752f, 0.030450800929220f}, + new float[] {0.006928740222316f, 0.030472201412626f}, + new float[] {0.006835220217939f, 0.030493315080068f}, + new float[] {0.006741635877866f, 0.030514141732814f}, + new float[] {0.006647988082948f, 0.030534681174838f}, + new float[] {0.006554277714635f, 0.030554933212813f}, + new float[] {0.006460505654964f, 0.030574897656119f}, + new float[] {0.006366672786553f, 0.030594574316845f}, + new float[] {0.006272779992593f, 0.030613963009786f}, + new float[] {0.006178828156839f, 0.030633063552447f}, + new float[] {0.006084818163601f, 0.030651875765048f}, + new float[] {0.005990750897737f, 0.030670399470520f}, + new float[] {0.005896627244644f, 0.030688634494512f}, + new float[] {0.005802448090250f, 0.030706580665388f}, + new float[] {0.005708214321004f, 0.030724237814232f}, + new float[] {0.005613926823871f, 0.030741605774849f}, + new float[] {0.005519586486321f, 0.030758684383764f}, + new float[] {0.005425194196321f, 0.030775473480228f}, + new float[] {0.005330750842327f, 0.030791972906214f}, + new float[] {0.005236257313276f, 0.030808182506425f}, + new float[] {0.005141714498576f, 0.030824102128288f}, + new float[] {0.005047123288102f, 0.030839731621963f}, + new float[] {0.004952484572181f, 0.030855070840339f}, + new float[] {0.004857799241589f, 0.030870119639036f}, + new float[] {0.004763068187541f, 0.030884877876411f}, + new float[] {0.004668292301681f, 0.030899345413553f}, + new float[] {0.004573472476075f, 0.030913522114288f}, + new float[] {0.004478609603205f, 0.030927407845180f}, + new float[] {0.004383704575956f, 0.030941002475530f}, + new float[] {0.004288758287610f, 0.030954305877381f}, + new float[] {0.004193771631837f, 0.030967317925516f}, + new float[] {0.004098745502689f, 0.030980038497461f}, + new float[] {0.004003680794587f, 0.030992467473486f}, + new float[] {0.003908578402316f, 0.031004604736602f}, + new float[] {0.003813439221017f, 0.031016450172571f}, + new float[] {0.003718264146176f, 0.031028003669899f}, + new float[] {0.003623054073616f, 0.031039265119839f}, + new float[] {0.003527809899492f, 0.031050234416394f}, + new float[] {0.003432532520278f, 0.031060911456318f}, + new float[] {0.003337222832760f, 0.031071296139114f}, + new float[] {0.003241881734029f, 0.031081388367037f}, + new float[] {0.003146510121474f, 0.031091188045095f}, + new float[] {0.003051108892766f, 0.031100695081051f}, + new float[] {0.002955678945860f, 0.031109909385419f}, + new float[] {0.002860221178978f, 0.031118830871473f}, + new float[] {0.002764736490604f, 0.031127459455239f}, + new float[] {0.002669225779478f, 0.031135795055501f}, + new float[] {0.002573689944583f, 0.031143837593803f}, + new float[] {0.002478129885137f, 0.031151586994444f}, + new float[] {0.002382546500589f, 0.031159043184484f}, + new float[] {0.002286940690606f, 0.031166206093743f}, + new float[] {0.002191313355067f, 0.031173075654800f}, + new float[] {0.002095665394051f, 0.031179651802998f}, + new float[] {0.001999997707835f, 0.031185934476438f}, + new float[] {0.001904311196878f, 0.031191923615985f}, + new float[] {0.001808606761820f, 0.031197619165268f}, + new float[] {0.001712885303465f, 0.031203021070678f}, + new float[] {0.001617147722782f, 0.031208129281370f}, + new float[] {0.001521394920889f, 0.031212943749264f}, + new float[] {0.001425627799047f, 0.031217464429043f}, + new float[] {0.001329847258653f, 0.031221691278159f}, + new float[] {0.001234054201231f, 0.031225624256825f}, + new float[] {0.001138249528420f, 0.031229263328024f}, + new float[] {0.001042434141971f, 0.031232608457502f}, + new float[] {0.000946608943736f, 0.031235659613775f}, + new float[] {0.000850774835656f, 0.031238416768124f}, + new float[] {0.000754932719759f, 0.031240879894597f}, + new float[] {0.000659083498149f, 0.031243048970010f}, + new float[] {0.000563228072993f, 0.031244923973948f}, + new float[] {0.000467367346520f, 0.031246504888762f}, + new float[] {0.000371502221008f, 0.031247791699571f}, + new float[] {0.000275633598775f, 0.031248784394264f}, + new float[] {0.000179762382174f, 0.031249482963498f}, + new float[] {0.000083889473581f, 0.031249887400697f} + }; + public static float[][] MDCT_TABLE_128 = { + new float[] {0.088387931675923f, 0.000271171628935f}, + new float[] {0.088354655998507f, 0.002440238387037f}, + new float[] {0.088268158780110f, 0.004607835236780f}, + new float[] {0.088128492123423f, 0.006772656498875f}, + new float[] {0.087935740158418f, 0.008933398165942f}, + new float[] {0.087690018991670f, 0.011088758687994f}, + new float[] {0.087391476636423f, 0.013237439756448f}, + new float[] {0.087040292923427f, 0.015378147086172f}, + new float[] {0.086636679392621f, 0.017509591195118f}, + new float[] {0.086180879165703f, 0.019630488181053f}, + new float[] {0.085673166799686f, 0.021739560494940f}, + new float[] {0.085113848121515f, 0.023835537710479f}, + new float[] {0.084503260043847f, 0.025917157289369f}, + new float[] {0.083841770362110f, 0.027983165341813f}, + new float[] {0.083129777532952f, 0.030032317381813f}, + new float[] {0.082367710434230f, 0.032063379076803f}, + new float[] {0.081556028106671f, 0.034075126991164f}, + new float[] {0.080695219477356f, 0.036066349323177f}, + new float[] {0.079785803065216f, 0.038035846634965f}, + new float[] {0.078828326668693f, 0.039982432574992f}, + new float[] {0.077823367035766f, 0.041904934592675f}, + new float[] {0.076771529516540f, 0.043802194644686f}, + new float[] {0.075673447698606f, 0.045673069892513f}, + new float[] {0.074529783025390f, 0.047516433390863f}, + new float[] {0.073341224397728f, 0.049331174766491f}, + new float[] {0.072108487758894f, 0.051116200887052f}, + new float[] {0.070832315663343f, 0.052870436519557f}, + new float[] {0.069513476829429f, 0.054592824978055f}, + new float[] {0.068152765676348f, 0.056282328760143f}, + new float[] {0.066751001845620f, 0.057937930171918f}, + new float[] {0.065309029707361f, 0.059558631940996f}, + new float[] {0.063827717851668f, 0.061143457817234f}, + new float[] {0.062307958565413f, 0.062691453160784f}, + new float[] {0.060750667294763f, 0.064201685517134f}, + new float[] {0.059156782093749f, 0.065673245178784f}, + new float[] {0.057527263059216f, 0.067105245733220f}, + new float[] {0.055863091752499f, 0.068496824596852f}, + new float[] {0.054165270608165f, 0.069847143534609f}, + new float[] {0.052434822330188f, 0.071155389164853f}, + new float[] {0.050672789275903f, 0.072420773449336f}, + new float[] {0.048880232828135f, 0.073642534167879f}, + new float[] {0.047058232755862f, 0.074819935377512f}, + new float[] {0.045207886563797f, 0.075952267855771f}, + new float[] {0.043330308831298f, 0.077038849527912f}, + new float[] {0.041426630540984f, 0.078079025877766f}, + new float[] {0.039497998397473f, 0.079072170341994f}, + new float[] {0.037545574136653f, 0.080017684687506f}, + new float[] {0.035570533825892f, 0.080914999371817f}, + new float[] {0.033574067155622f, 0.081763573886112f}, + new float[] {0.031557376722714f, 0.082562897080836f}, + new float[] {0.029521677306074f, 0.083312487473584f}, + new float[] {0.027468195134911f, 0.084011893539132f}, + new float[] {0.025398167150101f, 0.084660693981419f}, + new float[] {0.023312840259098f, 0.085258497987320f}, + new float[] {0.021213470584847f, 0.085804945462053f}, + new float[] {0.019101322709138f, 0.086299707246093f}, + new float[] {0.016977668910873f, 0.086742485313442f}, + new float[] {0.014843788399692f, 0.087133012951149f}, + new float[] {0.012700966545425f, 0.087471054919968f}, + new float[] {0.010550494103830f, 0.087756407596056f}, + new float[] {0.008393666439096f, 0.087988899093631f}, + new float[] {0.006231782743558f, 0.088168389368510f}, + new float[] {0.004066145255116f, 0.088294770302461f}, + new float[] {0.001898058472816f, 0.088367965768336f} + }; + public static float[][] MDCT_TABLE_1920 = { + new float[] {0.032274858518097f, 0.000013202404176f}, + new float[] {0.032274642494505f, 0.000118821372483f}, + new float[] {0.032274080835421f, 0.000224439068308f}, + new float[] {0.032273173546860f, 0.000330054360572f}, + new float[] {0.032271920638538f, 0.000435666118218f}, + new float[] {0.032270322123873f, 0.000541273210231f}, + new float[] {0.032268378019984f, 0.000646874505642f}, + new float[] {0.032266088347691f, 0.000752468873546f}, + new float[] {0.032263453131514f, 0.000858055183114f}, + new float[] {0.032260472399674f, 0.000963632303600f}, + new float[] {0.032257146184092f, 0.001069199104358f}, + new float[] {0.032253474520390f, 0.001174754454853f}, + new float[] {0.032249457447888f, 0.001280297224671f}, + new float[] {0.032245095009606f, 0.001385826283535f}, + new float[] {0.032240387252262f, 0.001491340501313f}, + new float[] {0.032235334226272f, 0.001596838748031f}, + new float[] {0.032229935985750f, 0.001702319893890f}, + new float[] {0.032224192588507f, 0.001807782809271f}, + new float[] {0.032218104096050f, 0.001913226364749f}, + new float[] {0.032211670573582f, 0.002018649431111f}, + new float[] {0.032204892090000f, 0.002124050879359f}, + new float[] {0.032197768717898f, 0.002229429580728f}, + new float[] {0.032190300533560f, 0.002334784406698f}, + new float[] {0.032182487616965f, 0.002440114229003f}, + new float[] {0.032174330051782f, 0.002545417919644f}, + new float[] {0.032165827925374f, 0.002650694350905f}, + new float[] {0.032156981328790f, 0.002755942395358f}, + new float[] {0.032147790356771f, 0.002861160925883f}, + new float[] {0.032138255107744f, 0.002966348815672f}, + new float[] {0.032128375683825f, 0.003071504938250f}, + new float[] {0.032118152190814f, 0.003176628167476f}, + new float[] {0.032107584738196f, 0.003281717377568f}, + new float[] {0.032096673439141f, 0.003386771443102f}, + new float[] {0.032085418410500f, 0.003491789239036f}, + new float[] {0.032073819772804f, 0.003596769640711f}, + new float[] {0.032061877650267f, 0.003701711523874f}, + new float[] {0.032049592170778f, 0.003806613764680f}, + new float[] {0.032036963465906f, 0.003911475239711f}, + new float[] {0.032023991670893f, 0.004016294825985f}, + new float[] {0.032010676924657f, 0.004121071400967f}, + new float[] {0.031997019369789f, 0.004225803842586f}, + new float[] {0.031983019152549f, 0.004330491029241f}, + new float[] {0.031968676422869f, 0.004435131839816f}, + new float[] {0.031953991334348f, 0.004539725153692f}, + new float[] {0.031938964044252f, 0.004644269850758f}, + new float[] {0.031923594713510f, 0.004748764811426f}, + new float[] {0.031907883506716f, 0.004853208916638f}, + new float[] {0.031891830592124f, 0.004957601047881f}, + new float[] {0.031875436141648f, 0.005061940087200f}, + new float[] {0.031858700330859f, 0.005166224917208f}, + new float[] {0.031841623338985f, 0.005270454421097f}, + new float[] {0.031824205348907f, 0.005374627482653f}, + new float[] {0.031806446547156f, 0.005478742986267f}, + new float[] {0.031788347123916f, 0.005582799816945f}, + new float[] {0.031769907273017f, 0.005686796860323f}, + new float[] {0.031751127191935f, 0.005790733002674f}, + new float[] {0.031732007081789f, 0.005894607130928f}, + new float[] {0.031712547147340f, 0.005998418132675f}, + new float[] {0.031692747596989f, 0.006102164896182f}, + new float[] {0.031672608642773f, 0.006205846310406f}, + new float[] {0.031652130500364f, 0.006309461265002f}, + new float[] {0.031631313389067f, 0.006413008650337f}, + new float[] {0.031610157531816f, 0.006516487357501f}, + new float[] {0.031588663155172f, 0.006619896278321f}, + new float[] {0.031566830489325f, 0.006723234305370f}, + new float[] {0.031544659768083f, 0.006826500331981f}, + new float[] {0.031522151228878f, 0.006929693252258f}, + new float[] {0.031499305112758f, 0.007032811961088f}, + new float[] {0.031476121664387f, 0.007135855354151f}, + new float[] {0.031452601132040f, 0.007238822327937f}, + new float[] {0.031428743767604f, 0.007341711779751f}, + new float[] {0.031404549826572f, 0.007444522607730f}, + new float[] {0.031380019568042f, 0.007547253710853f}, + new float[] {0.031355153254712f, 0.007649903988952f}, + new float[] {0.031329951152882f, 0.007752472342725f}, + new float[] {0.031304413532445f, 0.007854957673748f}, + new float[] {0.031278540666888f, 0.007957358884484f}, + new float[] {0.031252332833290f, 0.008059674878300f}, + new float[] {0.031225790312316f, 0.008161904559473f}, + new float[] {0.031198913388214f, 0.008264046833205f}, + new float[] {0.031171702348814f, 0.008366100605636f}, + new float[] {0.031144157485525f, 0.008468064783849f}, + new float[] {0.031116279093331f, 0.008569938275893f}, + new float[] {0.031088067470786f, 0.008671719990782f}, + new float[] {0.031059522920014f, 0.008773408838517f}, + new float[] {0.031030645746705f, 0.008875003730092f}, + new float[] {0.031001436260110f, 0.008976503577507f}, + new float[] {0.030971894773039f, 0.009077907293780f}, + new float[] {0.030942021601857f, 0.009179213792959f}, + new float[] {0.030911817066483f, 0.009280421990133f}, + new float[] {0.030881281490382f, 0.009381530801444f}, + new float[] {0.030850415200566f, 0.009482539144097f}, + new float[] {0.030819218527589f, 0.009583445936373f}, + new float[] {0.030787691805541f, 0.009684250097643f}, + new float[] {0.030755835372048f, 0.009784950548375f}, + new float[] {0.030723649568268f, 0.009885546210147f}, + new float[] {0.030691134738883f, 0.009986036005661f}, + new float[] {0.030658291232103f, 0.010086418858753f}, + new float[] {0.030625119399655f, 0.010186693694402f}, + new float[] {0.030591619596781f, 0.010286859438745f}, + new float[] {0.030557792182239f, 0.010386915019088f}, + new float[] {0.030523637518292f, 0.010486859363916f}, + new float[] {0.030489155970710f, 0.010586691402906f}, + new float[] {0.030454347908763f, 0.010686410066936f}, + new float[] {0.030419213705216f, 0.010786014288099f}, + new float[] {0.030383753736329f, 0.010885502999714f}, + new float[] {0.030347968381849f, 0.010984875136338f}, + new float[] {0.030311858025010f, 0.011084129633775f}, + new float[] {0.030275423052523f, 0.011183265429088f}, + new float[] {0.030238663854579f, 0.011282281460612f}, + new float[] {0.030201580824838f, 0.011381176667967f}, + new float[] {0.030164174360430f, 0.011479949992062f}, + new float[] {0.030126444861948f, 0.011578600375117f}, + new float[] {0.030088392733446f, 0.011677126760663f}, + new float[] {0.030050018382430f, 0.011775528093563f}, + new float[] {0.030011322219859f, 0.011873803320018f}, + new float[] {0.029972304660138f, 0.011971951387578f}, + new float[] {0.029932966121114f, 0.012069971245157f}, + new float[] {0.029893307024070f, 0.012167861843041f}, + new float[] {0.029853327793724f, 0.012265622132901f}, + new float[] {0.029813028858222f, 0.012363251067801f}, + new float[] {0.029772410649132f, 0.012460747602215f}, + new float[] {0.029731473601443f, 0.012558110692033f}, + new float[] {0.029690218153558f, 0.012655339294575f}, + new float[] {0.029648644747289f, 0.012752432368600f}, + new float[] {0.029606753827855f, 0.012849388874320f}, + new float[] {0.029564545843872f, 0.012946207773407f}, + new float[] {0.029522021247356f, 0.013042888029011f}, + new float[] {0.029479180493710f, 0.013139428605762f}, + new float[] {0.029436024041725f, 0.013235828469789f}, + new float[] {0.029392552353570f, 0.013332086588727f}, + new float[] {0.029348765894794f, 0.013428201931728f}, + new float[] {0.029304665134313f, 0.013524173469475f}, + new float[] {0.029260250544412f, 0.013620000174189f}, + new float[] {0.029215522600735f, 0.013715681019643f}, + new float[] {0.029170481782283f, 0.013811214981173f}, + new float[] {0.029125128571406f, 0.013906601035686f}, + new float[] {0.029079463453801f, 0.014001838161674f}, + new float[] {0.029033486918505f, 0.014096925339225f}, + new float[] {0.028987199457889f, 0.014191861550031f}, + new float[] {0.028940601567655f, 0.014286645777401f}, + new float[] {0.028893693746829f, 0.014381277006273f}, + new float[] {0.028846476497755f, 0.014475754223221f}, + new float[] {0.028798950326094f, 0.014570076416472f}, + new float[] {0.028751115740811f, 0.014664242575910f}, + new float[] {0.028702973254178f, 0.014758251693091f}, + new float[] {0.028654523381760f, 0.014852102761253f}, + new float[] {0.028605766642418f, 0.014945794775326f}, + new float[] {0.028556703558297f, 0.015039326731945f}, + new float[] {0.028507334654823f, 0.015132697629457f}, + new float[] {0.028457660460698f, 0.015225906467935f}, + new float[] {0.028407681507891f, 0.015318952249187f}, + new float[] {0.028357398331639f, 0.015411833976768f}, + new float[] {0.028306811470432f, 0.015504550655988f}, + new float[] {0.028255921466016f, 0.015597101293927f}, + new float[] {0.028204728863381f, 0.015689484899442f}, + new float[] {0.028153234210760f, 0.015781700483179f}, + new float[] {0.028101438059619f, 0.015873747057582f}, + new float[] {0.028049340964652f, 0.015965623636907f}, + new float[] {0.027996943483779f, 0.016057329237229f}, + new float[] {0.027944246178133f, 0.016148862876456f}, + new float[] {0.027891249612061f, 0.016240223574335f}, + new float[] {0.027837954353113f, 0.016331410352467f}, + new float[] {0.027784360972039f, 0.016422422234315f}, + new float[] {0.027730470042780f, 0.016513258245214f}, + new float[] {0.027676282142466f, 0.016603917412384f}, + new float[] {0.027621797851405f, 0.016694398764938f}, + new float[] {0.027567017753080f, 0.016784701333894f}, + new float[] {0.027511942434143f, 0.016874824152183f}, + new float[] {0.027456572484404f, 0.016964766254662f}, + new float[] {0.027400908496833f, 0.017054526678124f}, + new float[] {0.027344951067546f, 0.017144104461307f}, + new float[] {0.027288700795801f, 0.017233498644904f}, + new float[] {0.027232158283994f, 0.017322708271577f}, + new float[] {0.027175324137651f, 0.017411732385960f}, + new float[] {0.027118198965418f, 0.017500570034678f}, + new float[] {0.027060783379060f, 0.017589220266351f}, + new float[] {0.027003077993454f, 0.017677682131607f}, + new float[] {0.026945083426576f, 0.017765954683088f}, + new float[] {0.026886800299502f, 0.017854036975468f}, + new float[] {0.026828229236397f, 0.017941928065456f}, + new float[] {0.026769370864511f, 0.018029627011808f}, + new float[] {0.026710225814170f, 0.018117132875340f}, + new float[] {0.026650794718768f, 0.018204444718934f}, + new float[] {0.026591078214767f, 0.018291561607551f}, + new float[] {0.026531076941680f, 0.018378482608238f}, + new float[] {0.026470791542075f, 0.018465206790142f}, + new float[] {0.026410222661558f, 0.018551733224515f}, + new float[] {0.026349370948775f, 0.018638060984730f}, + new float[] {0.026288237055398f, 0.018724189146286f}, + new float[] {0.026226821636121f, 0.018810116786819f}, + new float[] {0.026165125348656f, 0.018895842986112f}, + new float[] {0.026103148853718f, 0.018981366826109f}, + new float[] {0.026040892815028f, 0.019066687390916f}, + new float[] {0.025978357899296f, 0.019151803766819f}, + new float[] {0.025915544776223f, 0.019236715042290f}, + new float[] {0.025852454118485f, 0.019321420307998f}, + new float[] {0.025789086601733f, 0.019405918656817f}, + new float[] {0.025725442904582f, 0.019490209183837f}, + new float[] {0.025661523708606f, 0.019574290986376f}, + new float[] {0.025597329698327f, 0.019658163163984f}, + new float[] {0.025532861561211f, 0.019741824818458f}, + new float[] {0.025468119987662f, 0.019825275053848f}, + new float[] {0.025403105671008f, 0.019908512976470f}, + new float[] {0.025337819307501f, 0.019991537694913f}, + new float[] {0.025272261596305f, 0.020074348320047f}, + new float[] {0.025206433239491f, 0.020156943965039f}, + new float[] {0.025140334942028f, 0.020239323745355f}, + new float[] {0.025073967411776f, 0.020321486778774f}, + new float[] {0.025007331359476f, 0.020403432185395f}, + new float[] {0.024940427498748f, 0.020485159087650f}, + new float[] {0.024873256546079f, 0.020566666610309f}, + new float[] {0.024805819220816f, 0.020647953880491f}, + new float[] {0.024738116245157f, 0.020729020027676f}, + new float[] {0.024670148344147f, 0.020809864183709f}, + new float[] {0.024601916245669f, 0.020890485482816f}, + new float[] {0.024533420680433f, 0.020970883061607f}, + new float[] {0.024464662381971f, 0.021051056059087f}, + new float[] {0.024395642086630f, 0.021131003616670f}, + new float[] {0.024326360533561f, 0.021210724878181f}, + new float[] {0.024256818464715f, 0.021290218989868f}, + new float[] {0.024187016624830f, 0.021369485100415f}, + new float[] {0.024116955761430f, 0.021448522360944f}, + new float[] {0.024046636624808f, 0.021527329925030f}, + new float[] {0.023976059968027f, 0.021605906948708f}, + new float[] {0.023905226546906f, 0.021684252590480f}, + new float[] {0.023834137120014f, 0.021762366011328f}, + new float[] {0.023762792448662f, 0.021840246374720f}, + new float[] {0.023691193296893f, 0.021917892846620f}, + new float[] {0.023619340431478f, 0.021995304595495f}, + new float[] {0.023547234621902f, 0.022072480792330f}, + new float[] {0.023474876640361f, 0.022149420610628f}, + new float[] {0.023402267261751f, 0.022226123226426f}, + new float[] {0.023329407263659f, 0.022302587818300f}, + new float[] {0.023256297426359f, 0.022378813567377f}, + new float[] {0.023182938532797f, 0.022454799657339f}, + new float[] {0.023109331368588f, 0.022530545274437f}, + new float[] {0.023035476722006f, 0.022606049607496f}, + new float[] {0.022961375383975f, 0.022681311847926f}, + new float[] {0.022887028148061f, 0.022756331189727f}, + new float[] {0.022812435810462f, 0.022831106829504f}, + new float[] {0.022737599170003f, 0.022905637966469f}, + new float[] {0.022662519028125f, 0.022979923802453f}, + new float[] {0.022587196188874f, 0.023053963541915f}, + new float[] {0.022511631458899f, 0.023127756391950f}, + new float[] {0.022435825647437f, 0.023201301562294f}, + new float[] {0.022359779566306f, 0.023274598265338f}, + new float[] {0.022283494029900f, 0.023347645716133f}, + new float[] {0.022206969855176f, 0.023420443132400f}, + new float[] {0.022130207861645f, 0.023492989734537f}, + new float[] {0.022053208871367f, 0.023565284745628f}, + new float[] {0.021975973708940f, 0.023637327391451f}, + new float[] {0.021898503201489f, 0.023709116900488f}, + new float[] {0.021820798178663f, 0.023780652503931f}, + new float[] {0.021742859472618f, 0.023851933435691f}, + new float[] {0.021664687918017f, 0.023922958932406f}, + new float[] {0.021586284352013f, 0.023993728233451f}, + new float[] {0.021507649614247f, 0.024064240580942f}, + new float[] {0.021428784546832f, 0.024134495219750f}, + new float[] {0.021349689994350f, 0.024204491397504f}, + new float[] {0.021270366803840f, 0.024274228364600f}, + new float[] {0.021190815824791f, 0.024343705374213f}, + new float[] {0.021111037909128f, 0.024412921682298f}, + new float[] {0.021031033911210f, 0.024481876547605f}, + new float[] {0.020950804687815f, 0.024550569231683f}, + new float[] {0.020870351098134f, 0.024618998998889f}, + new float[] {0.020789674003759f, 0.024687165116394f}, + new float[] {0.020708774268678f, 0.024755066854194f}, + new float[] {0.020627652759262f, 0.024822703485116f}, + new float[] {0.020546310344257f, 0.024890074284826f}, + new float[] {0.020464747894775f, 0.024957178531837f}, + new float[] {0.020382966284284f, 0.025024015507516f}, + new float[] {0.020300966388600f, 0.025090584496093f}, + new float[] {0.020218749085876f, 0.025156884784668f}, + new float[] {0.020136315256592f, 0.025222915663218f}, + new float[] {0.020053665783549f, 0.025288676424605f}, + new float[] {0.019970801551857f, 0.025354166364584f}, + new float[] {0.019887723448925f, 0.025419384781811f}, + new float[] {0.019804432364452f, 0.025484330977848f}, + new float[] {0.019720929190419f, 0.025549004257175f}, + new float[] {0.019637214821078f, 0.025613403927192f}, + new float[] {0.019553290152943f, 0.025677529298230f}, + new float[] {0.019469156084779f, 0.025741379683559f}, + new float[] {0.019384813517595f, 0.025804954399392f}, + new float[] {0.019300263354632f, 0.025868252764895f}, + new float[] {0.019215506501354f, 0.025931274102193f}, + new float[] {0.019130543865439f, 0.025994017736379f}, + new float[] {0.019045376356769f, 0.026056482995518f}, + new float[] {0.018960004887419f, 0.026118669210657f}, + new float[] {0.018874430371648f, 0.026180575715833f}, + new float[] {0.018788653725892f, 0.026242201848076f}, + new float[] {0.018702675868750f, 0.026303546947421f}, + new float[] {0.018616497720974f, 0.026364610356909f}, + new float[] {0.018530120205464f, 0.026425391422602f}, + new float[] {0.018443544247254f, 0.026485889493583f}, + new float[] {0.018356770773502f, 0.026546103921965f}, + new float[] {0.018269800713483f, 0.026606034062902f}, + new float[] {0.018182634998576f, 0.026665679274589f}, + new float[] {0.018095274562256f, 0.026725038918274f}, + new float[] {0.018007720340083f, 0.026784112358263f}, + new float[] {0.017919973269692f, 0.026842898961926f}, + new float[] {0.017832034290785f, 0.026901398099707f}, + new float[] {0.017743904345116f, 0.026959609145127f}, + new float[] {0.017655584376488f, 0.027017531474792f}, + new float[] {0.017567075330734f, 0.027075164468401f}, + new float[] {0.017478378155718f, 0.027132507508750f}, + new float[] {0.017389493801313f, 0.027189559981742f}, + new float[] {0.017300423219401f, 0.027246321276391f}, + new float[] {0.017211167363854f, 0.027302790784828f}, + new float[] {0.017121727190533f, 0.027358967902310f}, + new float[] {0.017032103657269f, 0.027414852027226f}, + new float[] {0.016942297723858f, 0.027470442561102f}, + new float[] {0.016852310352050f, 0.027525738908608f}, + new float[] {0.016762142505537f, 0.027580740477564f}, + new float[] {0.016671795149944f, 0.027635446678948f}, + new float[] {0.016581269252819f, 0.027689856926900f}, + new float[] {0.016490565783622f, 0.027743970638730f}, + new float[] {0.016399685713714f, 0.027797787234924f}, + new float[] {0.016308630016347f, 0.027851306139149f}, + new float[] {0.016217399666655f, 0.027904526778260f}, + new float[] {0.016125995641641f, 0.027957448582309f}, + new float[] {0.016034418920170f, 0.028010070984544f}, + new float[] {0.015942670482954f, 0.028062393421421f}, + new float[] {0.015850751312545f, 0.028114415332610f}, + new float[] {0.015758662393324f, 0.028166136160998f}, + new float[] {0.015666404711489f, 0.028217555352697f}, + new float[] {0.015573979255046f, 0.028268672357047f}, + new float[] {0.015481387013797f, 0.028319486626627f}, + new float[] {0.015388628979331f, 0.028369997617257f}, + new float[] {0.015295706145012f, 0.028420204788004f}, + new float[] {0.015202619505968f, 0.028470107601191f}, + new float[] {0.015109370059084f, 0.028519705522399f}, + new float[] {0.015015958802984f, 0.028568998020472f}, + new float[] {0.014922386738030f, 0.028617984567529f}, + new float[] {0.014828654866302f, 0.028666664638963f}, + new float[] {0.014734764191593f, 0.028715037713449f}, + new float[] {0.014640715719398f, 0.028763103272951f}, + new float[] {0.014546510456900f, 0.028810860802724f}, + new float[] {0.014452149412962f, 0.028858309791325f}, + new float[] {0.014357633598114f, 0.028905449730613f}, + new float[] {0.014262964024545f, 0.028952280115756f}, + new float[] {0.014168141706090f, 0.028998800445240f}, + new float[] {0.014073167658220f, 0.029045010220868f}, + new float[] {0.013978042898030f, 0.029090908947771f}, + new float[] {0.013882768444231f, 0.029136496134411f}, + new float[] {0.013787345317136f, 0.029181771292585f}, + new float[] {0.013691774538648f, 0.029226733937433f}, + new float[] {0.013596057132255f, 0.029271383587441f}, + new float[] {0.013500194123014f, 0.029315719764447f}, + new float[] {0.013404186537539f, 0.029359741993647f}, + new float[] {0.013308035403995f, 0.029403449803598f}, + new float[] {0.013211741752084f, 0.029446842726223f}, + new float[] {0.013115306613032f, 0.029489920296820f}, + new float[] {0.013018731019584f, 0.029532682054063f}, + new float[] {0.012922016005985f, 0.029575127540008f}, + new float[] {0.012825162607977f, 0.029617256300097f}, + new float[] {0.012728171862781f, 0.029659067883165f}, + new float[] {0.012631044809089f, 0.029700561841444f}, + new float[] {0.012533782487056f, 0.029741737730567f}, + new float[] {0.012436385938281f, 0.029782595109573f}, + new float[] {0.012338856205805f, 0.029823133540913f}, + new float[] {0.012241194334091f, 0.029863352590452f}, + new float[] {0.012143401369021f, 0.029903251827477f}, + new float[] {0.012045478357878f, 0.029942830824699f}, + new float[] {0.011947426349339f, 0.029982089158259f}, + new float[] {0.011849246393462f, 0.030021026407731f}, + new float[] {0.011750939541676f, 0.030059642156129f}, + new float[] {0.011652506846768f, 0.030097935989909f}, + new float[] {0.011553949362874f, 0.030135907498976f}, + new float[] {0.011455268145464f, 0.030173556276684f}, + new float[] {0.011356464251335f, 0.030210881919845f}, + new float[] {0.011257538738598f, 0.030247884028732f}, + new float[] {0.011158492666665f, 0.030284562207083f}, + new float[] {0.011059327096240f, 0.030320916062102f}, + new float[] {0.010960043089307f, 0.030356945204470f}, + new float[] {0.010860641709118f, 0.030392649248343f}, + new float[] {0.010761124020182f, 0.030428027811361f}, + new float[] {0.010661491088253f, 0.030463080514646f}, + new float[] {0.010561743980319f, 0.030497806982812f}, + new float[] {0.010461883764593f, 0.030532206843968f}, + new float[] {0.010361911510496f, 0.030566279729717f}, + new float[] {0.010261828288652f, 0.030600025275167f}, + new float[] {0.010161635170872f, 0.030633443118931f}, + new float[] {0.010061333230142f, 0.030666532903129f}, + new float[] {0.009960923540617f, 0.030699294273397f}, + new float[] {0.009860407177603f, 0.030731726878888f}, + new float[] {0.009759785217550f, 0.030763830372273f}, + new float[] {0.009659058738038f, 0.030795604409750f}, + new float[] {0.009558228817767f, 0.030827048651045f}, + new float[] {0.009457296536545f, 0.030858162759415f}, + new float[] {0.009356262975275f, 0.030888946401653f}, + new float[] {0.009255129215945f, 0.030919399248091f}, + new float[] {0.009153896341616f, 0.030949520972603f}, + new float[] {0.009052565436412f, 0.030979311252611f}, + new float[] {0.008951137585505f, 0.031008769769084f}, + new float[] {0.008849613875105f, 0.031037896206544f}, + new float[] {0.008747995392451f, 0.031066690253072f}, + new float[] {0.008646283225794f, 0.031095151600306f}, + new float[] {0.008544478464390f, 0.031123279943448f}, + new float[] {0.008442582198486f, 0.031151074981266f}, + new float[] {0.008340595519310f, 0.031178536416098f}, + new float[] {0.008238519519057f, 0.031205663953853f}, + new float[] {0.008136355290878f, 0.031232457304017f}, + new float[] {0.008034103928871f, 0.031258916179656f}, + new float[] {0.007931766528065f, 0.031285040297416f}, + new float[] {0.007829344184412f, 0.031310829377528f}, + new float[] {0.007726837994772f, 0.031336283143813f}, + new float[] {0.007624249056906f, 0.031361401323680f}, + new float[] {0.007521578469457f, 0.031386183648135f}, + new float[] {0.007418827331946f, 0.031410629851778f}, + new float[] {0.007315996744755f, 0.031434739672811f}, + new float[] {0.007213087809115f, 0.031458512853036f}, + new float[] {0.007110101627101f, 0.031481949137863f}, + new float[] {0.007007039301610f, 0.031505048276306f}, + new float[] {0.006903901936357f, 0.031527810020993f}, + new float[] {0.006800690635862f, 0.031550234128164f}, + new float[] {0.006697406505433f, 0.031572320357675f}, + new float[] {0.006594050651161f, 0.031594068473000f}, + new float[] {0.006490624179905f, 0.031615478241233f}, + new float[] {0.006387128199278f, 0.031636549433095f}, + new float[] {0.006283563817639f, 0.031657281822929f}, + new float[] {0.006179932144080f, 0.031677675188707f}, + new float[] {0.006076234288412f, 0.031697729312034f}, + new float[] {0.005972471361157f, 0.031717443978146f}, + new float[] {0.005868644473532f, 0.031736818975914f}, + new float[] {0.005764754737440f, 0.031755854097848f}, + new float[] {0.005660803265456f, 0.031774549140098f}, + new float[] {0.005556791170816f, 0.031792903902453f}, + new float[] {0.005452719567407f, 0.031810918188350f}, + new float[] {0.005348589569753f, 0.031828591804869f}, + new float[] {0.005244402293001f, 0.031845924562742f}, + new float[] {0.005140158852914f, 0.031862916276347f}, + new float[] {0.005035860365855f, 0.031879566763717f}, + new float[] {0.004931507948778f, 0.031895875846539f}, + new float[] {0.004827102719212f, 0.031911843350155f}, + new float[] {0.004722645795254f, 0.031927469103567f}, + new float[] {0.004618138295554f, 0.031942752939435f}, + new float[] {0.004513581339303f, 0.031957694694082f}, + new float[] {0.004408976046222f, 0.031972294207493f}, + new float[] {0.004304323536549f, 0.031986551323320f}, + new float[] {0.004199624931030f, 0.032000465888879f}, + new float[] {0.004094881350902f, 0.032014037755158f}, + new float[] {0.003990093917884f, 0.032027266776813f}, + new float[] {0.003885263754166f, 0.032040152812170f}, + new float[] {0.003780391982394f, 0.032052695723232f}, + new float[] {0.003675479725661f, 0.032064895375674f}, + new float[] {0.003570528107494f, 0.032076751638847f}, + new float[] {0.003465538251839f, 0.032088264385780f}, + new float[] {0.003360511283053f, 0.032099433493181f}, + new float[] {0.003255448325892f, 0.032110258841438f}, + new float[] {0.003150350505494f, 0.032120740314619f}, + new float[] {0.003045218947373f, 0.032130877800478f}, + new float[] {0.002940054777404f, 0.032140671190449f}, + new float[] {0.002834859121810f, 0.032150120379653f}, + new float[] {0.002729633107153f, 0.032159225266897f}, + new float[] {0.002624377860318f, 0.032167985754674f}, + new float[] {0.002519094508504f, 0.032176401749168f}, + new float[] {0.002413784179212f, 0.032184473160250f}, + new float[] {0.002308448000231f, 0.032192199901481f}, + new float[] {0.002203087099626f, 0.032199581890114f}, + new float[] {0.002097702605728f, 0.032206619047093f}, + new float[] {0.001992295647121f, 0.032213311297057f}, + new float[] {0.001886867352628f, 0.032219658568338f}, + new float[] {0.001781418851302f, 0.032225660792960f}, + new float[] {0.001675951272410f, 0.032231317906644f}, + new float[] {0.001570465745428f, 0.032236629848809f}, + new float[] {0.001464963400018f, 0.032241596562566f}, + new float[] {0.001359445366028f, 0.032246217994727f}, + new float[] {0.001253912773470f, 0.032250494095799f}, + new float[] {0.001148366752513f, 0.032254424819990f}, + new float[] {0.001042808433471f, 0.032258010125204f}, + new float[] {0.000937238946789f, 0.032261249973045f}, + new float[] {0.000831659423030f, 0.032264144328817f}, + new float[] {0.000726070992868f, 0.032266693161525f}, + new float[] {0.000620474787068f, 0.032268896443871f}, + new float[] {0.000514871936481f, 0.032270754152261f}, + new float[] {0.000409263572030f, 0.032272266266801f}, + new float[] {0.000303650824695f, 0.032273432771295f}, + new float[] {0.000198034825504f, 0.032274253653254f}, + new float[] {0.000092416705518f, 0.032274728903884f} + }; + public static float[][] MDCT_TABLE_240 = { + new float[] {0.091286604111815f, 0.000298735779793f}, + new float[] {0.091247502481454f, 0.002688238127538f}, + new float[] {0.091145864370807f, 0.005075898091152f}, + new float[] {0.090981759437558f, 0.007460079287760f}, + new float[] {0.090755300151030f, 0.009839147718664f}, + new float[] {0.090466641715108f, 0.012211472889198f}, + new float[] {0.090115981961863f, 0.014575428926191f}, + new float[] {0.089703561215976f, 0.016929395692256f}, + new float[] {0.089229662130024f, 0.019271759896156f}, + new float[] {0.088694609490769f, 0.021600916198470f}, + new float[] {0.088098769996564f, 0.023915268311810f}, + new float[] {0.087442552006035f, 0.026213230094844f}, + new float[] {0.086726405258214f, 0.028493226639351f}, + new float[] {0.085950820564309f, 0.030753695349588f}, + new float[] {0.085116329471329f, 0.032993087013213f}, + new float[] {0.084223503897785f, 0.035209866863042f}, + new float[] {0.083272955741727f, 0.037402515628894f}, + new float[] {0.082265336461381f, 0.039569530578832f}, + new float[] {0.081201336628670f, 0.041709426549053f}, + new float[] {0.080081685455930f, 0.043820736961749f}, + new float[] {0.078907150296148f, 0.045902014830227f}, + new float[] {0.077678536117054f, 0.047951833750597f}, + new float[] {0.076396684949434f, 0.049968788879362f}, + new float[] {0.075062475310050f, 0.051951497896226f}, + new float[] {0.073676821599542f, 0.053898601951466f}, + new float[] {0.072240673475749f, 0.055808766597225f}, + new float[] {0.070755015202858f, 0.057680682702068f}, + new float[] {0.069220864976840f, 0.059513067348201f}, + new float[] {0.067639274227625f, 0.061304664710718f}, + new float[] {0.066011326898512f, 0.063054246918278f}, + new float[] {0.064338138703282f, 0.064760614894630f}, + new float[] {0.062620856361546f, 0.066422599180399f}, + new float[] {0.060860656812842f, 0.068039060734572f}, + new float[] {0.059058746410016f, 0.069608891715145f}, + new float[] {0.057216360092450f, 0.071131016238378f}, + new float[] {0.055334760539699f, 0.072604391116154f}, + new float[] {0.053415237306106f, 0.074028006570930f}, + new float[] {0.051459105937014f, 0.075400886927784f}, + new float[] {0.049467707067153f, 0.076722091283096f}, + new float[] {0.047442405501835f, 0.077990714149396f}, + new float[] {0.045384589281588f, 0.079205886075941f}, + new float[] {0.043295668730857f, 0.080366774244592f}, + new float[] {0.041177075491445f, 0.081472583040586f}, + new float[] {0.039030261541332f, 0.082522554597810f}, + new float[] {0.036856698199564f, 0.083515969318206f}, + new float[] {0.034657875117883f, 0.084452146364948f}, + new float[] {0.032435299259796f, 0.085330444129049f}, + new float[] {0.030190493867775f, 0.086150260669096f}, + new float[] {0.027924997419306f, 0.086911034123781f}, + new float[] {0.025640362572491f, 0.087612243096981f}, + new float[] {0.023338155101933f, 0.088253407015092f}, + new float[] {0.021019952825636f, 0.088834086456390f}, + new float[] {0.018687344523641f, 0.089353883452193f}, + new float[] {0.016341928849164f, 0.089812441759604f}, + new float[] {0.013985313232951f, 0.090209447105664f}, + new float[] {0.011619112781631f, 0.090544627402740f}, + new float[] {0.009244949170797f, 0.090817752935000f}, + new float[] {0.006864449533597f, 0.091028636515846f}, + new float[] {0.004479245345574f, 0.091177133616206f}, + new float[] {0.002090971306534f, 0.091263142463585f} + }; + } +} diff --git a/SharpJaad.AAC/Filterbank/SineWindows.cs b/SharpJaad.AAC/Filterbank/SineWindows.cs new file mode 100644 index 0000000..16558b2 --- /dev/null +++ b/SharpJaad.AAC/Filterbank/SineWindows.cs @@ -0,0 +1,2246 @@ +namespace SharpJaad.AAC.Filterbank +{ + public static class SineWindows + { + public static float[] SINE_1024 = { + 0.00076699031874270449f, + 0.002300969151425805f, + 0.0038349425697062275f, + 0.0053689069639963425f, + 0.0069028587247297558f, + 0.0084367942423697988f, + 0.0099707099074180308f, + 0.011504602110422714f, + 0.013038467241987334f, + 0.014572301692779064f, + 0.016106101853537287f, + 0.017639864115082053f, + 0.019173584868322623f, + 0.020707260504265895f, + 0.022240887414024961f, + 0.023774461988827555f, + 0.025307980620024571f, + 0.026841439699098531f, + 0.028374835617672099f, + 0.029908164767516555f, + 0.031441423540560301f, + 0.032974608328897335f, + 0.03450771552479575f, + 0.036040741520706229f, + 0.037573682709270494f, + 0.039106535483329888f, + 0.040639296235933736f, + 0.042171961360347947f, + 0.043704527250063421f, + 0.04523699029880459f, + 0.046769346900537863f, + 0.048301593449480144f, + 0.049833726340107277f, + 0.051365741967162593f, + 0.052897636725665324f, + 0.054429407010919133f, + 0.055961049218520569f, + 0.057492559744367566f, + 0.059023934984667931f, + 0.060555171335947788f, + 0.062086265195060088f, + 0.063617212959193106f, + 0.065148011025878833f, + 0.066678655793001557f, + 0.068209143658806329f, + 0.069739471021907307f, + 0.071269634281296401f, + 0.072799629836351673f, + 0.074329454086845756f, + 0.075859103432954447f, + 0.077388574275265049f, + 0.078917863014784942f, + 0.080446966052950014f, + 0.081975879791633066f, + 0.083504600633152432f, + 0.085033124980280275f, + 0.08656144923625117f, + 0.088089569804770507f, + 0.089617483090022959f, + 0.091145185496681005f, + 0.09267267342991331f, + 0.094199943295393204f, + 0.095726991499307162f, + 0.097253814448363271f, + 0.098780408549799623f, + 0.10030677021139286f, + 0.10183289584146653f, + 0.10335878184889961f, + 0.10488442464313497f, + 0.10640982063418768f, + 0.10793496623265365f, + 0.10945985784971798f, + 0.11098449189716339f, + 0.11250886478737869f, + 0.1140329729333672f, + 0.11555681274875526f, + 0.11708038064780059f, + 0.11860367304540072f, + 0.1201266863571015f, + 0.12164941699910553f, + 0.12317186138828048f, + 0.12469401594216764f, + 0.12621587707899035f, + 0.12773744121766231f, + 0.12925870477779614f, + 0.13077966417971171f, + 0.13230031584444465f, + 0.13382065619375472f, + 0.13534068165013421f, + 0.13686038863681638f, + 0.13837977357778389f, + 0.13989883289777721f, + 0.14141756302230302f, + 0.14293596037764267f, + 0.14445402139086047f, + 0.14597174248981221f, + 0.14748912010315357f, + 0.14900615066034845f, + 0.1505228305916774f, + 0.15203915632824605f, + 0.15355512430199345f, + 0.15507073094570051f, + 0.15658597269299843f, + 0.15810084597837698f, + 0.15961534723719306f, + 0.16112947290567881f, + 0.16264321942095031f, + 0.16415658322101581f, + 0.16566956074478412f, + 0.16718214843207294f, + 0.16869434272361733f, + 0.17020614006107807f, + 0.17171753688704997f, + 0.17322852964507032f, + 0.1747391147796272f, + 0.17624928873616788f, + 0.17775904796110717f, + 0.17926838890183575f, + 0.18077730800672859f, + 0.1822858017251533f, + 0.18379386650747845f, + 0.1853014988050819f, + 0.18680869507035927f, + 0.18831545175673212f, + 0.18982176531865641f, + 0.1913276322116309f, + 0.19283304889220523f, + 0.1943380118179886f, + 0.19584251744765785f, + 0.19734656224096592f, + 0.19885014265875009f, + 0.20035325516294045f, + 0.20185589621656805f, + 0.20335806228377332f, + 0.20485974982981442f, + 0.20636095532107551f, + 0.20786167522507507f, + 0.20936190601047416f, + 0.21086164414708486f, + 0.21236088610587842f, + 0.21385962835899375f, + 0.21535786737974555f, + 0.21685559964263262f, + 0.21835282162334632f, + 0.2198495297987787f, + 0.22134572064703081f, + 0.22284139064742112f, + 0.2243365362804936f, + 0.22583115402802617f, + 0.22732524037303886f, + 0.22881879179980222f, + 0.23031180479384544f, + 0.23180427584196478f, + 0.23329620143223159f, + 0.23478757805400097f, + 0.23627840219791957f, + 0.23776867035593419f, + 0.23925837902129998f, + 0.24074752468858843f, + 0.24223610385369601f, + 0.24372411301385216f, + 0.24521154866762754f, + 0.24669840731494241f, + 0.24818468545707478f, + 0.24967037959666857f, + 0.25115548623774192f, + 0.25264000188569552f, + 0.25412392304732062f, + 0.25560724623080738f, + 0.25708996794575312f, + 0.25857208470317034f, + 0.26005359301549519f, + 0.26153448939659552f, + 0.263014770361779f, + 0.26449443242780163f, + 0.26597347211287559f, + 0.26745188593667762f, + 0.26892967042035726f, + 0.27040682208654482f, + 0.27188333745935972f, + 0.27335921306441868f, + 0.27483444542884394f, + 0.27630903108127108f, + 0.27778296655185769f, + 0.27925624837229118f, + 0.28072887307579719f, + 0.28220083719714756f, + 0.28367213727266843f, + 0.28514276984024867f, + 0.28661273143934779f, + 0.28808201861100413f, + 0.28955062789784303f, + 0.29101855584408509f, + 0.29248579899555388f, + 0.29395235389968466f, + 0.29541821710553201f, + 0.29688338516377827f, + 0.2983478546267414f, + 0.29981162204838335f, + 0.30127468398431795f, + 0.30273703699181914f, + 0.30419867762982911f, + 0.30565960245896612f, + 0.3071198080415331f, + 0.30857929094152509f, + 0.31003804772463789f, + 0.31149607495827591f, + 0.3129533692115602f, + 0.31440992705533666f, + 0.31586574506218396f, + 0.31732081980642174f, + 0.31877514786411848f, + 0.32022872581309986f, + 0.32168155023295658f, + 0.32313361770505233f, + 0.32458492481253215f, + 0.32603546814033024f, + 0.327485244275178f, + 0.3289342498056122f, + 0.33038248132198278f, + 0.33182993541646111f, + 0.33327660868304793f, + 0.33472249771758122f, + 0.33616759911774452f, + 0.33761190948307462f, + 0.33905542541496964f, + 0.34049814351669716f, + 0.34194006039340219f, + 0.34338117265211504f, + 0.34482147690175929f, + 0.34626096975316001f, + 0.34769964781905138f, + 0.34913750771408497f, + 0.35057454605483751f, + 0.35201075945981908f, + 0.35344614454948081f, + 0.35488069794622279f, + 0.35631441627440241f, + 0.3577472961603419f, + 0.3591793342323365f, + 0.36061052712066227f, + 0.36204087145758418f, + 0.36347036387736376f, + 0.36489900101626732f, + 0.36632677951257359f, + 0.36775369600658198f, + 0.36917974714062002f, + 0.37060492955905167f, + 0.37202923990828501f, + 0.3734526748367803f, + 0.37487523099505754f, + 0.37629690503570479f, + 0.37771769361338564f, + 0.37913759338484732f, + 0.38055660100892852f, + 0.38197471314656722f, + 0.38339192646080866f, + 0.38480823761681288f, + 0.38622364328186298f, + 0.38763814012537273f, + 0.38905172481889438f, + 0.39046439403612659f, + 0.39187614445292235f, + 0.3932869727472964f, + 0.39469687559943356f, + 0.39610584969169627f, + 0.39751389170863233f, + 0.39892099833698291f, + 0.40032716626569009f, + 0.40173239218590501f, + 0.4031366727909953f, + 0.404540004776553f, + 0.40594238484040251f, + 0.40734380968260797f, + 0.40874427600548136f, + 0.41014378051359024f, + 0.41154231991376522f, + 0.41293989091510808f, + 0.4143364902289991f, + 0.41573211456910536f, + 0.41712676065138787f, + 0.4185204251941097f, + 0.41991310491784362f, + 0.42130479654547964f, + 0.42269549680223295f, + 0.42408520241565156f, + 0.4254739101156238f, + 0.42686161663438643f, + 0.42824831870653196f, + 0.42963401306901638f, + 0.43101869646116703f, + 0.43240236562469014f, + 0.43378501730367852f, + 0.43516664824461926f, + 0.4365472551964012f, + 0.43792683491032286f, + 0.43930538414009995f, + 0.4406828996418729f, + 0.4420593781742147f, + 0.44343481649813848f, + 0.44480921137710488f, + 0.44618255957703007f, + 0.44755485786629301f, + 0.44892610301574326f, + 0.45029629179870861f, + 0.45166542099100249f, + 0.45303348737093158f, + 0.45440048771930358f, + 0.45576641881943464f, + 0.45713127745715698f, + 0.45849506042082627f, + 0.45985776450132954f, + 0.46121938649209238f, + 0.46257992318908681f, + 0.46393937139083852f, + 0.4652977278984346f, + 0.46665498951553092f, + 0.46801115304835983f, + 0.46936621530573752f, + 0.4707201730990716f, + 0.47207302324236866f, + 0.47342476255224153f, + 0.47477538784791712f, + 0.47612489595124358f, + 0.47747328368669806f, + 0.47882054788139389f, + 0.48016668536508839f, + 0.48151169297018986f, + 0.48285556753176567f, + 0.48419830588754903f, + 0.48553990487794696f, + 0.48688036134604734f, + 0.48821967213762679f, + 0.48955783410115744f, + 0.49089484408781509f, + 0.49223069895148602f, + 0.49356539554877477f, + 0.49489893073901126f, + 0.49623130138425825f, + 0.49756250434931915f, + 0.49889253650174459f, + 0.50022139471184068f, + 0.50154907585267539f, + 0.50287557680008699f, + 0.50420089443269034f, + 0.50552502563188539f, + 0.50684796728186321f, + 0.5081697162696146f, + 0.50949026948493636f, + 0.51080962382043904f, + 0.51212777617155469f, + 0.51344472343654346f, + 0.5147604625165012f, + 0.51607499031536663f, + 0.51738830373992906f, + 0.51870039969983495f, + 0.52001127510759604f, + 0.52132092687859566f, + 0.52262935193109661f, + 0.5239365471862486f, + 0.52524250956809471f, + 0.52654723600357944f, + 0.52785072342255523f, + 0.52915296875779061f, + 0.53045396894497632f, + 0.53175372092273332f, + 0.53305222163261945f, + 0.53434946801913752f, + 0.53564545702974109f, + 0.53694018561484291f, + 0.5382336507278217f, + 0.53952584932502889f, + 0.54081677836579667f, + 0.54210643481244392f, + 0.5433948156302848f, + 0.54468191778763453f, + 0.54596773825581757f, + 0.54725227400917409f, + 0.54853552202506739f, + 0.54981747928389091f, + 0.55109814276907543f, + 0.55237750946709607f, + 0.55365557636747931f, + 0.55493234046281037f, + 0.55620779874873993f, + 0.55748194822399155f, + 0.55875478589036831f, + 0.56002630875276038f, + 0.56129651381915147f, + 0.56256539810062656f, + 0.56383295861137817f, + 0.56509919236871398f, + 0.56636409639306384f, + 0.56762766770798623f, + 0.56888990334017586f, + 0.5701508003194703f, + 0.57141035567885723f, + 0.57266856645448116f, + 0.57392542968565075f, + 0.57518094241484508f, + 0.57643510168772183f, + 0.5776879045531228f, + 0.57893934806308178f, + 0.58018942927283168f, + 0.58143814524081017f, + 0.58268549302866846f, + 0.58393146970127618f, + 0.58517607232673041f, + 0.5864192979763605f, + 0.58766114372473666f, + 0.58890160664967572f, + 0.59014068383224882f, + 0.59137837235678758f, + 0.59261466931089113f, + 0.59384957178543363f, + 0.59508307687456996f, + 0.59631518167574371f, + 0.59754588328969316f, + 0.59877517882045872f, + 0.60000306537538894f, + 0.6012295400651485f, + 0.60245460000372375f, + 0.60367824230843037f, + 0.60490046409991982f, + 0.60612126250218612f, + 0.60734063464257293f, + 0.60855857765177945f, + 0.60977508866386843f, + 0.61099016481627166f, + 0.61220380324979795f, + 0.61341600110863859f, + 0.61462675554037505f, + 0.61583606369598509f, + 0.61704392272984976f, + 0.61825032979976025f, + 0.61945528206692402f, + 0.62065877669597214f, + 0.62186081085496536f, + 0.62306138171540126f, + 0.62426048645222065f, + 0.62545812224381436f, + 0.62665428627202935f, + 0.62784897572217646f, + 0.629042187783036f, + 0.63023391964686437f, + 0.63142416850940186f, + 0.63261293156987741f, + 0.63380020603101728f, + 0.63498598909904946f, + 0.63617027798371217f, + 0.63735306989825913f, + 0.63853436205946679f, + 0.63971415168764045f, + 0.64089243600662138f, + 0.64206921224379254f, + 0.64324447763008585f, + 0.64441822939998838f, + 0.64559046479154869f, + 0.64676118104638392f, + 0.64793037540968534f, + 0.64909804513022595f, + 0.65026418746036585f, + 0.65142879965605982f, + 0.65259187897686244f, + 0.65375342268593606f, + 0.65491342805005603f, + 0.6560718923396176f, + 0.65722881282864254f, + 0.65838418679478505f, + 0.65953801151933866f, + 0.6606902842872423f, + 0.66184100238708687f, + 0.66299016311112147f, + 0.66413776375526001f, + 0.66528380161908718f, + 0.66642827400586524f, + 0.66757117822254031f, + 0.66871251157974798f, + 0.66985227139182102f, + 0.67099045497679422f, + 0.67212705965641173f, + 0.67326208275613297f, + 0.67439552160513905f, + 0.67552737353633852f, + 0.67665763588637495f, + 0.6777863059956315f, + 0.67891338120823841f, + 0.68003885887207893f, + 0.68116273633879543f, + 0.68228501096379557f, + 0.68340568010625868f, + 0.6845247411291423f, + 0.68564219139918747f, + 0.68675802828692589f, + 0.68787224916668555f, + 0.68898485141659704f, + 0.69009583241859995f, + 0.69120518955844845f, + 0.69231292022571822f, + 0.69341902181381176f, + 0.69452349171996552f, + 0.69562632734525487f, + 0.6967275260946012f, + 0.69782708537677729f, + 0.69892500260441415f, + 0.70002127519400625f, + 0.70111590056591866f, + 0.70220887614439187f, + 0.70330019935754873f, + 0.70438986763740041f, + 0.7054778784198521f, + 0.70656422914470951f, + 0.70764891725568435f, + 0.70873194020040065f, + 0.70981329543040084f, + 0.71089298040115168f, + 0.71197099257204999f, + 0.71304732940642923f, + 0.71412198837156471f, + 0.71519496693868001f, + 0.71626626258295312f, + 0.71733587278352173f, + 0.71840379502348972f, + 0.71947002678993299f, + 0.72053456557390527f, + 0.72159740887044366f, + 0.72265855417857561f, + 0.72371799900132339f, + 0.72477574084571128f, + 0.72583177722277037f, + 0.72688610564754497f, + 0.72793872363909862f, + 0.72898962872051931f, + 0.73003881841892615f, + 0.73108629026547423f, + 0.73213204179536129f, + 0.73317607054783274f, + 0.73421837406618817f, + 0.73525894989778673f, + 0.73629779559405306f, + 0.73733490871048279f, + 0.73837028680664851f, + 0.73940392744620576f, + 0.74043582819689802f, + 0.74146598663056329f, + 0.74249440032313918f, + 0.74352106685466912f, + 0.74454598380930725f, + 0.74556914877532543f, + 0.74659055934511731f, + 0.74761021311520515f, + 0.74862810768624533f, + 0.74964424066303348f, + 0.75065860965451059f, + 0.75167121227376843f, + 0.75268204613805523f, + 0.75369110886878121f, + 0.75469839809152439f, + 0.75570391143603588f, + 0.75670764653624567f, + 0.75770960103026808f, + 0.75870977256040739f, + 0.75970815877316344f, + 0.76070475731923692f, + 0.76169956585353527f, + 0.76269258203517787f, + 0.76368380352750187f, + 0.76467322799806714f, + 0.76566085311866239f, + 0.76664667656531038f, + 0.76763069601827327f, + 0.76861290916205827f, + 0.76959331368542294f, + 0.7705719072813807f, + 0.7715486876472063f, + 0.77252365248444133f, + 0.77349679949889905f, + 0.77446812640067086f, + 0.77543763090413043f, + 0.77640531072794039f, + 0.7773711635950562f, + 0.77833518723273309f, + 0.7792973793725303f, + 0.78025773775031659f, + 0.78121626010627609f, + 0.7821729441849129f, + 0.78312778773505731f, + 0.78408078850986995f, + 0.78503194426684808f, + 0.78598125276783015f, + 0.7869287117790017f, + 0.78787431907090011f, + 0.78881807241842017f, + 0.78975996960081907f, + 0.79070000840172161f, + 0.79163818660912577f, + 0.79257450201540758f, + 0.79350895241732666f, + 0.79444153561603059f, + 0.79537224941706119f, + 0.79630109163035911f, + 0.7972280600702687f, + 0.79815315255554375f, + 0.79907636690935235f, + 0.79999770095928191f, + 0.8009171525373443f, + 0.80183471947998131f, + 0.80275039962806916f, + 0.80366419082692409f, + 0.804576090926307f, + 0.80548609778042912f, + 0.80639420924795624f, + 0.80730042319201445f, + 0.80820473748019472f, + 0.80910714998455813f, + 0.81000765858164114f, + 0.81090626115245967f, + 0.81180295558251536f, + 0.81269773976179949f, + 0.81359061158479851f, + 0.81448156895049861f, + 0.81537060976239129f, + 0.81625773192847739f, + 0.81714293336127297f, + 0.81802621197781344f, + 0.81890756569965895f, + 0.81978699245289899f, + 0.82066449016815746f, + 0.82154005678059761f, + 0.82241369022992639f, + 0.82328538846040011f, + 0.82415514942082857f, + 0.82502297106458022f, + 0.82588885134958678f, + 0.82675278823834852f, + 0.8276147796979384f, + 0.82847482370000713f, + 0.82933291822078825f, + 0.83018906124110237f, + 0.83104325074636232f, + 0.83189548472657759f, + 0.83274576117635946f, + 0.83359407809492514f, + 0.83444043348610319f, + 0.83528482535833737f, + 0.83612725172469216f, + 0.83696771060285702f, + 0.83780620001515094f, + 0.8386427179885273f, + 0.83947726255457855f, + 0.84030983174954077f, + 0.84114042361429808f, + 0.84196903619438768f, + 0.84279566754000412f, + 0.84362031570600404f, + 0.84444297875191066f, + 0.84526365474191822f, + 0.84608234174489694f, + 0.84689903783439735f, + 0.84771374108865427f, + 0.84852644959059265f, + 0.84933716142783067f, + 0.85014587469268521f, + 0.85095258748217573f, + 0.85175729789802912f, + 0.85256000404668397f, + 0.85336070403929543f, + 0.85415939599173873f, + 0.85495607802461482f, + 0.85575074826325392f, + 0.85654340483771996f, + 0.85733404588281559f, + 0.85812266953808602f, + 0.8589092739478239f, + 0.85969385726107261f, + 0.86047641763163207f, + 0.86125695321806206f, + 0.86203546218368721f, + 0.86281194269660033f, + 0.86358639292966799f, + 0.86435881106053403f, + 0.86512919527162369f, + 0.86589754375014882f, + 0.86666385468811102f, + 0.86742812628230692f, + 0.86819035673433131f, + 0.86895054425058238f, + 0.86970868704226556f, + 0.87046478332539767f, + 0.8712188313208109f, + 0.8719708292541577f, + 0.8727207753559143f, + 0.87346866786138488f, + 0.8742145050107063f, + 0.87495828504885154f, + 0.8757000062256346f, + 0.87643966679571361f, + 0.87717726501859594f, + 0.87791279915864173f, + 0.87864626748506813f, + 0.87937766827195318f, + 0.88010699979824036f, + 0.88083426034774204f, + 0.88155944820914378f, + 0.8822825616760086f, + 0.88300359904678072f, + 0.88372255862478966f, + 0.8844394387182537f, + 0.88515423764028511f, + 0.88586695370889279f, + 0.88657758524698704f, + 0.88728613058238315f, + 0.88799258804780556f, + 0.88869695598089171f, + 0.88939923272419552f, + 0.89009941662519221f, + 0.89079750603628149f, + 0.89149349931479138f, + 0.89218739482298248f, + 0.89287919092805168f, + 0.89356888600213602f, + 0.89425647842231604f, + 0.89494196657062075f, + 0.89562534883403f, + 0.89630662360447966f, + 0.89698578927886397f, + 0.89766284425904075f, + 0.89833778695183419f, + 0.89901061576903907f, + 0.89968132912742393f, + 0.9003499254487356f, + 0.90101640315970233f, + 0.90168076069203773f, + 0.9023429964824442f, + 0.90300310897261704f, + 0.90366109660924798f, + 0.90431695784402832f, + 0.90497069113365325f, + 0.90562229493982516f, + 0.90627176772925766f, + 0.90691910797367803f, + 0.90756431414983252f, + 0.9082073847394887f, + 0.90884831822943912f, + 0.90948711311150543f, + 0.91012376788254157f, + 0.91075828104443757f, + 0.91139065110412232f, + 0.91202087657356823f, + 0.9126489559697939f, + 0.91327488781486776f, + 0.91389867063591168f, + 0.91452030296510445f, + 0.91513978333968526f, + 0.91575711030195672f, + 0.91637228239928914f, + 0.91698529818412289f, + 0.91759615621397295f, + 0.9182048550514309f, + 0.91881139326416994f, + 0.91941576942494696f, + 0.92001798211160657f, + 0.92061802990708386f, + 0.92121591139940873f, + 0.92181162518170812f, + 0.92240516985220988f, + 0.92299654401424625f, + 0.92358574627625656f, + 0.9241727752517912f, + 0.92475762955951391f, + 0.9253403078232062f, + 0.92592080867176996f, + 0.92649913073923051f, + 0.9270752726647401f, + 0.92764923309258118f, + 0.92822101067216944f, + 0.92879060405805702f, + 0.9293580119099355f, + 0.92992323289263956f, + 0.93048626567614978f, + 0.93104710893559517f, + 0.93160576135125783f, + 0.93216222160857432f, + 0.93271648839814025f, + 0.93326856041571205f, + 0.93381843636221096f, + 0.9343661149437259f, + 0.93491159487151609f, + 0.93545487486201462f, + 0.9359959536368313f, + 0.9365348299227555f, + 0.93707150245175919f, + 0.93760596996099999f, + 0.93813823119282436f, + 0.93866828489477017f, + 0.9391961298195699f, + 0.93972176472515334f, + 0.94024518837465088f, + 0.94076639953639607f, + 0.94128539698392866f, + 0.94180217949599765f, + 0.94231674585656378f, + 0.94282909485480271f, + 0.94333922528510772f, + 0.94384713594709269f, + 0.94435282564559475f, + 0.94485629319067721f, + 0.94535753739763229f, + 0.94585655708698391f, + 0.94635335108449059f, + 0.946847918221148f, + 0.94734025733319194f, + 0.94783036726210101f, + 0.94831824685459909f, + 0.94880389496265838f, + 0.94928731044350201f, + 0.94976849215960668f, + 0.95024743897870523f, + 0.95072414977378961f, + 0.95119862342311323f, + 0.95167085881019386f, + 0.95214085482381583f, + 0.95260861035803324f, + 0.9530741243121722f, + 0.95353739559083328f, + 0.95399842310389449f, + 0.95445720576651349f, + 0.95491374249913052f, + 0.95536803222747024f, + 0.95582007388254542f, + 0.95626986640065814f, + 0.95671740872340305f, + 0.9571626997976701f, + 0.95760573857564624f, + 0.9580465240148186f, + 0.9584850550779761f, + 0.95892133073321306f, + 0.95935534995393079f, + 0.9597871117188399f, + 0.96021661501196343f, + 0.96064385882263847f, + 0.96106884214551935f, + 0.961491563980579f, + 0.9619120233331121f, + 0.9623302192137374f, + 0.96274615063839941f, + 0.96315981662837136f, + 0.96357121621025721f, + 0.96398034841599411f, + 0.96438721228285429f, + 0.9647918068534479f, + 0.96519413117572472f, + 0.96559418430297683f, + 0.96599196529384057f, + 0.96638747321229879f, + 0.96678070712768327f, + 0.96717166611467664f, + 0.96756034925331436f, + 0.9679467556289878f, + 0.9683308843324453f, + 0.96871273445979478f, + 0.9690923051125061f, + 0.96946959539741295f, + 0.96984460442671483f, + 0.97021733131797916f, + 0.97058777519414363f, + 0.97095593518351797f, + 0.97132181041978616f, + 0.97168540004200854f, + 0.9720467031946235f, + 0.97240571902744977f, + 0.97276244669568857f, + 0.97311688535992513f, + 0.97346903418613095f, + 0.9738188923456661f, + 0.97416645901528032f, + 0.97451173337711572f, + 0.97485471461870843f, + 0.97519540193299037f, + 0.97553379451829136f, + 0.97586989157834103f, + 0.97620369232227056f, + 0.97653519596461447f, + 0.97686440172531264f, + 0.97719130882971228f, + 0.97751591650856928f, + 0.97783822399805043f, + 0.97815823053973505f, + 0.97847593538061683f, + 0.97879133777310567f, + 0.97910443697502925f, + 0.97941523224963478f, + 0.97972372286559117f, + 0.98002990809698998f, + 0.98033378722334796f, + 0.98063535952960812f, + 0.98093462430614164f, + 0.98123158084874973f, + 0.98152622845866466f, + 0.9818185664425525f, + 0.98210859411251361f, + 0.98239631078608469f, + 0.98268171578624086f, + 0.98296480844139644f, + 0.98324558808540707f, + 0.98352405405757126f, + 0.98380020570263149f, + 0.98407404237077645f, + 0.9843455634176419f, + 0.9846147682043126f, + 0.9848816560973237f, + 0.98514622646866223f, + 0.98540847869576842f, + 0.98566841216153755f, + 0.98592602625432113f, + 0.98618132036792827f, + 0.98643429390162707f, + 0.98668494626014669f, + 0.98693327685367771f, + 0.98717928509787434f, + 0.98742297041385541f, + 0.98766433222820571f, + 0.98790336997297779f, + 0.98814008308569257f, + 0.98837447100934128f, + 0.98860653319238645f, + 0.98883626908876354f, + 0.98906367815788154f, + 0.98928875986462517f, + 0.98951151367935519f, + 0.98973193907791057f, + 0.98995003554160899f, + 0.9901658025572484f, + 0.99037923961710816f, + 0.99059034621895015f, + 0.99079912186602037f, + 0.99100556606704937f, + 0.99120967833625406f, + 0.99141145819333854f, + 0.99161090516349537f, + 0.99180801877740643f, + 0.99200279857124452f, + 0.99219524408667392f, + 0.99238535487085167f, + 0.99257313047642881f, + 0.99275857046155114f, + 0.99294167438986047f, + 0.99312244183049558f, + 0.99330087235809328f, + 0.99347696555278919f, + 0.99365072100021912f, + 0.99382213829151966f, + 0.99399121702332938f, + 0.99415795679778973f, + 0.99432235722254581f, + 0.9944844179107476f, + 0.99464413848105071f, + 0.99480151855761711f, + 0.99495655777011638f, + 0.99510925575372611f, + 0.99525961214913339f, + 0.9954076266025349f, + 0.99555329876563847f, + 0.99569662829566352f, + 0.99583761485534161f, + 0.99597625811291779f, + 0.99611255774215113f, + 0.99624651342231552f, + 0.99637812483820021f, + 0.99650739168011082f, + 0.9966343136438699f, + 0.996758890430818f, + 0.99688112174781385f, + 0.99700100730723529f, + 0.99711854682697998f, + 0.99723374003046616f, + 0.99734658664663323f, + 0.99745708640994191f, + 0.99756523906037575f, + 0.997671044343441f, + 0.99777450201016782f, + 0.99787561181711015f, + 0.99797437352634699f, + 0.99807078690548234f, + 0.99816485172764624f, + 0.99825656777149518f, + 0.99834593482121237f, + 0.99843295266650844f, + 0.99851762110262221f, + 0.99859993993032037f, + 0.99867990895589909f, + 0.99875752799118334f, + 0.99883279685352799f, + 0.99890571536581829f, + 0.99897628335646982f, + 0.99904450065942929f, + 0.99911036711417489f, + 0.99917388256571638f, + 0.99923504686459585f, + 0.99929385986688779f, + 0.99935032143419944f, + 0.9994044314336713f, + 0.99945618973797734f, + 0.99950559622532531f, + 0.99955265077945699f, + 0.99959735328964838f, + 0.9996397036507102f, + 0.99967970176298793f, + 0.99971734753236219f, + 0.99975264087024884f, + 0.99978558169359921f, + 0.99981616992490041f, + 0.99984440549217524f, + 0.99987028832898295f, + 0.99989381837441849f, + 0.99991499557311347f, + 0.999933819875236f, + 0.99995029123649048f, + 0.99996440961811828f, + 0.99997617498689761f, + 0.9999855873151432f, + 0.99999264658070719f, + 0.99999735276697821f, + 0.99999970586288223f + }; + public static float[] SINE_128 = { + 0.0061358846491544753f, + 0.01840672990580482f, + 0.030674803176636626f, + 0.04293825693494082f, + 0.055195244349689934f, + 0.067443919563664051f, + 0.079682437971430126f, + 0.091908956497132724f, + 0.10412163387205459f, + 0.11631863091190475f, + 0.12849811079379317f, + 0.14065823933284921f, + 0.15279718525844344f, + 0.16491312048996989f, + 0.17700422041214875f, + 0.18906866414980619f, + 0.2011046348420919f, + 0.21311031991609136f, + 0.22508391135979283f, + 0.2370236059943672f, + 0.24892760574572015f, + 0.26079411791527551f, + 0.27262135544994898f, + 0.28440753721127188f, + 0.29615088824362379f, + 0.30784964004153487f, + 0.31950203081601569f, + 0.33110630575987643f, + 0.34266071731199438f, + 0.35416352542049034f, + 0.36561299780477385f, + 0.37700741021641826f, + 0.38834504669882625f, + 0.39962419984564679f, + 0.41084317105790391f, + 0.42200027079979968f, + 0.43309381885315196f, + 0.4441221445704292f, + 0.45508358712634384f, + 0.46597649576796618f, + 0.47679923006332209f, + 0.487550160148436f, + 0.49822766697278187f, + 0.50883014254310699f, + 0.51935599016558964f, + 0.52980362468629461f, + 0.54017147272989285f, + 0.55045797293660481f, + 0.56066157619733603f, + 0.57078074588696726f, + 0.58081395809576453f, + 0.59075970185887416f, + 0.60061647938386897f, + 0.61038280627630948f, + 0.6200572117632891f, + 0.62963823891492698f, + 0.63912444486377573f, + 0.64851440102211244f, + 0.65780669329707864f, + 0.66699992230363747f, + 0.67609270357531592f, + 0.68508366777270036f, + 0.693971460889654f, + 0.7027547444572253f, + 0.71143219574521643f, + 0.72000250796138165f, + 0.7284643904482252f, + 0.73681656887736979f, + 0.74505778544146595f, + 0.75318679904361241f, + 0.76120238548426178f, + 0.76910333764557959f, + 0.77688846567323244f, + 0.78455659715557524f, + 0.79210657730021239f, + 0.79953726910790501f, + 0.80684755354379922f, + 0.8140363297059483f, + 0.82110251499110465f, + 0.8280450452577558f, + 0.83486287498638001f, + 0.84155497743689833f, + 0.84812034480329712f, + 0.85455798836540053f, + 0.86086693863776731f, + 0.86704624551569265f, + 0.87309497841829009f, + 0.87901222642863341f, + 0.88479709843093779f, + 0.89044872324475788f, + 0.89596624975618511f, + 0.90134884704602203f, + 0.90659570451491533f, + 0.91170603200542988f, + 0.9166790599210427f, + 0.9215140393420419f, + 0.92621024213831127f, + 0.93076696107898371f, + 0.9351835099389475f, + 0.93945922360218992f, + 0.94359345816196039f, + 0.94758559101774109f, + 0.95143502096900834f, + 0.95514116830577067f, + 0.9587034748958716f, + 0.96212140426904158f, + 0.9653944416976894f, + 0.96852209427441727f, + 0.97150389098625178f, + 0.97433938278557586f, + 0.97702814265775439f, + 0.97956976568544052f, + 0.98196386910955524f, + 0.98421009238692903f, + 0.98630809724459867f, + 0.98825756773074946f, + 0.99005821026229712f, + 0.99170975366909953f, + 0.9932119492347945f, + 0.99456457073425542f, + 0.99576741446765982f, + 0.99682029929116567f, + 0.99772306664419164f, + 0.99847558057329477f, + 0.99907772775264536f, + 0.99952941750109314f, + 0.9998305817958234f, + 0.99998117528260111f + }; + public static float[] SINE_960 = { + 0.00081812299560725323f, + 0.0024543667964602917f, + 0.0040906040262347889f, + 0.0057268303042312674f, + 0.0073630412497795667f, + 0.0089992324822505774f, + 0.010635399621067975f, + 0.012271538285719924f, + 0.013907644095770845f, + 0.015543712670873098f, + 0.017179739630778748f, + 0.018815720595351273f, + 0.020451651184577292f, + 0.022087527018578291f, + 0.023723343717622358f, + 0.025359096902135895f, + 0.02699478219271537f, + 0.028630395210139003f, + 0.030265931575378519f, + 0.031901386909610863f, + 0.033536756834229922f, + 0.035172036970858266f, + 0.036807222941358832f, + 0.038442310367846677f, + 0.040077294872700696f, + 0.041712172078575326f, + 0.043346937608412288f, + 0.044981587085452281f, + 0.046616116133246711f, + 0.048250520375669431f, + 0.049884795436928406f, + 0.051518936941577477f, + 0.053152940514528055f, + 0.05478680178106083f, + 0.056420516366837495f, + 0.05805407989791244f, + 0.059687488000744485f, + 0.061320736302208578f, + 0.062953820429607482f, + 0.064586736010683557f, + 0.066219478673630344f, + 0.06785204404710439f, + 0.069484427760236861f, + 0.071116625442645326f, + 0.072748632724445372f, + 0.07438044523626236f, + 0.076012058609243122f, + 0.077643468475067631f, + 0.079274670465960706f, + 0.080905660214703745f, + 0.082536433354646319f, + 0.084166985519717977f, + 0.085797312344439894f, + 0.08742740946393647f, + 0.089057272513947183f, + 0.090686897130838162f, + 0.092316278951613845f, + 0.093945413613928788f, + 0.095574296756099186f, + 0.097202924017114667f, + 0.098831291036649963f, + 0.10045939345507648f, + 0.10208722691347409f, + 0.10371478705364276f, + 0.10534206951811415f, + 0.10696906995016341f, + 0.10859578399382072f, + 0.11022220729388306f, + 0.11184833549592579f, + 0.11347416424631435f, + 0.11509968919221586f, + 0.11672490598161089f, + 0.11834981026330495f, + 0.11997439768694031f, + 0.12159866390300751f, + 0.12322260456285709f, + 0.12484621531871121f, + 0.12646949182367517f, + 0.12809242973174936f, + 0.12971502469784052f, + 0.13133727237777362f, + 0.13295916842830346f, + 0.13458070850712617f, + 0.13620188827289101f, + 0.1378227033852118f, + 0.13944314950467873f, + 0.14106322229286994f, + 0.14268291741236291f, + 0.14430223052674654f, + 0.1459211573006321f, + 0.14753969339966552f, + 0.14915783449053857f, + 0.15077557624100058f, + 0.15239291431987001f, + 0.1540098443970461f, + 0.15562636214352044f, + 0.15724246323138855f, + 0.15885814333386142f, + 0.16047339812527725f, + 0.16208822328111283f, + 0.16370261447799525f, + 0.16531656739371339f, + 0.16693007770722967f, + 0.16854314109869134f, + 0.17015575324944232f, + 0.17176790984203447f, + 0.17337960656023954f, + 0.1749908390890603f, + 0.17660160311474243f, + 0.17821189432478593f, + 0.17982170840795647f, + 0.18143104105429744f, + 0.18303988795514095f, + 0.1846482448031197f, + 0.18625610729217834f, + 0.1878634711175852f, + 0.18947033197594348f, + 0.19107668556520319f, + 0.19268252758467228f, + 0.19428785373502844f, + 0.19589265971833042f, + 0.19749694123802966f, + 0.19910069399898173f, + 0.20070391370745785f, + 0.20230659607115639f, + 0.20390873679921437f, + 0.20551033160221882f, + 0.20711137619221856f, + 0.2087118662827353f, + 0.21031179758877552f, + 0.21191116582684155f, + 0.21350996671494335f, + 0.21510819597260972f, + 0.21670584932089998f, + 0.2183029224824154f, + 0.21989941118131037f, + 0.22149531114330431f, + 0.22309061809569264f, + 0.22468532776735861f, + 0.22627943588878449f, + 0.22787293819206314f, + 0.22946583041090929f, + 0.23105810828067114f, + 0.23264976753834157f, + 0.23424080392256985f, + 0.2358312131736727f, + 0.23742099103364595f, + 0.23901013324617584f, + 0.24059863555665045f, + 0.24218649371217096f, + 0.24377370346156332f, + 0.24536026055538934f, + 0.24694616074595824f, + 0.24853139978733788f, + 0.25011597343536629f, + 0.25169987744766298f, + 0.25328310758364025f, + 0.25486565960451457f, + 0.25644752927331788f, + 0.25802871235490898f, + 0.25960920461598508f, + 0.26118900182509258f, + 0.26276809975263904f, + 0.264346494170904f, + 0.26592418085405067f, + 0.26750115557813692f, + 0.2690774141211269f, + 0.27065295226290209f, + 0.2722277657852728f, + 0.27380185047198918f, + 0.27537520210875299f, + 0.2769478164832283f, + 0.27851968938505312f, + 0.28009081660585067f, + 0.28166119393924061f, + 0.28323081718085019f, + 0.28479968212832563f, + 0.28636778458134327f, + 0.28793512034162105f, + 0.2895016852129294f, + 0.29106747500110264f, + 0.29263248551405047f, + 0.2941967125617686f, + 0.29576015195635058f, + 0.29732279951199847f, + 0.29888465104503475f, + 0.30044570237391266f, + 0.30200594931922808f, + 0.30356538770373032f, + 0.30512401335233358f, + 0.30668182209212791f, + 0.3082388097523906f, + 0.30979497216459695f, + 0.31135030516243201f, + 0.3129048045818012f, + 0.31445846626084178f, + 0.31601128603993378f, + 0.31756325976171151f, + 0.31911438327107416f, + 0.32066465241519732f, + 0.32221406304354389f, + 0.3237626110078754f, + 0.32531029216226293f, + 0.32685710236309828f, + 0.32840303746910487f, + 0.32994809334134939f, + 0.3314922658432522f, + 0.33303555084059877f, + 0.33457794420155085f, + 0.33611944179665709f, + 0.33766003949886464f, + 0.33919973318352969f, + 0.34073851872842903f, + 0.34227639201377064f, + 0.34381334892220483f, + 0.34534938533883547f, + 0.34688449715123082f, + 0.34841868024943456f, + 0.34995193052597684f, + 0.35148424387588523f, + 0.3530156161966958f, + 0.35454604338846402f, + 0.35607552135377557f, + 0.35760404599775775f, + 0.35913161322809023f, + 0.36065821895501554f, + 0.36218385909135092f, + 0.36370852955249849f, + 0.36523222625645668f, + 0.36675494512383078f, + 0.36827668207784414f, + 0.36979743304434909f, + 0.37131719395183754f, + 0.37283596073145214f, + 0.37435372931699717f, + 0.37587049564494951f, + 0.37738625565446909f, + 0.37890100528741022f, + 0.38041474048833229f, + 0.38192745720451066f, + 0.38343915138594736f, + 0.38494981898538222f, + 0.38645945595830333f, + 0.38796805826295838f, + 0.38947562186036483f, + 0.39098214271432141f, + 0.39248761679141814f, + 0.3939920400610481f, + 0.39549540849541737f, + 0.39699771806955625f, + 0.39849896476132979f, + 0.39999914455144892f, + 0.40149825342348083f, + 0.4029962873638599f, + 0.40449324236189854f, + 0.40598911440979762f, + 0.40748389950265762f, + 0.40897759363848879f, + 0.41047019281822261f, + 0.41196169304572178f, + 0.4134520903277914f, + 0.41494138067418929f, + 0.41642956009763715f, + 0.41791662461383078f, + 0.41940257024145089f, + 0.42088739300217382f, + 0.42237108892068231f, + 0.42385365402467584f, + 0.42533508434488143f, + 0.42681537591506419f, + 0.42829452477203828f, + 0.42977252695567697f, + 0.43124937850892364f, + 0.4327250754778022f, + 0.43419961391142781f, + 0.43567298986201736f, + 0.43714519938489987f, + 0.43861623853852766f, + 0.44008610338448595f, + 0.44155478998750436f, + 0.44302229441546676f, + 0.4444886127394222f, + 0.44595374103359531f, + 0.44741767537539667f, + 0.44888041184543348f, + 0.45034194652752002f, + 0.45180227550868812f, + 0.45326139487919759f, + 0.45471930073254679f, + 0.45617598916548296f, + 0.45763145627801283f, + 0.45908569817341294f, + 0.46053871095824001f, + 0.46199049074234161f, + 0.46344103363886635f, + 0.46489033576427435f, + 0.46633839323834758f, + 0.46778520218420055f, + 0.46923075872829029f, + 0.47067505900042683f, + 0.47211809913378361f, + 0.47355987526490806f, + 0.47500038353373153f, + 0.47643962008357982f, + 0.47787758106118372f, + 0.47931426261668875f, + 0.48074966090366611f, + 0.48218377207912272f, + 0.48361659230351117f, + 0.48504811774074069f, + 0.48647834455818684f, + 0.48790726892670194f, + 0.48933488702062544f, + 0.49076119501779414f, + 0.49218618909955225f, + 0.4936098654507618f, + 0.49503222025981269f, + 0.49645324971863303f, + 0.49787295002269943f, + 0.49929131737104687f, + 0.50070834796627917f, + 0.50212403801457872f, + 0.50353838372571758f, + 0.50495138131306638f, + 0.50636302699360547f, + 0.50777331698793449f, + 0.50918224752028263f, + 0.51058981481851906f, + 0.51199601511416237f, + 0.51340084464239111f, + 0.51480429964205421f, + 0.51620637635567967f, + 0.51760707102948678f, + 0.51900637991339404f, + 0.5204042992610306f, + 0.52180082532974559f, + 0.5231959543806185f, + 0.52458968267846895f, + 0.52598200649186677f, + 0.52737292209314235f, + 0.52876242575839572f, + 0.53015051376750777f, + 0.53153718240414882f, + 0.53292242795578992f, + 0.53430624671371152f, + 0.53568863497301467f, + 0.5370695890326298f, + 0.5384491051953274f, + 0.53982717976772743f, + 0.54120380906030963f, + 0.54257898938742311f, + 0.54395271706729609f, + 0.54532498842204646f, + 0.54669579977769045f, + 0.54806514746415402f, + 0.54943302781528081f, + 0.55079943716884383f, + 0.55216437186655387f, + 0.55352782825406999f, + 0.55488980268100907f, + 0.55625029150095584f, + 0.55760929107147217f, + 0.55896679775410718f, + 0.56032280791440714f, + 0.56167731792192455f, + 0.56303032415022869f, + 0.56438182297691453f, + 0.56573181078361312f, + 0.56708028395600085f, + 0.56842723888380908f, + 0.56977267196083425f, + 0.57111657958494688f, + 0.5724589581581021f, + 0.57379980408634845f, + 0.57513911377983773f, + 0.57647688365283478f, + 0.57781311012372738f, + 0.57914778961503466f, + 0.58048091855341843f, + 0.5818124933696911f, + 0.58314251049882604f, + 0.58447096637996743f, + 0.58579785745643886f, + 0.5871231801757536f, + 0.58844693098962408f, + 0.58976910635397084f, + 0.59108970272893235f, + 0.59240871657887517f, + 0.59372614437240179f, + 0.59504198258236196f, + 0.5963562276858605f, + 0.59766887616426767f, + 0.5989799245032289f, + 0.60028936919267273f, + 0.60159720672682204f, + 0.60290343360420195f, + 0.60420804632765002f, + 0.60551104140432543f, + 0.60681241534571839f, + 0.60811216466765883f, + 0.60941028589032709f, + 0.61070677553826169f, + 0.61200163014036979f, + 0.61329484622993602f, + 0.6145864203446314f, + 0.61587634902652377f, + 0.61716462882208556f, + 0.61845125628220421f, + 0.61973622796219074f, + 0.6210195404217892f, + 0.62230119022518593f, + 0.62358117394101897f, + 0.62485948814238634f, + 0.62613612940685637f, + 0.62741109431647646f, + 0.62868437945778133f, + 0.62995598142180387f, + 0.6312258968040827f, + 0.63249412220467238f, + 0.63376065422815175f, + 0.63502548948363347f, + 0.63628862458477287f, + 0.63755005614977711f, + 0.63880978080141437f, + 0.6400677951670225f, + 0.6413240958785188f, + 0.64257867957240766f, + 0.6438315428897915f, + 0.64508268247637779f, + 0.64633209498248945f, + 0.64757977706307335f, + 0.64882572537770888f, + 0.65006993659061751f, + 0.65131240737067142f, + 0.65255313439140239f, + 0.65379211433101081f, + 0.65502934387237444f, + 0.6562648197030575f, + 0.65749853851531959f, + 0.65873049700612374f, + 0.65996069187714679f, + 0.66118911983478657f, + 0.66241577759017178f, + 0.66364066185917048f, + 0.66486376936239888f, + 0.66608509682523009f, + 0.66730464097780284f, + 0.66852239855503071f, + 0.66973836629660977f, + 0.67095254094702894f, + 0.67216491925557675f, + 0.67337549797635199f, + 0.67458427386827102f, + 0.67579124369507693f, + 0.67699640422534846f, + 0.67819975223250772f, + 0.6794012844948305f, + 0.68060099779545302f, + 0.68179888892238183f, + 0.6829949546685018f, + 0.68418919183158522f, + 0.68538159721429948f, + 0.6865721676242168f, + 0.68776089987382172f, + 0.68894779078052026f, + 0.69013283716664853f, + 0.69131603585948032f, + 0.69249738369123692f, + 0.69367687749909468f, + 0.69485451412519361f, + 0.69603029041664599f, + 0.6972042032255451f, + 0.6983762494089728f, + 0.69954642582900894f, + 0.70071472935273893f, + 0.70188115685226271f, + 0.703045705204703f, + 0.70420837129221303f, + 0.70536915200198613f, + 0.70652804422626281f, + 0.70768504486233985f, + 0.70884015081257845f, + 0.70999335898441229f, + 0.711144666290356f, + 0.71229406964801356f, + 0.71344156598008623f, + 0.71458715221438096f, + 0.71573082528381871f, + 0.71687258212644234f, + 0.7180124196854254f, + 0.71915033490907943f, + 0.72028632475086318f, + 0.72142038616938997f, + 0.72255251612843596f, + 0.72368271159694852f, + 0.72481096954905444f, + 0.72593728696406756f, + 0.72706166082649704f, + 0.72818408812605595f, + 0.72930456585766834f, + 0.73042309102147851f, + 0.73153966062285747f, + 0.73265427167241282f, + 0.73376692118599507f, + 0.73487760618470677f, + 0.73598632369490979f, + 0.73709307074823405f, + 0.73819784438158409f, + 0.73930064163714881f, + 0.74040145956240788f, + 0.74150029521014049f, + 0.74259714563843304f, + 0.74369200791068657f, + 0.74478487909562552f, + 0.74587575626730485f, + 0.74696463650511791f, + 0.74805151689380456f, + 0.74913639452345926f, + 0.75021926648953785f, + 0.75130012989286621f, + 0.7523789818396478f, + 0.75345581944147111f, + 0.75453063981531809f, + 0.75560344008357094f, + 0.75667421737402052f, + 0.7577429688198738f, + 0.75880969155976163f, + 0.75987438273774599f, + 0.76093703950332836f, + 0.76199765901145666f, + 0.76305623842253345f, + 0.76411277490242291f, + 0.76516726562245885f, + 0.76621970775945258f, + 0.76727009849569949f, + 0.76831843501898767f, + 0.76936471452260458f, + 0.77040893420534517f, + 0.77145109127151923f, + 0.77249118293095853f, + 0.77352920639902467f, + 0.77456515889661659f, + 0.77559903765017746f, + 0.7766308398917029f, + 0.77766056285874774f, + 0.77868820379443371f, + 0.77971375994745684f, + 0.78073722857209438f, + 0.7817586069282132f, + 0.78277789228127592f, + 0.78379508190234881f, + 0.78481017306810918f, + 0.78582316306085265f, + 0.78683404916849986f, + 0.78784282868460476f, + 0.78884949890836087f, + 0.78985405714460888f, + 0.7908565007038445f, + 0.79185682690222425f, + 0.79285503306157412f, + 0.79385111650939566f, + 0.79484507457887377f, + 0.79583690460888357f, + 0.79682660394399751f, + 0.79781416993449272f, + 0.79879959993635785f, + 0.7997828913113002f, + 0.80076404142675273f, + 0.80174304765588156f, + 0.80271990737759213f, + 0.80369461797653707f, + 0.80466717684312306f, + 0.80563758137351682f, + 0.80660582896965372f, + 0.80757191703924336f, + 0.80853584299577752f, + 0.80949760425853612f, + 0.81045719825259477f, + 0.81141462240883167f, + 0.81236987416393436f, + 0.81332295096040608f, + 0.81427385024657373f, + 0.81522256947659355f, + 0.81616910611045879f, + 0.817113457614006f, + 0.81805562145892186f, + 0.81899559512275044f, + 0.81993337608889916f, + 0.82086896184664637f, + 0.8218023498911472f, + 0.82273353772344116f, + 0.82366252285045805f, + 0.82458930278502529f, + 0.82551387504587381f, + 0.82643623715764558f, + 0.82735638665089983f, + 0.82827432106211907f, + 0.82919003793371693f, + 0.83010353481404364f, + 0.83101480925739324f, + 0.83192385882400965f, + 0.83283068108009373f, + 0.8337352735978093f, + 0.83463763395529011f, + 0.83553775973664579f, + 0.83643564853196872f, + 0.83733129793734051f, + 0.83822470555483797f, + 0.83911586899254031f, + 0.84000478586453453f, + 0.84089145379092289f, + 0.84177587039782842f, + 0.84265803331740163f, + 0.84353794018782702f, + 0.844415588653329f, + 0.8452909763641786f, + 0.84616410097669936f, + 0.84703496015327406f, + 0.84790355156235053f, + 0.84876987287844818f, + 0.8496339217821639f, + 0.85049569596017938f, + 0.85135519310526508f, + 0.85221241091628896f, + 0.85306734709822085f, + 0.85391999936213903f, + 0.85477036542523732f, + 0.85561844301082923f, + 0.85646422984835635f, + 0.85730772367339259f, + 0.85814892222765116f, + 0.85898782325899026f, + 0.85982442452141961f, + 0.86065872377510555f, + 0.86149071878637817f, + 0.8623204073277364f, + 0.86314778717785412f, + 0.8639728561215867f, + 0.86479561194997623f, + 0.86561605246025763f, + 0.86643417545586487f, + 0.8672499787464365f, + 0.86806346014782154f, + 0.8688746174820855f, + 0.86968344857751589f, + 0.87048995126862883f, + 0.87129412339617363f, + 0.87209596280713941f, + 0.8728954673547612f, + 0.87369263489852422f, + 0.87448746330417149f, + 0.87527995044370765f, + 0.8760700941954066f, + 0.87685789244381551f, + 0.87764334307976144f, + 0.87842644400035663f, + 0.8792071931090043f, + 0.87998558831540408f, + 0.88076162753555787f, + 0.88153530869177488f, + 0.88230662971267804f, + 0.88307558853320878f, + 0.88384218309463292f, + 0.8846064113445461f, + 0.88536827123687933f, + 0.88612776073190425f, + 0.88688487779623937f, + 0.88763962040285393f, + 0.8883919865310751f, + 0.88914197416659235f, + 0.88988958130146301f, + 0.8906348059341177f, + 0.89137764606936609f, + 0.89211809971840139f, + 0.89285616489880615f, + 0.89359183963455813f, + 0.89432512195603453f, + 0.89505600990001799f, + 0.89578450150970124f, + 0.8965105948346932f, + 0.89723428793102367f, + 0.89795557886114807f, + 0.89867446569395382f, + 0.89939094650476448f, + 0.90010501937534515f, + 0.900816682393908f, + 0.90152593365511691f, + 0.90223277126009283f, + 0.90293719331641886f, + 0.90363919793814496f, + 0.90433878324579353f, + 0.90503594736636439f, + 0.90573068843333915f, + 0.90642300458668679f, + 0.90711289397286898f, + 0.90780035474484411f, + 0.90848538506207266f, + 0.90916798309052227f, + 0.90984814700267291f, + 0.9105258749775208f, + 0.91120116520058425f, + 0.91187401586390815f, + 0.91254442516606893f, + 0.9132123913121788f, + 0.91387791251389161f, + 0.91454098698940678f, + 0.91520161296347435f, + 0.91585978866739981f, + 0.91651551233904871f, + 0.91716878222285148f, + 0.91781959656980805f, + 0.91846795363749245f, + 0.91911385169005766f, + 0.9197572889982405f, + 0.9203982638393654f, + 0.92103677449734989f, + 0.92167281926270861f, + 0.92230639643255874f, + 0.92293750431062316f, + 0.92356614120723612f, + 0.92419230543934783f, + 0.92481599533052783f, + 0.92543720921097061f, + 0.92605594541749991f, + 0.92667220229357261f, + 0.92728597818928349f, + 0.9278972714613698f, + 0.92850608047321548f, + 0.9291124035948557f, + 0.92971623920298097f, + 0.93031758568094147f, + 0.93091644141875196f, + 0.93151280481309506f, + 0.93210667426732674f, + 0.93269804819147983f, + 0.93328692500226818f, + 0.93387330312309147f, + 0.93445718098403896f, + 0.93503855702189376f, + 0.9356174296801375f, + 0.93619379740895381f, + 0.93676765866523259f, + 0.93733901191257496f, + 0.93790785562129597f, + 0.93847418826842988f, + 0.93903800833773399f, + 0.93959931431969212f, + 0.94015810471151917f, + 0.94071437801716529f, + 0.94126813274731924f, + 0.94181936741941319f, + 0.94236808055762578f, + 0.94291427069288691f, + 0.94345793636288133f, + 0.94399907611205225f, + 0.9445376884916058f, + 0.94507377205951448f, + 0.94560732538052128f, + 0.94613834702614352f, + 0.94666683557467624f, + 0.94719278961119657f, + 0.94771620772756759f, + 0.94823708852244104f, + 0.94875543060126255f, + 0.94927123257627433f, + 0.94978449306651924f, + 0.95029521069784428f, + 0.9508033841029051f, + 0.95130901192116835f, + 0.9518120927989161f, + 0.95231262538924943f, + 0.95281060835209208f, + 0.95330604035419386f, + 0.95379892006913403f, + 0.95428924617732525f, + 0.95477701736601728f, + 0.95526223232929941f, + 0.95574488976810545f, + 0.95622498839021619f, + 0.95670252691026292f, + 0.95717750404973156f, + 0.95764991853696524f, + 0.95811976910716812f, + 0.95858705450240911f, + 0.95905177347162429f, + 0.95951392477062125f, + 0.95997350716208196f, + 0.96043051941556579f, + 0.96088496030751369f, + 0.96133682862125036f, + 0.96178612314698864f, + 0.96223284268183173f, + 0.9626769860297768f, + 0.96311855200171881f, + 0.96355753941545252f, + 0.96399394709567654f, + 0.96442777387399625f, + 0.96485901858892686f, + 0.96528768008589627f, + 0.96571375721724895f, + 0.96613724884224783f, + 0.96655815382707866f, + 0.96697647104485207f, + 0.96739219937560694f, + 0.96780533770631338f, + 0.96821588493087585f, + 0.9686238399501359f, + 0.96902920167187501f, + 0.96943196901081796f, + 0.96983214088863534f, + 0.9702297162339466f, + 0.97062469398232287f, + 0.97101707307629004f, + 0.97140685246533098f, + 0.97179403110588902f, + 0.97217860796137046f, + 0.97256058200214734f, + 0.97293995220556007f, + 0.97331671755592064f, + 0.97369087704451474f, + 0.97406242966960455f, + 0.97443137443643235f, + 0.97479771035722163f, + 0.97516143645118103f, + 0.97552255174450631f, + 0.97588105527038305f, + 0.97623694606898959f, + 0.97659022318749911f, + 0.97694088568008242f, + 0.97728893260791039f, + 0.97763436303915685f, + 0.97797717604900047f, + 0.97831737071962765f, + 0.97865494614023485f, + 0.97898990140703124f, + 0.97932223562324061f, + 0.97965194789910426f, + 0.9799790373518833f, + 0.98030350310586067f, + 0.98062534429234405f, + 0.98094456004966768f, + 0.98126114952319499f, + 0.98157511186532054f, + 0.98188644623547261f, + 0.98219515180011563f, + 0.98250122773275184f, + 0.98280467321392362f, + 0.98310548743121629f, + 0.98340366957925973f, + 0.98369921885973044f, + 0.98399213448135414f, + 0.98428241565990748f, + 0.98457006161822058f, + 0.98485507158617835f, + 0.98513744480072363f, + 0.98541718050585803f, + 0.98569427795264519f, + 0.98596873639921168f, + 0.98624055511074971f, + 0.98650973335951875f, + 0.98677627042484772f, + 0.98704016559313645f, + 0.98730141815785832f, + 0.98756002741956173f, + 0.9878159926858715f, + 0.98806931327149194f, + 0.98831998849820735f, + 0.98856801769488489f, + 0.98881340019747566f, + 0.98905613534901682f, + 0.98929622249963345f, + 0.98953366100653983f, + 0.98976845023404181f, + 0.99000058955353776f, + 0.99023007834352106f, + 0.99045691598958097f, + 0.99068110188440506f, + 0.99090263542778001f, + 0.99112151602659404f, + 0.99133774309483769f, + 0.99155131605360625f, + 0.99176223433110056f, + 0.99197049736262888f, + 0.99217610459060845f, + 0.99237905546456673f, + 0.99257934944114334f, + 0.99277698598409092f, + 0.99297196456427694f, + 0.99316428465968509f, + 0.99335394575541669f, + 0.99354094734369169f, + 0.99372528892385081f, + 0.99390697000235606f, + 0.99408599009279242f, + 0.99426234871586938f, + 0.99443604539942176f, + 0.99460707967841133f, + 0.99477545109492771f, + 0.99494115919819004f, + 0.99510420354454787f, + 0.99526458369748239f, + 0.99542229922760772f, + 0.99557734971267187f, + 0.9957297347375581f, + 0.99587945389428578f, + 0.99602650678201154f, + 0.99617089300703077f, + 0.996312612182778f, + 0.99645166392982831f, + 0.99658804787589839f, + 0.99672176365584741f, + 0.99685281091167788f, + 0.99698118929253687f, + 0.99710689845471678f, + 0.99722993806165661f, + 0.99735030778394196f, + 0.99746800729930707f, + 0.99758303629263489f, + 0.99769539445595812f, + 0.99780508148846014f, + 0.99791209709647588f, + 0.99801644099349218f, + 0.99811811290014918f, + 0.9982171125442405f, + 0.9983134396607144f, + 0.99840709399167404f, + 0.99849807528637868f, + 0.99858638330124405f, + 0.99867201779984294f, + 0.99875497855290607f, + 0.99883526533832245f, + 0.99891287794114036f, + 0.99898781615356746f, + 0.99906007977497147f, + 0.99912966861188113f, + 0.99919658247798593f, + 0.99926082119413751f, + 0.99932238458834954f, + 0.999381272495798f, + 0.99943748475882255f, + 0.9994910212269259f, + 0.99954188175677483f, + 0.99959006621220048f, + 0.99963557446419837f, + 0.99967840639092931f, + 0.99971856187771946f, + 0.99975604081706027f, + 0.99979084310860955f, + 0.99982296865919107f, + 0.99985241738279484f, + 0.99987918920057806f, + 0.99990328404086426f, + 0.9999247018391445f, + 0.99994344253807688f, + 0.99995950608748674f, + 0.99997289244436727f, + 0.99998360157287902f, + 0.9999916334443506f, + 0.99999698803727821f, + 0.99999966533732598f + }; + public static float[] SINE_120 = { + 0.0065449379673518581f, + 0.019633692460628301f, + 0.032719082821776137f, + 0.045798866936520771f, + 0.058870803651189033f, + 0.071932653156719387f, + 0.084982177372441667f, + 0.09801714032956059f, + 0.11103530855427769f, + 0.12403445145048532f, + 0.13701234168196802f, + 0.14996675555404498f, + 0.16289547339458874f, + 0.17579627993435451f, + 0.18866696468655525f, + 0.2015053223256171f, + 0.21430915306505074f, + 0.2270762630343732f, + 0.23980446465501654f, + 0.25249157701515795f, + 0.26513542624340797f, + 0.27773384588129219f, + 0.29028467725446233f, + 0.3027857698425746f, + 0.31523498164776964f, + 0.32763017956169349f, + 0.33996923973099424f, + 0.35225004792123354f, + 0.36447049987914965f, + 0.37662850169321077f, + 0.38872197015239557f, + 0.40074883310314097f, + 0.41270702980439467f, + 0.42459451128071307f, + 0.43640924067334208f, + 0.44814919358922256f, + 0.45981235844785984f, + 0.47139673682599764f, + 0.48290034380003727f, + 0.49432120828614462f, + 0.50565737337798455f, + 0.51690689668202761f, + 0.52806785065036799f, + 0.53913832291100017f, + 0.55011641659549337f, + 0.56100025066400983f, + 0.57178796022761225f, + 0.58247769686780215f, + 0.59306762895323706f, + 0.60355594195357143f, + 0.61394083875036642f, + 0.62422053994501758f, + 0.63439328416364549f, + 0.64445732835889735f, + 0.65441094810861034f, + 0.66425243791128175f, + 0.67398011147829784f, + 0.68359230202287125f, + 0.69308736254563585f, + 0.70246366611685174f, + 0.71171960615517138f, + 0.72085359670291882f, + 0.7298640726978356f, + 0.73874949024124625f, + 0.74750832686259672f, + 0.75613908178032285f, + 0.76464027615900032f, + 0.77301045336273699f, + 0.78124817920475853f, + 0.78935204219315003f, + 0.79732065377270711f, + 0.80515264856285829f, + 0.81284668459161513f, + 0.82040144352551359f, + 0.82781563089550203f, + 0.83508797631874299f, + 0.84221723371628654f, + 0.84920218152657889f, + 0.85604162291477137f, + 0.86273438597779184f, + 0.86927932394514362f, + 0.87567531537539967f, + 0.88192126434835494f, + 0.88801610065280734f, + 0.89395877996993212f, + 0.8997482840522214f, + 0.90538362089795521f, + 0.91086382492117568f, + 0.91618795711713596f, + 0.92135510522319242f, + 0.9263643838751181f, + 0.93121493475880346f, + 0.93590592675732565f, + 0.94043655609335486f, + 0.94480604646687805f, + 0.94901364918821385f, + 0.95305864330629697f, + 0.95694033573220882f, + 0.9606580613579353f, + 0.96421118317032928f, + 0.96759909236025976f, + 0.9708212084269281f, + 0.97387697927733363f, + 0.97676588132087239f, + 0.97948741955905139f, + 0.98204112767030394f, + 0.98442656808989171f, + 0.98664333208487898f, + 0.98869103982416728f, + 0.99056934044357725f, + 0.99227791210596705f, + 0.99381646205637808f, + 0.99518472667219682f, + 0.99638247150832537f, + 0.99740949133735191f, + 0.99826561018471593f, + 0.99895068135886012f, + 0.99946458747636568f, + 0.99980724048206482f, + 0.99997858166412923f + }; + } +} diff --git a/SharpJaad.AAC/Gain/FFT.cs b/SharpJaad.AAC/Gain/FFT.cs new file mode 100644 index 0000000..2859564 --- /dev/null +++ b/SharpJaad.AAC/Gain/FFT.cs @@ -0,0 +1,144 @@ +using System; + +namespace SharpJaad.AAC.Gain +{ + public static class FFT + { + public static float[][] FFT_TABLE_128 = { + new float[] {1.0f, -0.0f}, + new float[] {0.99879545f, -0.049067676f}, + new float[] {0.9951847f, -0.09801714f}, + new float[] {0.9891765f, -0.14673047f}, + new float[] {0.98078525f, -0.19509032f}, + new float[] {0.97003126f, -0.24298018f}, + new float[] {0.95694035f, -0.29028466f}, + new float[] {0.94154406f, -0.33688986f}, + new float[] {0.9238795f, -0.38268343f}, + new float[] {0.9039893f, -0.42755508f}, + new float[] {0.8819213f, -0.47139674f}, + new float[] {0.8577286f, -0.51410276f}, + new float[] {0.8314696f, -0.55557024f}, + new float[] {0.8032075f, -0.5956993f}, + new float[] {0.77301043f, -0.6343933f}, + new float[] {0.7409511f, -0.671559f}, + new float[] {0.70710677f, -0.70710677f}, + new float[] {0.671559f, -0.7409511f}, + new float[] {0.6343933f, -0.77301043f}, + new float[] {0.5956993f, -0.8032075f}, + new float[] {0.55557024f, -0.8314696f}, + new float[] {0.51410276f, -0.8577286f}, + new float[] {0.47139674f, -0.8819213f}, + new float[] {0.42755508f, -0.9039893f}, + new float[] {0.38268343f, -0.9238795f}, + new float[] {0.33688986f, -0.94154406f}, + new float[] {0.29028466f, -0.95694035f}, + new float[] {0.24298018f, -0.97003126f}, + new float[] {0.19509032f, -0.98078525f}, + new float[] {0.14673047f, -0.9891765f}, + new float[] {0.09801714f, -0.9951847f}, + new float[] {0.049067676f, -0.99879545f}, + new float[] {6.123234E-17f, -1.0f}, + new float[] {-0.049067676f, -0.99879545f}, + new float[] {-0.09801714f, -0.9951847f}, + new float[] {-0.14673047f, -0.9891765f}, + new float[] {-0.19509032f, -0.98078525f}, + new float[] {-0.24298018f, -0.97003126f}, + new float[] {-0.29028466f, -0.95694035f}, + new float[] {-0.33688986f, -0.94154406f}, + new float[] {-0.38268343f, -0.9238795f}, + new float[] {-0.42755508f, -0.9039893f}, + new float[] {-0.47139674f, -0.8819213f}, + new float[] {-0.51410276f, -0.8577286f}, + new float[] {-0.55557024f, -0.8314696f}, + new float[] {-0.5956993f, -0.8032075f}, + new float[] {-0.6343933f, -0.77301043f}, + new float[] {-0.671559f, -0.7409511f}, + new float[] {-0.70710677f, -0.70710677f}, + new float[] {-0.7409511f, -0.671559f}, + new float[] {-0.77301043f, -0.6343933f}, + new float[] {-0.8032075f, -0.5956993f}, + new float[] {-0.8314696f, -0.55557024f}, + new float[] {-0.8577286f, -0.51410276f}, + new float[] {-0.8819213f, -0.47139674f}, + new float[] {-0.9039893f, -0.42755508f}, + new float[] {-0.9238795f, -0.38268343f}, + new float[] {-0.94154406f, -0.33688986f}, + new float[] {-0.95694035f, -0.29028466f}, + new float[] {-0.97003126f, -0.24298018f}, + new float[] {-0.98078525f, -0.19509032f}, + new float[] {-0.9891765f, -0.14673047f}, + new float[] {-0.9951847f, -0.09801714f}, + new float[] {-0.99879545f, -0.049067676f} + }; + public static float[][] FFT_TABLE_16 = { + new float[] {1.0f, -0.0f}, + new float[] {0.9238795f, -0.38268343f}, + new float[] {0.70710677f, -0.70710677f}, + new float[] {0.38268343f, -0.9238795f}, + new float[] {6.123234E-17f, -1.0f}, + new float[] {-0.38268343f, -0.9238795f}, + new float[] {-0.70710677f, -0.70710677f}, + new float[] {-0.9238795f, -0.38268343f} + }; + + public static void Process(float[][] input, int n) + { + int ln = (int)Math.Round(Math.Log(n) / Math.Log(2)); + float[][] table = n == 128 ? FFT_TABLE_128 : FFT_TABLE_16; + + //bit-reversal + float[][] rev = new float[n][]; + int i, ii = 0; + for (i = 0; i < n; i++) + { + rev[i][0] = input[ii][0]; + rev[i][1] = input[ii][1]; + int kk = n >> 1; + while (ii >= kk && kk > 0) + { + ii -= kk; + kk >>= 1; + } + ii += kk; + } + + for (i = 0; i < n; i++) + { + input[i][0] = rev[i][0]; + input[i][1] = rev[i][1]; + } + + //calculation + int blocks = n / 2; + int size = 2; + int j, k, l, k0, k1, size2; + float[] a = new float[2]; + for (i = 0; i < ln; i++) + { + size2 = size / 2; + k0 = 0; + k1 = size2; + for (j = 0; j < blocks; ++j) + { + l = 0; + for (k = 0; k < size2; ++k) + { + a[0] = input[k1][0] * table[l][0] - input[k1][1] * table[l][1]; + a[1] = input[k1][0] * table[l][1] + input[k1][1] * table[l][0]; + input[k1][0] = input[k0][0] - a[0]; + input[k1][1] = input[k0][1] - a[1]; + input[k0][0] += a[0]; + input[k0][1] += a[1]; + l += blocks; + k0++; + k1++; + } + k0 += size2; + k1 += size2; + } + blocks = blocks / 2; + size = size * 2; + } + } + } +} diff --git a/SharpJaad.AAC/Gain/GCConstants.cs b/SharpJaad.AAC/Gain/GCConstants.cs new file mode 100644 index 0000000..c100fee --- /dev/null +++ b/SharpJaad.AAC/Gain/GCConstants.cs @@ -0,0 +1,16 @@ +namespace SharpJaad.AAC.Gain +{ + public class GCConstants + { + public const int BANDS = 4; + public const int MAX_CHANNELS = 5; + public const int NPQFTAPS = 96; + public const int NPEPARTS = 64; //number of pre-echo inhibition parts + public const int ID_GAIN = 16; + public static int[] LN_GAIN = + { + -4, -3, -2, -1, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11 + }; + } +} diff --git a/SharpJaad.AAC/Gain/GainControl.cs b/SharpJaad.AAC/Gain/GainControl.cs new file mode 100644 index 0000000..683e9c5 --- /dev/null +++ b/SharpJaad.AAC/Gain/GainControl.cs @@ -0,0 +1,360 @@ +using SharpJaad.AAC.Syntax; +using System; +using System.Linq; +using static SharpJaad.AAC.Syntax.ICSInfo; + +namespace SharpJaad.AAC.Gain +{ + public class GainControl + { + private int _frameLen; + private int _lbLong; + private int _lbShort; + private IMDCT _imdct; + private IPQF _ipqf; + private float[] _buffer1, _function; + private float[][] _buffer2, _overlap; + private int _maxBand; + private int[][][] _level, _levelPrev; + private int[][][] _location, _locationPrev; + + public GainControl(int frameLen) + { + _frameLen = frameLen; + _lbLong = frameLen / GCConstants.BANDS; + _lbShort = _lbLong / 8; + _imdct = new IMDCT(frameLen); + _ipqf = new IPQF(); + _levelPrev = new int[0][][]; + _locationPrev = new int[0][][]; + _buffer1 = new float[frameLen / 2]; + _buffer2 = new float[GCConstants.BANDS][]; + for (int i = 0; i < GCConstants.BANDS; i++) + { + _buffer2[i] = new float[_lbLong]; + } + _function = new float[_lbLong * 2]; + _overlap = new float[GCConstants.BANDS][]; + for (int i = 0; i < GCConstants.BANDS; i++) + { + _overlap[i] = new float[_lbLong * 2]; + } + } + + public void Decode(BitStream input, WindowSequence winSeq) + { + _maxBand = input.ReadBits(2) + 1; + + int wdLen, locBits, locBits2 = 0; + switch (winSeq) + { + case WindowSequence.ONLY_LONG_SEQUENCE: + wdLen = 1; + locBits = 5; + locBits2 = 5; + break; + case WindowSequence.EIGHT_SHORT_SEQUENCE: + wdLen = 8; + locBits = 2; + locBits2 = 2; + break; + case WindowSequence.LONG_START_SEQUENCE: + wdLen = 2; + locBits = 4; + locBits2 = 2; + break; + case WindowSequence.LONG_STOP_SEQUENCE: + wdLen = 2; + locBits = 4; + locBits2 = 5; + break; + default: + return; + } + _level = new int[_maxBand][][]; + for (int i = 0; i < _maxBand; i++) + { + _level[i] = new int[wdLen][]; + } + _location = new int[_maxBand][][]; + for (int i = 0; i < _maxBand; i++) + { + _location[i] = new int[wdLen][]; + } + + int wd, k, len, bits; + for (int bd = 1; bd < _maxBand; bd++) + { + for (wd = 0; wd < wdLen; wd++) + { + len = input.ReadBits(3); + _level[bd][wd] = new int[len]; + _location[bd][wd] = new int[len]; + for (k = 0; k < len; k++) + { + _level[bd][wd][k] = input.ReadBits(4); + bits = wd == 0 ? locBits : locBits2; + _location[bd][wd][k] = input.ReadBits(bits); + } + } + } + } + + public void Process(float[] data, int winShape, int winShapePrev, WindowSequence winSeq) + { + _imdct.Process(data, _buffer1, winShape, winShapePrev, winSeq); + + for (int i = 0; i < GCConstants.BANDS; i++) + { + Compensate(_buffer1, _buffer2, winSeq, i); + } + + _ipqf.Process(_buffer2, _frameLen, _maxBand, data); + } + + /** + * gain compensation and overlap-add: + * - the gain control function is calculated + * - the gain control function applies to IMDCT output samples as a another IMDCT window + * - the reconstructed time domain signal produces by overlap-add + */ + private void Compensate(float[] input, float[][] output, WindowSequence winSeq, int band) + { + int j; + if (winSeq.Equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) + { + int a, b; + for (int k = 0; k < 8; k++) + { + //calculation + CalculateFunctionData(_lbShort * 2, band, winSeq, k); + //applying + for (j = 0; j < _lbShort * 2; j++) + { + a = band * _lbLong * 2 + k * _lbShort * 2 + j; + input[a] *= _function[j]; + } + //overlapping + for (j = 0; j < _lbShort; j++) + { + a = j + _lbLong * 7 / 16 + _lbShort * k; + b = band * _lbLong * 2 + k * _lbShort * 2 + j; + _overlap[band][a] += input[b]; + } + //store for next frame + for (j = 0; j < _lbShort; j++) + { + a = j + _lbLong * 7 / 16 + _lbShort * (k + 1); + b = band * _lbLong * 2 + k * _lbShort * 2 + _lbShort + j; + + _overlap[band][a] = input[b]; + } + _locationPrev[band][0] = _location[band][k].ToArray(); + _levelPrev[band][0] = _level[band][k].ToArray(); + } + Array.Copy(_overlap[band], 0, output[band], 0, _lbLong); + Array.Copy(_overlap[band], _lbLong, _overlap[band], 0, _lbLong); + } + else + { + //calculation + CalculateFunctionData(_lbLong * 2, band, winSeq, 0); + //applying + for (j = 0; j < _lbLong * 2; j++) + { + input[band * _lbLong * 2 + j] *= _function[j]; + } + //overlapping + for (j = 0; j < _lbLong; j++) + { + output[band][j] = _overlap[band][j] + input[band * _lbLong * 2 + j]; + } + //store for next frame + for (j = 0; j < _lbLong; j++) + { + _overlap[band][j] = input[band * _lbLong * 2 + _lbLong + j]; + } + + int lastBlock = winSeq.Equals(WindowSequence.ONLY_LONG_SEQUENCE) ? 1 : 0; + _locationPrev[band][0] = _location[band][lastBlock].ToArray(); + _levelPrev[band][0] = _level[band][lastBlock].ToArray(); + } + } + + //produces gain control function data, stores it in 'function' array + private void CalculateFunctionData(int samples, int band, WindowSequence winSeq, int blockID) + { + int[] locA = new int[10]; + float[] levA = new float[10]; + float[] modFunc = new float[samples]; + float[] buf1 = new float[samples / 2]; + float[] buf2 = new float[samples / 2]; + float[] buf3 = new float[samples / 2]; + + int maxLocGain0 = 0, maxLocGain1 = 0, maxLocGain2 = 0; + switch (winSeq) + { + case WindowSequence.ONLY_LONG_SEQUENCE: + case WindowSequence.EIGHT_SHORT_SEQUENCE: + maxLocGain0 = maxLocGain1 = samples / 2; + maxLocGain2 = 0; + break; + case WindowSequence.LONG_START_SEQUENCE: + maxLocGain0 = samples / 2; + maxLocGain1 = samples * 7 / 32; + maxLocGain2 = samples / 16; + break; + case WindowSequence.LONG_STOP_SEQUENCE: + maxLocGain0 = samples / 16; + maxLocGain1 = samples * 7 / 32; + maxLocGain2 = samples / 2; + break; + } + + //calculate the fragment modification functions + //for the first half region + CalculateFMD(band, 0, true, maxLocGain0, samples, locA, levA, buf1); + + //for the latter half region + int block = winSeq.Equals(WindowSequence.EIGHT_SHORT_SEQUENCE) ? blockID : 0; + float secLevel = CalculateFMD(band, block, false, maxLocGain1, samples, locA, levA, buf2); + + //for the non-overlapped region + if (winSeq.Equals(WindowSequence.LONG_START_SEQUENCE) || winSeq.Equals(WindowSequence.LONG_STOP_SEQUENCE)) + { + CalculateFMD(band, 1, false, maxLocGain2, samples, locA, levA, buf3); + } + + //calculate a gain modification function + int i; + int flatLen = 0; + if (winSeq.Equals(WindowSequence.LONG_STOP_SEQUENCE)) + { + flatLen = samples / 2 - maxLocGain0 - maxLocGain1; + for (i = 0; i < flatLen; i++) + { + modFunc[i] = 1.0f; + } + } + if (winSeq.Equals(WindowSequence.ONLY_LONG_SEQUENCE) || winSeq.Equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) levA[0] = 1.0f; + + for (i = 0; i < maxLocGain0; i++) + { + modFunc[i + flatLen] = levA[0] * secLevel * buf1[i]; + } + for (i = 0; i < maxLocGain1; i++) + { + modFunc[i + flatLen + maxLocGain0] = levA[0] * buf2[i]; + } + + if (winSeq.Equals(WindowSequence.LONG_START_SEQUENCE)) + { + for (i = 0; i < maxLocGain2; i++) + { + modFunc[i + maxLocGain0 + maxLocGain1] = buf3[i]; + } + flatLen = samples / 2 - maxLocGain1 - maxLocGain2; + for (i = 0; i < flatLen; i++) + { + modFunc[i + maxLocGain0 + maxLocGain1 + maxLocGain2] = 1.0f; + } + } + else if (winSeq.Equals(WindowSequence.LONG_STOP_SEQUENCE)) + { + for (i = 0; i < maxLocGain2; i++) + { + modFunc[i + flatLen + maxLocGain0 + maxLocGain1] = buf3[i]; + } + } + + //calculate a gain control function + for (i = 0; i < samples; i++) + { + _function[i] = 1.0f / modFunc[i]; + } + } + + /* + * calculates a fragment modification function by interpolating the gain + * values of the gain change positions + */ + private float CalculateFMD(int bd, int wd, bool prev, int maxLocGain, int samples, int[] loc, float[] lev, float[] fmd) + { + int[] m = new int[samples / 2]; + int[] lct = prev ? _locationPrev[bd][wd] : _location[bd][wd]; + int[] lvl = prev ? _levelPrev[bd][wd] : _level[bd][wd]; + int length = lct.Length; + + int lngain; + int i; + for (i = 0; i < length; i++) + { + loc[i + 1] = 8 * lct[i]; //gainc + lngain = GetGainChangePointID(lvl[i]); //gainc + if (lngain < 0) + lev[i + 1] = 1.0f / (float)Math.Pow(2, -lngain); + else + lev[i + 1] = (float)Math.Pow(2, lngain); + } + + //set start point values + loc[0] = 0; + if (length == 0) + lev[0] = 1.0f; + else + lev[0] = lev[1]; + float secLevel = lev[0]; + + //set end point values + loc[length + 1] = maxLocGain; + lev[length + 1] = 1.0f; + + int j; + for (i = 0; i < maxLocGain; i++) + { + m[i] = 0; + for (j = 0; j <= length + 1; j++) + { + if (loc[j] <= i) m[i] = j; + } + } + + for (i = 0; i < maxLocGain; i++) + { + if (i >= loc[m[i]] && i <= loc[m[i]] + 7) + fmd[i] = InterpolateGain(lev[m[i]], lev[m[i] + 1], i - loc[m[i]]); + else + fmd[i] = lev[m[i] + 1]; + } + + return secLevel; + } + + /** + * transformes the exponent value of the gain to the id of the gain change + * point + */ + private int GetGainChangePointID(int lngain) + { + for (int i = 0; i < GCConstants.ID_GAIN; i++) + { + if (lngain == GCConstants.LN_GAIN[i]) + return i; + } + return 0; //shouldn't happen + } + + /** + * calculates a fragment modification function + * the interpolated gain value between the gain values of two gain change + * positions is calculated by the formula: + * f(a,b,j) = 2^(((8-j)log2(a)+j*log2(b))/8) + */ + private float InterpolateGain(float alev0, float alev1, int iloc) + { + float a0 = (float)(Math.Log(alev0) / Math.Log(2)); + float a1 = (float)(Math.Log(alev1) / Math.Log(2)); + return (float)Math.Pow(2.0f, ((8 - iloc) * a0 + iloc * a1) / 8); + } + } +} diff --git a/SharpJaad.AAC/Gain/IMDCT.cs b/SharpJaad.AAC/Gain/IMDCT.cs new file mode 100644 index 0000000..2bd325f --- /dev/null +++ b/SharpJaad.AAC/Gain/IMDCT.cs @@ -0,0 +1,232 @@ +using System; +using SharpJaad.AAC; +using static SharpJaad.AAC.Syntax.ICSInfo; + +namespace SharpJaad.AAC.Gain +{ + public class IMDCT + { + private static float[][] _LONG_WINDOWS = { Windows.SINE_256, Windows.KBD_256 }; + private static float[][] _SHORT_WINDOWS = { Windows.SINE_32, Windows.KBD_32 }; + private int _frameLen, _shortFrameLen, _lbLong, _lbShort, _lbMid; + + public IMDCT(int frameLen) + { + _frameLen = frameLen; + _lbLong = frameLen / GCConstants.BANDS; + _shortFrameLen = frameLen / 8; + _lbShort = _shortFrameLen / GCConstants.BANDS; + _lbMid = (_lbLong - _lbShort) / 2; + } + + public void Process(float[] input, float[] output, int winShape, int winShapePrev, WindowSequence winSeq) + { + float[] buf = new float[_frameLen]; + + int b, j, i; + if (winSeq.Equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) + { + for (b = 0; b < GCConstants.BANDS; b++) + { + for (j = 0; j < 8; j++) + { + for (i = 0; i < _lbShort; i++) + { + if (b % 2 == 0) + buf[_lbLong * b + _lbShort * j + i] = input[_shortFrameLen * j + _lbShort * b + i]; + else + buf[_lbLong * b + _lbShort * j + i] = input[_shortFrameLen * j + _lbShort * b + _lbShort - 1 - i]; + } + } + } + } + else + { + for (b = 0; b < GCConstants.BANDS; b++) + { + for (i = 0; i < _lbLong; i++) + { + if (b % 2 == 0) + buf[_lbLong * b + i] = input[_lbLong * b + i]; + else + buf[_lbLong * b + i] = input[_lbLong * b + _lbLong - 1 - i]; + } + } + } + + for (b = 0; b < GCConstants.BANDS; b++) + { + Process2(buf, output, winSeq, winShape, winShapePrev, b); + } + } + + private void Process2(float[] input, float[] output, WindowSequence winSeq, int winShape, int winShapePrev, int band) + { + float[] bufIn = new float[_lbLong]; + float[] bufOut = new float[_lbLong * 2]; + float[] window = new float[_lbLong * 2]; + float[] window1 = new float[_lbShort * 2]; + float[] window2 = new float[_lbShort * 2]; + + //init windows + int i; + switch (winSeq) + { + case WindowSequence.ONLY_LONG_SEQUENCE: + for (i = 0; i < _lbLong; i++) + { + window[i] = _LONG_WINDOWS[winShapePrev][i]; + window[_lbLong * 2 - 1 - i] = _LONG_WINDOWS[winShape][i]; + } + break; + case WindowSequence.EIGHT_SHORT_SEQUENCE: + for (i = 0; i < _lbShort; i++) + { + window1[i] = _SHORT_WINDOWS[winShapePrev][i]; + window1[_lbShort * 2 - 1 - i] = _SHORT_WINDOWS[winShape][i]; + window2[i] = _SHORT_WINDOWS[winShape][i]; + window2[_lbShort * 2 - 1 - i] = _SHORT_WINDOWS[winShape][i]; + } + break; + case WindowSequence.LONG_START_SEQUENCE: + for (i = 0; i < _lbLong; i++) + { + window[i] = _LONG_WINDOWS[winShapePrev][i]; + } + for (i = 0; i < _lbMid; i++) + { + window[i + _lbLong] = 1.0f; + } + + for (i = 0; i < _lbShort; i++) + { + window[i + _lbMid + _lbLong] = _SHORT_WINDOWS[winShape][_lbShort - 1 - i]; + } + for (i = 0; i < _lbMid; i++) + { + window[i + _lbMid + _lbLong + _lbShort] = 0.0f; + } + break; + case WindowSequence.LONG_STOP_SEQUENCE: + for (i = 0; i < _lbMid; i++) + { + window[i] = 0.0f; + } + for (i = 0; i < _lbShort; i++) + { + window[i + _lbMid] = _SHORT_WINDOWS[winShapePrev][i]; + } + for (i = 0; i < _lbMid; i++) + { + window[i + _lbMid + _lbShort] = 1.0f; + } + for (i = 0; i < _lbLong; i++) + { + window[i + _lbMid + _lbShort + _lbMid] = _LONG_WINDOWS[winShape][_lbLong - 1 - i]; + } + break; + } + + int j; + if (winSeq.Equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) + { + int k; + for (j = 0; j < 8; j++) + { + for (k = 0; k < _lbShort; k++) + { + bufIn[k] = input[band * _lbLong + j * _lbShort + k]; + } + if (j == 0) + Array.Copy(window1, 0, window, 0, _lbShort * 2); + else + Array.Copy(window2, 0, window, 0, _lbShort * 2); + Imdct(bufIn, bufOut, window, _lbShort); + for (k = 0; k < _lbShort * 2; k++) + { + output[band * _lbLong * 2 + j * _lbShort * 2 + k] = bufOut[k] / 32.0f; + } + } + } + else + { + for (j = 0; j < _lbLong; j++) + { + bufIn[j] = input[band * _lbLong + j]; + } + Imdct(bufIn, bufOut, window, _lbLong); + for (j = 0; j < _lbLong * 2; j++) + { + output[band * _lbLong * 2 + j] = bufOut[j] / 256.0f; + } + } + } + + private void Imdct(float[] input, float[] output, float[] window, int n) + { + int n2 = n / 2; + float[][] table, table2; + if (n == 256) + { + table = IMDCTTables.IMDCT_TABLE_256; + table2 = IMDCTTables.IMDCT_POST_TABLE_256; + } + else if (n == 32) + { + table = IMDCTTables.IMDCT_TABLE_32; + table2 = IMDCTTables.IMDCT_POST_TABLE_32; + } + else throw new AACException("gain control: unexpected IMDCT length"); + + float[] tmp = new float[n]; + int i; + for (i = 0; i < n2; ++i) + { + tmp[i] = input[2 * i]; + } + for (i = n2; i < n; ++i) + { + tmp[i] = -input[2 * n - 1 - 2 * i]; + } + + //pre-twiddle + float[][] buf = new float[n2][]; + for (i = 0; i < n2; i++) + { + buf[i] = new float[2]; + } + for (i = 0; i < n2; i++) + { + buf[i][0] = table[i][0] * tmp[2 * i] - table[i][1] * tmp[2 * i + 1]; + buf[i][1] = table[i][0] * tmp[2 * i + 1] + table[i][1] * tmp[2 * i]; + } + + //fft + FFT.Process(buf, n2); + + //post-twiddle and reordering + for (i = 0; i < n2; i++) + { + tmp[i] = table2[i][0] * buf[i][0] + table2[i][1] * buf[n2 - 1 - i][0] + + table2[i][2] * buf[i][1] + table2[i][3] * buf[n2 - 1 - i][1]; + tmp[n - 1 - i] = table2[i][2] * buf[i][0] - table2[i][3] * buf[n2 - 1 - i][0] + - table2[i][0] * buf[i][1] + table2[i][1] * buf[n2 - 1 - i][1]; + } + + //copy to output and apply window + Array.Copy(tmp, n2, output, 0, n2); + for (i = n2; i < n * 3 / 2; ++i) + { + output[i] = -tmp[n * 3 / 2 - 1 - i]; + } + for (i = n * 3 / 2; i < n * 2; ++i) + { + output[i] = -tmp[i - n * 3 / 2]; + } + for (i = 0; i < n; i++) + { + output[i] *= window[i]; + } + } + } +} diff --git a/SharpJaad.AAC/Gain/IMDCTTables.cs b/SharpJaad.AAC/Gain/IMDCTTables.cs new file mode 100644 index 0000000..a95458e --- /dev/null +++ b/SharpJaad.AAC/Gain/IMDCTTables.cs @@ -0,0 +1,304 @@ +namespace SharpJaad.AAC.Gain +{ + public static class IMDCTTables + { + //pre-twiddling tables + public static float[][] IMDCT_TABLE_256 = { + new float[] {1.0f, -0.0f}, + new float[] {0.9996988f, -0.024541229f}, + new float[] {0.99879545f, -0.049067676f}, + new float[] {0.99729043f, -0.07356457f}, + new float[] {0.9951847f, -0.09801714f}, + new float[] {0.99247956f, -0.12241068f}, + new float[] {0.9891765f, -0.14673047f}, + new float[] {0.98527765f, -0.1709619f}, + new float[] {0.98078525f, -0.19509032f}, + new float[] {0.9757021f, -0.21910124f}, + new float[] {0.97003126f, -0.2429802f}, + new float[] {0.96377605f, -0.26671278f}, + new float[] {0.95694035f, -0.29028466f}, + new float[] {0.94952816f, -0.31368175f}, + new float[] {0.94154406f, -0.33688986f}, + new float[] {0.9329928f, -0.35989505f}, + new float[] {0.9238795f, -0.38268346f}, + new float[] {0.9142097f, -0.40524134f}, + new float[] {0.9039893f, -0.42755508f}, + new float[] {0.8932243f, -0.44961134f}, + new float[] {0.88192123f, -0.47139674f}, + new float[] {0.87008697f, -0.49289823f}, + new float[] {0.8577286f, -0.51410276f}, + new float[] {0.8448536f, -0.53499764f}, + new float[] {0.8314696f, -0.55557024f}, + new float[] {0.8175848f, -0.5758082f}, + new float[] {0.8032075f, -0.5956993f}, + new float[] {0.7883464f, -0.61523163f}, + new float[] {0.77301043f, -0.63439333f}, + new float[] {0.7572088f, -0.65317285f}, + new float[] {0.7409511f, -0.671559f}, + new float[] {0.7242471f, -0.68954057f}, + new float[] {0.70710677f, -0.70710677f}, + new float[] {0.6895405f, -0.7242471f}, + new float[] {0.6715589f, -0.7409512f}, + new float[] {0.6531728f, -0.7572089f}, + new float[] {0.6343933f, -0.77301043f}, + new float[] {0.6152316f, -0.7883464f}, + new float[] {0.5956993f, -0.8032075f}, + new float[] {0.57580817f, -0.8175848f}, + new float[] {0.5555702f, -0.83146966f}, + new float[] {0.53499764f, -0.8448536f}, + new float[] {0.5141027f, -0.85772866f}, + new float[] {0.4928982f, -0.87008697f}, + new float[] {0.47139665f, -0.8819213f}, + new float[] {0.4496113f, -0.8932243f}, + new float[] {0.4275551f, -0.9039893f}, + new float[] {0.40524128f, -0.9142098f}, + new float[] {0.38268343f, -0.9238795f}, + new float[] {0.35989496f, -0.9329928f}, + new float[] {0.33688983f, -0.94154406f}, + new float[] {0.31368166f, -0.9495282f}, + new float[] {0.29028463f, -0.95694035f}, + new float[] {0.26671275f, -0.96377605f}, + new float[] {0.24298012f, -0.97003126f}, + new float[] {0.21910122f, -0.9757021f}, + new float[] {0.19509023f, -0.9807853f}, + new float[] {0.17096186f, -0.98527765f}, + new float[] {0.1467305f, -0.9891765f}, + new float[] {0.122410625f, -0.99247956f}, + new float[] {0.098017134f, -0.9951847f}, + new float[] {0.07356449f, -0.99729043f}, + new float[] {0.04906765f, -0.99879545f}, + new float[] {0.024541136f, -0.9996988f}, + new float[] {-4.371139E-8f, -1.0f}, + new float[] {-0.024541223f, -0.9996988f}, + new float[] {-0.04906774f, -0.99879545f}, + new float[] {-0.073564574f, -0.99729043f}, + new float[] {-0.09801722f, -0.9951847f}, + new float[] {-0.12241071f, -0.9924795f}, + new float[] {-0.14673057f, -0.9891765f}, + new float[] {-0.17096195f, -0.98527765f}, + new float[] {-0.19509032f, -0.98078525f}, + new float[] {-0.21910131f, -0.9757021f}, + new float[] {-0.2429802f, -0.97003126f}, + new float[] {-0.26671284f, -0.96377605f}, + new float[] {-0.29028472f, -0.9569403f}, + new float[] {-0.31368172f, -0.94952816f}, + new float[] {-0.33688992f, -0.94154406f}, + new float[] {-0.35989505f, -0.9329928f}, + new float[] {-0.38268352f, -0.9238795f}, + new float[] {-0.40524134f, -0.9142097f}, + new float[] {-0.42755508f, -0.9039893f}, + new float[] {-0.44961137f, -0.8932243f}, + new float[] {-0.47139683f, -0.88192123f}, + new float[] {-0.49289817f, -0.870087f}, + new float[] {-0.51410276f, -0.8577286f}, + new float[] {-0.5349977f, -0.8448535f}, + new float[] {-0.55557036f, -0.83146954f}, + new float[] {-0.57580817f, -0.8175848f}, + new float[] {-0.59569937f, -0.8032075f}, + new float[] {-0.6152317f, -0.78834635f}, + new float[] {-0.6343933f, -0.7730105f}, + new float[] {-0.65317285f, -0.7572088f}, + new float[] {-0.67155904f, -0.74095106f}, + new float[] {-0.6895407f, -0.724247f}, + new float[] {-0.70710677f, -0.70710677f}, + new float[] {-0.72424716f, -0.6895405f}, + new float[] {-0.74095124f, -0.67155886f}, + new float[] {-0.7572088f, -0.65317285f}, + new float[] {-0.7730105f, -0.6343933f}, + new float[] {-0.78834647f, -0.6152315f}, + new float[] {-0.80320764f, -0.59569913f}, + new float[] {-0.8175848f, -0.57580817f}, + new float[] {-0.83146966f, -0.5555702f}, + new float[] {-0.84485364f, -0.53499746f}, + new float[] {-0.8577286f, -0.51410276f}, + new float[] {-0.870087f, -0.49289814f}, + new float[] {-0.88192135f, -0.47139663f}, + new float[] {-0.8932243f, -0.44961137f}, + new float[] {-0.9039893f, -0.42755505f}, + new float[] {-0.9142098f, -0.40524122f}, + new float[] {-0.9238796f, -0.38268328f}, + new float[] {-0.9329928f, -0.35989505f}, + new float[] {-0.9415441f, -0.3368898f}, + new float[] {-0.9495282f, -0.3136816f}, + new float[] {-0.95694035f, -0.29028472f}, + new float[] {-0.96377605f, -0.26671273f}, + new float[] {-0.97003126f, -0.24298008f}, + new float[] {-0.97570217f, -0.21910107f}, + new float[] {-0.9807853f, -0.19509031f}, + new float[] {-0.98527765f, -0.17096181f}, + new float[] {-0.9891765f, -0.14673033f}, + new float[] {-0.9924795f, -0.1224107f}, + new float[] {-0.9951847f, -0.0980171f}, + new float[] {-0.9972905f, -0.07356445f}, + new float[] {-0.99879545f, -0.049067486f}, + new float[] {-0.9996988f, -0.02454121f} + }; + public static float[][] IMDCT_TABLE_32 = { + new float[] {1.0f, -0.0f}, + new float[] {0.98078525f, -0.19509032f}, + new float[] {0.9238795f, -0.38268346f}, + new float[] {0.8314696f, -0.55557024f}, + new float[] {0.70710677f, -0.70710677f}, + new float[] {0.5555702f, -0.83146966f}, + new float[] {0.38268343f, -0.9238795f}, + new float[] {0.19509023f, -0.9807853f}, + new float[] {-4.371139E-8f, -1.0f}, + new float[] {-0.19509032f, -0.98078525f}, + new float[] {-0.38268352f, -0.9238795f}, + new float[] {-0.55557036f, -0.83146954f}, + new float[] {-0.70710677f, -0.70710677f}, + new float[] {-0.83146966f, -0.5555702f}, + new float[] {-0.9238796f, -0.38268328f}, + new float[] {-0.9807853f, -0.19509031f} + }; + //post-twiddling tables + public static float[][] IMDCT_POST_TABLE_256 = { + new float[] {0.49232805f, 0.50766724f, 0.50147516f, 0.49840719f}, + new float[] {0.47697723f, 0.5229804f, 0.50407255f, 0.4948688f}, + new float[] {0.46162924f, 0.5382531f, 0.50619966f, 0.49086043f}, + new float[] {0.44629848f, 0.5534709f, 0.50785726f, 0.4863832f}, + new float[] {0.43099934f, 0.5686195f, 0.5090466f, 0.48143846f}, + new float[] {0.41574615f, 0.58368444f, 0.5097693f, 0.47602817f}, + new float[] {0.40055317f, 0.5986516f, 0.5100275f, 0.47015458f}, + new float[] {0.38543463f, 0.6135067f, 0.50982374f, 0.46382055f}, + new float[] {0.37040454f, 0.6282357f, 0.5091608f, 0.45702913f}, + new float[] {0.35547704f, 0.64282453f, 0.50804234f, 0.4497841f}, + new float[] {0.34066594f, 0.65725935f, 0.506472f, 0.44208938f}, + new float[] {0.32598507f, 0.6715264f, 0.5044541f, 0.43394947f}, + new float[] {0.31144798f, 0.6856121f, 0.5019932f, 0.42536932f}, + new float[] {0.29706824f, 0.6995029f, 0.4990945f, 0.41635424f}, + new float[] {0.2828591f, 0.7131856f, 0.49576342f, 0.40690988f}, + new float[] {0.26883373f, 0.726647f, 0.4920059f, 0.39704242f}, + new float[] {0.25500503f, 0.73987424f, 0.48782825f, 0.3867584f}, + new float[] {0.24138579f, 0.7528547f, 0.48323712f, 0.3760647f}, + new float[] {0.22798851f, 0.76557565f, 0.4782396f, 0.36496866f}, + new float[] {0.21482554f, 0.7780249f, 0.47284314f, 0.35347793f}, + new float[] {0.20190886f, 0.79019046f, 0.46705556f, 0.3416006f}, + new float[] {0.18925038f, 0.8020605f, 0.4608851f, 0.3293451f}, + new float[] {0.17686158f, 0.8136235f, 0.45434028f, 0.3167202f}, + new float[] {0.16475382f, 0.8248682f, 0.44743007f, 0.30373502f}, + new float[] {0.15293804f, 0.8357836f, 0.44016364f, 0.2903991f}, + new float[] {0.14142501f, 0.84635913f, 0.4325506f, 0.2767222f}, + new float[] {0.13022512f, 0.85658425f, 0.42460087f, 0.26271448f}, + new float[] {0.11934847f, 0.86644906f, 0.41632465f, 0.24838635f}, + new float[] {0.10880476f, 0.8759437f, 0.40773243f, 0.23374856f}, + new float[] {0.09860358f, 0.8850589f, 0.39883506f, 0.21881217f}, + new float[] {0.08875397f, 0.89378536f, 0.38964373f, 0.20358856f}, + new float[] {0.0792647f, 0.9021145f, 0.38016963f, 0.18808924f}, + new float[] {0.07014418f, 0.91003793f, 0.37042463f, 0.1723262f}, + new float[] {0.061400414f, 0.91754776f, 0.36042035f, 0.1563114f}, + new float[] {0.05304113f, 0.92463624f, 0.35016915f, 0.14005733f}, + new float[] {0.0450736f, 0.9312961f, 0.3396833f, 0.12357649f}, + new float[] {0.037504703f, 0.9375206f, 0.32897532f, 0.106881686f}, + new float[] {0.03034103f, 0.9433032f, 0.3180581f, 0.08998602f}, + new float[] {0.023588628f, 0.94863784f, 0.30694458f, 0.07290262f}, + new float[] {0.01725325f, 0.95351887f, 0.29564792f, 0.055644885f}, + new float[] {0.011340171f, 0.95794106f, 0.28418133f, 0.038226277f}, + new float[] {0.005854279f, 0.9618995f, 0.27255845f, 0.020660654f}, + new float[] {8.0010295E-4f, 0.96538985f, 0.26079288f, 0.0029617846f}, + new float[] {-0.003818363f, 0.9684081f, 0.24889828f, -0.014856413f}, + new float[] {-0.007997453f, 0.9709507f, 0.23688862f, -0.0327797f}, + new float[] {-0.011734039f, 0.9730145f, 0.22477779f, -0.050794043f}, + new float[] {-0.015025228f, 0.97459674f, 0.21258f, -0.06888495f}, + new float[] {-0.017868847f, 0.97569525f, 0.20030917f, -0.08703829f}, + new float[] {-0.020262927f, 0.9763082f, 0.1879797f, -0.10523948f}, + new float[] {-0.022206068f, 0.9764342f, 0.17560576f, -0.12347408f}, + new float[] {-0.023697197f, 0.9760722f, 0.16320162f, -0.14172764f}, + new float[] {-0.024735779f, 0.9752219f, 0.15078172f, -0.15998542f}, + new float[] {-0.025321692f, 0.97388303f, 0.13836022f, -0.17823319f}, + new float[] {-0.025455266f, 0.97205615f, 0.12595156f, -0.19645613f}, + new float[] {-0.025137246f, 0.96974206f, 0.113570005f, -0.21463984f}, + new float[] {-0.024368823f, 0.966942f, 0.10122979f, -0.23276988f}, + new float[] {-0.023151666f, 0.96365774f, 0.088945225f, -0.25083166f}, + new float[] {-0.021487802f, 0.9598913f, 0.07673041f, -0.26881093f}, + new float[] {-0.019379854f, 0.9556455f, 0.06459953f, -0.28669322f}, + new float[] {-0.016830623f, 0.95092314f, 0.0525665f, -0.3044645f}, + new float[] {-0.013843596f, 0.9457279f, 0.04064539f, -0.32211035f}, + new float[] {-0.010422587f, 0.9400635f, 0.02884984f, -0.33961698f}, + new float[] {-0.0065717697f, 0.9339343f, 0.017193556f, -0.35697052f}, + new float[] {-0.002295822f, 0.92734504f, 0.0056901723f, -0.374157f}, + new float[] {0.0024001896f, 0.92030096f, -0.0056470186f, -0.3911631f}, + new float[] {0.0075107515f, 0.91280746f, -0.016804636f, -0.40797502f}, + new float[] {0.013030022f, 0.90487075f, -0.027769819f, -0.4245798f}, + new float[] {0.018951744f, 0.896497f, -0.038529605f, -0.44096428f}, + new float[] {0.025269121f, 0.88769305f, -0.049071252f, -0.4571154f}, + new float[] {0.03197518f, 0.8784661f, -0.059382424f, -0.47302073f}, + new float[] {0.03906241f, 0.86882365f, -0.06945078f, -0.48866767f}, + new float[] {0.046523094f, 0.85877365f, -0.07926449f, -0.5040442f}, + new float[] {0.054348946f, 0.84832436f, -0.08881168f, -0.51913816f}, + new float[] {0.06253144f, 0.8374845f, -0.09808087f, -0.533938f}, + new float[] {0.07106161f, 0.82626295f, -0.107060805f, -0.5484321f}, + new float[] {0.079930276f, 0.81466925f, -0.11574057f, -0.56260943f}, + new float[] {0.0891279f, 0.8027128f, -0.124109596f, -0.57645917f}, + new float[] {0.098644555f, 0.7904038f, -0.13215744f, -0.58997077f}, + new float[] {0.10847002f, 0.7777525f, -0.13987413f, -0.6031339f}, + new float[] {0.11859363f, 0.7647697f, -0.14724979f, -0.61593866f}, + new float[] {0.12900484f, 0.75146604f, -0.15427522f, -0.6283754f}, + new float[] {0.13969228f, 0.73785305f, -0.16094121f, -0.640435f}, + new float[] {0.15064475f, 0.7239419f, -0.16723916f, -0.65210843f}, + new float[] {0.16185057f, 0.7097445f, -0.17316064f, -0.6633872f}, + new float[] {0.1732978f, 0.6952729f, -0.1786977f, -0.674263f}, + new float[] {0.18497421f, 0.6805394f, -0.18384269f, -0.684728f}, + new float[] {0.19686763f, 0.6655563f, -0.18858838f, -0.69477504f}, + new float[] {0.20896536f, 0.65033644f, -0.1929279f, -0.7043968f}, + new float[] {0.22125456f, 0.6348928f, -0.19685477f, -0.71358657f}, + new float[] {0.23372234f, 0.61923826f, -0.20036295f, -0.7223382f}, + new float[] {0.24635552f, 0.6033862f, -0.20344675f, -0.7306459f}, + new float[] {0.2591407f, 0.58735025f, -0.20610088f, -0.73850405f}, + new float[] {0.27206418f, 0.5711441f, -0.2083205f, -0.7459076f}, + new float[] {0.28511274f, 0.554781f, -0.21010125f, -0.752852f}, + new float[] {0.2982724f, 0.5382753f, -0.21143904f, -0.75933313f}, + new float[] {0.31152916f, 0.521641f, -0.21233031f, -0.765347f}, + new float[] {0.3248692f, 0.50489205f, -0.21277195f, -0.7708905f}, + new float[] {0.33827832f, 0.48804274f, -0.2127612f, -0.77596056f}, + new float[] {0.3517424f, 0.47110736f, -0.21229571f, -0.7805547f}, + new float[] {0.365247f, 0.4541005f, -0.21137378f, -0.78467095f}, + new float[] {0.37877813f, 0.43703625f, -0.20999387f, -0.78830767f}, + new float[] {0.39232132f, 0.41992924f, -0.20815507f, -0.79146373f}, + new float[] {0.40586197f, 0.40279418f, -0.20585686f, -0.79413843f}, + new float[] {0.4193862f, 0.3856451f, -0.20309913f, -0.79633147f}, + new float[] {0.43287942f, 0.36849675f, -0.19988227f, -0.798043f}, + new float[] {0.4463272f, 0.3513636f, -0.19620705f, -0.79927367f}, + new float[] {0.45971522f, 0.33426026f, -0.19207478f, -0.80002457f}, + new float[] {0.47302932f, 0.3172009f, -0.18748704f, -0.80029714f}, + new float[] {0.48625526f, 0.30019996f, -0.18244597f, -0.8000933f}, + new float[] {0.49937868f, 0.2832719f, -0.17695424f, -0.79941547f}, + new float[] {0.51238585f, 0.2664307f, -0.1710147f, -0.79826653f}, + new float[] {0.52526253f, 0.24969055f, -0.16463086f, -0.7966496f}, + new float[] {0.53799486f, 0.23306563f, -0.15780658f, -0.7945684f}, + new float[] {0.5505693f, 0.21656957f, -0.15054607f, -0.7920271f}, + new float[] {0.5629722f, 0.20021626f, -0.14285406f, -0.7890301f}, + new float[] {0.5751899f, 0.18401925f, -0.13473573f, -0.7855824f}, + new float[] {0.5872092f, 0.1679922f, -0.12619662f, -0.78168947f}, + new float[] {0.599017f, 0.15214804f, -0.117242515f, -0.77735686f}, + new float[] {0.61060053f, 0.13650006f, -0.10787988f, -0.7725909f}, + new float[] {0.62194663f, 0.121061325f, -0.09811556f, -0.7673981f}, + new float[] {0.6330432f, 0.10584408f, -0.08795637f, -0.7617854f}, + new float[] {0.64387786f, 0.09086105f, -0.07741001f, -0.7557601f}, + new float[] {0.6544384f, 0.0761244f, -0.06648436f, -0.7493299f}, + new float[] {0.6647129f, 0.061646253f, -0.055187732f, -0.74250305f}, + new float[] {0.67469f, 0.047438115f, -0.043528587f, -0.7352879f}, + new float[] {0.6843584f, 0.03351158f, -0.031515926f, -0.7276931f}, + new float[] {0.693707f, 0.01987794f, -0.019159257f, -0.71972805f}, + new float[] {0.70272505f, 0.006547779f, -0.0064679384f, -0.711402f} + }; + public static float[][] IMDCT_POST_TABLE_32 = { + new float[] {0.43864408f, 0.56105477f, 0.5085104f, 0.48396915f}, + new float[] {0.3186977f, 0.67859274f, 0.5032787f, 0.4297141f}, + new float[] {0.20833567f, 0.7841439f, 0.46999773f, 0.34758708f}, + new float[] {0.114034384f, 0.87124324f, 0.41206735f, 0.24110544f}, + new float[] {0.041238904f, 0.9344632f, 0.33435628f, 0.115255035f}, + new float[] {-0.0059630573f, 0.9697391f, 0.24290696f, -0.023805834f}, + new float[] {-0.02508533f, 0.9746135f, 0.14457026f, -0.16911149f}, + new float[] {-0.015391618f, 0.9483844f, 0.046591163f, -0.3133039f}, + new float[] {0.022061408f, 0.8921483f, -0.043828517f, -0.44906986f}, + new float[] {0.0844886f, 0.8087357f, -0.119964585f, -0.5695759f}, + new float[] {0.16754475f, 0.7025422f, -0.1759777f, -0.66887593f}, + new float[] {0.26558587f, 0.57926774f, -0.20726526f, -0.7422629f}, + new float[] {0.37201017f, 0.44557464f, -0.21074113f, -0.7865493f}, + new float[] {0.4796542f, 0.30869222f, -0.18502301f, -0.80025464f}, + new float[] {0.5812251f, 0.17598371f, -0.13051844f, -0.7836913f}, + new float[] {0.66973937f, 0.054507732f, -0.049402922f, -0.73894346f} + }; + } +} diff --git a/SharpJaad.AAC/Gain/IPQF.cs b/SharpJaad.AAC/Gain/IPQF.cs new file mode 100644 index 0000000..867ba1a --- /dev/null +++ b/SharpJaad.AAC/Gain/IPQF.cs @@ -0,0 +1,91 @@ +namespace SharpJaad.AAC.Gain +{ + public class IPQF + { + private float[] _buf; + private float[,] _tmp1, _tmp2; + + public IPQF() + { + _buf = new float[GCConstants.BANDS]; + _tmp1 = new float[GCConstants.BANDS / 2, GCConstants.NPQFTAPS / GCConstants.BANDS]; + _tmp2 = new float[GCConstants.BANDS / 2, GCConstants.NPQFTAPS / GCConstants.BANDS]; + } + + public void Process(float[][] input, int frameLen, int maxBand, float[] output) + { + int i, j; + for (i = 0; i < frameLen; i++) + { + output[i] = 0.0f; + } + + for (i = 0; i < frameLen / GCConstants.BANDS; i++) + { + for (j = 0; j < GCConstants.BANDS; j++) + { + _buf[j] = input[j][i]; + } + PerformSynthesis(_buf, output, i * GCConstants.BANDS); + } + } + + private void PerformSynthesis(float[] input, float[] output, int outOff) + { + int kk = GCConstants.NPQFTAPS / (2 * GCConstants.BANDS); + int i, n, k; + float acc; + + for (n = 0; n < GCConstants.BANDS / 2; ++n) + { + for (k = 0; k < 2 * kk - 1; ++k) + { + _tmp1[n, k] = _tmp1[n, k + 1]; + _tmp2[n, k] = _tmp2[n, k + 1]; + } + } + + for (n = 0; n < GCConstants.BANDS / 2; ++n) + { + acc = 0.0f; + for (i = 0; i < GCConstants.BANDS; ++i) + { + acc += PQFTables.COEFS_Q0[n][i] * input[i]; + } + _tmp1[n, 2 * kk - 1] = acc; + + acc = 0.0f; + for (i = 0; i < GCConstants.BANDS; ++i) + { + acc += PQFTables.COEFS_Q1[n][i] * input[i]; + } + _tmp2[n, 2 * kk - 1] = acc; + } + + for (n = 0; n < GCConstants.BANDS / 2; ++n) + { + acc = 0.0f; + for (k = 0; k < kk; ++k) + { + acc += PQFTables.COEFS_T0[n][k] * _tmp1[n, 2 * kk - 1 - 2 * k]; + } + for (k = 0; k < kk; ++k) + { + acc += PQFTables.COEFS_T1[n][k] * _tmp2[n, 2 * kk - 2 - 2 * k]; + } + output[outOff + n] = acc; + + acc = 0.0f; + for (k = 0; k < kk; ++k) + { + acc += PQFTables.COEFS_T0[GCConstants.BANDS - 1 - n][k] * _tmp1[n, 2 * kk - 1 - 2 * k]; + } + for (k = 0; k < kk; ++k) + { + acc -= PQFTables.COEFS_T1[GCConstants.BANDS - 1 - n][k] * _tmp2[n, 2 * kk - 2 - 2 * k]; + } + output[outOff + GCConstants.BANDS - 1 - n] = acc; + } + } + } +} diff --git a/SharpJaad.AAC/Gain/PQFTables.cs b/SharpJaad.AAC/Gain/PQFTables.cs new file mode 100644 index 0000000..b9b1fe5 --- /dev/null +++ b/SharpJaad.AAC/Gain/PQFTables.cs @@ -0,0 +1,128 @@ +namespace SharpJaad.AAC.Gain +{ + public static class PQFTables + { + public static float[] PROTO_TABLE = { + 1.2206911E-5f, + 1.7261988E-5f, + 1.2300094E-5f, + -1.0833943E-5f, + -5.77725E-5f, + -1.2764768E-4f, + -2.0965187E-4f, + -2.8166673E-4f, + -3.123486E-4f, + -2.673852E-4f, + -1.19494245E-4f, + 1.396514E-4f, + 4.886414E-4f, + 8.7044627E-4f, + 0.001194943f, + 0.0013519708f, + 0.0012346314f, + 7.695321E-4f, + -5.2242434E-5f, + -0.0011516092f, + -0.002353847f, + -0.0034033123f, + -0.004002855f, + -0.0038745415f, + -0.0028321072f, + -8.503889E-4f, + 0.0018856751f, + 0.004968874f, + 0.0078056706f, + 0.0097027905f, + 0.009996043f, + 0.008201936f, + 0.0041642073f, + -0.0018364454f, + -0.0090384865f, + -0.016241528f, + -0.021939551f, + -0.02453318f, + -0.022591664f, + -0.015122066f, + -0.0017971713f, + 0.016903413f, + 0.039672315f, + 0.064487524f, + 0.08885003f, + 0.11011329f, + 0.12585402f, + 0.13422394f, + 0.13422394f, + 0.12585402f, + 0.11011329f, + 0.08885003f, + 0.064487524f, + 0.039672315f, + 0.016903413f, + -0.0017971713f, + -0.015122066f, + -0.022591664f, + -0.02453318f, + -0.021939551f, + -0.016241528f, + -0.0090384865f, + -0.0018364454f, + 0.0041642073f, + 0.008201936f, + 0.009996043f, + 0.0097027905f, + 0.0078056706f, + 0.004968874f, + 0.0018856751f, + -8.503889E-4f, + -0.0028321072f, + -0.0038745415f, + -0.004002855f, + -0.0034033123f, + -0.002353847f, + -0.0011516092f, + -5.2242434E-5f, + 7.695321E-4f, + 0.0012346314f, + 0.0013519708f, + 0.001194943f, + 8.7044627E-4f, + 4.886414E-4f, + 1.396514E-4f, + -1.19494245E-4f, + -2.673852E-4f, + -3.123486E-4f, + -2.8166673E-4f, + -2.0965187E-4f, + -1.2764768E-4f, + -5.77725E-5f, + -1.0833943E-5f, + 1.2300094E-5f, + 1.7261988E-5f, + 1.2206911E-5f + }; + public static float[][] COEFS_Q0 = { + new float[] {1.6629392f, -0.39018065f, -1.9615706f, -1.11114f}, + new float[] {1.9615705f, 1.6629392f, 1.1111404f, 0.39018047f}, + new float[] {1.9615705f, 1.6629392f, 1.1111404f, 0.39018047f}, + new float[] {1.6629392f, -0.39018065f, -1.9615706f, -1.11114f} + }; + public static float[][] COEFS_Q1 = { + new float[] {1.1111404f, -1.9615706f, 0.39018083f, 1.6629387f}, + new float[] {0.39018047f, -1.11114f, 1.6629387f, -1.9615704f}, + new float[] {-0.39018065f, 1.1111408f, -1.6629395f, 1.9615709f}, + new float[] {-1.1111407f, 1.9615705f, -0.39018044f, -1.6629392f} + }; + public static float[][] COEFS_T0 = { + new float[] {4.8827646E-5f, 0.0012493944f, 0.0049385256f, 0.011328429f, 0.016656829f, 0.0071886852f, 0.53689575f, 0.060488265f, 0.032807745f, 0.015498166f, 0.0054078833f, 0.0011266669f}, + new float[] {6.904795E-5f, 0.0010695409f, 0.0030781284f, 0.0034015556f, -0.0073457817f, -0.067613654f, 0.50341606f, 0.090366654f, 0.03998417f, 0.01601142f, 0.004779772f, 8.386075E-4f}, + new float[] {4.9200375E-5f, 4.7797698E-4f, -2.0896974E-4f, -0.0075427005f, -0.036153946f, -0.15868926f, 0.44045317f, 0.09813272f, 0.038811162f, 0.013613249f, 0.003481785f, 5.1059073E-4f}, + new float[] {-4.333577E-5f, -5.586056E-4f, -0.004606437f, -0.019875497f, -0.06496611f, -0.2579501f, 0.35540012f, 0.087758206f, 0.031222682f, 0.009415388f, 0.0019545655f, 2.3109E-4f} + }; + public static float[][] COEFS_T1 = { + new float[] {-2.3109E-4f, -0.0019545655f, -0.009415388f, -0.031222682f, -0.087758206f, -0.35540012f, 0.2579501f, 0.06496611f, 0.019875497f, 0.004606437f, 5.586056E-4f, 4.333577E-5f}, + new float[] {-5.1059073E-4f, -0.003481785f, -0.013613249f, -0.038811162f, -0.09813272f, -0.44045317f, 0.15868926f, 0.036153946f, 0.0075427005f, 2.0896974E-4f, -4.7797698E-4f, -4.9200375E-5f}, + new float[] {-8.386075E-4f, -0.004779772f, -0.01601142f, -0.03998417f, -0.090366654f, -0.50341606f, 0.067613654f, 0.0073457817f, -0.0034015556f, -0.0030781284f, -0.0010695409f, -6.904795E-5f}, + new float[] {-0.0011266669f, -0.0054078833f, -0.015498166f, -0.032807745f, -0.060488265f, -0.53689575f, -0.0071886852f, -0.016656829f, -0.011328429f, -0.0049385256f, -0.0012493944f, -4.8827646E-5f} + }; + } +} diff --git a/SharpJaad.AAC/Gain/Windows.cs b/SharpJaad.AAC/Gain/Windows.cs new file mode 100644 index 0000000..0a0073b --- /dev/null +++ b/SharpJaad.AAC/Gain/Windows.cs @@ -0,0 +1,590 @@ +namespace SharpJaad.AAC.Gain +{ + public static class Windows + { + public static float[] SINE_256 = { + 0.003067956762965976f, + 0.00920375478205982f, + 0.0153392062849881f, + 0.021474080275469508f, + 0.02760814577896574f, + 0.03374117185137758f, + 0.03987292758773981f, + 0.04600318213091462f, + 0.052131704680283324f, + 0.05825826450043575f, + 0.06438263092985747f, + 0.07050457338961386f, + 0.07662386139203149f, + 0.08274026454937569f, + 0.0888535525825246f, + 0.09496349532963899f, + 0.10106986275482782f, + 0.10717242495680884f, + 0.11327095217756435f, + 0.11936521481099135f, + 0.12545498341154623f, + 0.13154002870288312f, + 0.13762012158648604f, + 0.14369503315029447f, + 0.1497645346773215f, + 0.15582839765426523f, + 0.16188639378011183f, + 0.16793829497473117f, + 0.17398387338746382f, + 0.18002290140569951f, + 0.18605515166344663f, + 0.19208039704989244f, + 0.19809841071795356f, + 0.20410896609281687f, + 0.2101118368804696f, + 0.21610679707621952f, + 0.2220936209732035f, + 0.22807208317088573f, + 0.23404195858354343f, + 0.2400030224487415f, + 0.2459550503357946f, + 0.25189781815421697f, + 0.257831102162159f, + 0.26375467897483135f, + 0.2696683255729151f, + 0.27557181931095814f, + 0.28146493792575794f, + 0.2873474595447295f, + 0.29321916269425863f, + 0.2990798263080405f, + 0.3049292297354024f, + 0.3107671527496115f, + 0.31659337555616585f, + 0.32240767880106985f, + 0.3282098435790925f, + 0.3339996514420094f, + 0.33977688440682685f, + 0.3455413249639891f, + 0.3512927560855671f, + 0.35703096123343f, + 0.3627557243673972f, + 0.3684668299533723f, + 0.37416406297145793f, + 0.37984720892405116f, + 0.38551605384391885f, + 0.39117038430225387f, + 0.3968099874167103f, + 0.40243465085941843f, + 0.4080441628649787f, + 0.4136383122384345f, + 0.4192168883632239f, + 0.4247796812091088f, + 0.4303264813400826f, + 0.4358570799222555f, + 0.44137126873171667f, + 0.44686884016237416f, + 0.4523495872337709f, + 0.4578133035988772f, + 0.46325978355186015f, + 0.4686888220358279f, + 0.47410021465054997f, + 0.479493757660153f, + 0.48486924800079106f, + 0.49022648328829116f, + 0.49556526182577254f, + 0.5008853826112407f, + 0.5061866453451552f, + 0.5114688504379703f, + 0.5167317990176499f, + 0.5219752929371544f, + 0.5271991347819013f, + 0.5324031278771979f, + 0.5375870762956454f, + 0.5427507848645159f, + 0.5478940591731002f, + 0.5530167055800275f, + 0.5581185312205561f, + 0.5631993440138341f, + 0.5682589526701315f, + 0.5732971666980422f, + 0.5783137964116556f, + 0.5833086529376983f, + 0.5882815482226452f, + 0.5932322950397998f, + 0.5981607069963423f, + 0.6030665985403482f, + 0.6079497849677736f, + 0.6128100824294097f, + 0.6176473079378039f, + 0.62246127937415f, + 0.6272518154951441f, + 0.6320187359398091f, + 0.6367618612362842f, + 0.6414810128085832f, + 0.6461760129833163f, + 0.6508466849963809f, + 0.6554928529996153f, + 0.6601143420674205f, + 0.6647109782033448f, + 0.669282588346636f, + 0.673829000378756f, + 0.6783500431298615f, + 0.6828455463852481f, + 0.687315340891759f, + 0.6917592583641577f, + 0.696177131491463f, + 0.7005687939432483f, + 0.7049340803759049f, + 0.7092728264388657f, + 0.7135848687807935f, + 0.7178700450557317f, + 0.7221281939292153f, + 0.726359155084346f, + 0.7305627692278276f, + 0.7347388780959634f, + 0.7388873244606151f, + 0.7430079521351217f, + 0.7471006059801801f, + 0.7511651319096864f, + 0.7552013768965365f, + 0.759209188978388f, + 0.7631884172633813f, + 0.7671389119358204f, + 0.7710605242618138f, + 0.7749531065948738f, + 0.7788165123814759f, + 0.7826505961665757f, + 0.7864552135990858f, + 0.79023022143731f, + 0.7939754775543372f, + 0.797690840943391f, + 0.8013761717231401f, + 0.8050313311429635f, + 0.808656181588175f, + 0.8122505865852039f, + 0.8158144108067338f, + 0.8193475200767969f, + 0.8228497813758263f, + 0.8263210628456635f, + 0.829761233794523f, + 0.8331701647019132f, + 0.8365477272235119f, + 0.8398937941959994f, + 0.8432082396418454f, + 0.8464909387740521f, + 0.8497417680008524f, + 0.8529606049303636f, + 0.8561473283751945f, + 0.8593018183570084f, + 0.8624239561110405f, + 0.865513624090569f, + 0.8685707059713409f, + 0.8715950866559511f, + 0.8745866522781761f, + 0.8775452902072612f, + 0.8804708890521608f, + 0.8833633386657316f, + 0.8862225301488806f, + 0.8890483558546646f, + 0.8918407093923427f, + 0.8945994856313826f, + 0.8973245807054183f, + 0.9000158920161603f, + 0.9026733182372588f, + 0.9052967593181188f, + 0.9078861164876663f, + 0.9104412922580671f, + 0.9129621904283981f, + 0.9154487160882678f, + 0.9179007756213904f, + 0.9203182767091105f, + 0.9227011283338785f, + 0.9250492407826776f, + 0.9273625256504011f, + 0.9296408958431812f, + 0.9318842655816681f, + 0.9340925504042589f, + 0.9362656671702783f, + 0.9384035340631081f, + 0.9405060705932683f, + 0.9425731976014469f, + 0.9446048372614803f, + 0.9466009130832835f, + 0.9485613499157303f, + 0.9504860739494817f, + 0.9523750127197659f, + 0.9542280951091057f, + 0.9560452513499964f, + 0.9578264130275329f, + 0.9595715130819845f, + 0.9612804858113206f, + 0.9629532668736839f, + 0.9645897932898126f, + 0.9661900034454126f, + 0.9677538370934755f, + 0.9692812353565485f, + 0.9707721407289504f, + 0.9722264970789363f, + 0.9736442496508119f, + 0.9750253450669941f, + 0.9763697313300211f, + 0.9776773578245099f, + 0.9789481753190622f, + 0.9801821359681173f, + 0.9813791933137546f, + 0.9825393022874412f, + 0.9836624192117303f, + 0.9847485018019042f, + 0.9857975091675674f, + 0.9868094018141854f, + 0.9877841416445722f, + 0.9887216919603238f, + 0.9896220174632008f, + 0.990485084256457f, + 0.9913108598461154f, + 0.9920993131421918f, + 0.9928504144598651f, + 0.9935641355205953f, + 0.9942404494531879f, + 0.9948793307948056f, + 0.9954807554919269f, + 0.996044700901252f, + 0.9965711457905548f, + 0.997060070339483f, + 0.9975114561403035f, + 0.997925286198596f, + 0.9983015449338929f, + 0.9986402181802653f, + 0.9989412931868569f, + 0.9992047586183639f, + 0.9994306045554617f, + 0.9996188224951786f, + 0.9997694053512153f, + 0.9998823474542126f, + 0.9999576445519639f, + 0.9999952938095762f + }; + public static float[] SINE_32 = { + 0.024541228522912288f, + 0.07356456359966743f, + 0.1224106751992162f, + 0.17096188876030122f, + 0.2191012401568698f, + 0.26671275747489837f, + 0.3136817403988915f, + 0.3598950365349881f, + 0.40524131400498986f, + 0.44961132965460654f, + 0.49289819222978404f, + 0.5349976198870972f, + 0.5758081914178453f, + 0.6152315905806268f, + 0.6531728429537768f, + 0.6895405447370668f, + 0.7242470829514669f, + 0.7572088465064846f, + 0.7883464276266062f, + 0.8175848131515837f, + 0.844853565249707f, + 0.8700869911087113f, + 0.8932243011955153f, + 0.9142097557035307f, + 0.9329927988347388f, + 0.9495281805930367f, + 0.9637760657954398f, + 0.9757021300385286f, + 0.9852776423889412f, + 0.99247953459871f, + 0.9972904566786902f, + 0.9996988186962042f + }; + public static float[] KBD_256 = { + 0.0005851230124487f, + 0.0009642149851497f, + 0.0013558207534965f, + 0.0017771849644394f, + 0.0022352533849672f, + 0.0027342299070304f, + 0.0032773001022195f, + 0.0038671998069216f, + 0.0045064443384152f, + 0.0051974336885144f, + 0.0059425050016407f, + 0.0067439602523141f, + 0.0076040812644888f, + 0.0085251378135895f, + 0.0095093917383048f, + 0.0105590986429280f, + 0.0116765080854300f, + 0.0128638627792770f, + 0.0141233971318631f, + 0.0154573353235409f, + 0.0168678890600951f, + 0.0183572550877256f, + 0.0199276125319803f, + 0.0215811201042484f, + 0.0233199132076965f, + 0.0251461009666641f, + 0.0270617631981826f, + 0.0290689473405856f, + 0.0311696653515848f, + 0.0333658905863535f, + 0.0356595546648444f, + 0.0380525443366107f, + 0.0405466983507029f, + 0.0431438043376910f, + 0.0458455957104702f, + 0.0486537485902075f, + 0.0515698787635492f, + 0.0545955386770205f, + 0.0577322144743916f, + 0.0609813230826460f, + 0.0643442093520723f, + 0.0678221432558827f, + 0.0714163171546603f, + 0.0751278431308314f, + 0.0789577503982528f, + 0.0829069827918993f, + 0.0869763963425241f, + 0.0911667569410503f, + 0.0954787380973307f, + 0.0999129187977865f, + 0.1044697814663005f, + 0.1091497100326053f, + 0.1139529881122542f, + 0.1188797973021148f, + 0.1239302155951605f, + 0.1291042159181728f, + 0.1344016647957880f, + 0.1398223211441467f, + 0.1453658351972151f, + 0.1510317475686540f, + 0.1568194884519144f, + 0.1627283769610327f, + 0.1687576206143887f, + 0.1749063149634756f, + 0.1811734433685097f, + 0.1875578769224857f, + 0.1940583745250518f, + 0.2006735831073503f, + 0.2074020380087318f, + 0.2142421635060113f, + 0.2211922734956977f, + 0.2282505723293797f, + 0.2354151558022098f, + 0.2426840122941792f, + 0.2500550240636293f, + 0.2575259686921987f, + 0.2650945206801527f, + 0.2727582531907993f, + 0.2805146399424422f, + 0.2883610572460804f, + 0.2962947861868143f, + 0.3043130149466800f, + 0.3124128412663888f, + 0.3205912750432127f, + 0.3288452410620226f, + 0.3371715818562547f, + 0.3455670606953511f, + 0.3540283646950029f, + 0.3625521080463003f, + 0.3711348353596863f, + 0.3797730251194006f, + 0.3884630932439016f, + 0.3972013967475546f, + 0.4059842374986933f, + 0.4148078660689724f, + 0.4236684856687616f, + 0.4325622561631607f, + 0.4414852981630577f, + 0.4504336971855032f, + 0.4594035078775303f, + 0.4683907582974173f, + 0.4773914542472655f, + 0.4864015836506502f, + 0.4954171209689973f, + 0.5044340316502417f, + 0.5134482766032377f, + 0.5224558166913167f, + 0.5314526172383208f, + 0.5404346525403849f, + 0.5493979103766972f, + 0.5583383965124314f, + 0.5672521391870222f, + 0.5761351935809411f, + 0.5849836462541291f, + 0.5937936195492526f, + 0.6025612759529649f, + 0.6112828224083939f, + 0.6199545145721097f, + 0.6285726610088878f, + 0.6371336273176413f, + 0.6456338401819751f, + 0.6540697913388968f, + 0.6624380414593221f, + 0.6707352239341151f, + 0.6789580485595255f, + 0.6871033051160131f, + 0.6951678668345944f, + 0.7031486937449871f, + 0.7110428359000029f, + 0.7188474364707993f, + 0.7265597347077880f, + 0.7341770687621900f, + 0.7416968783634273f, + 0.7491167073477523f, + 0.7564342060337386f, + 0.7636471334404891f, + 0.7707533593446514f, + 0.7777508661725849f, + 0.7846377507242818f, + 0.7914122257259034f, + 0.7980726212080798f, + 0.8046173857073919f, + 0.8110450872887550f, + 0.8173544143867162f, + 0.8235441764639875f, + 0.8296133044858474f, + 0.8355608512093652f, + 0.8413859912867303f, + 0.8470880211822968f, + 0.8526663589032990f, + 0.8581205435445334f, + 0.8634502346476508f, + 0.8686552113760616f, + 0.8737353715068081f, + 0.8786907302411250f, + 0.8835214188357692f, + 0.8882276830575707f, + 0.8928098814640207f, + 0.8972684835130879f, + 0.9016040675058185f, + 0.9058173183656508f, + 0.9099090252587376f, + 0.9138800790599416f, + 0.9177314696695282f, + 0.9214642831859411f, + 0.9250796989403991f, + 0.9285789863994010f, + 0.9319635019415643f, + 0.9352346855155568f, + 0.9383940571861993f, + 0.9414432135761304f, + 0.9443838242107182f, + 0.9472176277741918f, + 0.9499464282852282f, + 0.9525720912004834f, + 0.9550965394547873f, + 0.9575217494469370f, + 0.9598497469802043f, + 0.9620826031668507f, + 0.9642224303060783f, + 0.9662713777449607f, + 0.9682316277319895f, + 0.9701053912729269f, + 0.9718949039986892f, + 0.9736024220549734f, + 0.9752302180233160f, + 0.9767805768831932f, + 0.9782557920246753f, + 0.9796581613210076f, + 0.9809899832703159f, + 0.9822535532154261f, + 0.9834511596505429f, + 0.9845850806232530f, + 0.9856575802399989f, + 0.9866709052828243f, + 0.9876272819448033f, + 0.9885289126911557f, + 0.9893779732525968f, + 0.9901766097569984f, + 0.9909269360049311f, + 0.9916310308941294f, + 0.9922909359973702f, + 0.9929086532976777f, + 0.9934861430841844f, + 0.9940253220113651f, + 0.9945280613237534f, + 0.9949961852476154f, + 0.9954314695504363f, + 0.9958356402684387f, + 0.9962103726017252f, + 0.9965572899760172f, + 0.9968779632693499f, + 0.9971739102014799f, + 0.9974465948831872f, + 0.9976974275220812f, + 0.9979277642809907f, + 0.9981389072844972f, + 0.9983321047686901f, + 0.9985085513687731f, + 0.9986693885387259f, + 0.9988157050968516f, + 0.9989485378906924f, + 0.9990688725744943f, + 0.9991776444921379f, + 0.9992757396582338f, + 0.9993639958299003f, + 0.9994432036616085f, + 0.9995141079353859f, + 0.9995774088586188f, + 0.9996337634216871f, + 0.9996837868076957f, + 0.9997280538466377f, + 0.9997671005064359f, + 0.9998014254134544f, + 0.9998314913952471f, + 0.9998577270385304f, + 0.9998805282555989f, + 0.9999002598526793f, + 0.9999172570940037f, + 0.9999318272557038f, + 0.9999442511639580f, + 0.9999547847121726f, + 0.9999636603523446f, + 0.9999710885561258f, + 0.9999772592414866f, + 0.9999823431612708f, + 0.9999864932503106f, + 0.9999898459281599f, + 0.9999925223548691f, + 0.9999946296375997f, + 0.9999962619864214f, + 0.9999975018180320f, + 0.9999984208055542f, + 0.9999990808746198f, + 0.9999995351446231f, + 0.9999998288155155f + }; + public static float[] KBD_32 = { + 0.0000875914060105f, + 0.0009321760265333f, + 0.0032114611466596f, + 0.0081009893216786f, + 0.0171240286619181f, + 0.0320720743527833f, + 0.0548307856028528f, + 0.0871361822564870f, + 0.1302923415174603f, + 0.1848955425508276f, + 0.2506163195331889f, + 0.3260874142923209f, + 0.4089316830907141f, + 0.4959414909423747f, + 0.5833939894958904f, + 0.6674601983218376f, + 0.7446454751465113f, + 0.8121892962974020f, + 0.8683559394406505f, + 0.9125649996381605f, + 0.9453396205809574f, + 0.9680864942677585f, + 0.9827581789763112f, + 0.9914756203467121f, + 0.9961964092194694f, + 0.9984956609571091f, + 0.9994855586984285f, + 0.9998533730714648f, + 0.9999671864476404f, + 0.9999948432453556f, + 0.9999995655238333f, + 0.9999999961638728f + }; + } +} diff --git a/SharpJaad.AAC/Huffman/Codebooks.cs b/SharpJaad.AAC/Huffman/Codebooks.cs new file mode 100644 index 0000000..fadb933 --- /dev/null +++ b/SharpJaad.AAC/Huffman/Codebooks.cs @@ -0,0 +1,1393 @@ +namespace SharpJaad.AAC.Huffman +{ + public static class Codebooks + { + public static int[][] HCB1 = { + new int[] {1, 0, 0, 0, 0, 0}, + new int[] {5, 16, 1, 0, 0, 0}, + new int[] {5, 17, -1, 0, 0, 0}, + new int[] {5, 18, 0, 0, 0, -1}, + new int[] {5, 19, 0, 1, 0, 0}, + new int[] {5, 20, 0, 0, 0, 1}, + new int[] {5, 21, 0, 0, -1, 0}, + new int[] {5, 22, 0, 0, 1, 0}, + new int[] {5, 23, 0, -1, 0, 0}, + new int[] {7, 96, 1, -1, 0, 0}, + new int[] {7, 97, -1, 1, 0, 0}, + new int[] {7, 98, 0, 0, -1, 1}, + new int[] {7, 99, 0, 1, -1, 0}, + new int[] {7, 100, 0, -1, 1, 0}, + new int[] {7, 101, 0, 0, 1, -1}, + new int[] {7, 102, 1, 1, 0, 0}, + new int[] {7, 103, 0, 0, -1, -1}, + new int[] {7, 104, -1, -1, 0, 0}, + new int[] {7, 105, 0, -1, -1, 0}, + new int[] {7, 106, 1, 0, -1, 0}, + new int[] {7, 107, 0, 1, 0, -1}, + new int[] {7, 108, -1, 0, 1, 0}, + new int[] {7, 109, 0, 0, 1, 1}, + new int[] {7, 110, 1, 0, 1, 0}, + new int[] {7, 111, 0, -1, 0, 1}, + new int[] {7, 112, 0, 1, 1, 0}, + new int[] {7, 113, 0, 1, 0, 1}, + new int[] {7, 114, -1, 0, -1, 0}, + new int[] {7, 115, 1, 0, 0, 1}, + new int[] {7, 116, -1, 0, 0, -1}, + new int[] {7, 117, 1, 0, 0, -1}, + new int[] {7, 118, -1, 0, 0, 1}, + new int[] {7, 119, 0, -1, 0, -1}, + new int[] {9, 480, 1, 1, -1, 0}, + new int[] {9, 481, -1, 1, -1, 0}, + new int[] {9, 482, 1, -1, 1, 0}, + new int[] {9, 483, 0, 1, 1, -1}, + new int[] {9, 484, 0, 1, -1, 1}, + new int[] {9, 485, 0, -1, 1, 1}, + new int[] {9, 486, 0, -1, 1, -1}, + new int[] {9, 487, 1, -1, -1, 0}, + new int[] {9, 488, 1, 0, -1, 1}, + new int[] {9, 489, 0, 1, -1, -1}, + new int[] {9, 490, -1, 1, 1, 0}, + new int[] {9, 491, -1, 0, 1, -1}, + new int[] {9, 492, -1, -1, 1, 0}, + new int[] {9, 493, 0, -1, -1, 1}, + new int[] {9, 494, 1, -1, 0, 1}, + new int[] {9, 495, 1, -1, 0, -1}, + new int[] {9, 496, -1, 1, 0, -1}, + new int[] {9, 497, -1, -1, -1, 0}, + new int[] {9, 498, 0, -1, -1, -1}, + new int[] {9, 499, 0, 1, 1, 1}, + new int[] {9, 500, 1, 0, 1, -1}, + new int[] {9, 501, 1, 1, 0, 1}, + new int[] {9, 502, -1, 1, 0, 1}, + new int[] {9, 503, 1, 1, 1, 0}, + new int[] {10, 1008, -1, -1, 0, 1}, + new int[] {10, 1009, -1, 0, -1, -1}, + new int[] {10, 1010, 1, 1, 0, -1}, + new int[] {10, 1011, 1, 0, -1, -1}, + new int[] {10, 1012, -1, 0, -1, 1}, + new int[] {10, 1013, -1, -1, 0, -1}, + new int[] {10, 1014, -1, 0, 1, 1}, + new int[] {10, 1015, 1, 0, 1, 1}, + new int[] {11, 2032, 1, -1, 1, -1}, + new int[] {11, 2033, -1, 1, -1, 1}, + new int[] {11, 2034, -1, 1, 1, -1}, + new int[] {11, 2035, 1, -1, -1, 1}, + new int[] {11, 2036, 1, 1, 1, 1}, + new int[] {11, 2037, -1, -1, 1, 1}, + new int[] {11, 2038, 1, 1, -1, -1}, + new int[] {11, 2039, -1, -1, 1, -1}, + new int[] {11, 2040, -1, -1, -1, -1}, + new int[] {11, 2041, 1, 1, -1, 1}, + new int[] {11, 2042, 1, -1, 1, 1}, + new int[] {11, 2043, -1, 1, 1, 1}, + new int[] {11, 2044, -1, 1, -1, -1}, + new int[] {11, 2045, -1, -1, -1, 1}, + new int[] {11, 2046, 1, -1, -1, -1}, + new int[] {11, 2047, 1, 1, 1, -1} + }; + public static int[][] HCB2 = { + new int[] {3, 0, 0, 0, 0, 0}, + new int[] {4, 2, 1, 0, 0, 0}, + new int[] {5, 6, -1, 0, 0, 0}, + new int[] {5, 7, 0, 0, 0, 1}, + new int[] {5, 8, 0, 0, -1, 0}, + new int[] {5, 9, 0, 0, 0, -1}, + new int[] {5, 10, 0, -1, 0, 0}, + new int[] {5, 11, 0, 0, 1, 0}, + new int[] {5, 12, 0, 1, 0, 0}, + new int[] {6, 26, 0, -1, 1, 0}, + new int[] {6, 27, -1, 1, 0, 0}, + new int[] {6, 28, 0, 1, -1, 0}, + new int[] {6, 29, 0, 0, 1, -1}, + new int[] {6, 30, 0, 1, 0, -1}, + new int[] {6, 31, 0, 0, -1, 1}, + new int[] {6, 32, -1, 0, 0, -1}, + new int[] {6, 33, 1, -1, 0, 0}, + new int[] {6, 34, 1, 0, -1, 0}, + new int[] {6, 35, -1, -1, 0, 0}, + new int[] {6, 36, 0, 0, -1, -1}, + new int[] {6, 37, 1, 0, 1, 0}, + new int[] {6, 38, 1, 0, 0, 1}, + new int[] {6, 39, 0, -1, 0, 1}, + new int[] {6, 40, -1, 0, 1, 0}, + new int[] {6, 41, 0, 1, 0, 1}, + new int[] {6, 42, 0, -1, -1, 0}, + new int[] {6, 43, -1, 0, 0, 1}, + new int[] {6, 44, 0, -1, 0, -1}, + new int[] {6, 45, -1, 0, -1, 0}, + new int[] {6, 46, 1, 1, 0, 0}, + new int[] {6, 47, 0, 1, 1, 0}, + new int[] {6, 48, 0, 0, 1, 1}, + new int[] {6, 49, 1, 0, 0, -1}, + new int[] {7, 100, 0, 1, -1, 1}, + new int[] {7, 101, 1, 0, -1, 1}, + new int[] {7, 102, -1, 1, -1, 0}, + new int[] {7, 103, 0, -1, 1, -1}, + new int[] {7, 104, 1, -1, 1, 0}, + new int[] {7, 105, 1, 1, 0, -1}, + new int[] {7, 106, 1, 0, 1, 1}, + new int[] {7, 107, -1, 1, 1, 0}, + new int[] {7, 108, 0, -1, -1, 1}, + new int[] {7, 109, 1, 1, 1, 0}, + new int[] {7, 110, -1, 0, 1, -1}, + new int[] {7, 111, -1, -1, -1, 0}, + new int[] {7, 112, -1, 0, -1, 1}, + new int[] {7, 113, 1, -1, -1, 0}, + new int[] {7, 114, 1, 1, -1, 0}, + new int[] {8, 230, 1, -1, 0, 1}, + new int[] {8, 231, -1, 1, 0, -1}, + new int[] {8, 232, -1, -1, 1, 0}, + new int[] {8, 233, -1, 0, 1, 1}, + new int[] {8, 234, -1, -1, 0, 1}, + new int[] {8, 235, -1, -1, 0, -1}, + new int[] {8, 236, 0, -1, -1, -1}, + new int[] {8, 237, 1, 0, 1, -1}, + new int[] {8, 238, 1, 0, -1, -1}, + new int[] {8, 239, 0, 1, -1, -1}, + new int[] {8, 240, 0, 1, 1, 1}, + new int[] {8, 241, -1, 1, 0, 1}, + new int[] {8, 242, -1, 0, -1, -1}, + new int[] {8, 243, 0, 1, 1, -1}, + new int[] {8, 244, 1, -1, 0, -1}, + new int[] {8, 245, 0, -1, 1, 1}, + new int[] {8, 246, 1, 1, 0, 1}, + new int[] {8, 247, 1, -1, 1, -1}, + new int[] {8, 248, -1, 1, -1, 1}, + new int[] {9, 498, 1, -1, -1, 1}, + new int[] {9, 499, -1, -1, -1, -1}, + new int[] {9, 500, -1, 1, 1, -1}, + new int[] {9, 501, -1, 1, 1, 1}, + new int[] {9, 502, 1, 1, 1, 1}, + new int[] {9, 503, -1, -1, 1, -1}, + new int[] {9, 504, 1, -1, 1, 1}, + new int[] {9, 505, -1, 1, -1, -1}, + new int[] {9, 506, -1, -1, 1, 1}, + new int[] {9, 507, 1, 1, -1, -1}, + new int[] {9, 508, 1, -1, -1, -1}, + new int[] {9, 509, -1, -1, -1, 1}, + new int[] {9, 510, 1, 1, -1, 1}, + new int[] {9, 511, 1, 1, 1, -1} + }; + public static int[][] HCB3 = { + new int[] {1, 0, 0, 0, 0, 0}, + new int[] {4, 8, 1, 0, 0, 0}, + new int[] {4, 9, 0, 0, 0, 1}, + new int[] {4, 10, 0, 1, 0, 0}, + new int[] {4, 11, 0, 0, 1, 0}, + new int[] {5, 24, 1, 1, 0, 0}, + new int[] {5, 25, 0, 0, 1, 1}, + new int[] {6, 52, 0, 1, 1, 0}, + new int[] {6, 53, 0, 1, 0, 1}, + new int[] {6, 54, 1, 0, 1, 0}, + new int[] {6, 55, 0, 1, 1, 1}, + new int[] {6, 56, 1, 0, 0, 1}, + new int[] {6, 57, 1, 1, 1, 0}, + new int[] {7, 116, 1, 1, 1, 1}, + new int[] {7, 117, 1, 0, 1, 1}, + new int[] {7, 118, 1, 1, 0, 1}, + new int[] {8, 238, 2, 0, 0, 0}, + new int[] {8, 239, 0, 0, 0, 2}, + new int[] {8, 240, 0, 0, 1, 2}, + new int[] {8, 241, 2, 1, 0, 0}, + new int[] {8, 242, 1, 2, 1, 0}, + new int[] {9, 486, 0, 0, 2, 1}, + new int[] {9, 487, 0, 1, 2, 1}, + new int[] {9, 488, 1, 2, 0, 0}, + new int[] {9, 489, 0, 1, 1, 2}, + new int[] {9, 490, 2, 1, 1, 0}, + new int[] {9, 491, 0, 0, 2, 0}, + new int[] {9, 492, 0, 2, 1, 0}, + new int[] {9, 493, 0, 1, 2, 0}, + new int[] {9, 494, 0, 2, 0, 0}, + new int[] {9, 495, 0, 1, 0, 2}, + new int[] {9, 496, 2, 0, 1, 0}, + new int[] {9, 497, 1, 2, 1, 1}, + new int[] {9, 498, 0, 2, 1, 1}, + new int[] {9, 499, 1, 1, 2, 0}, + new int[] {9, 500, 1, 1, 2, 1}, + new int[] {10, 1002, 1, 2, 0, 1}, + new int[] {10, 1003, 1, 0, 2, 0}, + new int[] {10, 1004, 1, 0, 2, 1}, + new int[] {10, 1005, 0, 2, 0, 1}, + new int[] {10, 1006, 2, 1, 1, 1}, + new int[] {10, 1007, 1, 1, 1, 2}, + new int[] {10, 1008, 2, 1, 0, 1}, + new int[] {10, 1009, 1, 0, 1, 2}, + new int[] {10, 1010, 0, 0, 2, 2}, + new int[] {10, 1011, 0, 1, 2, 2}, + new int[] {10, 1012, 2, 2, 1, 0}, + new int[] {10, 1013, 1, 2, 2, 0}, + new int[] {10, 1014, 1, 0, 0, 2}, + new int[] {10, 1015, 2, 0, 0, 1}, + new int[] {10, 1016, 0, 2, 2, 1}, + new int[] {11, 2034, 2, 2, 0, 0}, + new int[] {11, 2035, 1, 2, 2, 1}, + new int[] {11, 2036, 1, 1, 0, 2}, + new int[] {11, 2037, 2, 0, 1, 1}, + new int[] {11, 2038, 1, 1, 2, 2}, + new int[] {11, 2039, 2, 2, 1, 1}, + new int[] {11, 2040, 0, 2, 2, 0}, + new int[] {11, 2041, 0, 2, 1, 2}, + new int[] {12, 4084, 1, 0, 2, 2}, + new int[] {12, 4085, 2, 2, 0, 1}, + new int[] {12, 4086, 2, 1, 2, 0}, + new int[] {12, 4087, 2, 2, 2, 0}, + new int[] {12, 4088, 0, 2, 2, 2}, + new int[] {12, 4089, 2, 2, 2, 1}, + new int[] {12, 4090, 2, 1, 2, 1}, + new int[] {12, 4091, 1, 2, 1, 2}, + new int[] {12, 4092, 1, 2, 2, 2}, + new int[] {13, 8186, 0, 2, 0, 2}, + new int[] {13, 8187, 2, 0, 2, 0}, + new int[] {13, 8188, 1, 2, 0, 2}, + new int[] {14, 16378, 2, 0, 2, 1}, + new int[] {14, 16379, 2, 1, 1, 2}, + new int[] {14, 16380, 2, 1, 0, 2}, + new int[] {15, 32762, 2, 2, 2, 2}, + new int[] {15, 32763, 2, 2, 1, 2}, + new int[] {15, 32764, 2, 1, 2, 2}, + new int[] {15, 32765, 2, 0, 1, 2}, + new int[] {15, 32766, 2, 0, 0, 2}, + new int[] {16, 65534, 2, 2, 0, 2}, + new int[] {16, 65535, 2, 0, 2, 2} + }; + public static int[][] HCB4 = { + new int[] {4, 0, 1, 1, 1, 1}, + new int[] {4, 1, 0, 1, 1, 1}, + new int[] {4, 2, 1, 1, 0, 1}, + new int[] {4, 3, 1, 1, 1, 0}, + new int[] {4, 4, 1, 0, 1, 1}, + new int[] {4, 5, 1, 0, 0, 0}, + new int[] {4, 6, 1, 1, 0, 0}, + new int[] {4, 7, 0, 0, 0, 0}, + new int[] {4, 8, 0, 0, 1, 1}, + new int[] {4, 9, 1, 0, 1, 0}, + new int[] {5, 20, 1, 0, 0, 1}, + new int[] {5, 21, 0, 1, 1, 0}, + new int[] {5, 22, 0, 0, 0, 1}, + new int[] {5, 23, 0, 1, 0, 1}, + new int[] {5, 24, 0, 0, 1, 0}, + new int[] {5, 25, 0, 1, 0, 0}, + new int[] {7, 104, 2, 1, 1, 1}, + new int[] {7, 105, 1, 1, 2, 1}, + new int[] {7, 106, 1, 2, 1, 1}, + new int[] {7, 107, 1, 1, 1, 2}, + new int[] {7, 108, 2, 1, 1, 0}, + new int[] {7, 109, 2, 1, 0, 1}, + new int[] {7, 110, 1, 2, 1, 0}, + new int[] {7, 111, 2, 0, 1, 1}, + new int[] {7, 112, 0, 1, 2, 1}, + new int[] {8, 226, 0, 1, 1, 2}, + new int[] {8, 227, 1, 1, 2, 0}, + new int[] {8, 228, 0, 2, 1, 1}, + new int[] {8, 229, 1, 0, 1, 2}, + new int[] {8, 230, 1, 2, 0, 1}, + new int[] {8, 231, 1, 1, 0, 2}, + new int[] {8, 232, 1, 0, 2, 1}, + new int[] {8, 233, 2, 1, 0, 0}, + new int[] {8, 234, 2, 0, 1, 0}, + new int[] {8, 235, 1, 2, 0, 0}, + new int[] {8, 236, 2, 0, 0, 1}, + new int[] {8, 237, 0, 1, 0, 2}, + new int[] {8, 238, 0, 2, 1, 0}, + new int[] {8, 239, 0, 0, 1, 2}, + new int[] {8, 240, 0, 1, 2, 0}, + new int[] {8, 241, 0, 2, 0, 1}, + new int[] {8, 242, 1, 0, 0, 2}, + new int[] {8, 243, 0, 0, 2, 1}, + new int[] {8, 244, 1, 0, 2, 0}, + new int[] {8, 245, 2, 0, 0, 0}, + new int[] {8, 246, 0, 0, 0, 2}, + new int[] {9, 494, 0, 2, 0, 0}, + new int[] {9, 495, 0, 0, 2, 0}, + new int[] {9, 496, 1, 2, 2, 1}, + new int[] {9, 497, 2, 2, 1, 1}, + new int[] {9, 498, 2, 1, 2, 1}, + new int[] {9, 499, 1, 1, 2, 2}, + new int[] {9, 500, 1, 2, 1, 2}, + new int[] {9, 501, 2, 1, 1, 2}, + new int[] {10, 1004, 1, 2, 2, 0}, + new int[] {10, 1005, 2, 2, 1, 0}, + new int[] {10, 1006, 2, 1, 2, 0}, + new int[] {10, 1007, 0, 2, 2, 1}, + new int[] {10, 1008, 0, 1, 2, 2}, + new int[] {10, 1009, 2, 2, 0, 1}, + new int[] {10, 1010, 0, 2, 1, 2}, + new int[] {10, 1011, 2, 0, 2, 1}, + new int[] {10, 1012, 1, 0, 2, 2}, + new int[] {10, 1013, 2, 2, 2, 1}, + new int[] {10, 1014, 1, 2, 0, 2}, + new int[] {10, 1015, 2, 0, 1, 2}, + new int[] {10, 1016, 2, 1, 0, 2}, + new int[] {10, 1017, 1, 2, 2, 2}, + new int[] {11, 2036, 2, 1, 2, 2}, + new int[] {11, 2037, 2, 2, 1, 2}, + new int[] {11, 2038, 0, 2, 2, 0}, + new int[] {11, 2039, 2, 2, 0, 0}, + new int[] {11, 2040, 0, 0, 2, 2}, + new int[] {11, 2041, 2, 0, 2, 0}, + new int[] {11, 2042, 0, 2, 0, 2}, + new int[] {11, 2043, 2, 0, 0, 2}, + new int[] {11, 2044, 2, 2, 2, 2}, + new int[] {11, 2045, 0, 2, 2, 2}, + new int[] {11, 2046, 2, 2, 2, 0}, + new int[] {12, 4094, 2, 2, 0, 2}, + new int[] {12, 4095, 2, 0, 2, 2} + }; + public static int[][] HCB5 = { + new int[] {1, 0, 0, 0}, + new int[] {4, 8, -1, 0}, + new int[] {4, 9, 1, 0}, + new int[] {4, 10, 0, 1}, + new int[] {4, 11, 0, -1}, + new int[] {5, 24, 1, -1}, + new int[] {5, 25, -1, 1}, + new int[] {5, 26, -1, -1}, + new int[] {5, 27, 1, 1}, + new int[] {7, 112, -2, 0}, + new int[] {7, 113, 0, 2}, + new int[] {7, 114, 2, 0}, + new int[] {7, 115, 0, -2}, + new int[] {8, 232, -2, -1}, + new int[] {8, 233, 2, 1}, + new int[] {8, 234, -1, -2}, + new int[] {8, 235, 1, 2}, + new int[] {8, 236, -2, 1}, + new int[] {8, 237, 2, -1}, + new int[] {8, 238, -1, 2}, + new int[] {8, 239, 1, -2}, + new int[] {8, 240, -3, 0}, + new int[] {8, 241, 3, 0}, + new int[] {8, 242, 0, -3}, + new int[] {8, 243, 0, 3}, + new int[] {9, 488, -3, -1}, + new int[] {9, 489, 1, 3}, + new int[] {9, 490, 3, 1}, + new int[] {9, 491, -1, -3}, + new int[] {9, 492, -3, 1}, + new int[] {9, 493, 3, -1}, + new int[] {9, 494, 1, -3}, + new int[] {9, 495, -1, 3}, + new int[] {9, 496, -2, 2}, + new int[] {9, 497, 2, 2}, + new int[] {9, 498, -2, -2}, + new int[] {9, 499, 2, -2}, + new int[] {10, 1000, -3, -2}, + new int[] {10, 1001, 3, -2}, + new int[] {10, 1002, -2, 3}, + new int[] {10, 1003, 2, -3}, + new int[] {10, 1004, 3, 2}, + new int[] {10, 1005, 2, 3}, + new int[] {10, 1006, -3, 2}, + new int[] {10, 1007, -2, -3}, + new int[] {10, 1008, 0, -4}, + new int[] {10, 1009, -4, 0}, + new int[] {10, 1010, 4, 1}, + new int[] {10, 1011, 4, 0}, + new int[] {11, 2024, -4, -1}, + new int[] {11, 2025, 0, 4}, + new int[] {11, 2026, 4, -1}, + new int[] {11, 2027, -1, -4}, + new int[] {11, 2028, 1, 4}, + new int[] {11, 2029, -1, 4}, + new int[] {11, 2030, -4, 1}, + new int[] {11, 2031, 1, -4}, + new int[] {11, 2032, 3, -3}, + new int[] {11, 2033, -3, -3}, + new int[] {11, 2034, -3, 3}, + new int[] {11, 2035, -2, 4}, + new int[] {11, 2036, -4, -2}, + new int[] {11, 2037, 4, 2}, + new int[] {11, 2038, 2, -4}, + new int[] {11, 2039, 2, 4}, + new int[] {11, 2040, 3, 3}, + new int[] {11, 2041, -4, 2}, + new int[] {12, 4084, -2, -4}, + new int[] {12, 4085, 4, -2}, + new int[] {12, 4086, 3, -4}, + new int[] {12, 4087, -4, -3}, + new int[] {12, 4088, -4, 3}, + new int[] {12, 4089, 3, 4}, + new int[] {12, 4090, -3, 4}, + new int[] {12, 4091, 4, 3}, + new int[] {12, 4092, 4, -3}, + new int[] {12, 4093, -3, -4}, + new int[] {13, 8188, 4, -4}, + new int[] {13, 8189, -4, 4}, + new int[] {13, 8190, 4, 4}, + new int[] {13, 8191, -4, -4} + }; + public static int[][] HCB6 = { + new int[] {4, 0, 0, 0}, + new int[] {4, 1, 1, 0}, + new int[] {4, 2, 0, -1}, + new int[] {4, 3, 0, 1}, + new int[] {4, 4, -1, 0}, + new int[] {4, 5, 1, 1}, + new int[] {4, 6, -1, 1}, + new int[] {4, 7, 1, -1}, + new int[] {4, 8, -1, -1}, + new int[] {6, 36, 2, -1}, + new int[] {6, 37, 2, 1}, + new int[] {6, 38, -2, 1}, + new int[] {6, 39, -2, -1}, + new int[] {6, 40, -2, 0}, + new int[] {6, 41, -1, 2}, + new int[] {6, 42, 2, 0}, + new int[] {6, 43, 1, -2}, + new int[] {6, 44, 1, 2}, + new int[] {6, 45, 0, -2}, + new int[] {6, 46, -1, -2}, + new int[] {6, 47, 0, 2}, + new int[] {6, 48, 2, -2}, + new int[] {6, 49, -2, 2}, + new int[] {6, 50, -2, -2}, + new int[] {6, 51, 2, 2}, + new int[] {7, 104, -3, 1}, + new int[] {7, 105, 3, 1}, + new int[] {7, 106, 3, -1}, + new int[] {7, 107, -1, 3}, + new int[] {7, 108, -3, -1}, + new int[] {7, 109, 1, 3}, + new int[] {7, 110, 1, -3}, + new int[] {7, 111, -1, -3}, + new int[] {7, 112, 3, 0}, + new int[] {7, 113, -3, 0}, + new int[] {7, 114, 0, -3}, + new int[] {7, 115, 0, 3}, + new int[] {7, 116, 3, 2}, + new int[] {8, 234, -3, -2}, + new int[] {8, 235, -2, 3}, + new int[] {8, 236, 2, 3}, + new int[] {8, 237, 3, -2}, + new int[] {8, 238, 2, -3}, + new int[] {8, 239, -2, -3}, + new int[] {8, 240, -3, 2}, + new int[] {8, 241, 3, 3}, + new int[] {9, 484, 3, -3}, + new int[] {9, 485, -3, -3}, + new int[] {9, 486, -3, 3}, + new int[] {9, 487, 1, -4}, + new int[] {9, 488, -1, -4}, + new int[] {9, 489, 4, 1}, + new int[] {9, 490, -4, 1}, + new int[] {9, 491, -4, -1}, + new int[] {9, 492, 1, 4}, + new int[] {9, 493, 4, -1}, + new int[] {9, 494, -1, 4}, + new int[] {9, 495, 0, -4}, + new int[] {9, 496, -4, 2}, + new int[] {9, 497, -4, -2}, + new int[] {9, 498, 2, 4}, + new int[] {9, 499, -2, -4}, + new int[] {9, 500, -4, 0}, + new int[] {9, 501, 4, 2}, + new int[] {9, 502, 4, -2}, + new int[] {9, 503, -2, 4}, + new int[] {9, 504, 4, 0}, + new int[] {9, 505, 2, -4}, + new int[] {9, 506, 0, 4}, + new int[] {10, 1014, -3, -4}, + new int[] {10, 1015, -3, 4}, + new int[] {10, 1016, 3, -4}, + new int[] {10, 1017, 4, -3}, + new int[] {10, 1018, 3, 4}, + new int[] {10, 1019, 4, 3}, + new int[] {10, 1020, -4, 3}, + new int[] {10, 1021, -4, -3}, + new int[] {11, 2044, 4, 4}, + new int[] {11, 2045, -4, 4}, + new int[] {11, 2046, -4, -4}, + new int[] {11, 2047, 4, -4} + }; + public static int[][] HCB7 = { + new int[] {1, 0, 0, 0}, + new int[] {3, 4, 1, 0}, + new int[] {3, 5, 0, 1}, + new int[] {4, 12, 1, 1}, + new int[] {6, 52, 2, 1}, + new int[] {6, 53, 1, 2}, + new int[] {6, 54, 2, 0}, + new int[] {6, 55, 0, 2}, + new int[] {7, 112, 3, 1}, + new int[] {7, 113, 1, 3}, + new int[] {7, 114, 2, 2}, + new int[] {7, 115, 3, 0}, + new int[] {7, 116, 0, 3}, + new int[] {8, 234, 2, 3}, + new int[] {8, 235, 3, 2}, + new int[] {8, 236, 1, 4}, + new int[] {8, 237, 4, 1}, + new int[] {8, 238, 1, 5}, + new int[] {8, 239, 5, 1}, + new int[] {8, 240, 3, 3}, + new int[] {8, 241, 2, 4}, + new int[] {8, 242, 0, 4}, + new int[] {8, 243, 4, 0}, + new int[] {9, 488, 4, 2}, + new int[] {9, 489, 2, 5}, + new int[] {9, 490, 5, 2}, + new int[] {9, 491, 0, 5}, + new int[] {9, 492, 6, 1}, + new int[] {9, 493, 5, 0}, + new int[] {9, 494, 1, 6}, + new int[] {9, 495, 4, 3}, + new int[] {9, 496, 3, 5}, + new int[] {9, 497, 3, 4}, + new int[] {9, 498, 5, 3}, + new int[] {9, 499, 2, 6}, + new int[] {9, 500, 6, 2}, + new int[] {9, 501, 1, 7}, + new int[] {10, 1004, 3, 6}, + new int[] {10, 1005, 0, 6}, + new int[] {10, 1006, 6, 0}, + new int[] {10, 1007, 4, 4}, + new int[] {10, 1008, 7, 1}, + new int[] {10, 1009, 4, 5}, + new int[] {10, 1010, 7, 2}, + new int[] {10, 1011, 5, 4}, + new int[] {10, 1012, 6, 3}, + new int[] {10, 1013, 2, 7}, + new int[] {10, 1014, 7, 3}, + new int[] {10, 1015, 6, 4}, + new int[] {10, 1016, 5, 5}, + new int[] {10, 1017, 4, 6}, + new int[] {10, 1018, 3, 7}, + new int[] {11, 2038, 7, 0}, + new int[] {11, 2039, 0, 7}, + new int[] {11, 2040, 6, 5}, + new int[] {11, 2041, 5, 6}, + new int[] {11, 2042, 7, 4}, + new int[] {11, 2043, 4, 7}, + new int[] {11, 2044, 5, 7}, + new int[] {11, 2045, 7, 5}, + new int[] {12, 4092, 7, 6}, + new int[] {12, 4093, 6, 6}, + new int[] {12, 4094, 6, 7}, + new int[] {12, 4095, 7, 7} + }; + public static int[][] HCB8 = { + new int[] {3, 0, 1, 1}, + new int[] {4, 2, 2, 1}, + new int[] {4, 3, 1, 0}, + new int[] {4, 4, 1, 2}, + new int[] {4, 5, 0, 1}, + new int[] {4, 6, 2, 2}, + new int[] {5, 14, 0, 0}, + new int[] {5, 15, 2, 0}, + new int[] {5, 16, 0, 2}, + new int[] {5, 17, 3, 1}, + new int[] {5, 18, 1, 3}, + new int[] {5, 19, 3, 2}, + new int[] {5, 20, 2, 3}, + new int[] {6, 42, 3, 3}, + new int[] {6, 43, 4, 1}, + new int[] {6, 44, 1, 4}, + new int[] {6, 45, 4, 2}, + new int[] {6, 46, 2, 4}, + new int[] {6, 47, 3, 0}, + new int[] {6, 48, 0, 3}, + new int[] {6, 49, 4, 3}, + new int[] {6, 50, 3, 4}, + new int[] {6, 51, 5, 2}, + new int[] {7, 104, 5, 1}, + new int[] {7, 105, 2, 5}, + new int[] {7, 106, 1, 5}, + new int[] {7, 107, 5, 3}, + new int[] {7, 108, 3, 5}, + new int[] {7, 109, 4, 4}, + new int[] {7, 110, 5, 4}, + new int[] {7, 111, 0, 4}, + new int[] {7, 112, 4, 5}, + new int[] {7, 113, 4, 0}, + new int[] {7, 114, 2, 6}, + new int[] {7, 115, 6, 2}, + new int[] {7, 116, 6, 1}, + new int[] {7, 117, 1, 6}, + new int[] {8, 236, 3, 6}, + new int[] {8, 237, 6, 3}, + new int[] {8, 238, 5, 5}, + new int[] {8, 239, 5, 0}, + new int[] {8, 240, 6, 4}, + new int[] {8, 241, 0, 5}, + new int[] {8, 242, 4, 6}, + new int[] {8, 243, 7, 1}, + new int[] {8, 244, 7, 2}, + new int[] {8, 245, 2, 7}, + new int[] {8, 246, 6, 5}, + new int[] {8, 247, 7, 3}, + new int[] {8, 248, 1, 7}, + new int[] {8, 249, 5, 6}, + new int[] {8, 250, 3, 7}, + new int[] {9, 502, 6, 6}, + new int[] {9, 503, 7, 4}, + new int[] {9, 504, 6, 0}, + new int[] {9, 505, 4, 7}, + new int[] {9, 506, 0, 6}, + new int[] {9, 507, 7, 5}, + new int[] {9, 508, 7, 6}, + new int[] {9, 509, 6, 7}, + new int[] {10, 1020, 5, 7}, + new int[] {10, 1021, 7, 0}, + new int[] {10, 1022, 0, 7}, + new int[] {10, 1023, 7, 7} + }; + public static int[][] HCB9 = { + new int[] {1, 0, 0, 0}, + new int[] {3, 4, 1, 0}, + new int[] {3, 5, 0, 1}, + new int[] {4, 12, 1, 1}, + new int[] {6, 52, 2, 1}, + new int[] {6, 53, 1, 2}, + new int[] {6, 54, 2, 0}, + new int[] {6, 55, 0, 2}, + new int[] {7, 112, 3, 1}, + new int[] {7, 113, 2, 2}, + new int[] {7, 114, 1, 3}, + new int[] {8, 230, 3, 0}, + new int[] {8, 231, 0, 3}, + new int[] {8, 232, 2, 3}, + new int[] {8, 233, 3, 2}, + new int[] {8, 234, 1, 4}, + new int[] {8, 235, 4, 1}, + new int[] {8, 236, 2, 4}, + new int[] {8, 237, 1, 5}, + new int[] {9, 476, 4, 2}, + new int[] {9, 477, 3, 3}, + new int[] {9, 478, 0, 4}, + new int[] {9, 479, 4, 0}, + new int[] {9, 480, 5, 1}, + new int[] {9, 481, 2, 5}, + new int[] {9, 482, 1, 6}, + new int[] {9, 483, 3, 4}, + new int[] {9, 484, 5, 2}, + new int[] {9, 485, 6, 1}, + new int[] {9, 486, 4, 3}, + new int[] {10, 974, 0, 5}, + new int[] {10, 975, 2, 6}, + new int[] {10, 976, 5, 0}, + new int[] {10, 977, 1, 7}, + new int[] {10, 978, 3, 5}, + new int[] {10, 979, 1, 8}, + new int[] {10, 980, 8, 1}, + new int[] {10, 981, 4, 4}, + new int[] {10, 982, 5, 3}, + new int[] {10, 983, 6, 2}, + new int[] {10, 984, 7, 1}, + new int[] {10, 985, 0, 6}, + new int[] {10, 986, 8, 2}, + new int[] {10, 987, 2, 8}, + new int[] {10, 988, 3, 6}, + new int[] {10, 989, 2, 7}, + new int[] {10, 990, 4, 5}, + new int[] {10, 991, 9, 1}, + new int[] {10, 992, 1, 9}, + new int[] {10, 993, 7, 2}, + new int[] {11, 1988, 6, 0}, + new int[] {11, 1989, 5, 4}, + new int[] {11, 1990, 6, 3}, + new int[] {11, 1991, 8, 3}, + new int[] {11, 1992, 0, 7}, + new int[] {11, 1993, 9, 2}, + new int[] {11, 1994, 3, 8}, + new int[] {11, 1995, 4, 6}, + new int[] {11, 1996, 3, 7}, + new int[] {11, 1997, 0, 8}, + new int[] {11, 1998, 10, 1}, + new int[] {11, 1999, 6, 4}, + new int[] {11, 2000, 2, 9}, + new int[] {11, 2001, 5, 5}, + new int[] {11, 2002, 8, 0}, + new int[] {11, 2003, 7, 0}, + new int[] {11, 2004, 7, 3}, + new int[] {11, 2005, 10, 2}, + new int[] {11, 2006, 9, 3}, + new int[] {11, 2007, 8, 4}, + new int[] {11, 2008, 1, 10}, + new int[] {11, 2009, 7, 4}, + new int[] {11, 2010, 6, 5}, + new int[] {11, 2011, 5, 6}, + new int[] {11, 2012, 4, 8}, + new int[] {11, 2013, 4, 7}, + new int[] {11, 2014, 3, 9}, + new int[] {11, 2015, 11, 1}, + new int[] {11, 2016, 5, 8}, + new int[] {11, 2017, 9, 0}, + new int[] {11, 2018, 8, 5}, + new int[] {12, 4038, 10, 3}, + new int[] {12, 4039, 2, 10}, + new int[] {12, 4040, 0, 9}, + new int[] {12, 4041, 11, 2}, + new int[] {12, 4042, 9, 4}, + new int[] {12, 4043, 6, 6}, + new int[] {12, 4044, 12, 1}, + new int[] {12, 4045, 4, 9}, + new int[] {12, 4046, 8, 6}, + new int[] {12, 4047, 1, 11}, + new int[] {12, 4048, 9, 5}, + new int[] {12, 4049, 10, 4}, + new int[] {12, 4050, 5, 7}, + new int[] {12, 4051, 7, 5}, + new int[] {12, 4052, 2, 11}, + new int[] {12, 4053, 1, 12}, + new int[] {12, 4054, 12, 2}, + new int[] {12, 4055, 11, 3}, + new int[] {12, 4056, 3, 10}, + new int[] {12, 4057, 5, 9}, + new int[] {12, 4058, 6, 7}, + new int[] {12, 4059, 8, 7}, + new int[] {12, 4060, 11, 4}, + new int[] {12, 4061, 0, 10}, + new int[] {12, 4062, 7, 6}, + new int[] {12, 4063, 12, 3}, + new int[] {12, 4064, 10, 0}, + new int[] {12, 4065, 10, 5}, + new int[] {12, 4066, 4, 10}, + new int[] {12, 4067, 6, 8}, + new int[] {12, 4068, 2, 12}, + new int[] {12, 4069, 9, 6}, + new int[] {12, 4070, 9, 7}, + new int[] {12, 4071, 4, 11}, + new int[] {12, 4072, 11, 0}, + new int[] {12, 4073, 6, 9}, + new int[] {12, 4074, 3, 11}, + new int[] {12, 4075, 5, 10}, + new int[] {13, 8152, 8, 8}, + new int[] {13, 8153, 7, 8}, + new int[] {13, 8154, 12, 5}, + new int[] {13, 8155, 3, 12}, + new int[] {13, 8156, 11, 5}, + new int[] {13, 8157, 7, 7}, + new int[] {13, 8158, 12, 4}, + new int[] {13, 8159, 11, 6}, + new int[] {13, 8160, 10, 6}, + new int[] {13, 8161, 4, 12}, + new int[] {13, 8162, 7, 9}, + new int[] {13, 8163, 5, 11}, + new int[] {13, 8164, 0, 11}, + new int[] {13, 8165, 12, 6}, + new int[] {13, 8166, 6, 10}, + new int[] {13, 8167, 12, 0}, + new int[] {13, 8168, 10, 7}, + new int[] {13, 8169, 5, 12}, + new int[] {13, 8170, 7, 10}, + new int[] {13, 8171, 9, 8}, + new int[] {13, 8172, 0, 12}, + new int[] {13, 8173, 11, 7}, + new int[] {13, 8174, 8, 9}, + new int[] {13, 8175, 9, 9}, + new int[] {13, 8176, 10, 8}, + new int[] {13, 8177, 7, 11}, + new int[] {13, 8178, 12, 7}, + new int[] {13, 8179, 6, 11}, + new int[] {13, 8180, 8, 11}, + new int[] {13, 8181, 11, 8}, + new int[] {13, 8182, 7, 12}, + new int[] {13, 8183, 6, 12}, + new int[] {14, 16368, 8, 10}, + new int[] {14, 16369, 10, 9}, + new int[] {14, 16370, 8, 12}, + new int[] {14, 16371, 9, 10}, + new int[] {14, 16372, 9, 11}, + new int[] {14, 16373, 9, 12}, + new int[] {14, 16374, 10, 11}, + new int[] {14, 16375, 12, 9}, + new int[] {14, 16376, 10, 10}, + new int[] {14, 16377, 11, 9}, + new int[] {14, 16378, 12, 8}, + new int[] {14, 16379, 11, 10}, + new int[] {14, 16380, 12, 10}, + new int[] {14, 16381, 12, 11}, + new int[] {15, 32764, 10, 12}, + new int[] {15, 32765, 11, 11}, + new int[] {15, 32766, 11, 12}, + new int[] {15, 32767, 12, 12} + }; + public static int[][] HCB10 = { + new int[] {4, 0, 1, 1}, + new int[] {4, 1, 1, 2}, + new int[] {4, 2, 2, 1}, + new int[] {5, 6, 2, 2}, + new int[] {5, 7, 1, 0}, + new int[] {5, 8, 0, 1}, + new int[] {5, 9, 1, 3}, + new int[] {5, 10, 3, 2}, + new int[] {5, 11, 3, 1}, + new int[] {5, 12, 2, 3}, + new int[] {5, 13, 3, 3}, + new int[] {6, 28, 2, 0}, + new int[] {6, 29, 0, 2}, + new int[] {6, 30, 2, 4}, + new int[] {6, 31, 4, 2}, + new int[] {6, 32, 1, 4}, + new int[] {6, 33, 4, 1}, + new int[] {6, 34, 0, 0}, + new int[] {6, 35, 4, 3}, + new int[] {6, 36, 3, 4}, + new int[] {6, 37, 3, 0}, + new int[] {6, 38, 0, 3}, + new int[] {6, 39, 4, 4}, + new int[] {6, 40, 2, 5}, + new int[] {6, 41, 5, 2}, + new int[] {7, 84, 1, 5}, + new int[] {7, 85, 5, 1}, + new int[] {7, 86, 5, 3}, + new int[] {7, 87, 3, 5}, + new int[] {7, 88, 5, 4}, + new int[] {7, 89, 4, 5}, + new int[] {7, 90, 6, 2}, + new int[] {7, 91, 2, 6}, + new int[] {7, 92, 6, 3}, + new int[] {7, 93, 4, 0}, + new int[] {7, 94, 6, 1}, + new int[] {7, 95, 0, 4}, + new int[] {7, 96, 1, 6}, + new int[] {7, 97, 3, 6}, + new int[] {7, 98, 5, 5}, + new int[] {7, 99, 6, 4}, + new int[] {7, 100, 4, 6}, + new int[] {8, 202, 6, 5}, + new int[] {8, 203, 7, 2}, + new int[] {8, 204, 3, 7}, + new int[] {8, 205, 2, 7}, + new int[] {8, 206, 5, 6}, + new int[] {8, 207, 8, 2}, + new int[] {8, 208, 7, 3}, + new int[] {8, 209, 5, 0}, + new int[] {8, 210, 7, 1}, + new int[] {8, 211, 0, 5}, + new int[] {8, 212, 8, 1}, + new int[] {8, 213, 1, 7}, + new int[] {8, 214, 8, 3}, + new int[] {8, 215, 7, 4}, + new int[] {8, 216, 4, 7}, + new int[] {8, 217, 2, 8}, + new int[] {8, 218, 6, 6}, + new int[] {8, 219, 7, 5}, + new int[] {8, 220, 1, 8}, + new int[] {8, 221, 3, 8}, + new int[] {8, 222, 8, 4}, + new int[] {8, 223, 4, 8}, + new int[] {8, 224, 5, 7}, + new int[] {8, 225, 8, 5}, + new int[] {8, 226, 5, 8}, + new int[] {9, 454, 7, 6}, + new int[] {9, 455, 6, 7}, + new int[] {9, 456, 9, 2}, + new int[] {9, 457, 6, 0}, + new int[] {9, 458, 6, 8}, + new int[] {9, 459, 9, 3}, + new int[] {9, 460, 3, 9}, + new int[] {9, 461, 9, 1}, + new int[] {9, 462, 2, 9}, + new int[] {9, 463, 0, 6}, + new int[] {9, 464, 8, 6}, + new int[] {9, 465, 9, 4}, + new int[] {9, 466, 4, 9}, + new int[] {9, 467, 10, 2}, + new int[] {9, 468, 1, 9}, + new int[] {9, 469, 7, 7}, + new int[] {9, 470, 8, 7}, + new int[] {9, 471, 9, 5}, + new int[] {9, 472, 7, 8}, + new int[] {9, 473, 10, 3}, + new int[] {9, 474, 5, 9}, + new int[] {9, 475, 10, 4}, + new int[] {9, 476, 2, 10}, + new int[] {9, 477, 10, 1}, + new int[] {9, 478, 3, 10}, + new int[] {9, 479, 9, 6}, + new int[] {9, 480, 6, 9}, + new int[] {9, 481, 8, 0}, + new int[] {9, 482, 4, 10}, + new int[] {9, 483, 7, 0}, + new int[] {9, 484, 11, 2}, + new int[] {10, 970, 7, 9}, + new int[] {10, 971, 11, 3}, + new int[] {10, 972, 10, 6}, + new int[] {10, 973, 1, 10}, + new int[] {10, 974, 11, 1}, + new int[] {10, 975, 9, 7}, + new int[] {10, 976, 0, 7}, + new int[] {10, 977, 8, 8}, + new int[] {10, 978, 10, 5}, + new int[] {10, 979, 3, 11}, + new int[] {10, 980, 5, 10}, + new int[] {10, 981, 8, 9}, + new int[] {10, 982, 11, 5}, + new int[] {10, 983, 0, 8}, + new int[] {10, 984, 11, 4}, + new int[] {10, 985, 2, 11}, + new int[] {10, 986, 7, 10}, + new int[] {10, 987, 6, 10}, + new int[] {10, 988, 10, 7}, + new int[] {10, 989, 4, 11}, + new int[] {10, 990, 1, 11}, + new int[] {10, 991, 12, 2}, + new int[] {10, 992, 9, 8}, + new int[] {10, 993, 12, 3}, + new int[] {10, 994, 11, 6}, + new int[] {10, 995, 5, 11}, + new int[] {10, 996, 12, 4}, + new int[] {10, 997, 11, 7}, + new int[] {10, 998, 12, 5}, + new int[] {10, 999, 3, 12}, + new int[] {10, 1000, 6, 11}, + new int[] {10, 1001, 9, 0}, + new int[] {10, 1002, 10, 8}, + new int[] {10, 1003, 10, 0}, + new int[] {10, 1004, 12, 1}, + new int[] {10, 1005, 0, 9}, + new int[] {10, 1006, 4, 12}, + new int[] {10, 1007, 9, 9}, + new int[] {10, 1008, 12, 6}, + new int[] {10, 1009, 2, 12}, + new int[] {10, 1010, 8, 10}, + new int[] {11, 2022, 9, 10}, + new int[] {11, 2023, 1, 12}, + new int[] {11, 2024, 11, 8}, + new int[] {11, 2025, 12, 7}, + new int[] {11, 2026, 7, 11}, + new int[] {11, 2027, 5, 12}, + new int[] {11, 2028, 6, 12}, + new int[] {11, 2029, 10, 9}, + new int[] {11, 2030, 8, 11}, + new int[] {11, 2031, 12, 8}, + new int[] {11, 2032, 0, 10}, + new int[] {11, 2033, 7, 12}, + new int[] {11, 2034, 11, 0}, + new int[] {11, 2035, 10, 10}, + new int[] {11, 2036, 11, 9}, + new int[] {11, 2037, 11, 10}, + new int[] {11, 2038, 0, 11}, + new int[] {11, 2039, 11, 11}, + new int[] {11, 2040, 9, 11}, + new int[] {11, 2041, 10, 11}, + new int[] {11, 2042, 12, 0}, + new int[] {11, 2043, 8, 12}, + new int[] {12, 4088, 12, 9}, + new int[] {12, 4089, 10, 12}, + new int[] {12, 4090, 9, 12}, + new int[] {12, 4091, 11, 12}, + new int[] {12, 4092, 12, 11}, + new int[] {12, 4093, 0, 12}, + new int[] {12, 4094, 12, 10}, + new int[] {12, 4095, 12, 12} + }; + public static int[][] HCB11 = { + new int[] {4, 0, 0, 0}, + new int[] {4, 1, 1, 1}, + new int[] {5, 4, 16, 16}, + new int[] {5, 5, 1, 0}, + new int[] {5, 6, 0, 1}, + new int[] {5, 7, 2, 1}, + new int[] {5, 8, 1, 2}, + new int[] {5, 9, 2, 2}, + new int[] {6, 20, 1, 3}, + new int[] {6, 21, 3, 1}, + new int[] {6, 22, 3, 2}, + new int[] {6, 23, 2, 0}, + new int[] {6, 24, 2, 3}, + new int[] {6, 25, 0, 2}, + new int[] {6, 26, 3, 3}, + new int[] {7, 54, 4, 1}, + new int[] {7, 55, 1, 4}, + new int[] {7, 56, 4, 2}, + new int[] {7, 57, 2, 4}, + new int[] {7, 58, 4, 3}, + new int[] {7, 59, 3, 4}, + new int[] {7, 60, 3, 0}, + new int[] {7, 61, 0, 3}, + new int[] {7, 62, 5, 1}, + new int[] {7, 63, 5, 2}, + new int[] {7, 64, 2, 5}, + new int[] {7, 65, 4, 4}, + new int[] {7, 66, 1, 5}, + new int[] {7, 67, 5, 3}, + new int[] {7, 68, 3, 5}, + new int[] {7, 69, 5, 4}, + new int[] {8, 140, 4, 5}, + new int[] {8, 141, 6, 2}, + new int[] {8, 142, 2, 6}, + new int[] {8, 143, 6, 1}, + new int[] {8, 144, 6, 3}, + new int[] {8, 145, 3, 6}, + new int[] {8, 146, 1, 6}, + new int[] {8, 147, 4, 16}, + new int[] {8, 148, 3, 16}, + new int[] {8, 149, 16, 5}, + new int[] {8, 150, 16, 3}, + new int[] {8, 151, 16, 4}, + new int[] {8, 152, 6, 4}, + new int[] {8, 153, 16, 6}, + new int[] {8, 154, 4, 0}, + new int[] {8, 155, 4, 6}, + new int[] {8, 156, 0, 4}, + new int[] {8, 157, 2, 16}, + new int[] {8, 158, 5, 5}, + new int[] {8, 159, 5, 16}, + new int[] {8, 160, 16, 7}, + new int[] {8, 161, 16, 2}, + new int[] {8, 162, 16, 8}, + new int[] {8, 163, 2, 7}, + new int[] {8, 164, 7, 2}, + new int[] {8, 165, 3, 7}, + new int[] {8, 166, 6, 5}, + new int[] {8, 167, 5, 6}, + new int[] {8, 168, 6, 16}, + new int[] {8, 169, 16, 10}, + new int[] {8, 170, 7, 3}, + new int[] {8, 171, 7, 1}, + new int[] {8, 172, 16, 9}, + new int[] {8, 173, 7, 16}, + new int[] {8, 174, 1, 16}, + new int[] {8, 175, 1, 7}, + new int[] {8, 176, 4, 7}, + new int[] {8, 177, 16, 11}, + new int[] {8, 178, 7, 4}, + new int[] {8, 179, 16, 12}, + new int[] {8, 180, 8, 16}, + new int[] {8, 181, 16, 1}, + new int[] {8, 182, 6, 6}, + new int[] {8, 183, 9, 16}, + new int[] {8, 184, 2, 8}, + new int[] {8, 185, 5, 7}, + new int[] {8, 186, 10, 16}, + new int[] {8, 187, 16, 13}, + new int[] {8, 188, 8, 3}, + new int[] {8, 189, 8, 2}, + new int[] {8, 190, 3, 8}, + new int[] {8, 191, 5, 0}, + new int[] {8, 192, 16, 14}, + new int[] {8, 193, 11, 16}, + new int[] {8, 194, 7, 5}, + new int[] {8, 195, 4, 8}, + new int[] {8, 196, 6, 7}, + new int[] {8, 197, 7, 6}, + new int[] {8, 198, 0, 5}, + new int[] {9, 398, 8, 4}, + new int[] {9, 399, 16, 15}, + new int[] {9, 400, 12, 16}, + new int[] {9, 401, 1, 8}, + new int[] {9, 402, 8, 1}, + new int[] {9, 403, 14, 16}, + new int[] {9, 404, 5, 8}, + new int[] {9, 405, 13, 16}, + new int[] {9, 406, 3, 9}, + new int[] {9, 407, 8, 5}, + new int[] {9, 408, 7, 7}, + new int[] {9, 409, 2, 9}, + new int[] {9, 410, 8, 6}, + new int[] {9, 411, 9, 2}, + new int[] {9, 412, 9, 3}, + new int[] {9, 413, 15, 16}, + new int[] {9, 414, 4, 9}, + new int[] {9, 415, 6, 8}, + new int[] {9, 416, 6, 0}, + new int[] {9, 417, 9, 4}, + new int[] {9, 418, 5, 9}, + new int[] {9, 419, 8, 7}, + new int[] {9, 420, 7, 8}, + new int[] {9, 421, 1, 9}, + new int[] {9, 422, 10, 3}, + new int[] {9, 423, 0, 6}, + new int[] {9, 424, 10, 2}, + new int[] {9, 425, 9, 1}, + new int[] {9, 426, 9, 5}, + new int[] {9, 427, 4, 10}, + new int[] {9, 428, 2, 10}, + new int[] {9, 429, 9, 6}, + new int[] {9, 430, 3, 10}, + new int[] {9, 431, 6, 9}, + new int[] {9, 432, 10, 4}, + new int[] {9, 433, 8, 8}, + new int[] {9, 434, 10, 5}, + new int[] {9, 435, 9, 7}, + new int[] {9, 436, 11, 3}, + new int[] {9, 437, 1, 10}, + new int[] {9, 438, 7, 0}, + new int[] {9, 439, 10, 6}, + new int[] {9, 440, 7, 9}, + new int[] {9, 441, 3, 11}, + new int[] {9, 442, 5, 10}, + new int[] {9, 443, 10, 1}, + new int[] {9, 444, 4, 11}, + new int[] {9, 445, 11, 2}, + new int[] {9, 446, 13, 2}, + new int[] {9, 447, 6, 10}, + new int[] {9, 448, 13, 3}, + new int[] {9, 449, 2, 11}, + new int[] {9, 450, 16, 0}, + new int[] {9, 451, 5, 11}, + new int[] {9, 452, 11, 5}, + new int[] {10, 906, 11, 4}, + new int[] {10, 907, 9, 8}, + new int[] {10, 908, 7, 10}, + new int[] {10, 909, 8, 9}, + new int[] {10, 910, 0, 16}, + new int[] {10, 911, 4, 13}, + new int[] {10, 912, 0, 7}, + new int[] {10, 913, 3, 13}, + new int[] {10, 914, 11, 6}, + new int[] {10, 915, 13, 1}, + new int[] {10, 916, 13, 4}, + new int[] {10, 917, 12, 3}, + new int[] {10, 918, 2, 13}, + new int[] {10, 919, 13, 5}, + new int[] {10, 920, 8, 10}, + new int[] {10, 921, 6, 11}, + new int[] {10, 922, 10, 8}, + new int[] {10, 923, 10, 7}, + new int[] {10, 924, 14, 2}, + new int[] {10, 925, 12, 4}, + new int[] {10, 926, 1, 11}, + new int[] {10, 927, 4, 12}, + new int[] {10, 928, 11, 1}, + new int[] {10, 929, 3, 12}, + new int[] {10, 930, 1, 13}, + new int[] {10, 931, 12, 2}, + new int[] {10, 932, 7, 11}, + new int[] {10, 933, 3, 14}, + new int[] {10, 934, 5, 12}, + new int[] {10, 935, 5, 13}, + new int[] {10, 936, 14, 4}, + new int[] {10, 937, 4, 14}, + new int[] {10, 938, 11, 7}, + new int[] {10, 939, 14, 3}, + new int[] {10, 940, 12, 5}, + new int[] {10, 941, 13, 6}, + new int[] {10, 942, 12, 6}, + new int[] {10, 943, 8, 0}, + new int[] {10, 944, 11, 8}, + new int[] {10, 945, 2, 12}, + new int[] {10, 946, 9, 9}, + new int[] {10, 947, 14, 5}, + new int[] {10, 948, 6, 13}, + new int[] {10, 949, 10, 10}, + new int[] {10, 950, 15, 2}, + new int[] {10, 951, 8, 11}, + new int[] {10, 952, 9, 10}, + new int[] {10, 953, 14, 6}, + new int[] {10, 954, 10, 9}, + new int[] {10, 955, 5, 14}, + new int[] {10, 956, 11, 9}, + new int[] {10, 957, 14, 1}, + new int[] {10, 958, 2, 14}, + new int[] {10, 959, 6, 12}, + new int[] {10, 960, 1, 12}, + new int[] {10, 961, 13, 8}, + new int[] {10, 962, 0, 8}, + new int[] {10, 963, 13, 7}, + new int[] {10, 964, 7, 12}, + new int[] {10, 965, 12, 7}, + new int[] {10, 966, 7, 13}, + new int[] {10, 967, 15, 3}, + new int[] {10, 968, 12, 1}, + new int[] {10, 969, 6, 14}, + new int[] {10, 970, 2, 15}, + new int[] {10, 971, 15, 5}, + new int[] {10, 972, 15, 4}, + new int[] {10, 973, 1, 14}, + new int[] {10, 974, 9, 11}, + new int[] {10, 975, 4, 15}, + new int[] {10, 976, 14, 7}, + new int[] {10, 977, 8, 13}, + new int[] {10, 978, 13, 9}, + new int[] {10, 979, 8, 12}, + new int[] {10, 980, 5, 15}, + new int[] {10, 981, 3, 15}, + new int[] {10, 982, 10, 11}, + new int[] {10, 983, 11, 10}, + new int[] {10, 984, 12, 8}, + new int[] {10, 985, 15, 6}, + new int[] {10, 986, 15, 7}, + new int[] {10, 987, 8, 14}, + new int[] {10, 988, 15, 1}, + new int[] {10, 989, 7, 14}, + new int[] {10, 990, 9, 0}, + new int[] {10, 991, 0, 9}, + new int[] {10, 992, 9, 13}, + new int[] {10, 993, 9, 12}, + new int[] {10, 994, 12, 9}, + new int[] {10, 995, 14, 8}, + new int[] {10, 996, 10, 13}, + new int[] {10, 997, 14, 9}, + new int[] {10, 998, 12, 10}, + new int[] {10, 999, 6, 15}, + new int[] {10, 1000, 7, 15}, + new int[] {11, 2002, 9, 14}, + new int[] {11, 2003, 15, 8}, + new int[] {11, 2004, 11, 11}, + new int[] {11, 2005, 11, 14}, + new int[] {11, 2006, 1, 15}, + new int[] {11, 2007, 10, 12}, + new int[] {11, 2008, 10, 14}, + new int[] {11, 2009, 13, 11}, + new int[] {11, 2010, 13, 10}, + new int[] {11, 2011, 11, 13}, + new int[] {11, 2012, 11, 12}, + new int[] {11, 2013, 8, 15}, + new int[] {11, 2014, 14, 11}, + new int[] {11, 2015, 13, 12}, + new int[] {11, 2016, 12, 13}, + new int[] {11, 2017, 15, 9}, + new int[] {11, 2018, 14, 10}, + new int[] {11, 2019, 10, 0}, + new int[] {11, 2020, 12, 11}, + new int[] {11, 2021, 9, 15}, + new int[] {11, 2022, 0, 10}, + new int[] {11, 2023, 12, 12}, + new int[] {11, 2024, 11, 0}, + new int[] {11, 2025, 12, 14}, + new int[] {11, 2026, 10, 15}, + new int[] {11, 2027, 13, 13}, + new int[] {11, 2028, 0, 13}, + new int[] {11, 2029, 14, 12}, + new int[] {11, 2030, 15, 10}, + new int[] {11, 2031, 15, 11}, + new int[] {11, 2032, 11, 15}, + new int[] {11, 2033, 14, 13}, + new int[] {11, 2034, 13, 0}, + new int[] {11, 2035, 0, 11}, + new int[] {11, 2036, 13, 14}, + new int[] {11, 2037, 15, 12}, + new int[] {11, 2038, 15, 13}, + new int[] {11, 2039, 12, 15}, + new int[] {11, 2040, 14, 0}, + new int[] {11, 2041, 14, 14}, + new int[] {11, 2042, 13, 15}, + new int[] {11, 2043, 12, 0}, + new int[] {11, 2044, 14, 15}, + new int[] {12, 4090, 0, 14}, + new int[] {12, 4091, 0, 12}, + new int[] {12, 4092, 15, 14}, + new int[] {12, 4093, 15, 0}, + new int[] {12, 4094, 0, 15}, + new int[] {12, 4095, 15, 15} + }; + public static int[][] HCB_SF = { + new int[] {1, 0, 60}, + new int[] {3, 4, 59}, + new int[] {4, 10, 61}, + new int[] {4, 11, 58}, + new int[] {4, 12, 62}, + new int[] {5, 26, 57}, + new int[] {5, 27, 63}, + new int[] {6, 56, 56}, + new int[] {6, 57, 64}, + new int[] {6, 58, 55}, + new int[] {6, 59, 65}, + new int[] {7, 120, 66}, + new int[] {7, 121, 54}, + new int[] {7, 122, 67}, + new int[] {8, 246, 53}, + new int[] {8, 247, 68}, + new int[] {8, 248, 52}, + new int[] {8, 249, 69}, + new int[] {8, 250, 51}, + new int[] {9, 502, 70}, + new int[] {9, 503, 50}, + new int[] {9, 504, 49}, + new int[] {9, 505, 71}, + new int[] {10, 1012, 72}, + new int[] {10, 1013, 48}, + new int[] {10, 1014, 73}, + new int[] {10, 1015, 47}, + new int[] {10, 1016, 74}, + new int[] {10, 1017, 46}, + new int[] {11, 2036, 76}, + new int[] {11, 2037, 75}, + new int[] {11, 2038, 77}, + new int[] {11, 2039, 78}, + new int[] {11, 2040, 45}, + new int[] {11, 2041, 43}, + new int[] {12, 4084, 44}, + new int[] {12, 4085, 79}, + new int[] {12, 4086, 42}, + new int[] {12, 4087, 41}, + new int[] {12, 4088, 80}, + new int[] {12, 4089, 40}, + new int[] {13, 8180, 81}, + new int[] {13, 8181, 39}, + new int[] {13, 8182, 82}, + new int[] {13, 8183, 38}, + new int[] {13, 8184, 83}, + new int[] {14, 16370, 37}, + new int[] {14, 16371, 35}, + new int[] {14, 16372, 85}, + new int[] {14, 16373, 33}, + new int[] {14, 16374, 36}, + new int[] {14, 16375, 34}, + new int[] {14, 16376, 84}, + new int[] {14, 16377, 32}, + new int[] {15, 32756, 87}, + new int[] {15, 32757, 89}, + new int[] {15, 32758, 30}, + new int[] {15, 32759, 31}, + new int[] {16, 65520, 86}, + new int[] {16, 65521, 29}, + new int[] {16, 65522, 26}, + new int[] {16, 65523, 27}, + new int[] {16, 65524, 28}, + new int[] {16, 65525, 24}, + new int[] {16, 65526, 88}, + new int[] {17, 131054, 25}, + new int[] {17, 131055, 22}, + new int[] {17, 131056, 23}, + new int[] {18, 262114, 90}, + new int[] {18, 262115, 21}, + new int[] {18, 262116, 19}, + new int[] {18, 262117, 3}, + new int[] {18, 262118, 1}, + new int[] {18, 262119, 2}, + new int[] {18, 262120, 0}, + new int[] {19, 524242, 98}, + new int[] {19, 524243, 99}, + new int[] {19, 524244, 100}, + new int[] {19, 524245, 101}, + new int[] {19, 524246, 102}, + new int[] {19, 524247, 117}, + new int[] {19, 524248, 97}, + new int[] {19, 524249, 91}, + new int[] {19, 524250, 92}, + new int[] {19, 524251, 93}, + new int[] {19, 524252, 94}, + new int[] {19, 524253, 95}, + new int[] {19, 524254, 96}, + new int[] {19, 524255, 104}, + new int[] {19, 524256, 111}, + new int[] {19, 524257, 112}, + new int[] {19, 524258, 113}, + new int[] {19, 524259, 114}, + new int[] {19, 524260, 115}, + new int[] {19, 524261, 116}, + new int[] {19, 524262, 110}, + new int[] {19, 524263, 105}, + new int[] {19, 524264, 106}, + new int[] {19, 524265, 107}, + new int[] {19, 524266, 108}, + new int[] {19, 524267, 109}, + new int[] {19, 524268, 118}, + new int[] {19, 524269, 6}, + new int[] {19, 524270, 8}, + new int[] {19, 524271, 9}, + new int[] {19, 524272, 10}, + new int[] {19, 524273, 5}, + new int[] {19, 524274, 103}, + new int[] {19, 524275, 120}, + new int[] {19, 524276, 119}, + new int[] {19, 524277, 4}, + new int[] {19, 524278, 7}, + new int[] {19, 524279, 15}, + new int[] {19, 524280, 16}, + new int[] {19, 524281, 18}, + new int[] {19, 524282, 20}, + new int[] {19, 524283, 17}, + new int[] {19, 524284, 11}, + new int[] {19, 524285, 12}, + new int[] {19, 524286, 14}, + new int[] {19, 524287, 13} + }; + public static int[][][] CODEBOOKS = { HCB1, HCB2, HCB3, HCB4, HCB5, HCB6, HCB7, HCB8, HCB9, HCB10, HCB11 }; + } +} diff --git a/SharpJaad.AAC/Huffman/HCB.cs b/SharpJaad.AAC/Huffman/HCB.cs new file mode 100644 index 0000000..5a2e302 --- /dev/null +++ b/SharpJaad.AAC/Huffman/HCB.cs @@ -0,0 +1,13 @@ +namespace SharpJaad.AAC.Huffman +{ + public static class HCB + { + public const int ZERO_HCB = 0; + public const int ESCAPE_HCB = 11; + public const int NOISE_HCB = 13; + public const int INTENSITY_HCB2 = 14; + public const int INTENSITY_HCB = 15; + // + public const int FIRST_PAIR_HCB = 5; + } +} diff --git a/SharpJaad.AAC/Huffman/HuffmanDec.cs b/SharpJaad.AAC/Huffman/HuffmanDec.cs new file mode 100644 index 0000000..aa1d268 --- /dev/null +++ b/SharpJaad.AAC/Huffman/HuffmanDec.cs @@ -0,0 +1,90 @@ +using SharpJaad.AAC; +using SharpJaad.AAC.Syntax; +using System; + +namespace SharpJaad.AAC.Huffman +{ + public static class HuffmanDec + { + private static readonly bool[] _UNSIGNED = { false, false, true, true, false, false, true, true, true, true, true }; + private static int _QUAD_LEN = 4, _PAIR_LEN = 2; + + private static int FindOffset(BitStream input, int[][] table) + { + int off = 0; + int len = table[off][0]; + int cw = input.ReadBits(len); + int j; + while (cw != table[off][1]) + { + off++; + j = table[off][0] - len; + len = table[off][0]; + cw <<= j; + cw |= input.ReadBits(j); + } + return off; + } + + private static void SignValues(BitStream input, int[] data, int off, int len) + { + for (int i = off; i < off + len; i++) + { + if (data[i] != 0) + { + if (input.ReadBool()) data[i] = -data[i]; + } + } + } + + private static int GetEscape(BitStream input, int s) + { + bool neg = s < 0; + + int i = 4; + while (input.ReadBool()) + { + i++; + } + int j = input.ReadBits(i) | 1 << i; + + return neg ? -j : j; + } + + public static int DecodeScaleFactor(BitStream input) + { + int offset = FindOffset(input, Codebooks.HCB_SF); + return Codebooks.HCB_SF[offset][2]; + } + + public static void DecodeSpectralData(BitStream input, int cb, int[] data, int off) + { + int[][] HCB = Codebooks.CODEBOOKS[cb - 1]; + + //find index + int offset = FindOffset(input, HCB); + + //copy data + data[off] = HCB[offset][2]; + data[off + 1] = HCB[offset][3]; + if (cb < 5) + { + data[off + 2] = HCB[offset][4]; + data[off + 3] = HCB[offset][5]; + } + + //sign & escape + if (cb < 11) + { + if (_UNSIGNED[cb - 1]) SignValues(input, data, off, cb < 5 ? _QUAD_LEN : _PAIR_LEN); + } + else if (cb == 11 || cb > 15) + { + SignValues(input, data, off, cb < 5 ? _QUAD_LEN : _PAIR_LEN); //virtual codebooks are always unsigned + if (Math.Abs(data[off]) == 16) data[off] = GetEscape(input, data[off]); + if (Math.Abs(data[off + 1]) == 16) data[off + 1] = GetEscape(input, data[off + 1]); + } + else throw new AACException("Huffman: unknown spectral codebook: " + cb); + } + } +} diff --git a/SharpJaad.AAC/Profile.cs b/SharpJaad.AAC/Profile.cs new file mode 100644 index 0000000..f28d14f --- /dev/null +++ b/SharpJaad.AAC/Profile.cs @@ -0,0 +1,35 @@ +namespace SharpJaad.AAC +{ + public enum Profile : int + { + UNKNOWN = -1, + AAC_MAIN = 1, + AAC_LC = 2, + AAC_SSR = 3, + AAC_LTP = 4, + AAC_SBR = 5, + AAC_SCALABLE = 6, + TWIN_VQ = 7, + AAC_LD = 11, + ER_AAC_LC = 17, + ER_AAC_SSR = 18, + ER_AAC_LTP = 19, + ER_AAC_SCALABLE = 20, + ER_TWIN_VQ = 21, + ER_BSAC = 22, + ER_AAC_LD = 23 + } + + public static class ProfileExtensions + { + public static bool IsErrorResilientProfile(this Profile profile) + { + return (int)profile > 16; + } + + public static bool IsDecodingSupported(this Profile profile) + { + return profile == Profile.AAC_MAIN || profile == Profile.AAC_LC || profile == Profile.AAC_SBR || profile == Profile.ER_AAC_LC || profile == Profile.AAC_LTP || profile == Profile.ER_AAC_LTP; + } + } +} diff --git a/SharpJaad.AAC/Ps/Filterbank.cs b/SharpJaad.AAC/Ps/Filterbank.cs new file mode 100644 index 0000000..29ba259 --- /dev/null +++ b/SharpJaad.AAC/Ps/Filterbank.cs @@ -0,0 +1,386 @@ +namespace SharpJaad.AAC.Ps +{ + public class Filterbank + { + private int _frameLen; + private int[] _resolution20 = new int[3]; + private int[] _resolution34 = new int[5]; + + private float[,] _work; + private float[,,] _buffer; + private float[,,] _temp; + + public Filterbank(int numTimeSlotsRate) + { + _resolution34[0] = 12; + _resolution34[1] = 8; + _resolution34[2] = 4; + _resolution34[3] = 4; + _resolution34[4] = 4; + + _resolution20[0] = 8; + _resolution20[1] = 2; + _resolution20[2] = 2; + + _frameLen = numTimeSlotsRate; + + _work = new float[(_frameLen + 12), 2]; + + _buffer = new float[5, _frameLen, 2]; + + _temp = new float[_frameLen, 12, 2]; + } + + public void HybridAnalysis(float[,,] X, float[,,] X_hybrid, bool use34, int numTimeSlotsRate) + { + int k, n, band; + int offset = 0; + int qmf_bands = use34 ? 5 : 3; + int[] resolution = use34 ? _resolution34 : _resolution20; + + for (band = 0; band < qmf_bands; band++) + { + /* build working buffer */ + //memcpy(this.work, this.buffer[band], 12*sizeof(qmf_t)); + for (int i = 0; i < 12; i++) + { + _work[i, 0] = _buffer[band, i, 0]; + _work[i, 1] = _buffer[band, i, 1]; + } + + /* add new samples */ + for (n = 0; n < _frameLen; n++) + { + _work[12 + n, 0] = X[n + 6 /*delay*/, band, 0]; + _work[12 + n, 0] = X[n + 6 /*delay*/, band, 0]; + } + + /* store samples */ + //memcpy(this.buffer[band], this.work+this.frame_len, 12*sizeof(qmf_t)); + for (int i = 0; i < 12; i++) + { + _buffer[band, i, 0] = _work[_frameLen + i, 0]; + _buffer[band, i, 1] = _work[_frameLen + i, 1]; + } + + switch (resolution[band]) + { + case 2: + /* Type B real filter, Q[p] = 2 */ + ChannelFilter2(_frameLen, PSTables.p2_13_20, _work, _temp); + break; + case 4: + /* Type A complex filter, Q[p] = 4 */ + ChannelFilter4(_frameLen, PSTables.p4_13_34, _work, _temp); + break; + case 8: + /* Type A complex filter, Q[p] = 8 */ + ChannelFilter8(_frameLen, use34 ? PSTables.p8_13_34 : PSTables.p8_13_20, + _work, _temp); + break; + case 12: + /* Type A complex filter, Q[p] = 12 */ + ChannelFilter12(_frameLen, PSTables.p12_13_34, _work, _temp); + break; + } + + for (n = 0; n < _frameLen; n++) + { + for (k = 0; k < resolution[band]; k++) + { + X_hybrid[n, offset + k, 0] = _temp[n, k, 0]; + X_hybrid[n, offset + k, 1] = _temp[n, k, 1]; + } + } + offset += resolution[band]; + } + + /* group hybrid channels */ + if (!use34) + { + for (n = 0; n < numTimeSlotsRate; n++) + { + X_hybrid[n, 3, 0] += X_hybrid[n, 4, 0]; + X_hybrid[n, 3, 1] += X_hybrid[n, 4, 1]; + X_hybrid[n, 4, 0] = 0; + X_hybrid[n, 4, 1] = 0; + + X_hybrid[n, 2, 0] += X_hybrid[n, 5, 0]; + X_hybrid[n, 2, 1] += X_hybrid[n, 5, 1]; + X_hybrid[n, 5, 0] = 0; + X_hybrid[n, 5, 1] = 0; + } + } + } + + /* real filter, size 2 */ + private static void ChannelFilter2(int frame_len, float[] filter, float[,] buffer, float[,,] X_hybrid) + { + int i; + + for (i = 0; i < frame_len; i++) + { + float r0 = filter[0] * (buffer[0 + i, 0] + buffer[12 + i, 0]); + float r1 = filter[1] * (buffer[1 + i, 0] + buffer[11 + i, 0]); + float r2 = filter[2] * (buffer[2 + i, 0] + buffer[10 + i, 0]); + float r3 = filter[3] * (buffer[3 + i, 0] + buffer[9 + i, 0]); + float r4 = filter[4] * (buffer[4 + i, 0] + buffer[8 + i, 0]); + float r5 = filter[5] * (buffer[5 + i, 0] + buffer[7 + i, 0]); + float r6 = filter[6] * buffer[6 + i, 0]; + float i0 = filter[0] * (buffer[0 + i, 1] + buffer[12 + i, 1]); + float i1 = filter[1] * (buffer[1 + i, 1] + buffer[11 + i, 1]); + float i2 = filter[2] * (buffer[2 + i, 1] + buffer[10 + i, 1]); + float i3 = filter[3] * (buffer[3 + i, 1] + buffer[9 + i, 1]); + float i4 = filter[4] * (buffer[4 + i, 1] + buffer[8 + i, 1]); + float i5 = filter[5] * (buffer[5 + i, 1] + buffer[7 + i, 1]); + float i6 = filter[6] * buffer[6 + i, 1]; + + /* q = 0 */ + X_hybrid[i, 0, 0] = r0 + r1 + r2 + r3 + r4 + r5 + r6; + X_hybrid[i, 0, 1] = i0 + i1 + i2 + i3 + i4 + i5 + i6; + + /* q = 1 */ + X_hybrid[i, 1, 0] = r0 - r1 + r2 - r3 + r4 - r5 + r6; + X_hybrid[i, 1, 1] = i0 - i1 + i2 - i3 + i4 - i5 + i6; + } + } + + /* complex filter, size 4 */ + private static void ChannelFilter4(int frame_len, float[] filter, float[,] buffer, float[,,] X_hybrid) + { + int i; + float[] input_re1 = new float[2], input_re2 = new float[2]; + float[] input_im1 = new float[2], input_im2 = new float[2]; + + for (i = 0; i < frame_len; i++) + { + input_re1[0] = -(filter[2] * (buffer[i + 2, 0] + buffer[i + 10, 0])) + + filter[6] * buffer[i + 6, 0]; + input_re1[1] = -0.70710678118655f + * (filter[1] * (buffer[i + 1, 0] + buffer[i + 11, 0]) + + filter[3] * (buffer[i + 3, 0] + buffer[i + 9, 0]) + - filter[5] * (buffer[i + 5, 0] + buffer[i + 7, 0])); + + input_im1[0] = filter[0] * (buffer[i + 0, 1] - buffer[i + 12, 1]) + - filter[4] * (buffer[i + 4, 1] - buffer[i + 8, 1]); + input_im1[1] = 0.70710678118655f + * (filter[1] * (buffer[i + 1, 1] - buffer[i + 11, 1]) + - filter[3] * (buffer[i + 3, 1] - buffer[i + 9, 1]) + - filter[5] * (buffer[i + 5, 1] - buffer[i + 7, 1])); + + input_re2[0] = filter[0] * (buffer[i + 0, 0] - buffer[i + 12, 0]) + - filter[4] * (buffer[i + 4, 0] - buffer[i + 8, 0]); + input_re2[1] = 0.70710678118655f + * (filter[1] * (buffer[i + 1, 0] - buffer[i + 11, 0]) + - filter[3] * (buffer[i + 3, 0] - buffer[i + 9, 0]) + - filter[5] * (buffer[i + 5, 0] - buffer[i + 7, 0])); + + input_im2[0] = -(filter[2] * (buffer[i + 2, 1] + buffer[i + 10, 1])) + + filter[6] * buffer[i + 6, 1]; + input_im2[1] = -0.70710678118655f + * (filter[1] * (buffer[i + 1, 1] + buffer[i + 11, 1]) + + filter[3] * (buffer[i + 3, 1] + buffer[i + 9, 1]) + - filter[5] * (buffer[i + 5, 1] + buffer[i + 7, 1])); + + /* q == 0 */ + X_hybrid[i, 0, 0] = input_re1[0] + input_re1[1] + input_im1[0] + input_im1[1]; + X_hybrid[i, 0, 1] = -input_re2[0] - input_re2[1] + input_im2[0] + input_im2[1]; + + /* q == 1 */ + X_hybrid[i, 1, 0] = input_re1[0] - input_re1[1] - input_im1[0] + input_im1[1]; + X_hybrid[i, 1, 1] = input_re2[0] - input_re2[1] + input_im2[0] - input_im2[1]; + + /* q == 2 */ + X_hybrid[i, 2, 0] = input_re1[0] - input_re1[1] + input_im1[0] - input_im1[1]; + X_hybrid[i, 2, 1] = -input_re2[0] + input_re2[1] + input_im2[0] - input_im2[1]; + + /* q == 3 */ + X_hybrid[i, 3, 0] = input_re1[0] + input_re1[1] - input_im1[0] - input_im1[1]; + X_hybrid[i, 3, 1] = input_re2[0] + input_re2[1] + input_im2[0] + input_im2[1]; + } + } + + private static void DCT3_4_Unscaled(float[] y, float[] x) + { + float f0, f1, f2, f3, f4, f5, f6, f7, f8; + + f0 = x[2] * 0.7071067811865476f; + f1 = x[0] - f0; + f2 = x[0] + f0; + f3 = x[1] + x[3]; + f4 = x[1] * 1.3065629648763766f; + f5 = f3 * -0.9238795325112866f; + f6 = x[3] * -0.5411961001461967f; + f7 = f4 + f5; + f8 = f6 - f5; + y[3] = f2 - f8; + y[0] = f2 + f8; + y[2] = f1 - f7; + y[1] = f1 + f7; + } + + /* complex filter, size 8 */ + private void ChannelFilter8(int frame_len, float[] filter, float[,] buffer, float[,,] X_hybrid) + { + int i, n; + float[] input_re1 = new float[4], input_re2 = new float[4]; + float[] input_im1 = new float[4], input_im2 = new float[4]; + float[] x = new float[4]; + + for (i = 0; i < frame_len; i++) + { + input_re1[0] = filter[6] * buffer[6 + i, 0]; + input_re1[1] = filter[5] * (buffer[5 + i, 0] + buffer[7 + i, 0]); + input_re1[2] = -(filter[0] * (buffer[0 + i, 0] + buffer[12 + i, 0])) + filter[4] * (buffer[4 + i, 0] + buffer[8 + i, 0]); + input_re1[3] = -(filter[1] * (buffer[1 + i, 0] + buffer[11 + i, 0])) + filter[3] * (buffer[3 + i, 0] + buffer[9 + i, 0]); + + input_im1[0] = filter[5] * (buffer[7 + i, 1] - buffer[5 + i, 1]); + input_im1[1] = filter[0] * (buffer[12 + i, 1] - buffer[0 + i, 1]) + filter[4] * (buffer[8 + i, 1] - buffer[4 + i, 1]); + input_im1[2] = filter[1] * (buffer[11 + i, 1] - buffer[1 + i, 1]) + filter[3] * (buffer[9 + i, 1] - buffer[3 + i, 1]); + input_im1[3] = filter[2] * (buffer[10 + i, 1] - buffer[2 + i, 1]); + + for (n = 0; n < 4; n++) + { + x[n] = input_re1[n] - input_im1[3 - n]; + } + DCT3_4_Unscaled(x, x); + X_hybrid[i, 7, 0] = x[0]; + X_hybrid[i, 5, 0] = x[2]; + X_hybrid[i, 3, 0] = x[3]; + X_hybrid[i, 1, 0] = x[1]; + + for (n = 0; n < 4; n++) + { + x[n] = input_re1[n] + input_im1[3 - n]; + } + DCT3_4_Unscaled(x, x); + X_hybrid[i, 6, 0] = x[1]; + X_hybrid[i, 4, 0] = x[3]; + X_hybrid[i, 2, 0] = x[2]; + X_hybrid[i, 0, 0] = x[0]; + + input_im2[0] = filter[6] * buffer[6 + i, 1]; + input_im2[1] = filter[5] * (buffer[5 + i, 1] + buffer[7 + i, 1]); + input_im2[2] = -(filter[0] * (buffer[0 + i, 1] + buffer[12 + i, 1])) + filter[4] * (buffer[4 + i, 1] + buffer[8 + i, 1]); + input_im2[3] = -(filter[1] * (buffer[1 + i, 1] + buffer[11 + i, 1])) + filter[3] * (buffer[3 + i, 1] + buffer[9 + i, 1]); + + input_re2[0] = filter[5] * (buffer[7 + i, 0] - buffer[5 + i, 0]); + input_re2[1] = filter[0] * (buffer[12 + i, 0] - buffer[0 + i, 0]) + filter[4] * (buffer[8 + i, 0] - buffer[4 + i, 0]); + input_re2[2] = filter[1] * (buffer[11 + i, 0] - buffer[1 + i, 0]) + filter[3] * (buffer[9 + i, 0] - buffer[3 + i, 0]); + input_re2[3] = filter[2] * (buffer[10 + i, 0] - buffer[2 + i, 0]); + + for (n = 0; n < 4; n++) + { + x[n] = input_im2[n] + input_re2[3 - n]; + } + DCT3_4_Unscaled(x, x); + X_hybrid[i, 7, 1] = x[0]; + X_hybrid[i, 5, 1] = x[2]; + X_hybrid[i, 3, 1] = x[3]; + X_hybrid[i, 1, 1] = x[1]; + + for (n = 0; n < 4; n++) + { + x[n] = input_im2[n] - input_re2[3 - n]; + } + DCT3_4_Unscaled(x, x); + X_hybrid[i, 6, 1] = x[1]; + X_hybrid[i, 4, 1] = x[3]; + X_hybrid[i, 2, 1] = x[2]; + X_hybrid[i, 0, 1] = x[0]; + } + } + + private void DCT3_6_Unscaled(float[] y, float[] x) + { + float f0, f1, f2, f3, f4, f5, f6, f7; + + f0 = x[3] * 0.70710678118655f; + f1 = x[0] + f0; + f2 = x[0] - f0; + f3 = (x[1] - x[5]) * 0.70710678118655f; + f4 = x[2] * 0.86602540378444f + x[4] * 0.5f; + f5 = f4 - x[4]; + f6 = x[1] * 0.96592582628907f + x[5] * 0.25881904510252f; + f7 = f6 - f3; + y[0] = f1 + f6 + f4; + y[1] = f2 + f3 - x[4]; + y[2] = f7 + f2 - f5; + y[3] = f1 - f7 - f5; + y[4] = f1 - f3 - x[4]; + y[5] = f2 - f6 + f4; + } + + /* complex filter, size 12 */ + private void ChannelFilter12(int frame_len, float[] filter, float[,] buffer, float[,,] X_hybrid) + { + int i, n; + float[] input_re1 = new float[6], input_re2 = new float[6]; + float[] input_im1 = new float[6], input_im2 = new float[6]; + float[] out_re1 = new float[6], out_re2 = new float[6]; + float[] out_im1 = new float[6], out_im2 = new float[6]; + + for (i = 0; i < frame_len; i++) + { + for (n = 0; n < 6; n++) + { + if (n == 0) + { + input_re1[0] = buffer[6 + i, 0] * filter[6]; + input_re2[0] = buffer[6 + i, 1] * filter[6]; + } + else + { + input_re1[6 - n] = (buffer[n + i, 0] + buffer[12 - n + i, 0]) * filter[n]; + input_re2[6 - n] = (buffer[n + i, 1] + buffer[12 - n + i, 1]) * filter[n]; + } + input_im2[n] = (buffer[n + i, 0] - buffer[12 - n + i, 0]) * filter[n]; + input_im1[n] = (buffer[n + i, 1] - buffer[12 - n + i, 1]) * filter[n]; + } + + DCT3_6_Unscaled(out_re1, input_re1); + DCT3_6_Unscaled(out_re2, input_re2); + + DCT3_6_Unscaled(out_im1, input_im1); + DCT3_6_Unscaled(out_im2, input_im2); + + for (n = 0; n < 6; n += 2) + { + X_hybrid[i, n, 0] = out_re1[n] - out_im1[n]; + X_hybrid[i, n, 1] = out_re2[n] + out_im2[n]; + X_hybrid[i, n + 1, 0] = out_re1[n + 1] + out_im1[n + 1]; + X_hybrid[i, n + 1, 1] = out_re2[n + 1] - out_im2[n + 1]; + + X_hybrid[i, 10 - n, 0] = out_re1[n + 1] - out_im1[n + 1]; + X_hybrid[i, 10 - n, 1] = out_re2[n + 1] + out_im2[n + 1]; + X_hybrid[i, 11 - n, 0] = out_re1[n] + out_im1[n]; + X_hybrid[i, 11 - n, 1] = out_re2[n] - out_im2[n]; + } + } + } + + public void HybridSynthesis(float[,,] X, float[,,] X_hybrid, bool use34, int numTimeSlotsRate) + { + int k, n, band; + int offset = 0; + int qmf_bands = use34 ? 5 : 3; + int[] resolution = use34 ? _resolution34 : _resolution20; + + for (band = 0; band < qmf_bands; band++) + { + for (n = 0; n < _frameLen; n++) + { + X[n, band, 0] = 0; + X[n, band, 1] = 0; + + for (k = 0; k < resolution[band]; k++) + { + X[n, band, 0] += X_hybrid[n, offset + k, 0]; + X[n, band, 1] += X_hybrid[n, offset + k, 1]; + } + } + offset += resolution[band]; + } + } + } +} diff --git a/SharpJaad.AAC/Ps/HuffmanTables.cs b/SharpJaad.AAC/Ps/HuffmanTables.cs new file mode 100644 index 0000000..81a8474 --- /dev/null +++ b/SharpJaad.AAC/Ps/HuffmanTables.cs @@ -0,0 +1,258 @@ +namespace SharpJaad.AAC.Ps +{ + public static class HuffmanTables + { + /* binary lookup huffman tables */ + public static int[][] f_huff_iid_def = { + new int[] { /*0*/-31, 1}, /* index 0: 1 bits: x */ + new int[] {2, 3}, /* index 1: 2 bits: 1x */ + new int[] { /*1*/-30, /*-1*/ -32}, /* index 2: 3 bits: 10x */ + new int[] {4, 5}, /* index 3: 3 bits: 11x */ + new int[] { /*2*/-29, /*-2*/ -33}, /* index 4: 4 bits: 110x */ + new int[] {6, 7}, /* index 5: 4 bits: 111x */ + new int[] { /*3*/-28, /*-3*/ -34}, /* index 6: 5 bits: 1110x */ + new int[] {8, 9}, /* index 7: 5 bits: 1111x */ + new int[] { /*-4*/-35, /*4*/ -27}, /* index 8: 6 bits: 11110x */ + new int[] { /*5*/-26, 10}, /* index 9: 6 bits: 11111x */ + new int[] { /*-5*/-36, 11}, /* index 10: 7 bits: 111111x */ + new int[] { /*6*/-25, 12}, /* index 11: 8 bits: 1111111x */ + new int[] { /*-6*/-37, 13}, /* index 12: 9 bits: 11111111x */ + new int[] { /*-7*/-38, 14}, /* index 13: 10 bits: 111111111x */ + new int[] { /*7*/-24, 15}, /* index 14: 11 bits: 1111111111x */ + new int[] {16, 17}, /* index 15: 12 bits: 11111111111x */ + new int[] { /*8*/-23, /*-8*/ -39}, /* index 16: 13 bits: 111111111110x */ + new int[] {18, 19}, /* index 17: 13 bits: 111111111111x */ + new int[] { /*9*/-22, /*10*/ -21}, /* index 18: 14 bits: 1111111111110x */ + new int[] {20, 21}, /* index 19: 14 bits: 1111111111111x */ + new int[] { /*-9*/-40, /*11*/ -20}, /* index 20: 15 bits: 11111111111110x */ + new int[] {22, 23}, /* index 21: 15 bits: 11111111111111x */ + new int[] { /*-10*/-41, 24}, /* index 22: 16 bits: 111111111111110x */ + new int[] {25, 26}, /* index 23: 16 bits: 111111111111111x */ + new int[] { /*-11*/-42, /*-14*/ -45}, /* index 24: 17 bits: 1111111111111101x */ + new int[] { /*-13*/-44, /*-12*/ -43}, /* index 25: 17 bits: 1111111111111110x */ + new int[] { /*12*/-19, 27}, /* index 26: 17 bits: 1111111111111111x */ + new int[] { /*13*/-18, /*14*/ -17} /* index 27: 18 bits: 11111111111111111x */}; + + public static int[][] t_huff_iid_def = { + new int[] { /*0*/-31, 1}, /* index 0: 1 bits: x */ + new int[] { /*-1*/-32, 2}, /* index 1: 2 bits: 1x */ + new int[] { /*1*/-30, 3}, /* index 2: 3 bits: 11x */ + new int[] { /*-2*/-33, 4}, /* index 3: 4 bits: 111x */ + new int[] { /*2*/-29, 5}, /* index 4: 5 bits: 1111x */ + new int[] { /*-3*/-34, 6}, /* index 5: 6 bits: 11111x */ + new int[] { /*3*/-28, 7}, /* index 6: 7 bits: 111111x */ + new int[] { /*-4*/-35, 8}, /* index 7: 8 bits: 1111111x */ + new int[] { /*4*/-27, 9}, /* index 8: 9 bits: 11111111x */ + new int[] { /*-5*/-36, 10}, /* index 9: 10 bits: 111111111x */ + new int[] { /*5*/-26, 11}, /* index 10: 11 bits: 1111111111x */ + new int[] { /*-6*/-37, 12}, /* index 11: 12 bits: 11111111111x */ + new int[] { /*6*/-25, 13}, /* index 12: 13 bits: 111111111111x */ + new int[] { /*7*/-24, 14}, /* index 13: 14 bits: 1111111111111x */ + new int[] { /*-7*/-38, 15}, /* index 14: 15 bits: 11111111111111x */ + new int[] {16, 17}, /* index 15: 16 bits: 111111111111111x */ + new int[] { /*8*/-23, /*-8*/ -39}, /* index 16: 17 bits: 1111111111111110x */ + new int[] {18, 19}, /* index 17: 17 bits: 1111111111111111x */ + new int[] {20, 21}, /* index 18: 18 bits: 11111111111111110x */ + new int[] {22, 23}, /* index 19: 18 bits: 11111111111111111x */ + new int[] { /*9*/-22, /*-14*/ -45}, /* index 20: 19 bits: 111111111111111100x */ + new int[] { /*-13*/-44, /*-12*/ -43}, /* index 21: 19 bits: 111111111111111101x */ + new int[] {24, 25}, /* index 22: 19 bits: 111111111111111110x */ + new int[] {26, 27}, /* index 23: 19 bits: 111111111111111111x */ + new int[] { /*-11*/-42, /*-10*/ -41}, /* index 24: 20 bits: 1111111111111111100x */ + new int[] { /*-9*/-40, /*10*/ -21}, /* index 25: 20 bits: 1111111111111111101x */ + new int[] { /*11*/-20, /*12*/ -19}, /* index 26: 20 bits: 1111111111111111110x */ + new int[] { -18, -17 } /* index 27: 20 bits: 1111111111111111111x */}; + + public static int[][] f_huff_iid_fine = { + new int[] {1, /*0*/ -31}, /* index 0: 1 bits: x */ + new int[] {2, 3}, /* index 1: 2 bits: 0x */ + new int[] {4, /*-1*/ -32}, /* index 2: 3 bits: 00x */ + new int[] { /*1*/-30, 5}, /* index 3: 3 bits: 01x */ + new int[] { /*-2*/-33, /*2*/ -29}, /* index 4: 4 bits: 000x */ + new int[] {6, 7}, /* index 5: 4 bits: 011x */ + new int[] { /*-3*/-34, /*3*/ -28}, /* index 6: 5 bits: 0110x */ + new int[] {8, 9}, /* index 7: 5 bits: 0111x */ + new int[] { /*-4*/-35, /*4*/ -27}, /* index 8: 6 bits: 01110x */ + new int[] {10, 11}, /* index 9: 6 bits: 01111x */ + new int[] { /*-5*/-36, /*5*/ -26}, /* index 10: 7 bits: 011110x */ + new int[] {12, 13}, /* index 11: 7 bits: 011111x */ + new int[] { /*-6*/-37, /*6*/ -25}, /* index 12: 8 bits: 0111110x */ + new int[] {14, 15}, /* index 13: 8 bits: 0111111x */ + new int[] { /*7*/-24, 16}, /* index 14: 9 bits: 01111110x */ + new int[] {17, 18}, /* index 15: 9 bits: 01111111x */ + new int[] {19, /*-8*/ -39}, /* index 16: 10 bits: 011111101x */ + new int[] { /*8*/-23, 20}, /* index 17: 10 bits: 011111110x */ + new int[] {21, /*-7*/ -38}, /* index 18: 10 bits: 011111111x */ + new int[] { /*10*/-21, 22}, /* index 19: 11 bits: 0111111010x */ + new int[] {23, /*-9*/ -40}, /* index 20: 11 bits: 0111111101x */ + new int[] { /*9*/-22, 24}, /* index 21: 11 bits: 0111111110x */ + new int[] { /*-11*/-42, /*11*/ -20}, /* index 22: 12 bits: 01111110101x */ + new int[] {25, 26}, /* index 23: 12 bits: 01111111010x */ + new int[] {27, /*-10*/ -41}, /* index 24: 12 bits: 01111111101x */ + new int[] {28, /*-12*/ -43}, /* index 25: 13 bits: 011111110100x */ + new int[] { /*12*/-19, 29}, /* index 26: 13 bits: 011111110101x */ + new int[] {30, 31}, /* index 27: 13 bits: 011111111010x */ + new int[] {32, /*-14*/ -45}, /* index 28: 14 bits: 0111111101000x */ + new int[] { /*14*/-17, 33}, /* index 29: 14 bits: 0111111101011x */ + new int[] {34, /*-13*/ -44}, /* index 30: 14 bits: 0111111110100x */ + new int[] { /*13*/-18, 35}, /* index 31: 14 bits: 0111111110101x */ + new int[] {36, 37}, /* index 32: 15 bits: 01111111010000x */ + new int[] {38, /*-15*/ -46}, /* index 33: 15 bits: 01111111010111x */ + new int[] { /*15*/-16, 39}, /* index 34: 15 bits: 01111111101000x */ + new int[] {40, 41}, /* index 35: 15 bits: 01111111101011x */ + new int[] {42, 43}, /* index 36: 16 bits: 011111110100000x */ + new int[] { /*-17*/-48, /*17*/ -14}, /* index 37: 16 bits: 011111110100001x */ + new int[] {44, 45}, /* index 38: 16 bits: 011111110101110x */ + new int[] {46, 47}, /* index 39: 16 bits: 011111111010001x */ + new int[] {48, 49}, /* index 40: 16 bits: 011111111010110x */ + new int[] { /*-16*/-47, /*16*/ -15}, /* index 41: 16 bits: 011111111010111x */ + new int[] { /*-21*/-52, /*21*/ -10}, /* index 42: 17 bits: 0111111101000000x */ + new int[] { /*-19*/-50, /*19*/ -12}, /* index 43: 17 bits: 0111111101000001x */ + new int[] { /*-18*/-49, /*18*/ -13}, /* index 44: 17 bits: 0111111101011100x */ + new int[] {50, 51}, /* index 45: 17 bits: 0111111101011101x */ + new int[] {52, 53}, /* index 46: 17 bits: 0111111110100010x */ + new int[] {54, 55}, /* index 47: 17 bits: 0111111110100011x */ + new int[] {56, 57}, /* index 48: 17 bits: 0111111110101100x */ + new int[] {58, 59}, /* index 49: 17 bits: 0111111110101101x */ + new int[] { /*-26*/-57, /*-25*/ -56}, /* index 50: 18 bits: 01111111010111010x */ + new int[] { /*-28*/-59, /*-27*/ -58}, /* index 51: 18 bits: 01111111010111011x */ + new int[] { /*-22*/-53, /*22*/ -9}, /* index 52: 18 bits: 01111111101000100x */ + new int[] { /*-24*/-55, /*-23*/ -54}, /* index 53: 18 bits: 01111111101000101x */ + new int[] { /*25*/-6, /*26*/ -5}, /* index 54: 18 bits: 01111111101000110x */ + new int[] { /*23*/-8, /*24*/ -7}, /* index 55: 18 bits: 01111111101000111x */ + new int[] { /*29*/-2, /*30*/ -1}, /* index 56: 18 bits: 01111111101011000x */ + new int[] { /*27*/-4, /*28*/ -3}, /* index 57: 18 bits: 01111111101011001x */ + new int[] { /*-30*/-61, /*-29*/ -60}, /* index 58: 18 bits: 01111111101011010x */ + new int[] { -51, -11 } /* index 59: 18 bits: 01111111101011011x */}; + + public static int[][] t_huff_iid_fine = { + new int[] {1, /*0*/ -31}, /* index 0: 1 bits: x */ + new int[] { /*1*/-30, 2}, /* index 1: 2 bits: 0x */ + new int[] {3, /*-1*/ -32}, /* index 2: 3 bits: 01x */ + new int[] {4, 5}, /* index 3: 4 bits: 010x */ + new int[] {6, 7}, /* index 4: 5 bits: 0100x */ + new int[] { /*-2*/-33, /*2*/ -29}, /* index 5: 5 bits: 0101x */ + new int[] {8, /*-3*/ -34}, /* index 6: 6 bits: 01000x */ + new int[] { /*3*/-28, 9}, /* index 7: 6 bits: 01001x */ + new int[] { /*-4*/-35, /*4*/ -27}, /* index 8: 7 bits: 010000x */ + new int[] {10, 11}, /* index 9: 7 bits: 010011x */ + new int[] { /*5*/-26, 12}, /* index 10: 8 bits: 0100110x */ + new int[] {13, 14}, /* index 11: 8 bits: 0100111x */ + new int[] { /*-6*/-37, /*6*/ -25}, /* index 12: 9 bits: 01001101x */ + new int[] {15, 16}, /* index 13: 9 bits: 01001110x */ + new int[] {17, /*-5*/ -36}, /* index 14: 9 bits: 01001111x */ + new int[] {18, /*-7*/ -38}, /* index 15: 10 bits: 010011100x */ + new int[] { /*7*/-24, 19}, /* index 16: 10 bits: 010011101x */ + new int[] {20, 21}, /* index 17: 10 bits: 010011110x */ + new int[] { /*9*/-22, 22}, /* index 18: 11 bits: 0100111000x */ + new int[] {23, 24}, /* index 19: 11 bits: 0100111011x */ + new int[] { /*-8*/-39, /*8*/ -23}, /* index 20: 11 bits: 0100111100x */ + new int[] {25, 26}, /* index 21: 11 bits: 0100111101x */ + new int[] { /*11*/-20, 27}, /* index 22: 12 bits: 01001110001x */ + new int[] {28, 29}, /* index 23: 12 bits: 01001110110x */ + new int[] { /*-10*/-41, /*10*/ -21}, /* index 24: 12 bits: 01001110111x */ + new int[] {30, 31}, /* index 25: 12 bits: 01001111010x */ + new int[] {32, /*-9*/ -40}, /* index 26: 12 bits: 01001111011x */ + new int[] {33, /*-13*/ -44}, /* index 27: 13 bits: 010011100011x */ + new int[] { /*13*/-18, 34}, /* index 28: 13 bits: 010011101100x */ + new int[] {35, 36}, /* index 29: 13 bits: 010011101101x */ + new int[] {37, /*-12*/ -43}, /* index 30: 13 bits: 010011110100x */ + new int[] { /*12*/-19, 38}, /* index 31: 13 bits: 010011110101x */ + new int[] {39, /*-11*/ -42}, /* index 32: 13 bits: 010011110110x */ + new int[] {40, 41}, /* index 33: 14 bits: 0100111000110x */ + new int[] {42, 43}, /* index 34: 14 bits: 0100111011001x */ + new int[] {44, 45}, /* index 35: 14 bits: 0100111011010x */ + new int[] {46, /*-15*/ -46}, /* index 36: 14 bits: 0100111011011x */ + new int[] { /*15*/-16, 47}, /* index 37: 14 bits: 0100111101000x */ + new int[] { /*-14*/-45, /*14*/ -17}, /* index 38: 14 bits: 0100111101011x */ + new int[] {48, 49}, /* index 39: 14 bits: 0100111101100x */ + new int[] { /*-21*/-52, /*-20*/ -51}, /* index 40: 15 bits: 01001110001100x */ + new int[] { /*18*/-13, /*19*/ -12}, /* index 41: 15 bits: 01001110001101x */ + new int[] { /*-19*/-50, /*-18*/ -49}, /* index 42: 15 bits: 01001110110010x */ + new int[] {50, 51}, /* index 43: 15 bits: 01001110110011x */ + new int[] {52, 53}, /* index 44: 15 bits: 01001110110100x */ + new int[] {54, 55}, /* index 45: 15 bits: 01001110110101x */ + new int[] {56, /*-17*/ -48}, /* index 46: 15 bits: 01001110110110x */ + new int[] { /*17*/-14, 57}, /* index 47: 15 bits: 01001111010001x */ + new int[] {58, /*-16*/ -47}, /* index 48: 15 bits: 01001111011000x */ + new int[] { /*16*/-15, 59}, /* index 49: 15 bits: 01001111011001x */ + new int[] { /*-26*/-57, /*26*/ -5}, /* index 50: 16 bits: 010011101100110x */ + new int[] { /*-28*/-59, /*-27*/ -58}, /* index 51: 16 bits: 010011101100111x */ + new int[] { /*29*/-2, /*30*/ -1}, /* index 52: 16 bits: 010011101101000x */ + new int[] { /*27*/-4, /*28*/ -3}, /* index 53: 16 bits: 010011101101001x */ + new int[] { /*-30*/-61, /*-29*/ -60}, /* index 54: 16 bits: 010011101101010x */ + new int[] { /*-25*/-56, /*25*/ -6}, /* index 55: 16 bits: 010011101101011x */ + new int[] { /*-24*/-55, /*24*/ -7}, /* index 56: 16 bits: 010011101101100x */ + new int[] { /*-23*/-54, /*23*/ -8}, /* index 57: 16 bits: 010011110100011x */ + new int[] { /*-22*/-53, /*22*/ -9}, /* index 58: 16 bits: 010011110110000x */ + new int[] { -11, -10 } /* index 59: 16 bits: 010011110110011x */}; + + public static int[][] f_huff_icc = { + new int[] { /*0*/-31, 1}, /* index 0: 1 bits: x */ + new int[] { /*1*/-30, 2}, /* index 1: 2 bits: 1x */ + new int[] { /*-1*/-32, 3}, /* index 2: 3 bits: 11x */ + new int[] { /*2*/-29, 4}, /* index 3: 4 bits: 111x */ + new int[] { /*-2*/-33, 5}, /* index 4: 5 bits: 1111x */ + new int[] { /*3*/-28, 6}, /* index 5: 6 bits: 11111x */ + new int[] { /*-3*/-34, 7}, /* index 6: 7 bits: 111111x */ + new int[] { /*4*/-27, 8}, /* index 7: 8 bits: 1111111x */ + new int[] { /*5*/-26, 9}, /* index 8: 9 bits: 11111111x */ + new int[] { /*-4*/-35, 10}, /* index 9: 10 bits: 111111111x */ + new int[] { /*6*/-25, 11}, /* index 10: 11 bits: 1111111111x */ + new int[] { /*-5*/-36, 12}, /* index 11: 12 bits: 11111111111x */ + new int[] { /*7*/-24, 13}, /* index 12: 13 bits: 111111111111x */ + new int[] { -37, -38 } /* index 13: 14 bits: 1111111111111x */}; + + public static int[][] t_huff_icc = { + new int[] { /*0*/-31, 1}, /* index 0: 1 bits: x */ + new int[] { /*1*/-30, 2}, /* index 1: 2 bits: 1x */ + new int[] { /*-1*/-32, 3}, /* index 2: 3 bits: 11x */ + new int[] { /*2*/-29, 4}, /* index 3: 4 bits: 111x */ + new int[] { /*-2*/-33, 5}, /* index 4: 5 bits: 1111x */ + new int[] { /*3*/-28, 6}, /* index 5: 6 bits: 11111x */ + new int[] { /*-3*/-34, 7}, /* index 6: 7 bits: 111111x */ + new int[] { /*4*/-27, 8}, /* index 7: 8 bits: 1111111x */ + new int[] { /*-4*/-35, 9}, /* index 8: 9 bits: 11111111x */ + new int[] { /*5*/-26, 10}, /* index 9: 10 bits: 111111111x */ + new int[] { /*-5*/-36, 11}, /* index 10: 11 bits: 1111111111x */ + new int[] { /*6*/-25, 12}, /* index 11: 12 bits: 11111111111x */ + new int[] { /*-6*/-37, 13}, /* index 12: 13 bits: 111111111111x */ + new int[] { -38, -24 } /* index 13: 14 bits: 1111111111111x */}; + + public static int[][] f_huff_ipd = { + new int[] {1, /*0*/ -31}, /* index 0: 1 bits: x */ + new int[] {2, 3}, /* index 1: 2 bits: 0x */ + new int[] { /*1*/-30, 4}, /* index 2: 3 bits: 00x */ + new int[] {5, 6}, /* index 3: 3 bits: 01x */ + new int[] { /*4*/-27, /*5*/ -26}, /* index 4: 4 bits: 001x */ + new int[] { /*3*/-28, /*6*/ -25}, /* index 5: 4 bits: 010x */ + new int[] { /*2*/-29, /*7*/ -24} /* index 6: 4 bits: 011x */}; + + public static int[][] t_huff_ipd = { + new int[] {1, /*0*/ -31}, /* index 0: 1 bits: x */ + new int[] {2, 3}, /* index 1: 2 bits: 0x */ + new int[] {4, 5}, /* index 2: 3 bits: 00x */ + new int[] { /*1*/-30, /*7*/ -24}, /* index 3: 3 bits: 01x */ + new int[] { /*5*/-26, 6}, /* index 4: 4 bits: 000x */ + new int[] { /*2*/-29, /*6*/ -25}, /* index 5: 4 bits: 001x */ + new int[] { /*4*/-27, /*3*/ -28} /* index 6: 5 bits: 0001x */}; + + public static int[][] f_huff_opd = { + new int[] {1, /*0*/ -31}, /* index 0: 1 bits: x */ + new int[] {2, 3}, /* index 1: 2 bits: 0x */ + new int[] { /*7*/-24, /*1*/ -30}, /* index 2: 3 bits: 00x */ + new int[] {4, 5}, /* index 3: 3 bits: 01x */ + new int[] { /*3*/-28, /*6*/ -25}, /* index 4: 4 bits: 010x */ + new int[] { /*2*/-29, 6}, /* index 5: 4 bits: 011x */ + new int[] { /*5*/-26, /*4*/ -27} /* index 6: 5 bits: 0111x */}; + + public static int[][] t_huff_opd = { + new int[] {1, /*0*/ -31}, /* index 0: 1 bits: x */ + new int[] {2, 3}, /* index 1: 2 bits: 0x */ + new int[] {4, 5}, /* index 2: 3 bits: 00x */ + new int[] { /*1*/-30, /*7*/ -24}, /* index 3: 3 bits: 01x */ + new int[] { /*5*/-26, /*2*/ -29}, /* index 4: 4 bits: 000x */ + new int[] { /*6*/-25, 6}, /* index 5: 4 bits: 001x */ + new int[] { /*4*/-27, /*3*/ -28} /* index 6: 5 bits: 0011x */}; + } +} diff --git a/SharpJaad.AAC/Ps/PS.cs b/SharpJaad.AAC/Ps/PS.cs new file mode 100644 index 0000000..d8a4fff --- /dev/null +++ b/SharpJaad.AAC/Ps/PS.cs @@ -0,0 +1,1466 @@ +using SharpJaad.AAC.Syntax; +using System; + +namespace SharpJaad.AAC.Ps +{ + public class PS + { + /* bitstream parameters */ + private bool _enableIid, _enableIcc, _enableExt; + private int _iidMode; + private int _iccMode; + private int _nrIidPar; + private int _nrIpdopdPar; + private int _nrIccPar; + private int _frameClass; + private int _numEnv; + private int[] _borderPosition = new int[PSConstants.MAX_PS_ENVELOPES + 1]; + private bool[] _iidDt = new bool[PSConstants.MAX_PS_ENVELOPES]; + private bool[] _iccDt = new bool[PSConstants.MAX_PS_ENVELOPES]; + private bool _enableIpdopd; + private int _ipdMode; + private bool[] _ipdDt = new bool[PSConstants.MAX_PS_ENVELOPES]; + private bool[] _opdDt = new bool[PSConstants.MAX_PS_ENVELOPES]; + + /* indices */ + private int[] _iidIndexPrev = new int[34]; + private int[] _iccIndexPrev = new int[34]; + private int[] _ipdIndexPrev = new int[17]; + private int[] _opdIndexPrev = new int[17]; + private int[][] _iidIndex = new int[PSConstants.MAX_PS_ENVELOPES][]; + private int[][] _iccIndex = new int[PSConstants.MAX_PS_ENVELOPES][]; + private int[][] _ipdIndex = new int[PSConstants.MAX_PS_ENVELOPES][]; + private int[][] _opdIndex = new int[PSConstants.MAX_PS_ENVELOPES][]; + + private int[] _ipdIndex1 = new int[17]; + private int[] _opdIndex1 = new int[17]; + private int[] _ipdIndex2 = new int[17]; + private int[] _opdIndex2 = new int[17]; + /* ps data was correctly read */ + private int _psDataAvailable; + /* a header has been read */ + public bool _headerRead; + /* hybrid filterbank parameters */ + private Filterbank _hyb; + private bool _use34hybridBands; + private int _numTimeSlotsRate; + private int _numGroups; + private int _numHybridGroups; + private int _nrParBands; + private int _nrAllpassBands; + private int _decayCutoff; + private int[] _groupBorder; + private int[] _mapGroup2bk; + /* filter delay handling */ + private int _savedDelay; + private int[] _delayBufIndexSer = new int[PSConstants.NO_ALLPASS_LINKS]; + private int[] _numSampleDelaySer = new int[PSConstants.NO_ALLPASS_LINKS]; + private int[] _delayD = new int[64]; + private int[] _delayBufIndexDelay = new int[64]; + private float[,,] _delayQmf = new float[14, 64, 2]; /* 14 samples delay max, 64 QMF channels */ + + private float[,,] _delaySubQmf = new float[2, 32, 2]; /* 2 samples delay max (SubQmf is always allpass filtered) */ + + private float[,,,] _delayQmfSer = new float[PSConstants.NO_ALLPASS_LINKS, 5, 64, 2]; /* 5 samples delay max (table 8.34), 64 QMF channels */ + + private float[,,,] _delaySubQmfSer = new float[PSConstants.NO_ALLPASS_LINKS, 5, 32, 2]; /* 5 samples delay max (table 8.34) */ + /* transients */ + + private float _alphaDecay; + private float _alphaSmooth; + private float[] _P_PeakDecayNrg = new float[34]; + private float[] _P_prev = new float[34]; + private float[] _P_SmoothPeakDecayDiffNrg_prev = new float[34]; + /* mixing and phase */ + private float[,] _h11Prev = new float[50, 2]; + private float[,] _h12Prev = new float[50, 2]; + private float[,] _h21Prev = new float[50, 2]; + private float[,] _h22Prev = new float[50, 2]; + private int _phaseHist; + private float[,,] _ipdPrev = new float[20, 2, 2]; + private float[,,] _opdPrev = new float[20, 2, 2]; + + public PS(SampleFrequency sr, int numTimeSlotsRate) + { + int i; + int short_delay_band; + + for (i = 0; i < PSConstants.MAX_PS_ENVELOPES; i++) + { + _iidIndex[i] = new int[34]; + _iccIndex[i] = new int[34]; + _ipdIndex[i] = new int[17]; + _opdIndex[i] = new int[17]; + } + + _hyb = new Filterbank(numTimeSlotsRate); + _numTimeSlotsRate = numTimeSlotsRate; + + _psDataAvailable = 0; + + /* delay stuff*/ + _savedDelay = 0; + + for (i = 0; i < 64; i++) + { + _delayBufIndexDelay[i] = 0; + } + + for (i = 0; i < PSConstants.NO_ALLPASS_LINKS; i++) + { + _delayBufIndexSer[i] = 0; + /* THESE ARE CONSTANTS NOW */ + _numSampleDelaySer[i] = PSTables.delay_length_d[i]; + } + + /* THESE ARE CONSTANTS NOW */ + short_delay_band = 35; + _nrAllpassBands = 22; + _alphaDecay = 0.76592833836465f; + _alphaSmooth = 0.25f; + + /* THESE ARE CONSTANT NOW IF PS IS INDEPENDANT OF SAMPLERATE */ + for (i = 0; i < short_delay_band; i++) + { + _delayD[i] = 14; + } + for (i = short_delay_band; i < 64; i++) + { + _delayD[i] = 1; + } + + /* mixing and phase */ + for (i = 0; i < 50; i++) + { + _h11Prev[i, 0] = 1; + _h12Prev[i, 1] = 1; + _h11Prev[i, 0] = 1; + _h12Prev[i, 1] = 1; + } + + _phaseHist = 0; + + for (i = 0; i < 20; i++) + { + _ipdPrev[i, 0, 0] = 0; + _ipdPrev[i, 0, 1] = 0; + _ipdPrev[i, 1, 0] = 0; + _ipdPrev[i, 1, 1] = 0; + _opdPrev[i, 0, 0] = 0; + _opdPrev[i, 0, 1] = 0; + _opdPrev[i, 1, 0] = 0; + _opdPrev[i, 1, 1] = 0; + } + } + + public int Decode(BitStream ld) + { + int tmp, n; + long bits = ld.GetPosition(); + + /* check for new PS header */ + if (ld.ReadBool()) + { + _headerRead = true; + + _use34hybridBands = false; + + /* Inter-channel Intensity Difference (IID) parameters enabled */ + _enableIid = ld.ReadBool(); + + if (_enableIid) + { + _iidMode = ld.ReadBits(3); + + _nrIidPar = PSTables.nr_iid_par_tab[_iidMode]; + _nrIpdopdPar = PSTables.nr_ipdopd_par_tab[_iidMode]; + + if (_iidMode == 2 || _iidMode == 5) + _use34hybridBands = true; + + /* IPD freq res equal to IID freq res */ + _ipdMode = _iidMode; + } + + /* Inter-channel Coherence (ICC) parameters enabled */ + _enableIcc = ld.ReadBool(); + + if (_enableIcc) + { + _iccMode = ld.ReadBits(3); + + _nrIccPar = PSTables.nr_icc_par_tab[_iccMode]; + + if (_iccMode == 2 || _iccMode == 5) + _use34hybridBands = true; + } + + /* PS extension layer enabled */ + _enableExt = ld.ReadBool(); + } + + /* we are here, but no header has been read yet */ + if (_headerRead == false) + { + _psDataAvailable = 0; + return 1; + } + + _frameClass = ld.ReadBit(); + tmp = ld.ReadBits(2); + + _numEnv = PSTables.num_env_tab[_frameClass][tmp]; + + if (_frameClass != 0) + { + for (n = 1; n < _numEnv + 1; n++) + { + _borderPosition[n] = ld.ReadBits(5) + 1; + } + } + + if (_enableIid) + { + for (n = 0; n < _numEnv; n++) + { + _iidDt[n] = ld.ReadBool(); + + /* iid_data */ + if (_iidMode < 3) + { + HuffData(ld, _iidDt[n], _nrIidPar, HuffmanTables.t_huff_iid_def, + HuffmanTables.f_huff_iid_def, _iidIndex[n]); + } + else + { + HuffData(ld, _iidDt[n], _nrIidPar, HuffmanTables.t_huff_iid_fine, + HuffmanTables.f_huff_iid_fine, _iidIndex[n]); + } + } + } + + if (_enableIcc) + { + for (n = 0; n < _numEnv; n++) + { + _iccDt[n] = ld.ReadBool(); + + /* icc_data */ + HuffData(ld, _iccDt[n], _nrIccPar, HuffmanTables.t_huff_icc, + HuffmanTables.f_huff_icc, _iccIndex[n]); + } + } + + if (_enableExt) + { + int num_bits_left; + int cnt = ld.ReadBits(4); + if (cnt == 15) + { + cnt += ld.ReadBits(8); + } + + num_bits_left = 8 * cnt; + while (num_bits_left > 7) + { + int ps_extension_id = ld.ReadBits(2); + + num_bits_left -= 2; + num_bits_left -= PsExtension(ld, ps_extension_id, num_bits_left); + } + + ld.SkipBits(num_bits_left); + } + + int bits2 = (int)(ld.GetPosition() - bits); + + _psDataAvailable = 1; + + return bits2; + } + + private int PsExtension(BitStream ld, int ps_extension_id, int num_bits_left) + { + int n; + long bits = ld.GetPosition(); + + if (ps_extension_id == 0) + { + _enableIpdopd = ld.ReadBool(); + + if (_enableIpdopd) + { + for (n = 0; n < _numEnv; n++) + { + _ipdDt[n] = ld.ReadBool(); + + /* ipd_data */ + HuffData(ld, _ipdDt[n], _nrIpdopdPar, HuffmanTables.t_huff_ipd, + HuffmanTables.f_huff_ipd, _ipdIndex[n]); + + _opdDt[n] = ld.ReadBool(); + + /* opd_data */ + HuffData(ld, _opdDt[n], _nrIpdopdPar, HuffmanTables.t_huff_opd, + HuffmanTables.f_huff_opd, _opdIndex[n]); + } + } + ld.ReadBit(); //reserved + } + + /* return number of bits read */ + int bits2 = (int)(ld.GetPosition() - bits); + + return bits2; + } + + /* read huffman data coded in either the frequency or the time direction */ + private void HuffData(BitStream ld, bool dt, int nr_par, int[][] t_huff, int[][] f_huff, int[] par) + { + int n; + + if (dt) + { + /* coded in time direction */ + for (n = 0; n < nr_par; n++) + { + par[n] = PsHuffDec(ld, t_huff); + } + } + else + { + /* coded in frequency direction */ + par[0] = PsHuffDec(ld, f_huff); + + for (n = 1; n < nr_par; n++) + { + par[n] = PsHuffDec(ld, f_huff); + } + } + } + + /* binary search huffman decoding */ + private int PsHuffDec(BitStream ld, int[][] t_huff) + { + int bit; + int index = 0; + + while (index >= 0) + { + bit = ld.ReadBit(); + index = t_huff[index][bit]; + } + + return index + 31; + } + + /* limits the value i to the range [min,max] */ + private int DeltaClip(int i, int min, int max) + { + if (i < min) return min; + else if (i > max) return max; + else return i; + } + + + /* delta decode array */ + private void DeltaDecode(bool enable, int[] index, int[] index_prev, bool dt_flag, int nr_par, int stride, int min_index, int max_index) + { + int i; + + if (enable) + { + if (!dt_flag) + { + /* delta coded in frequency direction */ + index[0] = 0 + index[0]; + index[0] = DeltaClip(index[0], min_index, max_index); + + for (i = 1; i < nr_par; i++) + { + index[i] = index[i - 1] + index[i]; + index[i] = DeltaClip(index[i], min_index, max_index); + } + } + else + { + /* delta coded in time direction */ + for (i = 0; i < nr_par; i++) + { + //int8_t tmp2; + //int8_t tmp = index[i]; + + //printf("%d %d\n", index_prev[i*stride], index[i]); + //printf("%d\n", index[i]); + index[i] = index_prev[i * stride] + index[i]; + //tmp2 = index[i]; + index[i] = DeltaClip(index[i], min_index, max_index); + + //if (iid) + //{ + // if (index[i] == 7) + // { + // printf("%d %d %d\n", index_prev[i*stride], tmp, tmp2); + // } + //} + } + } + } + else + { + /* set indices to zero */ + for (i = 0; i < nr_par; i++) + { + index[i] = 0; + } + } + + /* coarse */ + if (stride == 2) + { + for (i = (nr_par << 1) - 1; i > 0; i--) + { + index[i] = index[i >> 1]; + } + } + } + + /* delta modulo decode array */ + /* in: log2 value of the modulo value to allow using AND instead of MOD */ + private void DeltaModuloDecode(bool enable, int[] index, int[] index_prev, bool dt_flag, int nr_par, int stride, int and_modulo) + { + int i; + + if (enable) + { + if (!dt_flag) + { + /* delta coded in frequency direction */ + index[0] = 0 + index[0]; + index[0] &= and_modulo; + + for (i = 1; i < nr_par; i++) + { + index[i] = index[i - 1] + index[i]; + index[i] &= and_modulo; + } + } + else + { + /* delta coded in time direction */ + for (i = 0; i < nr_par; i++) + { + index[i] = index_prev[i * stride] + index[i]; + index[i] &= and_modulo; + } + } + } + else + { + /* set indices to zero */ + for (i = 0; i < nr_par; i++) + { + index[i] = 0; + } + } + + /* coarse */ + if (stride == 2) + { + index[0] = 0; + for (i = (nr_par << 1) - 1; i > 0; i--) + { + index[i] = index[i >> 1]; + } + } + } + + private void Map20IndexTo34(int[] index, int bins) + { + //index[0] = index[0]; + index[1] = (index[0] + index[1]) / 2; + index[2] = index[1]; + index[3] = index[2]; + index[4] = (index[2] + index[3]) / 2; + index[5] = index[3]; + index[6] = index[4]; + index[7] = index[4]; + index[8] = index[5]; + index[9] = index[5]; + index[10] = index[6]; + index[11] = index[7]; + index[12] = index[8]; + index[13] = index[8]; + index[14] = index[9]; + index[15] = index[9]; + index[16] = index[10]; + + if (bins == 34) + { + index[17] = index[11]; + index[18] = index[12]; + index[19] = index[13]; + index[20] = index[14]; + index[21] = index[14]; + index[22] = index[15]; + index[23] = index[15]; + index[24] = index[16]; + index[25] = index[16]; + index[26] = index[17]; + index[27] = index[17]; + index[28] = index[18]; + index[29] = index[18]; + index[30] = index[18]; + index[31] = index[18]; + index[32] = index[19]; + index[33] = index[19]; + } + } + + /* parse the bitstream data decoded in ps_data() */ + private void PsDataDecode() + { + int env, bin; + + /* ps data not available, use data from previous frame */ + if (_psDataAvailable == 0) + { + _numEnv = 0; + } + + for (env = 0; env < _numEnv; env++) + { + int[] iid_index_prev; + int[] icc_index_prev; + int[] ipd_index_prev; + int[] opd_index_prev; + + int num_iid_steps = _iidMode < 3 ? 7 : 15 /*fine quant*/; + + if (env == 0) + { + /* take last envelope from previous frame */ + iid_index_prev = _iidIndexPrev; + icc_index_prev = _iccIndexPrev; + ipd_index_prev = _ipdIndexPrev; + opd_index_prev = _opdIndexPrev; + } + else + { + /* take index values from previous envelope */ + iid_index_prev = _iidIndex[env - 1]; + icc_index_prev = _iccIndex[env - 1]; + ipd_index_prev = _ipdIndex[env - 1]; + opd_index_prev = _opdIndex[env - 1]; + } + + // iid = 1; + /* delta decode iid parameters */ + DeltaDecode(_enableIid, _iidIndex[env], iid_index_prev, + _iidDt[env], _nrIidPar, + _iidMode == 0 || _iidMode == 3 ? 2 : 1, + -num_iid_steps, num_iid_steps); + // iid = 0; + + /* delta decode icc parameters */ + DeltaDecode(_enableIcc, _iccIndex[env], icc_index_prev, + _iccDt[env], _nrIccPar, + _iccMode == 0 || _iccMode == 3 ? 2 : 1, + 0, 7); + + /* delta modulo decode ipd parameters */ + DeltaModuloDecode(_enableIpdopd, _ipdIndex[env], ipd_index_prev, + _ipdDt[env], _nrIpdopdPar, 1, 7); + + /* delta modulo decode opd parameters */ + DeltaModuloDecode(_enableIpdopd, _opdIndex[env], opd_index_prev, + _opdDt[env], _nrIpdopdPar, 1, 7); + } + + /* handle error case */ + if (_numEnv == 0) + { + /* force to 1 */ + _numEnv = 1; + + if (_enableIid) + { + for (bin = 0; bin < 34; bin++) + { + _iidIndex[0][bin] = _iidIndexPrev[bin]; + } + } + else + { + for (bin = 0; bin < 34; bin++) + { + _iidIndex[0][bin] = 0; + } + } + + if (_enableIcc) + { + for (bin = 0; bin < 34; bin++) + { + _iccIndex[0][bin] = _iccIndexPrev[bin]; + } + } + else + { + for (bin = 0; bin < 34; bin++) + { + _iccIndex[0][bin] = 0; + } + } + + if (_enableIpdopd) + { + for (bin = 0; bin < 17; bin++) + { + _ipdIndex[0][bin] = _ipdIndexPrev[bin]; + _opdIndex[0][bin] = _opdIndexPrev[bin]; + } + } + else + { + for (bin = 0; bin < 17; bin++) + { + _ipdIndex[0][bin] = 0; + _opdIndex[0][bin] = 0; + } + } + } + + /* update previous indices */ + for (bin = 0; bin < 34; bin++) + { + _iidIndexPrev[bin] = _iidIndex[_numEnv - 1][bin]; + } + for (bin = 0; bin < 34; bin++) + { + _iccIndexPrev[bin] = _iccIndex[_numEnv - 1][bin]; + } + for (bin = 0; bin < 17; bin++) + { + _ipdIndexPrev[bin] = _ipdIndex[_numEnv - 1][bin]; + _opdIndexPrev[bin] = _opdIndex[_numEnv - 1][bin]; + } + + _psDataAvailable = 0; + + if (_frameClass == 0) + { + _borderPosition[0] = 0; + for (env = 1; env < _numEnv; env++) + { + _borderPosition[env] = env * _numTimeSlotsRate / _numEnv; + } + _borderPosition[_numEnv] = _numTimeSlotsRate; + } + else + { + _borderPosition[0] = 0; + + if (_borderPosition[_numEnv] < _numTimeSlotsRate) + { + for (bin = 0; bin < 34; bin++) + { + _iidIndex[_numEnv][bin] = _iidIndex[_numEnv - 1][bin]; + _iccIndex[_numEnv][bin] = _iccIndex[_numEnv - 1][bin]; + } + for (bin = 0; bin < 17; bin++) + { + _ipdIndex[_numEnv][bin] = _ipdIndex[_numEnv - 1][bin]; + _opdIndex[_numEnv][bin] = _opdIndex[_numEnv - 1][bin]; + } + _numEnv++; + _borderPosition[_numEnv] = _numTimeSlotsRate; + } + + for (env = 1; env < _numEnv; env++) + { + int thr = _numTimeSlotsRate - (_numEnv - env); + + if (_borderPosition[env] > thr) + { + _borderPosition[env] = thr; + } + else + { + thr = _borderPosition[env - 1] + 1; + if (_borderPosition[env] < thr) + { + _borderPosition[env] = thr; + } + } + } + } + + /* make sure that the indices of all parameters can be mapped + * to the same hybrid synthesis filterbank + */ + if (_use34hybridBands) + { + for (env = 0; env < _numEnv; env++) + { + if (_iidMode != 2 && _iidMode != 5) + Map20IndexTo34(_iidIndex[env], 34); + if (_iccMode != 2 && _iccMode != 5) + Map20IndexTo34(_iccIndex[env], 34); + if (_ipdMode != 2 && _ipdMode != 5) + { + Map20IndexTo34(_ipdIndex[env], 17); + Map20IndexTo34(_opdIndex[env], 17); + } + } + } + } + + /* decorrelate the mono signal using an allpass filter */ + private void PsDecorrelate(float[,,] X_left, float[,,] X_right, float[,,] X_hybrid_left, float[,,] X_hybrid_right) + { + int gr, n, m, bk; + int temp_delay = 0; + int sb, maxsb; + int[] temp_delay_ser = new int[PSConstants.NO_ALLPASS_LINKS]; + float P_SmoothPeakDecayDiffNrg, nrg; + float[,] P = new float[32, 34]; + float[,] G_TransientRatio = new float[32, 34]; + float[] inputLeft = new float[2]; + + + /* chose hybrid filterbank: 20 or 34 band case */ + float[][] Phi_Fract_SubQmf; + if (_use34hybridBands) + { + Phi_Fract_SubQmf = PSTables.Phi_Fract_SubQmf34; + } + else + { + Phi_Fract_SubQmf = PSTables.Phi_Fract_SubQmf20; + } + + /* clear the energy values */ + for (n = 0; n < 32; n++) + { + for (bk = 0; bk < 34; bk++) + { + P[n, bk] = 0; + } + } + + /* calculate the energy in each parameter band b(k) */ + for (gr = 0; gr < _numGroups; gr++) + { + /* select the parameter index b(k) to which this group belongs */ + bk = ~PSConstants.NEGATE_IPD_MASK & _mapGroup2bk[gr]; + + /* select the upper subband border for this group */ + maxsb = gr < _numHybridGroups ? _groupBorder[gr] + 1 : _groupBorder[gr + 1]; + + for (sb = _groupBorder[gr]; sb < maxsb; sb++) + { + for (n = _borderPosition[0]; n < _borderPosition[_numEnv]; n++) + { + + /* input from hybrid subbands or QMF subbands */ + if (gr < _numHybridGroups) + { + inputLeft[0] = X_hybrid_left[n, sb, 0]; + inputLeft[1] = X_hybrid_left[n, sb, 1]; + } + else + { + inputLeft[0] = X_left[n, sb, 0]; + inputLeft[1] = X_left[n, sb, 1]; + } + + /* accumulate energy */ + P[n, bk] += inputLeft[0] * inputLeft[0] + inputLeft[1] * inputLeft[1]; + } + } + } + + /* calculate transient reduction ratio for each parameter band b(k) */ + for (bk = 0; bk < _nrParBands; bk++) + { + for (n = _borderPosition[0]; n < _borderPosition[_numEnv]; n++) + { + float gamma = 1.5f; + + _P_PeakDecayNrg[bk] = _P_PeakDecayNrg[bk] * _alphaDecay; + if (_P_PeakDecayNrg[bk] < P[n, bk]) + _P_PeakDecayNrg[bk] = P[n, bk]; + + /* apply smoothing filter to peak decay energy */ + P_SmoothPeakDecayDiffNrg = _P_SmoothPeakDecayDiffNrg_prev[bk]; + P_SmoothPeakDecayDiffNrg += (_P_PeakDecayNrg[bk] - P[n, bk] - _P_SmoothPeakDecayDiffNrg_prev[bk]) * _alphaSmooth; + _P_SmoothPeakDecayDiffNrg_prev[bk] = P_SmoothPeakDecayDiffNrg; + + /* apply smoothing filter to energy */ + nrg = _P_prev[bk]; + nrg += (P[n, bk] - _P_prev[bk]) * _alphaSmooth; + _P_prev[bk] = nrg; + + /* calculate transient ratio */ + if (P_SmoothPeakDecayDiffNrg * gamma <= nrg) + { + G_TransientRatio[n, bk] = 1.0f; + } + else + { + G_TransientRatio[n, bk] = nrg / (P_SmoothPeakDecayDiffNrg * gamma); + } + } + } + + /* apply stereo decorrelation filter to the signal */ + for (gr = 0; gr < _numGroups; gr++) + { + if (gr < _numHybridGroups) + maxsb = _groupBorder[gr] + 1; + else + maxsb = _groupBorder[gr + 1]; + + /* QMF channel */ + for (sb = _groupBorder[gr]; sb < maxsb; sb++) + { + float g_DecaySlope; + float[] g_DecaySlope_filt = new float[PSConstants.NO_ALLPASS_LINKS]; + + /* g_DecaySlope: [0..1] */ + if (gr < _numHybridGroups || sb <= _decayCutoff) + { + g_DecaySlope = 1.0f; + } + else + { + int decay = _decayCutoff - sb; + if (decay <= -20 /* -1/DECAY_SLOPE */) + { + g_DecaySlope = 0; + } + else + { + /* decay(int)*decay_slope(frac) = g_DecaySlope(frac) */ + g_DecaySlope = 1.0f + PSConstants.DECAY_SLOPE * decay; + } + } + + /* calculate g_DecaySlope_filt for every m multiplied by filter_a[m] */ + for (m = 0; m < PSConstants.NO_ALLPASS_LINKS; m++) + { + g_DecaySlope_filt[m] = g_DecaySlope * PSTables.filter_a[m]; + } + + + /* set delay indices */ + temp_delay = _savedDelay; + for (n = 0; n < PSConstants.NO_ALLPASS_LINKS; n++) + { + temp_delay_ser[n] = _delayBufIndexSer[n]; + } + + for (n = _borderPosition[0]; n < _borderPosition[_numEnv]; n++) + { + float[] tmp = new float[2], tmp0 = new float[2], R0 = new float[2]; + + if (gr < _numHybridGroups) + { + /* hybrid filterbank input */ + inputLeft[0] = X_hybrid_left[n, sb, 0]; + inputLeft[1] = X_hybrid_left[n, sb, 1]; + } + else + { + /* QMF filterbank input */ + inputLeft[0] = X_left[n, sb, 0]; + inputLeft[1] = X_left[n, sb, 1]; + } + + if (sb > _nrAllpassBands && gr >= _numHybridGroups) + { + /* delay */ + + /* never hybrid subbands here, always QMF subbands */ + tmp[0] = _delayQmf[_delayBufIndexDelay[sb], sb, 0]; + tmp[1] = _delayQmf[_delayBufIndexDelay[sb], sb, 1]; + R0[0] = tmp[0]; + R0[1] = tmp[1]; + _delayQmf[_delayBufIndexDelay[sb], sb, 0] = inputLeft[0]; + _delayQmf[_delayBufIndexDelay[sb], sb, 1] = inputLeft[1]; + } + else + { + /* allpass filter */ + //int m; + float[] Phi_Fract = new float[2]; + + /* fetch parameters */ + if (gr < _numHybridGroups) + { + /* select data from the hybrid subbands */ + tmp0[0] = _delaySubQmf[temp_delay, sb, 0]; + tmp0[1] = _delaySubQmf[temp_delay, sb, 1]; + + _delaySubQmf[temp_delay, sb, 0] = inputLeft[0]; + _delaySubQmf[temp_delay, sb, 1] = inputLeft[1]; + + Phi_Fract[0] = Phi_Fract_SubQmf[sb][0]; + Phi_Fract[1] = Phi_Fract_SubQmf[sb][1]; + } + else + { + /* select data from the QMF subbands */ + tmp0[0] = _delayQmf[temp_delay, sb, 0]; + tmp0[1] = _delayQmf[temp_delay, sb, 1]; + + _delayQmf[temp_delay, sb, 0] = inputLeft[0]; + _delayQmf[temp_delay, sb, 1] = inputLeft[1]; + + Phi_Fract[0] = PSTables.Phi_Fract_Qmf[sb][0]; + Phi_Fract[1] = PSTables.Phi_Fract_Qmf[sb][1]; + } + + /* z^(-2) * Phi_Fract[k] */ + tmp[0] = tmp[0] * Phi_Fract[0] + tmp0[1] * Phi_Fract[1]; + tmp[1] = tmp0[1] * Phi_Fract[0] - tmp0[0] * Phi_Fract[1]; + + R0[0] = tmp[0]; + R0[1] = tmp[1]; + for (m = 0; m < PSConstants.NO_ALLPASS_LINKS; m++) + { + float[] Q_Fract_allpass = new float[2], tmp2 = new float[2]; + + /* fetch parameters */ + if (gr < _numHybridGroups) + { + /* select data from the hybrid subbands */ + tmp0[0] = _delaySubQmfSer[m, temp_delay_ser[m], sb, 0]; + tmp0[1] = _delaySubQmfSer[m, temp_delay_ser[m], sb, 1]; + + if (_use34hybridBands) + { + Q_Fract_allpass[0] = PSTables.Q_Fract_allpass_SubQmf34[sb][m][0]; + Q_Fract_allpass[1] = PSTables.Q_Fract_allpass_SubQmf34[sb][m][1]; + } + else + { + Q_Fract_allpass[0] = PSTables.Q_Fract_allpass_SubQmf20[sb][m][0]; + Q_Fract_allpass[1] = PSTables.Q_Fract_allpass_SubQmf20[sb][m][1]; + } + } + else + { + /* select data from the QMF subbands */ + tmp0[0] = _delayQmfSer[m, temp_delay_ser[m], sb, 0]; + tmp0[1] = _delayQmfSer[m, temp_delay_ser[m], sb, 1]; + + Q_Fract_allpass[0] = PSTables.Q_Fract_allpass_Qmf[sb][m][0]; + Q_Fract_allpass[1] = PSTables.Q_Fract_allpass_Qmf[sb][m][1]; + } + + /* delay by a fraction */ + /* z^(-d(m)) * Q_Fract_allpass[k,m] */ + tmp[0] = tmp0[0] * Q_Fract_allpass[0] + tmp0[1] * Q_Fract_allpass[1]; + tmp[1] = tmp0[1] * Q_Fract_allpass[0] - tmp0[0] * Q_Fract_allpass[1]; + + /* -a(m) * g_DecaySlope[k] */ + tmp[0] += -(g_DecaySlope_filt[m] * R0[0]); + tmp[1] += -(g_DecaySlope_filt[m] * R0[1]); + + /* -a(m) * g_DecaySlope[k] * Q_Fract_allpass[k,m] * z^(-d(m)) */ + tmp2[0] = R0[0] + g_DecaySlope_filt[m] * tmp[0]; + tmp2[1] = R0[1] + g_DecaySlope_filt[m] * tmp[1]; + + /* store sample */ + if (gr < _numHybridGroups) + { + _delaySubQmfSer[m, temp_delay_ser[m], sb, 0] = tmp2[0]; + _delaySubQmfSer[m, temp_delay_ser[m], sb, 1] = tmp2[1]; + } + else + { + _delayQmfSer[m, temp_delay_ser[m], sb, 0] = tmp2[0]; + _delayQmfSer[m, temp_delay_ser[m], sb, 1] = tmp2[1]; + } + + /* store for next iteration (or as output value if last iteration) */ + R0[0] = tmp[0]; + R0[1] = tmp[1]; + } + } + + /* select b(k) for reading the transient ratio */ + bk = ~PSConstants.NEGATE_IPD_MASK & _mapGroup2bk[gr]; + + /* duck if a past transient is found */ + R0[0] = G_TransientRatio[n, bk] * R0[0]; + R0[1] = G_TransientRatio[n, bk] * R0[1]; + + if (gr < _numHybridGroups) + { + /* hybrid */ + X_hybrid_right[n, sb, 0] = R0[0]; + X_hybrid_right[n, sb, 1] = R0[1]; + } + else + { + /* QMF */ + X_right[n, sb, 0] = R0[0]; + X_right[n, sb, 1] = R0[1]; + } + + /* Update delay buffer index */ + if (++temp_delay >= 2) + { + temp_delay = 0; + } + + /* update delay indices */ + if (sb > _nrAllpassBands && gr >= _numHybridGroups) + { + /* delay_D depends on the samplerate, it can hold the values 14 and 1 */ + if (++_delayBufIndexDelay[sb] >= _delayD[sb]) + { + _delayBufIndexDelay[sb] = 0; + } + } + + for (m = 0; m < PSConstants.NO_ALLPASS_LINKS; m++) + { + if (++temp_delay_ser[m] >= _numSampleDelaySer[m]) + { + temp_delay_ser[m] = 0; + } + } + } + } + } + + /* update delay indices */ + _savedDelay = temp_delay; + for (m = 0; m < PSConstants.NO_ALLPASS_LINKS; m++) + { + _delayBufIndexSer[m] = temp_delay_ser[m]; + } + } + + private float MagnitudeC(float[] c) + { + return (float)Math.Sqrt(c[0] * c[0] + c[1] * c[1]); + } + + private void PsMixPhase(float[,,] X_left, float[,,] X_right, float[,,] X_hybrid_left, float[,,] X_hybrid_right) + { + int n; + int gr; + int bk = 0; + int sb, maxsb; + int env; + int nr_ipdopd_par; + float[] h11 = new float[2], h12 = new float[2], h21 = new float[2], h22 = new float[2]; + float[] H11 = new float[2], H12 = new float[2], H21 = new float[2], H22 = new float[2]; + float[] deltaH11 = new float[2], deltaH12 = new float[2], deltaH21 = new float[2], deltaH22 = new float[2]; + float[] tempLeft = new float[2]; + float[] tempRight = new float[2]; + float[] phaseLeft = new float[2]; + float[] phaseRight = new float[2]; + float L; + float[] sf_iid; + int no_iid_steps; + + if (_iidMode >= 3) + { + no_iid_steps = 15; + sf_iid = PSTables.sf_iid_fine; + } + else + { + no_iid_steps = 7; + sf_iid = PSTables.sf_iid_normal; + } + + if (_ipdMode == 0 || _ipdMode == 3) + { + nr_ipdopd_par = 11; /* resolution */ + + } + else + { + nr_ipdopd_par = _nrIpdopdPar; + } + + for (gr = 0; gr < _numGroups; gr++) + { + bk = ~PSConstants.NEGATE_IPD_MASK & _mapGroup2bk[gr]; + + /* use one channel per group in the subqmf domain */ + maxsb = gr < _numHybridGroups ? _groupBorder[gr] + 1 : _groupBorder[gr + 1]; + + for (env = 0; env < _numEnv; env++) + { + if (_iccMode < 3) + { + /* type 'A' mixing as described in 8.6.4.6.2.1 */ + float c_1, c_2; + float cosa, sina; + float cosb, sinb; + float ab1, ab2; + float ab3, ab4; + + /* + c_1 = sqrt(2.0 / (1.0 + pow(10.0, quant_iid[no_iid_steps + iid_index] / 10.0))); + c_2 = sqrt(2.0 / (1.0 + pow(10.0, quant_iid[no_iid_steps - iid_index] / 10.0))); + alpha = 0.5 * acos(quant_rho[icc_index]); + beta = alpha * ( c_1 - c_2 ) / sqrt(2.0); + */ + //printf("%d\n", ps.iid_index[env][bk]); + + /* calculate the scalefactors c_1 and c_2 from the intensity differences */ + c_1 = sf_iid[no_iid_steps + _iidIndex[env][bk]]; + c_2 = sf_iid[no_iid_steps - _iidIndex[env][bk]]; + + /* calculate alpha and beta using the ICC parameters */ + cosa = PSTables.cos_alphas[_iccIndex[env][bk]]; + sina = PSTables.sin_alphas[_iccIndex[env][bk]]; + + if (_iidMode >= 3) + { + if (_iidIndex[env][bk] < 0) + { + cosb = PSTables.cos_betas_fine[-_iidIndex[env][bk]][_iccIndex[env][bk]]; + sinb = -PSTables.sin_betas_fine[-_iidIndex[env][bk]][_iccIndex[env][bk]]; + } + else + { + cosb = PSTables.cos_betas_fine[_iidIndex[env][bk]][_iccIndex[env][bk]]; + sinb = PSTables.sin_betas_fine[_iidIndex[env][bk]][_iccIndex[env][bk]]; + } + } + else + { + if (_iidIndex[env][bk] < 0) + { + cosb = PSTables.cos_betas_normal[-_iidIndex[env][bk]][_iccIndex[env][bk]]; + sinb = -PSTables.sin_betas_normal[-_iidIndex[env][bk]][_iccIndex[env][bk]]; + } + else + { + cosb = PSTables.cos_betas_normal[_iidIndex[env][bk]][_iccIndex[env][bk]]; + sinb = PSTables.sin_betas_normal[_iidIndex[env][bk]][_iccIndex[env][bk]]; + } + } + + ab1 = cosb * cosa; + ab2 = sinb * sina; + ab3 = sinb * cosa; + ab4 = cosb * sina; + + /* h_xy: COEF */ + h11[0] = c_2 * (ab1 - ab2); + h12[0] = c_1 * (ab1 + ab2); + h21[0] = c_2 * (ab3 + ab4); + h22[0] = c_1 * (ab3 - ab4); + } + else + { + /* type 'B' mixing as described in 8.6.4.6.2.2 */ + float sina, cosa; + float cosg, sing; + + if (_iidMode >= 3) + { + int abs_iid = Math.Abs(_iidIndex[env][bk]); + + cosa = PSTables.sincos_alphas_B_fine[no_iid_steps + _iidIndex[env][bk]][_iccIndex[env][bk]]; + sina = PSTables.sincos_alphas_B_fine[30 - (no_iid_steps + _iidIndex[env][bk])][_iccIndex[env][bk]]; + cosg = PSTables.cos_gammas_fine[abs_iid][_iccIndex[env][bk]]; + sing = PSTables.sin_gammas_fine[abs_iid][_iccIndex[env][bk]]; + } + else + { + int abs_iid = Math.Abs(_iidIndex[env][bk]); + + cosa = PSTables.sincos_alphas_B_normal[no_iid_steps + _iidIndex[env][bk]][_iccIndex[env][bk]]; + sina = PSTables.sincos_alphas_B_normal[14 - (no_iid_steps + _iidIndex[env][bk])][_iccIndex[env][bk]]; + cosg = PSTables.cos_gammas_normal[abs_iid][_iccIndex[env][bk]]; + sing = PSTables.sin_gammas_normal[abs_iid][_iccIndex[env][bk]]; + } + + h11[0] = PSConstants.COEF_SQRT2 * (cosa * cosg); + h12[0] = PSConstants.COEF_SQRT2 * (sina * cosg); + h21[0] = PSConstants.COEF_SQRT2 * (-cosa * sing); + h22[0] = PSConstants.COEF_SQRT2 * (sina * sing); + } + + /* calculate phase rotation parameters H_xy */ + /* note that the imaginary part of these parameters are only calculated when + IPD and OPD are enabled + */ + if (_enableIpdopd && bk < nr_ipdopd_par) + { + float xy, pq, xypq; + + /* ringbuffer index */ + int i = _phaseHist; + + /* previous value */ + tempLeft[0] = _ipdPrev[bk, i, 0] * 0.25f; + tempLeft[1] = _ipdPrev[bk, i, 1] * 0.25f; + tempRight[0] = _opdPrev[bk, i, 0] * 0.25f; + tempRight[1] = _opdPrev[bk, i, 1] * 0.25f; + + /* save current value */ + _ipdPrev[bk, i, 0] = PSTables.ipdopd_cos_tab[Math.Abs(_ipdIndex[env][bk])]; + _ipdPrev[bk, i, 1] = PSTables.ipdopd_sin_tab[Math.Abs(_ipdIndex[env][bk])]; + _opdPrev[bk, i, 0] = PSTables.ipdopd_cos_tab[Math.Abs(_opdIndex[env][bk])]; + _opdPrev[bk, i, 1] = PSTables.ipdopd_sin_tab[Math.Abs(_opdIndex[env][bk])]; + + /* add current value */ + tempLeft[0] += _ipdPrev[bk, i, 0]; + tempLeft[1] += _ipdPrev[bk, i, 1]; + tempRight[0] += _opdPrev[bk, i, 0]; + tempRight[1] += _opdPrev[bk, i, 1]; + + /* ringbuffer index */ + if (i == 0) + { + i = 2; + } + i--; + + /* get value before previous */ + tempLeft[0] += _ipdPrev[bk, i, 0] * 0.5f; + tempLeft[1] += _ipdPrev[bk, i, 1] * 0.5f; + tempRight[0] += _opdPrev[bk, i, 0] * 0.5f; + tempRight[1] += _opdPrev[bk, i, 1] * 0.5f; + + xy = MagnitudeC(tempRight); + pq = MagnitudeC(tempLeft); + + if (xy != 0) + { + phaseLeft[0] = tempRight[0] / xy; + phaseLeft[1] = tempRight[1] / xy; + } + else + { + phaseLeft[0] = 0; + phaseLeft[1] = 0; + } + + xypq = xy * pq; + + if (xypq != 0) + { + float tmp1 = tempRight[0] * tempLeft[0] + tempRight[1] * tempLeft[1]; + float tmp2 = tempRight[1] * tempLeft[0] - tempRight[0] * tempLeft[1]; + + phaseRight[0] = tmp1 / xypq; + phaseRight[1] = tmp2 / xypq; + } + else + { + phaseRight[0] = 0; + phaseRight[1] = 0; + } + + /* MUL_F(COEF, REAL) = COEF */ + h11[1] = h11[0] * phaseLeft[1]; + h12[1] = h12[0] * phaseRight[1]; + h21[1] = h21[0] * phaseLeft[1]; + h22[1] = h22[0] * phaseRight[1]; + + h11[0] = h11[0] * phaseLeft[0]; + h12[0] = h12[0] * phaseRight[0]; + h21[0] = h21[0] * phaseLeft[0]; + h22[0] = h22[0] * phaseRight[0]; + } + + /* length of the envelope n_e+1 - n_e (in time samples) */ + /* 0 < L <= 32: integer */ + L = _borderPosition[env + 1] - _borderPosition[env]; + + /* obtain final H_xy by means of linear interpolation */ + deltaH11[0] = (h11[0] - _h11Prev[gr, 0]) / L; + deltaH12[0] = (h12[0] - _h12Prev[gr, 0]) / L; + deltaH21[0] = (h21[0] - _h21Prev[gr, 0]) / L; + deltaH22[0] = (h22[0] - _h22Prev[gr, 0]) / L; + + H11[0] = _h11Prev[gr, 0]; + H12[0] = _h12Prev[gr, 0]; + H21[0] = _h21Prev[gr, 0]; + H22[0] = _h22Prev[gr, 0]; + + _h11Prev[gr, 0] = h11[0]; + _h12Prev[gr, 0] = h12[0]; + _h21Prev[gr, 0] = h21[0]; + _h22Prev[gr, 0] = h22[0]; + + /* only calculate imaginary part when needed */ + if (_enableIpdopd && bk < nr_ipdopd_par) + { + /* obtain final H_xy by means of linear interpolation */ + deltaH11[1] = (h11[1] - _h11Prev[gr, 1]) / L; + deltaH12[1] = (h12[1] - _h12Prev[gr, 1]) / L; + deltaH21[1] = (h21[1] - _h21Prev[gr, 1]) / L; + deltaH22[1] = (h22[1] - _h22Prev[gr, 1]) / L; + + H11[1] = _h11Prev[gr, 1]; + H12[1] = _h12Prev[gr, 1]; + H21[1] = _h21Prev[gr, 1]; + H22[1] = _h22Prev[gr, 1]; + + if ((PSConstants.NEGATE_IPD_MASK & _mapGroup2bk[gr]) != 0) + { + deltaH11[1] = -deltaH11[1]; + deltaH12[1] = -deltaH12[1]; + deltaH21[1] = -deltaH21[1]; + deltaH22[1] = -deltaH22[1]; + + H11[1] = -H11[1]; + H12[1] = -H12[1]; + H21[1] = -H21[1]; + H22[1] = -H22[1]; + } + + _h11Prev[gr, 1] = h11[1]; + _h12Prev[gr, 1] = h12[1]; + _h21Prev[gr, 1] = h21[1]; + _h22Prev[gr, 1] = h22[1]; + } + + /* apply H_xy to the current envelope band of the decorrelated subband */ + for (n = _borderPosition[env]; n < _borderPosition[env + 1]; n++) + { + /* addition finalises the interpolation over every n */ + H11[0] += deltaH11[0]; + H12[0] += deltaH12[0]; + H21[0] += deltaH21[0]; + H22[0] += deltaH22[0]; + if (_enableIpdopd && bk < nr_ipdopd_par) + { + H11[1] += deltaH11[1]; + H12[1] += deltaH12[1]; + H21[1] += deltaH21[1]; + H22[1] += deltaH22[1]; + } + + /* channel is an alias to the subband */ + for (sb = _groupBorder[gr]; sb < maxsb; sb++) + { + float[] inLeft = new float[2], inRight = new float[2]; + + /* load decorrelated samples */ + if (gr < _numHybridGroups) + { + inLeft[0] = X_hybrid_left[n, sb, 0]; + inLeft[1] = X_hybrid_left[n, sb, 1]; + inRight[0] = X_hybrid_right[n, sb, 0]; + inRight[1] = X_hybrid_right[n, sb, 1]; + } + else + { + inLeft[0] = X_left[n, sb, 0]; + inLeft[1] = X_left[n, sb, 1]; + inRight[0] = X_right[n, sb, 0]; + inRight[1] = X_right[n, sb, 1]; + } + + /* apply mixing */ + tempLeft[0] = H11[0] * inLeft[0] + H21[0] * inRight[0]; + tempLeft[1] = H11[0] * inLeft[1] + H21[0] * inRight[1]; + tempRight[0] = H12[0] * inLeft[0] + H22[0] * inRight[0]; + tempRight[1] = H12[0] * inLeft[1] + H22[0] * inRight[1]; + + /* only perform imaginary operations when needed */ + if (_enableIpdopd && bk < nr_ipdopd_par) + { + /* apply rotation */ + tempLeft[0] -= H11[1] * inLeft[1] + H21[1] * inRight[1]; + tempLeft[1] += H11[1] * inLeft[0] + H21[1] * inRight[0]; + tempRight[0] -= H12[1] * inLeft[1] + H22[1] * inRight[1]; + tempRight[1] += H12[1] * inLeft[0] + H22[1] * inRight[0]; + } + + /* store final samples */ + if (gr < _numHybridGroups) + { + X_hybrid_left[n, sb, 0] = tempLeft[0]; + X_hybrid_left[n, sb, 1] = tempLeft[1]; + X_hybrid_right[n, sb, 0] = tempRight[0]; + X_hybrid_right[n, sb, 1] = tempRight[1]; + } + else + { + X_left[n, sb, 0] = tempLeft[0]; + X_left[n, sb, 1] = tempLeft[1]; + X_right[n, sb, 0] = tempRight[0]; + X_right[n, sb, 1] = tempRight[1]; + } + } + } + + /* shift phase smoother's circular buffer index */ + _phaseHist++; + if (_phaseHist == 2) + { + _phaseHist = 0; + } + } + } + } + + /* main Parametric Stereo decoding function */ + public int Process(float[,,] X_left, float[,,] X_right) + { + float[,,] X_hybrid_left = new float[32, 32, 2]; + float[,,] X_hybrid_right = new float[32, 32, 2]; + + /* delta decoding of the bitstream data */ + PsDataDecode(); + + /* set up some parameters depending on filterbank type */ + if (_use34hybridBands) + { + _groupBorder = PSTables.group_border34; + _mapGroup2bk = PSTables.map_group2bk34; + _numGroups = 32 + 18; + _numHybridGroups = 32; + _nrParBands = 34; + _decayCutoff = 5; + } + else + { + _groupBorder = PSTables.group_border20; + _mapGroup2bk = PSTables.map_group2bk20; + _numGroups = 10 + 12; + _numHybridGroups = 10; + _nrParBands = 20; + _decayCutoff = 3; + } + + /* Perform further analysis on the lowest subbands to get a higher + * frequency resolution + */ + _hyb.HybridAnalysis(X_left, X_hybrid_left, + _use34hybridBands, _numTimeSlotsRate); + + /* decorrelate mono signal */ + PsDecorrelate(X_left, X_right, X_hybrid_left, X_hybrid_right); + + /* apply mixing and phase parameters */ + PsMixPhase(X_left, X_right, X_hybrid_left, X_hybrid_right); + + /* hybrid synthesis, to rebuild the SBR QMF matrices */ + _hyb.HybridSynthesis(X_left, X_hybrid_left, + _use34hybridBands, _numTimeSlotsRate); + + _hyb.HybridSynthesis(X_right, X_hybrid_right, + _use34hybridBands, _numTimeSlotsRate); + + return 0; + } + } +} diff --git a/SharpJaad.AAC/Ps/PSConstants.cs b/SharpJaad.AAC/Ps/PSConstants.cs new file mode 100644 index 0000000..5765542 --- /dev/null +++ b/SharpJaad.AAC/Ps/PSConstants.cs @@ -0,0 +1,11 @@ +namespace SharpJaad.AAC.Ps +{ + public static class PSConstants + { + public const int MAX_PS_ENVELOPES = 5; + public const int NO_ALLPASS_LINKS = 3; + public const int NEGATE_IPD_MASK = 0x1000; + public const float DECAY_SLOPE = 0.05f; + public const float COEF_SQRT2 = 1.4142135623731f; + } +} diff --git a/SharpJaad.AAC/Ps/PSTables.cs b/SharpJaad.AAC/Ps/PSTables.cs new file mode 100644 index 0000000..66c7687 --- /dev/null +++ b/SharpJaad.AAC/Ps/PSTables.cs @@ -0,0 +1,620 @@ +namespace SharpJaad.AAC.Ps +{ + public static class PSTables + { + /* type definitaions */ + /* static data tables */ + public static int[] nr_iid_par_tab = + { + 10, 20, 34, 10, 20, 34, 0, 0 + }; + public static int[] nr_icc_par_tab = + { + 10, 20, 34, 10, 20, 34, 0, 0 + }; + public static int[] nr_ipdopd_par_tab = + { + 5, 11, 17, 5, 11, 17, 0, 0 + }; + public static int[][] num_env_tab = + { + new int[] {0, 1, 2, 4}, + new int[] {1, 2, 3, 4} + }; + public static float[] filter_a = + { /* a(m) = exp(-d_48kHz(m)/7) */ + 0.65143905753106f, + 0.56471812200776f, + 0.48954165955695f + }; + + public static int[] group_border20 = + { + 6, 7, 0, 1, 2, 3, /* 6 subqmf subbands */ + 9, 8, /* 2 subqmf subbands */ + 10, 11, /* 2 subqmf subbands */ + 3, 4, 5, 6, 7, 8, 9, 11, 14, 18, 23, 35, 64 + }; + + public static int[] group_border34 = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, /* 12 subqmf subbands */ + 12, 13, 14, 15, 16, 17, 18, 19, /* 8 subqmf subbands */ + 20, 21, 22, 23, /* 4 subqmf subbands */ + 24, 25, 26, 27, /* 4 subqmf subbands */ + 28, 29, 30, 31, /* 4 subqmf subbands */ + 32-27, 33-27, 34-27, 35-27, 36-27, 37-27, 38-27, 40-27, 42-27, 44-27, 46-27, 48-27, 51-27, 54-27, 57-27, 60-27, 64-27, 68-27, 91-27 + }; + + public static int[] map_group2bk20 = + { + PSConstants.NEGATE_IPD_MASK|1, PSConstants.NEGATE_IPD_MASK|0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 + }; + + public static int[] map_group2bk34 = + { + 0, 1, 2, 3, 4, 5, 6, 6, 7, PSConstants.NEGATE_IPD_MASK|2, PSConstants.NEGATE_IPD_MASK|1, PSConstants.NEGATE_IPD_MASK|0, + 10, 10, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 9, + 14, 11, 12, 13, + 14, 15, 16, 13, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33 + }; + + public static int[] delay_length_d = { 3, 4, 5 /* d_48kHz */}; + /* tables */ + /* filters are mirrored in coef 6, second half left out */ + public static float[] p8_13_20 = + { + 0.00746082949812f, + 0.02270420949825f, + 0.04546865930473f, + 0.07266113929591f, + 0.09885108575264f, + 0.11793710567217f, + 0.125f + }; + + public static float[] p2_13_20 = + { + 0.0f, + 0.01899487526049f, + 0.0f, + -0.07293139167538f, + 0.0f, + 0.30596630545168f, + 0.5f + }; + + public static float[] p12_13_34 = + { + 0.04081179924692f, + 0.03812810994926f, + 0.05144908135699f, + 0.06399831151592f, + 0.07428313801106f, + 0.08100347892914f, + 0.08333333333333f + }; + + public static float[] p8_13_34 = + { + 0.01565675600122f, + 0.03752716391991f, + 0.05417891378782f, + 0.08417044116767f, + 0.10307344158036f, + 0.12222452249753f, + 0.125f + }; + + public static float[] p4_13_34 = + { + -0.05908211155639f, + -0.04871498374946f, + 0.0f, + 0.07778723915851f, + 0.16486303567403f, + 0.23279856662996f, + 0.25f + }; + + /* RE(ps->Phi_Fract_Qmf[j]) = (float)cos(M_PI*(j+0.5)*(0.39)); */ + /* IM(ps->Phi_Fract_Qmf[j]) = (float)sin(M_PI*(j+0.5)*(0.39)); */ + public static float[][] Phi_Fract_Qmf = + { + new float[] {0.8181497455f, 0.5750052333f}, + new float[] {-0.2638730407f, 0.9645574093f}, + new float[] {-0.9969173074f, 0.0784590989f}, + new float[] {-0.4115143716f, -0.9114032984f}, + new float[] {0.7181262970f, -0.6959127784f}, + new float[] {0.8980275989f, 0.4399391711f}, + new float[] {-0.1097343117f, 0.9939609766f}, + new float[] {-0.9723699093f, 0.2334453613f}, + new float[] {-0.5490227938f, -0.8358073831f}, + new float[] {0.6004202366f, -0.7996846437f}, + new float[] {0.9557930231f, 0.2940403223f}, + new float[] {0.0471064523f, 0.9988898635f}, + new float[] {-0.9238795042f, 0.3826834261f}, + new float[] {-0.6730124950f, -0.7396311164f}, + new float[] {0.4679298103f, -0.8837656379f}, + new float[] {0.9900236726f, 0.1409012377f}, + new float[] {0.2027872950f, 0.9792228341f}, + new float[] {-0.8526401520f, 0.5224985480f}, + new float[] {-0.7804304361f, -0.6252426505f}, + new float[] {0.3239174187f, -0.9460853338f}, + new float[] {0.9998766184f, -0.0157073177f}, + new float[] {0.3534748554f, 0.9354440570f}, + new float[] {-0.7604059577f, 0.6494480371f}, + new float[] {-0.8686315417f, -0.4954586625f}, + new float[] {0.1719291061f, -0.9851093292f}, + new float[] {0.9851093292f, -0.1719291061f}, + new float[] {0.4954586625f, 0.8686315417f}, + new float[] {-0.6494480371f, 0.7604059577f}, + new float[] {-0.9354440570f, -0.3534748554f}, + new float[] {0.0157073177f, -0.9998766184f}, + new float[] {0.9460853338f, -0.3239174187f}, + new float[] {0.6252426505f, 0.7804304361f}, + new float[] {-0.5224985480f, 0.8526401520f}, + new float[] {-0.9792228341f, -0.2027872950f}, + new float[] {-0.1409012377f, -0.9900236726f}, + new float[] {0.8837656379f, -0.4679298103f}, + new float[] {0.7396311164f, 0.6730124950f}, + new float[] {-0.3826834261f, 0.9238795042f}, + new float[] {-0.9988898635f, -0.0471064523f}, + new float[] {-0.2940403223f, -0.9557930231f}, + new float[] {0.7996846437f, -0.6004202366f}, + new float[] {0.8358073831f, 0.5490227938f}, + new float[] {-0.2334453613f, 0.9723699093f}, + new float[] {-0.9939609766f, 0.1097343117f}, + new float[] {-0.4399391711f, -0.8980275989f}, + new float[] {0.6959127784f, -0.7181262970f}, + new float[] {0.9114032984f, 0.4115143716f}, + new float[] {-0.0784590989f, 0.9969173074f}, + new float[] {-0.9645574093f, 0.2638730407f}, + new float[] {-0.5750052333f, -0.8181497455f}, + new float[] {0.5750052333f, -0.8181497455f}, + new float[] {0.9645574093f, 0.2638730407f}, + new float[] {0.0784590989f, 0.9969173074f}, + new float[] {-0.9114032984f, 0.4115143716f}, + new float[] {-0.6959127784f, -0.7181262970f}, + new float[] {0.4399391711f, -0.8980275989f}, + new float[] {0.9939609766f, 0.1097343117f}, + new float[] {0.2334453613f, 0.9723699093f}, + new float[] {-0.8358073831f, 0.5490227938f}, + new float[] {-0.7996846437f, -0.6004202366f}, + new float[] {0.2940403223f, -0.9557930231f}, + new float[] {0.9988898635f, -0.0471064523f}, + new float[] {0.3826834261f, 0.9238795042f}, + new float[] {-0.7396311164f, 0.6730124950f} + }; + + /* RE(Phi_Fract_SubQmf20[j]) = (float)cos(M_PI*f_center_20[j]*0.39); */ + /* IM(Phi_Fract_SubQmf20[j]) = (float)sin(M_PI*f_center_20[j]*0.39); */ + public static float[][] Phi_Fract_SubQmf20 = + { + new float[] {0.9882950187f, 0.1525546312f}, + new float[] {0.8962930441f, 0.4434623122f}, + new float[] {0.7208535671f, 0.6930873394f}, + new float[] {0.4783087075f, 0.8781917691f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {0.8962930441f, -0.4434623122f}, + new float[] {0.9882950187f, -0.1525546312f}, + new float[] {-0.5424415469f, 0.8400935531f}, + new float[] {0.0392598175f, 0.9992290139f}, + new float[] {-0.9268565774f, 0.3754155636f}, + new float[] {-0.9741733670f, -0.2258012742f} + }; + + /* RE(Phi_Fract_SubQmf34[j]) = (float)cos(M_PI*f_center_34[j]*0.39); */ + /* IM(Phi_Fract_SubQmf34[j]) = (float)sin(M_PI*f_center_34[j]*0.39); */ + public static float[][] Phi_Fract_SubQmf34 = + { + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {-0.7705132365f, 0.6374239922f}, + new float[] {-0.7705132365f, 0.6374239922f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {1.0000000000f, 0.0000000000f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {-0.7705132365f, 0.6374239922f}, + new float[] {-0.7705132365f, 0.6374239922f}, + new float[] {-0.8607420325f, -0.5090414286f}, + new float[] {0.3387379348f, 0.9408807755f}, + new float[] {0.1873813123f, -0.9822872281f}, + new float[] {-0.7705132365f, 0.6374239922f}, + new float[] {-0.8607420325f, -0.5090414286f}, + new float[] {-0.8607420325f, -0.5090414286f}, + new float[] {0.1873813123f, -0.9822872281f}, + new float[] {0.1873813123f, -0.9822872281f}, + new float[] {0.9876883626f, -0.1564344615f}, + new float[] {-0.8607420325f, -0.5090414286f} + }; + + /* RE(Q_Fract_allpass_Qmf[j][i]) = (float)cos(M_PI*(j+0.5)*(frac_delay_q[i])); */ + /* IM(Q_Fract_allpass_Qmf[j][i]) = (float)sin(M_PI*(j+0.5)*(frac_delay_q[i])); */ + public static float[][][] Q_Fract_allpass_Qmf = + { + new float[][] {new float[] {0.7804303765f, 0.6252426505f}, new float[] { 0.3826834261f, 0.9238795042f}, new float[] { 0.8550928831f, 0.5184748173f}}, + new float[][] {new float[] {-0.4399392009f, 0.8980275393f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {-0.0643581524f, 0.9979268909f}}, + new float[][] {new float[] {-0.9723699093f, -0.2334454209f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {-0.9146071672f, 0.4043435752f}}, + new float[][] {new float[] {0.0157073960f, -0.9998766184f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {-0.7814115286f, -0.6240159869f}}, + new float[][] {new float[] {0.9792228341f, -0.2027871907f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {0.1920081824f, -0.9813933372f}}, + new float[][] {new float[] {0.4115142524f, 0.9114032984f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {0.9589683414f, -0.2835132182f}}, + new float[][] {new float[] {-0.7996847630f, 0.6004201174f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {0.6947838664f, 0.7192186117f}}, + new float[][] {new float[] {-0.7604058385f, -0.6494481564f}, new float[] {0.3826834261f, -0.9238795042f}, new float[] {-0.3164770305f, 0.9486001730f}}, + new float[][] {new float[] {0.4679299891f, -0.8837655187f}, new float[] {0.3826834261f, 0.9238795042f}, new float[] {-0.9874414206f, 0.1579856575f}}, + new float[][] {new float[] {0.9645573497f, 0.2638732493f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {-0.5966450572f, -0.8025052547f}}, + new float[][] {new float[] {-0.0471066870f, 0.9988898635f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {0.4357025325f, -0.9000906944f}}, + new float[][] {new float[] {-0.9851093888f, 0.1719288528f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {0.9995546937f, -0.0298405960f}}, + new float[][] {new float[] {-0.3826831877f, -0.9238796234f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {0.4886211455f, 0.8724960685f}}, + new float[][] {new float[] {0.8181498647f, -0.5750049949f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {-0.5477093458f, 0.8366686702f}}, + new float[][] {new float[] {0.7396308780f, 0.6730127335f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {-0.9951074123f, -0.0987988561f}}, + new float[][] {new float[] {-0.4954589605f, 0.8686313629f}, new float[] {0.3826834261f, -0.9238795042f}, new float[] {-0.3725017905f, -0.9280315042f}}, + new float[][] {new float[] {-0.9557929039f, -0.2940406799f}, new float[] {0.3826834261f, 0.9238795042f}, new float[] {0.6506417990f, -0.7593847513f}}, + new float[][] {new float[] {0.0784594864f, -0.9969173074f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {0.9741733670f, 0.2258014232f}}, + new float[][] {new float[] {0.9900237322f, -0.1409008205f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {0.2502108514f, 0.9681913853f}}, + new float[][] {new float[] {0.3534744382f, 0.9354441762f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {-0.7427945137f, 0.6695194840f}}, + new float[][] {new float[] {-0.8358076215f, 0.5490224361f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {-0.9370992780f, -0.3490629196f}}, + new float[][] {new float[] {-0.7181259394f, -0.6959131360f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {-0.1237744763f, -0.9923103452f}}, + new float[][] {new float[] {0.5224990249f, -0.8526399136f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {0.8226406574f, -0.5685616732f}}, + new float[][] {new float[] {0.9460852146f, 0.3239179254f}, new float[] {0.3826834261f, -0.9238795042f}, new float[] {0.8844994903f, 0.4665412009f}}, + new float[][] {new float[] {-0.1097348556f, 0.9939609170f}, new float[] {0.3826834261f, 0.9238795042f}, new float[] {-0.0047125919f, 0.9999889135f}}, + new float[][] {new float[] {-0.9939610362f, 0.1097337380f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {-0.8888573647f, 0.4581840038f}}, + new float[][] {new float[] {-0.3239168525f, -0.9460855722f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {-0.8172453642f, -0.5762898922f}}, + new float[][] {new float[] {0.8526405096f, -0.5224980116f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {0.1331215799f, -0.9910997152f}}, + new float[][] {new float[] {0.6959123611f, 0.7181267142f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {0.9403476119f, -0.3402152061f}}, + new float[][] {new float[] {-0.5490233898f, 0.8358070254f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {0.7364512086f, 0.6764906645f}}, + new float[][] {new float[] {-0.9354437590f, -0.3534754813f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {-0.2593250275f, 0.9657900929f}}, + new float[][] {new float[] {0.1409019381f, -0.9900235534f}, new float[] {0.3826834261f, -0.9238795042f}, new float[] {-0.9762582779f, 0.2166097313f}}, + new float[][] {new float[] {0.9969173670f, -0.0784583688f}, new float[] {0.3826834261f, 0.9238795042f}, new float[] {-0.6434556246f, -0.7654833794f}}, + new float[][] {new float[] {0.2940396070f, 0.9557932615f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {0.3812320232f, -0.9244794250f}}, + new float[][] {new float[] {-0.8686318994f, 0.4954580069f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {0.9959943891f, -0.0894154981f}}, + new float[][] {new float[] {-0.6730118990f, -0.7396316528f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {0.5397993922f, 0.8417937160f}}, + new float[][] {new float[] {0.5750059485f, -0.8181492686f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {-0.4968227744f, 0.8678520322f}}, + new float[][] {new float[] {0.9238792062f, 0.3826842010f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {-0.9992290139f, -0.0392601527f}}, + new float[][] {new float[] {-0.1719299555f, 0.9851091504f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {-0.4271997511f, -0.9041572809f}}, + new float[][] {new float[] {-0.9988899231f, 0.0471055657f}, new float[] {0.3826834261f, -0.9238795042f}, new float[] {0.6041822433f, -0.7968461514f}}, + new float[][] {new float[] {-0.2638721764f, -0.9645576477f}, new float[] {0.3826834261f, 0.9238795042f}, new float[] {0.9859085083f, 0.1672853529f}}, + new float[][] {new float[] {0.8837660551f, -0.4679289758f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {0.3075223565f, 0.9515408874f}}, + new float[][] {new float[] {0.6494473219f, 0.7604066133f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {-0.7015317082f, 0.7126382589f}}, + new float[][] {new float[] {-0.6004210114f, 0.7996840477f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {-0.9562535882f, -0.2925389707f}}, + new float[][] {new float[] {-0.9114028811f, -0.4115152657f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {-0.1827499419f, -0.9831594229f}}, + new float[][] {new float[] {0.2027882934f, -0.9792225957f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {0.7872582674f, -0.6166234016f}}, + new float[][] {new float[] {0.9998766780f, -0.0157062728f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {0.9107555747f, 0.4129458666f}}, + new float[][] {new float[] {0.2334443331f, 0.9723701477f}, new float[] {0.3826834261f, -0.9238795042f}, new float[] {0.0549497530f, 0.9984891415f}}, + new float[][] {new float[] {-0.8980280757f, 0.4399381876f}, new float[] {0.3826834261f, 0.9238795042f}, new float[] {-0.8599416018f, 0.5103924870f}}, + new float[][] {new float[] {-0.6252418160f, -0.7804310918f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {-0.8501682281f, -0.5265110731f}}, + new float[][] {new float[] {0.6252435446f, -0.7804297209f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {0.0737608299f, -0.9972759485f}}, + new float[][] {new float[] {0.8980270624f, 0.4399402142f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {0.9183775187f, -0.3957053721f}}, + new float[][] {new float[] {-0.2334465086f, 0.9723696709f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {0.7754954696f, 0.6313531399f}}, + new float[][] {new float[] {-0.9998766184f, -0.0157085191f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {-0.2012493610f, 0.9795400500f}}, + new float[][] {new float[] {-0.2027861029f, -0.9792230725f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {-0.9615978599f, 0.2744622827f}}, + new float[][] {new float[] {0.9114037752f, -0.4115132093f}, new float[] {0.3826834261f, -0.9238795042f}, new float[] {-0.6879743338f, -0.7257350087f}}, + new float[][] {new float[] {0.6004192233f, 0.7996854186f}, new float[] {0.3826834261f, 0.9238795042f}, new float[] {0.3254036009f, -0.9455752373f}}, + new float[][] {new float[] {-0.6494490504f, 0.7604051232f}, new float[] {-0.9238795042f, -0.3826834261f}, new float[] {0.9888865948f, -0.1486719251f}}, + new float[][] {new float[] {-0.8837650418f, -0.4679309726f}, new float[] {0.9238795042f, -0.3826834261f}, new float[] {0.5890548825f, 0.8080930114f}}, + new float[][] {new float[] {0.2638743520f, -0.9645570517f}, new float[] {-0.3826834261f, 0.9238795042f}, new float[] {-0.4441666007f, 0.8959442377f}}, + new float[][] {new float[] {0.9988898039f, 0.0471078083f}, new float[] {-0.3826834261f, -0.9238795042f}, new float[] {-0.9997915030f, 0.0204183888f}}, + new float[][] {new float[] {0.1719277352f, 0.9851095676f}, new float[] {0.9238795042f, 0.3826834261f}, new float[] {-0.4803760946f, -0.8770626187f}}, + new float[][] {new float[] {-0.9238800406f, 0.3826821446f}, new float[] {-0.9238795042f, 0.3826834261f}, new float[] {0.5555707216f, -0.8314692974f}}, + new float[][] {new float[] { -0.5750041008f, -0.8181505203f},new float[] {0.3826834261f, -0.9238795042f}, new float[] {0.9941320419f, 0.1081734300f}} + }; + + /* RE(Q_Fract_allpass_SubQmf20[j][i]) = (float)cos(M_PI*f_center_20[j]*frac_delay_q[i]); */ + /* IM(Q_Fract_allpass_SubQmf20[j][i]) = (float)sin(M_PI*f_center_20[j]*frac_delay_q[i]); */ + public static float[][][] Q_Fract_allpass_SubQmf20 = + { + new float[][] { new float[] {0.9857769012f, 0.1680592746f}, new float[] {0.9569403529f, 0.2902846634f}, new float[] {0.9907300472f, 0.1358452588f}}, + new float[][] { new float[] {0.8744080663f, 0.4851911962f}, new float[] {0.6343932748f, 0.7730104327f}, new float[] {0.9175986052f, 0.3975082636f}}, + new float[][] { new float[] {0.6642524004f, 0.7475083470f}, new float[] {0.0980171412f, 0.9951847196f}, new float[] {0.7767338753f, 0.6298289299f}}, + new float[][] { new float[] {0.3790524006f, 0.9253752232f}, new float[] {-0.4713967443f, 0.8819212914f}, new float[] {0.5785340071f, 0.8156582713f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {0.8744080663f, -0.4851911962f}, new float[] {0.6343932748f, -0.7730104327f}, new float[] {0.9175986052f, -0.3975082636f}}, + new float[][] { new float[] {0.9857769012f, -0.1680592746f}, new float[] {0.9569403529f, -0.2902846634f}, new float[] {0.9907300472f, -0.1358452588f}}, + new float[][] { new float[] {-0.7126385570f, 0.7015314102f}, new float[] {-0.5555702448f, -0.8314695954f},new float[] {-0.3305967748f, 0.9437720776f}}, + new float[][] { new float[] {-0.1175374240f, 0.9930684566f}, new float[] {-0.9807852507f, 0.1950903237f}, new float[] {0.2066311091f, 0.9784189463f}}, + new float[][] { new float[] {-0.9947921634f, 0.1019244045f}, new float[] {0.5555702448f, -0.8314695954f}, new float[] {-0.7720130086f, 0.6356067061f}}, + new float[][] { new float[] { -0.8400934935f, -0.5424416065f}, new float[] { 0.9807852507f, 0.1950903237f}, new float[] { -0.9896889329f, 0.1432335079f}} + }; + + /* RE(Q_Fract_allpass_SubQmf34[j][i]) = (float)cos(M_PI*f_center_34[j]*frac_delay_q[i]); */ + /* IM(Q_Fract_allpass_SubQmf34[j][i]) = (float)sin(M_PI*f_center_34[j]*frac_delay_q[i]); */ + public static float[][][] Q_Fract_allpass_SubQmf34 = + { + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {-0.9048270583f, 0.4257792532f}, new float[] {-0.0000000000f, -1.0000000000f}, new float[] {-0.5724321604f, 0.8199520707f}}, + new float[][] { new float[] {-0.9048270583f, 0.4257792532f}, new float[] {-0.0000000000f, -1.0000000000f}, new float[] {-0.5724321604f, 0.8199520707f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}, new float[] {1.0000000000f, 0.0000000000f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {-0.9048270583f, 0.4257792532f}, new float[] {-0.0000000000f, -1.0000000000f}, new float[] {-0.5724321604f, 0.8199520707f}}, + new float[][] { new float[] {-0.9048270583f, 0.4257792532f}, new float[] {-0.0000000000f, -1.0000000000f}, new float[] {-0.5724321604f, 0.8199520707f}}, + new float[][] { new float[] {-0.6129069924f, -0.7901550531f},new float[] {0.7071067691f, 0.7071067691f}, new float[] {-0.9917160273f, -0.1284494549f}}, + new float[][] { new float[] {0.2181432247f, 0.9759167433f}, new float[] {-0.7071067691f, 0.7071067691f}, new float[] {0.4623677433f, 0.8866882324f}}, + new float[][] { new float[] {0.6374240518f, -0.7705131769f}, new float[] {-1.0000000000f, 0.0000000000f}, new float[] {-0.3446428776f, -0.9387338758f}}, + new float[][] { new float[] {-0.9048270583f, 0.4257792532f}, new float[] {-0.0000000000f, -1.0000000000f}, new float[] {-0.5724321604f, 0.8199520707f}}, + new float[][] { new float[] {-0.6129069924f, -0.7901550531f},new float[] {0.7071067691f, 0.7071067691f}, new float[] {-0.9917160273f, -0.1284494549f}}, + new float[][] { new float[] {-0.6129069924f, -0.7901550531f},new float[] {0.7071067691f, 0.7071067691f}, new float[] {-0.9917160273f, -0.1284494549f}}, + new float[][] { new float[] {0.6374240518f, -0.7705131769f}, new float[] {-1.0000000000f, 0.0000000000f}, new float[] {-0.3446428776f, -0.9387338758f}}, + new float[][] { new float[] {0.6374240518f, -0.7705131769f}, new float[] {-1.0000000000f, 0.0000000000f}, new float[] {-0.3446428776f, -0.9387338758f}}, + new float[][] { new float[] {0.8910064697f, 0.4539906085f}, new float[] {0.7071067691f, -0.7071067691f}, new float[] {0.6730125546f, -0.7396310568f}}, + new float[][] { new float[] {-0.6129069924f, -0.7901550531f},new float[] {0.7071067691f, 0.7071067691f}, new float[] {-0.9917160273f, -0.1284494549f}} + }; + + public static float[] cos_alphas = + { + 1.0000000000f, 0.9841239700f, 0.9594738210f, + 0.8946843079f, 0.8269340931f, 0.7071067812f, + 0.4533210856f, 0.0000000000f + }; + + public static float[] sin_alphas = + { + 0.0000000000f, 0.1774824264f, 0.2817977763f, + 0.4466989918f, 0.5622988580f, 0.7071067812f, + 0.8913472911f, 1.0000000000f + }; + + public static float[][] cos_betas_normal = + { + new float[] {1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f}, + new float[] {1.0000000000f, 0.9995871699f, 0.9989419133f, 0.9972204583f, 0.9953790839f, 0.9920112747f, 0.9843408180f, 0.9681727381f}, + new float[] {1.0000000000f, 0.9984497744f, 0.9960279377f, 0.9895738413f, 0.9826814632f, 0.9701058164f, 0.9416098832f, 0.8822105900f}, + new float[] {1.0000000000f, 0.9959398908f, 0.9896038018f, 0.9727589768f, 0.9548355329f, 0.9223070404f, 0.8494349490f, 0.7013005535f}, + new float[] {1.0000000000f, 0.9932417400f, 0.9827071856f, 0.9547730996f, 0.9251668930f, 0.8717461589f, 0.7535520592f, 0.5198827312f}, + new float[] {1.0000000000f, 0.9902068095f, 0.9749613872f, 0.9346538534f, 0.8921231300f, 0.8158851259f, 0.6495964302f, 0.3313370772f}, + new float[] {1.0000000000f, 0.9880510933f, 0.9694670261f, 0.9204347876f, 0.8688622825f, 0.7768516704f, 0.5782161800f, 0.2069970356f}, + new float[] {1.0000000000f, 0.9858996945f, 0.9639898866f, 0.9063034786f, 0.8458214608f, 0.7384262300f, 0.5089811277f, 0.0905465944f} + }; + + public static float[][] sin_betas_normal = + { + new float[] {0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f}, + new float[] {0.0000000000f, -0.0287313368f, -0.0459897147f, -0.0745074328f, -0.0960233266f, -0.1261492408f, -0.1762757894f, -0.2502829383f}, + new float[] {0.0000000000f, -0.0556601118f, -0.0890412670f, -0.1440264301f, -0.1853028382f, -0.2426823129f, -0.3367058477f, -0.4708550466f}, + new float[] {0.0000000000f, -0.0900207420f, -0.1438204281f, -0.2318188366f, -0.2971348264f, -0.3864579191f, -0.5276933461f, -0.7128657193f}, + new float[] {0.0000000000f, -0.1160639735f, -0.1851663774f, -0.2973353800f, -0.3795605619f, -0.4899577884f, -0.6573882369f, -0.8542376401f}, + new float[] {0.0000000000f, -0.1396082894f, -0.2223742196f, -0.3555589603f, -0.4517923427f, -0.5782140273f, -0.7602792104f, -0.9435124489f}, + new float[] {0.0000000000f, -0.1541266914f, -0.2452217065f, -0.3908961522f, -0.4950538699f, -0.6296836366f, -0.8158836002f, -0.9783415698f}, + new float[] {0.0000000000f, -0.1673373610f, -0.2659389001f, -0.4226275012f, -0.5334660781f, -0.6743342664f, -0.8607776784f, -0.9958922202f} + }; + + public static float[][] cos_betas_fine = + { + new float[] {1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f}, + new float[] {1.0000000000f, 0.9995871699f, 0.9989419133f, 0.9972204583f, 0.9953790839f, 0.9920112747f, 0.9843408180f, 0.9681727381f}, + new float[] {1.0000000000f, 0.9984497744f, 0.9960279377f, 0.9895738413f, 0.9826814632f, 0.9701058164f, 0.9416098832f, 0.8822105900f}, + new float[] {1.0000000000f, 0.9968361371f, 0.9918968104f, 0.9787540479f, 0.9647515190f, 0.9392903010f, 0.8820167114f, 0.7645325390f}, + new float[] {1.0000000000f, 0.9950262915f, 0.9872675041f, 0.9666584578f, 0.9447588606f, 0.9050918405f, 0.8165997379f, 0.6383824796f}, + new float[] {1.0000000000f, 0.9932417400f, 0.9827071856f, 0.9547730996f, 0.9251668930f, 0.8717461589f, 0.7535520592f, 0.5198827312f}, + new float[] {1.0000000000f, 0.9908827998f, 0.9766855904f, 0.9391249214f, 0.8994531782f, 0.8282352693f, 0.6723983174f, 0.3719473225f}, + new float[] {1.0000000000f, 0.9890240165f, 0.9719459866f, 0.9268448110f, 0.8793388536f, 0.7944023271f, 0.6101812098f, 0.2621501145f}, + new float[] {1.0000000000f, 0.9876350461f, 0.9684073447f, 0.9176973944f, 0.8643930070f, 0.7693796058f, 0.5646720713f, 0.1838899556f}, + new float[] {1.0000000000f, 0.9866247085f, 0.9658349704f, 0.9110590761f, 0.8535668048f, 0.7513165426f, 0.5320914819f, 0.1289530943f}, + new float[] {1.0000000000f, 0.9858996945f, 0.9639898866f, 0.9063034786f, 0.8458214608f, 0.7384262300f, 0.5089811277f, 0.0905465944f}, + new float[] {1.0000000000f, 0.9851245614f, 0.9620180268f, 0.9012265590f, 0.8375623272f, 0.7247108045f, 0.4845204297f, 0.0504115003f}, + new float[] {1.0000000000f, 0.9846869856f, 0.9609052357f, 0.8983639533f, 0.8329098386f, 0.7169983441f, 0.4708245354f, 0.0281732509f}, + new float[] {1.0000000000f, 0.9844406325f, 0.9602788522f, 0.8967533934f, 0.8302936455f, 0.7126658102f, 0.4631492839f, 0.0157851140f}, + new float[] {1.0000000000f, 0.9843020502f, 0.9599265269f, 0.8958477331f, 0.8288229094f, 0.7102315840f, 0.4588429315f, 0.0088578059f}, + new float[] {1.0000000000f, 0.9842241136f, 0.9597283916f, 0.8953385094f, 0.8279961409f, 0.7088635748f, 0.4564246834f, 0.0049751355f} + }; + + public static float[][] sin_betas_fine = + { + new float[] {0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f}, + new float[] {0.0000000000f, -0.0287313368f, -0.0459897147f, -0.0745074328f, -0.0960233266f, -0.1261492408f, -0.1762757894f, -0.2502829383f}, + new float[] {0.0000000000f, -0.0556601118f, -0.0890412670f, -0.1440264301f, -0.1853028382f, -0.2426823129f, -0.3367058477f, -0.4708550466f}, + new float[] {0.0000000000f, -0.0794840594f, -0.1270461238f, -0.2050378347f, -0.2631625097f, -0.3431234916f, -0.4712181245f, -0.6445851354f}, + new float[] {0.0000000000f, -0.0996126459f, -0.1590687758f, -0.2560691819f, -0.3277662204f, -0.4252161335f, -0.5772043556f, -0.7697193058f}, + new float[] {0.0000000000f, -0.1160639735f, -0.1851663774f, -0.2973353800f, -0.3795605619f, -0.4899577884f, -0.6573882369f, -0.8542376401f}, + new float[] {0.0000000000f, -0.1347266752f, -0.2146747714f, -0.3435758752f, -0.4370171396f, -0.5603805303f, -0.7401895046f, -0.9282538388f}, + new float[] {0.0000000000f, -0.1477548470f, -0.2352041647f, -0.3754446647f, -0.4761965776f, -0.6073919186f, -0.7922618830f, -0.9650271071f}, + new float[] {0.0000000000f, -0.1567705832f, -0.2493736450f, -0.3972801182f, -0.5028167951f, -0.6387918458f, -0.8253153651f, -0.9829468369f}, + new float[] {0.0000000000f, -0.1630082348f, -0.2591578860f, -0.4122758299f, -0.5209834064f, -0.6599420072f, -0.8466868694f, -0.9916506943f}, + new float[] {0.0000000000f, -0.1673373610f, -0.2659389001f, -0.4226275012f, -0.5334660781f, -0.6743342664f, -0.8607776784f, -0.9958922202f}, + new float[] {0.0000000000f, -0.1718417832f, -0.2729859267f, -0.4333482310f, -0.5463417868f, -0.6890531546f, -0.8747799456f, -0.9987285320f}, + new float[] {0.0000000000f, -0.1743316967f, -0.2768774604f, -0.4392518725f, -0.5534087104f, -0.6970748701f, -0.8822268738f, -0.9996030552f}, + new float[] {0.0000000000f, -0.1757175038f, -0.2790421580f, -0.4425306221f, -0.5573261722f, -0.7015037013f, -0.8862802834f, -0.9998754073f}, + new float[] {0.0000000000f, -0.1764921355f, -0.2802517850f, -0.4443611583f, -0.5595110229f, -0.7039681080f, -0.8885173967f, -0.9999607689f}, + new float[] {0.0000000000f, -0.1769262394f, -0.2809295540f, -0.4453862969f, -0.5607337966f, -0.7053456119f, -0.8897620516f, -0.9999876239f} + }; + + public static float[][] sincos_alphas_B_normal = + { + new float[] {0.0561454100f, 0.0526385859f, 0.0472937334f, 0.0338410641f, 0.0207261065f, 0.0028205635f, 0.0028205635f, 0.0028205635f}, + new float[] {0.1249065138f, 0.1173697697f, 0.1057888284f, 0.0761985131f, 0.0468732723f, 0.0063956103f, 0.0063956103f, 0.0063956103f}, + new float[] {0.1956693050f, 0.1846090179f, 0.1673645109f, 0.1220621836f, 0.0757362479f, 0.0103882630f, 0.0103882630f, 0.0103882630f}, + new float[] {0.3015113269f, 0.2870525790f, 0.2637738799f, 0.1984573949f, 0.1260749909f, 0.0175600126f, 0.0175600126f, 0.0175600126f}, + new float[] {0.4078449476f, 0.3929852420f, 0.3680589270f, 0.2911029124f, 0.1934512363f, 0.0278686716f, 0.0278686716f, 0.0278686716f}, + new float[] {0.5336171261f, 0.5226637762f, 0.5033652606f, 0.4349162672f, 0.3224682122f, 0.0521999036f, 0.0521999036f, 0.0521999036f}, + new float[] {0.6219832023f, 0.6161847276f, 0.6057251063f, 0.5654342668f, 0.4826149915f, 0.1058044758f, 0.1058044758f, 0.1058044758f}, + new float[] {0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f}, + new float[] {0.7830305572f, 0.7876016373f, 0.7956739618f, 0.8247933372f, 0.8758325942f, 0.9943869542f, 0.9943869542f, 0.9943869542f}, + new float[] {0.8457261833f, 0.8525388778f, 0.8640737401f, 0.9004708933f, 0.9465802987f, 0.9986366532f, 0.9986366532f, 0.9986366532f}, + new float[] {0.9130511848f, 0.9195447612f, 0.9298024282f, 0.9566917233f, 0.9811098801f, 0.9996115928f, 0.9996115928f, 0.9996115928f}, + new float[] {0.9534625907f, 0.9579148236f, 0.9645845234f, 0.9801095128f, 0.9920207064f, 0.9998458099f, 0.9998458099f, 0.9998458099f}, + new float[] {0.9806699215f, 0.9828120260f, 0.9858950861f, 0.9925224431f, 0.9971278825f, 0.9999460406f, 0.9999460406f, 0.9999460406f}, + new float[] {0.9921685024f, 0.9930882705f, 0.9943886135f, 0.9970926648f, 0.9989008403f, 0.9999795479f, 0.9999795479f, 0.9999795479f}, + new float[] {0.9984226014f, 0.9986136287f, 0.9988810254f, 0.9994272242f, 0.9997851906f, 0.9999960221f, 0.9999960221f, 0.9999960221f} + }; + + public static float[][] sincos_alphas_B_fine = + { + new float[] {0.0031622158f, 0.0029630181f, 0.0026599892f, 0.0019002704f, 0.0011626042f, 0.0001580278f, 0.0001580278f, 0.0001580278f}, + new float[] {0.0056232673f, 0.0052689825f, 0.0047302825f, 0.0033791756f, 0.0020674015f, 0.0002811710f, 0.0002811710f, 0.0002811710f}, + new float[] {0.0099994225f, 0.0093696693f, 0.0084117414f, 0.0060093796f, 0.0036766009f, 0.0005000392f, 0.0005000392f, 0.0005000392f}, + new float[] {0.0177799194f, 0.0166607102f, 0.0149581377f, 0.0106875809f, 0.0065392545f, 0.0008893767f, 0.0008893767f, 0.0008893767f}, + new float[] {0.0316069684f, 0.0296211579f, 0.0265987295f, 0.0190113813f, 0.0116349973f, 0.0015826974f, 0.0015826974f, 0.0015826974f}, + new float[] {0.0561454100f, 0.0526385859f, 0.0472937334f, 0.0338410641f, 0.0207261065f, 0.0028205635f, 0.0028205635f, 0.0028205635f}, + new float[] {0.0791834041f, 0.0742798103f, 0.0667907269f, 0.0478705292f, 0.0293500747f, 0.0039966755f, 0.0039966755f, 0.0039966755f}, + new float[] {0.1115021177f, 0.1047141985f, 0.0943053154f, 0.0678120561f, 0.0416669150f, 0.0056813213f, 0.0056813213f, 0.0056813213f}, + new float[] {0.1565355066f, 0.1473258371f, 0.1330924027f, 0.0963282233f, 0.0594509113f, 0.0081277946f, 0.0081277946f, 0.0081277946f}, + new float[] {0.2184643682f, 0.2064579524f, 0.1876265439f, 0.1375744167f, 0.0856896681f, 0.0117817338f, 0.0117817338f, 0.0117817338f}, + new float[] {0.3015113269f, 0.2870525790f, 0.2637738799f, 0.1984573949f, 0.1260749909f, 0.0175600126f, 0.0175600126f, 0.0175600126f}, + new float[] {0.3698741335f, 0.3547727297f, 0.3298252076f, 0.2556265829f, 0.1665990017f, 0.0236344541f, 0.0236344541f, 0.0236344541f}, + new float[] {0.4480623975f, 0.4339410024f, 0.4098613774f, 0.3322709108f, 0.2266784729f, 0.0334094131f, 0.0334094131f, 0.0334094131f}, + new float[] {0.5336171261f, 0.5226637762f, 0.5033652606f, 0.4349162672f, 0.3224682122f, 0.0521999036f, 0.0521999036f, 0.0521999036f}, + new float[] {0.6219832023f, 0.6161847276f, 0.6057251063f, 0.5654342668f, 0.4826149915f, 0.1058044758f, 0.1058044758f, 0.1058044758f}, + new float[] {0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f, 0.7071067657f}, + new float[] {0.7830305572f, 0.7876016373f, 0.7956739618f, 0.8247933372f, 0.8758325942f, 0.9943869542f, 0.9943869542f, 0.9943869542f}, + new float[] {0.8457261833f, 0.8525388778f, 0.8640737401f, 0.9004708933f, 0.9465802987f, 0.9986366532f, 0.9986366532f, 0.9986366532f}, + new float[] {0.8940022267f, 0.9009412572f, 0.9121477564f, 0.9431839770f, 0.9739696219f, 0.9994417480f, 0.9994417480f, 0.9994417480f}, + new float[] {0.9290818561f, 0.9349525662f, 0.9440420138f, 0.9667755833f, 0.9860247275f, 0.9997206664f, 0.9997206664f, 0.9997206664f}, + new float[] {0.9534625907f, 0.9579148236f, 0.9645845234f, 0.9801095128f, 0.9920207064f, 0.9998458099f, 0.9998458099f, 0.9998458099f}, + new float[] {0.9758449068f, 0.9784554646f, 0.9822404252f, 0.9904914275f, 0.9963218730f, 0.9999305926f, 0.9999305926f, 0.9999305926f}, + new float[] {0.9876723320f, 0.9890880155f, 0.9911036356f, 0.9953496173f, 0.9982312259f, 0.9999669685f, 0.9999669685f, 0.9999669685f}, + new float[] {0.9937641889f, 0.9945023501f, 0.9955433130f, 0.9976981117f, 0.9991315558f, 0.9999838610f, 0.9999838610f, 0.9999838610f}, + new float[] {0.9968600642f, 0.9972374385f, 0.9977670024f, 0.9988535464f, 0.9995691924f, 0.9999920129f, 0.9999920129f, 0.9999920129f}, + new float[] {0.9984226014f, 0.9986136287f, 0.9988810254f, 0.9994272242f, 0.9997851906f, 0.9999960221f, 0.9999960221f, 0.9999960221f}, + new float[] {0.9995003746f, 0.9995611974f, 0.9996461891f, 0.9998192657f, 0.9999323103f, 0.9999987475f, 0.9999987475f, 0.9999987475f}, + new float[] {0.9998419236f, 0.9998611991f, 0.9998881193f, 0.9999428861f, 0.9999786185f, 0.9999996045f, 0.9999996045f, 0.9999996045f}, + new float[] {0.9999500038f, 0.9999561034f, 0.9999646206f, 0.9999819429f, 0.9999932409f, 0.9999998750f, 0.9999998750f, 0.9999998750f}, + new float[] {0.9999841890f, 0.9999861183f, 0.9999888121f, 0.9999942902f, 0.9999978628f, 0.9999999605f, 0.9999999605f, 0.9999999605f}, + new float[] {0.9999950000f, 0.9999956102f, 0.9999964621f, 0.9999981945f, 0.9999993242f, 0.9999999875f, 0.9999999875f, 0.9999999875f} + }; + + public static float[][] cos_gammas_normal = + { + new float[] {1.0000000000f, 0.9841239707f, 0.9594738226f, 0.8946843024f, 0.8269341029f, 0.7245688486f, 0.7245688486f, 0.7245688486f}, + new float[] {1.0000000000f, 0.9849690570f, 0.9617776789f, 0.9020941550f, 0.8436830391f, 0.7846832804f, 0.7846832804f, 0.7846832804f}, + new float[] {1.0000000000f, 0.9871656089f, 0.9676774734f, 0.9199102884f, 0.8785067015f, 0.8464232214f, 0.8464232214f, 0.8464232214f}, + new float[] {1.0000000000f, 0.9913533967f, 0.9786000177f, 0.9496063381f, 0.9277157252f, 0.9133354077f, 0.9133354077f, 0.9133354077f}, + new float[] {1.0000000000f, 0.9948924435f, 0.9875319180f, 0.9716329849f, 0.9604805241f, 0.9535949574f, 0.9535949574f, 0.9535949574f}, + new float[] {1.0000000000f, 0.9977406278f, 0.9945423840f, 0.9878736667f, 0.9833980494f, 0.9807207440f, 0.9807207440f, 0.9807207440f}, + new float[] {1.0000000000f, 0.9990607067f, 0.9977417734f, 0.9950323970f, 0.9932453273f, 0.9921884740f, 0.9921884740f, 0.9921884740f}, + new float[] {1.0000000000f, 0.9998081748f, 0.9995400312f, 0.9989936459f, 0.9986365356f, 0.9984265591f, 0.9984265591f, 0.9984265591f} + }; + + public static float[][] cos_gammas_fine = + { + new float[] {1.0000000000f, 0.9841239707f, 0.9594738226f, 0.8946843024f, 0.8269341029f, 0.7245688486f, 0.7245688486f, 0.7245688486f}, + new float[] {1.0000000000f, 0.9849690570f, 0.9617776789f, 0.9020941550f, 0.8436830391f, 0.7846832804f, 0.7846832804f, 0.7846832804f}, + new float[] {1.0000000000f, 0.9871656089f, 0.9676774734f, 0.9199102884f, 0.8785067015f, 0.8464232214f, 0.8464232214f, 0.8464232214f}, + new float[] {1.0000000000f, 0.9899597309f, 0.9750098690f, 0.9402333855f, 0.9129698759f, 0.8943765944f, 0.8943765944f, 0.8943765944f}, + new float[] {1.0000000000f, 0.9926607607f, 0.9819295710f, 0.9580160104f, 0.9404993670f, 0.9293004472f, 0.9293004472f, 0.9293004472f}, + new float[] {1.0000000000f, 0.9948924435f, 0.9875319180f, 0.9716329849f, 0.9604805241f, 0.9535949574f, 0.9535949574f, 0.9535949574f}, + new float[] {1.0000000000f, 0.9972074644f, 0.9932414270f, 0.9849197629f, 0.9792926592f, 0.9759092525f, 0.9759092525f, 0.9759092525f}, + new float[] {1.0000000000f, 0.9985361982f, 0.9964742028f, 0.9922136306f, 0.9893845420f, 0.9877041371f, 0.9877041371f, 0.9877041371f}, + new float[] {1.0000000000f, 0.9992494366f, 0.9981967170f, 0.9960386625f, 0.9946185834f, 0.9937800239f, 0.9937800239f, 0.9937800239f}, + new float[] {1.0000000000f, 0.9996194722f, 0.9990869422f, 0.9979996269f, 0.9972873651f, 0.9968679747f, 0.9968679747f, 0.9968679747f}, + new float[] {1.0000000000f, 0.9998081748f, 0.9995400312f, 0.9989936459f, 0.9986365356f, 0.9984265591f, 0.9984265591f, 0.9984265591f}, + new float[] {1.0000000000f, 0.9999390971f, 0.9998540271f, 0.9996809352f, 0.9995679735f, 0.9995016284f, 0.9995016284f, 0.9995016284f}, + new float[] {1.0000000000f, 0.9999807170f, 0.9999537862f, 0.9998990191f, 0.9998632947f, 0.9998423208f, 0.9998423208f, 0.9998423208f}, + new float[] {1.0000000000f, 0.9999938979f, 0.9999853814f, 0.9999680568f, 0.9999567596f, 0.9999501270f, 0.9999501270f, 0.9999501270f}, + new float[] {1.0000000000f, 0.9999980703f, 0.9999953731f, 0.9999898968f, 0.9999863277f, 0.9999842265f, 0.9999842265f, 0.9999842265f}, + new float[] {1.0000000000f, 0.9999993891f, 0.9999985397f, 0.9999968037f, 0.9999956786f, 0.9999950155f, 0.9999950155f, 0.9999950155f} + }; + + public static float[][] sin_gammas_normal = + { + new float[] {0.0000000000f, 0.1774824223f, 0.2817977711f, 0.4466990028f, 0.5622988435f, 0.6892024258f, 0.6892024258f, 0.6892024258f}, + new float[] {0.0000000000f, 0.1727308798f, 0.2738315110f, 0.4315392630f, 0.5368416242f, 0.6198968861f, 0.6198968861f, 0.6198968861f}, + new float[] {0.0000000000f, 0.1596999079f, 0.2521910140f, 0.3921288836f, 0.4777300236f, 0.5325107795f, 0.5325107795f, 0.5325107795f}, + new float[] {0.0000000000f, 0.1312190642f, 0.2057717310f, 0.3134450552f, 0.3732874674f, 0.4072080955f, 0.4072080955f, 0.4072080955f}, + new float[] {0.0000000000f, 0.1009407043f, 0.1574189028f, 0.2364938532f, 0.2783471983f, 0.3010924396f, 0.3010924396f, 0.3010924396f}, + new float[] {0.0000000000f, 0.0671836269f, 0.1043333428f, 0.1552598422f, 0.1814615013f, 0.1954144885f, 0.1954144885f, 0.1954144885f}, + new float[] {0.0000000000f, 0.0433324862f, 0.0671666110f, 0.0995516398f, 0.1160332699f, 0.1247478739f, 0.1247478739f, 0.1247478739f}, + new float[] {0.0000000000f, 0.0195860576f, 0.0303269852f, 0.0448519274f, 0.0522022017f, 0.0560750040f, 0.0560750040f, 0.0560750040f} + }; + + public static float[][] sin_gammas_fine = + { + new float[] {0.0000000000f, 0.1774824223f, 0.2817977711f, 0.4466990028f, 0.5622988435f, 0.6892024258f, 0.6892024258f, 0.6892024258f}, + new float[] {0.0000000000f, 0.1727308798f, 0.2738315110f, 0.4315392630f, 0.5368416242f, 0.6198968861f, 0.6198968861f, 0.6198968861f}, + new float[] {0.0000000000f, 0.1596999079f, 0.2521910140f, 0.3921288836f, 0.4777300236f, 0.5325107795f, 0.5325107795f, 0.5325107795f}, + new float[] {0.0000000000f, 0.1413496768f, 0.2221615526f, 0.3405307340f, 0.4080269669f, 0.4473147744f, 0.4473147744f, 0.4473147744f}, + new float[] {0.0000000000f, 0.1209322714f, 0.1892467110f, 0.2867147079f, 0.3397954394f, 0.3693246252f, 0.3693246252f, 0.3693246252f}, + new float[] {0.0000000000f, 0.1009407043f, 0.1574189028f, 0.2364938532f, 0.2783471983f, 0.3010924396f, 0.3010924396f, 0.3010924396f}, + new float[] {0.0000000000f, 0.0746811420f, 0.1160666523f, 0.1730117353f, 0.2024497161f, 0.2181768341f, 0.2181768341f, 0.2181768341f}, + new float[] {0.0000000000f, 0.0540875291f, 0.0838997203f, 0.1245476266f, 0.1453211203f, 0.1563346972f, 0.1563346972f, 0.1563346972f}, + new float[] {0.0000000000f, 0.0387371058f, 0.0600276114f, 0.0889212171f, 0.1036044086f, 0.1113609634f, 0.1113609634f, 0.1113609634f}, + new float[] {0.0000000000f, 0.0275846110f, 0.0427233177f, 0.0632198125f, 0.0736064637f, 0.0790837596f, 0.0790837596f, 0.0790837596f}, + new float[] {0.0000000000f, 0.0195860576f, 0.0303269852f, 0.0448519274f, 0.0522022017f, 0.0560750040f, 0.0560750040f, 0.0560750040f}, + new float[] {0.0000000000f, 0.0110363955f, 0.0170857974f, 0.0252592108f, 0.0293916021f, 0.0315673054f, 0.0315673054f, 0.0315673054f}, + new float[] {0.0000000000f, 0.0062101284f, 0.0096138203f, 0.0142109649f, 0.0165345659f, 0.0177576316f, 0.0177576316f, 0.0177576316f}, + new float[] {0.0000000000f, 0.0034934509f, 0.0054071189f, 0.0079928316f, 0.0092994041f, 0.0099871631f, 0.0099871631f, 0.0099871631f}, + new float[] {0.0000000000f, 0.0019645397f, 0.0030419905f, 0.0044951511f, 0.0052291853f, 0.0056166498f, 0.0056166498f, 0.0056166498f}, + new float[] {0.0000000000f, 0.0011053943f, 0.0017089869f, 0.0025283670f, 0.0029398552f, 0.0031573685f, 0.0031573685f, 0.0031573685f} + }; + + public static float[] sf_iid_normal = + { + 1.4119827747f, 1.4031381607f, 1.3868767023f, + 1.3483997583f, 1.2912493944f, 1.1960374117f, + 1.1073724031f, 1.0000000000f, 0.8796171546f, + 0.7546485662f, 0.5767799020f, 0.4264014363f, + 0.2767182887f, 0.1766446233f, 0.0794016272f + }; + + public static float[] sf_iid_fine = + { + 1.4142065048f, 1.4141912460f, 1.4141428471f, + 1.4139900208f, 1.4135069847f, 1.4119827747f, + 1.4097729921f, 1.4053947926f, 1.3967796564f, + 1.3800530434f, 1.3483997583f, 1.3139201403f, + 1.2643101215f, 1.1960374117f, 1.1073724031f, + 1.0000000000f, 0.8796171546f, 0.7546485662f, + 0.6336560845f, 0.5230810642f, 0.4264014363f, + 0.3089554012f, 0.2213746458f, 0.1576878875f, + 0.1119822487f, 0.0794016272f, 0.0446990170f, + 0.0251446925f, 0.0141414283f, 0.0079525812f, + 0.0044721137f + }; + public static float[] ipdopd_cos_tab = + { + 1.000000000000000f, + 0.707106781186548f, + 0.000000000000000f, + -0.707106781186547f, + -1.000000000000000f, + -0.707106781186548f, + -0.000000000000000f, + 0.707106781186547f, + 1.000000000000000f + }; + + public static float[] ipdopd_sin_tab = + { + 0.000000000000000f, + 0.707106781186547f, + 1.000000000000000f, + 0.707106781186548f, + 0.000000000000000f, + -0.707106781186547f, + -1.000000000000000f, + -0.707106781186548f, + -0.000000000000000f + }; + } +} diff --git a/SharpJaad.AAC/SampleBuffer.cs b/SharpJaad.AAC/SampleBuffer.cs new file mode 100644 index 0000000..abd2a28 --- /dev/null +++ b/SharpJaad.AAC/SampleBuffer.cs @@ -0,0 +1,68 @@ +namespace SharpJaad.AAC +{ + /// + /// The SampleBuffer holds the decoded AAC frame. It contains the raw PCM data and its format. + /// + public class SampleBuffer + { + public int SampleRate { get; private set; } + public int Channels { get; private set; } + public int BitsPerSample { get; private set; } + public double Length { get; private set; } + public double Bitrate { get; private set; } + public double EncodedBitrate { get; private set; } + public byte[] Data { get; private set; } + public bool BigEndian { get; private set; } + + public SampleBuffer() + { + Data = new byte[0]; + SampleRate = 0; + Channels = 0; + BitsPerSample = 0; + BigEndian = true; + } + + /// + /// Sets the endianness for the data. + /// + /// if true the data will be in big endian, else in little endian + public void SetBigEndian(bool bigEndian) + { + if (bigEndian != BigEndian) + { + byte tmp; + for (int i = 0; i < Data.Length; i += 2) + { + tmp = Data[i]; + Data[i] = Data[i + 1]; + Data[i + 1] = tmp; + } + BigEndian = bigEndian; + } + } + + public void SetData(byte[] data, int sampleRate, int channels, int bitsPerSample, int bitsRead) + { + Data = data; + SampleRate = sampleRate; + Channels = channels; + BitsPerSample = bitsPerSample; + + if (sampleRate == 0) + { + Length = 0; + Bitrate = 0; + EncodedBitrate = 0; + } + else + { + int bytesPerSample = bitsPerSample / 8; //usually 2 + int samplesPerChannel = data.Length / (bytesPerSample * channels); //=1024 + Length = samplesPerChannel / (double)sampleRate; + Bitrate = samplesPerChannel * bitsPerSample * channels / Length; + EncodedBitrate = bitsRead / Length; + } + } + } +} diff --git a/SharpJaad.AAC/SampleFrequency.cs b/SharpJaad.AAC/SampleFrequency.cs new file mode 100644 index 0000000..4bbd7d9 --- /dev/null +++ b/SharpJaad.AAC/SampleFrequency.cs @@ -0,0 +1,280 @@ +namespace SharpJaad.AAC +{ + public enum SampleFrequency : int + { + SAMPLE_FREQUENCY_96000 = 0, + SAMPLE_FREQUENCY_88200 = 1, + SAMPLE_FREQUENCY_64000 = 2, + SAMPLE_FREQUENCY_48000 = 3, + SAMPLE_FREQUENCY_44100 = 4, + SAMPLE_FREQUENCY_32000 = 5, + SAMPLE_FREQUENCY_24000 = 6, + SAMPLE_FREQUENCY_22050 = 7, + SAMPLE_FREQUENCY_16000 = 8, + SAMPLE_FREQUENCY_12000 = 9, + SAMPLE_FREQUENCY_11025 = 10, + SAMPLE_FREQUENCY_8000 = 11, + SAMPLE_FREQUENCY_NONE = -1 + } + + public static class SampleFrequencyExtensions + { + public static SampleFrequency FromFrequency(int frequency) + { + SampleFrequency ret; + switch (frequency) + { + case 96000: + ret = SampleFrequency.SAMPLE_FREQUENCY_96000; + break; + + case 88200: + ret = SampleFrequency.SAMPLE_FREQUENCY_88200; + break; + + case 64000: + ret = SampleFrequency.SAMPLE_FREQUENCY_64000; + break; + + case 48000: + ret = SampleFrequency.SAMPLE_FREQUENCY_48000; + break; + + case 44100: + ret = SampleFrequency.SAMPLE_FREQUENCY_44100; + break; + + case 32000: + ret = SampleFrequency.SAMPLE_FREQUENCY_32000; + break; + + case 24000: + ret = SampleFrequency.SAMPLE_FREQUENCY_24000; + break; + + case 22050: + ret = SampleFrequency.SAMPLE_FREQUENCY_22050; + break; + + case 16000: + ret = SampleFrequency.SAMPLE_FREQUENCY_16000; + break; + + case 12000: + ret = SampleFrequency.SAMPLE_FREQUENCY_12000; + break; + + case 11025: + ret = SampleFrequency.SAMPLE_FREQUENCY_11025; + break; + + case 8000: + ret = SampleFrequency.SAMPLE_FREQUENCY_8000; + break; + + default: + ret = SampleFrequency.SAMPLE_FREQUENCY_NONE; + break; + } + + return ret; + } + + public static int GetFrequency(this SampleFrequency frequency) + { + int ret; + switch (frequency) + { + case SampleFrequency.SAMPLE_FREQUENCY_96000: + ret = 96000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_88200: + ret = 88200; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_64000: + ret = 64000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_48000: + ret = 48000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_44100: + ret = 44100; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_32000: + ret = 32000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_24000: + ret = 24000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_22050: + ret = 22050; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_16000: + ret = 16000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_12000: + ret = 12000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_11025: + ret = 11025; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_8000: + ret = 8000; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_NONE: + default: + ret = 0; + break; + } + + return ret; + } + + public static int GetMaximalPredictionSFB(this SampleFrequency frequency) + { + int ret; + switch (frequency) + { + case SampleFrequency.SAMPLE_FREQUENCY_96000: + case SampleFrequency.SAMPLE_FREQUENCY_88200: + ret = 33; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_64000: + ret = 38; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_48000: + case SampleFrequency.SAMPLE_FREQUENCY_44100: + case SampleFrequency.SAMPLE_FREQUENCY_32000: + ret = 40; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_24000: + case SampleFrequency.SAMPLE_FREQUENCY_22050: + ret = 41; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_16000: + case SampleFrequency.SAMPLE_FREQUENCY_12000: + case SampleFrequency.SAMPLE_FREQUENCY_11025: + ret = 37; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_8000: + ret = 34; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_NONE: + default: + ret = 0; + break; + } + + return ret; + } + + public static int GetPredictorCount(this SampleFrequency frequency) + { + int ret; + switch (frequency) + { + case SampleFrequency.SAMPLE_FREQUENCY_96000: + case SampleFrequency.SAMPLE_FREQUENCY_88200: + ret = 512; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_64000: + ret = 664; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_48000: + case SampleFrequency.SAMPLE_FREQUENCY_44100: + case SampleFrequency.SAMPLE_FREQUENCY_32000: + ret = 672; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_24000: + case SampleFrequency.SAMPLE_FREQUENCY_22050: + ret = 652; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_16000: + case SampleFrequency.SAMPLE_FREQUENCY_12000: + case SampleFrequency.SAMPLE_FREQUENCY_11025: + case SampleFrequency.SAMPLE_FREQUENCY_8000: + ret = 664; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_NONE: + default: + ret = 0; + break; + } + + return ret; + } + + public static int GetMaximalTNS_SFB(this SampleFrequency frequency, bool shortWindow) + { + int ret; + switch (frequency) + { + case SampleFrequency.SAMPLE_FREQUENCY_96000: + case SampleFrequency.SAMPLE_FREQUENCY_88200: + ret = shortWindow ? 9 : 31; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_64000: + ret = shortWindow ? 10 : 34; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_48000: + ret = shortWindow ? 14 : 40; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_44100: + ret = shortWindow ? 14 : 42; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_32000: + ret = shortWindow ? 14 : 51; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_24000: + case SampleFrequency.SAMPLE_FREQUENCY_22050: + ret = shortWindow ? 14 : 46; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_16000: + case SampleFrequency.SAMPLE_FREQUENCY_12000: + case SampleFrequency.SAMPLE_FREQUENCY_11025: + ret = shortWindow ? 14 : 42; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_8000: + ret = shortWindow ? 14 : 39; + break; + + case SampleFrequency.SAMPLE_FREQUENCY_NONE: + default: + ret = 0; + break; + } + + return ret; + } + } +} diff --git a/SharpJaad.AAC/Sbr/AnalysisFilterbank.cs b/SharpJaad.AAC/Sbr/AnalysisFilterbank.cs new file mode 100644 index 0000000..fbce5c3 --- /dev/null +++ b/SharpJaad.AAC/Sbr/AnalysisFilterbank.cs @@ -0,0 +1,105 @@ +using SharpJaad.AAC.Tools; + +namespace SharpJaad.AAC.Sbr +{ + public class AnalysisFilterbank + { + private float[] _x; //x is implemented as double ringbuffer + private int _xIndex; //ringbuffer index + private int _channels; + + public AnalysisFilterbank(int channels) + { + _channels = channels; + _x = new float[2 * channels * 10]; + _xIndex = 0; + } + + public void Reset() + { + Arrays.Fill(_x, 0); + } + + public void SbrQmfAnalysis32(SBR sbr, float[] input, float[,,,] X, int ch, int offset, int kx) + { + float[] u = new float[64]; + float[] in_real = new float[32], in_imag = new float[32]; + float[] out_real = new float[32], out_imag = new float[32]; + int iin = 0; + int l; + + /* qmf subsample l */ + for (l = 0; l < sbr._numTimeSlotsRate; l++) + { + int n; + + /* shift input buffer x */ + /* input buffer is not shifted anymore, x is implemented as double ringbuffer */ + //memmove(qmfa.x + 32, qmfa.x, (320-32)*sizeof(real_t)); + + /* add new samples to input buffer x */ + for (n = 32 - 1; n >= 0; n--) + { + _x[_xIndex + n] = _x[_xIndex + n + 320] = input[iin++]; + } + + /* window and summation to create array u */ + for (n = 0; n < 64; n++) + { + u[n] = _x[_xIndex + n] * FilterbankTable.qmf_c[2 * n] + + _x[_xIndex + n + 64] * FilterbankTable.qmf_c[2 * (n + 64)] + + _x[_xIndex + n + 128] * FilterbankTable.qmf_c[2 * (n + 128)] + + _x[_xIndex + n + 192] * FilterbankTable.qmf_c[2 * (n + 192)] + + _x[_xIndex + n + 256] * FilterbankTable.qmf_c[2 * (n + 256)]; + } + + /* update ringbuffer index */ + _xIndex -= 32; + if (_xIndex < 0) + _xIndex = 320 - 32; + + /* calculate 32 subband samples by introducing X */ + // Reordering of data moved from DCT_IV to here + in_imag[31] = u[1]; + in_real[0] = u[0]; + for (n = 1; n < 31; n++) + { + in_imag[31 - n] = u[n + 1]; + in_real[n] = -u[64 - n]; + } + in_imag[0] = u[32]; + in_real[31] = -u[33]; + + // dct4_kernel is DCT_IV without reordering which is done before and after FFT + DCT.Dct4Kernel(in_real, in_imag, out_real, out_imag); + + // Reordering of data moved from DCT_IV to here + for (n = 0; n < 16; n++) + { + if (2 * n + 1 < kx) + { + X[ch, l + offset, 2 * n, 0] = 2.0f * out_real[n]; + X[ch, l + offset, 2 * n, 1] = 2.0f * out_imag[n]; + X[ch, l + offset, 2 * n + 1, 0] = -2.0f * out_imag[31 - n]; + X[ch, l + offset, 2 * n + 1, 1] = -2.0f * out_real[31 - n]; + } + else + { + if (2 * n < kx) + { + X[ch, l + offset, 2 * n, 0] = 2.0f * out_real[n]; + X[ch, l + offset, 2 * n, 1] = 2.0f * out_imag[n]; + } + else + { + X[ch, l + offset, 2 * n, 0] = 0; + X[ch, l + offset, 2 * n, 1] = 0; + } + X[ch, l + offset, 2 * n + 1, 0] = 0; + X[ch, l + offset, 2 * n + 1, 1] = 0; + } + } + } + } + } +} diff --git a/SharpJaad.AAC/Sbr/Constants.cs b/SharpJaad.AAC/Sbr/Constants.cs new file mode 100644 index 0000000..8e70cfd --- /dev/null +++ b/SharpJaad.AAC/Sbr/Constants.cs @@ -0,0 +1,37 @@ +namespace SharpJaad.AAC.Sbr +{ + public static class Constants + { + public static int[] startMinTable = { 7, 7, 10, 11, 12, 16, 16, 17, 24, 32, 35, 48 }; + public static int[] offsetIndexTable = { 5, 5, 4, 4, 4, 3, 2, 1, 0, 6, 6, 6 }; + public static int[][] OFFSET = { + new int[] {-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}, //16000 + new int[] {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13}, //22050 + new int[] {-5, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16}, //24000 + new int[] {-6, -4, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16}, //32000 + new int[] {-4, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16, 20}, //44100-64000 + new int[] {-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16, 20, 24}, //>64000 + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16, 20, 24, 28, 33} + }; + + public const int EXTENSION_ID_PS = 2; + public const int MAX_NTSRHFG = 40; //maximum of number_time_slots * rate + HFGen. 16*2+8 + public const int MAX_NTSR = 32; //max number_time_slots * rate, ok for DRM and not DRM mode + public const int MAX_M = 49; //maximum value for M + public const int MAX_L_E = 5; //maximum value for L_E + public const int EXT_SBR_DATA = 13; + public const int EXT_SBR_DATA_CRC = 14; + public const int FIXFIX = 0; + public const int FIXVAR = 1; + public const int VARFIX = 2; + public const int VARVAR = 3; + public const int LO_RES = 0; + public const int HI_RES = 1; + public const int NO_TIME_SLOTS_960 = 15; + public const int NO_TIME_SLOTS = 16; + public const int RATE = 2; + public const int NOISE_FLOOR_OFFSET = 6; + public const int T_HFGEN = 8; + public const int T_HFADJ = 2; + } +} diff --git a/SharpJaad.AAC/Sbr/DCT.cs b/SharpJaad.AAC/Sbr/DCT.cs new file mode 100644 index 0000000..f24ddf8 --- /dev/null +++ b/SharpJaad.AAC/Sbr/DCT.cs @@ -0,0 +1,408 @@ +namespace SharpJaad.AAC.Sbr +{ + public static class DCT + { + private static int n = 32; + + // w_array_real[i] = cos(2*M_PI*i/32) + private static float[] w_array_real = { + 1.000000000000000f, 0.980785279337272f, + 0.923879528329380f, 0.831469603195765f, + 0.707106765732237f, 0.555570210304169f, + 0.382683402077046f, 0.195090284503576f, + 0.000000000000000f, -0.195090370246552f, + -0.382683482845162f, -0.555570282993553f, + -0.707106827549476f, -0.831469651765257f, + -0.923879561784627f, -0.980785296392607f + }; + + // w_array_imag[i] = sin(-2*M_PI*i/32) + private static float[] w_array_imag = { + 0.000000000000000f, -0.195090327375064f, + -0.382683442461104f, -0.555570246648862f, + -0.707106796640858f, -0.831469627480512f, + -0.923879545057005f, -0.980785287864940f, + -1.000000000000000f, -0.980785270809601f, + -0.923879511601754f, -0.831469578911016f, + -0.707106734823616f, -0.555570173959476f, + -0.382683361692986f, -0.195090241632088f + }; + + private static float[] dct4_64_tab = { + 0.999924719333649f, 0.998118102550507f, + 0.993906974792480f, 0.987301409244537f, + 0.978317379951477f, 0.966976463794708f, + 0.953306019306183f, 0.937339007854462f, + 0.919113874435425f, 0.898674488067627f, + 0.876070082187653f, 0.851355195045471f, + 0.824589252471924f, 0.795836925506592f, + 0.765167236328125f, 0.732654273509979f, + 0.698376238346100f, 0.662415742874146f, + 0.624859452247620f, 0.585797846317291f, + 0.545324981212616f, 0.503538429737091f, + 0.460538715124130f, 0.416429549455643f, + 0.371317148208618f, 0.325310230255127f, + 0.278519600629807f, 0.231058135628700f, + 0.183039888739586f, 0.134580686688423f, + 0.085797272622585f, 0.036807164549828f, + -1.012196302413940f, -1.059438824653626f, + -1.104129195213318f, -1.146159529685974f, + -1.185428738594055f, -1.221842169761658f, + -1.255311965942383f, -1.285757660865784f, + -1.313105940818787f, -1.337290763854981f, + -1.358253836631775f, -1.375944852828980f, + -1.390321016311646f, -1.401347875595093f, + -1.408998727798462f, -1.413255214691162f, + -1.414107084274292f, -1.411552190780640f, + -1.405596733093262f, -1.396255016326904f, + -1.383549690246582f, -1.367511272430420f, + -1.348178386688232f, -1.325597524642944f, + -1.299823284149170f, -1.270917654037476f, + -1.238950133323669f, -1.203998088836670f, + -1.166145324707031f, -1.125483393669128f, + -1.082109928131104f, -1.036129593849182f, + -0.987653195858002f, -0.936797380447388f, + -0.883684754371643f, -0.828443288803101f, + -0.771206021308899f, -0.712110757827759f, + -0.651300072669983f, -0.588920354843140f, + -0.525121808052063f, -0.460058242082596f, + -0.393886327743530f, -0.326765477657318f, + -0.258857429027557f, -0.190325915813446f, + -0.121335685253143f, -0.052053272724152f, + 0.017354607582092f, 0.086720645427704f, + 0.155877828598022f, 0.224659323692322f, + 0.292899727821350f, 0.360434412956238f, + 0.427100926637650f, 0.492738455533981f, + 0.557188928127289f, 0.620297133922577f, + 0.681910991668701f, 0.741881847381592f, + 0.800065577030182f, 0.856321990489960f, + 0.910515367984772f, 0.962515234947205f, + 1.000000000000000f, 0.998795449733734f, + 0.995184719562531f, 0.989176511764526f, + 0.980785250663757f, 0.970031261444092f, + 0.956940352916718f, 0.941544055938721f, + 0.923879504203796f, 0.903989315032959f, + 0.881921231746674f, 0.857728600502014f, + 0.831469595432281f, 0.803207516670227f, + 0.773010432720184f, 0.740951120853424f, + 0.707106769084930f, 0.671558916568756f, + 0.634393274784088f, 0.595699310302734f, + 0.555570185184479f, 0.514102697372437f, + 0.471396654844284f, 0.427555114030838f, + 0.382683426141739f, 0.336889833211899f, + 0.290284633636475f, 0.242980122566223f, + 0.195090234279633f, 0.146730497479439f, + 0.098017133772373f, 0.049067649990320f, + -1.000000000000000f, -1.047863125801086f, + -1.093201875686646f, -1.135906934738159f, + -1.175875544548035f, -1.213011503219605f, + -1.247225046157837f, -1.278433918952942f, + -1.306562900543213f, -1.331544399261475f, + -1.353317975997925f, -1.371831417083740f, + -1.387039899826050f, -1.398906826972961f, + -1.407403707504273f, -1.412510156631470f, + 0f, -1.412510156631470f, + -1.407403707504273f, -1.398906826972961f, + -1.387039899826050f, -1.371831417083740f, + -1.353317975997925f, -1.331544399261475f, + -1.306562900543213f, -1.278433918952942f, + -1.247225046157837f, -1.213011384010315f, + -1.175875544548035f, -1.135907053947449f, + -1.093201875686646f, -1.047863125801086f, + -1.000000000000000f, -0.949727773666382f, + -0.897167563438416f, -0.842446029186249f, + -0.785694956779480f, -0.727051079273224f, + -0.666655659675598f, -0.604654192924500f, + -0.541196048259735f, -0.476434230804443f, + -0.410524487495422f, -0.343625843524933f, + -0.275899350643158f, -0.207508206367493f, + -0.138617098331451f, -0.069392144680023f, + 0f, 0.069392263889313f, + 0.138617157936096f, 0.207508206367493f, + 0.275899469852448f, 0.343625962734222f, + 0.410524636507034f, 0.476434201002121f, + 0.541196107864380f, 0.604654192924500f, + 0.666655719280243f, 0.727051138877869f, + 0.785695075988770f, 0.842446029186249f, + 0.897167563438416f, 0.949727773666382f + }; + + private static int[] bit_rev_tab = { 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23, 15, 31 }; + + // FFT decimation in frequency + // 4*16*2+16=128+16=144 multiplications + // 6*16*2+10*8+4*16*2=192+80+128=400 additions + private static void FftDif(float[] Real, float[] Imag) + { + float w_real, w_imag; // For faster access + float point1_real, point1_imag, point2_real, point2_imag; // For faster access + int j, i, i2, w_index; // Counters + + // First 2 stages of 32 point FFT decimation in frequency + // 4*16*2=64*2=128 multiplications + // 6*16*2=96*2=192 additions + // Stage 1 of 32 point FFT decimation in frequency + for (i = 0; i < 16; i++) + { + point1_real = Real[i]; + point1_imag = Imag[i]; + i2 = i + 16; + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + w_real = w_array_real[i]; + w_imag = w_array_imag[i]; + + // temp1 = x[i] - x[i2] + point1_real -= point2_real; + point1_imag -= point2_imag; + + // x[i1] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = (x[i] - x[i2]) * w + Real[i2] = point1_real * w_real - point1_imag * w_imag; + Imag[i2] = point1_real * w_imag + point1_imag * w_real; + } + // Stage 2 of 32 point FFT decimation in frequency + for (j = 0, w_index = 0; j < 8; j++, w_index += 2) + { + w_real = w_array_real[w_index]; + w_imag = w_array_imag[w_index]; + + i = j; + point1_real = Real[i]; + point1_imag = Imag[i]; + i2 = i + 8; + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // temp1 = x[i] - x[i2] + point1_real -= point2_real; + point1_imag -= point2_imag; + + // x[i1] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = (x[i] - x[i2]) * w + Real[i2] = point1_real * w_real - point1_imag * w_imag; + Imag[i2] = point1_real * w_imag + point1_imag * w_real; + + i = j + 16; + point1_real = Real[i]; + point1_imag = Imag[i]; + i2 = i + 8; + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // temp1 = x[i] - x[i2] + point1_real -= point2_real; + point1_imag -= point2_imag; + + // x[i1] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = (x[i] - x[i2]) * w + Real[i2] = point1_real * w_real - point1_imag * w_imag; + Imag[i2] = point1_real * w_imag + point1_imag * w_real; + } + + // Stage 3 of 32 point FFT decimation in frequency + // 2*4*2=16 multiplications + // 4*4*2+6*4*2=10*8=80 additions + for (i = 0; i < n; i += 8) + { + i2 = i + 4; + point1_real = Real[i]; + point1_imag = Imag[i]; + + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // out[i1] = point1 + point2 + Real[i] += point2_real; + Imag[i] += point2_imag; + + // out[i2] = point1 - point2 + Real[i2] = point1_real - point2_real; + Imag[i2] = point1_imag - point2_imag; + } + w_real = w_array_real[4]; // = sqrt(2)/2 + // w_imag = -w_real; // = w_array_imag[4]; // = -sqrt(2)/2 + for (i = 1; i < n; i += 8) + { + i2 = i + 4; + point1_real = Real[i]; + point1_imag = Imag[i]; + + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // temp1 = x[i] - x[i2] + point1_real -= point2_real; + point1_imag -= point2_imag; + + // x[i1] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = (x[i] - x[i2]) * w + Real[i2] = (point1_real + point1_imag) * w_real; + Imag[i2] = (point1_imag - point1_real) * w_real; + } + for (i = 2; i < n; i += 8) + { + i2 = i + 4; + point1_real = Real[i]; + point1_imag = Imag[i]; + + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // x[i] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = (x[i] - x[i2]) * (-i) + Real[i2] = point1_imag - point2_imag; + Imag[i2] = point2_real - point1_real; + } + w_real = w_array_real[12]; // = -sqrt(2)/2 + // w_imag = w_real; // = w_array_imag[12]; // = -sqrt(2)/2 + for (i = 3; i < n; i += 8) + { + i2 = i + 4; + point1_real = Real[i]; + point1_imag = Imag[i]; + + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // temp1 = x[i] - x[i2] + point1_real -= point2_real; + point1_imag -= point2_imag; + + // x[i1] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = (x[i] - x[i2]) * w + Real[i2] = (point1_real - point1_imag) * w_real; + Imag[i2] = (point1_real + point1_imag) * w_real; + } + + // Stage 4 of 32 point FFT decimation in frequency (no multiplications) + // 16*4=64 additions + for (i = 0; i < n; i += 4) + { + i2 = i + 2; + point1_real = Real[i]; + point1_imag = Imag[i]; + + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // x[i1] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = x[i] - x[i2] + Real[i2] = point1_real - point2_real; + Imag[i2] = point1_imag - point2_imag; + } + for (i = 1; i < n; i += 4) + { + i2 = i + 2; + point1_real = Real[i]; + point1_imag = Imag[i]; + + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // x[i] = x[i] + x[i2] + Real[i] += point2_real; + Imag[i] += point2_imag; + + // x[i2] = (x[i] - x[i2]) * (-i) + Real[i2] = point1_imag - point2_imag; + Imag[i2] = point2_real - point1_real; + } + + // Stage 5 of 32 point FFT decimation in frequency (no multiplications) + // 16*4=64 additions + for (i = 0; i < n; i += 2) + { + i2 = i + 1; + point1_real = Real[i]; + point1_imag = Imag[i]; + + point2_real = Real[i2]; + point2_imag = Imag[i2]; + + // out[i1] = point1 + point2 + Real[i] += point2_real; + Imag[i] += point2_imag; + + // out[i2] = point1 - point2 + Real[i2] = point1_real - point2_real; + Imag[i2] = point1_imag - point2_imag; + } + + //FFTReorder(Real, Imag); + } + + /* size 64 only! */ + public static void Dct4Kernel(float[] in_real, float[] in_imag, float[] out_real, float[] out_imag) + { + // Tables with bit reverse values for 5 bits, bit reverse of i at i-th position + int i, i_rev; + + /* Step 2: modulate */ + // 3*32=96 multiplications + // 3*32=96 additions + for (i = 0; i < 32; i++) + { + float x_re, x_im, tmp; + x_re = in_real[i]; + x_im = in_imag[i]; + tmp = (x_re + x_im) * dct4_64_tab[i]; + in_real[i] = x_im * dct4_64_tab[i + 64] + tmp; + in_imag[i] = x_re * dct4_64_tab[i + 32] + tmp; + } + + /* Step 3: FFT, but with output in bit reverse order */ + FftDif(in_real, in_imag); + + /* Step 4: modulate + bitreverse reordering */ + // 3*31+2=95 multiplications + // 3*31+2=95 additions + for (i = 0; i < 16; i++) + { + float x_re, x_im, tmp; + i_rev = bit_rev_tab[i]; + x_re = in_real[i_rev]; + x_im = in_imag[i_rev]; + + tmp = (x_re + x_im) * dct4_64_tab[i + 3 * 32]; + out_real[i] = x_im * dct4_64_tab[i + 5 * 32] + tmp; + out_imag[i] = x_re * dct4_64_tab[i + 4 * 32] + tmp; + } + // i = 16, i_rev = 1 = rev(16); + out_imag[16] = (in_imag[1] - in_real[1]) * dct4_64_tab[16 + 3 * 32]; + out_real[16] = (in_real[1] + in_imag[1]) * dct4_64_tab[16 + 3 * 32]; + for (i = 17; i < 32; i++) + { + float x_re, x_im, tmp; + i_rev = bit_rev_tab[i]; + x_re = in_real[i_rev]; + x_im = in_imag[i_rev]; + tmp = (x_re + x_im) * dct4_64_tab[i + 3 * 32]; + out_real[i] = x_im * dct4_64_tab[i + 5 * 32] + tmp; + out_imag[i] = x_re * dct4_64_tab[i + 4 * 32] + tmp; + } + } + } +} diff --git a/SharpJaad.AAC/Sbr/FBT.cs b/SharpJaad.AAC/Sbr/FBT.cs new file mode 100644 index 0000000..43f065e --- /dev/null +++ b/SharpJaad.AAC/Sbr/FBT.cs @@ -0,0 +1,472 @@ +using System; + +namespace SharpJaad.AAC.Sbr +{ + public static class FBT + { + /* calculate the start QMF channel for the master frequency band table */ + /* parameter is also called k0 */ + public static int QmfStartChannel(int bs_start_freq, int bs_samplerate_mode, + SampleFrequency sample_rate) + { + int startMin = Constants.startMinTable[(int)sample_rate]; + int offsetIndex = Constants.offsetIndexTable[(int)sample_rate]; + + if (bs_samplerate_mode != 0) + { + return startMin + Constants.OFFSET[offsetIndex][bs_start_freq]; + } + else + { + return startMin + Constants.OFFSET[6][bs_start_freq]; + } + } + + private static int[] stopMinTable = {13, 15, 20, 21, 23, + 32, 32, 35, 48, 64, 70, 96}; + + private static int[][] STOP_OFFSET_TABLE = { + new int[] {0, 2, 4, 6, 8, 11, 14, 18, 22, 26, 31, 37, 44, 51}, + new int[] {0, 2, 4, 6, 8, 11, 14, 18, 22, 26, 31, 36, 42, 49}, + new int[] {0, 2, 4, 6, 8, 11, 14, 17, 21, 25, 29, 34, 39, 44}, + new int[] {0, 2, 4, 6, 8, 11, 14, 17, 20, 24, 28, 33, 38, 43}, + new int[] {0, 2, 4, 6, 8, 11, 14, 17, 20, 24, 28, 32, 36, 41}, + new int[] {0, 2, 4, 6, 8, 10, 12, 14, 17, 20, 23, 26, 29, 32}, + new int[] {0, 2, 4, 6, 8, 10, 12, 14, 17, 20, 23, 26, 29, 32}, + new int[] {0, 1, 3, 5, 7, 9, 11, 13, 15, 17, 20, 23, 26, 29}, + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16}, + new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new int[] {0, -1, -2, -3, -4, -5, -6, -6, -6, -6, -6, -6, -6, -6}, + new int[] {0, -3, -6, -9, -12, -15, -18, -20, -22, -24, -26, -28, -30, -32} + }; + + /* calculate the stop QMF channel for the master frequency band table */ + /* parameter is also called k2 */ + public static int QmfStopChannel(int bs_stop_freq, SampleFrequency sample_rate, + int k0) + { + if (bs_stop_freq == 15) + { + return Math.Min(64, k0 * 3); + } + else if (bs_stop_freq == 14) + { + return Math.Min(64, k0 * 2); + } + else + { + + int stopMin = stopMinTable[(int)sample_rate]; + + /* bs_stop_freq <= 13 */ + return Math.Min(64, stopMin + STOP_OFFSET_TABLE[(int)sample_rate][Math.Min(bs_stop_freq, 13)]); + } + } + + /* calculate the master frequency table from k0, k2, bs_freq_scale + and bs_alter_scale + + version for bs_freq_scale = 0 + */ + public static int MasterFrequencyTableFs0(SBR sbr, int k0, int k2, bool bs_alter_scale) + { + int incr; + int k; + int dk; + int nrBands, k2Achieved; + int k2Diff; + int[] vDk = new int[64]; + + /* mft only defined for k2 > k0 */ + if (k2 <= k0) + { + sbr._N_master = 0; + return 1; + } + + dk = bs_alter_scale ? 2 : 1; + + if (bs_alter_scale) + { + nrBands = k2 - k0 + 2 >> 2 << 1; + } + else + { + nrBands = k2 - k0 >> 1 << 1; + } + nrBands = Math.Min(nrBands, 63); + if (nrBands <= 0) + return 1; + + k2Achieved = k0 + nrBands * dk; + k2Diff = k2 - k2Achieved; + for (k = 0; k < nrBands; k++) + { + vDk[k] = dk; + } + + if (k2Diff != 0) + { + incr = k2Diff > 0 ? -1 : 1; + k = k2Diff > 0 ? nrBands - 1 : 0; + + while (k2Diff != 0) + { + vDk[k] -= incr; + k += incr; + k2Diff += incr; + } + } + + sbr._f_master[0] = k0; + for (k = 1; k <= nrBands; k++) + { + sbr._f_master[k] = sbr._f_master[k - 1] + vDk[k - 1]; + } + + sbr._N_master = nrBands; + sbr._N_master = Math.Min(sbr._N_master, 64); + + return 0; + } + + /* + This function finds the number of bands using this formula: + bands * log(a1/a0)/log(2.0) + 0.5 + */ + public static int FindBands(int warp, int bands, int a0, int a1) + { + float div = (float)Math.Log(2.0); + if (warp != 0) div *= 1.3f; + + return (int)(bands * Math.Log(a1 / (float)a0) / div + 0.5); + } + + public static float FindInitialPower(int bands, int a0, int a1) + { + return (float)Math.Pow(a1 / (float)a0, 1.0f / bands); + } + + /* + version for bs_freq_scale > 0 + */ + public static int MasterFrequencyTable(SBR sbr, int k0, int k2, int bs_freq_scale, bool bs_alter_scale) + { + int k, bands; + bool twoRegions; + int k1; + int nrBand0, nrBand1; + int[] vDk0 = new int[64], vDk1 = new int[64]; + int[] vk0 = new int[64], vk1 = new int[64]; + int[] temp1 = { 6, 5, 4 }; + float q, qk; + int A_1; + + /* mft only defined for k2 > k0 */ + if (k2 <= k0) + { + sbr._N_master = 0; + return 1; + } + + bands = temp1[bs_freq_scale - 1]; + + if (k2 / (float)k0 > 2.2449) + { + twoRegions = true; + k1 = k0 << 1; + } + else + { + twoRegions = false; + k1 = k2; + } + + nrBand0 = 2 * FindBands(0, bands, k0, k1); + nrBand0 = Math.Min(nrBand0, 63); + if (nrBand0 <= 0) + return 1; + + q = FindInitialPower(nrBand0, k0, k1); + qk = k0; + A_1 = (int)(qk + 0.5f); + for (k = 0; k <= nrBand0; k++) + { + int A_0 = A_1; + qk *= q; + A_1 = (int)(qk + 0.5f); + vDk0[k] = A_1 - A_0; + } + + /* needed? */ + //qsort(vDk0, nrBand0, sizeof(vDk0[0]), longcmp); + Array.Sort(vDk0, 0, nrBand0); + + vk0[0] = k0; + for (k = 1; k <= nrBand0; k++) + { + vk0[k] = vk0[k - 1] + vDk0[k - 1]; + if (vDk0[k - 1] == 0) + return 1; + } + + if (!twoRegions) + { + for (k = 0; k <= nrBand0; k++) + { + sbr._f_master[k] = vk0[k]; + } + + sbr._N_master = nrBand0; + sbr._N_master = Math.Min(sbr._N_master, 64); + return 0; + } + + nrBand1 = 2 * FindBands(1 /* warped */, bands, k1, k2); + nrBand1 = Math.Min(nrBand1, 63); + + q = FindInitialPower(nrBand1, k1, k2); + qk = k1; + A_1 = (int)(qk + 0.5f); + for (k = 0; k <= nrBand1 - 1; k++) + { + int A_0 = A_1; + qk *= q; + A_1 = (int)(qk + 0.5f); + vDk1[k] = A_1 - A_0; + } + + if (vDk1[0] < vDk0[nrBand0 - 1]) + { + int change; + + /* needed? */ + //qsort(vDk1, nrBand1+1, sizeof(vDk1[0]), longcmp); + Array.Sort(vDk1, 0, nrBand1 + 1); + change = vDk0[nrBand0 - 1] - vDk1[0]; + vDk1[0] = vDk0[nrBand0 - 1]; + vDk1[nrBand1 - 1] = vDk1[nrBand1 - 1] - change; + } + + /* needed? */ + //qsort(vDk1, nrBand1, sizeof(vDk1[0]), longcmp); + Array.Sort(vDk1, 0, nrBand1); + vk1[0] = k1; + for (k = 1; k <= nrBand1; k++) + { + vk1[k] = vk1[k - 1] + vDk1[k - 1]; + if (vDk1[k - 1] == 0) + return 1; + } + + sbr._N_master = nrBand0 + nrBand1; + sbr._N_master = Math.Min(sbr._N_master, 64); + for (k = 0; k <= nrBand0; k++) + { + sbr._f_master[k] = vk0[k]; + } + for (k = nrBand0 + 1; k <= sbr._N_master; k++) + { + sbr._f_master[k] = vk1[k - nrBand0]; + } + + return 0; + } + + /* calculate the derived frequency border tables from f_master */ + public static int DerivedFrequencyTable(SBR sbr, int bs_xover_band, + int k2) + { + int k, i = 0; + int minus; + + /* The following relation shall be satisfied: bs_xover_band < N_Master */ + if (sbr._N_master <= bs_xover_band) + return 1; + + sbr._N_high = sbr._N_master - bs_xover_band; + sbr._N_low = (sbr._N_high >> 1) + (sbr._N_high - (sbr._N_high >> 1 << 1)); + + sbr._n[0] = sbr._N_low; + sbr._n[1] = sbr._N_high; + + for (k = 0; k <= sbr._N_high; k++) + { + sbr._f_table_res[Constants.HI_RES, k] = sbr._f_master[k + bs_xover_band]; + } + + sbr._M = sbr._f_table_res[Constants.HI_RES, sbr._N_high] - sbr._f_table_res[Constants.HI_RES, 0]; + sbr._kx = sbr._f_table_res[Constants.HI_RES, 0]; + if (sbr._kx > 32) + return 1; + if (sbr._kx + sbr._M > 64) + return 1; + + minus = (sbr._N_high & 1) != 0 ? 1 : 0; + + for (k = 0; k <= sbr._N_low; k++) + { + if (k == 0) + i = 0; + else + i = 2 * k - minus; + sbr._f_table_res[Constants.LO_RES, k] = sbr._f_table_res[Constants.HI_RES, i]; + } + + sbr._N_Q = 0; + if (sbr._bs_noise_bands == 0) + { + sbr._N_Q = 1; + } + else + { + sbr._N_Q = Math.Max(1, FindBands(0, sbr._bs_noise_bands, sbr._kx, k2)); + sbr._N_Q = Math.Min(5, sbr._N_Q); + } + + for (k = 0; k <= sbr._N_Q; k++) + { + if (k == 0) + { + i = 0; + } + else + { + /* i = i + (int32_t)((sbr.N_low - i)/(sbr.N_Q + 1 - k)); */ + i += (sbr._N_low - i) / (sbr._N_Q + 1 - k); + } + sbr._f_table_noise[k] = sbr._f_table_res[Constants.LO_RES, i]; + } + + /* build table for mapping k to g in hf patching */ + for (k = 0; k < 64; k++) + { + int g; + for (g = 0; g < sbr._N_Q; g++) + { + if (sbr._f_table_noise[g] <= k + && k < sbr._f_table_noise[g + 1]) + { + sbr._table_map_k_to_g[k] = g; + break; + } + } + } + return 0; + } + + /* TODO: blegh, ugly */ + /* Modified to calculate for all possible bs_limiter_bands always + * This reduces the number calls to this functions needed (now only on + * header reset) + */ + private static float[] limiterBandsCompare = {1.327152f, + 1.185093f, 1.119872f}; + + public static void LimiterFrequencyTable(SBR sbr) + { + int k, s; + int nrLim; + + sbr._f_table_lim[0, 0] = sbr._f_table_res[Constants.LO_RES, 0] - sbr._kx; + sbr._f_table_lim[0, 1] = sbr._f_table_res[Constants.LO_RES, sbr._N_low] - sbr._kx; + sbr._N_L[0] = 1; + + for (s = 1; s < 4; s++) + { + int[] limTable = new int[100 /*TODO*/]; + int[] patchBorders = new int[64/*??*/]; + + patchBorders[0] = sbr._kx; + for (k = 1; k <= sbr._noPatches; k++) + { + patchBorders[k] = patchBorders[k - 1] + sbr._patchNoSubbands[k - 1]; + } + + for (k = 0; k <= sbr._N_low; k++) + { + limTable[k] = sbr._f_table_res[Constants.LO_RES, k]; + } + for (k = 1; k < sbr._noPatches; k++) + { + limTable[k + sbr._N_low] = patchBorders[k]; + } + + /* needed */ + //qsort(limTable, sbr.noPatches+sbr.N_low, sizeof(limTable[0]), longcmp); + Array.Sort(limTable, 0, sbr._noPatches + sbr._N_low); + k = 1; + nrLim = sbr._noPatches + sbr._N_low - 1; + + if (nrLim < 0) // TODO: BIG FAT PROBLEM + return; + + //restart: + while (k <= nrLim) + { + float nOctaves; + + if (limTable[k - 1] != 0) + nOctaves = limTable[k] / (float)limTable[k - 1]; + else + nOctaves = 0; + + if (nOctaves < limiterBandsCompare[s - 1]) + { + int i; + if (limTable[k] != limTable[k - 1]) + { + bool found = false, found2 = false; + for (i = 0; i <= sbr._noPatches; i++) + { + if (limTable[k] == patchBorders[i]) + found = true; + } + if (found) + { + found2 = false; + for (i = 0; i <= sbr._noPatches; i++) + { + if (limTable[k - 1] == patchBorders[i]) + found2 = true; + } + if (found2) + { + k++; + continue; + } + else + { + /* remove (k-1)th element */ + limTable[k - 1] = sbr._f_table_res[Constants.LO_RES, sbr._N_low]; + //qsort(limTable, sbr.noPatches+sbr.N_low, sizeof(limTable[0]), longcmp); + Array.Sort(limTable, 0, sbr._noPatches + sbr._N_low); + nrLim--; + continue; + } + } + } + /* remove kth element */ + limTable[k] = sbr._f_table_res[Constants.LO_RES, sbr._N_low]; + //qsort(limTable, nrLim, sizeof(limTable[0]), longcmp); + Array.Sort(limTable, 0, nrLim); + nrLim--; + //continue; + } + else + { + k++; + //continue; + } + } + + sbr._N_L[s] = nrLim; + for (k = 0; k <= nrLim; k++) + { + sbr._f_table_lim[s, k] = limTable[k] - sbr._kx; + } + + } + } + } +} diff --git a/SharpJaad.AAC/Sbr/FilterbankTable.cs b/SharpJaad.AAC/Sbr/FilterbankTable.cs new file mode 100644 index 0000000..cfe8e2e --- /dev/null +++ b/SharpJaad.AAC/Sbr/FilterbankTable.cs @@ -0,0 +1,328 @@ +namespace SharpJaad.AAC.Sbr +{ + public static class FilterbankTable + { + public static float[] qmf_c = { + 0f, -0.00055252865047f, + -0.00056176925738f, -0.00049475180896f, + -0.00048752279712f, -0.00048937912498f, + -0.00050407143497f, -0.00052265642972f, + -0.00054665656337f, -0.00056778025613f, + -0.00058709304852f, -0.00061327473938f, + -0.00063124935319f, -0.00065403333621f, + -0.00067776907764f, -0.00069416146273f, + -0.00071577364744f, -0.00072550431222f, + -0.00074409418541f, -0.00074905980532f, + -0.0007681371927f, -0.00077248485949f, + -0.00078343322877f, -0.00077798694927f, + -0.000780366471f, -0.00078014496257f, + -0.0007757977331f, -0.00076307935757f, + -0.00075300014201f, -0.00073193571525f, + -0.00072153919876f, -0.00069179375372f, + -0.00066504150893f, -0.00063415949025f, + -0.0005946118933f, -0.00055645763906f, + -0.00051455722108f, -0.00046063254803f, + -0.00040951214522f, -0.00035011758756f, + -0.00028969811748f, -0.0002098337344f, + -0.00014463809349f, -6.173344072E-005f, + 1.349497418E-005f, 0.00010943831274f, + 0.00020430170688f, 0.00029495311041f, + 0.0004026540216f, 0.00051073884952f, + 0.00062393761391f, 0.00074580258865f, + 0.00086084433262f, 0.00098859883015f, + 0.00112501551307f, 0.00125778846475f, + 0.00139024948272f, 0.00154432198471f, + 0.00168680832531f, 0.00183482654224f, + 0.00198411407369f, 0.00214615835557f, + 0.00230172547746f, 0.00246256169126f, + 0.00262017586902f, 0.00278704643465f, + 0.00294694477165f, 0.00311254206525f, + 0.00327396134847f, 0.00344188741828f, + 0.00360082681231f, 0.00376039229104f, + 0.00392074323703f, 0.00408197531935f, + 0.0042264269227f, 0.00437307196781f, + 0.00452098527825f, 0.00466064606118f, + 0.00479325608498f, 0.00491376035745f, + 0.00503930226013f, 0.00514073539032f, + 0.00524611661324f, 0.00534716811982f, + 0.00541967759307f, 0.00548760401507f, + 0.00554757145088f, 0.00559380230045f, + 0.00562206432097f, 0.00564551969164f, + 0.00563891995151f, 0.00562661141932f, + 0.0055917128663f, 0.005540436394f, + 0.0054753783077f, 0.0053838975897f, + 0.00527157587272f, 0.00513822754514f, + 0.00498396877629f, 0.004810946906f, + 0.00460395301471f, 0.00438018617447f, + 0.0041251642327f, 0.00384564081246f, + 0.00354012465507f, 0.00320918858098f, + 0.00284467578623f, 0.00245085400321f, + 0.0020274176185f, 0.00157846825768f, + 0.00109023290512f, 0.0005832264248f, + 2.760451905E-005f, -0.00054642808664f, + -0.00115681355227f, -0.00180394725893f, + -0.00248267236449f, -0.003193377839f, + -0.00394011240522f, -0.004722259624f, + -0.00553372111088f, -0.00637922932685f, + -0.00726158168517f, -0.00817982333726f, + -0.00913253296085f, -0.01011502154986f, + -0.01113155480321f, -0.01218499959508f, + 0.01327182200351f, 0.01439046660792f, + 0.01554055533423f, 0.01673247129989f, + 0.01794333813443f, 0.01918724313698f, + 0.02045317933555f, 0.02174675502535f, + 0.02306801692862f, 0.02441609920285f, + 0.02578758475467f, 0.02718594296329f, + 0.02860721736385f, 0.03005026574279f, + 0.03150176087389f, 0.03297540810337f, + 0.03446209487686f, 0.03596975605542f, + 0.03748128504252f, 0.03900536794745f, + 0.04053491705584f, 0.04206490946367f, + 0.04360975421304f, 0.04514884056413f, + 0.04668430272642f, 0.04821657200672f, + 0.04973857556014f, 0.05125561555216f, + 0.05276307465207f, 0.05424527683589f, + 0.05571736482138f, 0.05716164501299f, + 0.0585915683626f, 0.05998374801761f, + 0.06134551717207f, 0.06268578081172f, + 0.06397158980681f, 0.0652247106438f, + 0.06643675122104f, 0.06760759851228f, + 0.06870438283512f, 0.06976302447127f, + 0.07076287107266f, 0.07170026731102f, + 0.07256825833083f, 0.07336202550803f, + 0.07410036424342f, 0.07474525581194f, + 0.07531373362019f, 0.07580083586584f, + 0.07619924793396f, 0.07649921704119f, + 0.07670934904245f, 0.07681739756964f, + 0.07682300113923f, 0.07672049241746f, + 0.07650507183194f, 0.07617483218536f, + 0.07573057565061f, 0.0751576255287f, + 0.07446643947564f, 0.0736406005762f, + 0.07267746427299f, 0.07158263647903f, + 0.07035330735093f, 0.06896640131951f, + 0.06745250215166f, 0.06576906686508f, + 0.06394448059633f, 0.06196027790387f, + 0.0598166570809f, 0.05751526919867f, + 0.05504600343009f, 0.05240938217366f, + 0.04959786763445f, 0.04663033051701f, + 0.04347687821958f, 0.04014582784127f, + 0.03664181168133f, 0.03295839306691f, + 0.02908240060125f, 0.02503075618909f, + 0.02079970728622f, 0.01637012582228f, + 0.01176238327857f, 0.00696368621617f, + 0.00197656014503f, -0.00320868968304f, + -0.00857117491366f, -0.01412888273558f, + -0.01988341292573f, -0.02582272888064f, + -0.03195312745332f, -0.03827765720822f, + -0.04478068215856f, -0.05148041767934f, + -0.05837053268336f, -0.06544098531359f, + -0.07269433008129f, -0.08013729344279f, + -0.08775475365593f, -0.09555333528914f, + -0.10353295311463f, -0.1116826931773f, + -0.120007798468f, -0.12850028503878f, + -0.13715517611934f, -0.1459766491187f, + -0.15496070710605f, -0.16409588556669f, + -0.17338081721706f, -0.18281725485142f, + -0.19239667457267f, -0.20212501768103f, + -0.21197358538056f, -0.22196526964149f, + -0.23206908706791f, -0.24230168845974f, + -0.25264803095722f, -0.26310532994603f, + -0.27366340405625f, -0.28432141891085f, + -0.29507167170646f, -0.30590985751916f, + -0.31682789136456f, -0.32781137272105f, + -0.33887226938665f, -0.3499914122931f, + 0.36115899031355f, 0.37237955463061f, + 0.38363500139043f, 0.39492117615675f, + 0.40623176767625f, 0.41756968968409f, + 0.42891199207373f, 0.44025537543665f, + 0.45159965356824f, 0.46293080852757f, + 0.47424532146115f, 0.48552530911099f, + 0.49677082545707f, 0.50798175000434f, + 0.51912349702391f, 0.53022408956855f, + 0.54125534487322f, 0.55220512585061f, + 0.5630789140137f, 0.57385241316923f, + 0.58454032354679f, 0.59511230862496f, + 0.6055783538918f, 0.61591099320291f, + 0.62612426956055f, 0.63619801077286f, + 0.64612696959461f, 0.65590163024671f, + 0.66551398801627f, 0.67496631901712f, + 0.68423532934598f, 0.69332823767032f, + 0.70223887193539f, 0.71094104263095f, + 0.71944626349561f, 0.72774489002994f, + 0.73582117582769f, 0.74368278636488f, + 0.75131374561237f, 0.75870807608242f, + 0.76586748650939f, 0.77277808813327f, + 0.77942875190216f, 0.7858353120392f, + 0.79197358416424f, 0.797846641377f, + 0.80344857518505f, 0.80876950044491f, + 0.81381912706217f, 0.81857760046468f, + 0.82304198905409f, 0.8272275347336f, + 0.8311038457152f, 0.83469373618402f, + 0.83797173378865f, 0.84095413924722f, + 0.84362382812005f, 0.84598184698206f, + 0.84803157770763f, 0.84978051984268f, + 0.85119715249343f, 0.85230470352147f, + 0.85310209497017f, 0.85357205739107f, + 0.85373856005937f /*max*/, 0.85357205739107f, + 0.85310209497017f, 0.85230470352147f, + 0.85119715249343f, 0.84978051984268f, + 0.84803157770763f, 0.84598184698206f, + 0.84362382812005f, 0.84095413924722f, + 0.83797173378865f, 0.83469373618402f, + 0.8311038457152f, 0.8272275347336f, + 0.82304198905409f, 0.81857760046468f, + 0.81381912706217f, 0.80876950044491f, + 0.80344857518505f, 0.797846641377f, + 0.79197358416424f, 0.7858353120392f, + 0.77942875190216f, 0.77277808813327f, + 0.76586748650939f, 0.75870807608242f, + 0.75131374561237f, 0.74368278636488f, + 0.73582117582769f, 0.72774489002994f, + 0.71944626349561f, 0.71094104263095f, + 0.70223887193539f, 0.69332823767032f, + 0.68423532934598f, 0.67496631901712f, + 0.66551398801627f, 0.65590163024671f, + 0.64612696959461f, 0.63619801077286f, + 0.62612426956055f, 0.61591099320291f, + 0.6055783538918f, 0.59511230862496f, + 0.58454032354679f, 0.57385241316923f, + 0.5630789140137f, 0.55220512585061f, + 0.54125534487322f, 0.53022408956855f, + 0.51912349702391f, 0.50798175000434f, + 0.49677082545707f, 0.48552530911099f, + 0.47424532146115f, 0.46293080852757f, + 0.45159965356824f, 0.44025537543665f, + 0.42891199207373f, 0.41756968968409f, + 0.40623176767625f, 0.39492117615675f, + 0.38363500139043f, 0.37237955463061f, + -0.36115899031355f, -0.3499914122931f, + -0.33887226938665f, -0.32781137272105f, + -0.31682789136456f, -0.30590985751916f, + -0.29507167170646f, -0.28432141891085f, + -0.27366340405625f, -0.26310532994603f, + -0.25264803095722f, -0.24230168845974f, + -0.23206908706791f, -0.22196526964149f, + -0.21197358538056f, -0.20212501768103f, + -0.19239667457267f, -0.18281725485142f, + -0.17338081721706f, -0.16409588556669f, + -0.15496070710605f, -0.1459766491187f, + -0.13715517611934f, -0.12850028503878f, + -0.120007798468f, -0.1116826931773f, + -0.10353295311463f, -0.09555333528914f, + -0.08775475365593f, -0.08013729344279f, + -0.07269433008129f, -0.06544098531359f, + -0.05837053268336f, -0.05148041767934f, + -0.04478068215856f, -0.03827765720822f, + -0.03195312745332f, -0.02582272888064f, + -0.01988341292573f, -0.01412888273558f, + -0.00857117491366f, -0.00320868968304f, + 0.00197656014503f, 0.00696368621617f, + 0.01176238327857f, 0.01637012582228f, + 0.02079970728622f, 0.02503075618909f, + 0.02908240060125f, 0.03295839306691f, + 0.03664181168133f, 0.04014582784127f, + 0.04347687821958f, 0.04663033051701f, + 0.04959786763445f, 0.05240938217366f, + 0.05504600343009f, 0.05751526919867f, + 0.0598166570809f, 0.06196027790387f, + 0.06394448059633f, 0.06576906686508f, + 0.06745250215166f, 0.06896640131951f, + 0.07035330735093f, 0.07158263647903f, + 0.07267746427299f, 0.0736406005762f, + 0.07446643947564f, 0.0751576255287f, + 0.07573057565061f, 0.07617483218536f, + 0.07650507183194f, 0.07672049241746f, + 0.07682300113923f, 0.07681739756964f, + 0.07670934904245f, 0.07649921704119f, + 0.07619924793396f, 0.07580083586584f, + 0.07531373362019f, 0.07474525581194f, + 0.07410036424342f, 0.07336202550803f, + 0.07256825833083f, 0.07170026731102f, + 0.07076287107266f, 0.06976302447127f, + 0.06870438283512f, 0.06760759851228f, + 0.06643675122104f, 0.0652247106438f, + 0.06397158980681f, 0.06268578081172f, + 0.06134551717207f, 0.05998374801761f, + 0.0585915683626f, 0.05716164501299f, + 0.05571736482138f, 0.05424527683589f, + 0.05276307465207f, 0.05125561555216f, + 0.04973857556014f, 0.04821657200672f, + 0.04668430272642f, 0.04514884056413f, + 0.04360975421304f, 0.04206490946367f, + 0.04053491705584f, 0.03900536794745f, + 0.03748128504252f, 0.03596975605542f, + 0.03446209487686f, 0.03297540810337f, + 0.03150176087389f, 0.03005026574279f, + 0.02860721736385f, 0.02718594296329f, + 0.02578758475467f, 0.02441609920285f, + 0.02306801692862f, 0.02174675502535f, + 0.02045317933555f, 0.01918724313698f, + 0.01794333813443f, 0.01673247129989f, + 0.01554055533423f, 0.01439046660792f, + -0.01327182200351f, -0.01218499959508f, + -0.01113155480321f, -0.01011502154986f, + -0.00913253296085f, -0.00817982333726f, + -0.00726158168517f, -0.00637922932685f, + -0.00553372111088f, -0.004722259624f, + -0.00394011240522f, -0.003193377839f, + -0.00248267236449f, -0.00180394725893f, + -0.00115681355227f, -0.00054642808664f, + 2.760451905E-005f, 0.0005832264248f, + 0.00109023290512f, 0.00157846825768f, + 0.0020274176185f, 0.00245085400321f, + 0.00284467578623f, 0.00320918858098f, + 0.00354012465507f, 0.00384564081246f, + 0.0041251642327f, 0.00438018617447f, + 0.00460395301471f, 0.004810946906f, + 0.00498396877629f, 0.00513822754514f, + 0.00527157587272f, 0.0053838975897f, + 0.0054753783077f, 0.005540436394f, + 0.0055917128663f, 0.00562661141932f, + 0.00563891995151f, 0.00564551969164f, + 0.00562206432097f, 0.00559380230045f, + 0.00554757145088f, 0.00548760401507f, + 0.00541967759307f, 0.00534716811982f, + 0.00524611661324f, 0.00514073539032f, + 0.00503930226013f, 0.00491376035745f, + 0.00479325608498f, 0.00466064606118f, + 0.00452098527825f, 0.00437307196781f, + 0.0042264269227f, 0.00408197531935f, + 0.00392074323703f, 0.00376039229104f, + 0.00360082681231f, 0.00344188741828f, + 0.00327396134847f, 0.00311254206525f, + 0.00294694477165f, 0.00278704643465f, + 0.00262017586902f, 0.00246256169126f, + 0.00230172547746f, 0.00214615835557f, + 0.00198411407369f, 0.00183482654224f, + 0.00168680832531f, 0.00154432198471f, + 0.00139024948272f, 0.00125778846475f, + 0.00112501551307f, 0.00098859883015f, + 0.00086084433262f, 0.00074580258865f, + 0.00062393761391f, 0.00051073884952f, + 0.0004026540216f, 0.00029495311041f, + 0.00020430170688f, 0.00010943831274f, + 1.349497418E-005f, -6.173344072E-005f, + -0.00014463809349f, -0.0002098337344f, + -0.00028969811748f, -0.00035011758756f, + -0.00040951214522f, -0.00046063254803f, + -0.00051455722108f, -0.00055645763906f, + -0.0005946118933f, -0.00063415949025f, + -0.00066504150893f, -0.00069179375372f, + -0.00072153919876f, -0.00073193571525f, + -0.00075300014201f, -0.00076307935757f, + -0.0007757977331f, -0.00078014496257f, + -0.000780366471f, -0.00077798694927f, + -0.00078343322877f, -0.00077248485949f, + -0.0007681371927f, -0.00074905980532f, + -0.00074409418541f, -0.00072550431222f, + -0.00071577364744f, -0.00069416146273f, + -0.00067776907764f, -0.00065403333621f, + -0.00063124935319f, -0.00061327473938f, + -0.00058709304852f, -0.00056778025613f, + -0.00054665656337f, -0.00052265642972f, + -0.00050407143497f, -0.00048937912498f, + -0.00048752279712f, -0.00049475180896f, + -0.00056176925738f, -0.00055252865047f + }; + } +} diff --git a/SharpJaad.AAC/Sbr/HFAdjustment.cs b/SharpJaad.AAC/Sbr/HFAdjustment.cs new file mode 100644 index 0000000..30b85d1 --- /dev/null +++ b/SharpJaad.AAC/Sbr/HFAdjustment.cs @@ -0,0 +1,503 @@ +using System; + +namespace SharpJaad.AAC.Sbr +{ + public class HFAdjustment + { + private static float[] h_smooth = { + 0.03183050093751f, 0.11516383427084f, + 0.21816949906249f, 0.30150283239582f, + 0.33333333333333f + }; + private static int[] phi_re = { 1, 0, -1, 0 }; + private static int[] phi_im = { 0, 1, 0, -1 }; + private static float[] limGain = { 0.5f, 1.0f, 2.0f, 1e10f }; + private static float EPS = 1e-12f; + private float[][] G_lim_boost = new float[Constants.MAX_L_E][]; + private float[][] Q_M_lim_boost = new float[Constants.MAX_L_E][]; + private float[][] S_M_boost = new float[Constants.MAX_L_E][]; + + public HFAdjustment() + { + for (int i = 0; i < Constants.MAX_L_E; i++) + { + G_lim_boost[i] = new float[Constants.MAX_M]; + Q_M_lim_boost[i] = new float[Constants.MAX_M]; + S_M_boost[i] = new float[Constants.MAX_M]; + } + } + + public static int HfAdjustment(SBR sbr, float[,,,] Xsbr, int ch) + { + HFAdjustment adj = new HFAdjustment(); + int ret = 0; + + if (sbr._bs_frame_class[ch] == Constants.FIXFIX) + { + sbr._l_A[ch] = -1; + } + else if (sbr._bs_frame_class[ch] == Constants.VARFIX) + { + if (sbr._bs_pointer[ch] > 1) + sbr._l_A[ch] = sbr._bs_pointer[ch] - 1; + else + sbr._l_A[ch] = -1; + } + else + { + if (sbr._bs_pointer[ch] == 0) + sbr._l_A[ch] = -1; + else + sbr._l_A[ch] = sbr._L_E[ch] + 1 - sbr._bs_pointer[ch]; + } + + ret = EstimateCurrentEnvelope(sbr, adj, Xsbr, ch); + if (ret > 0) return 1; + + CalculateGain(sbr, adj, ch); + + HfAssembly(sbr, adj, Xsbr, ch); + + return 0; + } + + private static int GetSMapped(SBR sbr, int ch, int l, int current_band) + { + if (sbr._f[ch, l] == Constants.HI_RES) + { + /* in case of using f_table_high we just have 1 to 1 mapping + * from bs_add_harmonic[l][k] + */ + if (l >= sbr._l_A[ch] + || sbr._bs_add_harmonic_prev[ch, current_band] != 0 && sbr._bs_add_harmonic_flag_prev[ch]) + { + return sbr._bs_add_harmonic[ch, current_band]; + } + } + else + { + int b, lb, ub; + + /* in case of f_table_low we check if any of the HI_RES bands + * within this LO_RES band has bs_add_harmonic[l][k] turned on + * (note that borders in the LO_RES table are also present in + * the HI_RES table) + */ + + /* find first HI_RES band in current LO_RES band */ + lb = 2 * current_band - ((sbr._N_high & 1) != 0 ? 1 : 0); + /* find first HI_RES band in next LO_RES band */ + ub = 2 * (current_band + 1) - ((sbr._N_high & 1) != 0 ? 1 : 0); + + /* check all HI_RES bands in current LO_RES band for sinusoid */ + for (b = lb; b < ub; b++) + { + if (l >= sbr._l_A[ch] + || sbr._bs_add_harmonic_prev[ch, b] != 0 && sbr._bs_add_harmonic_flag_prev[ch]) + { + if (sbr._bs_add_harmonic[ch, b] == 1) + return 1; + } + } + } + + return 0; + } + + private static int EstimateCurrentEnvelope(SBR sbr, HFAdjustment adj, + float[,,,] Xsbr, int ch) + { + int m, l, j, k, k_l, k_h, p; + float nrg, div; + + if (sbr._bs_interpol_freq) + { + for (l = 0; l < sbr._L_E[ch]; l++) + { + int i, l_i, u_i; + + l_i = sbr._t_E[ch, l]; + u_i = sbr._t_E[ch, l + 1]; + + div = u_i - l_i; + + if (div == 0) + div = 1; + + for (m = 0; m < sbr._M; m++) + { + nrg = 0; + + for (i = l_i + sbr._tHFAdj; i < u_i + sbr._tHFAdj; i++) + { + nrg += Xsbr[ch, i, m + sbr._kx, 0] * Xsbr[ch, i, m + sbr._kx, 0] + + Xsbr[ch, i, m + sbr._kx, 1] * Xsbr[ch, i, m + sbr._kx, 1]; + } + + sbr._E_curr[ch, m, l] = nrg / div; + } + } + } + else + { + for (l = 0; l < sbr._L_E[ch]; l++) + { + for (p = 0; p < sbr._n[sbr._f[ch, l]]; p++) + { + k_l = sbr._f_table_res[sbr._f[ch, l], p]; + k_h = sbr._f_table_res[sbr._f[ch, l], p + 1]; + + for (k = k_l; k < k_h; k++) + { + int i, l_i, u_i; + nrg = 0; + + l_i = sbr._t_E[ch, l]; + u_i = sbr._t_E[ch, l + 1]; + + div = (u_i - l_i) * (k_h - k_l); + + if (div == 0) + div = 1; + + for (i = l_i + sbr._tHFAdj; i < u_i + sbr._tHFAdj; i++) + { + for (j = k_l; j < k_h; j++) + { + nrg += Xsbr[ch, i, j, 0] * Xsbr[ch, i, j, 0] + + Xsbr[ch, i, j, 1] * Xsbr[ch, i, j, 1]; + } + } + + sbr._E_curr[ch, k - sbr._kx, l] = nrg / div; + } + } + } + } + + return 0; + } + + private static void HfAssembly(SBR sbr, HFAdjustment adj, + float[,,,] Xsbr, int ch) + { + + int m, l, i, n; + int fIndexNoise = 0; + int fIndexSine = 0; + bool assembly_reset = false; + + float G_filt, Q_filt; + + int h_SL; + + if (sbr._Reset) + { + assembly_reset = true; + fIndexNoise = 0; + } + else + { + fIndexNoise = sbr._index_noise_prev[ch]; + } + fIndexSine = sbr._psi_is_prev[ch]; + + for (l = 0; l < sbr._L_E[ch]; l++) + { + bool no_noise = l == sbr._l_A[ch] || l == sbr._prevEnvIsShort[ch]; + + h_SL = sbr._bs_smoothing_mode ? 0 : 4; + h_SL = no_noise ? 0 : h_SL; + + if (assembly_reset) + { + for (n = 0; n < 4; n++) + { + // БЫЛО: Array.Copy(...) - вызывает RankException + // Array.Copy(adj.G_lim_boost[l], 0, sbr._G_temp_prev, 2 * 5 * ch + 5 * n + 0, sbr._M); + + // СТАЛО: Ручное копирование в 3D массив + for (int k = 0; k < sbr._M; k++) + { + sbr._G_temp_prev[ch, n, k] = adj.G_lim_boost[l][k]; + sbr._Q_temp_prev[ch, n, k] = adj.Q_M_lim_boost[l][k]; + } + } + /* reset ringbuffer index */ + sbr._GQ_ringbuf_index[ch] = 4; + assembly_reset = false; + } + + for (i = sbr._t_E[ch, l]; i < sbr._t_E[ch, l + 1]; i++) + { + /* load new values into ringbuffer */ + // БЫЛО: Array.Copy(...) + // Array.Copy(adj.G_lim_boost[l], 0, sbr._G_temp_prev, 2 * 5 * ch + 5 * sbr._GQ_ringbuf_index[ch] + 0, sbr._M); + + // СТАЛО: + int ri = sbr._GQ_ringbuf_index[ch]; + for (int k = 0; k < sbr._M; k++) + { + sbr._G_temp_prev[ch, ri, k] = adj.G_lim_boost[l][k]; + sbr._Q_temp_prev[ch, ri, k] = adj.Q_M_lim_boost[l][k]; + } + + for (m = 0; m < sbr._M; m++) + { + float[] psi = new float[2]; + + G_filt = 0; + Q_filt = 0; + + if (h_SL != 0) + { + for (n = 0; n <= 4; n++) + { + float curr_h_smooth = h_smooth[n]; + ri++; + if (ri >= 5) + ri -= 5; + G_filt += sbr._G_temp_prev[ch, ri, m] * curr_h_smooth; + Q_filt += sbr._Q_temp_prev[ch, ri, m] * curr_h_smooth; + } + } + else + { + G_filt = sbr._G_temp_prev[ch, sbr._GQ_ringbuf_index[ch], m]; + Q_filt = sbr._Q_temp_prev[ch, sbr._GQ_ringbuf_index[ch], m]; + } + + Q_filt = adj.S_M_boost[l][m] != 0 || no_noise ? 0 : Q_filt; + + /* add noise to the output */ + fIndexNoise = fIndexNoise + 1 & 511; + + /* the smoothed gain values are applied to Xsbr */ + /* V is defined, not calculated */ + Xsbr[ch, i + sbr._tHFAdj, m + sbr._kx, 0] = G_filt * Xsbr[ch, i + sbr._tHFAdj, m + sbr._kx, 0] + + Q_filt * NoiseTable.NOISE_TABLE[fIndexNoise, 0]; + if (sbr._bs_extension_id == 3 && sbr._bs_extension_data == 42) + Xsbr[ch, i + sbr._tHFAdj, m + sbr._kx, 0] = 16428320; + Xsbr[ch, i + sbr._tHFAdj, m + sbr._kx, 1] = G_filt * Xsbr[ch, i + sbr._tHFAdj, m + sbr._kx, 1] + + Q_filt * NoiseTable.NOISE_TABLE[fIndexNoise, 1]; + + { + int rev = (m + sbr._kx & 1) != 0 ? -1 : 1; + psi[0] = adj.S_M_boost[l][m] * phi_re[fIndexSine]; + Xsbr[ch, i + sbr._tHFAdj, m + sbr._kx, 0] += psi[0]; + + psi[1] = rev * adj.S_M_boost[l][m] * phi_im[fIndexSine]; + Xsbr[ch, i + sbr._tHFAdj, m + sbr._kx, 1] += psi[1]; + } + } + + fIndexSine = fIndexSine + 1 & 3; + + /* update the ringbuffer index used for filtering G and Q with h_smooth */ + sbr._GQ_ringbuf_index[ch]++; + if (sbr._GQ_ringbuf_index[ch] >= 5) + sbr._GQ_ringbuf_index[ch] = 0; + } + } + + sbr._index_noise_prev[ch] = fIndexNoise; + sbr._psi_is_prev[ch] = fIndexSine; + } + + private static void CalculateGain(SBR sbr, HFAdjustment adj, int ch) + { + int m, l, k; + + int current_t_noise_band = 0; + int S_mapped; + + float[] Q_M_lim = new float[Constants.MAX_M]; + float[] G_lim = new float[Constants.MAX_M]; + float G_boost; + float[] S_M = new float[Constants.MAX_M]; + + for (l = 0; l < sbr._L_E[ch]; l++) + { + int current_f_noise_band = 0; + int current_res_band = 0; + int current_res_band2 = 0; + int current_hi_res_band = 0; + + float delta = l == sbr._l_A[ch] || l == sbr._prevEnvIsShort[ch] ? 0 : 1; + + S_mapped = GetSMapped(sbr, ch, l, current_res_band2); + + if (sbr._t_E[ch, l + 1] > sbr._t_Q[ch, current_t_noise_band + 1]) + { + current_t_noise_band++; + } + + for (k = 0; k < sbr._N_L[sbr._bs_limiter_bands]; k++) + { + float G_max; + float den = 0; + float acc1 = 0; + float acc2 = 0; + //int current_res_band_size = 0; + + int ml1, ml2; + + ml1 = sbr._f_table_lim[sbr._bs_limiter_bands, k]; + ml2 = sbr._f_table_lim[sbr._bs_limiter_bands, k + 1]; + + + /* calculate the accumulated E_orig and E_curr over the limiter band */ + for (m = ml1; m < ml2; m++) + { + if (m + sbr._kx == sbr._f_table_res[sbr._f[ch, l], current_res_band + 1]) + { + current_res_band++; + } + acc1 += sbr._E_orig[ch, current_res_band, l]; + acc2 += sbr._E_curr[ch, m, l]; + } + + + /* calculate the maximum gain */ + /* ratio of the energy of the original signal and the energy + * of the HF generated signal + */ + G_max = (EPS + acc1) / (EPS + acc2) * limGain[sbr._bs_limiter_gains]; + G_max = Math.Min(G_max, 1e10f); + + for (m = ml1; m < ml2; m++) + { + float Q_M, G; + float Q_div, Q_div2; + int S_index_mapped; + + + /* check if m is on a noise band border */ + if (m + sbr._kx == sbr._f_table_noise[current_f_noise_band + 1]) + { + /* step to next noise band */ + current_f_noise_band++; + } + + + /* check if m is on a resolution band border */ + if (m + sbr._kx == sbr._f_table_res[sbr._f[ch, l], current_res_band2 + 1]) + { + /* step to next resolution band */ + current_res_band2++; + + /* if we move to a new resolution band, we should check if we are + * going to add a sinusoid in this band + */ + S_mapped = GetSMapped(sbr, ch, l, current_res_band2); + } + + + /* check if m is on a HI_RES band border */ + if (m + sbr._kx == sbr._f_table_res[Constants.HI_RES, current_hi_res_band + 1]) + { + /* step to next HI_RES band */ + current_hi_res_band++; + } + + + /* find S_index_mapped + * S_index_mapped can only be 1 for the m in the middle of the + * current HI_RES band + */ + S_index_mapped = 0; + if (l >= sbr._l_A[ch] + || sbr._bs_add_harmonic_prev[ch, current_hi_res_band] != 0 && sbr._bs_add_harmonic_flag_prev[ch]) + { + /* find the middle subband of the HI_RES frequency band */ + if (m + sbr._kx == sbr._f_table_res[Constants.HI_RES, current_hi_res_band + 1] + sbr._f_table_res[Constants.HI_RES, current_hi_res_band] >> 1) + S_index_mapped = sbr._bs_add_harmonic[ch, current_hi_res_band]; + } + + + /* Q_div: [0..1] (1/(1+Q_mapped)) */ + Q_div = sbr._Q_div[ch, current_f_noise_band, current_t_noise_band]; + + + /* Q_div2: [0..1] (Q_mapped/(1+Q_mapped)) */ + Q_div2 = sbr._Q_div2[ch, current_f_noise_band, current_t_noise_band]; + + + /* Q_M only depends on E_orig and Q_div2: + * since N_Q <= N_Low <= N_High we only need to recalculate Q_M on + * a change of current noise band + */ + Q_M = sbr._E_orig[ch, current_res_band2, l] * Q_div2; + + + /* S_M only depends on E_orig, Q_div and S_index_mapped: + * S_index_mapped can only be non-zero once per HI_RES band + */ + if (S_index_mapped == 0) + { + S_M[m] = 0; + } + else + { + S_M[m] = sbr._E_orig[ch, current_res_band2, l] * Q_div; + + /* accumulate sinusoid part of the total energy */ + den += S_M[m]; + } + + + /* calculate gain */ + /* ratio of the energy of the original signal and the energy + * of the HF generated signal + */ + G = sbr._E_orig[ch, current_res_band2, l] / (1.0f + sbr._E_curr[ch, m, l]); + if (S_mapped == 0 && delta == 1) + G *= Q_div; + else if (S_mapped == 1) + G *= Q_div2; + + + /* limit the additional noise energy level */ + /* and apply the limiter */ + if (G_max > G) + { + Q_M_lim[m] = Q_M; + G_lim[m] = G; + } + else + { + Q_M_lim[m] = Q_M * G_max / G; + G_lim[m] = G_max; + } + + + /* accumulate the total energy */ + den += sbr._E_curr[ch, m, l] * G_lim[m]; + if (S_index_mapped == 0 && l != sbr._l_A[ch]) + den += Q_M_lim[m]; + } + + /* G_boost: [0..2.51188643] */ + G_boost = (acc1 + EPS) / (den + EPS); + G_boost = Math.Min(G_boost, 2.51188643f /* 1.584893192 ^ 2 */); + + for (m = ml1; m < ml2; m++) + { + /* apply compensation to gain, noise floor sf's and sinusoid levels */ + adj.G_lim_boost[l][m] = (float)Math.Sqrt(G_lim[m] * G_boost); + adj.Q_M_lim_boost[l][m] = (float)Math.Sqrt(Q_M_lim[m] * G_boost); + + if (S_M[m] != 0) + { + adj.S_M_boost[l][m] = (float)Math.Sqrt(S_M[m] * G_boost); + } + else + { + adj.S_M_boost[l][m] = 0; + } + } + } + } + } + } +} diff --git a/SharpJaad.AAC/Sbr/HFGeneration.cs b/SharpJaad.AAC/Sbr/HFGeneration.cs new file mode 100644 index 0000000..b0a1287 --- /dev/null +++ b/SharpJaad.AAC/Sbr/HFGeneration.cs @@ -0,0 +1,346 @@ +using System; + +namespace SharpJaad.AAC.Sbr +{ + public class HFGeneration + { + private static int[] goalSbTab = { 21, 23, 32, 43, 46, 64, 85, 93, 128, 0, 0, 0 }; + + private class ACorrCoef + { + public float[] _r01 = new float[2]; + public float[] _r02 = new float[2]; + public float[] _r11 = new float[2]; + public float[] _r12 = new float[2]; + public float[] _r22 = new float[2]; + public float _det; + } + + public static void HfGeneration(SBR sbr, float[,,,] Xlow, float[,,,] Xhigh, int ch) + { + int l, i, x; + float[,] alpha_0 = new float[64, 2], alpha_1 = new float[64, 2]; + + int offset = sbr._tHFAdj; + int first = sbr._t_E[ch, 0]; + int last = sbr._t_E[ch, sbr._L_E[ch]]; + + CalcChirpFactors(sbr, ch); + + if (ch == 0 && sbr._Reset) + PatchConstruction(sbr); + + /* calculate the prediction coefficients */ + + /* actual HF generation */ + for (i = 0; i < sbr._noPatches; i++) + { + for (x = 0; x < sbr._patchNoSubbands[i]; x++) + { + float a0_r, a0_i, a1_r, a1_i; + float bw, bw2; + int q, p, k, g; + + /* find the low and high band for patching */ + k = sbr._kx + x; + for (q = 0; q < i; q++) + { + k += sbr._patchNoSubbands[q]; + } + p = sbr._patchStartSubband[i] + x; + + g = sbr._table_map_k_to_g[k]; + + bw = sbr._bwArray[ch, g]; + bw2 = bw * bw; + + /* do the patching */ + /* with or without filtering */ + if (bw2 > 0) + { + float temp1_r, temp2_r, temp3_r; + float temp1_i, temp2_i, temp3_i; + CalcPredictionCoef(sbr, Xlow, ch, alpha_0, alpha_1, p); + + a0_r = alpha_0[p, 0] * bw; + a1_r = alpha_1[p, 0] * bw2; + a0_i = alpha_0[p, 1] * bw; + a1_i = alpha_1[p, 1] * bw2; + + temp2_r = Xlow[ch, first - 2 + offset, p, 0]; + temp3_r = Xlow[ch, first - 1 + offset, p, 0]; + temp2_i = Xlow[ch, first - 2 + offset, p, 1]; + temp3_i = Xlow[ch, first - 1 + offset, p, 1]; + for (l = first; l < last; l++) + { + temp1_r = temp2_r; + temp2_r = temp3_r; + temp3_r = Xlow[ch, l + offset, p, 0]; + temp1_i = temp2_i; + temp2_i = temp3_i; + temp3_i = Xlow[ch, l + offset, p, 1]; + + Xhigh[ch, l + offset, k, 0] + = temp3_r + + (a0_r * temp2_r + - a0_i * temp2_i + + a1_r * temp1_r + - a1_i * temp1_i); + Xhigh[ch, l + offset, k, 1] + = temp3_i + + (a0_i * temp2_r + + a0_r * temp2_i + + a1_i * temp1_r + + a1_r * temp1_i); + } + } + else + { + for (l = first; l < last; l++) + { + Xhigh[ch, l + offset, k, 0] = Xlow[ch, l + offset, p, 0]; + Xhigh[ch, l + offset, k, 1] = Xlow[ch, l + offset, p, 1]; + } + } + } + } + + if (sbr._Reset) + { + FBT.LimiterFrequencyTable(sbr); + } + } + + private static void AutoCorrelation(SBR sbr, ACorrCoef ac, float[,,,] buffer, int ch, int bd, int len) + { + float r01r = 0, r01i = 0, r02r = 0, r02i = 0, r11r = 0; + float temp1_r, temp1_i, temp2_r, temp2_i, temp3_r, temp3_i, temp4_r, temp4_i, temp5_r, temp5_i; + float rel = 1.0f / (1 + 1e-6f); + int j; + int offset = sbr._tHFAdj; + + temp2_r = buffer[ch, offset - 2, bd, 0]; + temp2_i = buffer[ch, offset - 2, bd, 1]; + temp3_r = buffer[ch, offset - 1, bd, 0]; + temp3_i = buffer[ch, offset - 1, bd, 1]; + // Save these because they are needed after loop + temp4_r = temp2_r; + temp4_i = temp2_i; + temp5_r = temp3_r; + temp5_i = temp3_i; + + for (j = offset; j < len + offset; j++) + { + temp1_r = temp2_r; // temp1_r = QMF_RE(buffer[j-2][bd]; + temp1_i = temp2_i; // temp1_i = QMF_IM(buffer[j-2][bd]; + temp2_r = temp3_r; // temp2_r = QMF_RE(buffer[j-1][bd]; + temp2_i = temp3_i; // temp2_i = QMF_IM(buffer[j-1][bd]; + temp3_r = buffer[ch, j, bd, 0]; + temp3_i = buffer[ch, j, bd, 1]; + r01r += temp3_r * temp2_r + temp3_i * temp2_i; + r01i += temp3_i * temp2_r - temp3_r * temp2_i; + r02r += temp3_r * temp1_r + temp3_i * temp1_i; + r02i += temp3_i * temp1_r - temp3_r * temp1_i; + r11r += temp2_r * temp2_r + temp2_i * temp2_i; + } + + // These are actual values in temporary variable at this point + // temp1_r = QMF_RE(buffer[len+offset-1-2][bd]; + // temp1_i = QMF_IM(buffer[len+offset-1-2][bd]; + // temp2_r = QMF_RE(buffer[len+offset-1-1][bd]; + // temp2_i = QMF_IM(buffer[len+offset-1-1][bd]; + // temp3_r = QMF_RE(buffer[len+offset-1][bd]); + // temp3_i = QMF_IM(buffer[len+offset-1][bd]); + // temp4_r = QMF_RE(buffer[offset-2][bd]); + // temp4_i = QMF_IM(buffer[offset-2][bd]); + // temp5_r = QMF_RE(buffer[offset-1][bd]); + // temp5_i = QMF_IM(buffer[offset-1][bd]); + ac._r12[0] = r01r + - (temp3_r * temp2_r + temp3_i * temp2_i) + + (temp5_r * temp4_r + temp5_i * temp4_i); + ac._r12[1] = r01i + - (temp3_i * temp2_r - temp3_r * temp2_i) + + (temp5_i * temp4_r - temp5_r * temp4_i); + ac._r22[0] = r11r + - (temp2_r * temp2_r + temp2_i * temp2_i) + + (temp4_r * temp4_r + temp4_i * temp4_i); + + ac._r01[0] = r01r; + ac._r01[1] = r01i; + ac._r02[0] = r02r; + ac._r02[1] = r02i; + ac._r11[0] = r11r; + + ac._det = ac._r11[0] * ac._r22[0] - rel * (ac._r12[0] * ac._r12[0] + ac._r12[1] * ac._r12[1]); + } + + /* calculate linear prediction coefficients using the covariance method */ + private static void CalcPredictionCoef(SBR sbr, float[,,,] Xlow, int ch, float[,] alpha_0, float[,] alpha_1, int k) + { + float tmp; + ACorrCoef ac = new ACorrCoef(); + + AutoCorrelation(sbr, ac, Xlow, ch, k, sbr._numTimeSlotsRate + 6); + + if (ac._det == 0) + { + alpha_1[k, 0] = 0; + alpha_1[k, 1] = 0; + } + else + { + tmp = 1.0f / ac._det; + alpha_1[k, 0] = (ac._r01[0] * ac._r12[0] - ac._r01[1] * ac._r12[1] - ac._r02[0] * ac._r11[0]) * tmp; + alpha_1[k, 1] = (ac._r01[1] * ac._r12[0] + ac._r01[0] * ac._r12[1] - ac._r02[1] * ac._r11[0]) * tmp; + } + + if (ac._r11[0] == 0) + { + alpha_0[k, 0] = 0; + alpha_0[k, 1] = 0; + } + else + { + tmp = 1.0f / ac._r11[0]; + alpha_0[k, 0] = -(ac._r01[0] + alpha_1[k, 0] * ac._r12[0] + alpha_1[k, 1] * ac._r12[1]) * tmp; + alpha_0[k, 1] = -(ac._r01[1] + alpha_1[k, 1] * ac._r12[0] - alpha_1[k, 0] * ac._r12[1]) * tmp; + } + + if (alpha_0[k, 0] * alpha_0[k, 0] + alpha_0[k, 1] * alpha_0[k, 1] >= 16.0f + || alpha_1[k, 0] * alpha_1[k, 0] + alpha_1[k, 1] * alpha_1[k, 1] >= 16.0f) + { + alpha_0[k, 0] = 0; + alpha_0[k, 1] = 0; + alpha_1[k, 0] = 0; + alpha_1[k, 1] = 0; + } + } + + /* FIXED POINT: bwArray = COEF */ + private static float MapNewBw(int invf_mode, int invf_mode_prev) + { + switch (invf_mode) + { + case 1: /* LOW */ + + if (invf_mode_prev == 0) /* NONE */ + return 0.6f; + else + return 0.75f; + + case 2: /* MID */ + + return 0.9f; + + case 3: /* HIGH */ + + return 0.98f; + + default: /* NONE */ + + if (invf_mode_prev == 1) /* LOW */ + return 0.6f; + else + return 0.0f; + } + } + + /* FIXED POINT: bwArray = COEF */ + private static void CalcChirpFactors(SBR sbr, int ch) + { + int i; + + for (i = 0; i < sbr._N_Q; i++) + { + sbr._bwArray[ch, i] = MapNewBw(sbr._bs_invf_mode[ch, i], sbr._bs_invf_mode_prev[ch, i]); + + if (sbr._bwArray[ch, i] < sbr._bwArray_prev[ch, i]) + sbr._bwArray[ch, i] = sbr._bwArray[ch, i] * 0.75f + sbr._bwArray_prev[ch, i] * 0.25f; + else + sbr._bwArray[ch, i] = sbr._bwArray[ch, i] * 0.90625f + sbr._bwArray_prev[ch, i] * 0.09375f; + + if (sbr._bwArray[ch, i] < 0.015625f) + sbr._bwArray[ch, i] = 0.0f; + + if (sbr._bwArray[ch, i] >= 0.99609375f) + sbr._bwArray[ch, i] = 0.99609375f; + + sbr._bwArray_prev[ch, i] = sbr._bwArray[ch, i]; + sbr._bs_invf_mode_prev[ch, i] = sbr._bs_invf_mode[ch, i]; + } + } + + private static void PatchConstruction(SBR sbr) + { + int i, k; + int odd, sb; + int msb = sbr._k0; + int usb = sbr._kx; + /* (uint8_t)(2.048e6/sbr.sample_rate + 0.5); */ + int goalSb = goalSbTab[(int)sbr._sampleRate]; + + sbr._noPatches = 0; + + if (goalSb < sbr._kx + sbr._M) + { + for (i = 0, k = 0; sbr._f_master[i] < goalSb; i++) + { + k = i + 1; + } + } + else + { + k = sbr._N_master; + } + + if (sbr._N_master == 0) + { + sbr._noPatches = 0; + sbr._patchNoSubbands[0] = 0; + sbr._patchStartSubband[0] = 0; + + return; + } + + do + { + int j = k + 1; + + do + { + j--; + + sb = sbr._f_master[j]; + odd = (sb - 2 + sbr._k0) % 2; + } + while (sb > sbr._k0 - 1 + msb - odd); + + sbr._patchNoSubbands[sbr._noPatches] = Math.Max(sb - usb, 0); + sbr._patchStartSubband[sbr._noPatches] = sbr._k0 - odd + - sbr._patchNoSubbands[sbr._noPatches]; + + if (sbr._patchNoSubbands[sbr._noPatches] > 0) + { + usb = sb; + msb = sb; + sbr._noPatches++; + } + else + { + msb = sbr._kx; + } + + if (sbr._f_master[k] - sb < 3) + k = sbr._N_master; + } + while (sb != sbr._kx + sbr._M); + + if (sbr._patchNoSubbands[sbr._noPatches - 1] < 3 && sbr._noPatches > 1) + { + sbr._noPatches--; + } + + sbr._noPatches = Math.Min(sbr._noPatches, 5); + } + } +} diff --git a/SharpJaad.AAC/Sbr/HuffmanTables.cs b/SharpJaad.AAC/Sbr/HuffmanTables.cs new file mode 100644 index 0000000..af231ee --- /dev/null +++ b/SharpJaad.AAC/Sbr/HuffmanTables.cs @@ -0,0 +1,629 @@ +namespace SharpJaad.AAC.Sbr +{ + public static class HuffmanTables + { + public static int[][] T_HUFFMAN_ENV_1_5DB = { + new int[] {1, 2}, + new int[] {-64, -65}, + new int[] {3, 4}, + new int[] {-63, -66}, + new int[] {5, 6}, + new int[] {-62, -67}, + new int[] {7, 8}, + new int[] {-61, -68}, + new int[] {9, 10}, + new int[] {-60, -69}, + new int[] {11, 12}, + new int[] {-59, -70}, + new int[] {13, 14}, + new int[] {-58, -71}, + new int[] {15, 16}, + new int[] {-57, -72}, + new int[] {17, 18}, + new int[] {-73, -56}, + new int[] {19, 21}, + new int[] {-74, 20}, + new int[] {-55, -75}, + new int[] {22, 26}, + new int[] {23, 24}, + new int[] {-54, -76}, + new int[] {-77, 25}, + new int[] {-53, -78}, + new int[] {27, 34}, + new int[] {28, 29}, + new int[] {-52, -79}, + new int[] {30, 31}, + new int[] {-80, -51}, + new int[] {32, 33}, + new int[] {-83, -82}, + new int[] {-81, -50}, + new int[] {35, 57}, + new int[] {36, 40}, + new int[] {37, 38}, + new int[] {-88, -84}, + new int[] {-48, 39}, + new int[] {-90, -85}, + new int[] {41, 46}, + new int[] {42, 43}, + new int[] {-49, -87}, + new int[] {44, 45}, + new int[] {-89, -86}, + new int[] {-124, -123}, + new int[] {47, 50}, + new int[] {48, 49}, + new int[] {-122, -121}, + new int[] {-120, -119}, + new int[] {51, 54}, + new int[] {52, 53}, + new int[] {-118, -117}, + new int[] {-116, -115}, + new int[] {55, 56}, + new int[] {-114, -113}, + new int[] {-112, -111}, + new int[] {58, 89}, + new int[] {59, 74}, + new int[] {60, 67}, + new int[] {61, 64}, + new int[] {62, 63}, + new int[] {-110, -109}, + new int[] {-108, -107}, + new int[] {65, 66}, + new int[] {-106, -105}, + new int[] {-104, -103}, + new int[] {68, 71}, + new int[] {69, 70}, + new int[] {-102, -101}, + new int[] {-100, -99}, + new int[] {72, 73}, + new int[] {-98, -97}, + new int[] {-96, -95}, + new int[] {75, 82}, + new int[] {76, 79}, + new int[] {77, 78}, + new int[] {-94, -93}, + new int[] {-92, -91}, + new int[] {80, 81}, + new int[] {-47, -46}, + new int[] {-45, -44}, + new int[] {83, 86}, + new int[] {84, 85}, + new int[] {-43, -42}, + new int[] {-41, -40}, + new int[] {87, 88}, + new int[] {-39, -38}, + new int[] {-37, -36}, + new int[] {90, 105}, + new int[] {91, 98}, + new int[] {92, 95}, + new int[] {93, 94}, + new int[] {-35, -34}, + new int[] {-33, -32}, + new int[] {96, 97}, + new int[] {-31, -30}, + new int[] {-29, -28}, + new int[] {99, 102}, + new int[] {100, 101}, + new int[] {-27, -26}, + new int[] {-25, -24}, + new int[] {103, 104}, + new int[] {-23, -22}, + new int[] {-21, -20}, + new int[] {106, 113}, + new int[] {107, 110}, + new int[] {108, 109}, + new int[] {-19, -18}, + new int[] {-17, -16}, + new int[] {111, 112}, + new int[] {-15, -14}, + new int[] {-13, -12}, + new int[] {114, 117}, + new int[] {115, 116}, + new int[] {-11, -10}, + new int[] {-9, -8}, + new int[] {118, 119}, + new int[] {-7, -6}, + new int[] {-5, -4} + }; + + public static int[][] F_HUFFMAN_ENV_1_5DB = { + new int[] {1, 2}, + new int[] {-64, -65}, + new int[] {3, 4}, + new int[] {-63, -66}, + new int[] {5, 6}, + new int[] {-67, -62}, + new int[] {7, 8}, + new int[] {-68, -61}, + new int[] {9, 10}, + new int[] {-69, -60}, + new int[] {11, 13}, + new int[] {-70, 12}, + new int[] {-59, -71}, + new int[] {14, 16}, + new int[] {-58, 15}, + new int[] {-72, -57}, + new int[] {17, 19}, + new int[] {-73, 18}, + new int[] {-56, -74}, + new int[] {20, 23}, + new int[] {21, 22}, + new int[] {-55, -75}, + new int[] {-54, -53}, + new int[] {24, 27}, + new int[] {25, 26}, + new int[] {-76, -52}, + new int[] {-77, -51}, + new int[] {28, 31}, + new int[] {29, 30}, + new int[] {-50, -78}, + new int[] {-79, -49}, + new int[] {32, 36}, + new int[] {33, 34}, + new int[] {-48, -47}, + new int[] {-80, 35}, + new int[] {-81, -82}, + new int[] {37, 47}, + new int[] {38, 41}, + new int[] {39, 40}, + new int[] {-83, -46}, + new int[] {-45, -84}, + new int[] {42, 44}, + new int[] {-85, 43}, + new int[] {-44, -43}, + new int[] {45, 46}, + new int[] {-88, -87}, + new int[] {-86, -90}, + new int[] {48, 66}, + new int[] {49, 56}, + new int[] {50, 53}, + new int[] {51, 52}, + new int[] {-92, -42}, + new int[] {-41, -39}, + new int[] {54, 55}, + new int[] {-105, -89}, + new int[] {-38, -37}, + new int[] {57, 60}, + new int[] {58, 59}, + new int[] {-94, -91}, + new int[] {-40, -36}, + new int[] {61, 63}, + new int[] {-20, 62}, + new int[] {-115, -110}, + new int[] {64, 65}, + new int[] {-108, -107}, + new int[] {-101, -97}, + new int[] {67, 89}, + new int[] {68, 75}, + new int[] {69, 72}, + new int[] {70, 71}, + new int[] {-95, -93}, + new int[] {-34, -27}, + new int[] {73, 74}, + new int[] {-22, -17}, + new int[] {-16, -124}, + new int[] {76, 82}, + new int[] {77, 79}, + new int[] {-123, 78}, + new int[] {-122, -121}, + new int[] {80, 81}, + new int[] {-120, -119}, + new int[] {-118, -117}, + new int[] {83, 86}, + new int[] {84, 85}, + new int[] {-116, -114}, + new int[] {-113, -112}, + new int[] {87, 88}, + new int[] {-111, -109}, + new int[] {-106, -104}, + new int[] {90, 105}, + new int[] {91, 98}, + new int[] {92, 95}, + new int[] {93, 94}, + new int[] {-103, -102}, + new int[] {-100, -99}, + new int[] {96, 97}, + new int[] {-98, -96}, + new int[] {-35, -33}, + new int[] {99, 102}, + new int[] {100, 101}, + new int[] {-32, -31}, + new int[] {-30, -29}, + new int[] {103, 104}, + new int[] {-28, -26}, + new int[] {-25, -24}, + new int[] {106, 113}, + new int[] {107, 110}, + new int[] {108, 109}, + new int[] {-23, -21}, + new int[] {-19, -18}, + new int[] {111, 112}, + new int[] {-15, -14}, + new int[] {-13, -12}, + new int[] {114, 117}, + new int[] {115, 116}, + new int[] {-11, -10}, + new int[] {-9, -8}, + new int[] {118, 119}, + new int[] {-7, -6}, + new int[] {-5, -4} + }; + + public static int[][] T_HUFFMAN_ENV_BAL_1_5DB = { + new int[] {-64, 1}, + new int[] {-63, 2}, + new int[] {-65, 3}, + new int[] {-62, 4}, + new int[] {-66, 5}, + new int[] {-61, 6}, + new int[] {-67, 7}, + new int[] {-60, 8}, + new int[] {-68, 9}, + new int[] {10, 11}, + new int[] {-69, -59}, + new int[] {12, 13}, + new int[] {-70, -58}, + new int[] {14, 28}, + new int[] {15, 21}, + new int[] {16, 18}, + new int[] {-57, 17}, + new int[] {-71, -56}, + new int[] {19, 20}, + new int[] {-88, -87}, + new int[] {-86, -85}, + new int[] {22, 25}, + new int[] {23, 24}, + new int[] {-84, -83}, + new int[] {-82, -81}, + new int[] {26, 27}, + new int[] {-80, -79}, + new int[] {-78, -77}, + new int[] {29, 36}, + new int[] {30, 33}, + new int[] {31, 32}, + new int[] {-76, -75}, + new int[] {-74, -73}, + new int[] {34, 35}, + new int[] {-72, -55}, + new int[] {-54, -53}, + new int[] {37, 41}, + new int[] {38, 39}, + new int[] {-52, -51}, + new int[] {-50, 40}, + new int[] {-49, -48}, + new int[] {42, 45}, + new int[] {43, 44}, + new int[] {-47, -46}, + new int[] {-45, -44}, + new int[] {46, 47}, + new int[] {-43, -42}, + new int[] {-41, -40} + }; + + public static int[][] F_HUFFMAN_ENV_BAL_1_5DB = { + new int [] {-64, 1}, + new int [] {-65, 2}, + new int [] {-63, 3}, + new int [] {-66, 4}, + new int [] {-62, 5}, + new int [] {-61, 6}, + new int [] {-67, 7}, + new int [] {-68, 8}, + new int [] {-60, 9}, + new int [] {10, 11}, + new int [] {-69, -59}, + new int [] {-70, 12}, + new int [] {-58, 13}, + new int [] {14, 17}, + new int [] {-71, 15}, + new int [] {-57, 16}, + new int [] {-56, -73}, + new int [] {18, 32}, + new int [] {19, 25}, + new int [] {20, 22}, + new int [] {-72, 21}, + new int [] {-88, -87}, + new int [] {23, 24}, + new int [] {-86, -85}, + new int [] {-84, -83}, + new int [] {26, 29}, + new int [] {27, 28}, + new int [] {-82, -81}, + new int [] {-80, -79}, + new int [] {30, 31}, + new int [] {-78, -77}, + new int [] {-76, -75}, + new int [] {33, 40}, + new int [] {34, 37}, + new int [] {35, 36}, + new int [] {-74, -55}, + new int [] {-54, -53}, + new int [] {38, 39}, + new int [] {-52, -51}, + new int [] {-50, -49}, + new int [] {41, 44}, + new int [] {42, 43}, + new int [] {-48, -47}, + new int [] {-46, -45}, + new int [] {45, 46}, + new int [] {-44, -43}, + new int [] {-42, 47}, + new int [] {-41, -40} + }; + + public static int[][] T_HUFFMAN_ENV_3_0DB = { + new int [] {-64, 1}, + new int [] {-65, 2}, + new int [] {-63, 3}, + new int [] {-66, 4}, + new int [] {-62, 5}, + new int [] {-67, 6}, + new int [] {-61, 7}, + new int [] {-68, 8}, + new int [] {-60, 9}, + new int [] {10, 11}, + new int [] {-69, -59}, + new int [] {12, 14}, + new int [] {-70, 13}, + new int [] {-71, -58}, + new int [] {15, 18}, + new int [] {16, 17}, + new int [] {-72, -57}, + new int [] {-73, -74}, + new int [] {19, 22}, + new int [] {-56, 20}, + new int [] {-55, 21}, + new int [] {-54, -77}, + new int [] {23, 31}, + new int [] {24, 25}, + new int [] {-75, -76}, + new int [] {26, 27}, + new int [] {-78, -53}, + new int [] {28, 29}, + new int [] {-52, -95}, + new int [] {-94, 30}, + new int [] {-93, -92}, + new int [] {32, 47}, + new int [] {33, 40}, + new int [] {34, 37}, + new int [] {35, 36}, + new int [] {-91, -90}, + new int [] {-89, -88}, + new int [] {38, 39}, + new int [] {-87, -86}, + new int [] {-85, -84}, + new int [] {41, 44}, + new int [] {42, 43}, + new int [] {-83, -82}, + new int [] {-81, -80}, + new int [] {45, 46}, + new int [] {-79, -51}, + new int [] {-50, -49}, + new int [] {48, 55}, + new int [] {49, 52}, + new int [] {50, 51}, + new int [] {-48, -47}, + new int [] {-46, -45}, + new int [] {53, 54}, + new int [] {-44, -43}, + new int [] {-42, -41}, + new int [] {56, 59}, + new int [] {57, 58}, + new int [] {-40, -39}, + new int [] {-38, -37}, + new int [] {60, 61}, + new int [] {-36, -35}, + new int [] {-34, -33} + }; + + public static int[][] F_HUFFMAN_ENV_3_0DB = { + new int[] {-64, 1}, + new int[] {-65, 2}, + new int[] {-63, 3}, + new int[] {-66, 4}, + new int[] {-62, 5}, + new int[] {-67, 6}, + new int[] {7, 8}, + new int[] {-61, -68}, + new int[] {9, 10}, + new int[] {-60, -69}, + new int[] {11, 12}, + new int[] {-59, -70}, + new int[] {13, 14}, + new int[] {-58, -71}, + new int[] {15, 16}, + new int[] {-57, -72}, + new int[] {17, 19}, + new int[] {-56, 18}, + new int[] {-55, -73}, + new int[] {20, 24}, + new int[] {21, 22}, + new int[] {-74, -54}, + new int[] {-53, 23}, + new int[] {-75, -76}, + new int[] {25, 30}, + new int[] {26, 27}, + new int[] {-52, -51}, + new int[] {28, 29}, + new int[] {-77, -79}, + new int[] {-50, -49}, + new int[] {31, 39}, + new int[] {32, 35}, + new int[] {33, 34}, + new int[] {-78, -46}, + new int[] {-82, -88}, + new int[] {36, 37}, + new int[] {-83, -48}, + new int[] {-47, 38}, + new int[] {-86, -85}, + new int[] {40, 47}, + new int[] {41, 44}, + new int[] {42, 43}, + new int[] {-80, -44}, + new int[] {-43, -42}, + new int[] {45, 46}, + new int[] {-39, -87}, + new int[] {-84, -40}, + new int[] {48, 55}, + new int[] {49, 52}, + new int[] {50, 51}, + new int[] {-95, -94}, + new int[] {-93, -92}, + new int[] {53, 54}, + new int[] {-91, -90}, + new int[] {-89, -81}, + new int[] {56, 59}, + new int[] {57, 58}, + new int[] {-45, -41}, + new int[] {-38, -37}, + new int[] {60, 61}, + new int[] {-36, -35}, + new int[] {-34, -33} + }; + + public static int[][] T_HUFFMAN_ENV_BAL_3_0DB = { + new int[] {-64, 1}, + new int[] {-63, 2}, + new int[] {-65, 3}, + new int[] {-66, 4}, + new int[] {-62, 5}, + new int[] {-61, 6}, + new int[] {-67, 7}, + new int[] {-68, 8}, + new int[] {-60, 9}, + new int[] {10, 16}, + new int[] {11, 13}, + new int[] {-69, 12}, + new int[] {-76, -75}, + new int[] {14, 15}, + new int[] {-74, -73}, + new int[] {-72, -71}, + new int[] {17, 20}, + new int[] {18, 19}, + new int[] {-70, -59}, + new int[] {-58, -57}, + new int[] {21, 22}, + new int[] {-56, -55}, + new int[] {-54, 23}, + new int[] {-53, -52} + }; + + public static int[][] F_HUFFMAN_ENV_BAL_3_0DB = { + new int[] {-64, 1}, + new int[] {-65, 2}, + new int[] {-63, 3}, + new int[] {-66, 4}, + new int[] {-62, 5}, + new int[] {-61, 6}, + new int[] {-67, 7}, + new int[] {-68, 8}, + new int[] {-60, 9}, + new int[] {10, 13}, + new int[] {-69, 11}, + new int[] {-59, 12}, + new int[] {-58, -76}, + new int[] {14, 17}, + new int[] {15, 16}, + new int[] {-75, -74}, + new int[] {-73, -72}, + new int[] {18, 21}, + new int[] {19, 20}, + new int[] {-71, -70}, + new int[] {-57, -56}, + new int[] {22, 23}, + new int[] {-55, -54}, + new int[] {-53, -52} + }; + + public static int[][] T_HUFFMAN_NOISE_3_0DB = { + new int[] {-64, 1}, + new int[] {-63, 2}, + new int[] {-65, 3}, + new int[] {-66, 4}, + new int[] {-62, 5}, + new int[] {-67, 6}, + new int[] {7, 8}, + new int[] {-61, -68}, + new int[] {9, 30}, + new int[] {10, 15}, + new int[] {-60, 11}, + new int[] {-69, 12}, + new int[] {13, 14}, + new int[] {-59, -53}, + new int[] {-95, -94}, + new int[] {16, 23}, + new int[] {17, 20}, + new int[] {18, 19}, + new int[] {-93, -92}, + new int[] {-91, -90}, + new int[] {21, 22}, + new int[] {-89, -88}, + new int[] {-87, -86}, + new int[] {24, 27}, + new int[] {25, 26}, + new int[] {-85, -84}, + new int[] {-83, -82}, + new int[] {28, 29}, + new int[] {-81, -80}, + new int[] {-79, -78}, + new int[] {31, 46}, + new int[] {32, 39}, + new int[] {33, 36}, + new int[] {34, 35}, + new int[] {-77, -76}, + new int[] {-75, -74}, + new int[] {37, 38}, + new int[] {-73, -72}, + new int[] {-71, -70}, + new int[] {40, 43}, + new int[] {41, 42}, + new int[] {-58, -57}, + new int[] {-56, -55}, + new int[] {44, 45}, + new int[] {-54, -52}, + new int[] {-51, -50}, + new int[] {47, 54}, + new int[] {48, 51}, + new int[] {49, 50}, + new int[] {-49, -48}, + new int[] {-47, -46}, + new int[] {52, 53}, + new int[] {-45, -44}, + new int[] {-43, -42}, + new int[] {55, 58}, + new int[] {56, 57}, + new int[] {-41, -40}, + new int[] {-39, -38}, + new int[] {59, 60}, + new int[] {-37, -36}, + new int[] {-35, 61}, + new int[] {-34, -33} + }; + + public static int[][] T_HUFFMAN_NOISE_BAL_3_0DB = { + new int[] {-64, 1}, + new int[] {-65, 2}, + new int[] {-63, 3}, + new int[] {4, 9}, + new int[] {-66, 5}, + new int[] {-62, 6}, + new int[] {7, 8}, + new int[] {-76, -75}, + new int[] {-74, -73}, + new int[] {10, 17}, + new int[] {11, 14}, + new int[] {12, 13}, + new int[] {-72, -71}, + new int[] {-70, -69}, + new int[] {15, 16}, + new int[] {-68, -67}, + new int[] {-61, -60}, + new int[] {18, 21}, + new int[] {19, 20}, + new int[] {-59, -58}, + new int[] {-57, -56}, + new int[] {22, 23}, + new int[] {-55, -54}, + new int[] {-53, -52} + }; + } +} \ No newline at end of file diff --git a/SharpJaad.AAC/Sbr/NoiseEnvelope.cs b/SharpJaad.AAC/Sbr/NoiseEnvelope.cs new file mode 100644 index 0000000..27aecee --- /dev/null +++ b/SharpJaad.AAC/Sbr/NoiseEnvelope.cs @@ -0,0 +1,499 @@ +namespace SharpJaad.AAC.Sbr +{ + public static class NoiseEnvelope + { + private static float[] E_deq_tab = { + 64.0f, 128.0f, 256.0f, 512.0f, 1024.0f, 2048.0f, 4096.0f, 8192.0f, + 16384.0f, 32768.0f, 65536.0f, 131072.0f, 262144.0f, 524288.0f, 1.04858E+006f, 2.09715E+006f, + 4.1943E+006f, 8.38861E+006f, 1.67772E+007f, 3.35544E+007f, 6.71089E+007f, 1.34218E+008f, 2.68435E+008f, 5.36871E+008f, + 1.07374E+009f, 2.14748E+009f, 4.29497E+009f, 8.58993E+009f, 1.71799E+010f, 3.43597E+010f, 6.87195E+010f, 1.37439E+011f, + 2.74878E+011f, 5.49756E+011f, 1.09951E+012f, 2.19902E+012f, 4.39805E+012f, 8.79609E+012f, 1.75922E+013f, 3.51844E+013f, + 7.03687E+013f, 1.40737E+014f, 2.81475E+014f, 5.6295E+014f, 1.1259E+015f, 2.2518E+015f, 4.5036E+015f, 9.0072E+015f, + 1.80144E+016f, 3.60288E+016f, 7.20576E+016f, 1.44115E+017f, 2.8823E+017f, 5.76461E+017f, 1.15292E+018f, 2.30584E+018f, + 4.61169E+018f, 9.22337E+018f, 1.84467E+019f, 3.68935E+019f, 7.3787E+019f, 1.47574E+020f, 2.95148E+020f, 5.90296E+020f + }; + + /* table for Q_div2 values when no coupling */ + private static float[] Q_div2_tab = { + 0.984615f, 0.969697f, + 0.941176f, 0.888889f, + 0.8f, 0.666667f, + 0.5f, 0.333333f, + 0.2f, 0.111111f, + 0.0588235f, 0.030303f, + 0.0153846f, 0.00775194f, + 0.00389105f, 0.00194932f, + 0.00097561f, 0.000488043f, + 0.000244081f, 0.000122055f, + 6.10314E-005f, 3.05166E-005f, + 1.52586E-005f, 7.62934E-006f, + 3.81468E-006f, 1.90734E-006f, + 9.53673E-007f, 4.76837E-007f, + 2.38419E-007f, 1.19209E-007f, + 5.96046E-008f}; + + private static float[][] Q_div2_tab_left = { + new float[] {0.0302959f, 0.111015f, 0.332468f, 0.663212f, 0.882759f, 0.962406f, 0.984615f, 0.990329f, 0.991768f, 0.992128f, 0.992218f, 0.992241f, 0.992246f}, + new float[] {0.0153809f, 0.0587695f, 0.199377f, 0.496124f, 0.790123f, 0.927536f, 0.969697f, 0.980843f, 0.98367f, 0.984379f, 0.984556f, 0.984601f, 0.984612f}, + new float[] {0.00775006f, 0.0302744f, 0.110727f, 0.329897f, 0.653061f, 0.864865f, 0.941176f, 0.962406f, 0.967864f, 0.969238f, 0.969582f, 0.969668f, 0.96969f}, + new float[] {0.0038901f, 0.0153698f, 0.0586081f, 0.197531f, 0.484848f, 0.761905f, 0.888889f, 0.927536f, 0.937729f, 0.940312f, 0.94096f, 0.941122f, 0.941163f}, + new float[] {0.00194884f, 0.00774443f, 0.0301887f, 0.109589f, 0.32f, 0.615385f, 0.8f, 0.864865f, 0.882759f, 0.887348f, 0.888503f, 0.888792f, 0.888865f}, + new float[] {0.000975372f, 0.00388727f, 0.0153257f, 0.057971f, 0.190476f, 0.444444f, 0.666667f, 0.761905f, 0.790123f, 0.797508f, 0.799375f, 0.799844f, 0.799961f}, + new float[] {0.000487924f, 0.00194742f, 0.00772201f, 0.0298507f, 0.105263f, 0.285714f, 0.5f, 0.615385f, 0.653061f, 0.663212f, 0.6658f, 0.66645f, 0.666612f}, + new float[] {0.000244021f, 0.000974659f, 0.00387597f, 0.0151515f, 0.0555556f, 0.166667f, 0.333333f, 0.444444f, 0.484848f, 0.496124f, 0.499025f, 0.499756f, 0.499939f}, + new float[] {0.000122026f, 0.000487567f, 0.00194175f, 0.00763359f, 0.0285714f, 0.0909091f, 0.2f, 0.285714f, 0.32f, 0.329897f, 0.332468f, 0.333116f, 0.333279f}, + new float[] {6.10165E-005f, 0.000243843f, 0.000971817f, 0.00383142f, 0.0144928f, 0.047619f, 0.111111f, 0.166667f, 0.190476f, 0.197531f, 0.199377f, 0.199844f, 0.199961f}, + new float[] {3.05092E-005f, 0.000121936f, 0.000486145f, 0.00191939f, 0.00729927f, 0.0243902f, 0.0588235f, 0.0909091f, 0.105263f, 0.109589f, 0.110727f, 0.111015f, 0.111087f}, + new float[] {1.52548E-005f, 6.09719E-005f, 0.000243132f, 0.000960615f, 0.003663f, 0.0123457f, 0.030303f, 0.047619f, 0.0555556f, 0.057971f, 0.0586081f, 0.0587695f, 0.05881f}, + new float[] {7.62747E-006f, 3.04869E-005f, 0.000121581f, 0.000480538f, 0.00183486f, 0.00621118f, 0.0153846f, 0.0243902f, 0.0285714f, 0.0298507f, 0.0301887f, 0.0302744f, 0.0302959f}, + new float[] {3.81375E-006f, 1.52437E-005f, 6.0794E-005f, 0.000240327f, 0.000918274f, 0.00311526f, 0.00775194f, 0.0123457f, 0.0144928f, 0.0151515f, 0.0153257f, 0.0153698f, 0.0153809f}, + new float[] {1.90688E-006f, 7.62189E-006f, 3.03979E-005f, 0.000120178f, 0.000459348f, 0.00156006f, 0.00389105f, 0.00621118f, 0.00729927f, 0.00763359f, 0.00772201f, 0.00774443f, 0.00775006f}, + new float[] {9.53441E-007f, 3.81096E-006f, 1.51992E-005f, 6.00925E-005f, 0.000229727f, 0.00078064f, 0.00194932f, 0.00311526f, 0.003663f, 0.00383142f, 0.00387597f, 0.00388727f, 0.0038901f}, + new float[] {4.76721E-007f, 1.90548E-006f, 7.59965E-006f, 3.00472E-005f, 0.000114877f, 0.000390472f, 0.00097561f, 0.00156006f, 0.00183486f, 0.00191939f, 0.00194175f, 0.00194742f, 0.00194884f}, + new float[] {2.3836E-007f, 9.52743E-007f, 3.79984E-006f, 1.50238E-005f, 5.74416E-005f, 0.000195274f, 0.000488043f, 0.00078064f, 0.000918274f, 0.000960615f, 0.000971817f, 0.000974659f, 0.000975372f}, + new float[] {1.1918E-007f, 4.76372E-007f, 1.89992E-006f, 7.51196E-006f, 2.87216E-005f, 9.76467E-005f, 0.000244081f, 0.000390472f, 0.000459348f, 0.000480538f, 0.000486145f, 0.000487567f, 0.000487924f}, + new float[] {5.95901E-008f, 2.38186E-007f, 9.49963E-007f, 3.756E-006f, 1.4361E-005f, 4.88257E-005f, 0.000122055f, 0.000195274f, 0.000229727f, 0.000240327f, 0.000243132f, 0.000243843f, 0.000244021f}, + new float[] {2.9795E-008f, 1.19093E-007f, 4.74982E-007f, 1.878E-006f, 7.18056E-006f, 2.44135E-005f, 6.10314E-005f, 9.76467E-005f, 0.000114877f, 0.000120178f, 0.000121581f, 0.000121936f, 0.000122026f}, + new float[] {1.48975E-008f, 5.95465E-008f, 2.37491E-007f, 9.39002E-007f, 3.59029E-006f, 1.22069E-005f, 3.05166E-005f, 4.88257E-005f, 5.74416E-005f, 6.00925E-005f, 6.0794E-005f, 6.09719E-005f, 6.10165E-005f}, + new float[] {7.44876E-009f, 2.97732E-008f, 1.18745E-007f, 4.69501E-007f, 1.79515E-006f, 6.10348E-006f, 1.52586E-005f, 2.44135E-005f, 2.87216E-005f, 3.00472E-005f, 3.03979E-005f, 3.04869E-005f, 3.05092E-005f}, + new float[] {3.72438E-009f, 1.48866E-008f, 5.93727E-008f, 2.34751E-007f, 8.97575E-007f, 3.05175E-006f, 7.62934E-006f, 1.22069E-005f, 1.4361E-005f, 1.50238E-005f, 1.51992E-005f, 1.52437E-005f, 1.52548E-005f}, + new float[] {1.86219E-009f, 7.44331E-009f, 2.96864E-008f, 1.17375E-007f, 4.48788E-007f, 1.52588E-006f, 3.81468E-006f, 6.10348E-006f, 7.18056E-006f, 7.51196E-006f, 7.59965E-006f, 7.62189E-006f, 7.62747E-006f}, + new float[] {9.31095E-010f, 3.72166E-009f, 1.48432E-008f, 5.86876E-008f, 2.24394E-007f, 7.62939E-007f, 1.90734E-006f, 3.05175E-006f, 3.59029E-006f, 3.756E-006f, 3.79984E-006f, 3.81096E-006f, 3.81375E-006f}, + new float[] {4.65548E-010f, 1.86083E-009f, 7.42159E-009f, 2.93438E-008f, 1.12197E-007f, 3.8147E-007f, 9.53673E-007f, 1.52588E-006f, 1.79515E-006f, 1.878E-006f, 1.89992E-006f, 1.90548E-006f, 1.90688E-006f}, + new float[] {2.32774E-010f, 9.30414E-010f, 3.71079E-009f, 1.46719E-008f, 5.60985E-008f, 1.90735E-007f, 4.76837E-007f, 7.62939E-007f, 8.97575E-007f, 9.39002E-007f, 9.49963E-007f, 9.52743E-007f, 9.53441E-007f}, + new float[] {1.16387E-010f, 4.65207E-010f, 1.8554E-009f, 7.33596E-009f, 2.80492E-008f, 9.53674E-008f, 2.38419E-007f, 3.8147E-007f, 4.48788E-007f, 4.69501E-007f, 4.74982E-007f, 4.76372E-007f, 4.76721E-007f}, + new float[] {5.81935E-011f, 2.32603E-010f, 9.27699E-010f, 3.66798E-009f, 1.40246E-008f, 4.76837E-008f, 1.19209E-007f, 1.90735E-007f, 2.24394E-007f, 2.34751E-007f, 2.37491E-007f, 2.38186E-007f, 2.3836E-007f}, + new float[] {2.90967E-011f, 1.16302E-010f, 4.63849E-010f, 1.83399E-009f, 7.01231E-009f, 2.38419E-008f, 5.96046E-008f, 9.53674E-008f, 1.12197E-007f, 1.17375E-007f, 1.18745E-007f, 1.19093E-007f, 1.1918E-007f} + }; + + private static float[][] Q_div2_tab_right = { + new float[] {0.992246f, 0.992241f, 0.992218f, 0.992128f, 0.991768f, 0.990329f, 0.984615f, 0.962406f, 0.882759f, 0.663212f, 0.332468f, 0.111015f, 0.0302959f}, + new float[] {0.984612f, 0.984601f, 0.984556f, 0.984379f, 0.98367f, 0.980843f, 0.969697f, 0.927536f, 0.790123f, 0.496124f, 0.199377f, 0.0587695f, 0.0153809f}, + new float[] {0.96969f, 0.969668f, 0.969582f, 0.969238f, 0.967864f, 0.962406f, 0.941176f, 0.864865f, 0.653061f, 0.329897f, 0.110727f, 0.0302744f, 0.00775006f}, + new float[] {0.941163f, 0.941122f, 0.94096f, 0.940312f, 0.937729f, 0.927536f, 0.888889f, 0.761905f, 0.484848f, 0.197531f, 0.0586081f, 0.0153698f, 0.0038901f}, + new float[] {0.888865f, 0.888792f, 0.888503f, 0.887348f, 0.882759f, 0.864865f, 0.8f, 0.615385f, 0.32f, 0.109589f, 0.0301887f, 0.00774443f, 0.00194884f}, + new float[] {0.799961f, 0.799844f, 0.799375f, 0.797508f, 0.790123f, 0.761905f, 0.666667f, 0.444444f, 0.190476f, 0.057971f, 0.0153257f, 0.00388727f, 0.000975372f}, + new float[] {0.666612f, 0.66645f, 0.6658f, 0.663212f, 0.653061f, 0.615385f, 0.5f, 0.285714f, 0.105263f, 0.0298507f, 0.00772201f, 0.00194742f, 0.000487924f}, + new float[] {0.499939f, 0.499756f, 0.499025f, 0.496124f, 0.484848f, 0.444444f, 0.333333f, 0.166667f, 0.0555556f, 0.0151515f, 0.00387597f, 0.000974659f, 0.000244021f}, + new float[] {0.333279f, 0.333116f, 0.332468f, 0.329897f, 0.32f, 0.285714f, 0.2f, 0.0909091f, 0.0285714f, 0.00763359f, 0.00194175f, 0.000487567f, 0.000122026f}, + new float[] {0.199961f, 0.199844f, 0.199377f, 0.197531f, 0.190476f, 0.166667f, 0.111111f, 0.047619f, 0.0144928f, 0.00383142f, 0.000971817f, 0.000243843f, 6.10165E-005f}, + new float[] {0.111087f, 0.111015f, 0.110727f, 0.109589f, 0.105263f, 0.0909091f, 0.0588235f, 0.0243902f, 0.00729927f, 0.00191939f, 0.000486145f, 0.000121936f, 3.05092E-005f}, + new float[] {0.05881f, 0.0587695f, 0.0586081f, 0.057971f, 0.0555556f, 0.047619f, 0.030303f, 0.0123457f, 0.003663f, 0.000960615f, 0.000243132f, 6.09719E-005f, 1.52548E-005f}, + new float[] {0.0302959f, 0.0302744f, 0.0301887f, 0.0298507f, 0.0285714f, 0.0243902f, 0.0153846f, 0.00621118f, 0.00183486f, 0.000480538f, 0.000121581f, 3.04869E-005f, 7.62747E-006f}, + new float[] {0.0153809f, 0.0153698f, 0.0153257f, 0.0151515f, 0.0144928f, 0.0123457f, 0.00775194f, 0.00311526f, 0.000918274f, 0.000240327f, 6.0794E-005f, 1.52437E-005f, 3.81375E-006f}, + new float[] {0.00775006f, 0.00774443f, 0.00772201f, 0.00763359f, 0.00729927f, 0.00621118f, 0.00389105f, 0.00156006f, 0.000459348f, 0.000120178f, 3.03979E-005f, 7.62189E-006f, 1.90688E-006f}, + new float[] {0.0038901f, 0.00388727f, 0.00387597f, 0.00383142f, 0.003663f, 0.00311526f, 0.00194932f, 0.00078064f, 0.000229727f, 6.00925E-005f, 1.51992E-005f, 3.81096E-006f, 9.53441E-007f}, + new float[] {0.00194884f, 0.00194742f, 0.00194175f, 0.00191939f, 0.00183486f, 0.00156006f, 0.00097561f, 0.000390472f, 0.000114877f, 3.00472E-005f, 7.59965E-006f, 1.90548E-006f, 4.76721E-007f}, + new float[] {0.000975372f, 0.000974659f, 0.000971817f, 0.000960615f, 0.000918274f, 0.00078064f, 0.000488043f, 0.000195274f, 5.74416E-005f, 1.50238E-005f, 3.79984E-006f, 9.52743E-007f, 2.3836E-007f}, + new float[] {0.000487924f, 0.000487567f, 0.000486145f, 0.000480538f, 0.000459348f, 0.000390472f, 0.000244081f, 9.76467E-005f, 2.87216E-005f, 7.51196E-006f, 1.89992E-006f, 4.76372E-007f, 1.1918E-007f}, + new float[] {0.000244021f, 0.000243843f, 0.000243132f, 0.000240327f, 0.000229727f, 0.000195274f, 0.000122055f, 4.88257E-005f, 1.4361E-005f, 3.756E-006f, 9.49963E-007f, 2.38186E-007f, 5.95901E-008f}, + new float[] {0.000122026f, 0.000121936f, 0.000121581f, 0.000120178f, 0.000114877f, 9.76467E-005f, 6.10314E-005f, 2.44135E-005f, 7.18056E-006f, 1.878E-006f, 4.74982E-007f, 1.19093E-007f, 2.9795E-008f}, + new float[] {6.10165E-005f, 6.09719E-005f, 6.0794E-005f, 6.00925E-005f, 5.74416E-005f, 4.88257E-005f, 3.05166E-005f, 1.22069E-005f, 3.59029E-006f, 9.39002E-007f, 2.37491E-007f, 5.95465E-008f, 1.48975E-008f}, + new float[] {3.05092E-005f, 3.04869E-005f, 3.03979E-005f, 3.00472E-005f, 2.87216E-005f, 2.44135E-005f, 1.52586E-005f, 6.10348E-006f, 1.79515E-006f, 4.69501E-007f, 1.18745E-007f, 2.97732E-008f, 7.44876E-009f}, + new float[] {1.52548E-005f, 1.52437E-005f, 1.51992E-005f, 1.50238E-005f, 1.4361E-005f, 1.22069E-005f, 7.62934E-006f, 3.05175E-006f, 8.97575E-007f, 2.34751E-007f, 5.93727E-008f, 1.48866E-008f, 3.72438E-009f}, + new float[] {7.62747E-006f, 7.62189E-006f, 7.59965E-006f, 7.51196E-006f, 7.18056E-006f, 6.10348E-006f, 3.81468E-006f, 1.52588E-006f, 4.48788E-007f, 1.17375E-007f, 2.96864E-008f, 7.44331E-009f, 1.86219E-009f}, + new float[] {3.81375E-006f, 3.81096E-006f, 3.79984E-006f, 3.756E-006f, 3.59029E-006f, 3.05175E-006f, 1.90734E-006f, 7.62939E-007f, 2.24394E-007f, 5.86876E-008f, 1.48432E-008f, 3.72166E-009f, 9.31095E-010f}, + new float[] {1.90688E-006f, 1.90548E-006f, 1.89992E-006f, 1.878E-006f, 1.79515E-006f, 1.52588E-006f, 9.53673E-007f, 3.8147E-007f, 1.12197E-007f, 2.93438E-008f, 7.42159E-009f, 1.86083E-009f, 4.65548E-010f}, + new float[] {9.53441E-007f, 9.52743E-007f, 9.49963E-007f, 9.39002E-007f, 8.97575E-007f, 7.62939E-007f, 4.76837E-007f, 1.90735E-007f, 5.60985E-008f, 1.46719E-008f, 3.71079E-009f, 9.30414E-010f, 2.32774E-010f}, + new float[] {4.76721E-007f, 4.76372E-007f, 4.74982E-007f, 4.69501E-007f, 4.48788E-007f, 3.8147E-007f, 2.38419E-007f, 9.53674E-008f, 2.80492E-008f, 7.33596E-009f, 1.8554E-009f, 4.65207E-010f, 1.16387E-010f}, + new float[] {2.3836E-007f, 2.38186E-007f, 2.37491E-007f, 2.34751E-007f, 2.24394E-007f, 1.90735E-007f, 1.19209E-007f, 4.76837E-008f, 1.40246E-008f, 3.66798E-009f, 9.27699E-010f, 2.32603E-010f, 5.81935E-011f}, + new float[] {1.1918E-007f, 1.19093E-007f, 1.18745E-007f, 1.17375E-007f, 1.12197E-007f, 9.53674E-008f, 5.96046E-008f, 2.38419E-008f, 7.01231E-009f, 1.83399E-009f, 4.63849E-010f, 1.16302E-010f, 2.90967E-011f} + }; + + /* table for Q_div values when no coupling */ + private static float[] Q_div_tab = { + 0.0153846f, 0.030303f, + 0.0588235f, 0.111111f, + 0.2f, 0.333333f, + 0.5f, 0.666667f, + 0.8f, 0.888889f, + 0.941176f, 0.969697f, + 0.984615f, 0.992248f, + 0.996109f, 0.998051f, + 0.999024f, 0.999512f, + 0.999756f, 0.999878f, + 0.999939f, 0.999969f, + 0.999985f, 0.999992f, + 0.999996f, 0.999998f, + 0.999999f, 1f, + 1f, 1f, + 1f + }; + + private static float[][] Q_div_tab_left = { + new float[] {0.969704f, 0.888985f, 0.667532f, 0.336788f, 0.117241f, 0.037594f, 0.0153846f, 0.00967118f, 0.00823245f, 0.00787211f, 0.00778198f, 0.00775945f, 0.00775382f}, + new float[] {0.984619f, 0.94123f, 0.800623f, 0.503876f, 0.209877f, 0.0724638f, 0.030303f, 0.0191571f, 0.0163305f, 0.0156212f, 0.0154438f, 0.0153994f, 0.0153883f}, + new float[] {0.99225f, 0.969726f, 0.889273f, 0.670103f, 0.346939f, 0.135135f, 0.0588235f, 0.037594f, 0.0321361f, 0.0307619f, 0.0304178f, 0.0303317f, 0.0303102f}, + new float[] {0.99611f, 0.98463f, 0.941392f, 0.802469f, 0.515152f, 0.238095f, 0.111111f, 0.0724638f, 0.0622711f, 0.0596878f, 0.0590397f, 0.0588776f, 0.058837f}, + new float[] {0.998051f, 0.992256f, 0.969811f, 0.890411f, 0.68f, 0.384615f, 0.2f, 0.135135f, 0.117241f, 0.112652f, 0.111497f, 0.111208f, 0.111135f}, + new float[] {0.999025f, 0.996113f, 0.984674f, 0.942029f, 0.809524f, 0.555556f, 0.333333f, 0.238095f, 0.209877f, 0.202492f, 0.200625f, 0.200156f, 0.200039f}, + new float[] {0.999512f, 0.998053f, 0.992278f, 0.970149f, 0.894737f, 0.714286f, 0.5f, 0.384615f, 0.346939f, 0.336788f, 0.3342f, 0.33355f, 0.333388f}, + new float[] {0.999756f, 0.999025f, 0.996124f, 0.984848f, 0.944444f, 0.833333f, 0.666667f, 0.555556f, 0.515152f, 0.503876f, 0.500975f, 0.500244f, 0.500061f}, + new float[] {0.999878f, 0.999512f, 0.998058f, 0.992366f, 0.971429f, 0.909091f, 0.8f, 0.714286f, 0.68f, 0.670103f, 0.667532f, 0.666884f, 0.666721f}, + new float[] {0.999939f, 0.999756f, 0.999028f, 0.996169f, 0.985507f, 0.952381f, 0.888889f, 0.833333f, 0.809524f, 0.802469f, 0.800623f, 0.800156f, 0.800039f}, + new float[] {0.999969f, 0.999878f, 0.999514f, 0.998081f, 0.992701f, 0.97561f, 0.941176f, 0.909091f, 0.894737f, 0.890411f, 0.889273f, 0.888985f, 0.888913f}, + new float[] {0.999985f, 0.999939f, 0.999757f, 0.999039f, 0.996337f, 0.987654f, 0.969697f, 0.952381f, 0.944444f, 0.942029f, 0.941392f, 0.94123f, 0.94119f}, + new float[] {0.999992f, 0.99997f, 0.999878f, 0.999519f, 0.998165f, 0.993789f, 0.984615f, 0.97561f, 0.971429f, 0.970149f, 0.969811f, 0.969726f, 0.969704f}, + new float[] {0.999996f, 0.999985f, 0.999939f, 0.99976f, 0.999082f, 0.996885f, 0.992248f, 0.987654f, 0.985507f, 0.984848f, 0.984674f, 0.98463f, 0.984619f}, + new float[] {0.999998f, 0.999992f, 0.99997f, 0.99988f, 0.999541f, 0.99844f, 0.996109f, 0.993789f, 0.992701f, 0.992366f, 0.992278f, 0.992256f, 0.99225f}, + new float[] {0.999999f, 0.999996f, 0.999985f, 0.99994f, 0.99977f, 0.999219f, 0.998051f, 0.996885f, 0.996337f, 0.996169f, 0.996124f, 0.996113f, 0.99611f}, + new float[] {1f, 0.999998f, 0.999992f, 0.99997f, 0.999885f, 0.99961f, 0.999024f, 0.99844f, 0.998165f, 0.998081f, 0.998058f, 0.998053f, 0.998051f}, + new float[] {1f, 0.999999f, 0.999996f, 0.999985f, 0.999943f, 0.999805f, 0.999512f, 0.999219f, 0.999082f, 0.999039f, 0.999028f, 0.999025f, 0.999025f}, + new float[] {1f, 1f, 0.999998f, 0.999992f, 0.999971f, 0.999902f, 0.999756f, 0.99961f, 0.999541f, 0.999519f, 0.999514f, 0.999512f, 0.999512f}, + new float[] {1f, 1f, 0.999999f, 0.999996f, 0.999986f, 0.999951f, 0.999878f, 0.999805f, 0.99977f, 0.99976f, 0.999757f, 0.999756f, 0.999756f}, + new float[] {1f, 1f, 1f, 0.999998f, 0.999993f, 0.999976f, 0.999939f, 0.999902f, 0.999885f, 0.99988f, 0.999878f, 0.999878f, 0.999878f}, + new float[] {1f, 1f, 1f, 0.999999f, 0.999996f, 0.999988f, 0.999969f, 0.999951f, 0.999943f, 0.99994f, 0.999939f, 0.999939f, 0.999939f}, + new float[] {1f, 1f, 1f, 1f, 0.999998f, 0.999994f, 0.999985f, 0.999976f, 0.999971f, 0.99997f, 0.99997f, 0.99997f, 0.999969f}, + new float[] {1f, 1f, 1f, 1f, 0.999999f, 0.999997f, 0.999992f, 0.999988f, 0.999986f, 0.999985f, 0.999985f, 0.999985f, 0.999985f}, + new float[] {1f, 1f, 1f, 1f, 1f, 0.999998f, 0.999996f, 0.999994f, 0.999993f, 0.999992f, 0.999992f, 0.999992f, 0.999992f}, + new float[] {1f, 1f, 1f, 1f, 1f, 0.999999f, 0.999998f, 0.999997f, 0.999996f, 0.999996f, 0.999996f, 0.999996f, 0.999996f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 0.999999f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f} + }; + + private static float[][] Q_div_tab_right = { + new float[] {0.00775382f, 0.00775945f, 0.00778198f, 0.00787211f, 0.00823245f, 0.00967118f, 0.0153846f, 0.037594f, 0.117241f, 0.336788f, 0.667532f, 0.888985f, 0.969704f}, + new float[] {0.0153883f, 0.0153994f, 0.0154438f, 0.0156212f, 0.0163305f, 0.0191571f, 0.030303f, 0.0724638f, 0.209877f, 0.503876f, 0.800623f, 0.94123f, 0.984619f}, + new float[] {0.0303102f, 0.0303317f, 0.0304178f, 0.0307619f, 0.0321361f, 0.037594f, 0.0588235f, 0.135135f, 0.346939f, 0.670103f, 0.889273f, 0.969726f, 0.99225f}, + new float[] {0.058837f, 0.0588776f, 0.0590397f, 0.0596878f, 0.0622711f, 0.0724638f, 0.111111f, 0.238095f, 0.515152f, 0.802469f, 0.941392f, 0.98463f, 0.99611f}, + new float[] {0.111135f, 0.111208f, 0.111497f, 0.112652f, 0.117241f, 0.135135f, 0.2f, 0.384615f, 0.68f, 0.890411f, 0.969811f, 0.992256f, 0.998051f}, + new float[] {0.200039f, 0.200156f, 0.200625f, 0.202492f, 0.209877f, 0.238095f, 0.333333f, 0.555556f, 0.809524f, 0.942029f, 0.984674f, 0.996113f, 0.999025f}, + new float[] {0.333388f, 0.33355f, 0.3342f, 0.336788f, 0.346939f, 0.384615f, 0.5f, 0.714286f, 0.894737f, 0.970149f, 0.992278f, 0.998053f, 0.999512f}, + new float[] {0.500061f, 0.500244f, 0.500975f, 0.503876f, 0.515152f, 0.555556f, 0.666667f, 0.833333f, 0.944444f, 0.984848f, 0.996124f, 0.999025f, 0.999756f}, + new float[] {0.666721f, 0.666884f, 0.667532f, 0.670103f, 0.68f, 0.714286f, 0.8f, 0.909091f, 0.971429f, 0.992366f, 0.998058f, 0.999512f, 0.999878f}, + new float[] {0.800039f, 0.800156f, 0.800623f, 0.802469f, 0.809524f, 0.833333f, 0.888889f, 0.952381f, 0.985507f, 0.996169f, 0.999028f, 0.999756f, 0.999939f}, + new float[] {0.888913f, 0.888985f, 0.889273f, 0.890411f, 0.894737f, 0.909091f, 0.941176f, 0.97561f, 0.992701f, 0.998081f, 0.999514f, 0.999878f, 0.999969f}, + new float[] {0.94119f, 0.94123f, 0.941392f, 0.942029f, 0.944444f, 0.952381f, 0.969697f, 0.987654f, 0.996337f, 0.999039f, 0.999757f, 0.999939f, 0.999985f}, + new float[] {0.969704f, 0.969726f, 0.969811f, 0.970149f, 0.971429f, 0.97561f, 0.984615f, 0.993789f, 0.998165f, 0.999519f, 0.999878f, 0.99997f, 0.999992f}, + new float[] {0.984619f, 0.98463f, 0.984674f, 0.984848f, 0.985507f, 0.987654f, 0.992248f, 0.996885f, 0.999082f, 0.99976f, 0.999939f, 0.999985f, 0.999996f}, + new float[] {0.99225f, 0.992256f, 0.992278f, 0.992366f, 0.992701f, 0.993789f, 0.996109f, 0.99844f, 0.999541f, 0.99988f, 0.99997f, 0.999992f, 0.999998f}, + new float[] {0.99611f, 0.996113f, 0.996124f, 0.996169f, 0.996337f, 0.996885f, 0.998051f, 0.999219f, 0.99977f, 0.99994f, 0.999985f, 0.999996f, 0.999999f}, + new float[] {0.998051f, 0.998053f, 0.998058f, 0.998081f, 0.998165f, 0.99844f, 0.999024f, 0.99961f, 0.999885f, 0.99997f, 0.999992f, 0.999998f, 1f}, + new float[] {0.999025f, 0.999025f, 0.999028f, 0.999039f, 0.999082f, 0.999219f, 0.999512f, 0.999805f, 0.999943f, 0.999985f, 0.999996f, 0.999999f, 1f}, + new float[] {0.999512f, 0.999512f, 0.999514f, 0.999519f, 0.999541f, 0.99961f, 0.999756f, 0.999902f, 0.999971f, 0.999992f, 0.999998f, 1f, 1f}, + new float[] {0.999756f, 0.999756f, 0.999757f, 0.99976f, 0.99977f, 0.999805f, 0.999878f, 0.999951f, 0.999986f, 0.999996f, 0.999999f, 1f, 1f}, + new float[] {0.999878f, 0.999878f, 0.999878f, 0.99988f, 0.999885f, 0.999902f, 0.999939f, 0.999976f, 0.999993f, 0.999998f, 1f, 1f, 1f}, + new float[] {0.999939f, 0.999939f, 0.999939f, 0.99994f, 0.999943f, 0.999951f, 0.999969f, 0.999988f, 0.999996f, 0.999999f, 1f, 1f, 1f}, + new float[] {0.999969f, 0.99997f, 0.99997f, 0.99997f, 0.999971f, 0.999976f, 0.999985f, 0.999994f, 0.999998f, 1f, 1f, 1f, 1f}, + new float[] {0.999985f, 0.999985f, 0.999985f, 0.999985f, 0.999986f, 0.999988f, 0.999992f, 0.999997f, 0.999999f, 1f, 1f, 1f, 1f}, + new float[] {0.999992f, 0.999992f, 0.999992f, 0.999992f, 0.999993f, 0.999994f, 0.999996f, 0.999998f, 1f, 1f, 1f, 1f, 1f}, + new float[] {0.999996f, 0.999996f, 0.999996f, 0.999996f, 0.999996f, 0.999997f, 0.999998f, 0.999999f, 1f, 1f, 1f, 1f, 1f}, + new float[] {0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999999f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}, + new float[] {1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f} + }; + + public static void ExtractEnvelopeData(SBR sbr, int ch) + { + int l, k; + + for (l = 0; l < sbr._L_E[ch]; l++) + { + if (sbr._bs_df_env[ch, l] == 0) + { + for (k = 1; k < sbr._n[sbr._f[ch, l]]; k++) + { + sbr._E[ch, k, l] = sbr._E[ch, k - 1, l] + sbr._E[ch, k, l]; + if (sbr._E[ch, k, l] < 0) + sbr._E[ch, k, l] = 0; + } + + } + else + { /* bs_df_env == 1 */ + + int g = l == 0 ? sbr._f_prev[ch] : sbr._f[ch, l - 1]; + int E_prev; + + if (sbr._f[ch, l] == g) + { + for (k = 0; k < sbr._n[sbr._f[ch, l]]; k++) + { + if (l == 0) + E_prev = sbr._E_prev[ch, k]; + else + E_prev = sbr._E[ch, k, l - 1]; + + sbr._E[ch, k, l] = E_prev + sbr._E[ch, k, l]; + } + + } + else if (g == 1 && sbr._f[ch, l] == 0) + { + int i; + + for (k = 0; k < sbr._n[sbr._f[ch, l]]; k++) + { + for (i = 0; i < sbr._N_high; i++) + { + if (sbr._f_table_res[Constants.HI_RES, i] == sbr._f_table_res[Constants.LO_RES, k]) + { + if (l == 0) + E_prev = sbr._E_prev[ch, i]; + else + E_prev = sbr._E[ch, i, l - 1]; + + sbr._E[ch, k, l] = E_prev + sbr._E[ch, k, l]; + } + } + } + + } + else if (g == 0 && sbr._f[ch, l] == 1) + { + int i; + + for (k = 0; k < sbr._n[sbr._f[ch, l]]; k++) + { + for (i = 0; i < sbr._N_low; i++) + { + if (sbr._f_table_res[Constants.LO_RES, i] <= sbr._f_table_res[Constants.HI_RES, k] + && sbr._f_table_res[Constants.HI_RES, k] < sbr._f_table_res[Constants.LO_RES, i + 1]) + { + if (l == 0) + E_prev = sbr._E_prev[ch, i]; + else + E_prev = sbr._E[ch, i, l - 1]; + + sbr._E[ch, k, l] = E_prev + sbr._E[ch, k, l]; + } + } + } + } + } + } + } + + public static void ExtractNoiseFloorData(SBR sbr, int ch) + { + int l, k; + + for (l = 0; l < sbr._L_Q[ch]; l++) + { + if (sbr._bs_df_noise[ch, l] == 0) + { + for (k = 1; k < sbr._N_Q; k++) + { + sbr._Q[ch, k, l] = sbr._Q[ch, k, l] + sbr._Q[ch, k - 1, l]; + } + } + else + { + if (l == 0) + { + for (k = 0; k < sbr._N_Q; k++) + { + sbr._Q[ch, k, l] = sbr._Q_prev[ch, k] + sbr._Q[ch, k, 0]; + } + } + else + { + for (k = 0; k < sbr._N_Q; k++) + { + sbr._Q[ch, k, l] = sbr._Q[ch, k, l - 1] + sbr._Q[ch, k, l]; + } + } + } + } + } + + /* calculates 1/(1+Q) */ + /* [0..1] */ + public static float CalcQDiv(SBR sbr, int ch, int m, int l) + { + if (sbr._bs_coupling) + { + /* left channel */ + if (sbr._Q[0, m, l] < 0 || sbr._Q[0, m, l] > 30 + || sbr._Q[1, m, l] < 0 || sbr._Q[1, m, l] > 24 /* 2*panOffset(1) */) + { + return 0; + } + else + { + /* the pan parameter is always even */ + if (ch == 0) + { + return Q_div_tab_left[sbr._Q[0, m, l]][sbr._Q[1, m, l] >> 1]; + } + else + { + return Q_div_tab_right[sbr._Q[0, m, l]][sbr._Q[1, m, l] >> 1]; + } + } + } + else + { + /* no coupling */ + if (sbr._Q[ch, m, l] < 0 || sbr._Q[ch, m, l] > 30) + { + return 0; + } + else + { + return Q_div_tab[sbr._Q[ch, m, l]]; + } + } + } + + /* calculates Q/(1+Q) */ + /* [0..1] */ + public static float CalcQDiv2(SBR sbr, int ch, int m, int l) + { + if (sbr._bs_coupling) + { + if (sbr._Q[0, m, l] < 0 || sbr._Q[0, m, l] > 30 + || sbr._Q[1, m, l] < 0 || sbr._Q[1, m, l] > 24 /* 2*panOffset(1) */) + { + return 0; + } + else + { + /* the pan parameter is always even */ + if (ch == 0) + { + return Q_div2_tab_left[sbr._Q[0, m, l]][sbr._Q[1, m, l] >> 1]; + } + else + { + return Q_div2_tab_right[sbr._Q[0, m, l]][sbr._Q[1, m, l] >> 1]; + } + } + } + else + { + /* no coupling */ + if (sbr._Q[ch, m, l] < 0 || sbr._Q[ch, m, l] > 30) + { + return 0; + } + else + { + return Q_div2_tab[sbr._Q[ch, m, l]]; + } + } + } + + public static void DequantChannel(SBR sbr, int ch) + { + if (!sbr._bs_coupling) + { + int exp; + int l, k; + int amp = sbr._ampRes[ch] ? 0 : 1; + + for (l = 0; l < sbr._L_E[ch]; l++) + { + for (k = 0; k < sbr._n[sbr._f[ch, l]]; k++) + { + /* +6 for the *64 and -10 for the /32 in the synthesis QMF (fixed) + * since this is a energy value: (x/32)^2 = (x^2)/1024 + */ + /* exp = (sbr.E[ch][k][l] >> amp) + 6; */ + exp = sbr._E[ch, k, l] >> amp; + + if (exp < 0 || exp >= 64) + { + sbr._E_orig[ch, k, l] = 0; + } + else + { + sbr._E_orig[ch, k, l] = E_deq_tab[exp]; + + /* save half the table size at the cost of 1 multiply */ + if (amp != 0 && (sbr._E[ch, k, l] & 1) != 0) + { + sbr._E_orig[ch, k, l] = sbr._E_orig[ch, k, l] * 1.414213562f; + } + } + } + } + + for (l = 0; l < sbr._L_Q[ch]; l++) + { + for (k = 0; k < sbr._N_Q; k++) + { + sbr._Q_div[ch, k, l] = CalcQDiv(sbr, ch, k, l); + sbr._Q_div2[ch, k, l] = CalcQDiv2(sbr, ch, k, l); + } + } + } + } + + private static float[] E_pan_tab = { + 0.000244081f, 0.000488043f, + 0.00097561f, 0.00194932f, + 0.00389105f, 0.00775194f, + 0.0153846f, 0.030303f, + 0.0588235f, 0.111111f, + 0.2f, 0.333333f, + 0.5f, 0.666667f, + 0.8f, 0.888889f, + 0.941176f, 0.969697f, + 0.984615f, 0.992248f, + 0.996109f, 0.998051f, + 0.999024f, 0.999512f, + 0.999756f + }; + + public static void Unmap(SBR sbr) + { + float tmp; + int exp0, exp1; + int l, k; + int amp0 = sbr._ampRes[0] ? 0 : 1; + int amp1 = sbr._ampRes[1] ? 0 : 1; + + for (l = 0; l < sbr._L_E[0]; l++) + { + for (k = 0; k < sbr._n[sbr._f[0, l]]; k++) + { + /* +6: * 64 ; +1: * 2 ; */ + exp0 = (sbr._E[0, k, l] >> amp0) + 1; + + /* UN_MAP removed: (x / 4096) same as (x >> 12) */ + /* E[1] is always even so no need for compensating the divide by 2 with + * an extra multiplication + */ + /* exp1 = (sbr.E[1][k][l] >> amp1) - 12; */ + exp1 = sbr._E[1, k, l] >> amp1; + + if (exp0 < 0 || exp0 >= 64 + || exp1 < 0 || exp1 > 24) + { + sbr._E_orig[1, k, l] = 0; + sbr._E_orig[0, k, l] = 0; + } + else + { + tmp = E_deq_tab[exp0]; + if (amp0 != 0 && (sbr._E[0, k, l] & 1) != 0) + { + tmp *= 1.414213562f; + } + + /* panning */ + sbr._E_orig[0, k, l] = tmp * E_pan_tab[exp1]; + sbr._E_orig[1, k, l] = tmp * E_pan_tab[24 - exp1]; + } + } + } + + for (l = 0; l < sbr._L_Q[0]; l++) + { + for (k = 0; k < sbr._N_Q; k++) + { + sbr._Q_div[0, k, l] = CalcQDiv(sbr, 0, k, l); + sbr._Q_div[1, k, l] = CalcQDiv(sbr, 1, k, l); + sbr._Q_div2[0, k, l] = CalcQDiv2(sbr, 0, k, l); + sbr._Q_div2[1, k, l] = CalcQDiv2(sbr, 1, k, l); + } + } + } + } +} diff --git a/SharpJaad.AAC/Sbr/NoiseTable.cs b/SharpJaad.AAC/Sbr/NoiseTable.cs new file mode 100644 index 0000000..3b82fdf --- /dev/null +++ b/SharpJaad.AAC/Sbr/NoiseTable.cs @@ -0,0 +1,521 @@ +namespace SharpJaad.AAC.Sbr +{ + public static class NoiseTable + { + /* Table 1.A.13 Noise table V */ + public static float[,] NOISE_TABLE = { + {-0.99948155879974f, -0.59483414888382f}, + {0.97113454341888f, -0.67528516054153f}, + {0.14130051434040f, -0.95090985298157f}, + {-0.47005495429039f, -0.37340548634529f}, + {0.80705064535141f, 0.29653668403625f}, + {-0.38981479406357f, 0.89572608470917f}, + {-0.01053049881011f, -0.66959059238434f}, + {-0.91266369819641f, -0.11522938311100f}, + {0.54840421676636f, 0.75221365690231f}, + {0.40009254217148f, -0.98929399251938f}, + {-0.99867975711823f, -0.88147068023682f}, + {-0.95531076192856f, 0.90908759832382f}, + {-0.45725932717323f, -0.56716322898865f}, + {-0.72929674386978f, -0.98008275032043f}, + {0.75622802972794f, 0.20950329303741f}, + {0.07069442421198f, -0.78247898817062f}, + {0.74496251344681f, -0.91169005632401f}, + {-0.96440184116364f, -0.94739919900894f}, + {0.30424630641937f, -0.49438267946243f}, + {0.66565030813217f, 0.64652937650681f}, + {0.91697007417679f, 0.17514097690582f}, + {-0.70774918794632f, 0.52548652887344f}, + {-0.70051413774490f, -0.45340028405190f}, + {-0.99496513605118f, -0.90071910619736f}, + {0.98164492845535f, -0.77463155984879f}, + {-0.54671579599380f, -0.02570928446949f}, + {-0.01689629070461f, 0.00287506449968f}, + {-0.86110347509384f, 0.42548584938049f}, + {-0.98892980813980f, -0.87881129980087f}, + {0.51756626367569f, 0.66926783323288f}, + {-0.99635028839111f, -0.58107727766037f}, + {-0.99969369173050f, 0.98369991779327f}, + {0.55266261100769f, 0.59449058771133f}, + {0.34581178426743f, 0.94879418611526f}, + {0.62664210796356f, -0.74402970075607f}, + {-0.77149701118469f, -0.33883658051491f}, + {-0.91592246294022f, 0.03687901422381f}, + {-0.76285493373871f, -0.91371870040894f}, + {0.79788339138031f, -0.93180972337723f}, + {0.54473078250885f, -0.11919206380844f}, + {-0.85639280080795f, 0.42429855465889f}, + {-0.92882400751114f, 0.27871808409691f}, + {-0.11708371341228f, -0.99800843000412f}, + {0.21356749534607f, -0.90716296434402f}, + {-0.76191693544388f, 0.99768120050430f}, + {0.98111045360565f, -0.95854461193085f}, + {-0.85913270711899f, 0.95766568183899f}, + {-0.93307244777679f, 0.49431759119034f}, + {0.30485755205154f, -0.70540034770966f}, + {0.85289651155472f, 0.46766132116318f}, + {0.91328084468842f, -0.99839597940445f}, + {-0.05890199914575f, 0.70741826295853f}, + {0.28398686647415f, 0.34633556008339f}, + {0.95258164405823f, -0.54893416166306f}, + {-0.78566324710846f, -0.75568538904190f}, + {-0.95789498090744f, -0.20423194766045f}, + {0.82411158084869f, 0.96654617786407f}, + {-0.65185445547104f, -0.88734990358353f}, + {-0.93643605709076f, 0.99870789051056f}, + {0.91427159309387f, -0.98290503025055f}, + {-0.70395684242249f, 0.58796799182892f}, + {0.00563771976158f, 0.61768198013306f}, + {0.89065051078796f, 0.52783352136612f}, + {-0.68683707714081f, 0.80806946754456f}, + {0.72165340185165f, -0.69259858131409f}, + {-0.62928247451782f, 0.13627037405968f}, + {0.29938435554504f, -0.46051329374313f}, + {-0.91781955957413f, -0.74012714624405f}, + {0.99298715591431f, 0.40816611051559f}, + {0.82368296384811f, -0.74036049842834f}, + {-0.98512834310532f, -0.99972331523895f}, + {-0.95915371179581f, -0.99237799644470f}, + {-0.21411126852036f, -0.93424820899963f}, + {-0.68821477890015f, -0.26892307400703f}, + {0.91851997375488f, 0.09358228743076f}, + {-0.96062767505646f, 0.36099094152451f}, + {0.51646184921265f, -0.71373331546783f}, + {0.61130720376968f, 0.46950140595436f}, + {0.47336128354073f, -0.27333179116249f}, + {0.90998309850693f, 0.96715664863586f}, + {0.44844800233841f, 0.99211573600769f}, + {0.66614890098572f, 0.96590173244476f}, + {0.74922239780426f, -0.89879858493805f}, + {-0.99571585655212f, 0.52785521745682f}, + {0.97401082515717f, -0.16855870187283f}, + {0.72683745622635f, -0.48060774803162f}, + {0.95432192087173f, 0.68849605321884f}, + {-0.72962206602097f, -0.76608443260193f}, + {-0.85359477996826f, 0.88738125562668f}, + {-0.81412428617477f, -0.97480767965317f}, + {-0.87930774688721f, 0.74748307466507f}, + {-0.71573328971863f, -0.98570609092712f}, + {0.83524298667908f, 0.83702534437180f}, + {-0.48086065053940f, -0.98848503828049f}, + {0.97139126062393f, 0.80093622207642f}, + {0.51992827653885f, 0.80247628688812f}, + {-0.00848591234535f, -0.76670128107071f}, + {-0.70294374227524f, 0.55359911918640f}, + {-0.95894426107407f, -0.43265503644943f}, + {0.97079253196716f, 0.09325857460499f}, + {-0.92404294013977f, 0.85507702827454f}, + {-0.69506472349167f, 0.98633414506912f}, + {0.26559203863144f, 0.73314309120178f}, + {0.28038442134857f, 0.14537914097309f}, + {-0.74138122797012f, 0.99310338497162f}, + {-0.01752796024084f, -0.82616633176804f}, + {-0.55126774311066f, -0.98898541927338f}, + {0.97960901260376f, -0.94021445512772f}, + {-0.99196308851242f, 0.67019015550613f}, + {-0.67684930562973f, 0.12631492316723f}, + {0.09140039235353f, -0.20537731051445f}, + {-0.71658962965012f, -0.97788202762604f}, + {0.81014639139175f, 0.53722649812698f}, + {0.40616992115974f, -0.26469007134438f}, + {-0.67680186033249f, 0.94502049684525f}, + {0.86849772930145f, -0.18333598971367f}, + {-0.99500381946564f, -0.02634122036397f}, + {0.84329187870026f, 0.10406957566738f}, + {-0.09215968847275f, 0.69540011882782f}, + {0.99956172704697f, -0.12358541786671f}, + {-0.79732781648636f, -0.91582524776459f}, + {0.96349972486496f, 0.96640455722809f}, + {-0.79942780733109f, 0.64323902130127f}, + {-0.11566039919853f, 0.28587844967842f}, + {-0.39922955632210f, 0.94129604101181f}, + {0.99089199304581f, -0.92062628269196f}, + {0.28631284832954f, -0.91035044193268f}, + {-0.83302724361420f, -0.67330408096313f}, + {0.95404446125031f, 0.49162766337395f}, + {-0.06449863314629f, 0.03250560909510f}, + {-0.99575054645538f, 0.42389783263206f}, + {-0.65501141548157f, 0.82546114921570f}, + {-0.81254440546036f, -0.51627236604691f}, + {-0.99646371603012f, 0.84490531682968f}, + {0.00287840608507f, 0.64768260717392f}, + {0.70176988840103f, -0.20453028380871f}, + {0.96361881494522f, 0.40706968307495f}, + {-0.68883758783340f, 0.91338956356049f}, + {-0.34875586628914f, 0.71472293138504f}, + {0.91980081796646f, 0.66507452726364f}, + {-0.99009048938751f, 0.85868018865585f}, + {0.68865793943405f, 0.55660319328308f}, + {-0.99484401941299f, -0.20052559673786f}, + {0.94214510917664f, -0.99696427583694f}, + {-0.67414629459381f, 0.49548220634460f}, + {-0.47339352965355f, -0.85904330015182f}, + {0.14323651790619f, -0.94145596027374f}, + {-0.29268294572830f, 0.05759225040674f}, + {0.43793860077858f, -0.78904968500137f}, + {-0.36345127224922f, 0.64874434471130f}, + {-0.08750604838133f, 0.97686946392059f}, + {-0.96495270729065f, -0.53960305452347f}, + {0.55526942014694f, 0.78891521692276f}, + {0.73538213968277f, 0.96452075242996f}, + {-0.30889773368835f, -0.80664390325546f}, + {0.03574995696545f, -0.97325617074966f}, + {0.98720687627792f, 0.48409134149551f}, + {-0.81689298152924f, -0.90827703475952f}, + {0.67866861820221f, 0.81284505128860f}, + {-0.15808570384979f, 0.85279554128647f}, + {0.80723392963409f, -0.24717418849468f}, + {0.47788757085800f, -0.46333149075508f}, + {0.96367555856705f, 0.38486748933792f}, + {-0.99143874645233f, -0.24945276975632f}, + {0.83081877231598f, -0.94780850410461f}, + {-0.58753192424774f, 0.01290772389621f}, + {0.95538109540939f, -0.85557049512863f}, + {-0.96490919589996f, -0.64020973443985f}, + {-0.97327101230621f, 0.12378127872944f}, + {0.91400367021561f, 0.57972472906113f}, + {-0.99925839900970f, 0.71084845066071f}, + {-0.86875903606415f, -0.20291699469090f}, + {-0.26240035891533f, -0.68264555931091f}, + {-0.24664412438869f, -0.87642270326614f}, + {0.02416275814176f, 0.27192914485931f}, + {0.82068622112274f, -0.85087788105011f}, + {0.88547372817993f, -0.89636802673340f}, + {-0.18173077702522f, -0.26152145862579f}, + {0.09355476498604f, 0.54845124483109f}, + {-0.54668414592743f, 0.95980775356293f}, + {0.37050989270210f, -0.59910142421722f}, + {-0.70373594760895f, 0.91227668523788f}, + {-0.34600785374641f, -0.99441426992416f}, + {-0.68774479627609f, -0.30238837003708f}, + {-0.26843291521072f, 0.83115667104721f}, + {0.49072334170341f, -0.45359709858894f}, + {0.38975992798805f, 0.95515358448029f}, + {-0.97757124900818f, 0.05305894464254f}, + {-0.17325553297997f, -0.92770671844482f}, + {0.99948036670685f, 0.58285546302795f}, + {-0.64946246147156f, 0.68645507097244f}, + {-0.12016920745373f, -0.57147324085236f}, + {-0.58947455883026f, -0.34847131371498f}, + {-0.41815140843391f, 0.16276422142982f}, + {0.99885648488998f, 0.11136095225811f}, + {-0.56649613380432f, -0.90494865179062f}, + {0.94138020277023f, 0.35281917452812f}, + {-0.75725078582764f, 0.53650552034378f}, + {0.20541973412037f, -0.94435143470764f}, + {0.99980372190475f, 0.79835915565491f}, + {0.29078277945518f, 0.35393777489662f}, + {-0.62858772277832f, 0.38765692710876f}, + {0.43440905213356f, -0.98546332120895f}, + {-0.98298585414886f, 0.21021524071693f}, + {0.19513028860092f, -0.94239830970764f}, + {-0.95476663112640f, 0.98364555835724f}, + {0.93379634618759f, -0.70881992578506f}, + {-0.85235410928726f, -0.08342348039150f}, + {-0.86425095796585f, -0.45795026421547f}, + {0.38879778981209f, 0.97274428606033f}, + {0.92045122385025f, -0.62433654069901f}, + {0.89162534475327f, 0.54950958490372f}, + {-0.36834338307381f, 0.96458297967911f}, + {0.93891763687134f, -0.89968353509903f}, + {0.99267655611038f, -0.03757034242153f}, + {-0.94063472747803f, 0.41332337260246f}, + {0.99740225076675f, -0.16830494999886f}, + {-0.35899412631989f, -0.46633225679398f}, + {0.05237237364054f, -0.25640362501144f}, + {0.36703583598137f, -0.38653266429901f}, + {0.91653180122375f, -0.30587628483772f}, + {0.69000804424286f, 0.90952169895172f}, + {-0.38658750057220f, 0.99501574039459f}, + {-0.29250815510750f, 0.37444993853569f}, + {-0.60182201862335f, 0.86779648065567f}, + {-0.97418588399887f, 0.96468526124954f}, + {0.88461571931839f, 0.57508403062820f}, + {0.05198933184147f, 0.21269661188126f}, + {-0.53499621152878f, 0.97241556644440f}, + {-0.49429559707642f, 0.98183864355087f}, + {-0.98935145139694f, -0.40249159932137f}, + {-0.98081380128860f, -0.72856897115707f}, + {-0.27338150143623f, 0.99950921535492f}, + {0.06310802698135f, -0.54539585113525f}, + {-0.20461677014828f, -0.14209978282452f}, + {0.66223841905594f, 0.72528582811356f}, + {-0.84764343500137f, 0.02372316829860f}, + {-0.89039862155914f, 0.88866579532623f}, + {0.95903307199478f, 0.76744925975800f}, + {0.73504126071930f, -0.03747203201056f}, + {-0.31744435429573f, -0.36834111809731f}, + {-0.34110826253891f, 0.40211221575737f}, + {0.47803884744644f, -0.39423218369484f}, + {0.98299193382263f, 0.01989791356027f}, + {-0.30963072180748f, -0.18076720833778f}, + {0.99992591142654f, -0.26281872391701f}, + {-0.93149733543396f, -0.98313164710999f}, + {0.99923473596573f, -0.80142992734909f}, + {-0.26024168729782f, -0.75999760627747f}, + {-0.35712513327599f, 0.19298963248730f}, + {-0.99899083375931f, 0.74645155668259f}, + {0.86557173728943f, 0.55593866109848f}, + {0.33408042788506f, 0.86185956001282f}, + {0.99010735750198f, 0.04602397605777f}, + {-0.66694271564484f, -0.91643613576889f}, + {0.64016789197922f, 0.15649530291557f}, + {0.99570536613464f, 0.45844584703445f}, + {-0.63431465625763f, 0.21079117059708f}, + {-0.07706847041845f, -0.89581435918808f}, + {0.98590087890625f, 0.88241720199585f}, + {0.80099332332611f, -0.36851897835732f}, + {0.78368133306503f, 0.45506998896599f}, + {0.08707806468010f, 0.80938994884491f}, + {-0.86811882257462f, 0.39347308874130f}, + {-0.39466530084610f, -0.66809433698654f}, + {0.97875326871872f, -0.72467839717865f}, + {-0.95038563013077f, 0.89563220739365f}, + {0.17005239427090f, 0.54683053493500f}, + {-0.76910793781281f, -0.96226614713669f}, + {0.99743282794952f, 0.42697158455849f}, + {0.95437383651733f, 0.97002321481705f}, + {0.99578905105591f, -0.54106825590134f}, + {0.28058260679245f, -0.85361421108246f}, + {0.85256522893906f, -0.64567607641220f}, + {-0.50608539581299f, -0.65846014022827f}, + {-0.97210735082626f, -0.23095212876797f}, + {0.95424050092697f, -0.99240148067474f}, + {-0.96926569938660f, 0.73775655031204f}, + {0.30872163176537f, 0.41514959931374f}, + {-0.24523839354515f, 0.63206630945206f}, + {-0.33813264966011f, -0.38661777973175f}, + {-0.05826828256249f, -0.06940773874521f}, + {-0.22898460924625f, 0.97054851055145f}, + {-0.18509915471077f, 0.47565764188766f}, + {-0.10488238185644f, -0.87769949436188f}, + {-0.71886587142944f, 0.78030979633331f}, + {0.99793875217438f, 0.90041309595108f}, + {0.57563304901123f, -0.91034334897995f}, + {0.28909647464752f, 0.96307784318924f}, + {0.42188999056816f, 0.48148649930954f}, + {0.93335050344467f, -0.43537023663521f}, + {-0.97087377309799f, 0.86636447906494f}, + {0.36722871661186f, 0.65291655063629f}, + {-0.81093025207520f, 0.08778370171785f}, + {-0.26240602135658f, -0.92774093151093f}, + {0.83996498584747f, 0.55839848518372f}, + {-0.99909615516663f, -0.96024608612061f}, + {0.74649465084076f, 0.12144893407822f}, + {-0.74774593114853f, -0.26898062229156f}, + {0.95781666040421f, -0.79047924280167f}, + {0.95472306013107f, -0.08588775992393f}, + {0.48708331584930f, 0.99999040365219f}, + {0.46332037448883f, 0.10964126139879f}, + {-0.76497006416321f, 0.89210927486420f}, + {0.57397389411926f, 0.35289704799652f}, + {0.75374317169189f, 0.96705216169357f}, + {-0.59174400568008f, -0.89405369758606f}, + {0.75087904930115f, -0.29612672328949f}, + {-0.98607856035233f, 0.25034910440445f}, + {-0.40761056542397f, -0.90045571327209f}, + {0.66929268836975f, 0.98629492521286f}, + {-0.97463697195053f, -0.00190223299433f}, + {0.90145510435104f, 0.99781388044357f}, + {-0.87259286642075f, 0.99233585596085f}, + {-0.91529458761215f, -0.15698707103729f}, + {-0.03305738791823f, -0.37205263972282f}, + {0.07223051041365f, -0.88805001974106f}, + {0.99498009681702f, 0.97094357013702f}, + {-0.74904936552048f, 0.99985486268997f}, + {0.04585228487849f, 0.99812334775925f}, + {-0.89054954051971f, -0.31791913509369f}, + {-0.83782142400742f, 0.97637635469437f}, + {0.33454805612564f, -0.86231517791748f}, + {-0.99707579612732f, 0.93237990140915f}, + {-0.22827528417110f, 0.18874759972095f}, + {0.67248046398163f, -0.03646211326122f}, + {-0.05146538093686f, -0.92599701881409f}, + {0.99947297573090f, 0.93625229597092f}, + {0.66951125860214f, 0.98905825614929f}, + {-0.99602955579758f, -0.44654715061188f}, + {0.82104903459549f, 0.99540740251541f}, + {0.99186509847641f, 0.72022998332977f}, + {-0.65284591913223f, 0.52186721563339f}, + {0.93885445594788f, -0.74895310401917f}, + {0.96735250949860f, 0.90891814231873f}, + {-0.22225968539715f, 0.57124030590057f}, + {-0.44132784008980f, -0.92688840627670f}, + {-0.85694974660873f, 0.88844531774521f}, + {0.91783040761948f, -0.46356892585754f}, + {0.72556972503662f, -0.99899554252625f}, + {-0.99711579084396f, 0.58211559057236f}, + {0.77638977766037f, 0.94321835041046f}, + {0.07717324048281f, 0.58638399839401f}, + {-0.56049829721451f, 0.82522302865982f}, + {0.98398894071579f, 0.39467439055443f}, + {0.47546947002411f, 0.68613046407700f}, + {0.65675091743469f, 0.18331636488438f}, + {0.03273375332355f, -0.74933111667633f}, + {-0.38684144616127f, 0.51337349414825f}, + {-0.97346270084381f, -0.96549361944199f}, + {-0.53282153606415f, -0.91423267126083f}, + {0.99817311763763f, 0.61133575439453f}, + {-0.50254499912262f, -0.88829338550568f}, + {0.01995873264968f, 0.85223513841629f}, + {0.99930381774902f, 0.94578897953033f}, + {0.82907766103745f, -0.06323442608118f}, + {-0.58660709857941f, 0.96840775012970f}, + {-0.17573736608028f, -0.48166921734810f}, + {0.83434289693832f, -0.13023450970650f}, + {0.05946491286159f, 0.20511047542095f}, + {0.81505483388901f, -0.94685947895050f}, + {-0.44976380467415f, 0.40894573926926f}, + {-0.89746475219727f, 0.99846577644348f}, + {0.39677256345749f, -0.74854665994644f}, + {-0.07588948309422f, 0.74096214771271f}, + {0.76343196630478f, 0.41746628284454f}, + {-0.74490106105804f, 0.94725912809372f}, + {0.64880120754242f, 0.41336661577225f}, + {0.62319535017014f, -0.93098312616348f}, + {0.42215818166733f, -0.07712787389755f}, + {0.02704554051161f, -0.05417517945170f}, + {0.80001771450043f, 0.91542196273804f}, + {-0.79351830482483f, -0.36208897829056f}, + {0.63872361183167f, 0.08128252625465f}, + {0.52890521287918f, 0.60048872232437f}, + {0.74238550662994f, 0.04491915181279f}, + {0.99096131324768f, -0.19451183080673f}, + {-0.80412328243256f, -0.88513815402985f}, + {-0.64612615108490f, 0.72198677062988f}, + {0.11657770723104f, -0.83662831783295f}, + {-0.95053184032440f, -0.96939903497696f}, + {-0.62228870391846f, 0.82767260074615f}, + {0.03004475869238f, -0.99738895893097f}, + {-0.97987216711044f, 0.36526128649712f}, + {-0.99986982345581f, -0.36021611094475f}, + {0.89110648632050f, -0.97894251346588f}, + {0.10407960414886f, 0.77357792854309f}, + {0.95964735746384f, -0.35435819625854f}, + {0.50843232870102f, 0.96107691526413f}, + {0.17006334662437f, -0.76854026317596f}, + {0.25872674584389f, 0.99893301725388f}, + {-0.01115998718888f, 0.98496019840240f}, + {-0.79598701000214f, 0.97138410806656f}, + {-0.99264711141586f, -0.99542820453644f}, + {-0.99829661846161f, 0.01877138763666f}, + {-0.70801013708115f, 0.33680686354637f}, + {-0.70467054843903f, 0.93272775411606f}, + {0.99846023321152f, -0.98725748062134f}, + {-0.63364970684052f, -0.16473594307899f}, + {-0.16258217394352f, -0.95939123630524f}, + {-0.43645593523979f, -0.94805032014847f}, + {-0.99848473072052f, 0.96245169639587f}, + {-0.16796459257603f, -0.98987513780594f}, + {-0.87979227304459f, -0.71725726127625f}, + {0.44183099269867f, -0.93568974733353f}, + {0.93310177326202f, -0.99913311004639f}, + {-0.93941932916641f, -0.56409376859665f}, + {-0.88590002059937f, 0.47624599933624f}, + {0.99971461296082f, -0.83889955282211f}, + {-0.75376385450363f, 0.00814643409103f}, + {0.93887686729431f, -0.11284527927637f}, + {0.85126435756683f, 0.52349251508713f}, + {0.39701420068741f, 0.81779634952545f}, + {-0.37024465203285f, -0.87071657180786f}, + {-0.36024826765060f, 0.34655734896660f}, + {-0.93388813734055f, -0.84476542472839f}, + {-0.65298801660538f, -0.18439576029778f}, + {0.11960318684578f, 0.99899345636368f}, + {0.94292563199997f, 0.83163905143738f}, + {0.75081145763397f, -0.35533222556114f}, + {0.56721979379654f, -0.24076835811138f}, + {0.46857765316963f, -0.30140233039856f}, + {0.97312313318253f, -0.99548190832138f}, + {-0.38299977779388f, 0.98516911268234f}, + {0.41025799512863f, 0.02116736955941f}, + {0.09638062119484f, 0.04411984235048f}, + {-0.85283249616623f, 0.91475564241409f}, + {0.88866806030273f, -0.99735265970230f}, + {-0.48202428221703f, -0.96805608272552f}, + {0.27572581171989f, 0.58634752035141f}, + {-0.65889132022858f, 0.58835631608963f}, + {0.98838084936142f, 0.99994349479675f}, + {-0.20651349425316f, 0.54593044519424f}, + {-0.62126415967941f, -0.59893679618835f}, + {0.20320105552673f, -0.86879181861877f}, + {-0.97790551185608f, 0.96290808916092f}, + {0.11112534999847f, 0.21484763920307f}, + {-0.41368338465691f, 0.28216838836670f}, + {0.24133038520813f, 0.51294362545013f}, + {-0.66393411159515f, -0.08249679952860f}, + {-0.53697830438614f, -0.97649902105331f}, + {-0.97224736213684f, 0.22081333398819f}, + {0.87392479181290f, -0.12796173989773f}, + {0.19050361216068f, 0.01602615416050f}, + {-0.46353441476822f, -0.95249038934708f}, + {-0.07064096629620f, -0.94479805231094f}, + {-0.92444086074829f, -0.10457590222359f}, + {-0.83822596073151f, -0.01695043221116f}, + {0.75214684009552f, -0.99955683946609f}, + {-0.42102998495102f, 0.99720942974091f}, + {-0.72094786167145f, -0.35008960962296f}, + {0.78843313455582f, 0.52851396799088f}, + {0.97394025325775f, -0.26695942878723f}, + {0.99206465482712f, -0.57010120153427f}, + {0.76789611577988f, -0.76519358158112f}, + {-0.82002419233322f, -0.73530179262161f}, + {0.81924992799759f, 0.99698424339294f}, + {-0.26719850301743f, 0.68903368711472f}, + {-0.43311259150505f, 0.85321813821793f}, + {0.99194979667664f, 0.91876250505447f}, + {-0.80691999197006f, -0.32627540826797f}, + {0.43080005049706f, -0.21919095516205f}, + {0.67709493637085f, -0.95478075742722f}, + {0.56151771545410f, -0.70693808794022f}, + {0.10831862688065f, -0.08628837019205f}, + {0.91229414939880f, -0.65987348556519f}, + {-0.48972892761230f, 0.56289243698120f}, + {-0.89033657312393f, -0.71656566858292f}, + {0.65269446372986f, 0.65916007757187f}, + {0.67439478635788f, -0.81684380769730f}, + {-0.47770830988884f, -0.16789555549622f}, + {-0.99715977907181f, -0.93565785884857f}, + {-0.90889590978622f, 0.62034398317337f}, + {-0.06618622690439f, -0.23812216520309f}, + {0.99430269002914f, 0.18812555074692f}, + {0.97686403989792f, -0.28664535284042f}, + {0.94813650846481f, -0.97506642341614f}, + {-0.95434498786926f, -0.79607981443405f}, + {-0.49104782938957f, 0.32895213365555f}, + {0.99881172180176f, 0.88993984460831f}, + {0.50449168682098f, -0.85995072126389f}, + {0.47162890434265f, -0.18680204451084f}, + {-0.62081581354141f, 0.75000673532486f}, + {-0.43867015838623f, 0.99998068809509f}, + {0.98630565404892f, -0.53578901290894f}, + {-0.61510360240936f, -0.89515018463135f}, + {-0.03841517493129f, -0.69888818264008f}, + {-0.30102157592773f, -0.07667808979750f}, + {0.41881284117699f, 0.02188098989427f}, + {-0.86135452985764f, 0.98947483301163f}, + {0.67226862907410f, -0.13494388759136f}, + {-0.70737397670746f, -0.76547348499298f}, + {0.94044947624207f, 0.09026201069355f}, + {-0.82386350631714f, 0.08924768865108f}, + {-0.32070666551590f, 0.50143420696259f}, + {0.57593160867691f, -0.98966425657272f}, + {-0.36326017975807f, 0.07440242916346f}, + {0.99979043006897f, -0.14130286872387f}, + {-0.92366021871567f, -0.97979295253754f}, + {-0.44607177376747f, -0.54233253002167f}, + {0.44226801395416f, 0.71326756477356f}, + {0.03671907261014f, 0.63606387376785f}, + {0.52175426483154f, -0.85396826267242f}, + {-0.94701141119003f, -0.01826348155737f}, + {-0.98759609460831f, 0.82288712263107f}, + {0.87434792518616f, 0.89399492740631f}, + {-0.93412041664124f, 0.41374051570892f}, + {0.96063941717148f, 0.93116706609726f}, + {0.97534251213074f, 0.86150932312012f}, + {0.99642467498779f, 0.70190042257309f}, + {-0.94705086946487f, -0.29580041766167f}, + {0.91599804162979f, -0.98147833347321f} + }; + } +} diff --git a/SharpJaad.AAC/Sbr/SBR.cs b/SharpJaad.AAC/Sbr/SBR.cs new file mode 100644 index 0000000..009dd34 --- /dev/null +++ b/SharpJaad.AAC/Sbr/SBR.cs @@ -0,0 +1,1477 @@ +using SharpJaad.AAC.Ps; +using SharpJaad.AAC.Syntax; +using System; + +namespace SharpJaad.AAC.Sbr +{ + public class SBR + { + private bool _downSampledSBR; + public SampleFrequency _sampleRate; + public int _maxAACLine; + public int _rate; + public bool _justSeeked; + public int _ret; + + public bool[] _ampRes = new bool[2]; + + public int _k0; + public int _kx; + public int _M; + public int _N_master; + public int _N_high; + public int _N_low; + public int _N_Q; + public int[] _N_L = new int[4]; + public int[] _n = new int[2]; + + public int[] _f_master = new int[64]; + public int[,] _f_table_res = new int[2, 64]; + public int[] _f_table_noise = new int[64]; + public int[,] _f_table_lim = new int[4, 64]; + + public int[] _table_map_k_to_g = new int[64]; + + public int[] _abs_bord_lead = new int[2]; + public int[] _abs_bord_trail = new int[2]; + public int[] _n_rel_lead = new int[2]; + public int[] _n_rel_trail = new int[2]; + + public int[] _L_E = new int[2]; + public int[] _L_E_prev = new int[2]; + public int[] _L_Q = new int[2]; + + public int[,] _t_E = new int[2, Constants.MAX_L_E + 1]; + public int[,] _t_Q = new int[2, 3]; + public int[,] _f = new int[2, Constants.MAX_L_E + 1]; + public int[] _f_prev = new int[2]; + + public float[,,] _G_temp_prev = new float[2, 5, 64]; + public float[,,] _Q_temp_prev = new float[2, 5, 64]; + public int[] _GQ_ringbuf_index = new int[2]; + + public int[,,] _E = new int[2, 64, Constants.MAX_L_E]; + public int[,] _E_prev = new int[2, 64]; + public float[,,] _E_orig = new float[2, 64, Constants.MAX_L_E]; + public float[,,] _E_curr = new float[2, 64, Constants.MAX_L_E]; + public int[,,] _Q = new int[2, 64, 2]; + public float[,,] _Q_div = new float[2, 64, 2]; + public float[,,] _Q_div2 = new float[2, 64, 2]; + public int[,] _Q_prev = new int[2, 64]; + + public int[] _l_A = new int[2]; + public int[] _l_A_prev = new int[2]; + + public int[,] _bs_invf_mode = new int[2, Constants.MAX_L_E]; + public int[,] _bs_invf_mode_prev = new int[2, Constants.MAX_L_E]; + public float[,] _bwArray = new float[2, 64]; + public float[,] _bwArray_prev = new float[2, 64]; + + public int _noPatches; + public int[] _patchNoSubbands = new int[64]; + public int[] _patchStartSubband = new int[64]; + + public int[,] _bs_add_harmonic = new int[2, 64]; + public int[,] _bs_add_harmonic_prev = new int[2, 64]; + + public int[] _index_noise_prev = new int[2]; + public int[] _psi_is_prev = new int[2]; + + public int _bs_start_freq_prev; + public int _bs_stop_freq_prev; + public int _bs_xover_band_prev; + public int _bs_freq_scale_prev; + public bool _bs_alter_scale_prev; + public int _bs_noise_bands_prev; + + public int[] _prevEnvIsShort = new int[2]; + + public int _kx_prev; + public int _bsco; + public int _bsco_prev; + public int _M_prev; + + public bool _Reset; + public int _frame; + public int _header_count; + + public bool _stereo; + public AnalysisFilterbank[] _qmfa = new AnalysisFilterbank[2]; + public SynthesisFilterbank[] _qmfs = new SynthesisFilterbank[2]; + + public float[,,,] _Xsbr = new float[2, Constants.MAX_NTSRHFG, 64, 2]; + + public int _numTimeSlotsRate; + public int _numTimeSlots; + public int _tHFGen; + public int _tHFAdj; + + public PS _ps; + public bool _ps_used; + public bool _psResetFlag; + + /* to get it compiling */ + /* we'll see during the coding of all the tools, whether + these are all used or not. + */ + public bool _bs_header_flag; + public int _bs_crc_flag; + public int _bs_sbr_crc_bits; + public int _bs_protocol_version; + public bool _bs_amp_res; + public int _bs_start_freq; + public int _bs_stop_freq; + public int _bs_xover_band; + public int _bs_freq_scale; + public bool _bs_alter_scale; + public int _bs_noise_bands; + public int _bs_limiter_bands; + public int _bs_limiter_gains; + public bool _bs_interpol_freq; + public bool _bs_smoothing_mode; + public int _bs_samplerate_mode; + public bool[] _bs_add_harmonic_flag = new bool[2]; + public bool[] _bs_add_harmonic_flag_prev = new bool[2]; + public bool _bs_extended_data; + public int _bs_extension_id; + public int _bs_extension_data; + public bool _bs_coupling; + public int[] _bs_frame_class = new int[2]; + public int[,] _bs_rel_bord = new int[2, 9]; + public int[,] _bs_rel_bord_0 = new int[2, 9]; + public int[,] _bs_rel_bord_1 = new int[2, 9]; + public int[] _bs_pointer = new int[2]; + public int[] _bs_abs_bord_0 = new int[2]; + public int[] _bs_abs_bord_1 = new int[2]; + public int[] _bs_num_rel_0 = new int[2]; + public int[] _bs_num_rel_1 = new int[2]; + public int[,] _bs_df_env = new int[2, 9]; + public int[,] _bs_df_noise = new int[2, 3]; + + public SBR(bool smallFrames, bool stereo, SampleFrequency sample_rate, bool downSampledSBR) + { + _downSampledSBR = downSampledSBR; + _stereo = stereo; + _sampleRate = sample_rate; + + _bs_freq_scale = 2; + _bs_alter_scale = true; + _bs_noise_bands = 2; + _bs_limiter_bands = 2; + _bs_limiter_gains = 2; + _bs_interpol_freq = true; + _bs_smoothing_mode = true; + _bs_start_freq = 5; + _bs_amp_res = true; + _bs_samplerate_mode = 1; + _prevEnvIsShort[0] = -1; + _prevEnvIsShort[1] = -1; + _header_count = 0; + _Reset = true; + + _tHFGen = Constants.T_HFGEN; + _tHFAdj = Constants.T_HFADJ; + + _bsco = 0; + _bsco_prev = 0; + _M_prev = 0; + + /* force sbr reset */ + _bs_start_freq_prev = -1; + + if (smallFrames) + { + _numTimeSlotsRate = Constants.RATE * Constants.NO_TIME_SLOTS_960; + _numTimeSlots = Constants.NO_TIME_SLOTS_960; + } + else + { + _numTimeSlotsRate = Constants.RATE * Constants.NO_TIME_SLOTS; + _numTimeSlots = Constants.NO_TIME_SLOTS; + } + + _GQ_ringbuf_index[0] = 0; + _GQ_ringbuf_index[1] = 0; + + if (stereo) + { + /* stereo */ + _qmfa[0] = new AnalysisFilterbank(32); + _qmfa[1] = new AnalysisFilterbank(32); + _qmfs[0] = new SynthesisFilterbank(downSampledSBR ? 32 : 64); + _qmfs[1] = new SynthesisFilterbank(downSampledSBR ? 32 : 64); + } + else + { + /* mono */ + _qmfa[0] = new AnalysisFilterbank(32); + _qmfs[0] = new SynthesisFilterbank(downSampledSBR ? 32 : 64); + _qmfs[1] = null; + } + } + + private void SbrReset() + { + int j = 5; + if (_qmfa[0] != null) _qmfa[0].Reset(); + if (_qmfa[1] != null) _qmfa[1].Reset(); + if (_qmfs[0] != null) _qmfs[0].Reset(); + if (_qmfs[1] != null) _qmfs[1].Reset(); + + Array.Clear(_G_temp_prev, 0, _G_temp_prev.Length); + Array.Clear(_Q_temp_prev, 0, _Q_temp_prev.Length); + + for (int i = 0; i < 40; i++) + { + for (int k = 0; k < 64; k++) + { + _Xsbr[0, i, j, 0] = 0; + _Xsbr[0, i, j, 1] = 0; + _Xsbr[1, i, j, 0] = 0; + _Xsbr[1, i, j, 1] = 0; + } + } + + _GQ_ringbuf_index[0] = 0; + _GQ_ringbuf_index[1] = 0; + _header_count = 0; + _Reset = true; + + _L_E_prev[0] = 0; + _L_E_prev[1] = 0; + _bs_freq_scale = 2; + _bs_alter_scale = true; + _bs_noise_bands = 2; + _bs_limiter_bands = 2; + _bs_limiter_gains = 2; + _bs_interpol_freq = true; + _bs_smoothing_mode = true; + _bs_start_freq = 5; + _bs_amp_res = true; + _bs_samplerate_mode = 1; + _prevEnvIsShort[0] = -1; + _prevEnvIsShort[1] = -1; + _bsco = 0; + _bsco_prev = 0; + _M_prev = 0; + _bs_start_freq_prev = -1; + + _f_prev[0] = 0; + _f_prev[1] = 0; + for (j = 0; j < Constants.MAX_M; j++) + { + _E_prev[0, j] = 0; + _Q_prev[0, j] = 0; + _E_prev[1, j] = 0; + _Q_prev[1, j] = 0; + _bs_add_harmonic_prev[0, j] = 0; + _bs_add_harmonic_prev[1, j] = 0; + } + _bs_add_harmonic_flag_prev[0] = false; + _bs_add_harmonic_flag_prev[1] = false; + } + + private void SbrResetInternal() + { + + /* if these are different from the previous frame: Reset = 1 */ + if (_bs_start_freq != _bs_start_freq_prev + || _bs_stop_freq != _bs_stop_freq_prev + || _bs_freq_scale != _bs_freq_scale_prev + || _bs_alter_scale != _bs_alter_scale_prev + || _bs_xover_band != _bs_xover_band_prev + || _bs_noise_bands != _bs_noise_bands_prev) + { + _Reset = true; + } + else + { + _Reset = false; + } + + _bs_start_freq_prev = _bs_start_freq; + _bs_stop_freq_prev = _bs_stop_freq; + _bs_freq_scale_prev = _bs_freq_scale; + _bs_alter_scale_prev = _bs_alter_scale; + _bs_xover_band_prev = _bs_xover_band; + _bs_noise_bands_prev = _bs_noise_bands; + } + + private int CalcSbrTables(int start_freq, int stop_freq, + int samplerate_mode, int freq_scale, + bool alter_scale, int xover_band) + { + int result = 0; + int k2; + + /* calculate the Master Frequency Table */ + _k0 = FBT.QmfStartChannel(start_freq, samplerate_mode, _sampleRate); + k2 = FBT.QmfStopChannel(stop_freq, _sampleRate, _k0); + + /* check k0 and k2 */ + if (_sampleRate.GetFrequency() >= 48000) + { + if (k2 - _k0 > 32) + result += 1; + } + else if (_sampleRate.GetFrequency() <= 32000) + { + if (k2 - _k0 > 48) + result += 1; + } + else + { /* (sbr.sample_rate == 44100) */ + + if (k2 - _k0 > 45) + result += 1; + } + + if (freq_scale == 0) + { + result += FBT.MasterFrequencyTableFs0(this, _k0, k2, alter_scale); + } + else + { + result += FBT.MasterFrequencyTable(this, _k0, k2, freq_scale, alter_scale); + } + result += FBT.DerivedFrequencyTable(this, xover_band, k2); + + result = result > 0 ? 1 : 0; + + return result; + } + + /* table 2 */ + public int Decode(BitStream ld, int bits, bool crc) + { + int result = 0; + int num_align_bits = 0; + long num_sbr_bits1 = ld.GetPosition(); + int num_sbr_bits2; + + int saved_start_freq, saved_samplerate_mode; + int saved_stop_freq, saved_freq_scale; + int saved_xover_band; + bool saved_alter_scale; + + if (crc) + { + _bs_sbr_crc_bits = ld.ReadBits(10); + } + + /* save old header values, in case the new ones are corrupted */ + saved_start_freq = _bs_start_freq; + saved_samplerate_mode = _bs_samplerate_mode; + saved_stop_freq = _bs_stop_freq; + saved_freq_scale = _bs_freq_scale; + saved_alter_scale = _bs_alter_scale; + saved_xover_band = _bs_xover_band; + + _bs_header_flag = ld.ReadBool(); + + if (_bs_header_flag) + SbrHeader(ld); + + /* Reset? */ + SbrResetInternal(); + + /* first frame should have a header */ + //if (!(sbr.frame == 0 && sbr.bs_header_flag == 0)) + if (_header_count != 0) + { + if (_Reset || _bs_header_flag && _justSeeked) + { + int rt = CalcSbrTables(_bs_start_freq, _bs_stop_freq, + _bs_samplerate_mode, _bs_freq_scale, + _bs_alter_scale, _bs_xover_band); + + /* if an error occured with the new header values revert to the old ones */ + if (rt > 0) + { + CalcSbrTables(saved_start_freq, saved_stop_freq, + saved_samplerate_mode, saved_freq_scale, + saved_alter_scale, saved_xover_band); + } + } + + if (result == 0) + { + result = SbrData(ld); + + /* sbr_data() returning an error means that there was an error in + envelope_time_border_vector(). + In this case the old time border vector is saved and all the previous + data normally read after sbr_grid() is saved. + */ + /* to be on the safe side, calculate old sbr tables in case of error */ + if (result > 0 + && (_Reset || _bs_header_flag && _justSeeked)) + { + CalcSbrTables(saved_start_freq, saved_stop_freq, + saved_samplerate_mode, saved_freq_scale, + saved_alter_scale, saved_xover_band); + } + + /* we should be able to safely set result to 0 now, */ + /* but practise indicates this doesn't work well */ + } + } + else + { + result = 1; + } + + num_sbr_bits2 = (int)(ld.GetPosition() - num_sbr_bits1); + + /* check if we read more bits then were available for sbr */ + if (bits < num_sbr_bits2) + { + throw new AACException("frame overread"); + //faad_resetbits(ld, num_sbr_bits1+8*cnt); + //num_sbr_bits2 = 8*cnt; + + /* turn off PS for the unfortunate case that we randomly read some + * PS data that looks correct */ + //this.ps_used = 0; + + /* Make sure it doesn't decode SBR in this frame, or we'll get glitches */ + //return 1; + } + + + { + /* -4 does not apply, bs_extension_type is re-read in this function */ + num_align_bits = bits /*- 4*/- num_sbr_bits2; + ld.SkipBits(num_align_bits); + } + + return result; + } + + /* table 3 */ + private void SbrHeader(BitStream ld) + { + bool bs_header_extra_1, bs_header_extra_2; + + _header_count++; + + _bs_amp_res = ld.ReadBool(); + + /* bs_start_freq and bs_stop_freq must define a fequency band that does + not exceed 48 channels */ + _bs_start_freq = ld.ReadBits(4); + _bs_stop_freq = ld.ReadBits(4); + _bs_xover_band = ld.ReadBits(3); + ld.ReadBits(2); //reserved + bs_header_extra_1 = ld.ReadBool(); + bs_header_extra_2 = ld.ReadBool(); + + if (bs_header_extra_1) + { + _bs_freq_scale = ld.ReadBits(2); + _bs_alter_scale = ld.ReadBool(); + _bs_noise_bands = ld.ReadBits(2); + } + else + { + /* Default values */ + _bs_freq_scale = 2; + _bs_alter_scale = true; + _bs_noise_bands = 2; + } + + if (bs_header_extra_2) + { + _bs_limiter_bands = ld.ReadBits(2); + _bs_limiter_gains = ld.ReadBits(2); + _bs_interpol_freq = ld.ReadBool(); + _bs_smoothing_mode = ld.ReadBool(); + } + else + { + /* Default values */ + _bs_limiter_bands = 2; + _bs_limiter_gains = 2; + _bs_interpol_freq = true; + _bs_smoothing_mode = true; + } + + } + + /* table 4 */ + private int SbrData(BitStream ld) + { + int result; + + _rate = _bs_samplerate_mode != 0 ? 2 : 1; + + if (_stereo) + { + if ((result = SbrChannelPairElement(ld)) > 0) + return result; + } + else + { + if ((result = SbrSingleChannelElement(ld)) > 0) + return result; + } + + return 0; + } + + /* table 5 */ + private int SbrSingleChannelElement(BitStream ld) + { + int result; + + if (ld.ReadBool()) + { + ld.ReadBits(4); //reserved + } + + if ((result = SbrGrid(ld, 0)) > 0) + return result; + + SbrDtdf(ld, 0); + InvfMode(ld, 0); + SbrEnvelope(ld, 0); + SbrNoise(ld, 0); + + NoiseEnvelope.DequantChannel(this, 0); + + Array.Clear(_bs_add_harmonic, 0, _bs_add_harmonic.Length); + + _bs_add_harmonic_flag[0] = ld.ReadBool(); + if (_bs_add_harmonic_flag[0]) + SinusoidalCoding(ld, 0); + + _bs_extended_data = ld.ReadBool(); + + if (_bs_extended_data) + { + int nr_bits_left; + int ps_ext_read = 0; + int cnt = ld.ReadBits(4); + if (cnt == 15) + { + cnt += ld.ReadBits(8); + } + + nr_bits_left = 8 * cnt; + while (nr_bits_left > 7) + { + int tmp_nr_bits = 0; + + _bs_extension_id = ld.ReadBits(2); + tmp_nr_bits += 2; + + /* allow only 1 PS extension element per extension data */ + if (_bs_extension_id == Constants.EXTENSION_ID_PS) + { + if (ps_ext_read == 0) + { + ps_ext_read = 1; + } + else + { + /* to be safe make it 3, will switch to "default" + * in sbr_extension() */ + _bs_extension_id = 3; + } + } + + tmp_nr_bits += SbrExtension(ld, _bs_extension_id, nr_bits_left); + + /* check if the data read is bigger than the number of available bits */ + if (tmp_nr_bits > nr_bits_left) + return 1; + + nr_bits_left -= tmp_nr_bits; + } + + /* Corrigendum */ + if (nr_bits_left > 0) + { + ld.ReadBits(nr_bits_left); + } + } + + return 0; + } + + /* table 6 */ + private int SbrChannelPairElement(BitStream ld) + { + int n, result; + + if (ld.ReadBool()) + { + //reserved + ld.ReadBits(4); + ld.ReadBits(4); + } + + _bs_coupling = ld.ReadBool(); + + if (_bs_coupling) + { + if ((result = SbrGrid(ld, 0)) > 0) + return result; + + /* need to copy some data from left to right */ + _bs_frame_class[1] = _bs_frame_class[0]; + _L_E[1] = _L_E[0]; + _L_Q[1] = _L_Q[0]; + _bs_pointer[1] = _bs_pointer[0]; + + for (n = 0; n <= _L_E[0]; n++) + { + _t_E[1, n] = _t_E[0, n]; + _f[1, n] = _f[0, n]; + } + for (n = 0; n <= _L_Q[0]; n++) + { + _t_Q[1, n] = _t_Q[0, n]; + } + + SbrDtdf(ld, 0); + SbrDtdf(ld, 1); + InvfMode(ld, 0); + + /* more copying */ + for (n = 0; n < _N_Q; n++) + { + _bs_invf_mode[1, n] = _bs_invf_mode[0, n]; + } + + SbrEnvelope(ld, 0); + SbrNoise(ld, 0); + SbrEnvelope(ld, 1); + SbrNoise(ld, 1); + + Array.Clear(_bs_add_harmonic, 0, _bs_add_harmonic.Length); + + _bs_add_harmonic_flag[0] = ld.ReadBool(); + if (_bs_add_harmonic_flag[0]) + SinusoidalCoding(ld, 0); + + _bs_add_harmonic_flag[1] = ld.ReadBool(); + if (_bs_add_harmonic_flag[1]) + SinusoidalCoding(ld, 1); + } + else + { + int[] saved_t_E = new int[6], saved_t_Q = new int[3]; + int saved_L_E = _L_E[0]; + int saved_L_Q = _L_Q[0]; + int saved_frame_class = _bs_frame_class[0]; + + for (n = 0; n < saved_L_E; n++) + { + saved_t_E[n] = _t_E[0, n]; + } + for (n = 0; n < saved_L_Q; n++) + { + saved_t_Q[n] = _t_Q[0, n]; + } + + if ((result = SbrGrid(ld, 0)) > 0) + return result; + if ((result = SbrGrid(ld, 1)) > 0) + { + /* restore first channel data as well */ + _bs_frame_class[0] = saved_frame_class; + _L_E[0] = saved_L_E; + _L_Q[0] = saved_L_Q; + for (n = 0; n < 6; n++) + { + _t_E[0, n] = saved_t_E[n]; + } + for (n = 0; n < 3; n++) + { + _t_Q[0, n] = saved_t_Q[n]; + } + + return result; + } + SbrDtdf(ld, 0); + SbrDtdf(ld, 1); + InvfMode(ld, 0); + InvfMode(ld, 1); + SbrEnvelope(ld, 0); + SbrEnvelope(ld, 1); + SbrNoise(ld, 0); + SbrNoise(ld, 1); + + Array.Clear(_bs_add_harmonic, 0, _bs_add_harmonic.Length); + + _bs_add_harmonic_flag[0] = ld.ReadBool(); + if (_bs_add_harmonic_flag[0]) + SinusoidalCoding(ld, 0); + + _bs_add_harmonic_flag[1] = ld.ReadBool(); + if (_bs_add_harmonic_flag[1]) + SinusoidalCoding(ld, 1); + } + NoiseEnvelope.DequantChannel(this, 0); + NoiseEnvelope.DequantChannel(this, 1); + + if (_bs_coupling) + NoiseEnvelope.Unmap(this); + + _bs_extended_data = ld.ReadBool(); + if (_bs_extended_data) + { + int nr_bits_left; + int cnt = ld.ReadBits(4); + if (cnt == 15) + { + cnt += ld.ReadBits(8); + } + + nr_bits_left = 8 * cnt; + while (nr_bits_left > 7) + { + int tmp_nr_bits = 0; + + _bs_extension_id = ld.ReadBits(2); + tmp_nr_bits += 2; + tmp_nr_bits += SbrExtension(ld, _bs_extension_id, nr_bits_left); + + /* check if the data read is bigger than the number of available bits */ + if (tmp_nr_bits > nr_bits_left) + return 1; + + nr_bits_left -= tmp_nr_bits; + } + + /* Corrigendum */ + if (nr_bits_left > 0) + { + ld.ReadBits(nr_bits_left); + } + } + + return 0; + } + + /* integer log[2](x): input range [0,10) */ + private int SbrLog2(int val) + { + int[] log2tab = new int[] { 0, 0, 1, 2, 2, 3, 3, 3, 3, 4 }; + if (val < 10 && val >= 0) + return log2tab[val]; + else + return 0; + } + + + /* table 7 */ + private int SbrGrid(BitStream ld, int ch) + { + int i, env, rel, result; + int bs_abs_bord, bs_abs_bord_1; + int bs_num_env = 0; + int saved_L_E = _L_E[ch]; + int saved_L_Q = _L_Q[ch]; + int saved_frame_class = _bs_frame_class[ch]; + + _bs_frame_class[ch] = ld.ReadBits(2); + + switch (_bs_frame_class[ch]) + { + case Constants.FIXFIX: + i = ld.ReadBits(2); + + bs_num_env = Math.Min(1 << i, 5); + + i = ld.ReadBit(); + for (env = 0; env < bs_num_env; env++) + { + _f[ch, env] = i; + } + + _abs_bord_lead[ch] = 0; + _abs_bord_trail[ch] = _numTimeSlots; + _n_rel_lead[ch] = bs_num_env - 1; + _n_rel_trail[ch] = 0; + break; + + case Constants.FIXVAR: + bs_abs_bord = ld.ReadBits(2) + _numTimeSlots; + bs_num_env = ld.ReadBits(2) + 1; + + for (rel = 0; rel < bs_num_env - 1; rel++) + { + _bs_rel_bord[ch, rel] = 2 * ld.ReadBits(2) + 2; + } + i = SbrLog2(bs_num_env + 1); + _bs_pointer[ch] = ld.ReadBits(i); + + for (env = 0; env < bs_num_env; env++) + { + _f[ch, bs_num_env - env - 1] = ld.ReadBit(); + } + + _abs_bord_lead[ch] = 0; + _abs_bord_trail[ch] = bs_abs_bord; + _n_rel_lead[ch] = 0; + _n_rel_trail[ch] = bs_num_env - 1; + break; + + case Constants.VARFIX: + bs_abs_bord = ld.ReadBits(2); + bs_num_env = ld.ReadBits(2) + 1; + + for (rel = 0; rel < bs_num_env - 1; rel++) + { + _bs_rel_bord[ch, rel] = 2 * ld.ReadBits(2) + 2; + } + i = SbrLog2(bs_num_env + 1); + _bs_pointer[ch] = ld.ReadBits(i); + + for (env = 0; env < bs_num_env; env++) + { + _f[ch, env] = ld.ReadBit(); + } + + _abs_bord_lead[ch] = bs_abs_bord; + _abs_bord_trail[ch] = _numTimeSlots; + _n_rel_lead[ch] = bs_num_env - 1; + _n_rel_trail[ch] = 0; + break; + + case Constants.VARVAR: + bs_abs_bord = ld.ReadBits(2); + bs_abs_bord_1 = ld.ReadBits(2) + _numTimeSlots; + _bs_num_rel_0[ch] = ld.ReadBits(2); + _bs_num_rel_1[ch] = ld.ReadBits(2); + + bs_num_env = Math.Min(5, _bs_num_rel_0[ch] + _bs_num_rel_1[ch] + 1); + + for (rel = 0; rel < _bs_num_rel_0[ch]; rel++) + { + _bs_rel_bord_0[ch, rel] = 2 * ld.ReadBits(2) + 2; + } + for (rel = 0; rel < _bs_num_rel_1[ch]; rel++) + { + _bs_rel_bord_1[ch, rel] = 2 * ld.ReadBits(2) + 2; + } + i = SbrLog2(_bs_num_rel_0[ch] + _bs_num_rel_1[ch] + 2); + _bs_pointer[ch] = ld.ReadBits(i); + + for (env = 0; env < bs_num_env; env++) + { + _f[ch, env] = ld.ReadBit(); + } + + _abs_bord_lead[ch] = bs_abs_bord; + _abs_bord_trail[ch] = bs_abs_bord_1; + _n_rel_lead[ch] = _bs_num_rel_0[ch]; + _n_rel_trail[ch] = _bs_num_rel_1[ch]; + break; + } + + if (_bs_frame_class[ch] == Constants.VARVAR) + _L_E[ch] = Math.Min(bs_num_env, 5); + else + _L_E[ch] = Math.Min(bs_num_env, 4); + + if (_L_E[ch] <= 0) + return 1; + + if (_L_E[ch] > 1) + _L_Q[ch] = 2; + else + _L_Q[ch] = 1; + + /* TODO: this code can probably be integrated into the code above! */ + if ((result = TFGrid.EnvelopeTimeBorderVector(this, ch)) > 0) + { + _bs_frame_class[ch] = saved_frame_class; + _L_E[ch] = saved_L_E; + _L_Q[ch] = saved_L_Q; + return result; + } + TFGrid.NoiseFloorTimeBorderVector(this, ch); + + return 0; + } + + /* table 8 */ + private void SbrDtdf(BitStream ld, int ch) + { + int i; + + for (i = 0; i < _L_E[ch]; i++) + { + _bs_df_env[ch, i] = ld.ReadBit(); + } + + for (i = 0; i < _L_Q[ch]; i++) + { + _bs_df_noise[ch, i] = ld.ReadBit(); + } + } + + /* table 9 */ + private void InvfMode(BitStream ld, int ch) + { + int n; + + for (n = 0; n < _N_Q; n++) + { + _bs_invf_mode[ch, n] = ld.ReadBits(2); + } + } + + private int SbrExtension(BitStream ld, int bs_extension_id, int num_bits_left) + { + int ret; + + switch (bs_extension_id) + { + case Constants.EXTENSION_ID_PS: + if (_ps == null) + { + _ps = new PS(_sampleRate, _numTimeSlotsRate); + } + if (_psResetFlag) + { + _ps._headerRead = false; + } + ret = _ps.Decode(ld); + + /* enable PS if and only if: a header has been decoded */ + if (!_ps_used && _ps._headerRead) + { + _ps_used = true; + } + + if (_ps._headerRead) + { + _psResetFlag = false; + } + + return ret; + default: + _bs_extension_data = ld.ReadBits(6); + return 6; + } + } + + /* table 12 */ + private void SinusoidalCoding(BitStream ld, int ch) + { + int n; + + for (n = 0; n < _N_high; n++) + { + _bs_add_harmonic[ch, n] = ld.ReadBit(); + } + } + /* table 10 */ + + private void SbrEnvelope(BitStream ld, int ch) + { + int env, band; + int delta = 0; + int[] + [] + t_huff, f_huff; + + if (_L_E[ch] == 1 && _bs_frame_class[ch] == Constants.FIXFIX) + _ampRes[ch] = false; + else + _ampRes[ch] = _bs_amp_res; + + if (_bs_coupling && ch == 1) + { + delta = 1; + if (_ampRes[ch]) + { + t_huff = HuffmanTables.T_HUFFMAN_ENV_BAL_3_0DB; + f_huff = HuffmanTables.F_HUFFMAN_ENV_BAL_3_0DB; + } + else + { + t_huff = HuffmanTables.T_HUFFMAN_ENV_BAL_1_5DB; + f_huff = HuffmanTables.F_HUFFMAN_ENV_BAL_1_5DB; + } + } + else + { + delta = 0; + if (_ampRes[ch]) + { + t_huff = HuffmanTables.T_HUFFMAN_ENV_3_0DB; + f_huff = HuffmanTables.F_HUFFMAN_ENV_3_0DB; + } + else + { + t_huff = HuffmanTables.T_HUFFMAN_ENV_1_5DB; + f_huff = HuffmanTables.F_HUFFMAN_ENV_1_5DB; + } + } + + for (env = 0; env < _L_E[ch]; env++) + { + if (_bs_df_env[ch, env] == 0) + { + if (_bs_coupling && ch == 1) + { + if (_ampRes[ch]) + { + _E[ch, 0, env] = ld.ReadBits(5) << delta; + } + else + { + _E[ch, 0, env] = ld.ReadBits(6) << delta; + } + } + else + { + if (_ampRes[ch]) + { + _E[ch, 0, env] = ld.ReadBits(6) << delta; + } + else + { + _E[ch, 0, env] = ld.ReadBits(7) << delta; + } + } + + for (band = 1; band < _n[_f[ch, env]]; band++) + { + _E[ch, band, env] = DecodeHuffman(ld, f_huff) << delta; + } + + } + else + { + for (band = 0; band < _n[_f[ch, env]]; band++) + { + _E[ch, band, env] = DecodeHuffman(ld, t_huff) << delta; + } + } + } + + NoiseEnvelope.ExtractEnvelopeData(this, ch); + } + + /* table 11 */ + private void SbrNoise(BitStream ld, int ch) + { + int noise, band; + int delta = 0; + int[] + [] + t_huff, f_huff; + + if (_bs_coupling && ch == 1) + { + delta = 1; + t_huff = HuffmanTables.T_HUFFMAN_NOISE_BAL_3_0DB; + f_huff = HuffmanTables.F_HUFFMAN_ENV_BAL_3_0DB; + } + else + { + delta = 0; + t_huff = HuffmanTables.T_HUFFMAN_NOISE_3_0DB; + f_huff = HuffmanTables.F_HUFFMAN_ENV_3_0DB; + } + + for (noise = 0; noise < _L_Q[ch]; noise++) + { + if (_bs_df_noise[ch, noise] == 0) + { + if (_bs_coupling && ch == 1) + { + _Q[ch, 0, noise] = ld.ReadBits(5) << delta; + } + else + { + _Q[ch, 0, noise] = ld.ReadBits(5) << delta; + } + for (band = 1; band < _N_Q; band++) + { + _Q[ch, band, noise] = DecodeHuffman(ld, f_huff) << delta; + } + } + else + { + for (band = 0; band < _N_Q; band++) + { + _Q[ch, band, noise] = DecodeHuffman(ld, t_huff) << delta; + } + } + } + + NoiseEnvelope.ExtractNoiseFloorData(this, ch); + } + + private int DecodeHuffman(BitStream ld, int[][] t_huff) + { + int bit; + int index = 0; + + while (index >= 0) + { + bit = ld.ReadBit(); + index = t_huff[index][bit]; + } + + return index + 64; + } + + private int SbrSavePrevData(int ch) + { + int i; + + /* save data for next frame */ + _kx_prev = _kx; + _M_prev = _M; + _bsco_prev = _bsco; + + _L_E_prev[ch] = _L_E[ch]; + + /* sbr.L_E[ch] can become 0 on files with bit errors */ + if (_L_E[ch] <= 0) + return 19; + + _f_prev[ch] = _f[ch, _L_E[ch] - 1]; + for (i = 0; i < Constants.MAX_M; i++) + { + _E_prev[ch, i] = _E[ch, i, _L_E[ch] - 1]; + _Q_prev[ch, i] = _Q[ch, i, _L_Q[ch] - 1]; + } + + for (i = 0; i < Constants.MAX_M; i++) + { + _bs_add_harmonic_prev[ch, i] = _bs_add_harmonic[ch, i]; + } + _bs_add_harmonic_flag_prev[ch] = _bs_add_harmonic_flag[ch]; + + if (_l_A[ch] == _L_E[ch]) + _prevEnvIsShort[ch] = 0; + else + _prevEnvIsShort[ch] = -1; + + return 0; + } + + private void SbrSaveMatrix(int ch) + { + int i; + + for (i = 0; i < _tHFGen; i++) + { + for (int j = 0; j < 64; j++) + { + _Xsbr[ch, i, j, 0] = _Xsbr[ch, i + _numTimeSlotsRate, j, 0]; + _Xsbr[ch, i, j, 1] = _Xsbr[ch, i + _numTimeSlotsRate, j, 1]; + } + } + for (i = _tHFGen; i < Constants.MAX_NTSRHFG; i++) + { + for (int j = 0; j < 64; j++) + { + _Xsbr[ch, i, j, 0] = 0; + _Xsbr[ch, i, j, 1] = 0; + } + } + } + + private int SbrProcessChannel(float[] channel_buf, float[,,] X, + int ch, bool dont_process) + { + int k, l; + int ret = 0; + + _bsco = 0; + + /* subband analysis */ + if (dont_process) + _qmfa[ch].SbrQmfAnalysis32(this, channel_buf, _Xsbr, ch, _tHFGen, 32); + else + _qmfa[ch].SbrQmfAnalysis32(this, channel_buf, _Xsbr, ch, _tHFGen, _kx); + + if (!dont_process) + { + /* insert high frequencies here */ + /* hf generation using patching */ + HFGeneration.HfGeneration(this, _Xsbr, _Xsbr, ch); + + + /* hf adjustment */ + ret = HFAdjustment.HfAdjustment(this, _Xsbr, ch); + if (ret > 0) + { + dont_process = true; + } + } + + if (_justSeeked || dont_process) + { + for (l = 0; l < _numTimeSlotsRate; l++) + { + for (k = 0; k < 32; k++) + { + X[l, k, 0] = _Xsbr[ch, l + _tHFAdj, k, 0]; + X[l, k, 1] = _Xsbr[ch, l + _tHFAdj, k, 1]; + } + for (k = 32; k < 64; k++) + { + X[l, k, 0] = 0; + X[l, k, 1] = 0; + } + } + } + else + { + for (l = 0; l < _numTimeSlotsRate; l++) + { + int kx_band, M_band, bsco_band; + + if (l < _t_E[ch, 0]) + { + kx_band = _kx_prev; + M_band = _M_prev; + bsco_band = _bsco_prev; + } + else + { + kx_band = _kx; + M_band = _M; + bsco_band = _bsco; + } + + for (k = 0; k < kx_band + bsco_band; k++) + { + X[l, k, 0] = _Xsbr[ch, l + _tHFAdj, k, 0]; + X[l, k, 1] = _Xsbr[ch, l + _tHFAdj, k, 1]; + } + for (k = kx_band + bsco_band; k < kx_band + M_band; k++) + { + X[l, k, 0] = _Xsbr[ch, l + _tHFAdj, k, 0]; + X[l, k, 1] = _Xsbr[ch, l + _tHFAdj, k, 1]; + } + for (k = Math.Max(kx_band + bsco_band, kx_band + M_band); k < 64; k++) + { + X[l, k, 0] = 0; + X[l, k, 1] = 0; + } + } + } + return ret; + } + + public int Process(float[] left_chan, float[] right_chan, + bool just_seeked) + { + bool dont_process = false; + int ret = 0; + float[,,] X = new float[Constants.MAX_NTSR, 64, 2]; + + /* case can occur due to bit errors */ + if (!_stereo) return 21; + + if (_ret != 0 || _header_count == 0) + { + /* don't process just upsample */ + dont_process = true; + + /* Re-activate reset for next frame */ + if (_ret != 0 && _Reset) + _bs_start_freq_prev = -1; + } + + if (just_seeked) + { + _justSeeked = true; + } + else + { + _justSeeked = false; + } + + _ret += SbrProcessChannel(left_chan, X, 0, dont_process); + /* subband synthesis */ + if (_downSampledSBR) + { + _qmfs[0].SbrQmfSynthesis32(this, X, left_chan); + } + else + { + _qmfs[0].SbrQmfSynthesis64(this, X, left_chan); + } + + _ret += SbrProcessChannel(right_chan, X, 1, dont_process); + /* subband synthesis */ + if (_downSampledSBR) + { + _qmfs[1].SbrQmfSynthesis32(this, X, right_chan); + } + else + { + _qmfs[1].SbrQmfSynthesis64(this, X, right_chan); + } + + if (_bs_header_flag) + _justSeeked = false; + + if (_header_count != 0 && _ret == 0) + { + ret = SbrSavePrevData(0); + if (ret != 0) return ret; + ret = SbrSavePrevData(1); + if (ret != 0) return ret; + } + + SbrSaveMatrix(0); + SbrSaveMatrix(1); + _frame++; + + return 0; + } + + public int Process(float[] channel, + bool just_seeked) + { + bool dont_process = false; + int ret = 0; + float[,,] X = new float[Constants.MAX_NTSR, 64, 2]; + + /* case can occur due to bit errors */ + if (_stereo) return 21; + + if (_ret != 0 || _header_count == 0) + { + /* don't process just upsample */ + dont_process = true; + + /* Re-activate reset for next frame */ + if (_ret != 0 && _Reset) + _bs_start_freq_prev = -1; + } + + if (just_seeked) + { + _justSeeked = true; + } + else + { + _justSeeked = false; + } + + _ret += SbrProcessChannel(channel, X, 0, dont_process); + /* subband synthesis */ + if (_downSampledSBR) + { + _qmfs[0].SbrQmfSynthesis32(this, X, channel); + } + else + { + _qmfs[0].SbrQmfSynthesis64(this, X, channel); + } + + if (_bs_header_flag) + _justSeeked = false; + + if (_header_count != 0 && _ret == 0) + { + ret = SbrSavePrevData(0); + if (ret != 0) return ret; + } + + SbrSaveMatrix(0); + + _frame++; + + return 0; + } + + public int ProcessPS(float[] left_channel, float[] right_channel, + bool just_seeked) + { + int l, k; + bool dont_process = false; + int ret = 0; + float[,,] X_left = new float[38, 64, 2]; + float[,,] X_right = new float[38, 64, 2]; + + /* case can occur due to bit errors */ + if (_stereo) return 21; + + if (_ret != 0 || _header_count == 0) + { + /* don't process just upsample */ + dont_process = true; + + /* Re-activate reset for next frame */ + if (_ret != 0 && _Reset) + _bs_start_freq_prev = -1; + } + + if (just_seeked) + { + _justSeeked = true; + } + else + { + _justSeeked = false; + } + + if (_qmfs[1] == null) + { + _qmfs[1] = new SynthesisFilterbank(_downSampledSBR ? 32 : 64); + } + + _ret += SbrProcessChannel(left_channel, X_left, 0, dont_process); + + /* copy some extra data for PS */ + for (l = _numTimeSlotsRate; l < _numTimeSlotsRate + 6; l++) + { + for (k = 0; k < 5; k++) + { + X_left[l, k, 0] = _Xsbr[0, _tHFAdj + l, k, 0]; + X_left[l, k, 1] = _Xsbr[0, _tHFAdj + l, k, 1]; + } + } + + /* perform parametric stereo */ + _ps.Process(X_left, X_right); + + /* subband synthesis */ + if (_downSampledSBR) + { + _qmfs[0].SbrQmfSynthesis32(this, X_left, left_channel); + _qmfs[1].SbrQmfSynthesis32(this, X_right, right_channel); + } + else + { + _qmfs[0].SbrQmfSynthesis64(this, X_left, left_channel); + _qmfs[1].SbrQmfSynthesis64(this, X_right, right_channel); + } + + if (_bs_header_flag) + _justSeeked = false; + + if (_header_count != 0 && _ret == 0) + { + ret = SbrSavePrevData(0); + if (ret != 0) return ret; + } + SbrSaveMatrix(0); + + _frame++; + + return 0; + } + + public bool IsPSUsed() + { + return _ps_used; + } + } +} diff --git a/SharpJaad.AAC/Sbr/SynthesisFilterbank.cs b/SharpJaad.AAC/Sbr/SynthesisFilterbank.cs new file mode 100644 index 0000000..eb5c2e3 --- /dev/null +++ b/SharpJaad.AAC/Sbr/SynthesisFilterbank.cs @@ -0,0 +1,1044 @@ +using SharpJaad.AAC.Tools; + +namespace SharpJaad.AAC.Sbr +{ + public class SynthesisFilterbank + { + private static float[,] qmf32_pre_twiddle = { + {0.999924701839145f, -0.012271538285720f}, + {0.999322384588350f, -0.036807222941359f}, + {0.998118112900149f, -0.061320736302209f}, + {0.996312612182778f, -0.085797312344440f}, + {0.993906970002356f, -0.110222207293883f}, + {0.990902635427780f, -0.134580708507126f}, + {0.987301418157858f, -0.158858143333861f}, + {0.983105487431216f, -0.183039887955141f}, + {0.978317370719628f, -0.207111376192219f}, + {0.972939952205560f, -0.231058108280671f}, + {0.966976471044852f, -0.254865659604515f}, + {0.960430519415566f, -0.278519689385053f}, + {0.953306040354194f, -0.302005949319228f}, + {0.945607325380521f, -0.325310292162263f}, + {0.937339011912575f, -0.348418680249435f}, + {0.928506080473216f, -0.371317193951838f}, + {0.919113851690058f, -0.393992040061048f}, + {0.909167983090522f, -0.416429560097637f}, + {0.898674465693954f, -0.438616238538528f}, + {0.887639620402854f, -0.460538710958240f}, + {0.876070094195407f, -0.482183772079123f}, + {0.863972856121587f, -0.503538383725718f}, + {0.851355193105265f, -0.524589682678469f}, + {0.838224705554838f, -0.545324988422046f}, + {0.824589302785025f, -0.565731810783613f}, + {0.810457198252595f, -0.585797857456439f}, + {0.795836904608884f, -0.605511041404326f}, + {0.780737228572094f, -0.624859488142386f}, + {0.765167265622459f, -0.643831542889791f}, + {0.749136394523459f, -0.662415777590172f}, + {0.732654271672413f, -0.680600997795453f}, + {0.715730825283819f, -0.698376249408973f} + }; + + private float[] _v; //double ringbuffer + private int _v_index; //ringbuffer index + private int _channels; + + public SynthesisFilterbank(int channels) + { + _channels = channels; + _v = new float[2 * channels * 20]; + _v_index = 0; + } + + public void Reset() + { + Arrays.Fill(_v, 0); + } + + public void SbrQmfSynthesis32(SBR sbr, float[,,] X, + float[] output) + { + float[] x1 = new float[32], x2 = new float[32]; + float scale = 1.0f / 64.0f; + int n, k, outt = 0; + int l; + + + /* qmf subsample l */ + for (l = 0; l < sbr._numTimeSlotsRate; l++) + { + /* shift buffer v */ + /* buffer is not shifted, we are using a ringbuffer */ + //memmove(qmfs.v + 64, qmfs.v, (640-64)*sizeof(real_t)); + + /* calculate 64 samples */ + /* complex pre-twiddle */ + for (k = 0; k < 32; k++) + { + x1[k] = X[l, k, 0] * qmf32_pre_twiddle[k, 0] - X[l, k, 1] * qmf32_pre_twiddle[k, 1]; + x2[k] = X[l, k, 1] * qmf32_pre_twiddle[k, 0] + X[l, k, 0] * qmf32_pre_twiddle[k, 1]; + + x1[k] *= scale; + x2[k] *= scale; + } + + /* transform */ + DCT4_32(x1, x1); + DST4_32(x2, x2); + + for (n = 0; n < 32; n++) + { + _v[_v_index + n] = _v[_v_index + 640 + n] = -x1[n] + x2[n]; + _v[_v_index + 63 - n] = _v[_v_index + 640 + 63 - n] = x1[n] + x2[n]; + } + + /* calculate 32 output samples and window */ + for (k = 0; k < 32; k++) + { + output[outt++] = _v[_v_index + k] * FilterbankTable.qmf_c[2 * k] + + _v[_v_index + 96 + k] * FilterbankTable.qmf_c[64 + 2 * k] + + _v[_v_index + 128 + k] * FilterbankTable.qmf_c[128 + 2 * k] + + _v[_v_index + 224 + k] * FilterbankTable.qmf_c[192 + 2 * k] + + _v[_v_index + 256 + k] * FilterbankTable.qmf_c[256 + 2 * k] + + _v[_v_index + 352 + k] * FilterbankTable.qmf_c[320 + 2 * k] + + _v[_v_index + 384 + k] * FilterbankTable.qmf_c[384 + 2 * k] + + _v[_v_index + 480 + k] * FilterbankTable.qmf_c[448 + 2 * k] + + _v[_v_index + 512 + k] * FilterbankTable.qmf_c[512 + 2 * k] + + _v[_v_index + 608 + k] * FilterbankTable.qmf_c[576 + 2 * k]; + } + + /* update ringbuffer index */ + _v_index -= 64; + if (_v_index < 0) + _v_index = 640 - 64; + } + } + + public void SbrQmfSynthesis64(SBR sbr, float[,,] X, + float[] output) + { + float[] in_real1 = new float[32], in_imag1 = new float[32], out_real1 = new float[32], out_imag1 = new float[32]; + float[] in_real2 = new float[32], in_imag2 = new float[32], out_real2 = new float[32], out_imag2 = new float[32]; + float scale = 1.0f / 64.0f; + int n, k, outt = 0; + int l; + + + /* qmf subsample l */ + for (l = 0; l < sbr._numTimeSlotsRate; l++) + { + /* shift buffer v */ + /* buffer is not shifted, we use double ringbuffer */ + //memmove(qmfs.v + 128, qmfs.v, (1280-128)*sizeof(real_t)); + + /* calculate 128 samples */ + + in_imag1[31] = scale * X[l, 1, 0]; + in_real1[0] = scale * X[l, 0, 0]; + in_imag2[31] = scale * X[l, 63 - 1, 1]; + in_real2[0] = scale * X[l, 63 - 0, 1]; + for (k = 1; k < 31; k++) + { + in_imag1[31 - k] = scale * X[l, 2 * k + 1, 0]; + in_real1[k] = scale * X[l, 2 * k, 0]; + in_imag2[31 - k] = scale * X[l, 63 - (2 * k + 1), 1]; + in_real2[k] = scale * X[l, 63 - 2 * k, 1]; + } + in_imag1[0] = scale * X[l, 63, 0]; + in_real1[31] = scale * X[l, 62, 0]; + in_imag2[0] = scale * X[l, 63 - 63, 1]; + in_real2[31] = scale * X[l, 63 - 62, 1]; + + // dct4_kernel is DCT_IV without reordering which is done before and after FFT + DCT.Dct4Kernel(in_real1, in_imag1, out_real1, out_imag1); + DCT.Dct4Kernel(in_real2, in_imag2, out_real2, out_imag2); + + int pring_buffer_1 = _v_index; //*v + int pring_buffer_3 = pring_buffer_1 + 1280; + // ptemp_1 = x1; + // ptemp_2 = x2; + + for (n = 0; n < 32; n++) + { + // pring_buffer_3 and pring_buffer_4 are needed only for double ring buffer + _v[pring_buffer_1 + 2 * n] = _v[pring_buffer_3 + 2 * n] = out_real2[n] - out_real1[n]; + _v[pring_buffer_1 + 127 - 2 * n] = _v[pring_buffer_3 + 127 - 2 * n] = out_real2[n] + out_real1[n]; + _v[pring_buffer_1 + 2 * n + 1] = _v[pring_buffer_3 + 2 * n + 1] = out_imag2[31 - n] + out_imag1[31 - n]; + _v[pring_buffer_1 + 127 - (2 * n + 1)] = _v[pring_buffer_3 + 127 - (2 * n + 1)] = out_imag2[31 - n] - out_imag1[31 - n]; + } + + pring_buffer_1 = _v_index; //*v + + /* calculate 64 output samples and window */ + for (k = 0; k < 64; k++) + { + output[outt++] + = _v[pring_buffer_1 + k + 0] * FilterbankTable.qmf_c[k + 0] + + _v[pring_buffer_1 + k + 192] * FilterbankTable.qmf_c[k + 64] + + _v[pring_buffer_1 + k + 256] * FilterbankTable.qmf_c[k + 128] + + _v[pring_buffer_1 + k + 256 + 192] * FilterbankTable.qmf_c[k + 192] + + _v[pring_buffer_1 + k + 512] * FilterbankTable.qmf_c[k + 256] + + _v[pring_buffer_1 + k + 512 + 192] * FilterbankTable.qmf_c[k + 320] + + _v[pring_buffer_1 + k + 768] * FilterbankTable.qmf_c[k + 384] + + _v[pring_buffer_1 + k + 768 + 192] * FilterbankTable.qmf_c[k + 448] + + _v[pring_buffer_1 + k + 1024] * FilterbankTable.qmf_c[k + 512] + + _v[pring_buffer_1 + k + 1024 + 192] * FilterbankTable.qmf_c[k + 576]; + } + + /* update ringbuffer index */ + _v_index -= 128; + if (_v_index < 0) + _v_index = 1280 - 128; + } + } + + private void DCT4_32(float[] y, float[] x) + { + float f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10; + float f11, f12, f13, f14, f15, f16, f17, f18, f19, f20; + float f21, f22, f23, f24, f25, f26, f27, f28, f29, f30; + float f31, f32, f33, f34, f35, f36, f37, f38, f39, f40; + float f41, f42, f43, f44, f45, f46, f47, f48, f49, f50; + float f51, f52, f53, f54, f55, f56, f57, f58, f59, f60; + float f61, f62, f63, f64, f65, f66, f67, f68, f69, f70; + float f71, f72, f73, f74, f75, f76, f77, f78, f79, f80; + float f81, f82, f83, f84, f85, f86, f87, f88, f89, f90; + float f91, f92, f93, f94, f95, f96, f97, f98, f99, f100; + float f101, f102, f103, f104, f105, f106, f107, f108, f109, f110; + float f111, f112, f113, f114, f115, f116, f117, f118, f119, f120; + float f121, f122, f123, f124, f125, f126, f127, f128, f129, f130; + float f131, f132, f133, f134, f135, f136, f137, f138, f139, f140; + float f141, f142, f143, f144, f145, f146, f147, f148, f149, f150; + float f151, f152, f153, f154, f155, f156, f157, f158, f159, f160; + float f161, f162, f163, f164, f165, f166, f167, f168, f169, f170; + float f171, f172, f173, f174, f175, f176, f177, f178, f179, f180; + float f181, f182, f183, f184, f185, f186, f187, f188, f189, f190; + float f191, f192, f193, f194, f195, f196, f197, f198, f199, f200; + float f201, f202, f203, f204, f205, f206, f207, f208, f209, f210; + float f211, f212, f213, f214, f215, f216, f217, f218, f219, f220; + float f221, f222, f223, f224, f225, f226, f227, f228, f229, f230; + float f231, f232, f233, f234, f235, f236, f237, f238, f239, f240; + float f241, f242, f243, f244, f245, f246, f247, f248, f249, f250; + float f251, f252, f253, f254, f255, f256, f257, f258, f259, f260; + float f261, f262, f263, f264, f265, f266, f267, f268, f269, f270; + float f271, f272, f273, f274, f275, f276, f277, f278, f279, f280; + float f281, f282, f283, f284, f285, f286, f287, f288, f289, f290; + float f291, f292, f293, f294, f295, f296, f297, f298, f299, f300; + float f301, f302, f303, f304, f305, f306, f307, f310, f311, f312; + float f313, f316, f317, f318, f319, f322, f323, f324, f325, f328; + float f329, f330, f331, f334, f335, f336, f337, f340, f341, f342; + float f343, f346, f347, f348, f349, f352, f353, f354, f355, f358; + float f359, f360, f361, f364, f365, f366, f367, f370, f371, f372; + float f373, f376, f377, f378, f379, f382, f383, f384, f385, f388; + float f389, f390, f391, f394, f395, f396, f397; + + f0 = x[15] - x[16]; + f1 = x[15] + x[16]; + f2 = 0.7071067811865476f * f1; + f3 = 0.7071067811865476f * f0; + f4 = x[8] - x[23]; + f5 = x[8] + x[23]; + f6 = 0.7071067811865476f * f5; + f7 = 0.7071067811865476f * f4; + f8 = x[12] - x[19]; + f9 = x[12] + x[19]; + f10 = 0.7071067811865476f * f9; + f11 = 0.7071067811865476f * f8; + f12 = x[11] - x[20]; + f13 = x[11] + x[20]; + f14 = 0.7071067811865476f * f13; + f15 = 0.7071067811865476f * f12; + f16 = x[14] - x[17]; + f17 = x[14] + x[17]; + f18 = 0.7071067811865476f * f17; + f19 = 0.7071067811865476f * f16; + f20 = x[9] - x[22]; + f21 = x[9] + x[22]; + f22 = 0.7071067811865476f * f21; + f23 = 0.7071067811865476f * f20; + f24 = x[13] - x[18]; + f25 = x[13] + x[18]; + f26 = 0.7071067811865476f * f25; + f27 = 0.7071067811865476f * f24; + f28 = x[10] - x[21]; + f29 = x[10] + x[21]; + f30 = 0.7071067811865476f * f29; + f31 = 0.7071067811865476f * f28; + f32 = x[0] - f2; + f33 = x[0] + f2; + f34 = x[31] - f3; + f35 = x[31] + f3; + f36 = x[7] - f6; + f37 = x[7] + f6; + f38 = x[24] - f7; + f39 = x[24] + f7; + f40 = x[3] - f10; + f41 = x[3] + f10; + f42 = x[28] - f11; + f43 = x[28] + f11; + f44 = x[4] - f14; + f45 = x[4] + f14; + f46 = x[27] - f15; + f47 = x[27] + f15; + f48 = x[1] - f18; + f49 = x[1] + f18; + f50 = x[30] - f19; + f51 = x[30] + f19; + f52 = x[6] - f22; + f53 = x[6] + f22; + f54 = x[25] - f23; + f55 = x[25] + f23; + f56 = x[2] - f26; + f57 = x[2] + f26; + f58 = x[29] - f27; + f59 = x[29] + f27; + f60 = x[5] - f30; + f61 = x[5] + f30; + f62 = x[26] - f31; + f63 = x[26] + f31; + f64 = f39 + f37; + f65 = -0.5411961001461969f * f39; + f66 = 0.9238795325112867f * f64; + f67 = 1.3065629648763766f * f37; + f68 = f65 + f66; + f69 = f67 - f66; + f70 = f38 + f36; + f71 = 1.3065629648763770f * f38; + f72 = -0.3826834323650904f * f70; + f73 = 0.5411961001461961f * f36; + f74 = f71 + f72; + f75 = f73 - f72; + f76 = f47 + f45; + f77 = -0.5411961001461969f * f47; + f78 = 0.9238795325112867f * f76; + f79 = 1.3065629648763766f * f45; + f80 = f77 + f78; + f81 = f79 - f78; + f82 = f46 + f44; + f83 = 1.3065629648763770f * f46; + f84 = -0.3826834323650904f * f82; + f85 = 0.5411961001461961f * f44; + f86 = f83 + f84; + f87 = f85 - f84; + f88 = f55 + f53; + f89 = -0.5411961001461969f * f55; + f90 = 0.9238795325112867f * f88; + f91 = 1.3065629648763766f * f53; + f92 = f89 + f90; + f93 = f91 - f90; + f94 = f54 + f52; + f95 = 1.3065629648763770f * f54; + f96 = -0.3826834323650904f * f94; + f97 = 0.5411961001461961f * f52; + f98 = f95 + f96; + f99 = f97 - f96; + f100 = f63 + f61; + f101 = -0.5411961001461969f * f63; + f102 = 0.9238795325112867f * f100; + f103 = 1.3065629648763766f * f61; + f104 = f101 + f102; + f105 = f103 - f102; + f106 = f62 + f60; + f107 = 1.3065629648763770f * f62; + f108 = -0.3826834323650904f * f106; + f109 = 0.5411961001461961f * f60; + f110 = f107 + f108; + f111 = f109 - f108; + f112 = f33 - f68; + f113 = f33 + f68; + f114 = f35 - f69; + f115 = f35 + f69; + f116 = f32 - f74; + f117 = f32 + f74; + f118 = f34 - f75; + f119 = f34 + f75; + f120 = f41 - f80; + f121 = f41 + f80; + f122 = f43 - f81; + f123 = f43 + f81; + f124 = f40 - f86; + f125 = f40 + f86; + f126 = f42 - f87; + f127 = f42 + f87; + f128 = f49 - f92; + f129 = f49 + f92; + f130 = f51 - f93; + f131 = f51 + f93; + f132 = f48 - f98; + f133 = f48 + f98; + f134 = f50 - f99; + f135 = f50 + f99; + f136 = f57 - f104; + f137 = f57 + f104; + f138 = f59 - f105; + f139 = f59 + f105; + f140 = f56 - f110; + f141 = f56 + f110; + f142 = f58 - f111; + f143 = f58 + f111; + f144 = f123 + f121; + f145 = -0.7856949583871021f * f123; + f146 = 0.9807852804032304f * f144; + f147 = 1.1758756024193588f * f121; + f148 = f145 + f146; + f149 = f147 - f146; + f150 = f127 + f125; + f151 = 0.2758993792829431f * f127; + f152 = 0.5555702330196022f * f150; + f153 = 1.3870398453221475f * f125; + f154 = f151 + f152; + f155 = f153 - f152; + f156 = f122 + f120; + f157 = 1.1758756024193591f * f122; + f158 = -0.1950903220161287f * f156; + f159 = 0.7856949583871016f * f120; + f160 = f157 + f158; + f161 = f159 - f158; + f162 = f126 + f124; + f163 = 1.3870398453221473f * f126; + f164 = -0.8314696123025455f * f162; + f165 = -0.2758993792829436f * f124; + f166 = f163 + f164; + f167 = f165 - f164; + f168 = f139 + f137; + f169 = -0.7856949583871021f * f139; + f170 = 0.9807852804032304f * f168; + f171 = 1.1758756024193588f * f137; + f172 = f169 + f170; + f173 = f171 - f170; + f174 = f143 + f141; + f175 = 0.2758993792829431f * f143; + f176 = 0.5555702330196022f * f174; + f177 = 1.3870398453221475f * f141; + f178 = f175 + f176; + f179 = f177 - f176; + f180 = f138 + f136; + f181 = 1.1758756024193591f * f138; + f182 = -0.1950903220161287f * f180; + f183 = 0.7856949583871016f * f136; + f184 = f181 + f182; + f185 = f183 - f182; + f186 = f142 + f140; + f187 = 1.3870398453221473f * f142; + f188 = -0.8314696123025455f * f186; + f189 = -0.2758993792829436f * f140; + f190 = f187 + f188; + f191 = f189 - f188; + f192 = f113 - f148; + f193 = f113 + f148; + f194 = f115 - f149; + f195 = f115 + f149; + f196 = f117 - f154; + f197 = f117 + f154; + f198 = f119 - f155; + f199 = f119 + f155; + f200 = f112 - f160; + f201 = f112 + f160; + f202 = f114 - f161; + f203 = f114 + f161; + f204 = f116 - f166; + f205 = f116 + f166; + f206 = f118 - f167; + f207 = f118 + f167; + f208 = f129 - f172; + f209 = f129 + f172; + f210 = f131 - f173; + f211 = f131 + f173; + f212 = f133 - f178; + f213 = f133 + f178; + f214 = f135 - f179; + f215 = f135 + f179; + f216 = f128 - f184; + f217 = f128 + f184; + f218 = f130 - f185; + f219 = f130 + f185; + f220 = f132 - f190; + f221 = f132 + f190; + f222 = f134 - f191; + f223 = f134 + f191; + f224 = f211 + f209; + f225 = -0.8971675863426361f * f211; + f226 = 0.9951847266721968f * f224; + f227 = 1.0932018670017576f * f209; + f228 = f225 + f226; + f229 = f227 - f226; + f230 = f215 + f213; + f231 = -0.4105245275223571f * f215; + f232 = 0.8819212643483549f * f230; + f233 = 1.3533180011743529f * f213; + f234 = f231 + f232; + f235 = f233 - f232; + f236 = f219 + f217; + f237 = 0.1386171691990915f * f219; + f238 = 0.6343932841636455f * f236; + f239 = 1.4074037375263826f * f217; + f240 = f237 + f238; + f241 = f239 - f238; + f242 = f223 + f221; + f243 = 0.6666556584777466f * f223; + f244 = 0.2902846772544623f * f242; + f245 = 1.2472250129866711f * f221; + f246 = f243 + f244; + f247 = f245 - f244; + f248 = f210 + f208; + f249 = 1.0932018670017574f * f210; + f250 = -0.0980171403295605f * f248; + f251 = 0.8971675863426364f * f208; + f252 = f249 + f250; + f253 = f251 - f250; + f254 = f214 + f212; + f255 = 1.3533180011743529f * f214; + f256 = -0.4713967368259979f * f254; + f257 = 0.4105245275223569f * f212; + f258 = f255 + f256; + f259 = f257 - f256; + f260 = f218 + f216; + f261 = 1.4074037375263826f * f218; + f262 = -0.7730104533627369f * f260; + f263 = -0.1386171691990913f * f216; + f264 = f261 + f262; + f265 = f263 - f262; + f266 = f222 + f220; + f267 = 1.2472250129866711f * f222; + f268 = -0.9569403357322089f * f266; + f269 = -0.6666556584777469f * f220; + f270 = f267 + f268; + f271 = f269 - f268; + f272 = f193 - f228; + f273 = f193 + f228; + f274 = f195 - f229; + f275 = f195 + f229; + f276 = f197 - f234; + f277 = f197 + f234; + f278 = f199 - f235; + f279 = f199 + f235; + f280 = f201 - f240; + f281 = f201 + f240; + f282 = f203 - f241; + f283 = f203 + f241; + f284 = f205 - f246; + f285 = f205 + f246; + f286 = f207 - f247; + f287 = f207 + f247; + f288 = f192 - f252; + f289 = f192 + f252; + f290 = f194 - f253; + f291 = f194 + f253; + f292 = f196 - f258; + f293 = f196 + f258; + f294 = f198 - f259; + f295 = f198 + f259; + f296 = f200 - f264; + f297 = f200 + f264; + f298 = f202 - f265; + f299 = f202 + f265; + f300 = f204 - f270; + f301 = f204 + f270; + f302 = f206 - f271; + f303 = f206 + f271; + f304 = f275 + f273; + f305 = -0.9751575901732920f * f275; + f306 = 0.9996988186962043f * f304; + f307 = 1.0242400472191164f * f273; + y[0] = f305 + f306; + y[31] = f307 - f306; + f310 = f279 + f277; + f311 = -0.8700688593994936f * f279; + f312 = 0.9924795345987100f * f310; + f313 = 1.1148902097979263f * f277; + y[2] = f311 + f312; + y[29] = f313 - f312; + f316 = f283 + f281; + f317 = -0.7566008898816587f * f283; + f318 = 0.9757021300385286f * f316; + f319 = 1.1948033701953984f * f281; + y[4] = f317 + f318; + y[27] = f319 - f318; + f322 = f287 + f285; + f323 = -0.6358464401941451f * f287; + f324 = 0.9495281805930367f * f322; + f325 = 1.2632099209919283f * f285; + y[6] = f323 + f324; + y[25] = f325 - f324; + f328 = f291 + f289; + f329 = -0.5089684416985408f * f291; + f330 = 0.9142097557035307f * f328; + f331 = 1.3194510697085207f * f289; + y[8] = f329 + f330; + y[23] = f331 - f330; + f334 = f295 + f293; + f335 = -0.3771887988789273f * f295; + f336 = 0.8700869911087114f * f334; + f337 = 1.3629851833384954f * f293; + y[10] = f335 + f336; + y[21] = f337 - f336; + f340 = f299 + f297; + f341 = -0.2417766217337384f * f299; + f342 = 0.8175848131515837f * f340; + f343 = 1.3933930045694289f * f297; + y[12] = f341 + f342; + y[19] = f343 - f342; + f346 = f303 + f301; + f347 = -0.1040360035527077f * f303; + f348 = 0.7572088465064845f * f346; + f349 = 1.4103816894602612f * f301; + y[14] = f347 + f348; + y[17] = f349 - f348; + f352 = f274 + f272; + f353 = 0.0347065382144002f * f274; + f354 = 0.6895405447370668f * f352; + f355 = 1.4137876276885337f * f272; + y[16] = f353 + f354; + y[15] = f355 - f354; + f358 = f278 + f276; + f359 = 0.1731148370459795f * f278; + f360 = 0.6152315905806268f * f358; + f361 = 1.4035780182072330f * f276; + y[18] = f359 + f360; + y[13] = f361 - f360; + f364 = f282 + f280; + f365 = 0.3098559453626100f * f282; + f366 = 0.5349976198870972f * f364; + f367 = 1.3798511851368043f * f280; + y[20] = f365 + f366; + y[11] = f367 - f366; + f370 = f286 + f284; + f371 = 0.4436129715409088f * f286; + f372 = 0.4496113296546065f * f370; + f373 = 1.3428356308501219f * f284; + y[22] = f371 + f372; + y[9] = f373 - f372; + f376 = f290 + f288; + f377 = 0.5730977622997509f * f290; + f378 = 0.3598950365349881f * f376; + f379 = 1.2928878353697271f * f288; + y[24] = f377 + f378; + y[7] = f379 - f378; + f382 = f294 + f292; + f383 = 0.6970633083205415f * f294; + f384 = 0.2667127574748984f * f382; + f385 = 1.2304888232703382f * f292; + y[26] = f383 + f384; + y[5] = f385 - f384; + f388 = f298 + f296; + f389 = 0.8143157536286401f * f298; + f390 = 0.1709618887603012f * f388; + f391 = 1.1562395311492424f * f296; + y[28] = f389 + f390; + y[3] = f391 - f390; + f394 = f302 + f300; + f395 = 0.9237258930790228f * f302; + f396 = 0.0735645635996674f * f394; + f397 = 1.0708550202783576f * f300; + y[30] = f395 + f396; + y[1] = f397 - f396; + } + + private void DST4_32(float[] y, float[] x) + { + float f0, f1, f2, f3, f4, f5, f6, f7, f8, f9; + float f10, f11, f12, f13, f14, f15, f16, f17, f18, f19; + float f20, f21, f22, f23, f24, f25, f26, f27, f28, f29; + float f30, f31, f32, f33, f34, f35, f36, f37, f38, f39; + float f40, f41, f42, f43, f44, f45, f46, f47, f48, f49; + float f50, f51, f52, f53, f54, f55, f56, f57, f58, f59; + float f60, f61, f62, f63, f64, f65, f66, f67, f68, f69; + float f70, f71, f72, f73, f74, f75, f76, f77, f78, f79; + float f80, f81, f82, f83, f84, f85, f86, f87, f88, f89; + float f90, f91, f92, f93, f94, f95, f96, f97, f98, f99; + float f100, f101, f102, f103, f104, f105, f106, f107, f108, f109; + float f110, f111, f112, f113, f114, f115, f116, f117, f118, f119; + float f120, f121, f122, f123, f124, f125, f126, f127, f128, f129; + float f130, f131, f132, f133, f134, f135, f136, f137, f138, f139; + float f140, f141, f142, f143, f144, f145, f146, f147, f148, f149; + float f150, f151, f152, f153, f154, f155, f156, f157, f158, f159; + float f160, f161, f162, f163, f164, f165, f166, f167, f168, f169; + float f170, f171, f172, f173, f174, f175, f176, f177, f178, f179; + float f180, f181, f182, f183, f184, f185, f186, f187, f188, f189; + float f190, f191, f192, f193, f194, f195, f196, f197, f198, f199; + float f200, f201, f202, f203, f204, f205, f206, f207, f208, f209; + float f210, f211, f212, f213, f214, f215, f216, f217, f218, f219; + float f220, f221, f222, f223, f224, f225, f226, f227, f228, f229; + float f230, f231, f232, f233, f234, f235, f236, f237, f238, f239; + float f240, f241, f242, f243, f244, f245, f246, f247, f248, f249; + float f250, f251, f252, f253, f254, f255, f256, f257, f258, f259; + float f260, f261, f262, f263, f264, f265, f266, f267, f268, f269; + float f270, f271, f272, f273, f274, f275, f276, f277, f278, f279; + float f280, f281, f282, f283, f284, f285, f286, f287, f288, f289; + float f290, f291, f292, f293, f294, f295, f296, f297, f298, f299; + float f300, f301, f302, f303, f304, f305, f306, f307, f308, f309; + float f310, f311, f312, f313, f314, f315, f316, f317, f318, f319; + float f320, f321, f322, f323, f324, f325, f326, f327, f328, f329; + float f330, f331, f332, f333, f334, f335; + + f0 = x[0] - x[1]; + f1 = x[2] - x[1]; + f2 = x[2] - x[3]; + f3 = x[4] - x[3]; + f4 = x[4] - x[5]; + f5 = x[6] - x[5]; + f6 = x[6] - x[7]; + f7 = x[8] - x[7]; + f8 = x[8] - x[9]; + f9 = x[10] - x[9]; + f10 = x[10] - x[11]; + f11 = x[12] - x[11]; + f12 = x[12] - x[13]; + f13 = x[14] - x[13]; + f14 = x[14] - x[15]; + f15 = x[16] - x[15]; + f16 = x[16] - x[17]; + f17 = x[18] - x[17]; + f18 = x[18] - x[19]; + f19 = x[20] - x[19]; + f20 = x[20] - x[21]; + f21 = x[22] - x[21]; + f22 = x[22] - x[23]; + f23 = x[24] - x[23]; + f24 = x[24] - x[25]; + f25 = x[26] - x[25]; + f26 = x[26] - x[27]; + f27 = x[28] - x[27]; + f28 = x[28] - x[29]; + f29 = x[30] - x[29]; + f30 = x[30] - x[31]; + f31 = 0.7071067811865476f * f15; + f32 = x[0] - f31; + f33 = x[0] + f31; + f34 = f7 + f23; + f35 = 1.3065629648763766f * f7; + f36 = -0.9238795325112866f * f34; + f37 = -0.5411961001461967f * f23; + f38 = f35 + f36; + f39 = f37 - f36; + f40 = f33 - f39; + f41 = f33 + f39; + f42 = f32 - f38; + f43 = f32 + f38; + f44 = f11 - f19; + f45 = f11 + f19; + f46 = 0.7071067811865476f * f45; + f47 = f3 - f46; + f48 = f3 + f46; + f49 = 0.7071067811865476f * f44; + f50 = f49 - f27; + f51 = f49 + f27; + f52 = f51 + f48; + f53 = -0.7856949583871021f * f51; + f54 = 0.9807852804032304f * f52; + f55 = 1.1758756024193588f * f48; + f56 = f53 + f54; + f57 = f55 - f54; + f58 = f50 + f47; + f59 = -0.2758993792829430f * f50; + f60 = 0.8314696123025452f * f58; + f61 = 1.3870398453221475f * f47; + f62 = f59 + f60; + f63 = f61 - f60; + f64 = f41 - f56; + f65 = f41 + f56; + f66 = f43 - f62; + f67 = f43 + f62; + f68 = f42 - f63; + f69 = f42 + f63; + f70 = f40 - f57; + f71 = f40 + f57; + f72 = f5 - f9; + f73 = f5 + f9; + f74 = f13 - f17; + f75 = f13 + f17; + f76 = f21 - f25; + f77 = f21 + f25; + f78 = 0.7071067811865476f * f75; + f79 = f1 - f78; + f80 = f1 + f78; + f81 = f73 + f77; + f82 = 1.3065629648763766f * f73; + f83 = -0.9238795325112866f * f81; + f84 = -0.5411961001461967f * f77; + f85 = f82 + f83; + f86 = f84 - f83; + f87 = f80 - f86; + f88 = f80 + f86; + f89 = f79 - f85; + f90 = f79 + f85; + f91 = 0.7071067811865476f * f74; + f92 = f29 - f91; + f93 = f29 + f91; + f94 = f76 + f72; + f95 = 1.3065629648763766f * f76; + f96 = -0.9238795325112866f * f94; + f97 = -0.5411961001461967f * f72; + f98 = f95 + f96; + f99 = f97 - f96; + f100 = f93 - f99; + f101 = f93 + f99; + f102 = f92 - f98; + f103 = f92 + f98; + f104 = f101 + f88; + f105 = -0.8971675863426361f * f101; + f106 = 0.9951847266721968f * f104; + f107 = 1.0932018670017576f * f88; + f108 = f105 + f106; + f109 = f107 - f106; + f110 = f90 - f103; + f111 = -0.6666556584777466f * f103; + f112 = 0.9569403357322089f * f110; + f113 = 1.2472250129866713f * f90; + f114 = f112 - f111; + f115 = f113 - f112; + f116 = f102 + f89; + f117 = -0.4105245275223571f * f102; + f118 = 0.8819212643483549f * f116; + f119 = 1.3533180011743529f * f89; + f120 = f117 + f118; + f121 = f119 - f118; + f122 = f87 - f100; + f123 = -0.1386171691990915f * f100; + f124 = 0.7730104533627370f * f122; + f125 = 1.4074037375263826f * f87; + f126 = f124 - f123; + f127 = f125 - f124; + f128 = f65 - f108; + f129 = f65 + f108; + f130 = f67 - f114; + f131 = f67 + f114; + f132 = f69 - f120; + f133 = f69 + f120; + f134 = f71 - f126; + f135 = f71 + f126; + f136 = f70 - f127; + f137 = f70 + f127; + f138 = f68 - f121; + f139 = f68 + f121; + f140 = f66 - f115; + f141 = f66 + f115; + f142 = f64 - f109; + f143 = f64 + f109; + f144 = f0 + f30; + f145 = 1.0478631305325901f * f0; + f146 = -0.9987954562051724f * f144; + f147 = -0.9497277818777548f * f30; + f148 = f145 + f146; + f149 = f147 - f146; + f150 = f4 + f26; + f151 = 1.2130114330978077f * f4; + f152 = -0.9700312531945440f * f150; + f153 = -0.7270510732912803f * f26; + f154 = f151 + f152; + f155 = f153 - f152; + f156 = f8 + f22; + f157 = 1.3315443865537255f * f8; + f158 = -0.9039892931234433f * f156; + f159 = -0.4764341996931612f * f22; + f160 = f157 + f158; + f161 = f159 - f158; + f162 = f12 + f18; + f163 = 1.3989068359730781f * f12; + f164 = -0.8032075314806453f * f162; + f165 = -0.2075082269882124f * f18; + f166 = f163 + f164; + f167 = f165 - f164; + f168 = f16 + f14; + f169 = 1.4125100802019777f * f16; + f170 = -0.6715589548470187f * f168; + f171 = 0.0693921705079402f * f14; + f172 = f169 + f170; + f173 = f171 - f170; + f174 = f20 + f10; + f175 = 1.3718313541934939f * f20; + f176 = -0.5141027441932219f * f174; + f177 = 0.3436258658070501f * f10; + f178 = f175 + f176; + f179 = f177 - f176; + f180 = f24 + f6; + f181 = 1.2784339185752409f * f24; + f182 = -0.3368898533922200f * f180; + f183 = 0.6046542117908008f * f6; + f184 = f181 + f182; + f185 = f183 - f182; + f186 = f28 + f2; + f187 = 1.1359069844201433f * f28; + f188 = -0.1467304744553624f * f186; + f189 = 0.8424460355094185f * f2; + f190 = f187 + f188; + f191 = f189 - f188; + f192 = f149 - f173; + f193 = f149 + f173; + f194 = f148 - f172; + f195 = f148 + f172; + f196 = f155 - f179; + f197 = f155 + f179; + f198 = f154 - f178; + f199 = f154 + f178; + f200 = f161 - f185; + f201 = f161 + f185; + f202 = f160 - f184; + f203 = f160 + f184; + f204 = f167 - f191; + f205 = f167 + f191; + f206 = f166 - f190; + f207 = f166 + f190; + f208 = f192 + f194; + f209 = 1.1758756024193588f * f192; + f210 = -0.9807852804032304f * f208; + f211 = -0.7856949583871021f * f194; + f212 = f209 + f210; + f213 = f211 - f210; + f214 = f196 + f198; + f215 = 1.3870398453221475f * f196; + f216 = -0.5555702330196022f * f214; + f217 = 0.2758993792829431f * f198; + f218 = f215 + f216; + f219 = f217 - f216; + f220 = f200 + f202; + f221 = 0.7856949583871022f * f200; + f222 = 0.1950903220161283f * f220; + f223 = 1.1758756024193586f * f202; + f224 = f221 + f222; + f225 = f223 - f222; + f226 = f204 + f206; + f227 = -0.2758993792829430f * f204; + f228 = 0.8314696123025452f * f226; + f229 = 1.3870398453221475f * f206; + f230 = f227 + f228; + f231 = f229 - f228; + f232 = f193 - f201; + f233 = f193 + f201; + f234 = f195 - f203; + f235 = f195 + f203; + f236 = f197 - f205; + f237 = f197 + f205; + f238 = f199 - f207; + f239 = f199 + f207; + f240 = f213 - f225; + f241 = f213 + f225; + f242 = f212 - f224; + f243 = f212 + f224; + f244 = f219 - f231; + f245 = f219 + f231; + f246 = f218 - f230; + f247 = f218 + f230; + f248 = f232 + f234; + f249 = 1.3065629648763766f * f232; + f250 = -0.9238795325112866f * f248; + f251 = -0.5411961001461967f * f234; + f252 = f249 + f250; + f253 = f251 - f250; + f254 = f236 + f238; + f255 = 0.5411961001461969f * f236; + f256 = 0.3826834323650898f * f254; + f257 = 1.3065629648763766f * f238; + f258 = f255 + f256; + f259 = f257 - f256; + f260 = f240 + f242; + f261 = 1.3065629648763766f * f240; + f262 = -0.9238795325112866f * f260; + f263 = -0.5411961001461967f * f242; + f264 = f261 + f262; + f265 = f263 - f262; + f266 = f244 + f246; + f267 = 0.5411961001461969f * f244; + f268 = 0.3826834323650898f * f266; + f269 = 1.3065629648763766f * f246; + f270 = f267 + f268; + f271 = f269 - f268; + f272 = f233 - f237; + f273 = f233 + f237; + f274 = f235 - f239; + f275 = f235 + f239; + f276 = f253 - f259; + f277 = f253 + f259; + f278 = f252 - f258; + f279 = f252 + f258; + f280 = f241 - f245; + f281 = f241 + f245; + f282 = f243 - f247; + f283 = f243 + f247; + f284 = f265 - f271; + f285 = f265 + f271; + f286 = f264 - f270; + f287 = f264 + f270; + f288 = f272 - f274; + f289 = f272 + f274; + f290 = 0.7071067811865474f * f288; + f291 = 0.7071067811865474f * f289; + f292 = f276 - f278; + f293 = f276 + f278; + f294 = 0.7071067811865474f * f292; + f295 = 0.7071067811865474f * f293; + f296 = f280 - f282; + f297 = f280 + f282; + f298 = 0.7071067811865474f * f296; + f299 = 0.7071067811865474f * f297; + f300 = f284 - f286; + f301 = f284 + f286; + f302 = 0.7071067811865474f * f300; + f303 = 0.7071067811865474f * f301; + f304 = f129 - f273; + f305 = f129 + f273; + f306 = f131 - f281; + f307 = f131 + f281; + f308 = f133 - f285; + f309 = f133 + f285; + f310 = f135 - f277; + f311 = f135 + f277; + f312 = f137 - f295; + f313 = f137 + f295; + f314 = f139 - f303; + f315 = f139 + f303; + f316 = f141 - f299; + f317 = f141 + f299; + f318 = f143 - f291; + f319 = f143 + f291; + f320 = f142 - f290; + f321 = f142 + f290; + f322 = f140 - f298; + f323 = f140 + f298; + f324 = f138 - f302; + f325 = f138 + f302; + f326 = f136 - f294; + f327 = f136 + f294; + f328 = f134 - f279; + f329 = f134 + f279; + f330 = f132 - f287; + f331 = f132 + f287; + f332 = f130 - f283; + f333 = f130 + f283; + f334 = f128 - f275; + f335 = f128 + f275; + y[31] = 0.5001506360206510f * f305; + y[30] = 0.5013584524464084f * f307; + y[29] = 0.5037887256810443f * f309; + y[28] = 0.5074711720725553f * f311; + y[27] = 0.5124514794082247f * f313; + y[26] = 0.5187927131053328f * f315; + y[25] = 0.5265773151542700f * f317; + y[24] = 0.5359098169079920f * f319; + y[23] = 0.5469204379855088f * f321; + y[22] = 0.5597698129470802f * f323; + y[21] = 0.5746551840326600f * f325; + y[20] = 0.5918185358574165f * f327; + y[19] = 0.6115573478825099f * f329; + y[18] = 0.6342389366884031f * f331; + y[17] = 0.6603198078137061f * f333; + y[16] = 0.6903721282002123f * f335; + y[15] = 0.7251205223771985f * f334; + y[14] = 0.7654941649730891f * f332; + y[13] = 0.8127020908144905f * f330; + y[12] = 0.8683447152233481f * f328; + y[11] = 0.9345835970364075f * f326; + y[10] = 1.0144082649970547f * f324; + y[9] = 1.1120716205797176f * f322; + y[8] = 1.2338327379765710f * f320; + y[7] = 1.3892939586328277f * f318; + y[6] = 1.5939722833856311f * f316; + y[5] = 1.8746759800084078f * f314; + y[4] = 2.2820500680051619f * f312; + y[3] = 2.9246284281582162f * f310; + y[2] = 4.0846110781292477f * f308; + y[1] = 6.7967507116736332f * f306; + y[0] = 20.3738781672314530f * f304; + } + } +} diff --git a/SharpJaad.AAC/Sbr/TFGrid.cs b/SharpJaad.AAC/Sbr/TFGrid.cs new file mode 100644 index 0000000..4ed591a --- /dev/null +++ b/SharpJaad.AAC/Sbr/TFGrid.cs @@ -0,0 +1,158 @@ +namespace SharpJaad.AAC.Sbr +{ + public class TFGrid + { + /* function constructs new time border vector */ + /* first build into temp vector to be able to use previous vector on error */ + public static int EnvelopeTimeBorderVector(SBR sbr, int ch) + { + int l, border, temp; + int[] t_E_temp = new int[6]; + + t_E_temp[0] = sbr._rate * sbr._abs_bord_lead[ch]; + t_E_temp[sbr._L_E[ch]] = sbr._rate * sbr._abs_bord_trail[ch]; + + switch (sbr._bs_frame_class[ch]) + { + case Constants.FIXFIX: + switch (sbr._L_E[ch]) + { + case 4: + temp = sbr._numTimeSlots / 4; + t_E_temp[3] = sbr._rate * 3 * temp; + t_E_temp[2] = sbr._rate * 2 * temp; + t_E_temp[1] = sbr._rate * temp; + break; + case 2: + t_E_temp[1] = sbr._rate * (sbr._numTimeSlots / 2); + break; + default: + break; + } + break; + + case Constants.FIXVAR: + if (sbr._L_E[ch] > 1) + { + int i = sbr._L_E[ch]; + border = sbr._abs_bord_trail[ch]; + + for (l = 0; l < sbr._L_E[ch] - 1; l++) + { + if (border < sbr._bs_rel_bord[ch, l]) + return 1; + + border -= sbr._bs_rel_bord[ch, l]; + t_E_temp[--i] = sbr._rate * border; + } + } + break; + + case Constants.VARFIX: + if (sbr._L_E[ch] > 1) + { + int i = 1; + border = sbr._abs_bord_lead[ch]; + + for (l = 0; l < sbr._L_E[ch] - 1; l++) + { + border += sbr._bs_rel_bord[ch, l]; + + if (sbr._rate * border + sbr._tHFAdj > sbr._numTimeSlotsRate + sbr._tHFGen) + return 1; + + t_E_temp[i++] = sbr._rate * border; + } + } + break; + + case Constants.VARVAR: + if (sbr._bs_num_rel_0[ch] != 0) + { + int i = 1; + border = sbr._abs_bord_lead[ch]; + + for (l = 0; l < sbr._bs_num_rel_0[ch]; l++) + { + border += sbr._bs_rel_bord_0[ch, l]; + + if (sbr._rate * border + sbr._tHFAdj > sbr._numTimeSlotsRate + sbr._tHFGen) + return 1; + + t_E_temp[i++] = sbr._rate * border; + } + } + + if (sbr._bs_num_rel_1[ch] != 0) + { + int i = sbr._L_E[ch]; + border = sbr._abs_bord_trail[ch]; + + for (l = 0; l < sbr._bs_num_rel_1[ch]; l++) + { + if (border < sbr._bs_rel_bord_1[ch, l]) + return 1; + + border -= sbr._bs_rel_bord_1[ch, l]; + t_E_temp[--i] = sbr._rate * border; + } + } + break; + } + + /* no error occured, we can safely use this t_E vector */ + for (l = 0; l < 6; l++) + { + sbr._t_E[ch, l] = t_E_temp[l]; + } + + return 0; + } + + public static void NoiseFloorTimeBorderVector(SBR sbr, int ch) + { + sbr._t_Q[ch, 0] = sbr._t_E[ch, 0]; + + if (sbr._L_E[ch] == 1) + { + sbr._t_Q[ch, 1] = sbr._t_E[ch, 1]; + sbr._t_Q[ch, 2] = 0; + } + else + { + int index = MiddleBorder(sbr, ch); + sbr._t_Q[ch, 1] = sbr._t_E[ch, index]; + sbr._t_Q[ch, 2] = sbr._t_E[ch, sbr._L_E[ch]]; + } + } + + private static int MiddleBorder(SBR sbr, int ch) + { + int retval = 0; + + switch (sbr._bs_frame_class[ch]) + { + case Constants.FIXFIX: + retval = sbr._L_E[ch] / 2; + break; + case Constants.VARFIX: + if (sbr._bs_pointer[ch] == 0) + retval = 1; + else if (sbr._bs_pointer[ch] == 1) + retval = sbr._L_E[ch] - 1; + else + retval = sbr._bs_pointer[ch] - 1; + break; + case Constants.FIXVAR: + case Constants.VARVAR: + if (sbr._bs_pointer[ch] > 1) + retval = sbr._L_E[ch] + 1 - sbr._bs_pointer[ch]; + else + retval = sbr._L_E[ch] - 1; + break; + } + + return retval > 0 ? retval : 0; + } + } +} diff --git a/SharpJaad.AAC/Syntax/BitStream.cs b/SharpJaad.AAC/Syntax/BitStream.cs new file mode 100644 index 0000000..8968129 --- /dev/null +++ b/SharpJaad.AAC/Syntax/BitStream.cs @@ -0,0 +1,227 @@ +using System; +using SharpJaad.AAC; + +namespace SharpJaad.AAC.Syntax +{ + public class BitStream + { + private const int WORD_BITS = 32; + private const int WORD_BYTES = 4; + private const int BYTE_MASK = 0xff; + private byte[] _buffer; + private int _pos; //offset in the buffer array + private int _cache; //current 4 bytes, that are read from the buffer + protected int _bitsCached; //remaining bits in current cache + protected int _position; //number of total bits read + + public BitStream() + { } + + public BitStream(byte[] data) + { + SetData(data); + } + + public void Destroy() + { + Reset(); + _buffer = null; + } + + public void SetData(byte[] data) + { + Reset(); + + int size = data.Length; + + // reduce the buffer size to an integer number of words + int shift = size % WORD_BYTES; + + // push leading bytes to cache + _bitsCached = 8 * shift; + + for (int i = 0; i < shift; ++i) + { + byte c = data[i]; + _cache <<= 8; + _cache |= 0xff & c; + } + + size -= shift; + + //only reallocate if needed + if (_buffer == null || _buffer.Length != size) + _buffer = new byte[size]; + + Buffer.BlockCopy(data, shift, _buffer, 0, _buffer.Length); + } + + public void ByteAlign() + { + int toFlush = _bitsCached & 7; + if (toFlush > 0) SkipBits(toFlush); + } + + public void Reset() + { + _pos = 0; + _bitsCached = 0; + _cache = 0; + _position = 0; + } + + public int GetPosition() + { + return _position; + } + + public int GetBitsLeft() + { + return 8 * (_buffer.Length - _pos) + _bitsCached; + } + + /** + * Reads the next four bytes. + * @param peek if true, the stream pointer will not be increased + */ + protected int ReadCache(bool peek) + { + int i; + if (_pos > _buffer.Length - WORD_BYTES) throw new AACException("end of stream", true); + else i = (_buffer[_pos] & BYTE_MASK) << 24 + | (_buffer[_pos + 1] & BYTE_MASK) << 16 + | (_buffer[_pos + 2] & BYTE_MASK) << 8 + | _buffer[_pos + 3] & BYTE_MASK; + if (!peek) _pos += WORD_BYTES; + return i; + } + + public int ReadBits(int n) + { + int result; + if (_bitsCached >= n) + { + _bitsCached -= n; + result = _cache >> _bitsCached & MaskBits(n); + _position += n; + } + else + { + _position += n; + int c = _cache & MaskBits(_bitsCached); + int left = n - _bitsCached; + _cache = ReadCache(false); + _bitsCached = WORD_BITS - left; + result = _cache >> _bitsCached & MaskBits(left) | c << left; + } + return result; + } + + public int ReadBit() + { + int i; + if (_bitsCached > 0) + { + _bitsCached--; + i = _cache >> _bitsCached & 1; + _position++; + } + else + { + _cache = ReadCache(false); + _bitsCached = WORD_BITS - 1; + _position++; + i = _cache >> _bitsCached & 1; + } + return i; + } + + public bool ReadBool() + { + return (ReadBit() & 0x1) != 0; + } + + public int PeekBits(int n) + { + int ret; + if (_bitsCached >= n) + { + ret = _cache >> _bitsCached - n & MaskBits(n); + } + else + { + //old cache + int c = _cache & MaskBits(_bitsCached); + n -= _bitsCached; + //read next & combine + ret = ReadCache(true) >> WORD_BITS - n & MaskBits(n) | c << n; + } + return ret; + } + + public int PeekBit() + { + int ret; + if (_bitsCached > 0) + { + ret = _cache >> _bitsCached - 1 & 1; + } + else + { + int word = ReadCache(true); + ret = word >> WORD_BITS - 1 & 1; + } + return ret; + } + + public void SkipBits(int n) + { + _position += n; + if (n <= _bitsCached) + { + _bitsCached -= n; + } + else + { + n -= _bitsCached; + while (n >= WORD_BITS) + { + n -= WORD_BITS; + ReadCache(false); + } + if (n > 0) + { + _cache = ReadCache(false); + _bitsCached = WORD_BITS - n; + } + else + { + _cache = 0; + _bitsCached = 0; + } + } + } + + public void SkipBit() + { + _position++; + if (_bitsCached > 0) + { + _bitsCached--; + } + else + { + _cache = ReadCache(false); + _bitsCached = WORD_BITS - 1; + } + } + + public int MaskBits(int n) + { + int i; + if (n == 32) i = -1; + else i = (1 << n) - 1; + return i; + } + } +} diff --git a/SharpJaad.AAC/Syntax/CCE.cs b/SharpJaad.AAC/Syntax/CCE.cs new file mode 100644 index 0000000..26f1c2c --- /dev/null +++ b/SharpJaad.AAC/Syntax/CCE.cs @@ -0,0 +1,191 @@ +using SharpJaad.AAC.Huffman; +using System; + +namespace SharpJaad.AAC.Syntax +{ + public class CCE : Element + { + public const int BEFORE_TNS = 0; + public const int AFTER_TNS = 1; + public const int AFTER_IMDCT = 2; + private static readonly float[] CCE_SCALE = + { + 1.09050773266525765921f, + 1.18920711500272106672f, + 1.4142135623730950488016887f, + 2f + }; + private readonly ICStream _ics; + //private float[] iqData; + private int _couplingPoint; + private int _coupledCount; + private readonly bool[] _channelPair; + private readonly int[] _idSelect; + private readonly int[] _chSelect; + /*[0] shared list of gains; [1] list of gains for right channel; + *[2] list of gains for left channel; [3] lists of gains for both channels + */ + private readonly float[,] _gain; + + public CCE(DecoderConfig config) + { + _ics = new ICStream(config); + _channelPair = new bool[8]; + _idSelect = new int[8]; + _chSelect = new int[8]; + _gain = new float[16, 120]; + } + + public int GetCouplingPoint() + { + return _couplingPoint; + } + + public int GetCoupledCount() + { + return _coupledCount; + } + + public bool IsChannelPair(int index) + { + return _channelPair[index]; + } + + public int GetIDSelect(int index) + { + return _idSelect[index]; + } + + public int GetCHSelect(int index) + { + return _chSelect[index]; + } + + public void Decode(BitStream input, DecoderConfig conf) + { + ReadElementInstanceTag(input); + _couplingPoint = 2 * input.ReadBit(); + _coupledCount = input.ReadBits(3); + int gainCount = 0; + int i; + for (i = 0; i <= _coupledCount; i++) + { + gainCount++; + _channelPair[i] = input.ReadBool(); + _idSelect[i] = input.ReadBits(4); + if (_channelPair[i]) + { + _chSelect[i] = input.ReadBits(2); + if (_chSelect[i] == 3) gainCount++; + } + else _chSelect[i] = 2; + } + _couplingPoint += input.ReadBit(); + _couplingPoint |= _couplingPoint >> 1; + + bool sign = input.ReadBool(); + double scale = CCE_SCALE[input.ReadBits(2)]; + + _ics.Decode(input, false, conf); + ICSInfo info = _ics.GetInfo(); + int windowGroupCount = info.GetWindowGroupCount(); + int maxSFB = info.GetMaxSFB(); + + int[] sfbCB = _ics.getSfbCB(); + for (i = 0; i < gainCount; i++) + { + int idx = 0; + int cge = 1; + int xg = 0; + float gainCache = 1.0f; + if (i > 0) + { + cge = _couplingPoint == 2 ? 1 : input.ReadBit(); + xg = cge == 0 ? 0 : HuffmanDec.DecodeScaleFactor(input) - 60; + gainCache = (float)Math.Pow(scale, -xg); + } + if (_couplingPoint == 2) _gain[i, 0] = gainCache; + else + { + int sfb; + for (int g = 0; g < windowGroupCount; g++) + { + for (sfb = 0; sfb < maxSFB; sfb++, idx++) + { + if (sfbCB[idx] != HCB.ZERO_HCB) + { + if (cge == 0) + { + int t = HuffmanDec.DecodeScaleFactor(input) - 60; + if (t != 0) + { + int s = 1; + t = xg += t; + if (!sign) + { + s -= 2 * (t & 0x1); + t >>= 1; + } + gainCache = (float)(Math.Pow(scale, -t) * s); + } + } + _gain[i, idx] = gainCache; + } + } + } + } + } + } + + public void Process() + { + //iqData = ics.getInvQuantData(); + } + + public void ApplyIndependentCoupling(int index, float[] data) + { + double g = _gain[index, 0]; + float[] iqData = _ics.GetInvQuantData(); + for (int i = 0; i < data.Length; i++) + { + data[i] += (float)(g * iqData[i]); + } + } + + public void ApplyDependentCoupling(int index, float[] data) + { + ICSInfo info = _ics.GetInfo(); + int[] swbOffsets = info.GetSWBOffsets(); + int windowGroupCount = info.GetWindowGroupCount(); + int maxSFB = info.GetMaxSFB(); + int[] sfbCB = _ics.getSfbCB(); + float[] iqData = _ics.GetInvQuantData(); + + int srcOff = 0; + int dstOff = 0; + + int len, sfb, group, k, idx = 0; + float x; + for (int g = 0; g < windowGroupCount; g++) + { + len = info.GetWindowGroupLength(g); + for (sfb = 0; sfb < maxSFB; sfb++, idx++) + { + if (sfbCB[idx] != HCB.ZERO_HCB) + { + x = _gain[index, idx]; + for (group = 0; group < len; group++) + { + for (k = swbOffsets[sfb]; k < swbOffsets[sfb + 1]; k++) + { + data[dstOff + group * 128 + k] += x * iqData[srcOff + group * 128 + k]; + } + } + } + } + dstOff += len * 128; + srcOff += len * 128; + } + } + } +} diff --git a/SharpJaad.AAC/Syntax/CPE.cs b/SharpJaad.AAC/Syntax/CPE.cs new file mode 100644 index 0000000..4f638c0 --- /dev/null +++ b/SharpJaad.AAC/Syntax/CPE.cs @@ -0,0 +1,95 @@ +using SharpJaad.AAC.Tools; + +namespace SharpJaad.AAC.Syntax +{ + public class CPE : Element + { + private MSMask _msMask; + private bool[] _msUsed; + private bool _commonWindow; + ICStream _icsL, _icsR; + + public CPE(DecoderConfig config) + { + _msUsed = new bool[Constants.MAX_MS_MASK]; + _icsL = new ICStream(config); + _icsR = new ICStream(config); + } + + public void Decode(BitStream input, DecoderConfig conf) + { + Profile profile = conf.GetProfile(); + SampleFrequency sf = conf.GetSampleFrequency(); + if (sf.Equals(SampleFrequency.SAMPLE_FREQUENCY_NONE)) throw new AACException("invalid sample frequency"); + + ReadElementInstanceTag(input); + + _commonWindow = input.ReadBool(); + ICSInfo info = _icsL.GetInfo(); + if (_commonWindow) + { + info.Decode(input, conf, _commonWindow); + _icsR.GetInfo().SetData(input, conf, info); + + _msMask = (MSMask)input.ReadBits(2); + if (_msMask.Equals(MSMask.TYPE_USED)) + { + int maxSFB = info.GetMaxSFB(); + int windowGroupCount = info.GetWindowGroupCount(); + + for (int idx = 0; idx < windowGroupCount * maxSFB; idx++) + { + _msUsed[idx] = input.ReadBool(); + } + } + else if (_msMask.Equals(MSMask.TYPE_ALL_1)) Arrays.Fill(_msUsed, true); + else if (_msMask.Equals(MSMask.TYPE_ALL_0)) Arrays.Fill(_msUsed, false); + else throw new AACException("reserved MS mask type used"); + } + else + { + _msMask = MSMask.TYPE_ALL_0; + Arrays.Fill(_msUsed, false); + } + + if (profile.IsErrorResilientProfile()) + { + LTPrediction ltp = _icsR.GetInfo().GetLTPrediction(); + if (ltp != null) ltp.Decode(input, info, profile); + } + + _icsL.Decode(input, _commonWindow, conf); + _icsR.Decode(input, _commonWindow, conf); + } + + public ICStream GetLeftChannel() + { + return _icsL; + } + + public ICStream GetRightChannel() + { + return _icsR; + } + + public MSMask GetMSMask() + { + return _msMask; + } + + public bool IsMSUsed(int off) + { + return _msUsed[off]; + } + + public bool IsMSMaskPresent() + { + return !_msMask.Equals(MSMask.TYPE_ALL_0); + } + + public bool IsCommonWindow() + { + return _commonWindow; + } + } +} diff --git a/SharpJaad.AAC/Syntax/Constants.cs b/SharpJaad.AAC/Syntax/Constants.cs new file mode 100644 index 0000000..c2929b0 --- /dev/null +++ b/SharpJaad.AAC/Syntax/Constants.cs @@ -0,0 +1,30 @@ +namespace SharpJaad.AAC.Syntax +{ + public class Constants + { + public const int MAX_ELEMENTS = 16; + public const int BYTE_MASK = 0xFF; + public const int MIN_INPUT_SIZE = 768; //6144 bits/channel + //frame length + public const int WINDOW_LEN_LONG = 1024; + public const int WINDOW_LEN_SHORT = WINDOW_LEN_LONG / 8; + public const int WINDOW_SMALL_LEN_LONG = 960; + public const int WINDOW_SMALL_LEN_SHORT = WINDOW_SMALL_LEN_LONG / 8; + //element types + public const int ELEMENT_SCE = 0; + public const int ELEMENT_CPE = 1; + public const int ELEMENT_CCE = 2; + public const int ELEMENT_LFE = 3; + public const int ELEMENT_DSE = 4; + public const int ELEMENT_PCE = 5; + public const int ELEMENT_FIL = 6; + public const int ELEMENT_END = 7; + //maximum numbers + public const int MAX_WINDOW_COUNT = 8; + public const int MAX_WINDOW_GROUP_COUNT = MAX_WINDOW_COUNT; + public const int MAX_LTP_SFB = 40; + public const int MAX_SECTIONS = 120; + public const int MAX_MS_MASK = 128; + public const float SQRT2 = 1.414213562f; + } +} diff --git a/SharpJaad.AAC/Syntax/DSE.cs b/SharpJaad.AAC/Syntax/DSE.cs new file mode 100644 index 0000000..60ae1c9 --- /dev/null +++ b/SharpJaad.AAC/Syntax/DSE.cs @@ -0,0 +1,24 @@ +namespace SharpJaad.AAC.Syntax +{ + public class DSE : Element + { + private byte[] _dataStreamBytes; + + public void Decode(BitStream input) + { + ReadElementInstanceTag(input); + + bool byteAlign = input.ReadBool(); + int count = input.ReadBits(8); + if (count == 255) count += input.ReadBits(8); + + if (byteAlign) input.ByteAlign(); + + _dataStreamBytes = new byte[count]; + for (int i = 0; i < count; i++) + { + _dataStreamBytes[i] = (byte)input.ReadBits(8); + } + } + } +} diff --git a/SharpJaad.AAC/Syntax/Element.cs b/SharpJaad.AAC/Syntax/Element.cs new file mode 100644 index 0000000..ce30ecb --- /dev/null +++ b/SharpJaad.AAC/Syntax/Element.cs @@ -0,0 +1,43 @@ +using SharpJaad.AAC.Sbr; + +namespace SharpJaad.AAC.Syntax +{ + public abstract class Element + { + private int _elementInstanceTag; + private SBR _sbr; + + protected void ReadElementInstanceTag(BitStream input) + { + _elementInstanceTag = input.ReadBits(4); + } + + public int GetElementInstanceTag() + { + return _elementInstanceTag; + } + + public void DecodeSBR(BitStream input, SampleFrequency sf, int count, bool stereo, bool crc, bool downSampled, bool smallFrames) + { + if (_sbr == null) + { + /* implicit SBR signalling, see 4.6.18.2.6 */ + int fq = sf.GetFrequency(); + if (fq < 24000 && !downSampled) + sf = SampleFrequencyExtensions.FromFrequency(2 * fq); + _sbr = new SBR(smallFrames, stereo, sf, downSampled); + } + _sbr.Decode(input, count, crc); + } + + public bool IsSBRPresent() + { + return _sbr != null; + } + + public SBR GetSBR() + { + return _sbr; + } + } +} diff --git a/SharpJaad.AAC/Syntax/FIL.cs b/SharpJaad.AAC/Syntax/FIL.cs new file mode 100644 index 0000000..428c615 --- /dev/null +++ b/SharpJaad.AAC/Syntax/FIL.cs @@ -0,0 +1,173 @@ +namespace SharpJaad.AAC.Syntax +{ + public class FIL + { + public class DynamicRangeInfo + { + public const int MAX_NBR_BANDS = 7; + public bool[] _excludeMask; + public bool[] _additionalExcludedChannels; + public bool _pceTagPresent; + public int _pceInstanceTag; + public int _tagReservedBits; + public bool _excludedChannelsPresent; + public bool _bandsPresent; + public int _bandsIncrement, _interpolationScheme; + public int[] _bandTop; + public bool _progRefLevelPresent; + public int _progRefLevel, _progRefLevelReservedBits; + public bool[] _dynRngSgn; + public int[] _dynRngCtl; + + public DynamicRangeInfo() + { + _excludeMask = new bool[MAX_NBR_BANDS]; + _additionalExcludedChannels = new bool[MAX_NBR_BANDS]; + } + } + + private const int TYPE_FILL = 0; + private const int TYPE_FILL_DATA = 1; + private const int TYPE_EXT_DATA_ELEMENT = 2; + private const int TYPE_DYNAMIC_RANGE = 11; + private const int TYPE_SBR_DATA = 13; + private const int TYPE_SBR_DATA_CRC = 14; + private bool _downSampledSBR; + private DynamicRangeInfo _dri; + + public FIL(bool downSampledSBR) + { + _downSampledSBR = downSampledSBR; + } + + public void Decode(BitStream input, Element prev, SampleFrequency sf, bool sbrEnabled, bool smallFrames) + { + int count = input.ReadBits(4); + if (count == 15) count += input.ReadBits(8) - 1; + count *= 8; //convert to bits + + int cpy = count; + int pos = input.GetPosition(); + + while (count > 0) + { + count = DecodeExtensionPayload(input, count, prev, sf, sbrEnabled, smallFrames); + } + + int pos2 = input.GetPosition() - pos; + int bitsLeft = cpy - pos2; + if (bitsLeft > 0) input.SkipBits(pos2); + else if (bitsLeft < 0) throw new AACException("FIL element overread: " + bitsLeft); + } + + private int DecodeExtensionPayload(BitStream input, int count, Element prev, SampleFrequency sf, bool sbrEnabled, bool smallFrames) + { + int type = input.ReadBits(4); + int ret = count - 4; + switch (type) + { + case TYPE_DYNAMIC_RANGE: + ret = DecodeDynamicRangeInfo(input, ret); + break; + case TYPE_SBR_DATA: + case TYPE_SBR_DATA_CRC: + if (sbrEnabled) + { + if (prev is SCE_LFE || prev is CPE || prev is CCE) + { + prev.DecodeSBR(input, sf, ret, prev is CPE, type == TYPE_SBR_DATA_CRC, _downSampledSBR, smallFrames); + ret = 0; + break; + } + else throw new AACException("SBR applied on unexpected element: " + prev); + } + else + { + input.SkipBits(ret); + ret = 0; + } + break; + case TYPE_FILL: + case TYPE_FILL_DATA: + case TYPE_EXT_DATA_ELEMENT: + default: + input.SkipBits(ret); + ret = 0; + break; + } + return ret; + } + + private int DecodeDynamicRangeInfo(BitStream input, int count) + { + if (_dri == null) _dri = new DynamicRangeInfo(); + int ret = count; + + int bandCount = 1; + + //pce tag + if (_dri._pceTagPresent = input.ReadBool()) + { + _dri._pceInstanceTag = input.ReadBits(4); + _dri._tagReservedBits = input.ReadBits(4); + } + + //excluded channels + if (_dri._excludedChannelsPresent = input.ReadBool()) + { + ret -= DecodeExcludedChannels(input); + } + + //bands + if (_dri._bandsPresent = input.ReadBool()) + { + _dri._bandsIncrement = input.ReadBits(4); + _dri._interpolationScheme = input.ReadBits(4); + ret -= 8; + bandCount += _dri._bandsIncrement; + _dri._bandTop = new int[bandCount]; + for (int i = 0; i < bandCount; i++) + { + _dri._bandTop[i] = input.ReadBits(8); + ret -= 8; + } + } + + //prog ref level + if (_dri._progRefLevelPresent = input.ReadBool()) + { + _dri._progRefLevel = input.ReadBits(7); + _dri._progRefLevelReservedBits = input.ReadBits(1); + ret -= 8; + } + + _dri._dynRngSgn = new bool[bandCount]; + _dri._dynRngCtl = new int[bandCount]; + for (int i = 0; i < bandCount; i++) + { + _dri._dynRngSgn[i] = input.ReadBool(); + _dri._dynRngCtl[i] = input.ReadBits(7); + ret -= 8; + } + return ret; + } + + private int DecodeExcludedChannels(BitStream input) + { + int i; + int exclChs = 0; + + do + { + for (i = 0; i < 7; i++) + { + _dri._excludeMask[exclChs] = input.ReadBool(); + exclChs++; + } + } + while (exclChs < 57 && input.ReadBool()); + + return exclChs / 7 * 8; + } + } +} diff --git a/SharpJaad.AAC/Syntax/ICSInfo.cs b/SharpJaad.AAC/Syntax/ICSInfo.cs new file mode 100644 index 0000000..180abf6 --- /dev/null +++ b/SharpJaad.AAC/Syntax/ICSInfo.cs @@ -0,0 +1,206 @@ +using SharpJaad.AAC.Tools; +using System.Linq; + +namespace SharpJaad.AAC.Syntax +{ + public class ICSInfo + { + public const int WINDOW_SHAPE_SINE = 0; + public const int WINDOW_SHAPE_KAISER = 1; + public const int PREVIOUS = 0; + public const int CURRENT = 1; + + public enum WindowSequence + { + ONLY_LONG_SEQUENCE = 0, + LONG_START_SEQUENCE = 1, + EIGHT_SHORT_SEQUENCE = 2, + LONG_STOP_SEQUENCE = 3 + } + + private int _frameLength; + private WindowSequence _windowSequence; + private int[] _windowShape; + private int _maxSFB; + //prediction + private bool _predictionDataPresent; + private ICPrediction _icPredict; + private LTPrediction _ltPredict; + //windows/sfbs + private int _windowCount; + private int _windowGroupCount; + private int[] _windowGroupLength; + private int _swbCount; + private int[] _swbOffsets; + + public ICSInfo(DecoderConfig config) + { + _frameLength = config.GetFrameLength(); + _windowShape = new int[2]; + _windowSequence = WindowSequence.ONLY_LONG_SEQUENCE; + _windowGroupLength = new int[Constants.MAX_WINDOW_GROUP_COUNT]; + + if (LTPrediction.IsLTPProfile(config.GetProfile())) + _ltPredict = new LTPrediction(_frameLength); + else + _ltPredict = null; + } + + /* ========== decoding ========== */ + public void Decode(BitStream input, DecoderConfig conf, bool commonWindow) + { + SampleFrequency sf = conf.GetSampleFrequency(); + if (sf.Equals(SampleFrequency.SAMPLE_FREQUENCY_NONE)) throw new AACException("invalid sample frequency"); + + input.SkipBit(); //reserved + _windowSequence = (WindowSequence)input.ReadBits(2); + _windowShape[PREVIOUS] = _windowShape[CURRENT]; + _windowShape[CURRENT] = input.ReadBit(); + + _windowGroupCount = 1; + _windowGroupLength[0] = 1; + + if (_windowSequence.Equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) + { + _maxSFB = input.ReadBits(4); + int i; + for (i = 0; i < 7; i++) + { + if (input.ReadBool()) _windowGroupLength[_windowGroupCount - 1]++; + else + { + _windowGroupCount++; + _windowGroupLength[_windowGroupCount - 1] = 1; + } + } + _windowCount = 8; + _swbOffsets = ScaleFactorBands.SWB_OFFSET_SHORT_WINDOW[(int)sf]; + _swbCount = ScaleFactorBands.SWB_SHORT_WINDOW_COUNT[(int)sf]; + } + else + { + _maxSFB = input.ReadBits(6); + _windowCount = 1; + _swbOffsets = ScaleFactorBands.SWB_OFFSET_LONG_WINDOW[(int)sf]; + _swbCount = ScaleFactorBands.SWB_LONG_WINDOW_COUNT[(int)sf]; + _predictionDataPresent = input.ReadBool(); + if (_predictionDataPresent) ReadPredictionData(input, conf.GetProfile(), sf, commonWindow); + } + } + + private void ReadPredictionData(BitStream input, Profile profile, SampleFrequency sf, bool commonWindow) + { + switch (profile) + { + case Profile.AAC_MAIN: + if (_icPredict == null) _icPredict = new ICPrediction(); + _icPredict.Decode(input, _maxSFB, sf); + break; + case Profile.AAC_LTP: + _ltPredict.Decode(input, this, profile); + break; + case Profile.ER_AAC_LTP: + if (!commonWindow) + { + _ltPredict.Decode(input, this, profile); + } + break; + default: + throw new AACException("unexpected profile for LTP: " + profile); + } + } + + /* =========== gets ============ */ + public int GetMaxSFB() + { + return _maxSFB; + } + + public int GetSWBCount() + { + return _swbCount; + } + + public int[] GetSWBOffsets() + { + return _swbOffsets; + } + + public int GetSWBOffsetMax() + { + return _swbOffsets[_swbCount]; + } + + public int GetWindowCount() + { + return _windowCount; + } + + public int GetWindowGroupCount() + { + return _windowGroupCount; + } + + public int GetWindowGroupLength(int g) + { + return _windowGroupLength[g]; + } + + public WindowSequence GetWindowSequence() + { + return _windowSequence; + } + + public bool IsEightShortFrame() + { + return _windowSequence.Equals(WindowSequence.EIGHT_SHORT_SEQUENCE); + } + + public int GetWindowShape(int index) + { + return _windowShape[index]; + } + + public bool IsICPredictionPresent() + { + return _predictionDataPresent; + } + + public ICPrediction GetICPrediction() + { + return _icPredict; + } + + public LTPrediction GetLTPrediction() + { + return _ltPredict; + } + + public void UnsetPredictionSFB(int sfb) + { + if (_predictionDataPresent) _icPredict.SetPredictionUnused(sfb); + if (_ltPredict != null) _ltPredict.SetPredictionUnused(sfb); + } + + public void SetData(BitStream input, DecoderConfig conf, ICSInfo info) + { + _windowSequence = info._windowSequence; + _windowShape[PREVIOUS] = _windowShape[CURRENT]; + _windowShape[CURRENT] = info._windowShape[CURRENT]; + _maxSFB = info._maxSFB; + _predictionDataPresent = info._predictionDataPresent; + if (_predictionDataPresent) _icPredict = info._icPredict; + + _windowCount = info._windowCount; + _windowGroupCount = info._windowGroupCount; + _windowGroupLength = info._windowGroupLength.ToArray(); + _swbCount = info._swbCount; + _swbOffsets = info._swbOffsets.ToArray(); + + if (_predictionDataPresent) + { + _ltPredict.Decode(input, this, conf.GetProfile()); + } + } + } +} diff --git a/SharpJaad.AAC/Syntax/ICStream.cs b/SharpJaad.AAC/Syntax/ICStream.cs new file mode 100644 index 0000000..cf0ab6a --- /dev/null +++ b/SharpJaad.AAC/Syntax/ICStream.cs @@ -0,0 +1,359 @@ +using SharpJaad.AAC.Error; +using SharpJaad.AAC.Gain; +using SharpJaad.AAC.Huffman; +using SharpJaad.AAC.Tools; +using System; + +namespace SharpJaad.AAC.Syntax +{ + public class ICStream + { + private const int SF_DELTA = 60; + private const int SF_OFFSET = 200; + private static int randomState = 0x1F2E3D4C; + private int _frameLength; + //always needed + private ICSInfo _info; + private int[] _sfbCB; + private int[] _sectEnd; + private float[] _data; + private float[] _scaleFactors; + private int _globalGain; + private bool _pulseDataPresent, _tnsDataPresent, _gainControlPresent; + //only allocated if needed + private TNS _tns; + private GainControl _gainControl; + private int[] _pulseOffset, _pulseAmp; + private int _pulseCount; + private int _pulseStartSWB; + //error resilience +#pragma warning disable CS0649 // Field 'ICStream._noiseUsed' is never assigned to, and will always have its default value false + private bool _noiseUsed; +#pragma warning restore CS0649 // Field 'ICStream._noiseUsed' is never assigned to, and will always have its default value false + private int _reorderedSpectralDataLen, _longestCodewordLen; + private RVLC _rvlc; + + public ICStream(DecoderConfig config) + { + _frameLength = config.GetFrameLength(); + _info = new ICSInfo(config); + _sfbCB = new int[Constants.MAX_SECTIONS]; + _sectEnd = new int[Constants.MAX_SECTIONS]; + _data = new float[_frameLength]; + _scaleFactors = new float[Constants.MAX_SECTIONS]; + } + + /* ========= decoding ========== */ + public void Decode(BitStream input, bool commonWindow, DecoderConfig conf) + { + if (conf.IsScalefactorResilienceUsed() && _rvlc == null) _rvlc = new RVLC(); + bool er = conf.GetProfile().IsErrorResilientProfile(); + + _globalGain = input.ReadBits(8); + + if (!commonWindow) _info.Decode(input, conf, commonWindow); + + DecodeSectionData(input, conf.IsSectionDataResilienceUsed()); + + //if(conf.isScalefactorResilienceUsed()) rvlc.decode(in, this, scaleFactors); + /*else*/ + DecodeScaleFactors(input); + + _pulseDataPresent = input.ReadBool(); + if (_pulseDataPresent) + { + if (_info.IsEightShortFrame()) throw new AACException("pulse data not allowed for short frames"); + //LOGGER.log(Level.FINE, "PULSE"); + DecodePulseData(input); + } + + _tnsDataPresent = input.ReadBool(); + if (_tnsDataPresent && !er) + { + if (_tns == null) _tns = new TNS(); + _tns.Decode(input, _info); + } + + _gainControlPresent = input.ReadBool(); + if (_gainControlPresent) + { + if (_gainControl == null) _gainControl = new GainControl(_frameLength); + //LOGGER.log(Level.FINE, "GAIN"); + _gainControl.Decode(input, _info.GetWindowSequence()); + } + + //RVLC spectral data + //if(conf.isScalefactorResilienceUsed()) rvlc.decodeScalefactors(this, in, scaleFactors); + + if (conf.IsSpectralDataResilienceUsed()) + { + int max = conf.GetChannelConfiguration() == ChannelConfiguration.CHANNEL_CONFIG_STEREO ? 6144 : 12288; + _reorderedSpectralDataLen = Math.Max(input.ReadBits(14), max); + _longestCodewordLen = Math.Max(input.ReadBits(6), 49); + //HCR.decodeReorderedSpectralData(this, in, data, conf.isSectionDataResilienceUsed()); + } + else DecodeSpectralData(input); + } + + public void DecodeSectionData(BitStream input, bool sectionDataResilienceUsed) + { + Arrays.Fill(_sfbCB, 0); + Arrays.Fill(_sectEnd, 0); + int bits = _info.IsEightShortFrame() ? 3 : 5; + int escVal = (1 << bits) - 1; + + int windowGroupCount = _info.GetWindowGroupCount(); + int maxSFB = _info.GetMaxSFB(); + + int end, cb, incr; + int idx = 0; + + for (int g = 0; g < windowGroupCount; g++) + { + int k = 0; + while (k < maxSFB) + { + end = k; + cb = input.ReadBits(4); + if (cb == 12) throw new AACException("invalid huffman codebook: 12"); + while ((incr = input.ReadBits(bits)) == escVal) + { + end += incr; + } + end += incr; + if (end > maxSFB) throw new AACException("too many bands: " + end + ", allowed: " + maxSFB); + for (; k < end; k++) + { + _sfbCB[idx] = cb; + _sectEnd[idx++] = end; + } + } + } + } + + private void DecodePulseData(BitStream input) + { + _pulseCount = input.ReadBits(2) + 1; + _pulseStartSWB = input.ReadBits(6); + if (_pulseStartSWB >= _info.GetSWBCount()) throw new AACException("pulse SWB out of range: " + _pulseStartSWB + " > " + _info.GetSWBCount()); + + if (_pulseOffset == null || _pulseCount != _pulseOffset.Length) + { + //only reallocate if needed + _pulseOffset = new int[_pulseCount]; + _pulseAmp = new int[_pulseCount]; + } + + _pulseOffset[0] = _info.GetSWBOffsets()[_pulseStartSWB]; + _pulseOffset[0] += input.ReadBits(5); + _pulseAmp[0] = input.ReadBits(4); + for (int i = 1; i < _pulseCount; i++) + { + _pulseOffset[i] = input.ReadBits(5) + _pulseOffset[i - 1]; + if (_pulseOffset[i] > 1023) throw new AACException("pulse offset out of range: " + _pulseOffset[0]); + _pulseAmp[i] = input.ReadBits(4); + } + } + + public void DecodeScaleFactors(BitStream input) + { + int windowGroups = _info.GetWindowGroupCount(); + int maxSFB = _info.GetMaxSFB(); + //0: spectrum, 1: noise, 2: intensity + int[] offset = { _globalGain, _globalGain - 90, 0 }; + + int tmp; + bool noiseFlag = true; + + int sfb, idx = 0; + for (int g = 0; g < windowGroups; g++) + { + for (sfb = 0; sfb < maxSFB;) + { + int end = _sectEnd[idx]; + switch (_sfbCB[idx]) + { + case HCB.ZERO_HCB: + for (; sfb < end; sfb++, idx++) + { + _scaleFactors[idx] = 0; + } + break; + case HCB.INTENSITY_HCB: + case HCB.INTENSITY_HCB2: + for (; sfb < end; sfb++, idx++) + { + offset[2] += HuffmanDec.DecodeScaleFactor(input) - SF_DELTA; + tmp = Math.Min(Math.Max(offset[2], -155), 100); + _scaleFactors[idx] = ScaleFactorTable.SCALEFACTOR_TABLE[-tmp + SF_OFFSET]; + } + break; + case HCB.NOISE_HCB: + for (; sfb < end; sfb++, idx++) + { + if (noiseFlag) + { + offset[1] += input.ReadBits(9) - 256; + noiseFlag = false; + } + else offset[1] += HuffmanDec.DecodeScaleFactor(input) - SF_DELTA; + tmp = Math.Min(Math.Max(offset[1], -100), 155); + _scaleFactors[idx] = -ScaleFactorTable.SCALEFACTOR_TABLE[tmp + SF_OFFSET]; + } + break; + default: + for (; sfb < end; sfb++, idx++) + { + offset[0] += HuffmanDec.DecodeScaleFactor(input) - SF_DELTA; + if (offset[0] > 255) throw new AACException("scalefactor out of range: " + offset[0]); + _scaleFactors[idx] = ScaleFactorTable.SCALEFACTOR_TABLE[offset[0] - 100 + SF_OFFSET]; + } + break; + } + } + } + } + + private void DecodeSpectralData(BitStream input) + { + Arrays.Fill(_data, 0); + int maxSFB = _info.GetMaxSFB(); + int windowGroups = _info.GetWindowGroupCount(); + int[] offsets = _info.GetSWBOffsets(); + int[] buf = new int[4]; + + int sfb, j, k, w, hcb, off, width, num; + int groupOff = 0, idx = 0; + for (int g = 0; g < windowGroups; g++) + { + int groupLen = _info.GetWindowGroupLength(g); + + for (sfb = 0; sfb < maxSFB; sfb++, idx++) + { + hcb = _sfbCB[idx]; + off = groupOff + offsets[sfb]; + width = offsets[sfb + 1] - offsets[sfb]; + if (hcb == HCB.ZERO_HCB || hcb == HCB.INTENSITY_HCB || hcb == HCB.INTENSITY_HCB2) + { + for (w = 0; w < groupLen; w++, off += 128) + { + Arrays.Fill(_data, off, off + width, 0); + } + } + else if (hcb == HCB.NOISE_HCB) + { + //apply PNS: fill with random values + for (w = 0; w < groupLen; w++, off += 128) + { + float energy = 0; + + for (k = 0; k < width; k++) + { + randomState = 1664525 * randomState + 1013904223; + _data[off + k] = randomState; + energy += _data[off + k] * _data[off + k]; + } + + float scale = (float)(_scaleFactors[idx] / Math.Sqrt(energy)); + for (k = 0; k < width; k++) + { + _data[off + k] *= scale; + } + } + } + else + { + for (w = 0; w < groupLen; w++, off += 128) + { + num = hcb >= HCB.FIRST_PAIR_HCB ? 2 : 4; + for (k = 0; k < width; k += num) + { + HuffmanDec.DecodeSpectralData(input, hcb, buf, 0); + + //inverse quantization & scaling + for (j = 0; j < num; j++) + { + _data[off + k + j] = buf[j] > 0 ? IQTable.IQ_TABLE[buf[j]] : -IQTable.IQ_TABLE[-buf[j]]; + _data[off + k + j] *= _scaleFactors[idx]; + } + } + } + } + } + groupOff += groupLen << 7; + } + } + + /* =========== gets ============ */ + /** + * Does inverse quantization and applies the scale factors on the decoded + * data. After this the noiseless decoding is finished and the decoded data + * is returned. + * @return the inverse quantized and scaled data + */ + public float[] GetInvQuantData() + { + return _data; + } + + public ICSInfo GetInfo() + { + return _info; + } + + public int[] GetSectEnd() + { + return _sectEnd; + } + + public int[] getSfbCB() + { + return _sfbCB; + } + + public float[] GetScaleFactors() + { + return _scaleFactors; + } + + public bool IsTNSDataPresent() + { + return _tnsDataPresent; + } + + public TNS GetTNS() + { + return _tns; + } + + public int GetGlobalGain() + { + return _globalGain; + } + + public bool IsNoiseUsed() + { + return _noiseUsed; + } + + public int GetLongestCodewordLength() + { + return _longestCodewordLen; + } + + public int GetReorderedSpectralDataLength() + { + return _reorderedSpectralDataLen; + } + + public bool IsGainControlPresent() + { + return _gainControlPresent; + } + + public GainControl GetGainControl() + { + return _gainControl; + } + } +} diff --git a/SharpJaad.AAC/Syntax/IQTable.cs b/SharpJaad.AAC/Syntax/IQTable.cs new file mode 100644 index 0000000..c1d4942 --- /dev/null +++ b/SharpJaad.AAC/Syntax/IQTable.cs @@ -0,0 +1,8199 @@ +namespace SharpJaad.AAC.Syntax +{ + public static class IQTable + { + public static float[] IQ_TABLE = { + 0.0f, + 1.0f, + 2.519842099789746f, + 4.3267487109222245f, + 6.3496042078727974f, + 8.549879733383484f, + 10.902723556992836f, + 13.390518279406722f, + 15.999999999999998f, + 18.720754407467133f, + 21.544346900318835f, + 24.463780996262464f, + 27.47314182127996f, + 30.567350940369842f, + 33.74199169845321f, + 36.993181114957046f, + 40.317473596635935f, + 43.71178704118999f, + 47.173345095760126f, + 50.69963132571694f, + 54.28835233189812f, + 57.93740770400352f, + 61.6448652744185f, + 65.40894053658599f, + 69.22797937475559f, + 73.10044345532164f, + 77.02489777859162f, + 80.99999999999997f, + 85.02449121251853f, + 89.09718794488955f, + 93.21697517861574f, + 97.38280022413316f, + 101.59366732596474f, + 105.84863288986224f, + 110.14680124343441f, + 114.4873208566006f, + 118.86938096020653f, + 123.29220851090024f, + 127.75506545836058f, + 132.25724627755247f, + 136.79807573413572f, + 141.3769068556919f, + 145.99311908523086f, + 150.6461165966291f, + 155.33532675434674f, + 160.0601987020528f, + 164.8202020667335f, + 169.6148257665186f, + 174.44357691188537f, + 179.30597979112554f, + 184.20157493201927f, + 189.12991823257562f, + 194.09058015449685f, + 199.08314497371674f, + 204.1072100829694f, + 209.16238534187647f, + 214.24829247050752f, + 219.36456448277784f, + 224.51084515641213f, + 229.6867885365223f, + 234.89205847013176f, + 240.1263281692325f, + 245.38927980018508f, + 250.6806040974726f, + 255.99999999999991f, + 261.3471743082887f, + 266.7218413610645f, + 272.12372272986045f, + 277.5525469303796f, + 283.0080491494619f, + 288.4899709865989f, + 293.99806020902247f, + 299.5320705194741f, + 305.0917613358298f, + 310.67689758182206f, + 316.28724948815585f, + 321.92259240337177f, + 327.58270661385535f, + 333.2673771724374f, + 338.97639373507025f, + 344.70955040510125f, + 350.46664558470013f, + 356.2474818330261f, + 362.0518657307514f, + 367.8796077505826f, + 373.7305221334451f, + 379.60442677002084f, + 385.501143087346f, + 391.42049594019943f, + 397.3623135070237f, + 403.32642719014467f, + 409.3126715200626f, + 415.320884063608f, + 421.35090533576465f, + 427.4025787149762f, + 433.4757503617617f, + 439.5702691404793f, + 445.6859865440827f, + 451.8227566217276f, + 457.9804359090913f, + 464.15888336127773f, + 470.35796028818726f, + 476.5775302922363f, + 482.81745920832043f, + 489.0776150459174f, + 495.3578679332358f, + 501.6580900633169f, + 507.9781556420037f, + 514.3179408376965f, + 520.6773237328167f, + 527.056184276906f, + 533.4544042412917f, + 539.8718671752513f, + 546.308458363615f, + 552.7640647857461f, + 559.2385750758419f, + 565.7318794845041f, + 572.2438698415234f, + 578.7744395198338f, + 585.3234834005884f, + 591.8908978393126f, + 598.4765806330926f, + 605.0804309887604f, + 611.7023494920364f, + 618.3422380775919f, + 624.9999999999998f, + 631.6755398055375f, + 638.3687633048116f, + 645.0795775461748f, + 651.8078907899041f, + 658.553612483115f, + 665.3166532353836f, + 672.0969247950522f, + 678.8943400261944f, + 685.7088128862142f, + 692.540258404062f, + 699.3885926590398f, + 706.2537327601806f, + 713.1355968261797f, + 720.0341039658604f, + 726.9491742591542f, + 733.8807287385821f, + 740.8286893712154f, + 747.7929790411054f, + 754.7735215321619f, + 761.7702415114704f, + 768.7830645130296f, + 775.811916921899f, + 782.8567259587425f, + 789.9174196647544f, + 796.993926886958f, + 804.0861772638627f, + 811.194101211471f, + 818.3176299096223f, + 825.4566952886656f, + 832.6112300164486f, + 839.781167485616f, + 846.9664418012055f, + 854.1669877685351f, + 861.3827408813713f, + 868.6136373103698f, + 875.859613891782f, + 883.1206081164196f, + 890.3965581188676f, + 897.6874026669418f, + 904.9930811513817f, + 912.3135335757719f, + 919.6487005466876f, + 926.9985232640562f, + 934.3629435117291f, + 941.7419036482586f, + 949.1353465978742f, + 956.5432158416521f, + 963.9654554088735f, + 971.4020098685654f, + 978.8528243212218f, + 986.3178443906959f, + 993.7970162162635f, + 1001.29028644485f, + 1008.797602223418f, + 1016.3189111915103f, + 1023.8541614739464f, + 1031.4033016736653f, + 1038.9662808647138f, + 1046.5430485853758f, + 1054.1335548314366f, + 1061.7377500495838f, + 1069.3555851309357f, + 1076.9870114046978f, + 1084.6319806319443f, + 1092.2904449995174f, + 1099.9623571140482f, + 1107.6476699960892f, + 1115.3463370743607f, + 1123.058312180106f, + 1130.783549541554f, + 1138.5220037784857f, + 1146.273629896901f, + 1154.0383832837879f, + 1161.816219701986f, + 1169.607095285146f, + 1177.4109665327805f, + 1185.2277903054078f, + 1193.0575238197798f, + 1200.9001246442f, + 1208.7555506939248f, + 1216.6237602266442f, + 1224.5047118380478f, + 1232.3983644574657f, + 1240.3046773435874f, + 1248.2236100802568f, + 1256.1551225723395f, + 1264.099175041662f, + 1272.0557280230228f, + 1280.024742360269f, + 1288.0061792024444f, + 1295.9999999999995f, + 1304.006166501068f, + 1312.0246407478062f, + 1320.055385072793f, + 1328.0983620954903f, + 1336.153534718765f, + 1344.2208661254647f, + 1352.3003197750522f, + 1360.3918594002962f, + 1368.4954490040145f, + 1376.6110528558709f, + 1384.7386354892244f, + 1392.8781616980295f, + 1401.0295965337855f, + 1409.1929053025353f, + 1417.368053561912f, + 1425.5550071182324f, + 1433.7537320236374f, + 1441.9641945732744f, + 1450.186361302528f, + 1458.4201989842913f, + 1466.6656746262797f, + 1474.9227554683875f, + 1483.1914089800841f, + 1491.4716028578516f, + 1499.7633050226596f, + 1508.0664836174794f, + 1516.3811070048375f, + 1524.7071437644029f, + 1533.044562690613f, + 1541.3933327903342f, + 1549.753423280558f, + 1558.1248035861304f, + 1566.507443337515f, + 1574.9013123685909f, + 1583.3063807144795f, + 1591.722618609407f, + 1600.149996484594f, + 1608.58848496618f, + 1617.0380548731737f, + 1625.4986772154357f, + 1633.9703231916887f, + 1642.4529641875577f, + 1650.9465717736346f, + 1659.4511177035752f, + 1667.9665739122186f, + 1676.4929125137353f, + 1685.0301057998013f, + 1693.5781262377957f, + 1702.136946469027f, + 1710.7065393069795f, + 1719.286877735588f, + 1727.8779349075323f, + 1736.4796841425596f, + 1745.0920989258252f, + 1753.7151529062583f, + 1762.3488198949503f, + 1770.993073863563f, + 1779.6478889427597f, + 1788.3132394206564f, + 1796.9890997412947f, + 1805.6754445031333f, + 1814.3722484575621f, + 1823.0794865074322f, + 1831.7971337056094f, + 1840.5251652535437f, + 1849.263556499858f, + 1858.0122829389563f, + 1866.7713202096493f, + 1875.5406440937966f, + 1884.3202305149687f, + 1893.110055537124f, + 1901.9100953633042f, + 1910.7203263343454f, + 1919.5407249276057f, + 1928.3712677557098f, + 1937.2119315653083f, + 1946.0626932358525f, + 1954.923529778386f, + 1963.79441833435f, + 1972.6753361744036f, + 1981.5662606972594f, + 1990.467169428533f, + 1999.378040019607f, + 2008.2988502465078f, + 2017.2295780087982f, + 2026.1702013284819f, + 2035.1206983489212f, + 2044.0810473337688f, + 2053.0512266659125f, + 2062.031214846431f, + 2071.0209904935646f, + 2080.020532341696f, + 2089.0298192403443f, + 2098.0488301531714f, + 2107.0775441569995f, + 2116.115940440839f, + 2125.1639983049317f, + 2134.2216971597995f, + 2143.2890165253098f, + 2152.3659360297484f, + 2161.452435408903f, + 2170.5484945051617f, + 2179.6540932666144f, + 2188.769211746171f, + 2197.893830100689f, + 2207.0279285901042f, + 2216.171487576584f, + 2225.324487523676f, + 2234.486908995478f, + 2243.65873265581f, + 2252.839939267398f, + 2262.03050969107f, + 2271.2304248849537f, + 2280.4396659036897f, + 2289.6582138976523f, + 2298.8860501121762f, + 2308.1231558867926f, + 2317.3695126544767f, + 2326.6251019409005f, + 2335.8899053636933f, + 2345.163904631713f, + 2354.4470815443233f, + 2363.739417990679f, + 2373.0408959490205f, + 2382.351497485973f, + 2391.671204755856f, + 2400.999999999999f, + 2410.337865546065f, + 2419.6847838073813f, + 2429.0407372822747f, + 2438.405708553419f, + 2447.779680287186f, + 2457.162635233001f, + 2466.554556222711f, + 2475.955426169957f, + 2485.3652280695474f, + 2494.7839449968487f, + 2504.2115601071737f, + 2513.648056635179f, + 2523.0934178942675f, + 2532.5476272760025f, + 2542.010668249519f, + 2551.482524360948f, + 2560.963179232844f, + 2570.4526165636184f, + 2579.950820126979f, + 2589.4577737713744f, + 2598.973461419446f, + 2608.4978670674823f, + 2618.0309747848837f, + 2627.572768713626f, + 2637.1232330677353f, + 2646.6823521327647f, + 2656.250110265277f, + 2665.826491892333f, + 2675.4114815109842f, + 2685.0050636877722f, + 2694.6072230582295f, + 2704.2179443263894f, + 2713.8372122642972f, + 2723.465011711528f, + 2733.1013275747096f, + 2742.7461448270483f, + 2752.39944850786f, + 2762.0612237221085f, + 2771.731455639942f, + 2781.4101294962406f, + 2791.097230590165f, + 2800.7927442847094f, + 2810.496656006259f, + 2820.208951244152f, + 2829.9296155502466f, + 2839.6586345384894f, + 2849.395993884492f, + 2859.1416793251065f, + 2868.8956766580086f, + 2878.6579717412847f, + 2888.4285504930212f, + 2898.2073988908974f, + 2907.9945029717837f, + 2917.789848831344f, + 2927.5934226236377f, + 2937.4052105607307f, + 2947.225198912308f, + 2957.053374005286f, + 2966.8897222234364f, + 2976.734230007005f, + 2986.5868838523397f, + 2996.4476703115197f, + 3006.316575991989f, + 3016.193587556191f, + 3026.078691721209f, + 3035.971875258411f, + 3045.8731249930906f, + 3055.7824278041207f, + 3065.699770623604f, + 3075.6251404365285f, + 3085.5585242804245f, + 3095.49990924503f, + 3105.449282471949f, + 3115.4066311543256f, + 3125.371942536509f, + 3135.3452039137287f, + 3145.3264026317715f, + 3155.3155260866592f, + 3165.3125617243295f, + 3175.3174970403234f, + 3185.330319579468f, + 3195.35101693557f, + 3205.379576751108f, + 3215.4159867169246f, + 3225.460234571929f, + 3235.5123081027928f, + 3245.572195143656f, + 3255.63988357583f, + 3265.7153613275095f, + 3275.7986163734795f, + 3285.889636734829f, + 3295.9884104786665f, + 3306.0949257178395f, + 3316.2091706106517f, + 3326.331133360588f, + 3336.4608022160382f, + 3346.598165470023f, + 3356.7432114599264f, + 3366.8959285672245f, + 3377.056305217221f, + 3387.2243298787826f, + 3397.3999910640764f, + 3407.5832773283128f, + 3417.7741772694862f, + 3427.9726795281194f, + 3438.1787727870123f, + 3448.3924457709873f, + 3458.6136872466445f, + 3468.842486022111f, + 3479.0788309467976f, + 3489.3227109111554f, + 3499.5741148464344f, + 3509.8330317244445f, + 3520.0994505573185f, + 3530.373360397275f, + 3540.6547503363886f, + 3550.9436095063534f, + 3561.239927078258f, + 3571.543692262354f, + 3581.854894307831f, + 3592.1735225025936f, + 3602.4995661730372f, + 3612.8330146838275f, + 3623.1738574376814f, + 3633.52208387515f, + 3643.877683474403f, + 3654.240645751014f, + 3664.6109602577494f, + 3674.9886165843564f, + 3685.3736043573545f, + 3695.7659132398294f, + 3706.165532931225f, + 3716.57245316714f, + 3726.986663719126f, + 3737.4081543944876f, + 3747.836915036078f, + 3758.272935522107f, + 3768.716205765941f, + 3779.1667157159077f, + 3789.6244553551055f, + 3800.089414701208f, + 3810.5615838062768f, + 3821.0409527565694f, + 3831.5275116723533f, + 3842.0212507077194f, + 3852.522160050396f, + 3863.0302299215673f, + 3873.5454505756893f, + 3884.067812300311f, + 3894.597305415892f, + 3905.1339202756285f, + 3915.677647265273f, + 3926.2284768029604f, + 3936.786399339034f, + 3947.3514053558706f, + 3957.9234853677135f, + 3968.502629920497f, + 3979.0888295916798f, + 3989.6820749900776f, + 4000.2823567556948f, + 4010.8896655595613f, + 4021.5039921035655f, + 4032.1253271202945f, + 4042.7536613728694f, + 4053.3889856547858f, + 4064.031290789755f, + 4074.680567631545f, + 4085.336807063822f, + 4095.999999999998f, + 4106.670137383071f, + 4117.347210185475f, + 4128.031209408926f, + 4138.722126084268f, + 4149.419951271327f, + 4160.124676058758f, + 4170.836291563898f, + 4181.554788932618f, + 4192.280159339177f, + 4203.012393986074f, + 4213.75148410391f, + 4224.497420951238f, + 4235.250195814426f, + 4246.0098000075095f, + 4256.776224872057f, + 4267.549461777031f, + 4278.329502118642f, + 4289.11633732022f, + 4299.909958832071f, + 4310.7103581313495f, + 4321.517526721914f, + 4332.3314561342f, + 4343.152137925088f, + 4353.979563677767f, + 4364.813725001605f, + 4375.654613532022f, + 4386.502220930359f, + 4397.356538883747f, + 4408.217559104982f, + 4419.085273332402f, + 4429.959673329753f, + 4440.840750886073f, + 4451.72849781556f, + 4462.622905957458f, + 4473.523967175923f, + 4484.431673359913f, + 4495.346016423058f, + 4506.26698830355f, + 4517.194580964012f, + 4528.128786391389f, + 4539.069596596828f, + 4550.017003615559f, + 4560.970999506781f, + 4571.931576353546f, + 4582.898726262647f, + 4593.8724413645f, + 4604.852713813035f, + 4615.839535785582f, + 4626.832899482757f, + 4637.832797128359f, + 4648.839220969251f, + 4659.852163275256f, + 4670.871616339047f, + 4681.897572476039f, + 4692.930024024284f, + 4703.9689633443595f, + 4715.014382819267f, + 4726.0662748543255f, + 4737.124631877068f, + 4748.189446337137f, + 4759.26071070618f, + 4770.338417477749f, + 4781.422559167199f, + 4792.513128311585f, + 4803.610117469561f, + 4814.713519221285f, + 4825.823326168315f, + 4836.93953093351f, + 4848.062126160935f, + 4859.191104515763f, + 4870.326458684178f, + 4881.468181373277f, + 4892.616265310977f, + 4903.770703245919f, + 4914.931487947375f, + 4926.098612205151f, + 4937.272068829496f, + 4948.451850651012f, + 4959.637950520555f, + 4970.830361309152f, + 4982.029075907904f, + 4993.234087227897f, + 5004.445388200115f, + 5015.662971775347f, + 5026.886830924101f, + 5038.116958636513f, + 5049.353347922266f, + 5060.595991810493f, + 5071.8448833497005f, + 5083.100015607673f, + 5094.3613816714f, + 5105.628974646975f, + 5116.902787659525f, + 5128.18281385312f, + 5139.469046390692f, + 5150.761478453947f, + 5162.060103243293f, + 5173.364913977747f, + 5184.675903894859f, + 5195.993066250632f, + 5207.316394319439f, + 5218.645881393944f, + 5229.981520785022f, + 5241.323305821685f, + 5252.671229850992f, + 5264.025286237983f, + 5275.385468365595f, + 5286.751769634588f, + 5298.124183463464f, + 5309.502703288395f, + 5320.887322563145f, + 5332.278034758998f, + 5343.674833364676f, + 5355.077711886272f, + 5366.486663847172f, + 5377.901682787985f, + 5389.3227622664635f, + 5400.749895857437f, + 5412.183077152737f, + 5423.622299761123f, + 5435.067557308219f, + 5446.518843436432f, + 5457.976151804887f, + 5469.439476089359f, + 5480.908809982197f, + 5492.384147192261f, + 5503.8654814448455f, + 5515.35280648162f, + 5526.846116060552f, + 5538.345403955847f, + 5549.850663957874f, + 5561.361889873103f, + 5572.879075524037f, + 5584.402214749145f, + 5595.9313014027975f, + 5607.466329355201f, + 5619.00729249233f, + 5630.554184715866f, + 5642.106999943128f, + 5653.665732107017f, + 5665.230375155943f, + 5676.8009230537655f, + 5688.377369779733f, + 5699.959709328416f, + 5711.547935709647f, + 5723.142042948459f, + 5734.742025085021f, + 5746.347876174581f, + 5757.959590287402f, + 5769.577161508701f, + 5781.200583938591f, + 5792.829851692021f, + 5804.464958898715f, + 5816.1058997031105f, + 5827.7526682643065f, + 5839.405258755998f, + 5851.06366536642f, + 5862.727882298291f, + 5874.397903768755f, + 5886.07372400932f, + 5897.755337265809f, + 5909.442737798296f, + 5921.135919881051f, + 5932.834877802487f, + 5944.539605865103f, + 5956.250098385426f, + 5967.966349693957f, + 5979.688354135121f, + 5991.416106067203f, + 6003.1495998623f, + 6014.888829906269f, + 6026.6337905986675f, + 6038.384476352703f, + 6050.140881595178f, + 6061.903000766441f, + 6073.670828320332f, + 6085.444358724127f, + 6097.223586458489f, + 6109.00850601742f, + 6120.7991119082f, + 6132.595398651345f, + 6144.397360780552f, + 6156.204992842646f, + 6168.018289397536f, + 6179.837245018158f, + 6191.661854290431f, + 6203.492111813202f, + 6215.3280121982025f, + 6227.1695500699925f, + 6239.01672006592f, + 6250.869516836063f, + 6262.727935043189f, + 6274.591969362706f, + 6286.461614482607f, + 6298.3368651034325f, + 6310.217715938217f, + 6322.104161712446f, + 6333.996197164003f, + 6345.893817043131f, + 6357.7970161123785f, + 6369.705789146558f, + 6381.620130932701f, + 6393.5400362700075f, + 6405.465499969803f, + 6417.396516855498f, + 6429.333081762534f, + 6441.275189538345f, + 6453.222835042314f, + 6465.176013145724f, + 6477.134718731716f, + 6489.098946695247f, + 6501.0686919430445f, + 6513.043949393563f, + 6525.024713976942f, + 6537.010980634961f, + 6549.002744321001f, + 6560.999999999996f, + 6573.002742648398f, + 6585.010967254128f, + 6597.024668816537f, + 6609.043842346366f, + 6621.0684828657f, + 6633.098585407935f, + 6645.134145017727f, + 6657.175156750956f, + 6669.221615674691f, + 6681.273516867135f, + 6693.3308554176f, + 6705.393626426459f, + 6717.461825005108f, + 6729.535446275926f, + 6741.614485372234f, + 6753.69893743826f, + 6765.788797629097f, + 6777.884061110663f, + 6789.984723059666f, + 6802.090778663563f, + 6814.20222312052f, + 6826.31905163938f, + 6838.441259439618f, + 6850.568841751307f, + 6862.701793815083f, + 6874.8401108821f, + 6886.983788213999f, + 6899.132821082872f, + 6911.287204771221f, + 6923.44693457192f, + 6935.612005788186f, + 6947.7824137335365f, + 6959.958153731754f, + 6972.139221116853f, + 6984.325611233041f, + 6996.517319434686f, + 7008.714341086277f, + 7020.916671562394f, + 7033.124306247668f, + 7045.337240536748f, + 7057.555469834268f, + 7069.77898955481f, + 7082.007795122871f, + 7094.241881972827f, + 7106.481245548902f, + 7118.7258813051285f, + 7130.975784705322f, + 7143.23095122304f, + 7155.491376341552f, + 7167.757055553804f, + 7180.027984362389f, + 7192.304158279513f, + 7204.585572826957f, + 7216.872223536052f, + 7229.164105947641f, + 7241.461215612049f, + 7253.76354808905f, + 7266.0710989478375f, + 7278.383863766987f, + 7290.70183813443f, + 7303.025017647417f, + 7315.353397912493f, + 7327.68697454546f, + 7340.025743171346f, + 7352.36969942438f, + 7364.718838947954f, + 7377.073157394597f, + 7389.432650425941f, + 7401.797313712694f, + 7414.167142934606f, + 7426.542133780443f, + 7438.922281947951f, + 7451.307583143835f, + 7463.698033083718f, + 7476.093627492121f, + 7488.49436210243f, + 7500.900232656865f, + 7513.311234906452f, + 7525.727364610994f, + 7538.148617539045f, + 7550.574989467873f, + 7563.006476183442f, + 7575.443073480374f, + 7587.884777161926f, + 7600.33158303996f, + 7612.783486934915f, + 7625.24048467578f, + 7637.702572100064f, + 7650.169745053768f, + 7662.64199939136f, + 7675.119330975745f, + 7687.60173567824f, + 7700.089209378544f, + 7712.581747964711f, + 7725.079347333125f, + 7737.582003388473f, + 7750.089712043714f, + 7762.602469220058f, + 7775.1202708469355f, + 7787.643112861973f, + 7800.1709912109645f, + 7812.703901847848f, + 7825.241840734677f, + 7837.784803841597f, + 7850.3327871468155f, + 7862.885786636581f, + 7875.443798305154f, + 7888.006818154784f, + 7900.57484219568f, + 7913.14786644599f, + 7925.725886931772f, + 7938.308899686972f, + 7950.896900753395f, + 7963.489886180685f, + 7976.087852026296f, + 7988.690794355469f, + 8001.298709241209f, + 8013.911592764257f, + 8026.529441013069f, + 8039.152250083789f, + 8051.780016080227f, + 8064.412735113835f, + 8077.05040330368f, + 8089.693016776422f, + 8102.340571666295f, + 8114.993064115073f, + 8127.650490272057f, + 8140.312846294045f, + 8152.98012834531f, + 8165.652332597579f, + 8178.329455230005f, + 8191.011492429153f, + 8203.698440388966f, + 8216.390295310746f, + 8229.087053403142f, + 8241.788710882107f, + 8254.495263970894f, + 8267.206708900021f, + 8279.923041907257f, + 8292.644259237595f, + 8305.37035714323f, + 8318.101331883543f, + 8330.837179725066f, + 8343.577896941475f, + 8356.323479813558f, + 8369.073924629198f, + 8381.82922768335f, + 8394.589385278021f, + 8407.354393722242f, + 8420.124249332057f, + 8432.898948430495f, + 8445.67848734755f, + 8458.462862420158f, + 8471.25206999218f, + 8484.046106414384f, + 8496.844968044408f, + 8509.648651246764f, + 8522.457152392795f, + 8535.270467860666f, + 8548.088594035344f, + 8560.911527308566f, + 8573.73926407884f, + 8586.5718007514f, + 8599.409133738207f, + 8612.251259457915f, + 8625.098174335855f, + 8637.94987480402f, + 8650.806357301039f, + 8663.667618272157f, + 8676.533654169225f, + 8689.404461450664f, + 8702.28003658146f, + 8715.160376033142f, + 8728.04547628375f, + 8740.935333817839f, + 8753.829945126436f, + 8766.729306707033f, + 8779.633415063572f, + 8792.542266706416f, + 8805.455858152332f, + 8818.374185924482f, + 8831.29724655239f, + 8844.225036571936f, + 8857.157552525327f, + 8870.094790961084f, + 8883.03674843403f, + 8895.983421505252f, + 8908.934806742107f, + 8921.890900718185f, + 8934.8517000133f, + 8947.817201213471f, + 8960.7874009109f, + 8973.76229570396f, + 8986.741882197173f, + 8999.726157001192f, + 9012.715116732788f, + 9025.708758014824f, + 9038.707077476247f, + 9051.710071752064f, + 9064.717737483328f, + 9077.730071317117f, + 9090.747069906518f, + 9103.768729910615f, + 9116.795047994465f, + 9129.826020829081f, + 9142.861645091423f, + 9155.901917464373f, + 9168.946834636716f, + 9181.996393303136f, + 9195.050590164185f, + 9208.109421926274f, + 9221.172885301656f, + 9234.240977008405f, + 9247.313693770408f, + 9260.391032317339f, + 9273.472989384647f, + 9286.559561713542f, + 9299.650746050975f, + 9312.74653914962f, + 9325.84693776787f, + 9338.951938669801f, + 9352.061538625176f, + 9365.175734409413f, + 9378.294522803584f, + 9391.417900594384f, + 9404.545864574127f, + 9417.678411540726f, + 9430.815538297675f, + 9443.957241654036f, + 9457.103518424428f, + 9470.254365429f, + 9483.40977949343f, + 9496.569757448893f, + 9509.734296132066f, + 9522.903392385091f, + 9536.07704305558f, + 9549.255244996582f, + 9562.437995066583f, + 9575.62529012948f, + 9588.817127054574f, + 9602.013502716549f, + 9615.214413995463f, + 9628.419857776727f, + 9641.629830951093f, + 9654.844330414644f, + 9668.063353068772f, + 9681.286895820167f, + 9694.514955580802f, + 9707.74752926792f, + 9720.984613804016f, + 9734.226206116828f, + 9747.472303139319f, + 9760.722901809664f, + 9773.977999071232f, + 9787.237591872581f, + 9800.501677167433f, + 9813.77025191467f, + 9827.04331307831f, + 9840.320857627503f, + 9853.602882536512f, + 9866.8893847847f, + 9880.18036135651f, + 9893.475809241469f, + 9906.775725434152f, + 9920.080106934185f, + 9933.388950746223f, + 9946.702253879943f, + 9960.020013350022f, + 9973.34222617613f, + 9986.668889382916f, + 9999.999999999995f, + 10013.335555061929f, + 10026.675551608221f, + 10040.0199866833f, + 10053.368857336509f, + 10066.722160622081f, + 10080.079893599144f, + 10093.442053331697f, + 10106.808636888598f, + 10120.17964134355f, + 10133.555063775097f, + 10146.934901266595f, + 10160.31915090622f, + 10173.707809786936f, + 10187.100875006496f, + 10200.498343667417f, + 10213.900212876984f, + 10227.306479747222f, + 10240.717141394889f, + 10254.132194941467f, + 10267.551637513146f, + 10280.975466240814f, + 10294.40367826004f, + 10307.836270711065f, + 10321.273240738796f, + 10334.71458549278f, + 10348.160302127204f, + 10361.610387800878f, + 10375.064839677221f, + 10388.523654924258f, + 10401.986830714592f, + 10415.454364225412f, + 10428.926252638465f, + 10442.402493140049f, + 10455.883082921007f, + 10469.368019176709f, + 10482.85729910704f, + 10496.350919916393f, + 10509.848878813653f, + 10523.351173012188f, + 10536.857799729838f, + 10550.368756188902f, + 10563.884039616121f, + 10577.403647242685f, + 10590.927576304197f, + 10604.455824040679f, + 10617.988387696556f, + 10631.525264520642f, + 10645.066451766135f, + 10658.611946690598f, + 10672.161746555956f, + 10685.715848628475f, + 10699.274250178762f, + 10712.836948481747f, + 10726.403940816675f, + 10739.97522446709f, + 10753.550796720836f, + 10767.130654870027f, + 10780.714796211058f, + 10794.303218044579f, + 10807.895917675487f, + 10821.492892412922f, + 10835.094139570248f, + 10848.699656465047f, + 10862.309440419107f, + 10875.923488758415f, + 10889.541798813138f, + 10903.16436791762f, + 10916.791193410372f, + 10930.422272634056f, + 10944.05760293548f, + 10957.697181665582f, + 10971.341006179427f, + 10984.98907383619f, + 10998.641381999149f, + 11012.297928035676f, + 11025.958709317223f, + 11039.623723219316f, + 11053.292967121542f, + 11066.96643840754f, + 11080.64413446499f, + 11094.326052685608f, + 11108.012190465128f, + 11121.702545203298f, + 11135.397114303863f, + 11149.09589517457f, + 11162.798885227143f, + 11176.506081877276f, + 11190.217482544635f, + 11203.933084652828f, + 11217.652885629415f, + 11231.376882905886f, + 11245.105073917659f, + 11258.837456104062f, + 11272.574026908333f, + 11286.314783777601f, + 11300.059724162888f, + 11313.808845519083f, + 11327.56214530495f, + 11341.319620983111f, + 11355.081270020033f, + 11368.847089886023f, + 11382.617078055218f, + 11396.391232005579f, + 11410.169549218874f, + 11423.952027180676f, + 11437.738663380347f, + 11451.529455311042f, + 11465.324400469679f, + 11479.123496356951f, + 11492.926740477304f, + 11506.734130338931f, + 11520.545663453764f, + 11534.361337337468f, + 11548.181149509423f, + 11562.005097492724f, + 11575.83317881417f, + 11589.665391004253f, + 11603.501731597149f, + 11617.342198130715f, + 11631.186788146468f, + 11645.03549918959f, + 11658.888328808911f, + 11672.745274556906f, + 11686.606333989675f, + 11700.471504666955f, + 11714.340784152086f, + 11728.214170012021f, + 11742.091659817312f, + 11755.9732511421f, + 11769.85894156411f, + 11783.748728664636f, + 11797.642610028539f, + 11811.540583244237f, + 11825.442645903695f, + 11839.34879560242f, + 11853.259029939445f, + 11867.173346517331f, + 11881.091742942153f, + 11895.014216823492f, + 11908.940765774427f, + 11922.871387411526f, + 11936.80607935484f, + 11950.744839227897f, + 11964.687664657684f, + 11978.634553274653f, + 11992.5855027127f, + 12006.540510609168f, + 12020.499574604826f, + 12034.462692343877f, + 12048.429861473938f, + 12062.401079646032f, + 12076.376344514589f, + 12090.355653737432f, + 12104.339004975769f, + 12118.326395894186f, + 12132.317824160644f, + 12146.313287446457f, + 12160.312783426303f, + 12174.316309778205f, + 12188.323864183525f, + 12202.335444326955f, + 12216.35104789651f, + 12230.37067258353f, + 12244.394316082657f, + 12258.421976091831f, + 12272.453650312296f, + 12286.489336448576f, + 12300.529032208471f, + 12314.57273530306f, + 12328.620443446678f, + 12342.672154356922f, + 12356.727865754638f, + 12370.78757536391f, + 12384.851280912055f, + 12398.918980129623f, + 12412.990670750381f, + 12427.066350511306f, + 12441.146017152583f, + 12455.229668417589f, + 12469.317302052901f, + 12483.408915808272f, + 12497.50450743663f, + 12511.604074694078f, + 12525.707615339878f, + 12539.815127136444f, + 12553.926607849342f, + 12568.042055247275f, + 12582.161467102082f, + 12596.284841188726f, + 12610.41217528529f, + 12624.54346717297f, + 12638.67871463607f, + 12652.817915461985f, + 12666.961067441209f, + 12681.108168367316f, + 12695.259216036962f, + 12709.41420824987f, + 12723.573142808826f, + 12737.73601751968f, + 12751.902830191326f, + 12766.073578635704f, + 12780.248260667788f, + 12794.426874105588f, + 12808.609416770132f, + 12822.795886485468f, + 12836.986281078653f, + 12851.180598379744f, + 12865.378836221802f, + 12879.580992440871f, + 12893.787064875982f, + 12907.997051369144f, + 12922.210949765336f, + 12936.428757912496f, + 12950.650473661524f, + 12964.876094866271f, + 12979.105619383532f, + 12993.33904507304f, + 13007.576369797454f, + 13021.817591422368f, + 13036.062707816287f, + 13050.311716850629f, + 13064.564616399723f, + 13078.821404340792f, + 13093.082078553954f, + 13107.346636922217f, + 13121.615077331466f, + 13135.887397670458f, + 13150.163595830825f, + 13164.44366970706f, + 13178.727617196502f, + 13193.015436199352f, + 13207.307124618648f, + 13221.602680360265f, + 13235.902101332911f, + 13250.20538544812f, + 13264.512530620239f, + 13278.823534766434f, + 13293.138395806676f, + 13307.457111663734f, + 13321.779680263176f, + 13336.106099533357f, + 13350.43636740541f, + 13364.770481813252f, + 13379.108440693562f, + 13393.450241985796f, + 13407.795883632158f, + 13422.145363577607f, + 13436.498679769855f, + 13450.855830159346f, + 13465.216812699266f, + 13479.58162534553f, + 13493.950266056772f, + 13508.32273279435f, + 13522.69902352233f, + 13537.079136207483f, + 13551.463068819285f, + 13565.850819329906f, + 13580.2423857142f, + 13594.637765949712f, + 13609.036958016655f, + 13623.439959897927f, + 13637.84676957908f, + 13652.257385048335f, + 13666.67180429656f, + 13681.090025317284f, + 13695.512046106669f, + 13709.93786466352f, + 13724.367478989278f, + 13738.800887088004f, + 13753.238086966387f, + 13767.679076633725f, + 13782.12385410194f, + 13796.572417385545f, + 13811.024764501659f, + 13825.480893469998f, + 13839.94080231286f, + 13854.404489055134f, + 13868.871951724283f, + 13883.34318835034f, + 13897.818196965914f, + 13912.296975606168f, + 13926.779522308825f, + 13941.26583511416f, + 13955.755912064991f, + 13970.24975120668f, + 13984.747350587126f, + 13999.248708256751f, + 14013.75382226851f, + 14028.262690677873f, + 14042.775311542828f, + 14057.291682923867f, + 14071.811802883994f, + 14086.335669488704f, + 14100.863280805994f, + 14115.39463490634f, + 14129.92972986271f, + 14144.468563750548f, + 14159.01113464777f, + 14173.55744063476f, + 14188.10747979437f, + 14202.6612502119f, + 14217.218749975118f, + 14231.779977174227f, + 14246.34492990188f, + 14260.913606253163f, + 14275.486004325601f, + 14290.062122219148f, + 14304.64195803617f, + 14319.225509881464f, + 14333.812775862236f, + 14348.403754088098f, + 14362.998442671067f, + 14377.59683972556f, + 14392.198943368388f, + 14406.804751718748f, + 14421.414262898223f, + 14436.027475030774f, + 14450.64438624274f, + 14465.264994662828f, + 14479.889298422106f, + 14494.517295654005f, + 14509.148984494313f, + 14523.784363081166f, + 14538.423429555049f, + 14553.066182058781f, + 14567.712618737527f, + 14582.362737738777f, + 14597.016537212348f, + 14611.674015310382f, + 14626.33517018734f, + 14640.999999999993f, + 14655.668502907418f, + 14670.340677071003f, + 14685.016520654426f, + 14699.69603182367f, + 14714.379208747f, + 14729.066049594967f, + 14743.756552540408f, + 14758.45071575843f, + 14773.148537426418f, + 14787.850015724018f, + 14802.555148833142f, + 14817.26393493796f, + 14831.976372224897f, + 14846.692458882624f, + 14861.41219310206f, + 14876.135573076363f, + 14890.862597000923f, + 14905.593263073371f, + 14920.327569493558f, + 14935.065514463557f, + 14949.807096187662f, + 14964.552312872382f, + 14979.301162726431f, + 14994.053643960735f, + 15008.809754788414f, + 15023.569493424788f, + 15038.33285808737f, + 15053.099846995858f, + 15067.870458372134f, + 15082.644690440264f, + 15097.422541426484f, + 15112.204009559202f, + 15126.989093068994f, + 15141.777790188597f, + 15156.570099152905f, + 15171.366018198967f, + 15186.165545565986f, + 15200.968679495301f, + 15215.775418230402f, + 15230.58576001691f, + 15245.39970310258f, + 15260.217245737298f, + 15275.038386173073f, + 15289.863122664035f, + 15304.691453466432f, + 15319.52337683862f, + 15334.358891041069f, + 15349.197994336348f, + 15364.040684989128f, + 15378.886961266177f, + 15393.736821436356f, + 15408.59026377061f, + 15423.447286541972f, + 15438.307888025554f, + 15453.172066498542f, + 15468.039820240196f, + 15482.91114753184f, + 15497.786046656869f, + 15512.664515900733f, + 15527.54655355094f, + 15542.432157897045f, + 15557.32132723066f, + 15572.214059845435f, + 15587.110354037064f, + 15602.010208103273f, + 15616.913620343823f, + 15631.820589060506f, + 15646.731112557136f, + 15661.645189139546f, + 15676.562817115593f, + 15691.483994795139f, + 15706.408720490062f, + 15721.336992514242f, + 15736.26880918356f, + 15751.2041688159f, + 15766.143069731135f, + 15781.085510251132f, + 15796.03148869974f, + 15810.981003402798f, + 15825.934052688119f, + 15840.890634885489f, + 15855.850748326673f, + 15870.8143913454f, + 15885.781562277361f, + 15900.752259460214f, + 15915.726481233565f, + 15930.704225938982f, + 15945.685491919978f, + 15960.67027752201f, + 15975.65858109248f, + 15990.65040098073f, + 16005.645735538035f, + 16020.644583117599f, + 16035.646942074556f, + 16050.652810765967f, + 16065.662187550806f, + 16080.675070789974f, + 16095.691458846273f, + 16110.711350084424f, + 16125.734742871053f, + 16140.761635574685f, + 16155.792026565747f, + 16170.82591421656f, + 16185.863296901338f, + 16200.904172996183f, + 16215.948540879079f, + 16230.9963989299f, + 16246.047745530386f, + 16261.102579064163f, + 16276.160897916721f, + 16291.22270047542f, + 16306.287985129484f, + 16321.356750269995f, + 16336.428994289896f, + 16351.504715583982f, + 16366.5839125489f, + 16381.66658358314f, + 16396.75272708704f, + 16411.842341462776f, + 16426.935425114363f, + 16442.031976447644f, + 16457.131993870298f, + 16472.23547579183f, + 16487.34242062356f, + 16502.45282677864f, + 16517.566692672033f, + 16532.684016720516f, + 16547.804797342676f, + 16562.929032958902f, + 16578.056721991394f, + 16593.18786286415f, + 16608.322454002962f, + 16623.460493835417f, + 16638.601980790896f, + 16653.746913300558f, + 16668.895289797354f, + 16684.047108716015f, + 16699.202368493046f, + 16714.361067566726f, + 16729.523204377107f, + 16744.68877736601f, + 16759.85778497701f, + 16775.030225655464f, + 16790.206097848466f, + 16805.385400004874f, + 16820.568130575302f, + 16835.754288012104f, + 16850.94387076938f, + 16866.136877302983f, + 16881.333306070494f, + 16896.53315553123f, + 16911.73642414625f, + 16926.94311037833f, + 16942.153212691992f, + 16957.366729553454f, + 16972.583659430682f, + 16987.804000793338f, + 17003.027752112816f, + 17018.254911862205f, + 17033.48547851631f, + 17048.719450551645f, + 17063.95682644642f, + 17079.197604680547f, + 17094.44178373563f, + 17109.689362094967f, + 17124.940338243552f, + 17140.19471066806f, + 17155.452477856852f, + 17170.713638299967f, + 17185.978190489128f, + 17201.246132917724f, + 17216.517464080825f, + 17231.792182475165f, + 17247.07028659914f, + 17262.351774952826f, + 17277.636646037936f, + 17292.924898357855f, + 17308.216530417623f, + 17323.51154072392f, + 17338.80992778509f, + 17354.111690111105f, + 17369.416826213594f, + 17384.72533460582f, + 17400.037213802683f, + 17415.352462320716f, + 17430.67107867809f, + 17445.993061394587f, + 17461.318408991636f, + 17476.647119992274f, + 17491.979192921164f, + 17507.314626304586f, + 17522.653418670423f, + 17537.995568548187f, + 17553.341074468986f, + 17568.689934965536f, + 17584.042148572156f, + 17599.39771382477f, + 17614.75662926089f, + 17630.118893419625f, + 17645.484504841683f, + 17660.853462069354f, + 17676.22576364651f, + 17691.60140811862f, + 17706.98039403272f, + 17722.362719937424f, + 17737.748384382936f, + 17753.137385921014f, + 17768.529723105f, + 17783.92539448979f, + 17799.324398631856f, + 17814.726734089225f, + 17830.13239942148f, + 17845.541393189767f, + 17860.95371395678f, + 17876.36936028677f, + 17891.788330745527f, + 17907.210623900395f, + 17922.636238320254f, + 17938.065172575527f, + 17953.497425238176f, + 17968.932994881692f, + 17984.371880081104f, + 17999.814079412972f, + 18015.25959145537f, + 18030.708414787914f, + 18046.16054799173f, + 18061.615989649465f, + 18077.074738345284f, + 18092.53679266486f, + 18108.002151195393f, + 18123.47081252557f, + 18138.9427752456f, + 18154.41803794719f, + 18169.896599223546f, + 18185.37845766938f, + 18200.863611880886f, + 18216.352060455767f, + 18231.843801993204f, + 18247.338835093873f, + 18262.837158359936f, + 18278.338770395032f, + 18293.84366980429f, + 18309.35185519431f, + 18324.863325173166f, + 18340.37807835041f, + 18355.89611333707f, + 18371.417428745623f, + 18386.942023190033f, + 18402.469895285714f, + 18418.00104364955f, + 18433.53546689987f, + 18449.073163656474f, + 18464.614132540602f, + 18480.158372174956f, + 18495.705881183676f, + 18511.25665819236f, + 18526.810701828035f, + 18542.368010719183f, + 18557.928583495715f, + 18573.492418788985f, + 18589.059515231773f, + 18604.629871458303f, + 18620.203486104212f, + 18635.78035780658f, + 18651.3604852039f, + 18666.943866936086f, + 18682.53050164448f, + 18698.12038797184f, + 18713.713524562332f, + 18729.30991006154f, + 18744.909543116457f, + 18760.51242237548f, + 18776.11854648842f, + 18791.72791410648f, + 18807.340523882274f, + 18822.95637446981f, + 18838.57546452449f, + 18854.19779270311f, + 18869.823357663863f, + 18885.452158066328f, + 18901.08419257147f, + 18916.71945984164f, + 18932.357958540564f, + 18947.999687333362f, + 18963.64464488652f, + 18979.292829867907f, + 18994.94424094676f, + 19010.598876793687f, + 19026.256736080668f, + 19041.917817481044f, + 19057.582119669532f, + 19073.2496413222f, + 19088.920381116473f, + 19104.594337731145f, + 19120.271509846356f, + 19135.951896143604f, + 19151.635495305738f, + 19167.322306016948f, + 19183.01232696278f, + 19198.705556830122f, + 19214.401994307198f, + 19230.10163808358f, + 19245.804486850167f, + 19261.510539299208f, + 19277.219794124274f, + 19292.932250020265f, + 19308.64790568342f, + 19324.366759811302f, + 19340.088811102793f, + 19355.8140582581f, + 19371.542499978754f, + 19387.2741349676f, + 19403.008961928797f, + 19418.746979567823f, + 19434.48818659147f, + 19450.232581707827f, + 19465.980163626304f, + 19481.730931057613f, + 19497.48488271376f, + 19513.242017308068f, + 19529.00233355514f, + 19544.765830170898f, + 19560.53250587254f, + 19576.302359378566f, + 19592.07538940876f, + 19607.85159468421f, + 19623.63097392727f, + 19639.41352586159f, + 19655.199249212103f, + 19670.988142705017f, + 19686.780205067822f, + 19702.57543502929f, + 19718.373831319448f, + 19734.175392669615f, + 19749.980117812374f, + 19765.78800548157f, + 19781.59905441232f, + 19797.413263341008f, + 19813.230631005274f, + 19829.051156144014f, + 19844.874837497395f, + 19860.701673806827f, + 19876.531663814985f, + 19892.36480626579f, + 19908.201099904407f, + 19924.04054347726f, + 19939.883135732012f, + 19955.72887541758f, + 19971.577761284105f, + 19987.429792082985f, + 20003.284966566847f, + 20019.14328348956f, + 20035.00474160622f, + 20050.86933967316f, + 20066.737076447942f, + 20082.60795068936f, + 20098.481961157428f, + 20114.359106613385f, + 20130.239385819703f, + 20146.122797540054f, + 20162.009340539353f, + 20177.899013583716f, + 20193.791815440476f, + 20209.68774487818f, + 20225.58680066659f, + 20241.48898157667f, + 20257.394286380597f, + 20273.302713851754f, + 20289.214262764715f, + 20305.128931895277f, + 20321.046720020415f, + 20336.967625918318f, + 20352.89164836836f, + 20368.818786151114f, + 20384.749038048347f, + 20400.68240284301f, + 20416.61887931925f, + 20432.55846626239f, + 20448.501162458953f, + 20464.44696669663f, + 20480.395877764302f, + 20496.347894452025f, + 20512.30301555103f, + 20528.261239853735f, + 20544.22256615372f, + 20560.18699324574f, + 20576.15451992572f, + 20592.125144990758f, + 20608.098867239107f, + 20624.075685470198f, + 20640.055598484618f, + 20656.038605084115f, + 20672.024704071595f, + 20688.013894251126f, + 20704.006174427926f, + 20720.00154340837f, + 20735.99999999999f, + 20752.001543011454f, + 20768.006171252597f, + 20784.013883534382f, + 20800.02467866893f, + 20816.038555469506f, + 20832.055512750507f, + 20848.075549327474f, + 20864.098664017085f, + 20880.12485563716f, + 20896.154123006647f, + 20912.186464945626f, + 20928.221880275312f, + 20944.260367818053f, + 20960.30192639731f, + 20976.346554837684f, + 20992.394251964895f, + 21008.445016605787f, + 21024.49884758832f, + 21040.555743741574f, + 21056.615703895754f, + 21072.67872688217f, + 21088.74481153325f, + 21104.813956682538f, + 21120.886161164683f, + 21136.96142381544f, + 21153.039743471683f, + 21169.12111897138f, + 21185.205549153605f, + 21201.293032858535f, + 21217.383568927453f, + 21233.47715620273f, + 21249.573793527845f, + 21265.67347974736f, + 21281.776213706937f, + 21297.881994253334f, + 21313.990820234398f, + 21330.102690499054f, + 21346.21760389733f, + 21362.335559280327f, + 21378.45655550024f, + 21394.580591410333f, + 21410.70766586496f, + 21426.837777719556f, + 21442.97092583063f, + 21459.10710905576f, + 21475.246326253604f, + 21491.388576283895f, + 21507.53385800743f, + 21523.682170286087f, + 21539.833511982797f, + 21555.987881961566f, + 21572.14527908746f, + 21588.305702226615f, + 21604.469150246216f, + 21620.63562201452f, + 21636.805116400832f, + 21652.97763227552f, + 21669.153168510005f, + 21685.331723976764f, + 21701.513297549318f, + 21717.697888102244f, + 21733.885494511167f, + 21750.07611565276f, + 21766.269750404736f, + 21782.46639764586f, + 21798.666056255934f, + 21814.8687251158f, + 21831.07440310734f, + 21847.283089113484f, + 21863.494782018177f, + 21879.709480706417f, + 21895.92718406423f, + 21912.147890978667f, + 21928.371600337818f, + 21944.598311030797f, + 21960.828021947746f, + 21977.06073197983f, + 21993.296440019243f, + 22009.535144959198f, + 22025.77684569393f, + 22042.02154111869f, + 22058.269230129757f, + 22074.51991162441f, + 22090.77358450096f, + 22107.030247658717f, + 22123.289899998013f, + 22139.552540420187f, + 22155.818167827587f, + 22172.08678112357f, + 22188.358379212495f, + 22204.632960999726f, + 22220.910525391642f, + 22237.1910712956f, + 22253.474597619977f, + 22269.761103274148f, + 22286.050587168473f, + 22302.343048214312f, + 22318.638485324027f, + 22334.936897410968f, + 22351.23828338947f, + 22367.54264217487f, + 22383.849972683485f, + 22400.16027383262f, + 22416.473544540568f, + 22432.789783726603f, + 22449.10899031099f, + 22465.431163214962f, + 22481.75630136074f, + 22498.084403671528f, + 22514.415469071497f, + 22530.749496485798f, + 22547.08648484056f, + 22563.42643306288f, + 22579.769340080824f, + 22596.115204823436f, + 22612.46402622072f, + 22628.815803203655f, + 22645.17053470418f, + 22661.5282196552f, + 22677.888856990587f, + 22694.25244564517f, + 22710.618984554734f, + 22726.988472656034f, + 22743.360908886778f, + 22759.736292185622f, + 22776.11462149219f, + 22792.495895747044f, + 22808.88011389172f, + 22825.26727486868f, + 22841.657377621348f, + 22858.050421094096f, + 22874.446404232243f, + 22890.845325982053f, + 22907.247185290722f, + 22923.651981106406f, + 22940.059712378195f, + 22956.470378056114f, + 22972.88397709113f, + 22989.300508435153f, + 23005.719971041017f, + 23022.1423638625f, + 23038.56768585431f, + 23054.99593597208f, + 23071.427113172387f, + 23087.86121641273f, + 23104.29824465153f, + 23120.738196848142f, + 23137.18107196285f, + 23153.626868956846f, + 23170.075586792263f, + 23186.52722443214f, + 23202.981780840448f, + 23219.439254982062f, + 23235.899645822796f, + 23252.362952329357f, + 23268.829173469378f, + 23285.298308211408f, + 23301.7703555249f, + 23318.245314380227f, + 23334.723183748658f, + 23351.203962602387f, + 23367.687649914504f, + 23384.174244659007f, + 23400.663745810794f, + 23417.15615234568f, + 23433.651463240367f, + 23450.14967747246f, + 23466.650794020472f, + 23483.154811863806f, + 23499.661729982763f, + 23516.171547358543f, + 23532.684262973235f, + 23549.19987580982f, + 23565.71838485219f, + 23582.23978908509f, + 23598.764087494194f, + 23615.29127906604f, + 23631.821362788058f, + 23648.354337648565f, + 23664.890202636765f, + 23681.428956742733f, + 23697.970598957443f, + 23714.51512827274f, + 23731.062543681343f, + 23747.612844176863f, + 23764.166028753774f, + 23780.72209640744f, + 23797.281046134085f, + 23813.842876930816f, + 23830.407587795606f, + 23846.975177727305f, + 23863.545645725622f, + 23880.11899079115f, + 23896.695211925336f, + 23913.2743081305f, + 23929.85627840982f, + 23946.441121767348f, + 23963.02883720799f, + 23979.619423737513f, + 23996.212880362546f, + 24012.809206090584f, + 24029.408399929966f, + 24046.0104608899f, + 24062.615387980433f, + 24079.223180212488f, + 24095.833836597827f, + 24112.447356149067f, + 24129.063737879667f, + 24145.682980803947f, + 24162.305083937077f, + 24178.930046295063f, + 24195.557866894764f, + 24212.18854475388f, + 24228.82207889096f, + 24245.45846832539f, + 24262.097712077397f, + 24278.73980916805f, + 24295.384758619257f, + 24312.03255945377f, + 24328.683210695162f, + 24345.33671136786f, + 24361.99306049711f, + 24378.652257108995f, + 24395.314300230442f, + 24411.979188889192f, + 24428.646922113825f, + 24445.317498933746f, + 24461.990918379193f, + 24478.66717948122f, + 24495.346281271726f, + 24512.028222783407f, + 24528.7130030498f, + 24545.40062110527f, + 24562.091075984976f, + 24578.784366724925f, + 24595.480492361927f, + 24612.179451933614f, + 24628.881244478434f, + 24645.585869035654f, + 24662.293324645343f, + 24679.003610348394f, + 24695.716725186514f, + 24712.43266820221f, + 24729.151438438807f, + 24745.873034940436f, + 24762.59745675203f, + 24779.324702919344f, + 24796.054772488926f, + 24812.787664508123f, + 24829.5233780251f, + 24846.26191208882f, + 24863.003265749034f, + 24879.747438056307f, + 24896.494428062004f, + 24913.244234818278f, + 24929.99685737808f, + 24946.752294795166f, + 24963.51054612408f, + 24980.271610420154f, + 24997.035486739525f, + 25013.802174139113f, + 25030.57167167663f, + 25047.343978410572f, + 25064.119093400237f, + 25080.897015705697f, + 25097.677744387816f, + 25114.46127850824f, + 25131.247617129404f, + 25148.036759314517f, + 25164.828704127583f, + 25181.62345063337f, + 25198.420997897447f, + 25215.221344986145f, + 25232.024490966574f, + 25248.83043490663f, + 25265.639175874974f, + 25282.45071294105f, + 25299.26504517507f, + 25316.082171648024f, + 25332.902091431668f, + 25349.724803598532f, + 25366.550307221914f, + 25383.378601375884f, + 25400.20968513527f, + 25417.04355757568f, + 25433.880217773472f, + 25450.719664805783f, + 25467.561897750507f, + 25484.406915686297f, + 25501.254717692573f, + 25518.10530284951f, + 25534.958670238055f, + 25551.814818939893f, + 25568.67374803748f, + 25585.535456614027f, + 25602.399943753502f, + 25619.26720854062f, + 25636.137250060856f, + 25653.01006740043f, + 25669.885659646327f, + 25686.76402588627f, + 25703.645165208734f, + 25720.529076702944f, + 25737.415759458876f, + 25754.305212567244f, + 25771.197435119517f, + 25788.0924262079f, + 25804.990184925344f, + 25821.890710365547f, + 25838.794001622948f, + 25855.700057792717f, + 25872.608877970775f, + 25889.52046125378f, + 25906.43480673912f, + 25923.351913524923f, + 25940.271780710063f, + 25957.194407394138f, + 25974.119792677477f, + 25991.047935661154f, + 26007.978835446964f, + 26024.912491137442f, + 26041.84890183584f, + 26058.788066646157f, + 26075.729984673108f, + 26092.674655022136f, + 26109.62207679941f, + 26126.57224911183f, + 26143.525171067016f, + 26160.480841773315f, + 26177.43926033979f, + 26194.40042587623f, + 26211.36433749315f, + 26228.330994301767f, + 26245.30039541404f, + 26262.272539942627f, + 26279.24742700092f, + 26296.225055703006f, + 26313.205425163702f, + 26330.18853449854f, + 26347.174382823756f, + 26364.162969256307f, + 26381.154292913852f, + 26398.148352914774f, + 26415.14514837815f, + 26432.144678423778f, + 26449.146942172156f, + 26466.151938744493f, + 26483.159667262702f, + 26500.170126849403f, + 26517.18331662792f, + 26534.199235722277f, + 26551.2178832572f, + 26568.239258358124f, + 26585.263360151173f, + 26602.29018776318f, + 26619.319740321676f, + 26636.352016954883f, + 26653.387016791727f, + 26670.424738961825f, + 26687.465182595493f, + 26704.508346823743f, + 26721.554230778267f, + 26738.602833591467f, + 26755.65415439643f, + 26772.70819232693f, + 26789.764946517433f, + 26806.824416103096f, + 26823.88660021976f, + 26840.95149800396f, + 26858.01910859291f, + 26875.089431124517f, + 26892.162464737365f, + 26909.23820857072f, + 26926.316661764547f, + 26943.39782345947f, + 26960.481692796813f, + 26977.56826891857f, + 26994.657550967422f, + 27011.74953808672f, + 27028.844229420498f, + 27045.941624113464f, + 27063.041721311005f, + 27080.14452015918f, + 27097.250019804727f, + 27114.35821939505f, + 27131.469118078236f, + 27148.58271500303f, + 27165.699009318858f, + 27182.818000175816f, + 27199.939686724665f, + 27217.064068116837f, + 27234.191143504428f, + 27251.320912040203f, + 27268.453372877593f, + 27285.588525170693f, + 27302.72636807427f, + 27319.866900743735f, + 27337.01012233518f, + 27354.156032005358f, + 27371.30462891167f, + 27388.455912212183f, + 27405.609881065626f, + 27422.766534631388f, + 27439.925872069507f, + 27457.087892540683f, + 27474.252595206275f, + 27491.419979228293f, + 27508.5900437694f, + 27525.762787992917f, + 27542.93821106281f, + 27560.116312143706f, + 27577.297090400876f, + 27594.480545000246f, + 27611.666675108383f, + 27628.855479892518f, + 27646.046958520514f, + 27663.24111016089f, + 27680.4379339828f, + 27697.637429156064f, + 27714.83959485113f, + 27732.04443023909f, + 27749.251934491687f, + 27766.4621067813f, + 27783.67494628095f, + 27800.8904521643f, + 27818.108623605658f, + 27835.329459779954f, + 27852.55295986278f, + 27869.779123030345f, + 27887.007948459504f, + 27904.239435327745f, + 27921.473582813196f, + 27938.710390094617f, + 27955.94985635139f, + 27973.19198076355f, + 27990.436762511745f, + 28007.684200777272f, + 28024.934294742037f, + 28042.1870435886f, + 28059.44244650013f, + 28076.700502660427f, + 28093.961211253933f, + 28111.224571465696f, + 28128.4905824814f, + 28145.759243487362f, + 28163.03055367051f, + 28180.304512218394f, + 28197.581118319198f, + 28214.860371161725f, + 28232.14226993539f, + 28249.426813830236f, + 28266.71400203693f, + 28284.003833746745f, + 28301.296308151585f, + 28318.59142444396f, + 28335.889181817f, + 28353.189579464466f, + 28370.492616580705f, + 28387.798292360705f, + 28405.10660600005f, + 28422.417556694945f, + 28439.73114364221f, + 28457.047366039264f, + 28474.36622308415f, + 28491.687713975512f, + 28509.01183791261f, + 28526.338594095305f, + 28543.66798172407f, + 28560.999999999985f, + 28578.33464812473f, + 28595.671925300605f, + 28613.0118307305f, + 28630.35436361791f, + 28647.699523166943f, + 28665.0473085823f, + 28682.39771906929f, + 28699.750753833818f, + 28717.10641208239f, + 28734.46469302212f, + 28751.82559586071f, + 28769.189119806462f, + 28786.55526406828f, + 28803.92402785566f, + 28821.2954103787f, + 28838.669410848088f, + 28856.046028475103f, + 28873.42526247163f, + 28890.80711205013f, + 28908.191576423673f, + 28925.578654805915f, + 28942.968346411097f, + 28960.360650454055f, + 28977.755566150212f, + 28995.15309271559f, + 29012.553229366786f, + 29029.955975320987f, + 29047.361329795975f, + 29064.769292010107f, + 29082.179861182336f, + 29099.593036532187f, + 29117.00881727978f, + 29134.427202645813f, + 29151.848191851568f, + 29169.27178411891f, + 29186.697978670283f, + 29204.126774728706f, + 29221.55817151779f, + 29238.992168261717f, + 29256.42876418525f, + 29273.867958513725f, + 29291.30975047306f, + 29308.754139289747f, + 29326.201124190855f, + 29343.65070440403f, + 29361.102879157483f, + 29378.557647680012f, + 29396.015009200975f, + 29413.47496295031f, + 29430.937508158524f, + 29448.402644056692f, + 29465.87036987647f, + 29483.34068485007f, + 29500.81358821028f, + 29518.289079190454f, + 29535.76715702451f, + 29553.247820946945f, + 29570.731070192807f, + 29588.216903997723f, + 29605.70532159787f, + 29623.19632223f, + 29640.68990513143f, + 29658.18606954003f, + 29675.684814694236f, + 29693.186139833047f, + 29710.690044196028f, + 29728.196527023298f, + 29745.705587555527f, + 29763.217225033964f, + 29780.731438700397f, + 29798.248227797183f, + 29815.76759156723f, + 29833.289529254005f, + 29850.81404010153f, + 29868.34112335438f, + 29885.870778257693f, + 29903.403004057145f, + 29920.937799998974f, + 29938.475165329975f, + 29956.01509929748f, + 29973.557601149394f, + 29991.102670134147f, + 30008.65030550074f, + 30026.20050649871f, + 30043.753272378144f, + 30061.308602389683f, + 30078.866495784507f, + 30096.426951814352f, + 30113.989969731494f, + 30131.55554878875f, + 30149.12368823949f, + 30166.69438733763f, + 30184.26764533761f, + 30201.843461494434f, + 30219.42183506364f, + 30237.00276530131f, + 30254.586251464058f, + 30272.172292809046f, + 30289.760888593977f, + 30307.35203807709f, + 30324.94574051716f, + 30342.541995173502f, + 30360.140801305966f, + 30377.742158174944f, + 30395.346065041358f, + 30412.952521166666f, + 30430.56152581286f, + 30448.173078242475f, + 30465.78717771856f, + 30483.40382350472f, + 30501.02301486507f, + 30518.644751064272f, + 30536.269031367516f, + 30553.895855040515f, + 30571.52522134952f, + 30589.157129561307f, + 30606.791578943175f, + 30624.428568762964f, + 30642.06809828903f, + 30659.71016679026f, + 30677.35477353607f, + 30695.00191779639f, + 30712.651598841687f, + 30730.303815942945f, + 30747.958568371676f, + 30765.615855399912f, + 30783.27567630021f, + 30800.938030345646f, + 30818.602916809814f, + 30836.270334966837f, + 30853.940284091354f, + 30871.61276345852f, + 30889.28777234401f, + 30906.965310024025f, + 30924.64537577527f, + 30942.327968874983f, + 30960.013088600903f, + 30977.700734231294f, + 30995.39090504493f, + 31013.0836003211f, + 31030.77881933962f, + 31048.476561380798f, + 31066.17682572547f, + 31083.87961165498f, + 31101.58491845118f, + 31119.29274539644f, + 31137.003091773637f, + 31154.715956866155f, + 31172.431339957893f, + 31190.14924033326f, + 31207.869657277162f, + 31225.592590075023f, + 31243.31803801277f, + 31261.04600037684f, + 31278.77647645417f, + 31296.50946553221f, + 31314.24496689891f, + 31331.98297984272f, + 31349.7235036526f, + 31367.466537618013f, + 31385.212081028923f, + 31402.960133175795f, + 31420.710693349596f, + 31438.46376084179f, + 31456.21933494435f, + 31473.977414949743f, + 31491.738000150934f, + 31509.50108984139f, + 31527.26668331507f, + 31545.034779866437f, + 31562.80537879045f, + 31580.578479382562f, + 31598.35408093872f, + 31616.13218275537f, + 31633.91278412945f, + 31651.695884358396f, + 31669.48148274013f, + 31687.269578573076f, + 31705.060171156143f, + 31722.853259788735f, + 31740.64884377075f, + 31758.446922402567f, + 31776.247494985066f, + 31794.050560819614f, + 31811.85611920806f, + 31829.664169452753f, + 31847.47471085652f, + 31865.287742722685f, + 31883.103264355046f, + 31900.9212750579f, + 31918.74177413602f, + 31936.56476089467f, + 31954.3902346396f, + 31972.21819467704f, + 31990.048640313704f, + 32007.881570856793f, + 32025.716985613984f, + 32043.554883893445f, + 32061.395265003815f, + 32079.238128254223f, + 32097.08347295427f, + 32114.93129841405f, + 32132.781603944117f, + 32150.634388855524f, + 32168.48965245979f, + 32186.347394068915f, + 32204.20761299537f, + 32222.07030855212f, + 32239.935480052583f, + 32257.80312681067f, + 32275.673248140767f, + 32293.54584335772f, + 32311.420911776862f, + 32329.298452713996f, + 32347.178465485395f, + 32365.060949407813f, + 32382.945903798463f, + 32400.83332797504f, + 32418.723221255706f, + 32436.615582959093f, + 32454.510412404306f, + 32472.407708910916f, + 32490.307471798966f, + 32508.20970038896f, + 32526.114394001877f, + 32544.021551959166f, + 32561.931173582732f, + 32579.843258194956f, + 32597.75780511868f, + 32615.67481367721f, + 32633.59428319433f, + 32651.51621299426f, + 32669.44060240171f, + 32687.367450741847f, + 32705.296757340297f, + 32723.228521523146f, + 32741.162742616943f, + 32759.099419948703f, + 32777.0385528459f, + 32794.980140636464f, + 32812.92418264879f, + 32830.87067821173f, + 32848.81962665459f, + 32866.77102730715f, + 32884.72487949962f, + 32902.68118256269f, + 32920.639935827494f, + 32938.60113862564f, + 32956.56479028918f, + 32974.53089015061f, + 32992.499437542894f, + 33010.47043179945f, + 33028.443872254145f, + 33046.41975824131f, + 33064.39808909571f, + 33082.37886415258f, + 33100.36208274759f, + 33118.34774421688f, + 33136.335847897026f, + 33154.32639312506f, + 33172.31937923847f, + 33190.31480557517f, + 33208.312671473555f, + 33226.31297627244f, + 33244.31571931111f, + 33262.320899929284f, + 33280.328517467125f, + 33298.33857126526f, + 33316.35106066475f, + 33334.36598500709f, + 33352.38334363424f, + 33370.40313588859f, + 33388.42536111299f, + 33406.45001865072f, + 33424.4771078455f, + 33442.50662804151f, + 33460.53857858335f, + 33478.57295881608f, + 33496.60976808519f, + 33514.64900573662f, + 33532.69067111674f, + 33550.734763572356f, + 33568.781282450735f, + 33586.83022709956f, + 33604.88159686697f, + 33622.93539110153f, + 33640.99160915224f, + 33659.05025036854f, + 33677.11131410032f, + 33695.17479969788f, + 33713.240706511984f, + 33731.309033893805f, + 33749.37978119497f, + 33767.45294776753f, + 33785.528532963974f, + 33803.60653613721f, + 33821.6869566406f, + 33839.76979382794f, + 33857.855047053425f, + 33875.94271567171f, + 33894.03279903787f, + 33912.12529650743f, + 33930.220207436316f, + 33948.31753118089f, + 33966.41726709796f, + 33984.519414544746f, + 34002.6239728789f, + 34020.73094145851f, + 34038.84031964208f, + 34056.952106788536f, + 34075.066302257255f, + 34093.182905408015f, + 34111.30191560103f, + 34129.42333219693f, + 34147.547154556785f, + 34165.67338204208f, + 34183.80201401472f, + 34201.93304983703f, + 34220.06648887178f, + 34238.20233048214f, + 34256.3405740317f, + 34274.481218884495f, + 34292.62426440495f, + 34310.76970995794f, + 34328.91755490873f, + 34347.06779862303f, + 34365.220440466954f, + 34383.37547980705f, + 34401.53291601026f, + 34419.69274844397f, + 34437.85497647597f, + 34456.01959947445f, + 34474.18661680806f, + 34492.35602784582f, + 34510.527831957195f, + 34528.70202851205f, + 34546.87861688068f, + 34565.05759643377f, + 34583.23896654245f, + 34601.42272657823f, + 34619.608875913065f, + 34637.797413919296f, + 34655.98833996969f, + 34674.18165343742f, + 34692.37735369608f, + 34710.57544011967f, + 34728.77591208258f, + 34746.97876895965f, + 34765.18401012608f, + 34783.39163495754f, + 34801.60164283005f, + 34819.81403312006f, + 34838.028805204456f, + 34856.24595846048f, + 34874.46549226582f, + 34892.68740599856f, + 34910.91169903718f, + 34929.138370760564f, + 34947.36742054803f, + 34965.59884777927f, + 34983.8326518344f, + 35002.06883209391f, + 35020.30738793874f, + 35038.54831875018f, + 35056.79162390998f, + 35075.03730280025f, + 35093.285354803505f, + 35111.53577930269f, + 35129.788575681116f, + 35148.043743322516f, + 35166.30128161101f, + 35184.56118993114f, + 35202.823467667826f, + 35221.08811420639f, + 35239.355128932555f, + 35257.62451123245f, + 35275.896260492584f, + 35294.170376099886f, + 35312.44685744167f, + 35330.72570390563f, + 35349.00691487989f, + 35367.290489752944f, + 35385.576427913686f, + 35403.86472875142f, + 35422.15539165581f, + 35440.44841601697f, + 35458.74380122534f, + 35477.041546671804f, + 35495.34165174762f, + 35513.644115844436f, + 35531.948938354304f, + 35550.256118669655f, + 35568.56565618331f, + 35586.8775502885f, + 35605.191800378816f, + 35623.50840584827f, + 35641.82736609124f, + 35660.148680502505f, + 35678.47234847723f, + 35696.79836941098f, + 35715.12674269968f, + 35733.45746773966f, + 35751.790543927644f, + 35770.12597066074f, + 35788.46374733642f, + 35806.80387335257f, + 35825.14634810745f, + 35843.49117099971f, + 35861.83834142837f, + 35880.18785879285f, + 35898.539722492955f, + 35916.89393192886f, + 35935.25048650113f, + 35953.60938561072f, + 35971.97062865896f, + 35990.33421504756f, + 36008.70014417861f, + 36027.068415454596f, + 36045.43902827837f, + 36063.81198205317f, + 36082.18727618261f, + 36100.564910070694f, + 36118.94488312179f, + 36137.327194740654f, + 36155.71184433243f, + 36174.09883130262f, + 36192.488155057115f, + 36210.87981500219f, + 36229.27381054447f, + 36247.670141091f, + 36266.06880604917f, + 36284.46980482674f, + 36302.87313683186f, + 36321.27880147307f, + 36339.68679815925f, + 36358.09712629968f, + 36376.50978530401f, + 36394.924774582265f, + 36413.342093544816f, + 36431.761741602444f, + 36450.18371816629f, + 36468.60802264786f, + 36487.03465445903f, + 36505.46361301206f, + 36523.89489771958f, + 36542.32850799458f, + 36560.76444325041f, + 36579.20270290083f, + 36597.643286359926f, + 36616.08619304218f, + 36634.53142236244f, + 36652.978973735895f, + 36671.42884657814f, + 36689.881040305125f, + 36708.33555433315f, + 36726.7923880789f, + 36745.25154095943f, + 36763.71301239214f, + 36782.17680179481f, + 36800.64290858559f, + 36819.11133218299f, + 36837.58207200587f, + 36856.05512747348f, + 36874.53049800542f, + 36893.00818302165f, + 36911.488181942506f, + 36929.970494188674f, + 36948.455119181206f, + 36966.94205634152f, + 36985.43130509139f, + 37003.92286485296f, + 37022.41673504873f, + 37040.91291510156f, + 37059.411404434664f, + 37077.91220247162f, + 37096.41530863639f, + 37114.92072235324f, + 37133.42844304686f, + 37151.93847014225f, + 37170.450803064785f, + 37188.96544124021f, + 37207.4823840946f, + 37226.0016310544f, + 37244.52318154643f, + 37263.04703499784f, + 37281.57319083615f, + 37300.101648489224f, + 37318.632407385296f, + 37337.165466952945f, + 37355.70082662111f, + 37374.238485819085f, + 37392.77844397651f, + 37411.320700523385f, + 37429.86525489006f, + 37448.41210650723f, + 37466.96125480597f, + 37485.51269921768f, + 37504.066439174116f, + 37522.622474107404f, + 37541.18080344999f, + 37559.741426634704f, + 37578.30434309469f, + 37596.86955226349f, + 37615.43705357494f, + 37634.00684646328f, + 37652.578930363044f, + 37671.153304709165f, + 37689.729968936896f, + 37708.30892248185f, + 37726.890164779965f, + 37745.47369526756f, + 37764.059513381275f, + 37782.64761855811f, + 37801.238010235415f, + 37819.83068785086f, + 37838.425650842495f, + 37857.02289864869f, + 37875.62243070817f, + 37894.22424646001f, + 37912.828345343616f, + 37931.43472679875f, + 37950.043390265506f, + 37968.65433518433f, + 37987.267560996f, + 38005.883067141665f, + 38024.500853062775f, + 38043.12091820116f, + 38061.74326199896f, + 38080.36788389868f, + 38098.99478334316f, + 38117.62395977556f, + 38136.25541263942f, + 38154.889141378575f, + 38173.525145437234f, + 38192.16342425994f, + 38210.80397729155f, + 38229.44680397729f, + 38248.0919037627f, + 38266.739276093685f, + 38285.388920416466f, + 38304.040836177606f, + 38322.695022824f, + 38341.3514798029f, + 38360.01020656186f, + 38378.671202548816f, + 38397.33446721199f, + 38415.99999999998f, + 38434.66780036168f, + 38453.33786774637f, + 38472.01020160361f, + 38490.68480138334f, + 38509.361666535784f, + 38528.04079651155f, + 38546.72219076155f, + 38565.405848737035f, + 38584.091769889594f, + 38602.77995367113f, + 38621.47039953391f, + 38640.163106930486f, + 38658.858075313794f, + 38677.55530413706f, + 38696.25479285386f, + 38714.956540918094f, + 38733.66054778399f, + 38752.36681290611f, + 38771.07533573935f, + 38789.78611573892f, + 38808.49915236037f, + 38827.21444505957f, + 38845.93199329274f, + 38864.65179651639f, + 38883.37385418738f, + 38902.098165762916f, + 38920.824730700486f, + 38939.55354845794f, + 38958.28461849343f, + 38977.01794026546f, + 38995.753513232834f, + 39014.4913368547f, + 39033.23141059052f, + 39051.97373390007f, + 39070.718306243485f, + 39089.46512708119f, + 39108.214195873945f, + 39126.96551208283f, + 39145.71907516926f, + 39164.474884594965f, + 39183.23293982199f, + 39201.99324031271f, + 39220.755785529815f, + 39239.52057493633f, + 39258.28760799559f, + 39277.05688417125f, + 39295.82840292729f, + 39314.60216372801f, + 39333.37816603802f, + 39352.15640932227f, + 39370.936893046004f, + 39389.71961667481f, + 39408.50457967458f, + 39427.29178151152f, + 39446.08122165217f, + 39464.87289956337f, + 39483.66681471229f, + 39502.46296656641f, + 39521.26135459354f, + 39540.06197826178f, + 39558.86483703957f, + 39577.669930395656f, + 39596.47725779911f, + 39615.2868187193f, + 39634.09861262592f, + 39652.91263898899f, + 39671.72889727882f, + 39690.547386966064f, + 39709.36810752165f, + 39728.19105841686f, + 39747.01623912326f, + 39765.84364911275f, + 39784.67328785753f, + 39803.505154830105f, + 39822.33924950332f, + 39841.17557135029f, + 39860.0141198445f, + 39878.85489445968f, + 39897.69789466991f, + 39916.54311994958f, + 39935.39056977337f, + 39954.2402436163f, + 39973.092140953675f, + 39991.94626126112f, + 40010.80260401455f, + 40029.661168690225f, + 40048.52195476468f, + 40067.38496171478f, + 40086.25018901768f, + 40105.117636150855f, + 40123.98730259209f, + 40142.85918781947f, + 40161.73329131138f, + 40180.60961254653f, + 40199.48815100391f, + 40218.368906162854f, + 40237.25187750296f, + 40256.13706450415f, + 40275.02446664667f, + 40293.91408341103f, + 40312.805914278084f, + 40331.69995872896f, + 40350.5962162451f, + 40369.49468630827f, + 40388.39536840051f, + 40407.29826200417f, + 40426.20336660192f, + 40445.110681676706f, + 40464.02020671179f, + 40482.93194119075f, + 40501.84588459744f, + 40520.76203641603f, + 40539.680396130985f, + 40558.60096322707f, + 40577.52373718937f, + 40596.448717503234f, + 40615.37590365434f, + 40634.30529512866f, + 40653.23689141245f, + 40672.170691992294f, + 40691.10669635505f, + 40710.04490398787f, + 40728.98531437824f, + 40747.9279270139f, + 40766.87274138292f, + 40785.81975697365f, + 40804.768973274746f, + 40823.72038977516f, + 40842.67400596413f, + 40861.62982133121f, + 40880.58783536623f, + 40899.54804755933f, + 40918.51045740093f, + 40937.47506438176f, + 40956.44186799285f, + 40975.4108677255f, + 40994.38206307133f, + 41013.355453522236f, + 41032.33103857042f, + 41051.30881770836f, + 41070.288790428865f, + 41089.27095622499f, + 41108.25531459011f, + 41127.24186501789f, + 41146.23060700229f, + 41165.22154003754f, + 41184.2146636182f, + 41203.20997723908f, + 41222.20748039531f, + 41241.2071725823f, + 41260.20905329575f, + 41279.21312203166f, + 41298.2193782863f, + 41317.227821556255f, + 41336.23845133838f, + 41355.25126712983f, + 41374.26626842804f, + 41393.28345473074f, + 41412.30282553595f, + 41431.32438034198f, + 41450.34811864742f, + 41469.374039951144f, + 41488.40214375233f, + 41507.43242955043f, + 41526.46489684518f, + 41545.49954513663f, + 41564.536373925075f, + 41583.575382711126f, + 41602.61657099567f, + 41621.659938279874f, + 41640.705484065205f, + 41659.7532078534f, + 41678.803109146495f, + 41697.8551874468f, + 41716.90944225691f, + 41735.96587307971f, + 41755.02447941836f, + 41774.085260776315f, + 41793.1482166573f, + 41812.21334656533f, + 41831.280650004715f, + 41850.350126480014f, + 41869.42177549611f, + 41888.49559655813f, + 41907.571589171515f, + 41926.64975284196f, + 41945.73008707546f, + 41964.812591378286f, + 41983.89726525698f, + 42002.98410821838f, + 42022.07311976959f, + 42041.164299418015f, + 42060.25764667131f, + 42079.35316103742f, + 42098.45084202459f, + 42117.550689141324f, + 42136.652701896404f, + 42155.75687979889f, + 42174.86322235814f, + 42193.97172908376f, + 42213.082399485655f, + 42232.195233074f, + 42251.310229359246f, + 42270.42738785213f, + 42289.546708063644f, + 42308.66818950508f, + 42327.791831687995f, + 42346.91763412423f, + 42366.04559632589f, + 42385.17571780535f, + 42404.307998075295f, + 42423.44243664864f, + 42442.57903303861f, + 42461.71778675867f, + 42480.858697322605f, + 42500.00176424442f, + 42519.146987038446f, + 42538.29436521925f, + 42557.44389830169f, + 42576.59558580088f, + 42595.74942723224f, + 42614.90542211142f, + 42634.06356995438f, + 42653.22387027732f, + 42672.386322596736f, + 42691.55092642938f, + 42710.71768129229f, + 42729.88658670276f, + 42749.05764217836f, + 42768.23084723694f, + 42787.4062013966f, + 42806.58370417574f, + 42825.76335509299f, + 42844.945153667286f, + 42864.129099417805f, + 42883.315191864014f, + 42902.50343052565f, + 42921.69381492269f, + 42940.88634457541f, + 42960.08101900435f, + 42979.2778377303f, + 42998.47680027432f, + 43017.67790615777f, + 43036.881154902236f, + 43056.08654602958f, + 43075.29407906196f, + 43094.50375352177f, + 43113.71556893167f, + 43132.9295248146f, + 43152.14562069376f, + 43171.36385609262f, + 43190.58423053491f, + 43209.80674354462f, + 43229.031394646016f, + 43248.25818336362f, + 43267.487109222224f, + 43286.71817174688f, + 43305.951370462906f, + 43325.18670489588f, + 43344.42417457165f, + 43363.66377901632f, + 43382.90551775626f, + 43402.1493903181f, + 43421.39539622875f, + 43440.64353501535f, + 43459.89380620532f, + 43479.146209326354f, + 43498.40074390638f, + 43517.657409473606f, + 43536.916205556496f, + 43556.177131683784f, + 43575.44018738444f, + 43594.705372187724f, + 43613.972685623135f, + 43633.24212722044f, + 43652.51369650967f, + 43671.78739302109f, + 43691.06321628527f, + 43710.341165833f, + 43729.621241195346f, + 43748.903441903625f, + 43768.18776748941f, + 43787.474217484545f, + 43806.762791421126f, + 43826.0534888315f, + 43845.34630924828f, + 43864.641252204325f, + 43883.938317232765f, + 43903.23750386697f, + 43922.538811640596f, + 43941.84224008751f, + 43961.14778874188f, + 43980.4554571381f, + 43999.765244810835f, + 44019.077151295f, + 44038.391176125755f, + 44057.70731883854f, + 44077.02557896902f, + 44096.34595605314f, + 44115.66844962708f, + 44134.99305922729f, + 44154.319784390456f, + 44173.648624653535f, + 44192.97957955373f, + 44212.31264862849f, + 44231.64783141553f, + 44250.985127452805f, + 44270.32453627854f, + 44289.66605743118f, + 44309.009690449464f, + 44328.355434872356f, + 44347.703290239064f, + 44367.05325608907f, + 44386.40533196211f, + 44405.75951739814f, + 44425.11581193739f, + 44444.47421512033f, + 44463.834726487694f, + 44483.19734558046f, + 44502.56207193984f, + 44521.92890510733f, + 44541.297844624634f, + 44560.66889003373f, + 44580.042040876855f, + 44599.417296696454f, + 44618.794657035265f, + 44638.174121436256f, + 44657.55568944264f, + 44676.93936059787f, + 44696.32513444567f, + 44715.71301053f, + 44735.102988395054f, + 44754.495067585296f, + 44773.88924764542f, + 44793.285528120374f, + 44812.683908555344f, + 44832.08438849578f, + 44851.48696748736f, + 44870.891645076015f, + 44890.298420807914f, + 44909.70729422949f, + 44929.11826488741f, + 44948.531332328566f, + 44967.946496100136f, + 44987.36375574951f, + 45006.783110824326f, + 45026.20456087247f, + 45045.6281054421f, + 45065.05374408157f, + 45084.48147633949f, + 45103.91130176475f, + 45123.34321990643f, + 45142.777230313885f, + 45162.21333253671f, + 45181.65152612473f, + 45201.09181062803f, + 45220.53418559692f, + 45239.978650581965f, + 45259.42520513396f, + 45278.87384880394f, + 45298.32458114319f, + 45317.777401703235f, + 45337.23231003585f, + 45356.68930569302f, + 45376.148388227f, + 45395.60955719027f, + 45415.07281213556f, + 45434.53815261583f, + 45454.00557818428f, + 45473.47508839436f, + 45492.946682799746f, + 45512.42036095436f, + 45531.89612241236f, + 45551.373966728155f, + 45570.85389345636f, + 45590.33590215187f, + 45609.819992369776f, + 45629.30616366544f, + 45648.79441559444f, + 45668.28474771261f, + 45687.777159576006f, + 45707.27165074092f, + 45726.76822076389f, + 45746.26686920169f, + 45765.76759561132f, + 45785.270399550034f, + 45804.7752805753f, + 45824.28223824482f, + 45843.79127211657f, + 45863.30238174872f, + 45882.81556669969f, + 45902.33082652812f, + 45921.84816079293f, + 45941.367569053225f, + 45960.889050868354f, + 45980.41260579793f, + 45999.93823340176f, + 46019.4659332399f, + 46038.99570487266f, + 46058.52754786055f, + 46078.06146176433f, + 46097.597446144995f, + 46117.135500563774f, + 46136.67562458211f, + 46156.2178177617f, + 46175.76207966446f, + 46195.30840985254f, + 46214.85680788833f, + 46234.40727333444f, + 46253.95980575372f, + 46273.51440470924f, + 46293.07106976431f, + 46312.62980048248f, + 46332.1905964275f, + 46351.75345716338f, + 46371.31838225435f, + 46390.88537126487f, + 46410.45442375962f, + 46430.025539303526f, + 46449.59871746173f, + 46469.17395779962f, + 46488.75125988279f, + 46508.33062327707f, + 46527.91204754854f, + 46547.49553226347f, + 46567.0810769884f, + 46586.66868129006f, + 46606.258344735434f, + 46625.850066891726f, + 46645.44384732635f, + 46665.039685606986f, + 46684.6375813015f, + 46704.237533978005f, + 46723.83954320484f, + 46743.44360855057f, + 46763.04972958399f, + 46782.657905874104f, + 46802.26813699017f, + 46821.88042250163f, + 46841.494761978196f, + 46861.111154989776f, + 46880.72960110652f, + 46900.3500998988f, + 46919.9726509372f, + 46939.597253792526f, + 46959.22390803584f, + 46978.8526132384f, + 46998.48336897169f, + 47018.11617480742f, + 47037.75103031755f, + 47057.38793507422f, + 47077.02688864981f, + 47096.66789061694f, + 47116.31094054843f, + 47135.95603801733f, + 47155.60318259692f, + 47175.2523738607f, + 47194.903611382375f, + 47214.5568947359f, + 47234.21222349542f, + 47253.86959723534f, + 47273.52901553025f, + 47293.19047795498f, + 47312.85398408458f, + 47332.519533494306f, + 47352.187125759665f, + 47371.85676045634f, + 47391.52843716029f, + 47411.20215544765f, + 47430.877914894794f, + 47450.5557150783f, + 47470.23555557498f, + 47489.91743596186f, + 47509.6013558162f, + 47529.28731471546f, + 47548.97531223731f, + 47568.66534795967f, + 47588.35742146065f, + 47608.051532318605f, + 47627.74768011208f, + 47647.445864419846f, + 47667.14608482091f, + 47686.848340894474f, + 47706.55263221997f, + 47726.258958377046f, + 47745.96731894555f, + 47765.67771350559f, + 47785.39014163743f, + 47805.104602921594f, + 47824.821096938824f, + 47844.539623270044f, + 47864.26018149643f, + 47883.98277119934f, + 47903.70739196039f, + 47923.43404336137f, + 47943.162724984315f, + 47962.89343641144f, + 47982.62617722522f, + 48002.36094700831f, + 48022.0977453436f, + 48041.83657181417f, + 48061.57742600335f, + 48081.32030749465f, + 48101.065215871815f, + 48120.81215071879f, + 48140.56111161974f, + 48160.31209815905f, + 48180.0651099213f, + 48199.82014649131f, + 48219.57720745407f, + 48239.336292394844f, + 48259.097400899045f, + 48278.86053255234f, + 48298.62568694059f, + 48318.392863649875f, + 48338.16206226648f, + 48357.933282376915f, + 48377.70652356789f, + 48397.48178542632f, + 48417.259067539344f, + 48437.0383694943f, + 48456.819690878765f, + 48476.60303128049f, + 48496.38839028745f, + 48516.17576748783f, + 48535.96516247005f, + 48555.756574822684f, + 48575.550004134566f, + 48595.34544999472f, + 48615.14291199238f, + 48634.94238971699f, + 48654.7438827582f, + 48674.54739070588f, + 48694.35291315008f, + 48714.16044968111f, + 48733.969999889436f, + 48753.78156336576f, + 48773.59513970098f, + 48793.41072848621f, + 48813.22832931277f, + 48833.04794177219f, + 48852.86956545619f, + 48872.69319995672f, + 48892.51884486592f, + 48912.346499776155f, + 48932.176164279976f, + 48952.00783797016f, + 48971.84152043966f, + 48991.677211281676f, + 49011.51491008959f, + 49031.354616456985f, + 49051.196329977654f, + 49071.04005024561f, + 49090.88577685506f, + 49110.73350940041f, + 49130.58324747627f, + 49150.43499067749f, + 49170.28873859906f, + 49190.14449083623f, + 49210.00224698444f, + 49229.86200663932f, + 49249.72376939672f, + 49269.587534852675f, + 49289.45330260345f, + 49309.32107224548f, + 49329.19084337544f, + 49349.06261559019f, + 49368.936388486785f, + 49388.81216166249f, + 49408.689934714785f, + 49428.569707241324f, + 49448.45147883999f, + 49468.33524910886f, + 49488.22101764621f, + 49508.10878405052f, + 49527.99854792047f, + 49547.89030885494f, + 49567.78406645301f, + 49587.67982031398f, + 49607.57757003732f, + 49627.47731522272f, + 49647.37905547007f, + 49667.28279037946f, + 49687.18851955118f, + 49707.09624258571f, + 49727.00595908374f, + 49746.917668646165f, + 49766.83137087407f, + 49786.747065368734f, + 49806.66475173166f, + 49826.58442956452f, + 49846.5060984692f, + 49866.429758047794f, + 49886.35540790258f, + 49906.28304763604f, + 49926.212676850846f, + 49946.14429514988f, + 49966.077902136225f, + 49986.01349741315f, + 50005.951080584135f, + 50025.890651252834f, + 50045.83220902312f, + 50065.77575349907f, + 50085.72128428493f, + 50105.668800985164f, + 50125.61830320443f, + 50145.569790547575f, + 50165.52326261965f, + 50185.4787190259f, + 50205.43615937177f, + 50225.39558326289f, + 50245.3569903051f, + 50265.32038010443f, + 50285.2857522671f, + 50305.25310639953f, + 50325.22244210834f, + 50345.193759000336f, + 50365.16705668252f, + 50385.1423347621f, + 50405.11959284647f, + 50425.09883054322f, + 50445.08004746013f, + 50465.06324320518f, + 50485.04841738654f, + 50505.03556961258f, + 50525.024699491856f, + 50545.01580663313f, + 50565.00889064534f, + 50585.00395113762f, + 50605.00098771933f, + 50624.99999999997f, + 50645.00098758927f, + 50665.00395009713f, + 50685.00888713368f, + 50705.01579830919f, + 50725.024683234165f, + 50745.03554151928f, + 50765.04837277541f, + 50785.06317661362f, + 50805.07995264516f, + 50825.09870048149f, + 50845.11941973424f, + 50865.142110015244f, + 50885.16677093652f, + 50905.19340211028f, + 50925.222003148934f, + 50945.25257366507f, + 50965.28511327147f, + 50985.31962158112f, + 51005.356098207165f, + 51025.39454276298f, + 51045.434954862096f, + 51065.477334118244f, + 51085.521680145364f, + 51105.567992557546f, + 51125.61627096911f, + 51145.66651499454f, + 51165.71872424852f, + 51185.77289834591f, + 51205.82903690178f, + 51225.88713953136f, + 51245.947205850105f, + 51266.00923547362f, + 51286.07322801772f, + 51306.1391830984f, + 51326.207100331856f, + 51346.27697933445f, + 51366.348819722756f, + 51386.42262111351f, + 51406.49838312366f, + 51426.57610537032f, + 51446.655787470794f, + 51466.73742904259f, + 51486.82102970338f, + 51506.90658907105f, + 51526.99410676363f, + 51547.08358239939f, + 51567.17501559674f, + 51587.2684059743f, + 51607.36375315086f, + 51627.461056745415f, + 51647.56031637713f, + 51667.66153166536f, + 51687.76470222966f, + 51707.86982768973f, + 51727.9769076655f, + 51748.085941777055f, + 51768.19692964468f, + 51788.309870888836f, + 51808.42476513017f, + 51828.54161198952f, + 51848.660411087905f, + 51868.781162046515f, + 51888.90386448674f, + 51909.02851803014f, + 51929.155122298485f, + 51949.28367691369f, + 51969.41418149788f, + 51989.54663567335f, + 52009.68103906259f, + 52029.81739128826f, + 52049.95569197321f, + 52070.09594074048f, + 52090.23813721327f, + 52110.38228101499f, + 52130.5283717692f, + 52150.676409099666f, + 52170.82639263033f, + 52190.97832198532f, + 52211.13219678893f, + 52231.288016665654f, + 52251.44578124015f, + 52271.60549013727f, + 52291.76714298204f, + 52311.93073939967f, + 52332.096279015546f, + 52352.26376145525f, + 52372.43318634451f, + 52392.604553309284f, + 52412.777861975665f, + 52432.953111969946f, + 52453.130302918595f, + 52473.30943444827f, + 52493.49050618579f, + 52513.67351775817f, + 52533.858468792605f, + 52554.04535891645f, + 52574.23418775725f, + 52594.42495494274f, + 52614.61766010081f, + 52634.81230285956f, + 52655.00888284723f, + 52675.20739969227f, + 52695.407853023295f, + 52715.6102424691f, + 52735.81456765866f, + 52756.02082822111f, + 52776.229023785796f, + 52796.439153982225f, + 52816.65121844006f, + 52836.86521678917f, + 52857.0811486596f, + 52877.29901368155f, + 52897.518811485425f, + 52917.74054170177f, + 52937.96420396135f, + 52958.18979789508f, + 52978.41732313405f, + 52998.64677930953f, + 53018.87816605298f, + 53039.111482996006f, + 53059.34672977042f, + 53079.58390600819f, + 53099.82301134148f, + 53120.0640454026f, + 53140.30700782406f, + 53160.55189823853f, + 53180.79871627886f, + 53201.04746157809f, + 53221.2981337694f, + 53241.550732486176f, + 53261.805257361964f, + 53282.06170803049f, + 53302.32008412564f, + 53322.58038528149f, + 53342.8426111323f, + 53363.10676131247f, + 53383.3728354566f, + 53403.64083319945f, + 53423.91075417597f, + 53444.18259802126f, + 53464.45636437061f, + 53484.73205285948f, + 53505.0096631235f, + 53525.28919479847f, + 53545.57064752036f, + 53565.85402092533f, + 53586.1393146497f, + 53606.426528329954f, + 53626.715661602764f, + 53647.00671410496f, + 53667.299685473554f, + 53687.59457534572f, + 53707.891383358816f, + 53728.19010915037f, + 53748.490752358055f, + 53768.79331261975f, + 53789.0977895735f, + 53809.404182857485f, + 53829.712492110106f, + 53850.0227169699f, + 53870.33485707559f, + 53890.648912066055f, + 53910.96488158037f, + 53931.28276525774f, + 53951.60256273758f, + 53971.92427365946f, + 53992.24789766311f, + 54012.57343438844f, + 54032.90088347553f, + 54053.23024456462f, + 54073.561517296126f, + 54093.894701310644f, + 54114.22979624891f, + 54134.566801751855f, + 54154.90571746057f, + 54175.246543016314f, + 54195.589278060506f, + 54215.933922234755f, + 54236.280475180814f, + 54256.62893654063f, + 54276.97930595628f, + 54297.331583070045f, + 54317.68576752436f, + 54338.04185896183f, + 54358.399857025215f, + 54378.75976135746f, + 54399.12157160167f, + 54419.48528740111f, + 54439.850908399225f, + 54460.218434239614f, + 54480.587864566056f, + 54500.95919902248f, + 54521.332437253f, + 54541.70757890188f, + 54562.084623613555f, + 54582.46357103264f, + 54602.844420803885f, + 54623.227172572246f, + 54643.61182598281f, + 54663.99838068084f, + 54684.38683631177f, + 54704.7771925212f, + 54725.1694489549f, + 54745.56360525877f, + 54765.95966107893f, + 54786.357616061614f, + 54806.757469853255f, + 54827.15922210044f, + 54847.56287244991f, + 54867.96842054858f, + 54888.375866043534f, + 54908.78520858201f, + 54929.19644781142f, + 54949.60958337932f, + 54970.02461493346f, + 54990.44154212173f, + 55010.86036459218f, + 55031.28108199306f, + 55051.70369397273f, + 55072.12820017975f, + 55092.55460026284f, + 55112.98289387087f, + 55133.41308065288f, + 55153.84516025806f, + 55174.27913233579f, + 55194.714996535586f, + 55215.15275250714f, + 55235.5923999003f, + 55256.033938365086f, + 55276.477367551655f, + 55296.92268711036f, + 55317.369896691685f, + 55337.818995946305f, + 55358.269984525024f, + 55378.72286207883f, + 55399.17762825887f, + 55419.63428271644f, + 55440.09282510301f, + 55460.553255070205f, + 55481.01557226981f, + 55501.479776353764f, + 55521.94586697419f, + 55542.413843783346f, + 55562.883706433655f, + 55583.355454577715f, + 55603.82908786826f, + 55624.30460595821f, + 55644.78200850064f, + 55665.26129514875f, + 55685.742465555944f, + 55706.225519375774f, + 55726.71045626193f, + 55747.197275868275f, + 55767.68597784884f, + 55788.176561857814f, + 55808.66902754953f, + 55829.16337457848f, + 55849.65960259933f, + 55870.15771126689f, + 55890.657700236145f, + 55911.15956916222f, + 55931.66331770041f, + 55952.168945506164f, + 55972.676452235086f, + 55993.185837542944f, + 56013.69710108565f, + 56034.2102425193f, + 56054.72526150012f, + 56075.24215768451f, + 56095.76093072901f, + 56116.28158029034f, + 56136.80410602537f, + 56157.328507591104f, + 56177.85478464474f, + 56198.3829368436f, + 56218.912963845185f, + 56239.44486530714f, + 56259.97864088727f, + 56280.51429024353f, + 56301.05181303404f, + 56321.59120891709f, + 56342.13247755108f, + 56362.675618594614f, + 56383.22063170642f, + 56403.7675165454f, + 56424.31627277061f, + 56444.86690004124f, + 56465.41939801667f, + 56485.973766356394f, + 56506.5300047201f, + 56527.08811276761f, + 56547.6480901589f, + 56568.20993655411f, + 56588.77365161352f, + 56609.339234997584f, + 56629.9066863669f, + 56650.47600538221f, + 56671.04719170442f, + 56691.6202449946f, + 56712.19516491396f, + 56732.77195112387f, + 56753.350603285835f, + 56773.93112106154f, + 56794.51350411282f, + 56815.09775210165f, + 56835.68386469015f, + 56856.27184154063f, + 56876.86168231552f, + 56897.4533866774f, + 56918.04695428902f, + 56938.6423848133f, + 56959.23967791326f, + 56979.83883325211f, + 57000.439850493225f, + 57021.04272930009f, + 57041.64746933637f, + 57062.25407026587f, + 57082.86253175256f, + 57103.47285346055f, + 57124.08503505411f, + 57144.69907619765f, + 57165.31497655575f, + 57185.9327357931f, + 57206.55235357461f, + 57227.173829565276f, + 57247.79716343028f, + 57268.42235483494f, + 57289.04940344473f, + 57309.678308925286f, + 57330.30907094237f, + 57350.94168916191f, + 57371.576163249985f, + 57392.212492872815f, + 57412.850677696784f, + 57433.490717388406f, + 57454.13261161437f, + 57474.77636004149f, + 57495.421962336746f, + 57516.069418167266f, + 57536.718727200314f, + 57557.36988910332f, + 57578.02290354386f, + 57598.67777018964f, + 57619.33448870855f, + 57639.99305876859f, + 57660.65348003794f, + 57681.315752184906f, + 57701.97987487797f, + 57722.64584778573f, + 57743.31367057695f, + 57763.98334292055f, + 57784.65486448557f, + 57805.32823494123f, + 57826.00345395688f, + 57846.680521202026f, + 57867.359436346305f, + 57888.04019905953f, + 57908.72280901163f, + 57929.40726587271f, + 57950.093569313f, + 57970.781719002895f, + 57991.47171461291f, + 58012.16355581375f, + 58032.85724227622f, + 58053.55277367131f, + 58074.25014967013f, + 58094.94936994395f, + 58115.650434164185f, + 58136.35334200239f, + 58157.058093130276f, + 58177.76468721969f, + 58198.47312394264f, + 58219.18340297126f, + 58239.89552397784f, + 58260.60948663482f, + 58281.325290614775f, + 58302.042935590434f, + 58322.76242123468f, + 58343.48374722051f, + 58364.206913221096f, + 58384.93191890975f, + 58405.65876395992f, + 58426.3874480452f, + 58447.11797083934f, + 58467.85033201621f, + 58488.584531249864f, + 58509.32056821446f, + 58530.05844258433f, + 58550.79815403393f, + 58571.539702237875f, + 58592.283086870906f, + 58613.02830760793f, + 58633.77536412398f, + 58654.52425609425f, + 58675.27498319405f, + 58696.02754509888f, + 58716.781941484325f, + 58737.53817202616f, + 58758.296236400274f, + 58779.05613428273f, + 58799.817865349694f, + 58820.5814292775f, + 58841.34682574264f, + 58862.11405442171f, + 58882.883114991484f, + 58903.65400712885f, + 58924.42673051085f, + 58945.201284814684f, + 58965.977669717664f, + 58986.75588489727f, + 59007.53593003111f, + 59028.31780479695f, + 59049.10150887266f, + 59069.8870419363f, + 59090.674403666046f, + 59111.46359374021f, + 59132.25461183726f, + 59153.0474576358f, + 59173.84213081458f, + 59194.63863105247f, + 59215.436958028506f, + 59236.237111421855f, + 59257.03909091183f, + 59277.84289617788f, + 59298.64852689959f, + 59319.455982756685f, + 59340.26526342905f, + 59361.076368596696f, + 59381.88929793976f, + 59402.70405113854f, + 59423.520627873484f, + 59444.33902782514f, + 59465.15925067423f, + 59485.9812961016f, + 59506.80516378825f, + 59527.63085341531f, + 59548.458364664046f, + 59569.28769721586f, + 59590.11885075232f, + 59610.95182495509f, + 59631.78661950601f, + 59652.62323408705f, + 59673.46166838031f, + 59694.30192206803f, + 59715.14399483259f, + 59735.987886356525f, + 59756.83359632248f, + 59777.681124413255f, + 59798.530470311794f, + 59819.38163370116f, + 59840.23461426457f, + 59861.08941168538f, + 59881.94602564707f, + 59902.80445583327f, + 59923.664701927744f, + 59944.52676361438f, + 59965.39064057724f, + 59986.25633250049f, + 60007.12383906844f, + 60027.99315996554f, + 60048.86429487638f, + 60069.73724348569f, + 60090.612005478324f, + 60111.488580539284f, + 60132.36696835371f, + 60153.24716860687f, + 60174.129180984164f, + 60195.01300517115f, + 60215.89864085351f, + 60236.78608771706f, + 60257.67534544775f, + 60278.56641373167f, + 60299.459292255044f, + 60320.35398070425f, + 60341.25047876576f, + 60362.14878612623f, + 60383.04890247242f, + 60403.95082749124f, + 60424.85456086972f, + 60445.76010229504f, + 60466.667451454516f, + 60487.57660803559f, + 60508.48757172584f, + 60529.400342213f, + 60550.31491918489f, + 60571.23130232952f, + 60592.149491335f, + 60613.06948588959f, + 60633.99128568168f, + 60654.914890399785f, + 60675.84029973257f, + 60696.76751336883f, + 60717.69653099749f, + 60738.6273523076f, + 60759.55997698837f, + 60780.49440472912f, + 60801.43063521932f, + 60822.368668148556f, + 60843.308503206565f, + 60864.250140083204f, + 60885.19357846847f, + 60906.138818052495f, + 60927.08585852554f, + 60948.03469957801f, + 60968.98534090042f, + 60989.93778218344f, + 61010.89202311786f, + 61031.84806339462f, + 61052.805902704764f, + 61073.76554073949f, + 61094.726977190134f, + 61115.69021174814f, + 61136.6552441051f, + 61157.62207395274f, + 61178.590700982924f, + 61199.561124887616f, + 61220.53334535895f, + 61241.50736208917f, + 61262.48317477066f, + 61283.46078309594f, + 61304.440186757645f, + 61325.42138544856f, + 61346.40437886158f, + 61367.389166689754f, + 61388.37574862626f, + 61409.36412436439f, + 61430.35429359757f, + 61451.34625601937f, + 61472.3400113235f, + 61493.33555920376f, + 61514.33289935412f, + 61535.33203146867f, + 61556.33295524162f, + 61577.33567036731f, + 61598.34017654024f, + 61619.34647345499f, + 61640.35456080633f, + 61661.3644382891f, + 61682.37610559831f, + 61703.38956242909f, + 61724.40480847669f, + 61745.42184343651f, + 61766.44066700406f, + 61787.46127887499f, + 61808.48367874506f, + 61829.5078663102f, + 61850.533841266435f, + 61871.56160330993f, + 61892.59115213697f, + 61913.62248744399f, + 61934.655608927525f, + 61955.69051628427f, + 61976.72720921102f, + 61997.765687404724f, + 62018.80595056245f, + 62039.847998381374f, + 62060.891830558845f, + 62081.93744679229f, + 62102.9848467793f, + 62124.034030217575f, + 62145.084996804966f, + 62166.137746239416f, + 62187.19227821903f, + 62208.248592442025f, + 62229.30668860674f, + 62250.366566411656f, + 62271.42822555538f, + 62292.49166573663f, + 62313.55688665427f, + 62334.62388800727f, + 62355.69266949476f, + 62376.763230815974f, + 62397.83557167027f, + 62418.909691757144f, + 62439.98559077621f, + 62461.06326842723f, + 62482.14272441005f, + 62503.223958424685f, + 62524.30697017127f, + 62545.39175935003f, + 62566.47832566137f, + 62587.56666880577f, + 62608.65678848388f, + 62629.74868439645f, + 62650.842356244364f, + 62671.93780372862f, + 62693.035026550366f, + 62714.13402441086f, + 62735.23479701148f, + 62756.33734405374f, + 62777.441665239276f, + 62798.54776026985f, + 62819.65562884736f, + 62840.7652706738f, + 62861.87668545132f, + 62882.989872882186f, + 62904.104832668774f, + 62925.2215645136f, + 62946.34006811931f, + 62967.46034318866f, + 62988.582389424526f, + 63009.70620652994f, + 63030.83179420802f, + 63051.95915216204f, + 63073.08828009537f, + 63094.21917771154f, + 63115.351844714154f, + 63136.48628080699f, + 63157.62248569392f, + 63178.760459078956f, + 63199.90020066622f, + 63221.04171015997f, + 63242.18498726457f, + 63263.330031684534f, + 63284.476843124474f, + 63305.62542128914f, + 63326.77576588341f, + 63347.92787661226f, + 63369.08175318081f, + 63390.237395294316f, + 63411.39480265812f, + 63432.553974977716f, + 63453.71491195871f, + 63474.87761330684f, + 63496.04207872794f, + 63517.208307928f, + 63538.37630061312f, + 63559.546056489504f, + 63580.717575263516f, + 63601.89085664161f, + 63623.06590033037f, + 63644.242706036515f, + 63665.42127346687f, + 63686.60160232838f, + 63707.783692328136f, + 63728.967543173334f, + 63750.15315457128f, + 63771.34052622942f, + 63792.52965785532f, + 63813.72054915665f, + 63834.91319984123f, + 63856.10760961698f, + 63877.30377819194f, + 63898.501705274284f, + 63919.7013905723f, + 63940.902833794404f, + 63962.106034649114f, + 63983.310992845094f, + 64004.51770809111f, + 64025.72618009605f, + 64046.93640856894f, + 64068.1483932189f, + 64089.362133755196f, + 64110.57762988719f, + 64131.79488132439f, + 64153.013887776404f, + 64174.23464895297f, + 64195.45716456394f, + 64216.68143431929f, + 64237.90745792911f, + 64259.135235103626f, + 64280.36476555316f, + 64301.59604898817f, + 64322.829085119236f, + 64344.06387365704f, + 64365.3004143124f, + 64386.53870679625f, + 64407.778750819634f, + 64429.02054609372f, + 64450.26409232981f, + 64471.50938923929f, + 64492.75643653371f, + 64514.005233924705f, + 64535.25578112403f, + 64556.50807784358f, + 64577.76212379536f, + 64599.017918691476f, + 64620.27546224417f, + 64641.534754165805f, + 64662.795794168844f, + 64684.058581965895f, + 64705.32311726966f, + 64726.589399792974f, + 64747.857429248776f, + 64769.12720535014f, + 64790.398727810236f, + 64811.671996342375f, + 64832.94701065997f, + 64854.22377047656f, + 64875.502275505794f, + 64896.78252546145f, + 64918.064520057414f, + 64939.34825900768f, + 64960.63374202639f, + 64981.92096882776f, + 65003.209939126165f, + 65024.50065263607f, + 65045.79310907207f, + 65067.08730814886f, + 65088.38324958128f, + 65109.68093308426f, + 65130.980358372864f, + 65152.28152516226f, + 65173.584433167736f, + 65194.8890821047f, + 65216.19547168868f, + 65237.50360163532f, + 65258.81347166035f, + 65280.125081479666f, + 65301.43843080924f, + 65322.75351936518f, + 65344.07034686371f, + 65365.38891302115f, + 65386.70921755396f, + 65408.0312601787f, + 65429.355040612056f, + 65450.68055857082f, + 65472.00781377191f, + 65493.336805932355f, + 65514.66753476928f, + 65535.999999999956f, + 65557.33420134176f, + 65578.67013851217f, + 65600.00781122879f, + 65621.34721920933f, + 65642.68836217163f, + 65664.03123983364f, + 65685.37585191341f, + 65706.72219812914f, + 65728.07027819908f, + 65749.42009184166f, + 65770.7716387754f, + 65792.12491871894f, + 65813.479931391f, + 65834.83667651046f, + 65856.1951537963f, + 65877.5553629676f, + 65898.91730374355f, + 65920.28097584349f, + 65941.64637898684f, + 65963.01351289316f, + 65984.38237728208f, + 66005.75297187339f, + 66027.12529638696f, + 66048.4993505428f, + 66069.87513406102f, + 66091.25264666184f, + 66112.63188806562f, + 66134.01285799277f, + 66155.39555616389f, + 66176.77998229963f, + 66198.1661361208f, + 66219.55401734827f, + 66240.9436257031f, + 66262.33496090639f, + 66283.7280226794f, + 66305.12281074344f, + 66326.51932482002f, + 66347.9175646307f, + 66369.31752989716f, + 66390.71922034123f, + 66412.12263568479f, + 66433.52777564988f, + 66454.93463995864f, + 66476.34322833332f, + 66497.75354049628f, + 66519.16557617f, + 66540.57933507704f, + 66561.99481694012f, + 66583.41202148204f, + 66604.83094842573f, + 66626.25159749422f, + 66647.67396841063f, + 66669.09806089824f, + 66690.52387468038f, + 66711.95140948056f, + 66733.38066502237f, + 66754.81164102948f, + 66776.24433722571f, + 66797.67875333499f, + 66819.11488908132f, + 66840.55274418888f, + 66861.9923183819f, + 66883.43361138474f, + 66904.87662292189f, + 66926.3213527179f, + 66947.7678004975f, + 66969.21596598547f, + 66990.66584890673f, + 67012.1174489863f, + 67033.57076594933f, + 67055.02579952106f, + 67076.48254942682f, + 67097.94101539208f, + 67119.40119714243f, + 67140.86309440355f, + 67162.32670690122f, + 67183.79203436135f, + 67205.25907650996f, + 67226.72783307315f, + 67248.19830377717f, + 67269.67048834835f, + 67291.14438651314f, + 67312.61999799809f, + 67334.09732252988f, + 67355.5763598353f, + 67377.05710964119f, + 67398.53957167457f, + 67420.02374566255f, + 67441.50963133233f, + 67462.99722841123f, + 67484.48653662669f, + 67505.97755570622f, + 67527.4702853775f, + 67548.96472536826f, + 67570.46087540637f, + 67591.9587352198f, + 67613.45830453663f, + 67634.95958308503f, + 67656.46257059333f, + 67677.9672667899f, + 67699.47367140325f, + 67720.98178416202f, + 67742.49160479492f, + 67764.0031330308f, + 67785.51636859858f, + 67807.03131122731f, + 67828.54796064617f, + 67850.0663165844f, + 67871.58637877138f, + 67893.10814693659f, + 67914.63162080961f, + 67936.15680012014f, + 67957.68368459797f, + 67979.21227397301f, + 68000.74256797526f, + 68022.27456633488f, + 68043.80826878206f, + 68065.34367504714f, + 68086.88078486058f, + 68108.41959795292f, + 68129.96011405479f, + 68151.50233289697f, + 68173.04625421032f, + 68194.59187772583f, + 68216.13920317456f, + 68237.6882302877f, + 68259.23895879654f, + 68280.79138843248f, + 68302.34551892703f, + 68323.90135001179f, + 68345.45888141848f, + 68367.01811287891f, + 68388.57904412503f, + 68410.14167488884f, + 68431.7060049025f, + 68453.27203389826f, + 68474.83976160845f, + 68496.40918776554f, + 68517.98031210208f, + 68539.55313435073f, + 68561.12765424428f, + 68582.70387151558f, + 68604.28178589763f, + 68625.8613971235f, + 68647.44270492639f, + 68669.0257090396f, + 68690.61040919652f, + 68712.19680513066f, + 68733.78489657563f, + 68755.37468326512f, + 68776.966164933f, + 68798.55934131313f, + 68820.15421213959f, + 68841.75077714647f, + 68863.34903606804f, + 68884.94898863863f, + 68906.55063459268f, + 68928.15397366474f, + 68949.75900558944f, + 68971.36573010158f, + 68992.97414693599f, + 69014.58425582763f, + 69036.19605651159f, + 69057.80954872302f, + 69079.4247321972f, + 69101.04160666953f, + 69122.66017187547f, + 69144.2804275506f, + 69165.90237343062f, + 69187.52600925133f, + 69209.15133474862f, + 69230.77834965847f, + 69252.40705371699f, + 69274.0374466604f, + 69295.669528225f, + 69317.30329814719f, + 69338.9387561635f, + 69360.57590201053f, + 69382.214735425f, + 69403.85525614375f, + 69425.49746390368f, + 69447.14135844183f, + 69468.78693949533f, + 69490.4342068014f, + 69512.08316009739f, + 69533.73379912072f, + 69555.38612360893f, + 69577.04013329967f, + 69598.69582793067f, + 69620.3532072398f, + 69642.01227096497f, + 69663.67301884426f, + 69685.33545061579f, + 69706.99956601784f, + 69728.66536478874f, + 69750.33284666698f, + 69772.00201139107f, + 69793.67285869969f, + 69815.34538833161f, + 69837.01960002567f, + 69858.69549352085f, + 69880.3730685562f, + 69902.0523248709f, + 69923.73326220422f, + 69945.41588029549f, + 69967.10017888421f, + 69988.78615770994f, + 70010.47381651236f, + 70032.16315503122f, + 70053.8541730064f, + 70075.54687017787f, + 70097.24124628572f, + 70118.93730107011f, + 70140.6350342713f, + 70162.33444562969f, + 70184.03553488574f, + 70205.73830178002f, + 70227.44274605322f, + 70249.1488674461f, + 70270.85666569954f, + 70292.56614055451f, + 70314.2772917521f, + 70335.9901190335f, + 70357.70462213994f, + 70379.42080081282f, + 70401.13865479361f, + 70422.85818382389f, + 70444.57938764534f, + 70466.30226599972f, + 70488.02681862892f, + 70509.75304527488f, + 70531.48094567971f, + 70553.21051958555f, + 70574.9417667347f, + 70596.6746868695f, + 70618.40927973246f, + 70640.1455450661f, + 70661.8834826131f, + 70683.62309211626f, + 70705.36437331841f, + 70727.10732596253f, + 70748.85194979167f, + 70770.59824454901f, + 70792.34620997778f, + 70814.09584582137f, + 70835.84715182323f, + 70857.6001277269f, + 70879.35477327603f, + 70901.11108821441f, + 70922.86907228586f, + 70944.62872523433f, + 70966.39004680388f, + 70988.15303673863f, + 71009.91769478285f, + 71031.68402068088f, + 71053.45201417715f, + 71075.22167501619f, + 71096.99300294266f, + 71118.76599770127f, + 71140.54065903684f, + 71162.31698669434f, + 71184.09498041874f, + 71205.87463995522f, + 71227.65596504895f, + 71249.4389554453f, + 71271.22361088963f, + 71293.00993112748f, + 71314.79791590448f, + 71336.5875649663f, + 71358.37887805876f, + 71380.17185492777f, + 71401.96649531931f, + 71423.76279897949f, + 71445.56076565449f, + 71467.3603950906f, + 71489.16168703421f, + 71510.96464123181f, + 71532.76925742996f, + 71554.57553537536f, + 71576.38347481475f, + 71598.19307549503f, + 71620.00433716313f, + 71641.81725956615f, + 71663.63184245121f, + 71685.4480855656f, + 71707.26598865664f, + 71729.0855514718f, + 71750.90677375859f, + 71772.72965526467f, + 71794.55419573777f, + 71816.38039492571f, + 71838.20825257644f, + 71860.03776843796f, + 71881.86894225838f, + 71903.70177378594f, + 71925.53626276893f, + 71947.37240895575f, + 71969.2102120949f, + 71991.04967193498f, + 72012.89078822469f, + 72034.73356071279f, + 72056.57798914817f, + 72078.42407327982f, + 72100.2718128568f, + 72122.12120762825f, + 72143.97225734347f, + 72165.8249617518f, + 72187.67932060269f, + 72209.53533364569f, + 72231.39300063043f, + 72253.25232130665f, + 72275.11329542418f, + 72296.97592273295f, + 72318.84020298296f, + 72340.70613592434f, + 72362.57372130727f, + 72384.4429588821f, + 72406.31384839918f, + 72428.18638960904f, + 72450.06058226222f, + 72471.93642610943f, + 72493.81392090143f, + 72515.6930663891f, + 72537.57386232339f, + 72559.45630845535f, + 72581.34040453614f, + 72603.22615031699f, + 72625.11354554925f, + 72647.00258998433f, + 72668.89328337376f, + 72690.78562546917f, + 72712.67961602227f, + 72734.57525478485f, + 72756.47254150882f, + 72778.37147594614f, + 72800.27205784894f, + 72822.17428696936f, + 72844.07816305969f, + 72865.98368587228f, + 72887.8908551596f, + 72909.79967067418f, + 72931.7101321687f, + 72953.62223939585f, + 72975.53599210849f, + 72997.45139005952f, + 73019.36843300196f, + 73041.28712068892f, + 73063.20745287361f, + 73085.1294293093f, + 73107.05304974939f, + 73128.97831394733f, + 73150.90522165672f, + 73172.83377263122f, + 73194.76396662457f, + 73216.69580339061f, + 73238.62928268328f, + 73260.56440425663f, + 73282.50116786477f, + 73304.4395732619f, + 73326.37962020234f, + 73348.32130844049f, + 73370.26463773084f, + 73392.20960782796f, + 73414.15621848653f, + 73436.10446946132f, + 73458.05436050717f, + 73480.00589137906f, + 73501.95906183199f, + 73523.91387162112f, + 73545.87032050166f, + 73567.82840822893f, + 73589.78813455833f, + 73611.74949924536f, + 73633.71250204562f, + 73655.67714271475f, + 73677.64342100856f, + 73699.61133668288f, + 73721.58088949369f, + 73743.55207919702f, + 73765.524905549f, + 73787.49936830586f, + 73809.4754672239f, + 73831.45320205955f, + 73853.43257256929f, + 73875.41357850972f, + 73897.3962196375f, + 73919.38049570941f, + 73941.36640648231f, + 73963.35395171314f, + 73985.34313115895f, + 74007.33394457687f, + 74029.32639172411f, + 74051.32047235797f, + 74073.31618623588f, + 74095.3135331153f, + 74117.31251275384f, + 74139.31312490914f, + 74161.31536933898f, + 74183.31924580119f, + 74205.32475405373f, + 74227.33189385463f, + 74249.34066496199f, + 74271.35106713403f, + 74293.36310012905f, + 74315.37676370544f, + 74337.39205762166f, + 74359.4089816363f, + 74381.427535508f, + 74403.4477189955f, + 74425.46953185767f, + 74447.4929738534f, + 74469.5180447417f, + 74491.54474428168f, + 74513.57307223254f, + 74535.60302835355f, + 74557.63461240409f, + 74579.6678241436f, + 74601.70266333164f, + 74623.73912972784f, + 74645.77722309194f, + 74667.81694318372f, + 74689.85828976311f, + 74711.9012625901f, + 74733.94586142474f, + 74755.99208602723f, + 74778.0399361578f, + 74800.08941157682f, + 74822.1405120447f, + 74844.19323732196f, + 74866.24758716923f, + 74888.30356134719f, + 74910.36115961662f, + 74932.42038173841f, + 74954.4812274735f, + 74976.54369658297f, + 74998.60778882793f, + 75020.6735039696f, + 75042.74084176932f, + 75064.80980198846f, + 75086.88038438853f, + 75108.9525887311f, + 75131.02641477782f, + 75153.10186229047f, + 75175.17893103085f, + 75197.25762076092f, + 75219.33793124268f, + 75241.41986223822f, + 75263.50341350974f, + 75285.5885848195f, + 75307.67537592987f, + 75329.76378660332f, + 75351.85381660235f, + 75373.94546568961f, + 75396.0387336278f, + 75418.13362017972f, + 75440.23012510825f, + 75462.32824817636f, + 75484.42798914711f, + 75506.52934778365f, + 75528.63232384919f, + 75550.73691710707f, + 75572.8431273207f, + 75594.95095425354f, + 75617.0603976692f, + 75639.17145733131f, + 75661.28413300365f, + 75683.39842445003f, + 75705.5143314344f, + 75727.63185372074f, + 75749.75099107318f, + 75771.87174325586f, + 75793.99411003308f, + 75816.11809116918f, + 75838.24368642858f, + 75860.37089557585f, + 75882.49971837556f, + 75904.63015459242f, + 75926.76220399122f, + 75948.89586633682f, + 75971.03114139418f, + 75993.16802892832f, + 76015.3065287044f, + 76037.4466404876f, + 76059.58836404321f, + 76081.73169913665f, + 76103.87664553335f, + 76126.02320299888f, + 76148.17137129887f, + 76170.32115019904f, + 76192.4725394652f, + 76214.62553886326f, + 76236.78014815917f, + 76258.93636711901f, + 76281.09419550892f, + 76303.25363309514f, + 76325.41467964397f, + 76347.57733492184f, + 76369.74159869523f, + 76391.90747073069f, + 76414.07495079488f, + 76436.24403865456f, + 76458.41473407655f, + 76480.58703682775f, + 76502.76094667517f, + 76524.93646338588f, + 76547.11358672705f, + 76569.29231646592f, + 76591.47265236982f, + 76613.65459420616f, + 76635.83814174247f, + 76658.02329474631f, + 76680.21005298535f, + 76702.39841622734f, + 76724.58838424014f, + 76746.77995679164f, + 76768.97313364987f, + 76791.1679145829f, + 76813.3642993589f, + 76835.56228774616f, + 76857.76187951297f, + 76879.9630744278f, + 76902.16587225911f, + 76924.37027277553f, + 76946.57627574573f, + 76968.78388093844f, + 76990.99308812252f, + 77013.2038970669f, + 77035.41630754055f, + 77057.63031931262f, + 77079.84593215224f, + 77102.0631458287f, + 77124.28196011129f, + 77146.50237476948f, + 77168.72438957276f, + 77190.94800429072f, + 77213.17321869303f, + 77235.40003254944f, + 77257.6284456298f, + 77279.85845770403f, + 77302.09006854212f, + 77324.32327791417f, + 77346.55808559034f, + 77368.79449134089f, + 77391.03249493614f, + 77413.27209614652f, + 77435.51329474253f, + 77457.75609049473f, + 77480.0004831738f, + 77502.2464725505f, + 77524.49405839563f, + 77546.74324048011f, + 77568.99401857494f, + 77591.2463924512f, + 77613.50036188003f, + 77635.75592663266f, + 77658.01308648044f, + 77680.27184119476f, + 77702.53219054709f, + 77724.79413430902f, + 77747.0576722522f, + 77769.32280414834f, + 77791.58952976925f, + 77813.85784888684f, + 77836.12776127306f, + 77858.3992667f, + 77880.67236493979f, + 77902.94705576463f, + 77925.22333894683f, + 77947.50121425878f, + 77969.78068147293f, + 77992.06174036184f, + 78014.34439069813f, + 78036.62863225449f, + 78058.91446480375f, + 78081.20188811873f, + 78103.49090197241f, + 78125.78150613782f, + 78148.07370038806f, + 78170.36748449634f, + 78192.66285823593f, + 78214.95982138017f, + 78237.2583737025f, + 78259.55851497645f, + 78281.86024497561f, + 78304.16356347366f, + 78326.46847024436f, + 78348.77496506154f, + 78371.08304769913f, + 78393.3927179311f, + 78415.70397553158f, + 78438.0168202747f, + 78460.3312519347f, + 78482.6472702859f, + 78504.96487510273f, + 78527.28406615963f, + 78549.6048432312f, + 78571.92720609205f, + 78594.25115451691f, + 78616.5766882806f, + 78638.90380715799f, + 78661.23251092403f, + 78683.56279935378f, + 78705.89467222235f, + 78728.22812930495f, + 78750.56317037686f, + 78772.89979521342f, + 78795.2380035901f, + 78817.5777952824f, + 78839.91917006593f, + 78862.26212771636f, + 78884.60666800945f, + 78906.95279072104f, + 78929.30049562705f, + 78951.64978250346f, + 78974.00065112638f, + 78996.35310127193f, + 79018.70713271636f, + 79041.06274523598f, + 79063.41993860717f, + 79085.77871260644f, + 79108.13906701028f, + 79130.50100159539f, + 79152.86451613842f, + 79175.22961041618f, + 79197.59628420553f, + 79219.96453728342f, + 79242.33436942687f, + 79264.70578041299f, + 79287.07877001894f, + 79309.45333802201f, + 79331.82948419951f, + 79354.20720832888f, + 79376.58651018758f, + 79398.96738955322f, + 79421.34984620343f, + 79443.73387991595f, + 79466.11949046858f, + 79488.50667763922f, + 79510.89544120582f, + 79533.28578094643f, + 79555.67769663916f, + 79578.07118806223f, + 79600.4662549939f, + 79622.86289721253f, + 79645.26111449653f, + 79667.66090662447f, + 79690.06227337488f, + 79712.46521452646f, + 79734.86972985794f, + 79757.27581914813f, + 79779.68348217595f, + 79802.09271872038f, + 79824.50352856045f, + 79846.91591147533f, + 79869.3298672442f, + 79891.74539564634f, + 79914.16249646115f, + 79936.58116946805f, + 79959.00141444655f, + 79981.42323117626f, + 80003.84661943685f, + 80026.27157900808f, + 80048.69810966977f, + 80071.12621120183f, + 80093.55588338424f, + 80115.98712599705f, + 80138.41993882041f, + 80160.85432163453f, + 80183.29027421969f, + 80205.72779635628f, + 80228.16688782471f, + 80250.60754840553f, + 80273.04977787934f, + 80295.49357602678f, + 80317.93894262865f, + 80340.38587746573f, + 80362.83438031895f, + 80385.28445096928f, + 80407.73608919779f, + 80430.1892947856f, + 80452.64406751392f, + 80475.10040716403f, + 80497.55831351732f, + 80520.01778635521f, + 80542.47882545921f, + 80564.94143061092f, + 80587.405601592f, + 80609.8713381842f, + 80632.33864016933f, + 80654.8075073293f, + 80677.27793944607f, + 80699.74993630168f, + 80722.22349767828f, + 80744.69862335804f, + 80767.17531312324f, + 80789.65356675624f, + 80812.13338403947f, + 80834.6147647554f, + 80857.09770868665f, + 80879.58221561585f, + 80902.06828532573f, + 80924.5559175991f, + 80947.04511221882f, + 80969.53586896788f, + 80992.02818762927f, + 81014.52206798614f, + 81037.01750982161f, + 81059.514512919f, + 81082.01307706161f, + 81104.51320203283f, + 81127.01488761618f, + 81149.5181335952f, + 81172.0229397535f, + 81194.5293058748f, + 81217.0372317429f, + 81239.54671714164f, + 81262.05776185496f, + 81284.57036566685f, + 81307.0845283614f, + 81329.60024972277f, + 81352.11752953519f, + 81374.63636758295f, + 81397.15676365045f, + 81419.67871752213f, + 81442.20222898253f, + 81464.72729781622f, + 81487.25392380793f, + 81509.78210674238f, + 81532.3118464044f, + 81554.8431425789f, + 81577.37599505084f, + 81599.91040360527f, + 81622.44636802733f, + 81644.98388810222f, + 81667.52296361518f, + 81690.06359435158f, + 81712.60578009684f, + 81735.14952063645f, + 81757.69481575597f, + 81780.24166524105f, + 81802.79006887741f, + 81825.34002645082f, + 81847.89153774717f, + 81870.44460255238f, + 81892.99922065248f, + 81915.5553918335f, + 81938.11311588167f, + 81960.67239258319f, + 81983.23322172434f, + 82005.79560309154f, + 82028.35953647122f, + 82050.9250216499f, + 82073.49205841421f, + 82096.06064655079f, + 82118.63078584638f, + 82141.20247608784f, + 82163.77571706203f, + 82186.35050855593f, + 82208.92685035657f, + 82231.50474225105f, + 82254.08418402658f, + 82276.66517547039f, + 82299.24771636983f, + 82321.83180651232f, + 82344.4174456853f, + 82367.00463367635f, + 82389.59337027307f, + 82412.18365526316f, + 82434.77548843439f, + 82457.3688695746f, + 82479.9637984717f, + 82502.56027491367f, + 82525.1582986886f, + 82547.7578695846f, + 82570.35898738986f, + 82592.96165189268f, + 82615.5658628814f, + 82638.17162014442f, + 82660.77892347026f, + 82683.38777264747f, + 82705.99816746471f, + 82728.61010771066f, + 82751.22359317412f, + 82773.83862364394f, + 82796.45519890904f, + 82819.07331875843f, + 82841.69298298119f, + 82864.31419136643f, + 82886.93694370337f, + 82909.56123978132f, + 82932.18707938964f, + 82954.81446231774f, + 82977.44338835512f, + 83000.07385729137f, + 83022.70586891612f, + 83045.33942301909f, + 83067.97451939009f, + 83090.61115781896f, + 83113.24933809563f, + 83135.8890600101f, + 83158.53032335246f, + 83181.17312791286f, + 83203.8174734815f, + 83226.46335984867f, + 83249.11078680474f, + 83271.75975414013f, + 83294.41026164537f, + 83317.062309111f, + 83339.7158963277f, + 83362.37102308616f, + 83385.02768917716f, + 83407.68589439159f, + 83430.34563852036f, + 83453.00692135448f, + 83475.669742685f, + 83498.33410230308f, + 83520.99999999994f, + 83543.66743556687f, + 83566.33640879519f, + 83589.00691947635f, + 83611.67896740185f, + 83634.35255236324f, + 83657.02767415217f, + 83679.70433256036f, + 83702.38252737955f, + 83725.06225840164f, + 83747.74352541851f, + 83770.42632822218f, + 83793.11066660468f, + 83815.79654035816f, + 83838.48394927483f, + 83861.17289314694f, + 83883.86337176684f, + 83906.55538492696f, + 83929.24893241975f, + 83951.9440140378f, + 83974.6406295737f, + 83997.33877882015f, + 84020.03846156993f, + 84042.73967761586f, + 84065.44242675084f, + 84088.14670876783f, + 84110.85252345992f, + 84133.55987062017f, + 84156.2687500418f, + 84178.97916151803f, + 84201.6911048422f, + 84224.40457980771f, + 84247.119586208f, + 84269.83612383662f, + 84292.55419248715f, + 84315.27379195328f, + 84337.99492202874f, + 84360.71758250732f, + 84383.44177318295f, + 84406.16749384951f, + 84428.89474430107f, + 84451.62352433169f, + 84474.35383373554f, + 84497.08567230683f, + 84519.81903983987f, + 84542.553936129f, + 84565.29036096868f, + 84588.0283141534f, + 84610.76779547772f, + 84633.50880473628f, + 84656.25134172381f, + 84678.99540623507f, + 84701.74099806492f, + 84724.48811700825f, + 84747.23676286006f, + 84769.9869354154f, + 84792.73863446941f, + 84815.49185981725f, + 84838.2466112542f, + 84861.00288857557f, + 84883.76069157677f, + 84906.52002005326f, + 84929.28087380057f, + 84952.0432526143f, + 84974.80715629015f, + 84997.5725846238f, + 85020.33953741111f, + 85043.10801444795f, + 85065.87801553024f, + 85088.64954045399f, + 85111.4225890153f, + 85134.19716101032f, + 85156.97325623524f, + 85179.75087448637f, + 85202.53001556007f, + 85225.31067925273f, + 85248.09286536086f, + 85270.87657368102f, + 85293.66180400981f, + 85316.44855614395f, + 85339.23682988019f, + 85362.02662501535f, + 85384.81794134635f, + 85407.61077867013f, + 85430.40513678372f, + 85453.20101548426f, + 85475.99841456886f, + 85498.7973338348f, + 85521.59777307935f, + 85544.3997320999f, + 85567.2032106939f, + 85590.00820865881f, + 85612.81472579224f, + 85635.62276189183f, + 85658.43231675526f, + 85681.24339018033f, + 85704.05598196488f, + 85726.8700919068f, + 85749.68571980408f, + 85772.50286545476f, + 85795.32152865696f, + 85818.14170920885f, + 85840.96340690868f, + 85863.78662155475f, + 85886.61135294545f, + 85909.43760087922f, + 85932.26536515457f, + 85955.09464557009f, + 85977.92544192442f, + 86000.75775401627f, + 86023.59158164443f, + 86046.42692460775f, + 86069.26378270512f, + 86092.10215573556f, + 86114.94204349807f, + 86137.7834457918f, + 86160.62636241592f, + 86183.47079316968f, + 86206.31673785238f, + 86229.1641962634f, + 86252.0131682022f, + 86274.8636534683f, + 86297.71565186126f, + 86320.56916318073f, + 86343.42418722642f, + 86366.28072379812f, + 86389.13877269567f, + 86411.99833371898f, + 86434.85940666801f, + 86457.72199134283f, + 86480.58608754353f, + 86503.45169507028f, + 86526.31881372335f, + 86549.18744330303f, + 86572.05758360968f, + 86594.92923444376f, + 86617.80239560577f, + 86640.67706689627f, + 86663.5532481159f, + 86686.43093906538f, + 86709.31013954544f, + 86732.19084935696f, + 86755.07306830082f, + 86777.95679617795f, + 86800.84203278944f, + 86823.72877793635f, + 86846.61703141985f, + 86869.50679304118f, + 86892.39806260161f, + 86915.29083990252f, + 86938.1851247453f, + 86961.08091693149f, + 86983.97821626259f, + 87006.87702254027f, + 87029.77733556618f, + 87052.67915514208f, + 87075.5824810698f, + 87098.48731315118f, + 87121.39365118822f, + 87144.3014949829f, + 87167.21084433729f, + 87190.12169905353f, + 87213.03405893384f, + 87235.9479237805f, + 87258.86329339583f, + 87281.78016758224f, + 87304.69854614217f, + 87327.61842887818f, + 87350.53981559286f, + 87373.46270608885f, + 87396.3871001689f, + 87419.31299763577f, + 87442.24039829234f, + 87465.16930194154f, + 87488.09970838632f, + 87511.03161742973f, + 87533.9650288749f, + 87556.89994252501f, + 87579.83635818327f, + 87602.77427565302f, + 87625.71369473761f, + 87648.65461524049f, + 87671.59703696515f, + 87694.54095971514f, + 87717.4863832941f, + 87740.43330750574f, + 87763.38173215378f, + 87786.33165704206f, + 87809.28308197446f, + 87832.23600675492f, + 87855.19043118745f, + 87878.14635507615f, + 87901.10377822515f, + 87924.06270043863f, + 87947.02312152089f, + 87969.98504127625f, + 87992.94845950909f, + 88015.9133760239f, + 88038.87979062517f, + 88061.84770311751f, + 88084.81711330556f, + 88107.78802099405f, + 88130.76042598773f, + 88153.73432809146f, + 88176.70972711014f, + 88199.68662284875f, + 88222.6650151123f, + 88245.6449037059f, + 88268.62628843471f, + 88291.60916910395f, + 88314.5935455189f, + 88337.57941748491f, + 88360.56678480741f, + 88383.55564729185f, + 88406.5460047438f, + 88429.53785696882f, + 88452.53120377261f, + 88475.52604496089f, + 88498.52238033945f, + 88521.52020971413f, + 88544.51953289087f, + 88567.52034967564f, + 88590.5226598745f, + 88613.52646329353f, + 88636.53175973892f, + 88659.5385490169f, + 88682.54683093374f, + 88705.55660529585f, + 88728.56787190959f, + 88751.58063058149f, + 88774.59488111807f, + 88797.61062332596f, + 88820.62785701183f, + 88843.6465819824f, + 88866.66679804446f, + 88889.68850500489f, + 88912.71170267061f, + 88935.73639084859f, + 88958.7625693459f, + 88981.79023796963f, + 89004.81939652696f, + 89027.85004482511f, + 89050.88218267141f, + 89073.9158098732f, + 89096.95092623789f, + 89119.98753157297f, + 89143.025625686f, + 89166.06520838456f, + 89189.10627947636f, + 89212.14883876909f, + 89235.19288607058f, + 89258.23842118867f, + 89281.28544393127f, + 89304.33395410638f, + 89327.38395152202f, + 89350.4354359863f, + 89373.4884073074f, + 89396.54286529354f, + 89419.598809753f, + 89442.65624049417f, + 89465.71515732541f, + 89488.77556005522f, + 89511.83744849212f, + 89534.90082244476f, + 89557.96568172173f, + 89581.03202613181f, + 89604.09985548374f, + 89627.1691695864f, + 89650.23996824867f, + 89673.31225127954f, + 89696.38601848802f, + 89719.4612696832f, + 89742.53800467425f, + 89765.61622327036f, + 89788.69592528083f, + 89811.77711051499f, + 89834.85977878221f, + 89857.94392989198f, + 89881.0295636538f, + 89904.11667987728f, + 89927.20527837201f, + 89950.29535894774f, + 89973.38692141422f, + 89996.47996558127f, + 90019.57449125877f, + 90042.67049825669f, + 90065.76798638502f, + 90088.86695545384f, + 90111.96740527326f, + 90135.06933565348f, + 90158.17274640476f, + 90181.2776373374f, + 90204.3840082618f, + 90227.49185898836f, + 90250.60118932759f, + 90273.71199909004f, + 90296.82428808633f, + 90319.93805612714f, + 90343.05330302319f, + 90366.1700285853f, + 90389.2882326243f, + 90412.40791495114f, + 90435.52907537678f, + 90458.65171371226f, + 90481.77582976868f, + 90504.90142335721f, + 90528.02849428906f, + 90551.1570423755f, + 90574.28706742791f, + 90597.41856925764f, + 90620.5515476762f, + 90643.68600249507f, + 90666.82193352585f, + 90689.95934058019f, + 90713.09822346977f, + 90736.23858200636f, + 90759.3804160018f, + 90782.52372526795f, + 90805.66850961676f, + 90828.81476886023f, + 90851.96250281043f, + 90875.11171127946f, + 90898.26239407953f, + 90921.41455102284f, + 90944.56818192174f, + 90967.72328658856f, + 90990.87986483572f, + 91014.03791647572f, + 91037.19744132107f, + 91060.35843918439f, + 91083.52090987834f, + 91106.68485321563f, + 91129.85026900904f, + 91153.0171570714f, + 91176.18551721562f, + 91199.35534925465f, + 91222.52665300149f, + 91245.69942826925f, + 91268.87367487104f, + 91292.04939262004f, + 91315.22658132955f, + 91338.40524081283f, + 91361.58537088329f, + 91384.76697135434f, + 91407.95004203948f, + 91431.13458275225f, + 91454.32059330626f, + 91477.50807351517f, + 91500.69702319271f, + 91523.88744215269f, + 91547.07933020893f, + 91570.27268717533f, + 91593.46751286586f, + 91616.66380709453f, + 91639.86156967544f, + 91663.06080042271f, + 91686.26149915057f, + 91709.46366567322f, + 91732.66729980502f, + 91755.87240136032f, + 91779.07897015357f, + 91802.28700599924f, + 91825.49650871192f, + 91848.70747810617f, + 91871.91991399668f, + 91895.13381619815f, + 91918.34918452542f, + 91941.56601879328f, + 91964.78431881666f, + 91988.0040844105f, + 92011.22531538982f, + 92034.44801156971f, + 92057.67217276528f, + 92080.89779879176f, + 92104.12488946435f, + 92127.35344459841f, + 92150.58346400928f, + 92173.81494751238f, + 92197.04789492322f, + 92220.28230605731f, + 92243.51818073026f, + 92266.75551875774f, + 92289.99431995547f, + 92313.2345841392f, + 92336.47631112477f, + 92359.71950072808f, + 92382.96415276507f, + 92406.21026705173f, + 92429.45784340418f, + 92452.70688163847f, + 92475.95738157081f, + 92499.20934301744f, + 92522.46276579465f, + 92545.7176497188f, + 92568.9739946063f, + 92592.23180027361f, + 92615.49106653726f, + 92638.75179321383f, + 92662.01398011995f, + 92685.27762707233f, + 92708.54273388773f, + 92731.80930038294f, + 92755.07732637487f, + 92778.34681168041f, + 92801.61775611658f, + 92824.89015950038f, + 92848.16402164895f, + 92871.43934237942f, + 92894.71612150903f, + 92917.99435885502f, + 92941.27405423473f, + 92964.55520746557f, + 92987.83781836496f, + 93011.12188675042f, + 93034.40741243947f, + 93057.69439524977f, + 93080.98283499895f, + 93104.27273150477f, + 93127.564084585f, + 93150.8568940575f, + 93174.15115974014f, + 93197.44688145092f, + 93220.7440590078f, + 93244.04269222889f, + 93267.34278093232f, + 93290.64432493623f, + 93313.94732405891f, + 93337.25177811863f, + 93360.55768693375f, + 93383.8650503227f, + 93407.17386810391f, + 93430.48414009594f, + 93453.79586611736f, + 93477.10904598678f, + 93500.42367952294f, + 93523.73976654456f, + 93547.05730687045f, + 93570.37630031949f, + 93593.69674671057f, + 93617.0186458627f, + 93640.3419975949f, + 93663.66680172624f, + 93686.99305807588f, + 93710.32076646303f, + 93733.64992670694f, + 93756.98053862691f, + 93780.31260204234f, + 93803.64611677264f, + 93826.9810826373f, + 93850.31749945584f, + 93873.65536704786f, + 93896.99468523303f, + 93920.33545383104f, + 93943.67767266167f, + 93967.0213415447f, + 93990.36646030005f, + 94013.71302874765f, + 94037.06104670743f, + 94060.4105139995f, + 94083.7614304439f, + 94107.11379586085f, + 94130.46761007051f, + 94153.82287289316f, + 94177.17958414911f, + 94200.53774365876f, + 94223.89735124253f, + 94247.25840672091f, + 94270.62090991443f, + 94293.98486064372f, + 94317.35025872942f, + 94340.71710399224f, + 94364.08539625294f, + 94387.45513533235f, + 94410.82632105134f, + 94434.19895323085f, + 94457.57303169188f, + 94480.94855625545f, + 94504.32552674267f, + 94527.70394297468f, + 94551.08380477272f, + 94574.46511195804f, + 94597.84786435193f, + 94621.23206177581f, + 94644.61770405111f, + 94668.00479099927f, + 94691.39332244187f, + 94714.78329820049f, + 94738.1747180968f, + 94761.56758195249f, + 94784.9618895893f, + 94808.35764082908f, + 94831.7548354937f, + 94855.15347340508f, + 94878.55355438517f, + 94901.95507825605f, + 94925.35804483978f, + 94948.76245395854f, + 94972.16830543448f, + 94995.57559908989f, + 95018.98433474707f, + 95042.3945122284f, + 95065.80613135628f, + 95089.21919195318f, + 95112.63369384164f, + 95136.04963684424f, + 95159.46702078362f, + 95182.88584548247f, + 95206.30611076353f, + 95229.72781644962f, + 95253.15096236358f, + 95276.57554832831f, + 95300.00157416679f, + 95323.42903970205f, + 95346.85794475715f, + 95370.28828915521f, + 95393.72007271941f, + 95417.15329527302f, + 95440.5879566393f, + 95464.02405664159f, + 95487.46159510332f, + 95510.9005718479f, + 95534.34098669887f, + 95557.78283947978f, + 95581.22613001426f, + 95604.67085812596f, + 95628.1170236386f, + 95651.56462637598f, + 95675.01366616192f, + 95698.4641428203f, + 95721.91605617508f, + 95745.36940605023f, + 95768.8241922698f, + 95792.28041465791f, + 95815.7380730387f, + 95839.19716723639f, + 95862.65769707522f, + 95886.11966237954f, + 95909.58306297369f, + 95933.04789868211f, + 95956.51416932927f, + 95979.98187473971f, + 96003.451014738f, + 96026.92158914881f, + 96050.39359779679f, + 96073.86704050671f, + 96097.34191710339f, + 96120.81822741163f, + 96144.29597125638f, + 96167.77514846258f, + 96191.25575885524f, + 96214.73780225945f, + 96238.2212785003f, + 96261.70618740298f, + 96285.19252879272f, + 96308.68030249479f, + 96332.16950833453f, + 96355.66014613732f, + 96379.1522157286f, + 96402.64571693387f, + 96426.14064957868f, + 96449.6370134886f, + 96473.13480848931f, + 96496.63403440651f, + 96520.13469106596f, + 96543.63677829347f, + 96567.1402959149f, + 96590.64524375615f, + 96614.15162164322f, + 96637.65942940213f, + 96661.16866685895f, + 96684.6793338398f, + 96708.19143017087f, + 96731.7049556784f, + 96755.21991018867f, + 96778.73629352801f, + 96802.25410552284f, + 96825.77334599958f, + 96849.29401478474f, + 96872.81611170487f, + 96896.33963658658f, + 96919.86458925651f, + 96943.39096954139f, + 96966.91877726796f, + 96990.44801226305f, + 97013.97867435352f, + 97037.5107633663f, + 97061.04427912834f, + 97084.57922146667f, + 97108.11559020838f, + 97131.6533851806f, + 97155.19260621049f, + 97178.73325312529f, + 97202.2753257523f, + 97225.81882391885f, + 97249.36374745233f, + 97272.91009618019f, + 97296.4578699299f, + 97320.00706852904f, + 97343.5576918052f, + 97367.10973958601f, + 97390.6632116992f, + 97414.2181079725f, + 97437.77442823374f, + 97461.33217231077f, + 97484.89134003149f, + 97508.4519312239f, + 97532.01394571598f, + 97555.57738333581f, + 97579.14224391151f, + 97602.70852727126f, + 97626.27623324326f, + 97649.84536165581f, + 97673.41591233722f, + 97696.98788511587f, + 97720.56127982022f, + 97744.1360962787f, + 97767.71233431989f, + 97791.28999377234f, + 97814.86907446472f, + 97838.44957622568f, + 97862.031498884f, + 97885.61484226845f, + 97909.19960620788f, + 97932.7857905312f, + 97956.37339506732f, + 97979.96241964525f, + 98003.55286409408f, + 98027.14472824286f, + 98050.73801192077f, + 98074.332714957f, + 98097.9288371808f, + 98121.52637842152f, + 98145.12533850846f, + 98168.72571727107f, + 98192.32751453877f, + 98215.93073014112f, + 98239.53536390766f, + 98263.14141566801f, + 98286.74888525181f, + 98310.35777248882f, + 98333.96807720876f, + 98357.57979924149f, + 98381.19293841685f, + 98404.80749456478f, + 98428.42346751524f, + 98452.04085709827f, + 98475.65966314392f, + 98499.27988548232f, + 98522.90152394367f, + 98546.52457835816f, + 98570.1490485561f, + 98593.77493436779f, + 98617.40223562362f, + 98641.03095215405f, + 98664.66108378951f, + 98688.29263036055f, + 98711.92559169777f, + 98735.5599676318f, + 98759.1957579933f, + 98782.83296261301f, + 98806.47158132173f, + 98830.11161395028f, + 98853.75306032957f, + 98877.39592029051f, + 98901.0401936641f, + 98924.68588028138f, + 98948.33297997342f, + 98971.98149257139f, + 98995.63141790645f, + 99019.28275580984f, + 99042.93550611287f, + 99066.58966864688f, + 99090.24524324323f, + 99113.9022297334f, + 99137.56062794886f, + 99161.22043772115f, + 99184.88165888184f, + 99208.54429126263f, + 99232.20833469517f, + 99255.87378901121f, + 99279.54065404256f, + 99303.20892962103f, + 99326.87861557852f, + 99350.54971174701f, + 99374.22221795844f, + 99397.89613404489f, + 99421.57145983842f, + 99445.24819517121f, + 99468.92633987544f, + 99492.60589378334f, + 99516.28685672721f, + 99539.9692285394f, + 99563.65300905229f, + 99587.33819809832f, + 99611.02479551f, + 99634.71280111987f, + 99658.4022147605f, + 99682.09303626454f, + 99705.7852654647f, + 99729.47890219369f, + 99753.17394628433f, + 99776.87039756944f, + 99800.56825588191f, + 99824.26752105469f, + 99847.96819292076f, + 99871.67027131317f, + 99895.373756065f, + 99919.07864700939f, + 99942.78494397952f, + 99966.49264680862f, + 99990.20175533001f, + 100013.91226937699f, + 100037.62418878295f, + 100061.33751338134f, + 100085.05224300563f, + 100108.76837748935f, + 100132.4859166661f, + 100156.2048603695f, + 100179.92520843323f, + 100203.64696069101f, + 100227.37011697664f, + 100251.09467712394f, + 100274.82064096678f, + 100298.54800833909f, + 100322.27677907483f, + 100346.00695300807f, + 100369.73852997283f, + 100393.47150980328f, + 100417.20589233354f, + 100440.94167739789f, + 100464.67886483055f, + 100488.41745446586f, + 100512.1574461382f, + 100535.89883968196f, + 100559.64163493161f, + 100583.3858317217f, + 100607.13142988674f, + 100630.87842926137f, + 100654.62682968024f, + 100678.37663097809f, + 100702.12783298964f, + 100725.88043554971f, + 100749.63443849317f, + 100773.38984165489f, + 100797.14664486986f, + 100820.90484797307f, + 100844.66445079957f, + 100868.42545318443f, + 100892.18785496285f, + 100915.95165596998f, + 100939.71685604108f, + 100963.48345501146f, + 100987.25145271645f, + 101011.02084899142f, + 101034.79164367184f, + 101058.56383659315f, + 101082.33742759094f, + 101106.11241650078f, + 101129.88880315828f, + 101153.66658739912f, + 101177.44576905905f, + 101201.22634797383f, + 101225.00832397929f, + 101248.7916969113f, + 101272.5764666058f, + 101296.36263289873f, + 101320.15019562612f, + 101343.93915462404f, + 101367.7295097286f, + 101391.52126077596f, + 101415.31440760233f, + 101439.10895004397f, + 101462.9048879372f, + 101486.70222111835f, + 101510.50094942382f, + 101534.30107269008f, + 101558.10259075361f, + 101581.90550345098f, + 101605.70981061876f, + 101629.5155120936f, + 101653.32260771218f, + 101677.13109731126f, + 101700.9409807276f, + 101724.75225779804f, + 101748.56492835947f, + 101772.3789922488f, + 101796.19444930303f, + 101820.01129935916f, + 101843.82954225427f, + 101867.64917782549f, + 101891.47020590997f, + 101915.29262634492f, + 101939.11643896763f, + 101962.94164361537f, + 101986.76824012553f, + 102010.59622833549f, + 102034.42560808272f, + 102058.25637920471f, + 102082.08854153901f, + 102105.92209492321f, + 102129.75703919494f, + 102153.59337419191f, + 102177.43109975185f, + 102201.27021571253f, + 102225.1107219118f, + 102248.95261818753f, + 102272.79590437764f, + 102296.64058032009f, + 102320.48664585294f, + 102344.33410081422f, + 102368.18294504205f, + 102392.03317837461f, + 102415.88480065008f, + 102439.73781170673f, + 102463.59221138287f, + 102487.44799951684f, + 102511.30517594703f, + 102535.1637405119f, + 102559.02369304994f, + 102582.88503339965f, + 102606.74776139966f, + 102630.6118768886f, + 102654.47737970512f, + 102678.34426968795f, + 102702.21254667587f, + 102726.08221050771f, + 102749.95326102231f, + 102773.8256980586f, + 102797.69952145554f, + 102821.57473105213f, + 102845.45132668741f, + 102869.32930820051f, + 102893.20867543056f, + 102917.08942821674f, + 102940.97156639831f, + 102964.85508981455f, + 102988.73999830478f, + 103012.6262917084f, + 103036.51396986481f, + 103060.40303261351f, + 103084.293479794f, + 103108.18531124585f, + 103132.07852680866f, + 103155.97312632212f, + 103179.8691096259f, + 103203.76647655977f, + 103227.66522696352f, + 103251.565360677f, + 103275.46687754011f, + 103299.36977739276f, + 103323.27406007495f, + 103347.1797254267f, + 103371.08677328809f, + 103394.99520349925f, + 103418.90501590034f, + 103442.81621033157f, + 103466.7287866332f, + 103490.64274464553f, + 103514.55808420894f, + 103538.4748051638f, + 103562.39290735057f, + 103586.31239060973f, + 103610.23325478184f, + 103634.15549970744f, + 103658.0791252272f, + 103682.00413118176f, + 103705.93051741188f, + 103729.8582837583f, + 103753.78743006183f, + 103777.71795616334f, + 103801.64986190372f, + 103825.58314712394f, + 103849.51781166499f, + 103873.4538553679f, + 103897.39127807376f, + 103921.33007962372f, + 103945.27025985895f, + 103969.21181862066f, + 103993.15475575015f, + 104017.0990710887f, + 104041.0447644777f, + 104064.99183575854f, + 104088.94028477269f, + 104112.89011136163f, + 104136.84131536692f, + 104160.79389663014f, + 104184.74785499295f, + 104208.70319029699f, + 104232.65990238401f, + 104256.61799109579f, + 104280.57745627411f, + 104304.53829776088f, + 104328.50051539797f, + 104352.46410902737f, + 104376.42907849104f, + 104400.39542363104f, + 104424.36314428947f, + 104448.33224030846f, + 104472.3027115302f, + 104496.27455779689f, + 104520.24777895081f, + 104544.22237483428f, + 104568.19834528965f, + 104592.17569015936f, + 104616.15440928582f, + 104640.13450251156f, + 104664.11596967909f, + 104688.09881063103f, + 104712.08302520998f, + 104736.06861325864f, + 104760.05557461972f, + 104784.043909136f, + 104808.03361665027f, + 104832.0246970054f, + 104856.01715004431f, + 104880.01097560991f, + 104904.00617354522f, + 104928.00274369326f, + 104952.00068589713f, + 104975.99999999993f, + 105000.00068584486f, + 105024.00274327511f, + 105048.00617213396f, + 105072.01097226472f, + 105096.0171435107f, + 105120.02468571535f, + 105144.03359872208f, + 105168.04388237436f, + 105192.05553651575f, + 105216.06856098982f, + 105240.08295564016f, + 105264.09872031047f, + 105288.11585484444f, + 105312.13435908582f, + 105336.1542328784f, + 105360.17547606604f, + 105384.19808849262f, + 105408.22207000206f, + 105432.24742043833f, + 105456.27413964548f, + 105480.30222746753f, + 105504.33168374863f, + 105528.36250833291f, + 105552.39470106458f, + 105576.42826178786f, + 105600.46319034706f, + 105624.49948658649f, + 105648.53715035053f, + 105672.5761814836f, + 105696.61657983017f, + 105720.65834523473f, + 105744.70147754184f, + 105768.74597659608f, + 105792.79184224212f, + 105816.83907432464f, + 105840.88767268835f, + 105864.93763717801f, + 105888.98896763846f, + 105913.04166391456f, + 105937.09572585119f, + 105961.15115329332f, + 105985.20794608595f, + 106009.2661040741f, + 106033.32562710284f, + 106057.3865150173f, + 106081.44876766266f, + 106105.51238488412f, + 106129.57736652695f, + 106153.64371243643f, + 106177.71142245791f, + 106201.78049643678f, + 106225.85093421848f, + 106249.92273564848f, + 106273.99590057228f, + 106298.07042883546f, + 106322.14632028362f, + 106346.2235747624f, + 106370.30219211751f, + 106394.38217219469f, + 106418.4635148397f, + 106442.54621989837f, + 106466.63028721658f, + 106490.71571664023f, + 106514.8025080153f, + 106538.89066118775f, + 106562.98017600364f, + 106587.07105230905f, + 106611.16328995011f, + 106635.25688877302f, + 106659.35184862395f, + 106683.44816934918f, + 106707.54585079502f, + 106731.64489280782f, + 106755.74529523395f, + 106779.84705791986f, + 106803.95018071201f, + 106828.05466345693f, + 106852.16050600118f, + 106876.26770819136f, + 106900.37626987413f, + 106924.48619089619f, + 106948.59747110425f, + 106972.71011034511f, + 106996.82410846559f, + 107020.93946531255f, + 107045.05618073288f, + 107069.17425457356f, + 107093.29368668159f, + 107117.41447690397f, + 107141.53662508781f, + 107165.66013108024f, + 107189.7849947284f, + 107213.91121587952f, + 107238.03879438085f, + 107262.16773007967f, + 107286.29802282334f, + 107310.42967245923f, + 107334.56267883476f, + 107358.69704179741f, + 107382.83276119467f, + 107406.96983687414f, + 107431.10826868335f, + 107455.24805646998f, + 107479.38920008171f, + 107503.53169936626f, + 107527.6755541714f, + 107551.82076434491f, + 107575.96732973469f, + 107600.11525018861f, + 107624.26452555461f, + 107648.41515568066f, + 107672.56714041479f, + 107696.72047960508f, + 107720.87517309963f, + 107745.03122074658f, + 107769.18862239414f, + 107793.34737789052f, + 107817.50748708403f, + 107841.66894982298f, + 107865.83176595572f, + 107889.99593533068f, + 107914.16145779629f, + 107938.32833320105f, + 107962.49656139348f, + 107986.66614222217f, + 108010.83707553573f, + 108035.00936118282f, + 108059.18299901215f, + 108083.35798887245f, + 108107.53433061253f, + 108131.71202408121f, + 108155.89106912735f, + 108180.07146559987f, + 108204.25321334775f, + 108228.43631221994f, + 108252.62076206553f, + 108276.80656273357f, + 108300.9937140732f, + 108325.18221593359f, + 108349.37206816394f, + 108373.5632706135f, + 108397.75582313156f, + 108421.94972556747f, + 108446.1449777706f, + 108470.34157959036f, + 108494.53953087622f, + 108518.7388314777f, + 108542.9394812443f, + 108567.14148002566f, + 108591.34482767139f, + 108615.54952403114f, + 108639.75556895464f, + 108663.96296229165f, + 108688.17170389196f, + 108712.38179360541f, + 108736.59323128188f, + 108760.80601677128f, + 108785.02014992358f, + 108809.23563058881f, + 108833.45245861699f, + 108857.67063385822f, + 108881.89015616261f, + 108906.11102538036f, + 108930.33324136169f, + 108954.55680395682f, + 108978.78171301607f, + 109003.00796838978f, + 109027.2355699283f, + 109051.4645174821f, + 109075.69481090162f, + 109099.92645003737f, + 109124.15943473988f, + 109148.39376485976f, + 109172.62944024763f, + 109196.86646075416f, + 109221.10482623006f, + 109245.3445365261f, + 109269.58559149304f, + 109293.82799098175f, + 109318.0717348431f, + 109342.316822928f, + 109366.56325508743f, + 109390.81103117237f, + 109415.06015103386f, + 109439.31061452301f, + 109463.56242149093f, + 109487.8155717888f, + 109512.0700652678f, + 109536.3259017792f, + 109560.58308117429f, + 109584.8416033044f, + 109609.1014680209f, + 109633.36267517522f, + 109657.62522461878f, + 109681.88911620309f, + 109706.15434977971f, + 109730.4209252002f, + 109754.68884231619f, + 109778.95810097932f, + 109803.22870104131f, + 109827.50064235389f, + 109851.77392476884f, + 109876.048548138f, + 109900.32451231324f, + 109924.60181714644f, + 109948.88046248957f, + 109973.1604481946f, + 109997.44177411357f, + 110021.72444009855f, + 110046.00844600165f, + 110070.29379167501f, + 110094.58047697082f, + 110118.86850174134f, + 110143.15786583882f, + 110167.44856911557f, + 110191.74061142397f, + 110216.0339926164f, + 110240.32871254528f, + 110264.62477106311f, + 110288.9221680224f, + 110313.22090327571f, + 110337.52097667565f, + 110361.82238807483f, + 110386.12513732594f, + 110410.42922428172f, + 110434.7346487949f, + 110459.04141071832f, + 110483.34950990479f, + 110507.6589462072f, + 110531.96971947847f, + 110556.28182957157f, + 110580.5952763395f, + 110604.91005963532f, + 110629.2261793121f, + 110653.54363522294f, + 110677.86242722106f, + 110702.18255515961f, + 110726.50401889188f, + 110750.82681827113f, + 110775.1509531507f, + 110799.47642338395f, + 110823.80322882428f, + 110848.13136932514f, + 110872.46084474004f, + 110896.79165492248f, + 110921.12379972603f, + 110945.4572790043f, + 110969.79209261097f, + 110994.12824039967f, + 111018.46572222418f, + 111042.80453793824f, + 111067.14468739566f, + 111091.48617045028f, + 111115.82898695602f, + 111140.1731367668f, + 111164.51861973657f, + 111188.86543571934f, + 111213.21358456917f, + 111237.56306614014f, + 111261.91388028639f, + 111286.26602686207f, + 111310.6195057214f, + 111334.97431671864f, + 111359.33045970804f, + 111383.68793454397f, + 111408.04674108078f, + 111432.40687917286f, + 111456.76834867468f, + 111481.13114944073f, + 111505.49528132551f, + 111529.8607441836f, + 111554.22753786962f, + 111578.59566223821f, + 111602.96511714405f, + 111627.33590244185f, + 111651.7080179864f, + 111676.08146363248f, + 111700.45623923496f, + 111724.8323446487f, + 111749.20977972864f, + 111773.58854432974f, + 111797.968638307f, + 111822.35006151545f, + 111846.73281381019f, + 111871.11689504632f, + 111895.50230507903f, + 111919.88904376348f, + 111944.27711095495f, + 111968.6665065087f, + 111993.05723028004f, + 112017.44928212435f, + 112041.842661897f, + 112066.23736945343f, + 112090.63340464912f, + 112115.0307673396f, + 112139.42945738042f, + 112163.82947462716f, + 112188.23081893545f, + 112212.63349016097f, + 112237.03748815943f, + 112261.44281278658f, + 112285.84946389822f, + 112310.25744135017f, + 112334.66674499828f, + 112359.07737469849f, + 112383.48933030672f, + 112407.90261167898f, + 112432.31721867126f, + 112456.73315113965f, + 112481.15040894024f, + 112505.56899192919f, + 112529.98889996266f, + 112554.41013289688f, + 112578.83269058811f, + 112603.25657289263f, + 112627.6817796668f, + 112652.10831076698f, + 112676.53616604958f, + 112700.96534537108f, + 112725.39584858794f, + 112749.82767555672f, + 112774.26082613398f, + 112798.6953001763f, + 112823.13109754038f, + 112847.56821808286f, + 112872.00666166049f, + 112896.44642813003f, + 112920.88751734828f, + 112945.32992917208f, + 112969.7736634583f, + 112994.21872006389f, + 113018.66509884578f, + 113043.11279966097f, + 113067.56182236652f, + 113092.01216681948f, + 113116.46383287695f, + 113140.9168203961f, + 113165.37112923413f, + 113189.82675924824f, + 113214.28371029573f, + 113238.74198223387f, + 113263.20157492002f, + 113287.66248821156f, + 113312.12472196593f, + 113336.58827604055f, + 113361.05315029295f, + 113385.51934458067f, + 113409.98685876124f, + 113434.45569269233f, + 113458.92584623155f, + 113483.39731923661f, + 113507.87011156522f, + 113532.34422307517f, + 113556.81965362425f, + 113581.2964030703f, + 113605.77447127122f, + 113630.2538580849f, + 113654.73456336933f, + 113679.21658698248f, + 113703.69992878241f, + 113728.18458862716f, + 113752.67056637487f, + 113777.15786188368f, + 113801.64647501177f, + 113826.13640561736f, + 113850.62765355874f, + 113875.12021869418f, + 113899.61410088204f, + 113924.1092999807f, + 113948.60581584855f, + 113973.10364834408f, + 113997.60279732574f, + 114022.1032626521f, + 114046.60504418172f, + 114071.10814177318f, + 114095.61255528513f, + 114120.11828457628f, + 114144.62532950533f, + 114169.13368993104f, + 114193.6433657122f, + 114218.15435670764f, + 114242.66666277626f, + 114267.18028377692f, + 114291.69521956862f, + 114316.21147001031f, + 114340.72903496103f, + 114365.24791427983f, + 114389.7681078258f, + 114414.2896154581f, + 114438.81243703587f, + 114463.33657241837f, + 114487.8620214648f, + 114512.38878403447f, + 114536.91685998671f, + 114561.44624918088f, + 114585.97695147636f, + 114610.5089667326f, + 114635.04229480909f, + 114659.57693556532f, + 114684.11288886084f, + 114708.65015455526f, + 114733.18873250818f, + 114757.72862257928f, + 114782.26982462825f, + 114806.81233851484f, + 114831.35616409882f, + 114855.90130124f, + 114880.44774979822f, + 114904.99550963337f, + 114929.5445806054f, + 114954.09496257425f, + 114978.64665539993f, + 115003.19965894247f, + 115027.75397306195f, + 115052.30959761847f, + 115076.86653247218f, + 115101.42477748329f, + 115125.984332512f, + 115150.54519741859f, + 115175.10737206334f, + 115199.67085630659f, + 115224.23565000873f, + 115248.80175303014f, + 115273.3691652313f, + 115297.93788647266f, + 115322.50791661476f, + 115347.07925551817f, + 115371.65190304347f, + 115396.2258590513f, + 115420.80112340231f, + 115445.37769595724f, + 115469.95557657682f, + 115494.53476512182f, + 115519.11526145306f, + 115543.6970654314f, + 115568.28017691776f, + 115592.86459577303f, + 115617.4503218582f, + 115642.03735503425f, + 115666.62569516223f, + 115691.21534210323f, + 115715.80629571836f, + 115740.39855586876f, + 115764.99212241563f, + 115789.58699522018f, + 115814.18317414368f, + 115838.78065904742f, + 115863.37944979276f, + 115887.97954624105f, + 115912.5809482537f, + 115937.18365569218f, + 115961.78766841792f, + 115986.39298629249f, + 116010.99960917742f, + 116035.60753693432f, + 116060.21676942479f, + 116084.82730651053f, + 116109.43914805322f, + 116134.0522939146f, + 116158.66674395645f, + 116183.2824980406f, + 116207.89955602886f, + 116232.51791778316f, + 116257.13758316539f, + 116281.75855203751f, + 116306.38082426153f, + 116331.0043996995f, + 116355.62927821343f, + 116380.25545966547f, + 116404.88294391775f, + 116429.51173083246f, + 116454.14182027178f, + 116478.77321209799f, + 116503.40590617337f, + 116528.03990236024f, + 116552.67520052097f, + 116577.31180051794f, + 116601.94970221359f, + 116626.5889054704f, + 116651.22941015086f, + 116675.8712161175f, + 116700.51432323293f, + 116725.15873135976f, + 116749.8044403606f, + 116774.45145009817f, + 116799.0997604352f, + 116823.74937123443f, + 116848.40028235866f, + 116873.05249367072f, + 116897.70600503348f, + 116922.36081630984f, + 116947.01692736275f, + 116971.67433805518f, + 116996.33304825013f, + 117020.99305781067f, + 117045.65436659988f, + 117070.31697448085f, + 117094.98088131678f, + 117119.64608697084f, + 117144.31259130625f, + 117168.98039418628f, + 117193.64949547425f, + 117218.31989503348f, + 117242.99159272734f, + 117267.66458841923f, + 117292.33888197262f, + 117317.01447325097f, + 117341.6913621178f, + 117366.36954843666f, + 117391.04903207115f, + 117415.72981288488f, + 117440.41189074152f, + 117465.09526550476f, + 117489.77993703831f, + 117514.46590520597f, + 117539.15316987154f, + 117563.84173089883f, + 117588.53158815173f, + 117613.22274149416f, + 117637.91519079005f, + 117662.60893590341f, + 117687.30397669821f, + 117712.00031303853f, + 117736.69794478847f, + 117761.39687181212f, + 117786.09709397367f, + 117810.7986111373f, + 117835.50142316725f, + 117860.20552992777f, + 117884.91093128319f, + 117909.6176270978f, + 117934.32561723603f, + 117959.03490156225f, + 117983.74547994092f, + 118008.45735223651f, + 118033.17051831353f, + 118057.88497803656f, + 118082.60073127014f, + 118107.31777787892f, + 118132.03611772758f, + 118156.75575068076f, + 118181.47667660323f, + 118206.19889535972f, + 118230.92240681504f, + 118255.64721083404f, + 118280.37330728157f, + 118305.10069602253f, + 118329.82937692187f, + 118354.55934984458f, + 118379.29061465565f, + 118404.02317122012f, + 118428.75701940308f, + 118453.49215906965f, + 118478.22859008498f, + 118502.96631231424f, + 118527.70532562268f, + 118552.44562987552f, + 118577.18722493808f, + 118601.93011067568f, + 118626.67428695368f, + 118651.41975363747f, + 118676.1665105925f, + 118700.91455768421f, + 118725.66389477813f, + 118750.41452173979f, + 118775.16643843475f, + 118799.91964472862f, + 118824.67414048707f, + 118849.42992557574f, + 118874.18699986035f, + 118898.94536320666f, + 118923.70501548043f, + 118948.46595654752f, + 118973.22818627374f, + 118997.99170452499f, + 119022.7565111672f, + 119047.52260606633f, + 119072.28998908834f, + 119097.0586600993f, + 119121.82861896523f, + 119146.59986555226f, + 119171.3723997265f, + 119196.14622135412f, + 119220.92133030134f, + 119245.69772643436f, + 119270.47540961947f, + 119295.25437972297f, + 119320.0346366112f, + 119344.81618015055f, + 119369.5990102074f, + 119394.38312664822f, + 119419.16852933947f, + 119443.95521814766f, + 119468.74319293935f, + 119493.53245358112f, + 119518.32299993958f, + 119543.1148318814f, + 119567.90794927324f, + 119592.70235198183f, + 119617.49803987393f, + 119642.29501281632f, + 119667.09327067583f, + 119691.89281331931f, + 119716.69364061367f, + 119741.49575242584f, + 119766.29914862274f, + 119791.10382907142f, + 119815.90979363887f, + 119840.71704219218f, + 119865.52557459843f, + 119890.33539072477f, + 119915.14649043836f, + 119939.95887360642f, + 119964.77254009615f, + 119989.58748977486f, + 120014.40372250983f, + 120039.22123816841f, + 120064.04003661797f, + 120088.86011772591f, + 120113.6814813597f, + 120138.50412738678f, + 120163.3280556747f, + 120188.15326609099f, + 120212.9797585032f, + 120237.807532779f, + 120262.636588786f, + 120287.46692639188f, + 120312.29854546436f, + 120337.13144587121f, + 120361.9656274802f, + 120386.80109015913f, + 120411.6378337759f, + 120436.47585819835f, + 120461.31516329442f, + 120486.15574893207f, + 120510.99761497928f, + 120535.84076130408f, + 120560.6851877745f, + 120585.53089425867f, + 120610.3778806247f, + 120635.22614674074f, + 120660.07569247499f, + 120684.92651769568f, + 120709.77862227106f, + 120734.63200606944f, + 120759.48666895913f, + 120784.3426108085f, + 120809.19983148595f, + 120834.05833085992f, + 120858.91810879884f, + 120883.77916517125f, + 120908.64149984565f, + 120933.5051126906f, + 120958.37000357473f, + 120983.23617236665f, + 121008.10361893504f, + 121032.9723431486f, + 121057.84234487606f, + 121082.71362398617f, + 121107.58618034775f, + 121132.46001382964f, + 121157.33512430069f, + 121182.21151162982f, + 121207.08917568595f, + 121231.96811633807f, + 121256.84833345517f, + 121281.72982690629f, + 121306.61259656049f, + 121331.49664228689f, + 121356.38196395461f, + 121381.26856143285f, + 121406.15643459078f, + 121431.04558329767f, + 121455.93600742277f, + 121480.82770683538f, + 121505.72068140487f, + 121530.61493100057f, + 121555.51045549192f, + 121580.40725474835f, + 121605.30532863933f, + 121630.20467703436f, + 121655.10529980299f, + 121680.00719681478f, + 121704.91036793934f, + 121729.81481304632f, + 121754.72053200539f, + 121779.62752468624f, + 121804.53579095862f, + 121829.44533069231f, + 121854.3561437571f, + 121879.26823002285f, + 121904.1815893594f, + 121929.09622163669f, + 121954.01212672464f, + 121978.92930449323f, + 122003.84775481246f, + 122028.76747755238f, + 122053.68847258303f, + 122078.61073977455f, + 122103.53427899707f, + 122128.45909012076f, + 122153.3851730158f, + 122178.31252755247f, + 122203.241153601f, + 122228.1710510317f, + 122253.10221971496f, + 122278.03465952107f, + 122302.9683703205f, + 122327.90335198362f, + 122352.83960438096f, + 122377.777127383f, + 122402.71592086025f, + 122427.65598468333f, + 122452.59731872278f, + 122477.53992284928f, + 122502.48379693348f, + 122527.42894084606f, + 122552.37535445779f, + 122577.3230376394f, + 122602.27199026172f, + 122627.22221219557f, + 122652.17370331181f, + 122677.12646348133f, + 122702.08049257506f, + 122727.03579046397f, + 122751.99235701906f, + 122776.95019211136f, + 122801.9092956119f, + 122826.8696673918f, + 122851.8313073222f, + 122876.79421527422f, + 122901.75839111907f, + 122926.72383472799f, + 122951.69054597223f, + 122976.65852472307f, + 123001.62777085182f, + 123026.59828422987f, + 123051.57006472857f, + 123076.54311221937f, + 123101.5174265737f, + 123126.49300766307f, + 123151.46985535898f, + 123176.447969533f, + 123201.42735005668f, + 123226.40799680166f, + 123251.38990963959f, + 123276.37308844214f, + 123301.35753308103f, + 123326.343243428f, + 123351.33021935483f, + 123376.31846073334f, + 123401.30796743535f, + 123426.29873933276f, + 123451.29077629748f, + 123476.28407820144f, + 123501.2786449166f, + 123526.27447631498f, + 123551.27157226863f, + 123576.2699326496f, + 123601.26955732999f, + 123626.27044618195f, + 123651.27259907764f, + 123676.27601588926f, + 123701.28069648903f, + 123726.28664074925f, + 123751.29384854218f, + 123776.30231974016f, + 123801.31205421555f, + 123826.32305184074f, + 123851.33531248817f, + 123876.34883603029f, + 123901.36362233957f, + 123926.37967128855f, + 123951.3969827498f, + 123976.41555659588f, + 124001.43539269941f, + 124026.45649093305f, + 124051.47885116948f, + 124076.50247328142f, + 124101.5273571416f, + 124126.55350262282f, + 124151.58090959788f, + 124176.60957793961f, + 124201.63950752091f, + 124226.67069821467f, + 124251.70314989384f, + 124276.73686243138f, + 124301.7718357003f, + 124326.80806957364f, + 124351.84556392446f, + 124376.88431862585f, + 124401.92433355095f, + 124426.96560857294f, + 124452.00814356498f, + 124477.05193840031f, + 124502.0969929522f, + 124527.14330709392f, + 124552.19088069882f, + 124577.23971364023f, + 124602.28980579154f, + 124627.34115702618f, + 124652.3937672176f, + 124677.44763623926f, + 124702.50276396469f, + 124727.55915026742f, + 124752.61679502104f, + 124777.67569809916f, + 124802.73585937542f, + 124827.79727872348f, + 124852.85995601704f, + 124877.92389112986f, + 124902.98908393568f, + 124928.05553430831f, + 124953.1232421216f, + 124978.19220724938f, + 125003.26242956554f, + 125028.33390894404f, + 125053.40664525882f, + 125078.48063838384f, + 125103.55588819316f, + 125128.63239456083f, + 125153.71015736091f, + 125178.78917646752f, + 125203.86945175481f, + 125228.95098309696f, + 125254.03377036817f, + 125279.1178134427f, + 125304.2031121948f, + 125329.28966649878f, + 125354.37747622898f, + 125379.46654125977f, + 125404.55686146552f, + 125429.6484367207f, + 125454.74126689974f, + 125479.83535187715f, + 125504.93069152744f, + 125530.02728572517f, + 125555.12513434493f, + 125580.22423726133f, + 125605.32459434902f, + 125630.42620548268f, + 125655.52907053704f, + 125680.63318938682f, + 125705.7385619068f, + 125730.84518797178f, + 125755.9530674566f, + 125781.06220023613f, + 125806.17258618528f, + 125831.28422517896f, + 125856.39711709213f, + 125881.51126179981f, + 125906.62665917698f, + 125931.74330909875f, + 125956.86121144016f, + 125981.98036607634f, + 126007.10077288245f, + 126032.22243173365f, + 126057.34534250517f, + 126082.46950507225f, + 126107.59491931014f, + 126132.72158509417f, + 126157.84950229966f, + 126182.97867080198f, + 126208.10909047653f, + 126233.24076119871f, + 126258.37368284403f, + 126283.50785528794f, + 126308.64327840599f, + 126333.7799520737f, + 126358.91787616667f, + 126384.0570505605f, + 126409.19747513086f, + 126434.3391497534f, + 126459.48207430386f, + 126484.62624865794f, + 126509.77167269142f, + 126534.9183462801f, + 126560.06626929982f, + 126585.21544162642f, + 126610.36586313581f, + 126635.51753370391f, + 126660.67045320668f, + 126685.82462152008f, + 126710.98003852014f, + 126736.1367040829f, + 126761.29461808444f, + 126786.45378040087f, + 126811.61419090834f, + 126836.77584948298f, + 126861.93875600102f, + 126887.10291033868f, + 126912.26831237224f, + 126937.43496197795f, + 126962.60285903217f, + 126987.77200341123f, + 127012.94239499152f, + 127038.11403364947f, + 127063.2869192615f, + 127088.46105170409f, + 127113.63643085376f, + 127138.81305658702f, + 127163.99092878048f, + 127189.17004731069f, + 127214.3504120543f, + 127239.53202288797f, + 127264.71487968838f, + 127289.89898233226f, + 127315.08433069635f, + 127340.27092465744f, + 127365.45876409234f, + 127390.64784887788f, + 127415.83817889093f, + 127441.02975400841f, + 127466.22257410725f, + 127491.4166390644f, + 127516.61194875685f, + 127541.80850306165f, + 127567.00630185583f, + 127592.20534501647f, + 127617.4056324207f, + 127642.60716394568f, + 127667.80993946856f, + 127693.01395886653f, + 127718.21922201688f, + 127743.42572879682f, + 127768.63347908368f, + 127793.8424727548f, + 127819.05270968749f, + 127844.26418975917f, + 127869.47691284724f, + 127894.69087882918f, + 127919.90608758242f, + 127945.12253898452f, + 127970.34023291297f, + 127995.55916924539f, + 128020.77934785932f, + 128046.00076863244f, + 128071.22343144237f, + 128096.44733616684f, + 128121.67248268353f, + 128146.89887087021f, + 128172.12650060465f, + 128197.35537176466f, + 128222.5854842281f, + 128247.81683787282f, + 128273.0494325767f, + 128298.28326821771f, + 128323.5183446738f, + 128348.75466182294f, + 128373.99221954317f, + 128399.23101771252f, + 128424.47105620909f, + 128449.71233491098f, + 128474.95485369631f, + 128500.19861244329f, + 128525.44361103009f, + 128550.68984933494f, + 128575.93732723613f, + 128601.18604461191f, + 128626.43600134061f, + 128651.68719730059f, + 128676.93963237021f, + 128702.1933064279f, + 128727.44821935208f, + 128752.70437102125f, + 128777.96176131385f, + 128803.22039010846f, + 128828.48025728362f, + 128853.74136271792f, + 128879.00370628996f, + 128904.2672878784f, + 128929.53210736193f, + 128954.79816461923f, + 128980.06545952905f, + 129005.33399197015f, + 129030.60376182134f, + 129055.87476896142f, + 129081.14701326926f, + 129106.42049462376f, + 129131.6952129038f, + 129156.97116798835f, + 129182.24835975636f, + 129207.52678808685f, + 129232.80645285884f, + 129258.08735395141f, + 129283.36949124365f, + 129308.65286461466f, + 129333.9374739436f, + 129359.22331910966f, + 129384.51039999202f, + 129409.79871646997f, + 129435.08826842274f, + 129460.37905572963f, + 129485.67107826998f, + 129510.96433592314f, + 129536.2588285685f, + 129561.55455608548f, + 129586.85151835352f, + 129612.14971525209f, + 129637.4491466607f, + 129662.74981245887f, + 129688.0517125262f, + 129713.35484674224f, + 129738.65921498663f, + 129763.96481713903f, + 129789.2716530791f, + 129814.57972268655f, + 129839.88902584116f, + 129865.19956242264f, + 129890.51133231082f, + 129915.82433538554f, + 129941.13857152662f, + 129966.45404061397f, + 129991.7707425275f, + 130017.08867714716f, + 130042.4078443529f, + 130067.72824402474f, + 130093.04987604271f, + 130118.37274028687f, + 130143.69683663732f, + 130169.02216497416f, + 130194.34872517755f, + 130219.67651712766f, + 130245.0055407047f, + 130270.33579578891f, + 130295.66728226055f, + 130320.99999999991f, + 130346.33394888733f, + 130371.66912880314f, + 130397.00553962773f, + 130422.34318124152f, + 130447.68205352494f, + 130473.02215635845f, + 130498.36348962256f, + 130523.70605319779f, + 130549.0498469647f, + 130574.39487080388f, + 130599.74112459592f, + 130625.08860822149f, + 130650.43732156123f, + 130675.78726449587f, + 130701.13843690613f, + 130726.49083867275f, + 130751.84446967654f, + 130777.19932979831f, + 130802.5554189189f, + 130827.91273691918f, + 130853.27128368006f, + 130878.63105908247f, + 130903.99206300738f, + 130929.35429533575f, + 130954.71775594862f, + 130980.08244472703f, + 131005.44836155206f, + 131030.81550630482f, + 131056.18387886642f, + 131081.55347911804f, + 131106.92430694087f, + 131132.29636221612f, + 131157.66964482504f, + 131183.0441546489f, + 131208.41989156904f, + 131233.79685546676f, + 131259.17504622342f, + 131284.55446372041f, + 131309.93510783918f, + 131335.31697846117f, + 131360.70007546784f, + 131386.0843987407f, + 131411.46994816128f, + 131436.85672361116f, + 131462.24472497194f, + 131487.6339521252f, + 131513.02440495262f, + 131538.41608333588f, + 131563.80898715663f, + 131589.2031162967f, + 131614.59847063778f, + 131639.9950500617f, + 131665.39285445024f, + 131690.7918836853f, + 131716.19213764873f, + 131741.5936162224f, + 131766.99631928833f, + 131792.4002467284f, + 131817.80539842462f, + 131843.21177425905f, + 131868.6193741137f, + 131894.02819787065f, + 131919.43824541202f, + 131944.84951661993f, + 131970.26201137656f, + 131995.67572956407f, + 132021.09067106468f, + 132046.50683576067f, + 132071.9242235343f, + 132097.34283426782f, + 132122.76266784366f, + 132148.1837241441f, + 132173.60600305157f, + 132199.02950444847f, + 132224.45422821722f, + 132249.88017424036f, + 132275.3073424003f, + 132300.73573257966f, + 132326.16534466096f, + 132351.59617852676f, + 132377.0282340597f, + 132402.46151114244f, + 132427.8960096576f, + 132453.3317294879f, + 132478.7686705161f, + 132504.2068326249f, + 132529.64621569714f, + 132555.0868196156f, + 132580.5286442631f, + 132605.97168952253f, + 132631.41595527678f, + 132656.8614414088f, + 132682.3081478015f, + 132707.75607433787f, + 132733.20522090094f, + 132758.65558737374f, + 132784.1071736393f, + 132809.55997958075f, + 132835.01400508118f, + 132860.46925002377f, + 132885.92571429166f, + 132911.3833977681f, + 132936.84230033628f, + 132962.30242187946f, + 132987.76376228096f, + 133013.22632142407f, + 133038.69009919214f, + 133064.15509546854f, + 133089.62131013666f, + 133115.08874307995f, + 133140.55739418184f, + 133166.0272633258f, + 133191.4983503954f, + 133216.97065527414f, + 133242.4441778456f, + 133267.91891799335f, + 133293.39487560102f, + 133318.87205055228f, + 133344.3504427308f, + 133369.83005202023f, + 133395.3108783044f, + 133420.79292146701f, + 133446.27618139185f, + 133471.76065796276f, + 133497.24635106357f, + 133522.73326057816f, + 133548.2213863904f, + 133573.71072838426f, + 133599.20128644365f, + 133624.6930604526f, + 133650.1860502951f, + 133675.68025585517f, + 133701.1756770169f, + 133726.67231366437f, + 133752.17016568172f, + 133777.66923295305f, + 133803.1695153626f, + 133828.67101279454f, + 133854.1737251331f, + 133879.67765226253f, + 133905.18279406714f, + 133930.68915043125f, + 133956.19672123916f, + 133981.70550637526f, + 134007.215505724f, + 134032.7267191697f, + 134058.23914659687f, + 134083.75278789f, + 134109.26764293358f, + 134134.78371161217f, + 134160.30099381026f, + 134185.8194894125f, + 134211.33919830353f, + 134236.8601203679f, + 134262.38225549037f, + 134287.90560355558f, + 134313.4301644483f, + 134338.95593805326f, + 134364.48292425525f, + 134390.0111229391f, + 134415.54053398955f, + 134441.0711572916f, + 134466.60299273f, + 134492.1360401898f, + 134517.67029955584f, + 134543.20577071316f, + 134568.74245354676f, + 134594.2803479416f, + 134619.81945378278f, + 134645.35977095537f, + 134670.90129934452f, + 134696.4440388353f, + 134721.9879893129f, + 134747.53315066252f, + 134773.07952276937f, + 134798.6271055187f, + 134824.17589879577f, + 134849.7259024859f, + 134875.27711647438f, + 134900.8295406466f, + 134926.38317488792f, + 134951.93801908373f, + 134977.4940731195f, + 135003.0513368807f, + 135028.60981025276f, + 135054.16949312127f, + 135079.73038537172f, + 135105.29248688967f, + 135130.85579756077f, + 135156.42031727062f, + 135181.98604590484f, + 135207.55298334916f, + 135233.12112948927f, + 135258.69048421088f, + 135284.26104739975f, + 135309.83281894168f, + 135335.4057987225f, + 135360.97998662802f, + 135386.55538254412f, + 135412.1319863567f, + 135437.70979795168f, + 135463.28881721498f, + 135488.86904403262f, + 135514.45047829056f, + 135540.03311987486f, + 135565.61696867156f, + 135591.20202456677f, + 135616.78828744654f, + 135642.37575719706f, + 135667.96443370447f, + 135693.55431685498f, + 135719.14540653478f, + 135744.7377026301f, + 135770.33120502727f, + 135795.92591361253f, + 135821.52182827223f, + 135847.11894889272f, + 135872.7172753604f, + 135898.3168075616f, + 135923.91754538284f, + 135949.51948871053f, + 135975.12263743114f, + 136000.72699143123f, + 136026.3325505973f, + 136051.9393148159f, + 136077.5472839737f, + 136103.15645795723f, + 136128.76683665317f, + 136154.37841994822f, + 136179.991207729f, + 136205.60519988232f, + 136231.2203962949f, + 136256.8367968535f, + 136282.45440144493f, + 136308.07320995603f, + 136333.69322227367f, + 136359.3144382847f, + 136384.93685787608f, + 136410.56048093468f, + 136436.18530734754f, + 136461.81133700156f, + 136487.43856978387f, + 136513.06700558143f, + 136538.6966442813f, + 136564.32748577066f, + 136589.95952993655f, + 136615.59277666616f, + 136641.22722584667f, + 136666.86287736523f, + 136692.49973110916f, + 136718.13778696564f, + 136743.77704482197f, + 136769.41750456547f, + 136795.05916608346f, + 136820.7020292633f, + 136846.34609399244f, + 136871.9913601582f, + 136897.63782764805f, + 136923.28549634948f, + 136948.93436614997f, + 136974.58443693706f, + 137000.23570859825f, + 137025.88818102115f, + 137051.54185409332f, + 137077.19672770242f, + 137102.8528017361f, + 137128.51007608202f, + 137154.16855062786f, + 137179.82822526142f, + 137205.4890998704f, + 137231.15117434258f, + 137256.8144485658f, + 137282.4789224279f, + 137308.14459581667f, + 137333.8114686201f, + 137359.47954072602f, + 137385.1488120224f, + 137410.8192823972f, + 137436.49095173844f, + 137462.16381993407f, + 137487.8378868722f, + 137513.5131524409f, + 137539.18961652822f, + 137564.8672790223f, + 137590.5461398113f, + 137616.22619878338f, + 137641.90745582676f, + 137667.58991082967f, + 137693.27356368033f, + 137718.95841426702f, + 137744.64446247809f, + 137770.33170820182f, + 137796.0201513266f, + 137821.7097917408f, + 137847.40062933284f, + 137873.09266399115f, + 137898.78589560417f, + 137924.48032406042f, + 137950.17594924837f, + 137975.8727710566f, + 138001.57078937365f, + 138027.27000408815f, + 138052.97041508864f, + 138078.67202226384f, + 138104.3748255024f, + 138130.07882469296f, + 138155.78401972432f, + 138181.49041048516f, + 138207.1979968643f, + 138232.9067787505f, + 138258.61675603263f, + 138284.3279285995f, + 138310.04029633995f, + 138335.75385914298f, + 138361.46861689744f, + 138387.18456949232f, + 138412.9017168166f, + 138438.62005875923f, + 138464.3395952093f, + 138490.06032605586f, + 138515.78225118798f, + 138541.50537049473f, + 138567.2296838653f, + 138592.95519118884f, + 138618.6818923545f, + 138644.40978725153f, + 138670.13887576913f, + 138695.86915779658f, + 138721.60063322316f, + 138747.33330193823f, + 138773.06716383106f, + 138798.80221879104f, + 138824.53846670757f, + 138850.27590747006f, + 138876.01454096794f, + 138901.7543670907f, + 138927.49538572782f, + 138953.2375967688f, + 138978.9810001032f, + 139004.7255956206f, + 139030.4713832106f, + 139056.2183627628f, + 139081.96653416683f, + 139107.7158973124f, + 139133.46645208917f, + 139159.2181983869f, + 139184.97113609532f, + 139210.7252651042f, + 139236.48058530336f, + 139262.23709658257f, + 139287.99479883176f, + 139313.75369194074f, + 139339.51377579942f, + 139365.27505029776f, + 139391.03751532568f, + 139416.80117077316f, + 139442.56601653024f, + 139468.3320524869f, + 139494.09927853322f, + 139519.86769455927f, + 139545.63730045516f, + 139571.408096111f, + 139597.18008141697f, + 139622.95325626322f, + 139648.72762054f, + 139674.5031741375f, + 139700.27991694602f, + 139726.0578488558f, + 139751.83696975713f, + 139777.61727954043f, + 139803.39877809596f, + 139829.18146531415f, + 139854.9653410854f, + 139880.75040530015f, + 139906.53665784886f, + 139932.324098622f, + 139958.11272751007f, + 139983.90254440365f, + 140009.69354919327f, + 140035.4857417695f, + 140061.27912202294f, + 140087.07368984428f, + 140112.86944512415f, + 140138.6663877532f, + 140164.4645176222f, + 140190.26383462187f, + 140216.06433864293f, + 140241.86602957622f, + 140267.66890731253f, + 140293.47297174268f, + 140319.27822275754f, + 140345.08466024802f, + 140370.89228410498f, + 140396.70109421943f, + 140422.51109048226f, + 140448.32227278448f, + 140474.13464101712f, + 140499.94819507122f, + 140525.7629348378f, + 140551.578860208f, + 140577.3959710729f, + 140603.21426732364f, + 140629.03374885136f, + 140654.8544155473f, + 140680.67626730262f, + 140706.49930400858f, + 140732.32352555645f, + 140758.1489318375f, + 140783.97552274304f, + 140809.80329816442f, + 140835.63225799298f, + 140861.46240212015f, + 140887.2937304373f, + 140913.12624283586f, + 140938.95993920733f, + 140964.79481944317f, + 140990.63088343487f, + 141016.468131074f, + 141042.30656225214f, + 141068.14617686084f, + 141093.98697479168f, + 141119.82895593636f, + 141145.6721201865f, + 141171.51646743377f, + 141197.36199756994f, + 141223.20871048668f, + 141249.05660607578f, + 141274.90568422904f, + 141300.75594483822f, + 141326.6073877952f, + 141352.4600129918f, + 141378.31382031992f, + 141404.16880967148f, + 141430.02498093838f, + 141455.8823340126f, + 141481.74086878612f, + 141507.60058515094f, + 141533.4614829991f, + 141559.32356222265f, + 141585.18682271364f, + 141611.0512643642f, + 141636.9168870665f, + 141662.78369071265f, + 141688.6516751948f, + 141714.5208404052f, + 141740.39118623605f, + 141766.26271257963f, + 141792.1354193282f, + 141818.00930637406f, + 141843.88437360956f, + 141869.760620927f, + 141895.6380482188f, + 141921.51665537735f, + 141947.39644229505f, + 141973.27740886438f, + 141999.15955497778f, + 142025.0428805278f, + 142050.9273854069f, + 142076.81306950765f, + 142102.69993272264f, + 142128.58797494444f, + 142154.4771960657f, + 142180.36759597904f, + 142206.25917457714f, + 142232.15193175265f, + 142258.04586739838f, + 142283.94098140698f, + 142309.83727367126f, + 142335.734744084f, + 142361.63339253806f, + 142387.5332189262f, + 142413.43422314132f, + 142439.33640507632f, + 142465.23976462413f, + 142491.14430167765f, + 142517.05001612983f, + 142542.95690787368f, + 142568.86497680223f, + 142594.77422280848f, + 142620.6846457855f, + 142646.5962456264f, + 142672.50902222423f, + 142698.42297547215f, + 142724.33810526333f, + 142750.25441149093f, + 142776.17189404817f, + 142802.09055282827f, + 142828.0103877245f, + 142853.93139863008f, + 142879.85358543837f, + 142905.77694804268f, + 142931.70148633636f, + 142957.62720021277f, + 142983.55408956532f, + 143009.48215428743f, + 143035.41139427255f, + 143061.34180941415f, + 143087.2733996057f, + 143113.20616474075f, + 143139.14010471283f, + 143165.0752194155f, + 143191.01150874238f, + 143216.94897258704f, + 143242.88761084314f, + 143268.82742340435f, + 143294.76841016437f, + 143320.71057101688f, + 143346.65390585564f, + 143372.59841457437f, + 143398.54409706692f, + 143424.490953227f, + 143450.43898294857f, + 143476.38818612538f, + 143502.33856265133f, + 143528.29011242036f, + 143554.24283532638f, + 143580.19673126334f, + 143606.1518001252f, + 143632.10804180597f, + 143658.0654561997f, + 143684.02404320036f, + 143709.98380270213f, + 143735.944734599f, + 143761.9068387852f, + 143787.87011515474f, + 143813.83456360188f, + 143839.8001840208f, + 143865.7669763057f, + 143891.7349403508f, + 143917.7040760504f, + 143943.67438329876f, + 143969.6458619902f, + 143995.61851201905f, + 144021.59233327967f, + 144047.56732566646f, + 144073.54348907378f, + 144099.52082339607f, + 144125.49932852783f, + 144151.4790043635f, + 144177.45985079758f, + 144203.44186772458f, + 144229.4250550391f, + 144255.40941263564f, + 144281.39494040885f, + 144307.3816382533f, + 144333.36950606373f, + 144359.35854373468f, + 144385.34875116093f, + 144411.34012823718f, + 144437.33267485813f, + 144463.32639091855f, + 144489.32127631325f, + 144515.31733093705f, + 144541.31455468474f, + 144567.3129474512f, + 144593.3125091313f, + 144619.31323961995f, + 144645.31513881206f, + 144671.31820660262f, + 144697.32244288657f, + 144723.3278475589f, + 144749.33442051467f, + 144775.34216164888f, + 144801.35107085665f, + 144827.36114803303f, + 144853.37239307314f, + 144879.38480587213f, + 144905.39838632516f, + 144931.41313432742f, + 144957.4290497741f, + 144983.44613256046f, + 145009.46438258173f, + 145035.48379973322f, + 145061.5043839102f, + 145087.52613500805f, + 145113.54905292206f, + 145139.57313754765f, + 145165.59838878017f, + 145191.6248065151f, + 145217.65239064783f, + 145243.68114107384f, + 145269.71105768863f, + 145295.74214038774f, + 145321.77438906668f, + 145347.807803621f, + 145373.8423839463f, + 145399.87812993818f, + 145425.9150414923f, + 145451.95311850426f, + 145477.9923608698f, + 145504.03276848458f, + 145530.07434124436f, + 145556.11707904484f, + 145582.1609817818f, + 145608.20604935108f, + 145634.25228164849f, + 145660.2996785698f, + 145686.34824001096f, + 145712.39796586783f, + 145738.4488560363f, + 145764.50091041232f, + 145790.55412889185f, + 145816.60851137087f, + 145842.66405774537f, + 145868.7207679114f, + 145894.778641765f, + 145920.83767920226f, + 145946.89788011924f, + 145972.95924441208f, + 145999.02177197693f, + 146025.08546270995f, + 146051.15031650732f, + 146077.21633326527f, + 146103.28351288004f, + 146129.3518552479f, + 146155.42136026506f, + 146181.49202782792f, + 146207.56385783272f, + 146233.63685017588f, + 146259.71100475377f, + 146285.78632146274f, + 146311.86280019928f, + 146337.94044085976f, + 146364.0192433407f, + 146390.09920753856f, + 146416.18033334985f, + 146442.26262067116f, + 146468.34606939898f, + 146494.43067942993f, + 146520.51645066062f, + 146546.60338298764f, + 146572.6914763077f, + 146598.7807305174f, + 146624.87114551352f, + 146650.96272119274f, + 146677.0554574518f, + 146703.14935418745f, + 146729.2444112965f, + 146755.34062867577f, + 146781.43800622207f, + 146807.53654383228f, + 146833.6362414033f, + 146859.73709883197f, + 146885.83911601527f, + 146911.94229285014f, + 146938.04662923355f, + 146964.15212506248f, + 146990.25878023397f, + 147016.36659464505f, + 147042.4755681928f, + 147068.58570077427f, + 147094.6969922866f, + 147120.80944262692f, + 147146.92305169237f, + 147173.03781938017f, + 147199.15374558745f, + 147225.2708302115f, + 147251.38907314953f, + 147277.5084742988f, + 147303.62903355664f, + 147329.75075082036f, + 147355.87362598727f, + 147381.99765895473f, + 147408.12284962015f, + 147434.2491978809f, + 147460.37670363448f, + 147486.50536677826f, + 147512.63518720976f, + 147538.76616482646f, + 147564.89829952587f, + 147591.03159120557f, + 147617.16603976308f, + 147643.301645096f, + 147669.43840710196f, + 147695.5763256786f, + 147721.71540072354f, + 147747.85563213445f, + 147773.9970198091f, + 147800.13956364512f, + 147826.28326354033f, + 147852.42811939248f, + 147878.57413109933f, + 147904.72129855872f, + 147930.8696216685f, + 147957.01910032652f, + 147983.16973443062f, + 148009.32152387875f, + 148035.47446856883f, + 148061.62856839882f, + 148087.78382326665f, + 148113.94023307035f, + 148140.09779770792f, + 148166.2565170774f, + 148192.41639107687f, + 148218.57741960438f, + 148244.73960255808f, + 148270.90293983606f, + 148297.0674313365f, + 148323.23307695755f, + 148349.39987659742f, + 148375.56783015432f, + 148401.73693752653f, + 148427.90719861226f, + 148454.07861330983f, + 148480.25118151752f, + 148506.42490313368f, + 148532.59977805667f, + 148558.77580618486f, + 148584.95298741665f, + 148611.13132165046f, + 148637.3108087847f, + 148663.4914487179f, + 148689.6732413485f, + 148715.85618657502f, + 148742.040284296f, + 148768.22553440998f, + 148794.41193681557f, + 148820.59949141133f, + 148846.7881980959f, + 148872.97805676793f, + 148899.16906732606f, + 148925.361229669f, + 148951.55454369547f, + 148977.74900930419f, + 149003.9446263939f, + 149030.14139486343f, + 149056.3393146115f, + 149082.538385537f, + 149108.73860753875f, + 149134.9399805156f, + 149161.14250436646f, + 149187.34617899026f, + 149213.5510042859f, + 149239.75698015234f, + 149265.96410648854f, + 149292.17238319354f, + 149318.38181016635f, + 149344.59238730598f, + 149370.80411451156f, + 149397.01699168212f, + 149423.2310187168f, + 149449.4461955147f, + 149475.66252197503f, + 149501.87999799693f, + 149528.0986234796f, + 149554.31839832227f, + 149580.53932242419f, + 149606.7613956846f, + 149632.98461800278f, + 149659.2089892781f, + 149685.43450940982f, + 149711.66117829733f, + 149737.88899584f, + 149764.11796193724f, + 149790.34807648844f, + 149816.5793393931f, + 149842.8117505506f, + 149869.0453098605f, + 149895.28001722222f, + 149921.51587253538f, + 149947.75287569952f, + 149973.99102661415f, + 150000.2303251789f, + 150026.47077129342f, + 150052.71236485732f, + 150078.95510577026f, + 150105.1989939319f, + 150131.444029242f, + 150157.69021160025f, + 150183.9375409064f, + 150210.18601706024f, + 150236.43563996154f, + 150262.68640951012f, + 150288.93832560582f, + 150315.19138814852f, + 150341.44559703805f, + 150367.70095217437f, + 150393.95745345735f, + 150420.215100787f, + 150446.4738940632f, + 150472.733833186f, + 150498.99491805542f, + 150525.25714857146f, + 150551.5205246342f, + 150577.7850461437f, + 150604.05071300003f, + 150630.31752510337f, + 150656.58548235384f, + 150682.8545846516f, + 150709.1248318968f, + 150735.39622398972f, + 150761.66876083054f, + 150787.9424423195f, + 150814.2172683569f, + 150840.49323884305f, + 150866.7703536782f, + 150893.04861276277f, + 150919.32801599705f, + 150945.60856328148f, + 150971.89025451642f, + 150998.1730896023f, + 151024.45706843957f, + 151050.74219092872f, + 151077.0284569702f, + 151103.31586646455f, + 151129.6044193123f, + 151155.894115414f, + 151182.1849546702f, + 151208.47693698155f, + 151234.77006224863f, + 151261.0643303721f, + 151287.35974125259f, + 151313.65629479082f, + 151339.95399088747f, + 151366.25282944329f, + 151392.55281035902f, + 151418.85393353543f, + 151445.1561988733f, + 151471.45960627345f, + 151497.76415563675f, + 151524.06984686397f, + 151550.3766798561f, + 151576.68465451393f, + 151602.99377073845f, + 151629.30402843058f, + 151655.61542749128f, + 151681.92796782157f, + 151708.24164932242f, + 151734.55647189484f, + 151760.87243543993f, + 151787.18953985872f, + 151813.50778505235f, + 151839.82717092187f, + 151866.14769736846f, + 151892.46936429327f, + 151918.79217159748f, + 151945.1161191823f, + 151971.4412069489f, + 151997.76743479856f, + 152024.09480263255f, + 152050.42331035214f, + 152076.75295785864f, + 152103.0837450534f, + 152129.41567183775f, + 152155.74873811303f, + 152182.08294378067f, + 152208.41828874208f, + 152234.7547728987f, + 152261.09239615197f, + 152287.43115840337f, + 152313.7710595544f, + 152340.11209950657f, + 152366.45427816146f, + 152392.79759542056f, + 152419.14205118554f, + 152445.48764535793f, + 152471.8343778394f, + 152498.1822485316f, + 152524.53125733617f, + 152550.88140415482f, + 152577.23268888926f, + 152603.5851114412f, + 152629.9386717124f, + 152656.29336960468f, + 152682.64920501978f, + 152709.00617785956f, + 152735.36428802583f, + 152761.72353542043f, + 152788.0839199453f, + 152814.4454415023f, + 152840.80809999333f, + 152867.1718953204f, + 152893.53682738543f, + 152919.9028960904f, + 152946.27010133737f, + 152972.63844302832f, + 152999.0079210653f, + 153025.3785353504f, + 153051.7502857857f, + 153078.12317227334f, + 153104.4971947154f, + 153130.8723530141f, + 153157.24864707157f, + 153183.62607679f, + 153210.00464207167f, + 153236.38434281875f, + 153262.76517893354f, + 153289.1471503183f, + 153315.53025687535f, + 153341.914498507f, + 153368.29987511563f, + 153394.68638660354f, + 153421.07403287315f, + 153447.4628138269f, + 153473.85272936718f, + 153500.24377939643f, + 153526.63596381716f, + 153553.02928253182f, + 153579.42373544298f, + 153605.81932245308f, + 153632.21604346478f, + 153658.61389838057f, + 153685.0128871031f, + 153711.41300953497f, + 153737.8142655788f, + 153764.21665513728f, + 153790.62017811305f, + 153817.02483440886f, + 153843.4306239274f, + 153869.8375465714f, + 153896.24560224364f, + 153922.65479084692f, + 153949.06511228404f, + 153975.4765664578f, + 154001.88915327107f, + 154028.3028726267f, + 154054.7177244276f, + 154081.13370857667f, + 154107.55082497682f, + 154133.969073531f, + 154160.38845414226f, + 154186.8089667135f, + 154213.23061114774f, + 154239.65338734805f, + 154266.07729521746f, + 154292.50233465908f, + 154318.92850557598f, + 154345.35580787127f, + 154371.7842414481f, + 154398.21380620962f, + 154424.64450205903f, + 154451.0763288995f, + 154477.50928663427f, + 154503.9433751666f, + 154530.3785943997f, + 154556.8149442369f, + 154583.25242458144f, + 154609.69103533673f, + 154636.13077640603f, + 154662.5716476928f, + 154689.01364910032f, + 154715.45678053208f, + 154741.90104189145f, + 154768.34643308193f, + 154794.79295400696f, + 154821.24060457002f, + 154847.68938467462f, + 154874.13929422433f, + 154900.59033312264f, + 154927.04250127316f, + 154953.49579857948f, + 154979.9502249452f, + 155006.40578027396f, + 155032.86246446942f, + 155059.32027743524f, + 155085.77921907514f, + 155112.2392892928f, + 155138.70048799197f, + 155165.16281507642f, + 155191.6262704499f, + 155218.09085401625f, + 155244.55656567923f, + 155271.0234053427f, + 155297.4913729106f, + 155323.96046828668f, + 155350.4306913749f, + 155376.9020420792f, + 155403.37452030348f, + 155429.8481259517f, + 155456.3228589279f, + 155482.79871913602f, + 155509.2757064801f, + 155535.75382086422f, + 155562.2330621924f, + 155588.71343036872f, + 155615.1949252973f, + 155641.67754688227f, + 155668.1612950278f, + 155694.64616963797f, + 155721.13217061706f, + 155747.6192978692f, + 155774.1075512987f, + 155800.59693080973f, + 155827.0874363066f, + 155853.5790676936f, + 155880.07182487496f, + 155906.56570775513f, + 155933.06071623837f, + 155959.5568502291f, + 155986.05410963166f, + 156012.5524943505f, + 156039.05200429005f, + 156065.55263935472f, + 156092.054399449f, + 156118.5572844774f, + 156145.06129434443f, + 156171.5664289546f, + 156198.07268821247f, + 156224.5800720226f, + 156251.0885802896f, + 156277.5982129181f, + 156304.10896981266f, + 156330.620850878f, + 156357.1338560188f, + 156383.6479851397f, + 156410.16323814544f, + 156436.67961494075f, + 156463.1971154304f, + 156489.71573951913f, + 156516.2354871118f, + 156542.7563581131f, + 156569.278352428f, + 156595.80146996127f, + 156622.32571061782f, + 156648.85107430254f, + 156675.37756092031f, + 156701.90517037612f, + 156728.4339025749f, + 156754.96375742165f, + 156781.4947348213f, + 156808.0268346789f, + 156834.5600568995f, + 156861.09440138817f, + 156887.62986804993f, + 156914.16645678994f, + 156940.70416751326f, + 156967.24300012505f, + 156993.78295453047f, + 157020.32403063469f, + 157046.8662283429f, + 157073.40954756032f, + 157099.9539881922f, + 157126.49955014378f, + 157153.04623332032f, + 157179.59403762716f, + 157206.14296296958f, + 157232.69300925292f, + 157259.24417638258f, + 157285.79646426387f, + 157312.3498728022f, + 157338.90440190304f, + 157365.46005147175f, + 157392.01682141385f, + 157418.57471163478f, + 157445.13372204005f, + 157471.69385253516f, + 157498.25510302564f, + 157524.81747341706f, + 157551.38096361503f, + 157577.9455735251f, + 157604.51130305286f, + 157631.07815210402f, + 157657.64612058416f, + 157684.21520839902f, + 157710.78541545427f, + 157737.3567416556f, + 157763.92918690876f, + 157790.50275111952f, + 157817.07743419363f, + 157843.65323603692f, + 157870.23015655516f, + 157896.80819565422f, + 157923.3873532399f, + 157949.96762921812f, + 157976.5490234948f, + 158003.13153597576f, + 158029.715166567f, + 158056.2999151745f, + 158082.88578170416f, + 158109.47276606198f, + 158136.06086815405f, + 158162.6500878863f, + 158189.24042516484f, + 158215.83187989573f, + 158242.42445198505f, + 158269.01814133892f, + 158295.61294786347f, + 158322.20887146486f, + 158348.80591204923f, + 158375.4040695228f, + 158402.00334379176f, + 158428.60373476235f, + 158455.2052423408f, + 158481.80786643337f, + 158508.4116069464f, + 158535.01646378616f, + 158561.62243685898f, + 158588.2295260712f, + 158614.8377313292f, + 158641.44705253936f, + 158668.05748960807f, + 158694.6690424418f, + 158721.28171094693f, + 158747.89549502998f, + 158774.5103945974f, + 158801.12640955573f, + 158827.74353981143f, + 158854.36178527112f, + 158880.9811458413f, + 158907.60162142856f, + 158934.22321193956f, + 158960.84591728085f, + 158987.4697373591f, + 159014.09467208097f, + 159040.72072135314f, + 159067.3478850823f, + 159093.9761631752f, + 159120.60555553852f, + 159147.23606207906f, + 159173.8676827036f, + 159200.5004173189f, + 159227.13426583182f, + 159253.76922814918f, + 159280.4053041778f, + 159307.0424938246f, + 159333.6807969965f, + 159360.32021360032f, + 159386.96074354305f, + 159413.60238673165f, + 159440.2451430731f, + 159466.88901247433f, + 159493.53399484244f, + 159520.18009008438f, + 159546.8272981072f, + 159573.47561881805f, + 159600.12505212397f, + 159626.77559793205f, + 159653.4272561494f, + 159680.08002668325f, + 159706.7339094407f, + 159733.38890432892f, + 159760.04501125516f, + 159786.70223012666f, + 159813.3605608506f, + 159840.02000333427f, + 159866.68055748497f, + 159893.34222320997f, + 159920.00500041663f, + 159946.66888901225f, + 159973.33388890422f, + 159999.99999999988f, + 160026.66722220668f, + 160053.33555543202f, + 160080.0049995833f, + 160106.675554568f, + 160133.3472202936f, + 160160.0199966676f, + 160186.6938835975f, + 160213.36888099083f, + 160240.04498875517f, + 160266.72220679803f, + 160293.4005350271f, + 160320.07997334987f, + 160346.76052167406f, + 160373.4421799073f, + 160400.1249479572f, + 160426.8088257315f, + 160453.49381313793f, + 160480.17991008417f, + 160506.86711647795f, + 160533.5554322271f, + 160560.24485723933f, + 160586.93539142248f, + 160613.62703468435f, + 160640.31978693279f, + 160667.0136480757f, + 160693.70861802087f, + 160720.40469667627f, + 160747.1018839498f, + 160773.80017974938f, + 160800.49958398298f, + 160827.20009655855f, + 160853.9017173841f, + 160880.60444636765f, + 160907.30828341722f, + 160934.0132284409f, + 160960.71928134665f, + 160987.4264420427f, + 161014.13471043704f, + 161040.84408643784f, + 161067.55456995327f, + 161094.26616089148f, + 161120.97885916062f, + 161147.69266466892f, + 161174.40757732463f, + 161201.12359703594f, + 161227.84072371112f, + 161254.55895725847f, + 161281.27829758628f, + 161307.99874460287f, + 161334.72029821656f, + 161361.4429583357f, + 161388.1667248687f, + 161414.8915977239f, + 161441.61757680977f, + 161468.34466203468f, + 161495.07285330712f, + 161521.80215053557f, + 161548.53255362847f, + 161575.26406249436f, + 161601.99667704175f, + 161628.7303971792f, + 161655.46522281526f, + 161682.2011538585f, + 161708.93819021754f, + 161735.676331801f, + 161762.4155785175f, + 161789.1559302757f, + 161815.89738698432f, + 161842.639948552f, + 161869.38361488748f, + 161896.1283858995f, + 161922.87426149676f, + 161949.62124158812f, + 161976.3693260823f, + 162003.1185148881f, + 162029.8688079144f, + 162056.62020507f, + 162083.37270626382f, + 162110.1263114047f, + 162136.88102040152f, + 162163.63683316324f, + 162190.3937495988f, + 162217.1517696171f, + 162243.91089312723f, + 162270.67112003808f, + 162297.43245025873f, + 162324.1948836982f, + 162350.9584202655f, + 162377.72305986975f, + 162404.48880242003f, + 162431.25564782543f, + 162458.0235959951f, + 162484.79264683815f, + 162511.56280026378f, + 162538.33405618116f, + 162565.1064144995f, + 162591.879875128f, + 162618.65443797593f, + 162645.43010295252f, + 162672.20686996708f, + 162698.98473892888f, + 162725.76370974723f, + 162752.54378233146f, + 162779.32495659095f, + 162806.10723243505f, + 162832.89060977317f, + 162859.67508851466f, + 162886.46066856902f, + 162913.24734984562f, + 162940.035132254f, + 162966.82401570358f, + 162993.6140001039f, + 163020.40508536444f, + 163047.19727139478f, + 163073.99055810447f, + 163100.78494540305f, + 163127.58043320014f, + 163154.37702140535f, + 163181.1747099283f, + 163207.97349867865f, + 163234.77338756606f, + 163261.57437650024f, + 163288.37646539084f, + 163315.17965414765f, + 163341.98394268038f, + 163368.78933089875f, + 163395.5958187126f, + 163422.40340603172f, + 163449.2120927659f, + 163476.02187882498f, + 163502.83276411882f, + 163529.6447485573f, + 163556.45783205028f, + 163583.2720145077f, + 163610.08729583945f, + 163636.90367595552f, + 163663.72115476584f, + 163690.53973218042f, + 163717.35940810922f, + 163744.18018246227f, + 163771.00205514964f, + 163797.82502608138f, + 163824.64909516752f, + 163851.4742623182f, + 163878.3005274435f, + 163905.12789045356f, + 163931.95635125853f, + 163958.78590976857f, + 163985.61656589387f, + 164012.44831954464f, + 164039.2811706311f, + 164066.11511906344f, + 164092.950164752f, + 164119.786307607f, + 164146.62354753874f, + 164173.46188445756f, + 164200.30131827376f, + 164227.1418488977f, + 164253.98347623978f, + 164280.82620021031f, + 164307.6700207198f, + 164334.51493767856f, + 164361.3609509971f, + 164388.20806058586f, + 164415.05626635533f, + 164441.905568216f, + 164468.75596607837f, + 164495.607459853f, + 164522.4600494504f, + 164549.31373478117f, + 164576.1685157559f, + 164603.02439228518f, + 164629.88136427966f, + 164656.7394316499f, + 164683.59859430668f, + 164710.4588521606f, + 164737.32020512238f, + 164764.18265310273f, + 164791.04619601235f, + 164817.91083376206f, + 164844.77656626256f, + 164871.6433934247f, + 164898.51131515924f, + 164925.38033137703f, + 164952.25044198887f, + 164979.1216469057f, + 165005.9939460383f, + 165032.86733929766f, + 165059.7418265946f, + 165086.61740784015f + }; + } +} diff --git a/SharpJaad.AAC/Syntax/PCE.cs b/SharpJaad.AAC/Syntax/PCE.cs new file mode 100644 index 0000000..dd54239 --- /dev/null +++ b/SharpJaad.AAC/Syntax/PCE.cs @@ -0,0 +1,179 @@ +namespace SharpJaad.AAC.Syntax +{ + public class PCE : Element + { + private const int MAX_FRONT_CHANNEL_ELEMENTS = 16; + private const int MAX_SIDE_CHANNEL_ELEMENTS = 16; + private const int MAX_BACK_CHANNEL_ELEMENTS = 16; + private const int MAX_LFE_CHANNEL_ELEMENTS = 4; + private const int MAX_ASSOC_DATA_ELEMENTS = 8; + private const int MAX_VALID_CC_ELEMENTS = 16; + + public sealed class TaggedElement + { + public bool _isCPE; + public int _tag; + + public TaggedElement(bool isCPE, int tag) + { + _isCPE = isCPE; + _tag = tag; + } + + public bool IsIsCPE() + { + return _isCPE; + } + + public int GetTag() + { + return _tag; + } + } + + public sealed class CCE + { + public bool _isIndSW; + public int _tag; + + public CCE(bool isIndSW, int tag) + { + _isIndSW = isIndSW; + _tag = tag; + } + + public bool IsIsIndSW() + { + return _isIndSW; + } + + public int GetTag() + { + return _tag; + } + } + + private Profile _profile; + private SampleFrequency _sampleFrequency; + private int _frontChannelElementsCount, _sideChannelElementsCount, _backChannelElementsCount; + private int _lfeChannelElementsCount, _assocDataElementsCount; + private int _validCCElementsCount; + private bool _monoMixdown, _stereoMixdown, _matrixMixdownIDXPresent; + private int _monoMixdownElementNumber, _stereoMixdownElementNumber, _matrixMixdownIDX; + private bool _pseudoSurround; + private TaggedElement[] _frontElements, _sideElements, _backElements; + private int[] _lfeElementTags; + private int[] _assocDataElementTags; + private CCE[] _ccElements; + private byte[] _commentFieldData; + + public PCE() + { + _frontElements = new TaggedElement[MAX_FRONT_CHANNEL_ELEMENTS]; + _sideElements = new TaggedElement[MAX_SIDE_CHANNEL_ELEMENTS]; + _backElements = new TaggedElement[MAX_BACK_CHANNEL_ELEMENTS]; + _lfeElementTags = new int[MAX_LFE_CHANNEL_ELEMENTS]; + _assocDataElementTags = new int[MAX_ASSOC_DATA_ELEMENTS]; + _ccElements = new CCE[MAX_VALID_CC_ELEMENTS]; + _sampleFrequency = SampleFrequency.SAMPLE_FREQUENCY_NONE; + } + + public void Decode(BitStream input) + { + ReadElementInstanceTag(input); + + _profile = (Profile)input.ReadBits(2); + + _sampleFrequency = (SampleFrequency)input.ReadBits(4); + + _frontChannelElementsCount = input.ReadBits(4); + _sideChannelElementsCount = input.ReadBits(4); + _backChannelElementsCount = input.ReadBits(4); + _lfeChannelElementsCount = input.ReadBits(2); + _assocDataElementsCount = input.ReadBits(3); + _validCCElementsCount = input.ReadBits(4); + + if (_monoMixdown = input.ReadBool()) + { + //Constants.LOGGER.warning("mono mixdown present, but not yet supported"); + _monoMixdownElementNumber = input.ReadBits(4); + } + if (_stereoMixdown = input.ReadBool()) + { + //Constants.LOGGER.warning("stereo mixdown present, but not yet supported"); + _stereoMixdownElementNumber = input.ReadBits(4); + } + if (_matrixMixdownIDXPresent = input.ReadBool()) + { + //Constants.LOGGER.warning("matrix mixdown present, but not yet supported"); + _matrixMixdownIDX = input.ReadBits(2); + _pseudoSurround = input.ReadBool(); + } + + ReadTaggedElementArray(_frontElements, input, _frontChannelElementsCount); + + ReadTaggedElementArray(_sideElements, input, _sideChannelElementsCount); + + ReadTaggedElementArray(_backElements, input, _backChannelElementsCount); + + int i; + for (i = 0; i < _lfeChannelElementsCount; ++i) + { + _lfeElementTags[i] = input.ReadBits(4); + } + + for (i = 0; i < _assocDataElementsCount; ++i) + { + _assocDataElementTags[i] = input.ReadBits(4); + } + + for (i = 0; i < _validCCElementsCount; ++i) + { + _ccElements[i] = new CCE(input.ReadBool(), input.ReadBits(4)); + } + + input.ByteAlign(); + + int commentFieldBytes = input.ReadBits(8); + _commentFieldData = new byte[commentFieldBytes]; + for (i = 0; i < commentFieldBytes; i++) + { + _commentFieldData[i] = (byte)input.ReadBits(8); + } + } + + private void ReadTaggedElementArray(TaggedElement[] te, BitStream input, int len) + { + for (int i = 0; i < len; ++i) + { + te[i] = new TaggedElement(input.ReadBool(), input.ReadBits(4)); + } + } + + public Profile GetProfile() + { + return _profile; + } + + public SampleFrequency GetSampleFrequency() + { + return _sampleFrequency; + } + + public int GetChannelCount() + { + int count = _lfeChannelElementsCount + _assocDataElementsCount; + + for (int n = 0; n < _frontChannelElementsCount; ++n) + count += _frontElements[n]._isCPE ? 2 : 1; + + for (int n = 0; n < _sideChannelElementsCount; ++n) + count += _sideElements[n]._isCPE ? 2 : 1; + + for (int n = 0; n < _backChannelElementsCount; ++n) + count += _backElements[n]._isCPE ? 2 : 1; + + return count; + } + } +} diff --git a/SharpJaad.AAC/Syntax/SCE_LFE.cs b/SharpJaad.AAC/Syntax/SCE_LFE.cs new file mode 100644 index 0000000..163f052 --- /dev/null +++ b/SharpJaad.AAC/Syntax/SCE_LFE.cs @@ -0,0 +1,23 @@ +namespace SharpJaad.AAC.Syntax +{ + public class SCE_LFE : Element + { + private ICStream _ics; + + public SCE_LFE(DecoderConfig config) + { + _ics = new ICStream(config); + } + + public void Decode(BitStream input, DecoderConfig conf) + { + ReadElementInstanceTag(input); + _ics.Decode(input, false, conf); + } + + public ICStream GetICStream() + { + return _ics; + } + } +} diff --git a/SharpJaad.AAC/Syntax/ScaleFactorBands.cs b/SharpJaad.AAC/Syntax/ScaleFactorBands.cs new file mode 100644 index 0000000..532d22b --- /dev/null +++ b/SharpJaad.AAC/Syntax/ScaleFactorBands.cs @@ -0,0 +1,112 @@ +namespace SharpJaad.AAC.Syntax +{ + public static class ScaleFactorBands + { + /* scalefactor-band tables end with -1, so that an error can be detected + by index[i+1] without an exception */ + public static int[] SWB_LONG_WINDOW_COUNT = { + 41, 41, 47, 49, 49, 51, 47, 47, 43, 43, 43, 40 + }; + public static int[] SWB_OFFSET_1024_96 = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, + 64, 72, 80, 88, 96, 108, 120, 132, 144, 156, 172, 188, 212, 240, + 276, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, + -1 + }; + public static int[] SWB_OFFSET_1024_64 = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, + 64, 72, 80, 88, 100, 112, 124, 140, 156, 172, 192, 216, 240, 268, + 304, 344, 384, 424, 464, 504, 544, 584, 624, 664, 704, 744, 784, 824, + 864, 904, 944, 984, 1024, + -1 + }; + public static int[] SWB_OFFSET_1024_48 = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, + 80, 88, 96, 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, + 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, + 768, 800, 832, 864, 896, 928, 1024, + -1 + }; + public static int[] SWB_OFFSET_1024_32 = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, + 80, 88, 96, 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, + 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, + 768, 800, 832, 864, 896, 928, 960, 992, 1024, + -1 + }; + public static int[] SWB_OFFSET_1024_24 = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, + 76, 84, 92, 100, 108, 116, 124, 136, 148, 160, 172, 188, 204, 220, + 240, 260, 284, 308, 336, 364, 396, 432, 468, 508, 552, 600, 652, 704, + 768, 832, 896, 960, 1024, + -1 + }; + public static int[] SWB_OFFSET_1024_16 = { + 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 100, 112, 124, + 136, 148, 160, 172, 184, 196, 212, 228, 244, 260, 280, 300, 320, 344, + 368, 396, 424, 456, 492, 532, 572, 616, 664, 716, 772, 832, 896, 960, 1024, + -1 + }; + public static int[] SWB_OFFSET_1024_8 = { + 0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 172, + 188, 204, 220, 236, 252, 268, 288, 308, 328, 348, 372, 396, 420, 448, + 476, 508, 544, 580, 620, 664, 712, 764, 820, 880, 944, 1024, + -1 + }; + public static int[][] SWB_OFFSET_LONG_WINDOW = { + SWB_OFFSET_1024_96, + SWB_OFFSET_1024_96, + SWB_OFFSET_1024_64, + SWB_OFFSET_1024_48, + SWB_OFFSET_1024_48, + SWB_OFFSET_1024_32, + SWB_OFFSET_1024_24, + SWB_OFFSET_1024_24, + SWB_OFFSET_1024_16, + SWB_OFFSET_1024_16, + SWB_OFFSET_1024_16, + SWB_OFFSET_1024_8 + }; + public static int[] SWB_SHORT_WINDOW_COUNT = { + 12, 12, 12, 14, 14, 14, 15, 15, 15, 15, 15, 15 + }; + public static int[] SWB_OFFSET_128_96 = { + 0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128, + -1 + }; + public static int[] SWB_OFFSET_128_64 = { + 0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128, + -1 + }; + public static int[] SWB_OFFSET_128_48 = { + 0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96, 112, 128, + -1 + }; + public static int[] SWB_OFFSET_128_24 = { + 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 64, 76, 92, 108, 128, + -1 + }; + public static int[] SWB_OFFSET_128_16 = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 60, 72, 88, 108, 128, + -1 + }; + public static int[] SWB_OFFSET_128_8 = { + 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 60, 72, 88, 108, 128, + -1 + }; + public static int[][] SWB_OFFSET_SHORT_WINDOW = { + SWB_OFFSET_128_96, + SWB_OFFSET_128_96, + SWB_OFFSET_128_64, + SWB_OFFSET_128_48, + SWB_OFFSET_128_48, + SWB_OFFSET_128_48, + SWB_OFFSET_128_24, + SWB_OFFSET_128_24, + SWB_OFFSET_128_16, + SWB_OFFSET_128_16, + SWB_OFFSET_128_16, + SWB_OFFSET_128_8 + }; + } +} diff --git a/SharpJaad.AAC/Syntax/ScaleFactorTable.cs b/SharpJaad.AAC/Syntax/ScaleFactorTable.cs new file mode 100644 index 0000000..2a4f092 --- /dev/null +++ b/SharpJaad.AAC/Syntax/ScaleFactorTable.cs @@ -0,0 +1,94 @@ +namespace SharpJaad.AAC.Syntax +{ + public static class ScaleFactorTable + { + public static float[] SCALEFACTOR_TABLE = {8.881784E-16f, 1.0562281E-15f, 1.2560739E-15f, + 1.4937321E-15f, 1.7763568E-15f, 2.1124561E-15f, + 2.5121479E-15f, 2.9874642E-15f, 3.5527137E-15f, 4.2249122E-15f, 5.0242958E-15f, + 5.9749285E-15f, 7.1054274E-15f, 8.4498245E-15f, 1.00485916E-14f, 1.1949857E-14f, + 1.4210855E-14f, 1.6899649E-14f, 2.0097183E-14f, 2.3899714E-14f, 2.842171E-14f, + 3.3799298E-14f, 4.0194366E-14f, 4.7799428E-14f, 5.684342E-14f, 6.7598596E-14f, + 8.038873E-14f, 9.5598856E-14f, 1.1368684E-13f, 1.3519719E-13f, 1.6077747E-13f, + 1.9119771E-13f, 2.2737368E-13f, 2.7039438E-13f, 3.2155493E-13f, 3.8239542E-13f, + 4.5474735E-13f, 5.4078877E-13f, 6.4310986E-13f, 7.6479085E-13f, 9.094947E-13f, + 1.0815775E-12f, 1.2862197E-12f, 1.5295817E-12f, 1.8189894E-12f, 2.163155E-12f, + 2.5724394E-12f, 3.0591634E-12f, 3.6379788E-12f, 4.32631E-12f, 5.144879E-12f, + 6.1183268E-12f, 7.2759576E-12f, 8.65262E-12f, 1.0289758E-11f, 1.22366535E-11f, + 1.4551915E-11f, 1.730524E-11f, 2.0579516E-11f, 2.4473307E-11f, 2.910383E-11f, + 3.461048E-11f, 4.115903E-11f, 4.8946614E-11f, 5.820766E-11f, 6.922096E-11f, + 8.231806E-11f, 9.789323E-11f, 1.1641532E-10f, 1.3844192E-10f, 1.6463612E-10f, + 1.9578646E-10f, 2.3283064E-10f, 2.7688385E-10f, 3.2927225E-10f, 3.915729E-10f, + 4.656613E-10f, 5.537677E-10f, 6.585445E-10f, 7.831458E-10f, 9.313226E-10f, + 1.1075354E-9f, 1.317089E-9f, 1.5662917E-9f, 1.8626451E-9f, 2.2150708E-9f, + 2.634178E-9f, 3.1325833E-9f, 3.7252903E-9f, 4.4301416E-9f, 5.268356E-9f, + 6.2651666E-9f, 7.4505806E-9f, 8.860283E-9f, 1.0536712E-8f, 1.2530333E-8f, + 1.4901161E-8f, 1.7720566E-8f, 2.1073424E-8f, 2.5060666E-8f, 2.9802322E-8f, + 3.5441133E-8f, 4.2146848E-8f, 5.0121333E-8f, 5.9604645E-8f, 7.0882265E-8f, + 8.4293696E-8f, 1.00242666E-7f, 1.1920929E-7f, 1.4176453E-7f, 1.6858739E-7f, + 2.0048533E-7f, 2.3841858E-7f, 2.8352906E-7f, 3.3717478E-7f, 4.0097066E-7f, + 4.7683716E-7f, 5.670581E-7f, 6.7434956E-7f, 8.019413E-7f, 9.536743E-7f, + 1.1341162E-6f, 1.3486991E-6f, 1.6038827E-6f, 1.9073486E-6f, 2.2682325E-6f, + 2.6973983E-6f, 3.2077653E-6f, 3.8146973E-6f, 4.536465E-6f, 5.3947965E-6f, + 6.4155306E-6f, 7.6293945E-6f, 9.07293E-6f, 1.0789593E-5f, 1.2831061E-5f, + 1.5258789E-5f, 1.814586E-5f, 2.1579186E-5f, 2.5662122E-5f, 3.0517578E-5f, + 3.629172E-5f, 4.3158372E-5f, 5.1324245E-5f, 6.1035156E-5f, 7.258344E-5f, + 8.6316744E-5f, 1.0264849E-4f, 1.2207031E-4f, 1.4516688E-4f, 1.7263349E-4f, + 2.0529698E-4f, 2.4414062E-4f, 2.9033376E-4f, 3.4526698E-4f, 4.1059396E-4f, + 4.8828125E-4f, 5.806675E-4f, 6.9053395E-4f, 8.211879E-4f, 9.765625E-4f, + 0.001161335f, 0.0013810679f, 0.0016423758f, 0.001953125f, 0.00232267f, + 0.0027621358f, 0.0032847517f, 0.00390625f, 0.00464534f, 0.0055242716f, + 0.0065695033f, 0.0078125f, 0.00929068f, 0.011048543f, 0.013139007f, + 0.015625f, 0.01858136f, 0.022097087f, 0.026278013f, 0.03125f, + 0.03716272f, 0.044194173f, 0.052556027f, 0.0625f, 0.07432544f, + 0.088388346f, 0.10511205f, 0.125f, 0.14865088f, 0.17677669f, + 0.2102241f, 0.25f, 0.29730177f, 0.35355338f, 0.4204482f, + 0.5f, 0.59460354f, 0.70710677f, 0.8408964f, 1.0f, + 1.1892071f, 1.4142135f, 1.6817929f, 2.0f, 2.3784142f, + 2.828427f, 3.3635857f, 4.0f, 4.7568283f, 5.656854f, + 6.7271714f, 8.0f, 9.513657f, 11.313708f, 13.454343f, + 16.0f, 19.027313f, 22.627417f, 26.908686f, 32.0f, + 38.054626f, 45.254833f, 53.81737f, 64.0f, 76.10925f, + 90.50967f, 107.63474f, 128.0f, 152.2185f, 181.01933f, + 215.26949f, 256.0f, 304.437f, 362.03867f, 430.53897f, + 512.0f, 608.874f, 724.07733f, 861.07794f, 1024.0f, + 1217.748f, 1448.1547f, 1722.1559f, 2048.0f, 2435.496f, + 2896.3093f, 3444.3118f, 4096.0f, 4870.992f, 5792.6187f, + 6888.6235f, 8192.0f, 9741.984f, 11585.237f, 13777.247f, + 16384.0f, 19483.969f, 23170.475f, 27554.494f, 32768.0f, + 38967.938f, 46340.95f, 55108.99f, 65536.0f, 77935.875f, + 92681.9f, 110217.98f, 131072.0f, 155871.75f, 185363.8f, + 220435.95f, 262144.0f, 311743.5f, 370727.6f, 440871.9f, + 524288.0f, 623487.0f, 741455.2f, 881743.8f, 1048576.0f, + 1246974.0f, 1482910.4f, 1763487.6f, 2097152.0f, 2493948.0f, + 2965820.8f, 3526975.2f, 4194304.0f, 4987896.0f, 5931641.5f, + 7053950.5f, 8388608.0f, 9975792.0f, 1.1863283E7f, 1.4107901E7f, + 1.6777216E7f, 1.9951584E7f, 2.3726566E7f, 2.8215802E7f, 3.3554432E7f, + 3.9903168E7f, 4.7453132E7f, 5.6431604E7f, 6.7108864E7f, 7.9806336E7f, + 9.4906264E7f, 1.12863208E8f, 1.34217728E8f, 1.59612672E8f, 1.89812528E8f, + 2.25726416E8f, 2.68435456E8f, 3.19225344E8f, 3.79625056E8f, 4.51452832E8f, + 5.3687091E8f, 6.3845069E8f, 7.5925011E8f, 9.0290566E8f, 1.07374182E9f, + 1.27690138E9f, 1.51850022E9f, 1.80581133E9f, 2.14748365E9f, 2.55380275E9f, + 3.03700045E9f, 3.61162266E9f, 4.2949673E9f, 5.1076055E9f, 6.0740009E9f, + 7.2232453E9f, 8.5899346E9f, 1.0215211E10f, 1.21480018E10f, 1.44464906E10f, + 1.71798692E10f, 2.0430422E10f, 2.42960036E10f, 2.88929812E10f, 3.4359738E10f, + 4.0860844E10f, 4.8592007E10f, 5.7785962E10f, 6.8719477E10f, 8.1721688E10f, + 9.7184014E10f, 1.15571925E11f, 1.37438953E11f, 1.63443376E11f, 1.94368029E11f, + 2.3114385E11f, 2.74877907E11f, 3.26886752E11f, 3.88736057E11f, 4.622877E11f, + 5.4975581E11f, 6.537735E11f, 7.7747211E11f, 9.245754E11f, 1.09951163E12f, + 1.30754701E12f, 1.55494423E12f, 1.8491508E12f, 2.19902326E12f, 2.61509402E12f, + 3.10988846E12f, 3.6983016E12f, 4.3980465E12f, 5.230188E12f, 6.2197769E12f, + 7.3966032E12f, 8.796093E12f, 1.04603761E13f, 1.24395538E13f, 1.47932064E13f, + 1.7592186E13f, 2.09207521E13f, 2.48791077E13f, 2.95864128E13f, 3.5184372E13f, + 4.1841504E13f, 4.9758215E13f, 5.9172826E13f, 7.0368744E13f, 8.3683009E13f, + 9.9516431E13f, 1.18345651E14f, 1.40737488E14f, 1.67366017E14f, 1.99032861E14f, + 2.36691302E14f, 2.81474977E14f, 3.34732034E14f, 3.98065723E14f, 4.73382605E14f, + 5.6294995E14f, 6.6946407E14f, 7.9613145E14f, 9.4676521E14f, 1.12589991E15f, + 1.33892814E15f, 1.59226289E15f, 1.89353042E15f, 2.25179981E15f, 2.67785627E15f, + 3.18452578E15f, 3.78706084E15f, 4.5035996E15f, 5.3557125E15f, 6.3690516E15f, + 7.5741217E15f, 9.0071993E15f, 1.07114251E16f, 1.27381031E16f, 1.51482434E16f, + 1.80143985E16f, 2.14228502E16f, 2.54762063E16f, 3.02964867E16f, 3.6028797E16f, + 4.28457E16f, 5.0952413E16f, 6.0592973E16f, 7.2057594E16f, 8.5691401E16f, + 1.01904825E17f, 1.21185947E17f + }; + } +} diff --git a/SharpJaad.AAC/Syntax/SyntacticElements.cs b/SharpJaad.AAC/Syntax/SyntacticElements.cs new file mode 100644 index 0000000..3bcbc58 --- /dev/null +++ b/SharpJaad.AAC/Syntax/SyntacticElements.cs @@ -0,0 +1,465 @@ +using SharpJaad.AAC.Filterbank; +using SharpJaad.AAC.Sbr; +using SharpJaad.AAC.Tools; +using System; + +namespace SharpJaad.AAC.Syntax +{ + public class SyntacticElements + { + //global properties + private DecoderConfig config; + private bool sbrPresent, psPresent; + private int bitsRead; + private int frame = 0; + //elements + private PCE pce; + private Element[] elements; //SCE, LFE and CPE + private CCE[] cces; + private DSE[] dses; + private FIL[] fils; + private int curElem, curCCE, curDSE, curFIL; + private float[][] data; + + public SyntacticElements(DecoderConfig config) + { + this.config = config; + + pce = new PCE(); + elements = new Element[4 * Constants.MAX_ELEMENTS]; + cces = new CCE[Constants.MAX_ELEMENTS]; + dses = new DSE[Constants.MAX_ELEMENTS]; + fils = new FIL[Constants.MAX_ELEMENTS]; + + StartNewFrame(); + } + + public void StartNewFrame() + { + curElem = 0; + curCCE = 0; + curDSE = 0; + curFIL = 0; + sbrPresent = false; + psPresent = false; + bitsRead = 0; + } + + public void Decode(BitStream input) + { + ++frame; + int start = input.GetPosition(); //should be 0 + + int type; + Element prev = null; + bool content = true; + if (!config.GetProfile().IsErrorResilientProfile()) + { + while (content && (type = input.ReadBits(3)) != Constants.ELEMENT_END) + { + switch (type) + { + case Constants.ELEMENT_SCE: + case Constants.ELEMENT_LFE: + //LOGGER.finest("SCE"); + prev = DecodeSCE_LFE(input); + break; + case Constants.ELEMENT_CPE: + //LOGGER.finest("CPE"); + prev = DecodeCPE(input); + break; + case Constants.ELEMENT_CCE: + //LOGGER.finest("CCE"); + DecodeCCE(input); + prev = null; + break; + case Constants.ELEMENT_DSE: + //LOGGER.finest("DSE"); + DecodeDSE(input); + prev = null; + break; + case Constants.ELEMENT_PCE: + //LOGGER.finest("PCE"); + DecodePCE(input); + prev = null; + break; + case Constants.ELEMENT_FIL: + //LOGGER.finest("FIL"); + DecodeFIL(input, prev); + prev = null; + break; + } + } + //LOGGER.finest("END"); + content = false; + prev = null; + } + else + { + //error resilient raw data block + switch (config.GetChannelConfiguration()) + { + case ChannelConfiguration.CHANNEL_CONFIG_MONO: + DecodeSCE_LFE(input); + break; + case ChannelConfiguration.CHANNEL_CONFIG_STEREO: + DecodeCPE(input); + break; + case ChannelConfiguration.CHANNEL_CONFIG_STEREO_PLUS_CENTER: + DecodeSCE_LFE(input); + DecodeCPE(input); + break; + case ChannelConfiguration.CHANNEL_CONFIG_STEREO_PLUS_CENTER_PLUS_REAR_MONO: + DecodeSCE_LFE(input); + DecodeCPE(input); + DecodeSCE_LFE(input); + break; + case ChannelConfiguration.CHANNEL_CONFIG_FIVE: + DecodeSCE_LFE(input); + DecodeCPE(input); + DecodeCPE(input); + break; + case ChannelConfiguration.CHANNEL_CONFIG_FIVE_PLUS_ONE: + DecodeSCE_LFE(input); + DecodeCPE(input); + DecodeCPE(input); + DecodeSCE_LFE(input); + break; + case ChannelConfiguration.CHANNEL_CONFIG_SEVEN_PLUS_ONE: + DecodeSCE_LFE(input); + DecodeCPE(input); + DecodeCPE(input); + DecodeCPE(input); + DecodeSCE_LFE(input); + break; + default: + throw new AACException("unsupported channel configuration for error resilience: " + config.GetChannelConfiguration()); + } + } + input.ByteAlign(); + + bitsRead = input.GetPosition() - start; + } + + private Element DecodeSCE_LFE(BitStream input) + { + if (elements[curElem] == null) elements[curElem] = new SCE_LFE(config); + ((SCE_LFE)elements[curElem]).Decode(input, config); + curElem++; + return elements[curElem - 1]; + } + + private Element DecodeCPE(BitStream input) + { + if (elements[curElem] == null) elements[curElem] = new CPE(config); + ((CPE)elements[curElem]).Decode(input, config); + curElem++; + return elements[curElem - 1]; + } + + private void DecodeCCE(BitStream input) + { + if (curCCE == Constants.MAX_ELEMENTS) throw new AACException("too much CCE elements"); + if (cces[curCCE] == null) cces[curCCE] = new CCE(config); + cces[curCCE].Decode(input, config); + curCCE++; + } + + private void DecodeDSE(BitStream input) + { + if (curDSE == Constants.MAX_ELEMENTS) throw new AACException("too much CCE elements"); + if (dses[curDSE] == null) dses[curDSE] = new DSE(); + dses[curDSE].Decode(input); + curDSE++; + } + + private void DecodePCE(BitStream input) + { + pce.Decode(input); + config.SetProfile(pce.GetProfile()); + config.SetSampleFrequency(pce.GetSampleFrequency()); + config.SetChannelConfiguration((ChannelConfiguration)pce.GetChannelCount()); + } + + private void DecodeFIL(BitStream input, Element prev) + { + if (curFIL == Constants.MAX_ELEMENTS) throw new AACException("too much FIL elements"); + if (fils[curFIL] == null) fils[curFIL] = new FIL(config.IsSBRDownSampled()); + fils[curFIL].Decode(input, prev, config.GetSampleFrequency(), config.IsSBREnabled(), config.IsSmallFrameUsed()); + curFIL++; + + if (prev != null && prev.IsSBRPresent()) + { + sbrPresent = true; + if (!psPresent && prev.GetSBR().IsPSUsed()) psPresent = true; + } + } + + public void Process(FilterBank filterBank) + { + Profile profile = config.GetProfile(); + SampleFrequency sf = config.GetSampleFrequency(); + //final ChannelConfiguration channels = config.getChannelConfiguration(); + + int chs = (int)config.GetChannelConfiguration(); + if (chs == 1 && psPresent) chs++; + int mult = sbrPresent ? 2 : 1; + //only reallocate if needed + if (data == null || chs != data.Length || mult * config.GetFrameLength() != data[0].Length) + { + data = new float[chs][]; + + for (int i = 0; i < chs; i++) + { + data[i] = new float[mult * config.GetFrameLength()]; + } + } + + int channel = 0; + Element e; + SCE_LFE scelfe; + CPE cpe; + for (int i = 0; i < elements.Length && channel < chs; i++) + { + e = elements[i]; + if (e == null) continue; + if (e is SCE_LFE) + { + scelfe = (SCE_LFE)e; + channel += ProcessSingle(scelfe, filterBank, channel, profile, sf); + } + else if (e is CPE) + { + cpe = (CPE)e; + ProcessPair(cpe, filterBank, channel, profile, sf); + channel += 2; + } + else if (e is CCE) + { + //applies invquant and save the result in the CCE + ((CCE)e).Process(); + channel++; + } + } + } + + private int ProcessSingle(SCE_LFE scelfe, FilterBank filterBank, int channel, Profile profile, SampleFrequency sf) + { + ICStream ics = scelfe.GetICStream(); + ICSInfo info = ics.GetInfo(); + LTPrediction ltp = info.GetLTPrediction(); + int elementID = scelfe.GetElementInstanceTag(); + + //inverse quantization + float[] iqData = ics.GetInvQuantData(); + + //prediction + if (profile.Equals(Profile.AAC_MAIN) && info.IsICPredictionPresent()) info.GetICPrediction().Process(ics, iqData, sf); + if (ltp != null) ltp.Process(ics, iqData, filterBank, sf); + + //dependent coupling + processDependentCoupling(false, elementID, CCE.BEFORE_TNS, iqData, null); + + //TNS + if (ics.IsTNSDataPresent()) ics.GetTNS().Process(ics, iqData, sf, false); + + //dependent coupling + processDependentCoupling(false, elementID, CCE.AFTER_TNS, iqData, null); + + //filterbank + filterBank.Process(info.GetWindowSequence(), info.GetWindowShape(ICSInfo.CURRENT), info.GetWindowShape(ICSInfo.PREVIOUS), iqData, data[channel], channel); + + if (ltp != null) ltp.UpdateState(data[channel], filterBank.GetOverlap(channel), profile); + + //dependent coupling + ProcessIndependentCoupling(false, elementID, data[channel], null); + + //gain control + if (ics.IsGainControlPresent()) ics.GetGainControl().Process(iqData, info.GetWindowShape(ICSInfo.CURRENT), info.GetWindowShape(ICSInfo.PREVIOUS), info.GetWindowSequence()); + + //SBR + int chs = 1; + if (sbrPresent && config.IsSBREnabled()) + { + //if(data[channel].Length==config.getFrameLength()) LOGGER.log(Level.WARNING, "SBR data present, but buffer has normal size!"); + SBR sbr = scelfe.GetSBR(); + if (sbr.IsPSUsed()) + { + chs = 2; + scelfe.GetSBR().ProcessPS(data[channel], data[channel + 1], false); + } + else + scelfe.GetSBR().Process(data[channel], false); + } + return chs; + } + + private void ProcessPair(CPE cpe, FilterBank filterBank, int channel, Profile profile, SampleFrequency sf) + { + ICStream ics1 = cpe.GetLeftChannel(); + ICStream ics2 = cpe.GetRightChannel(); + ICSInfo info1 = ics1.GetInfo(); + ICSInfo info2 = ics2.GetInfo(); + LTPrediction ltp1 = info1.GetLTPrediction(); + LTPrediction ltp2 = info2.GetLTPrediction(); + int elementID = cpe.GetElementInstanceTag(); + + //inverse quantization + float[] iqData1 = ics1.GetInvQuantData(); + float[] iqData2 = ics2.GetInvQuantData(); + + //MS + if (cpe.IsCommonWindow() && cpe.IsMSMaskPresent()) SharpJaad.AAC.Tools.MS.Process(cpe, iqData1, iqData2); + //main prediction + if (profile.Equals(Profile.AAC_MAIN)) + { + if (info1.IsICPredictionPresent()) info1.GetICPrediction().Process(ics1, iqData1, sf); + if (info2.IsICPredictionPresent()) info2.GetICPrediction().Process(ics2, iqData2, sf); + } + //IS + IS.Process(cpe, iqData1, iqData2); + + //LTP + if (ltp1 != null) ltp1.Process(ics1, iqData1, filterBank, sf); + if (ltp2 != null) ltp2.Process(ics2, iqData2, filterBank, sf); + + //dependent coupling + processDependentCoupling(true, elementID, CCE.BEFORE_TNS, iqData1, iqData2); + + //TNS + if (ics1.IsTNSDataPresent()) ics1.GetTNS().Process(ics1, iqData1, sf, false); + if (ics2.IsTNSDataPresent()) ics2.GetTNS().Process(ics2, iqData2, sf, false); + + //dependent coupling + processDependentCoupling(true, elementID, CCE.AFTER_TNS, iqData1, iqData2); + + //filterbank + filterBank.Process(info1.GetWindowSequence(), info1.GetWindowShape(ICSInfo.CURRENT), info1.GetWindowShape(ICSInfo.PREVIOUS), iqData1, data[channel], channel); + filterBank.Process(info2.GetWindowSequence(), info2.GetWindowShape(ICSInfo.CURRENT), info2.GetWindowShape(ICSInfo.PREVIOUS), iqData2, data[channel + 1], channel + 1); + + if (ltp1 != null) ltp1.UpdateState(data[channel], filterBank.GetOverlap(channel), profile); + if (ltp2 != null) ltp2.UpdateState(data[channel + 1], filterBank.GetOverlap(channel + 1), profile); + + //independent coupling + ProcessIndependentCoupling(true, elementID, data[channel], data[channel + 1]); + + //gain control + if (ics1.IsGainControlPresent()) ics1.GetGainControl().Process(iqData1, info1.GetWindowShape(ICSInfo.CURRENT), info1.GetWindowShape(ICSInfo.PREVIOUS), info1.GetWindowSequence()); + if (ics2.IsGainControlPresent()) ics2.GetGainControl().Process(iqData2, info2.GetWindowShape(ICSInfo.CURRENT), info2.GetWindowShape(ICSInfo.PREVIOUS), info2.GetWindowSequence()); + + //SBR + if (sbrPresent && config.IsSBREnabled()) + { + //if(data[channel].Length==config.getFrameLength()) LOGGER.log(Level.WARNING, "SBR data present, but buffer has normal size!"); + cpe.GetSBR().Process(data[channel], data[channel + 1], false); + } + } + + private void ProcessIndependentCoupling(bool channelPair, int elementID, float[] data1, float[] data2) + { + int index, c, chSelect; + CCE cce; + for (int i = 0; i < cces.Length; i++) + { + cce = cces[i]; + index = 0; + if (cce != null && cce.GetCouplingPoint() == CCE.AFTER_IMDCT) + { + for (c = 0; c <= cce.GetCoupledCount(); c++) + { + chSelect = cce.GetCHSelect(c); + if (cce.IsChannelPair(c) == channelPair && cce.GetIDSelect(c) == elementID) + { + if (chSelect != 1) + { + cce.ApplyIndependentCoupling(index, data1); + if (chSelect != 0) index++; + } + if (chSelect != 2) + { + cce.ApplyIndependentCoupling(index, data2); + index++; + } + } + else index += 1 + (chSelect == 3 ? 1 : 0); + } + } + } + } + + private void processDependentCoupling(bool channelPair, int elementID, int couplingPoint, float[] data1, float[] data2) + { + int index, c, chSelect; + CCE cce; + for (int i = 0; i < cces.Length; i++) + { + cce = cces[i]; + index = 0; + if (cce != null && cce.GetCouplingPoint() == couplingPoint) + { + for (c = 0; c <= cce.GetCoupledCount(); c++) + { + chSelect = cce.GetCHSelect(c); + if (cce.IsChannelPair(c) == channelPair && cce.GetIDSelect(c) == elementID) + { + if (chSelect != 1) + { + cce.ApplyDependentCoupling(index, data1); + if (chSelect != 0) index++; + } + if (chSelect != 2) + { + cce.ApplyDependentCoupling(index, data2); + index++; + } + } + else + index += 1 + (chSelect == 3 ? 1 : 0); + } + } + } + } + + public void SendToOutput(SampleBuffer buffer) + { + bool be = buffer.BigEndian; + + // always allocate at least two channels + // mono can't be upgraded after implicit PS occures + int chs = Math.Max(data.Length, 2); + + int mult = sbrPresent && config.IsSBREnabled() ? 2 : 1; + int length = mult * config.GetFrameLength(); + int freq = mult * config.GetSampleFrequency().GetFrequency(); + + byte[] b = buffer.Data; + if (b.Length != chs * length * 2) b = new byte[chs * length * 2]; + + float[] cur; + int i, j, off; + short s; + for (i = 0; i < chs; i++) + { + // duplicate possible mono channel + cur = data[i < data.Length ? i : 0]; + for (j = 0; j < length; j++) + { + s = (short)Math.Max(Math.Min(Math.Round(cur[j]), short.MaxValue), short.MinValue); + off = (j * chs + i) * 2; + if (be) + { + b[off] = (byte)(s >> 8 & Constants.BYTE_MASK); + b[off + 1] = (byte)(s & Constants.BYTE_MASK); + } + else + { + b[off + 1] = (byte)(s >> 8 & Constants.BYTE_MASK); + b[off] = (byte)(s & Constants.BYTE_MASK); + } + } + } + + buffer.SetData(b, freq, chs, 16, bitsRead); + } + } +} diff --git a/SharpJaad.AAC/Tools/Arrays.cs b/SharpJaad.AAC/Tools/Arrays.cs new file mode 100644 index 0000000..ea9ca5f --- /dev/null +++ b/SharpJaad.AAC/Tools/Arrays.cs @@ -0,0 +1,18 @@ +namespace SharpJaad.AAC.Tools +{ + internal class Arrays + { + public static void Fill(T[] array, T value) + { + Fill(array, 0, array.Length, value); + } + + public static void Fill(T[] array, int fromIndex, int toIndex, T value) + { + for (int i = fromIndex; i < toIndex; i++) + { + array[i] = value; + } + } + } +} diff --git a/SharpJaad.AAC/Tools/ICPrediction.cs b/SharpJaad.AAC/Tools/ICPrediction.cs new file mode 100644 index 0000000..db71928 --- /dev/null +++ b/SharpJaad.AAC/Tools/ICPrediction.cs @@ -0,0 +1,162 @@ +using SharpJaad.AAC.Syntax; +using System; + +namespace SharpJaad.AAC.Tools +{ + public class ICPrediction + { + private const float SF_SCALE = 1.0f / -1024.0f; + private const float INV_SF_SCALE = 1.0f / SF_SCALE; + private const int MAX_PREDICTORS = 672; + private const float A = 0.953125f; //61.0 / 64 + private const float ALPHA = 0.90625f; //29.0 / 32 + private bool _predictorReset; + private int _predictorResetGroup; + private bool[] _predictionUsed; + private PredictorState[] _states; + + private sealed class PredictorState + { + public float _cor0 = 0.0f; + public float _cor1 = 0.0f; + public float _var0 = 0.0f; + public float _var1 = 0.0f; + public float _r0 = 1.0f; + public float _r1 = 1.0f; + } + + public ICPrediction() + { + _states = new PredictorState[MAX_PREDICTORS]; + ResetAllPredictors(); + } + + public void Decode(BitStream input, int maxSFB, SampleFrequency sf) + { + int predictorCount = sf.GetPredictorCount(); + + if (_predictorReset = input.ReadBool()) _predictorResetGroup = input.ReadBits(5); + + int maxPredSFB = sf.GetMaximalPredictionSFB(); + int length = Math.Min(maxSFB, maxPredSFB); + _predictionUsed = new bool[length]; + for (int sfb = 0; sfb < length; sfb++) + { + _predictionUsed[sfb] = input.ReadBool(); + } + //Constants.LOGGER.log(Level.WARNING, "ICPrediction: maxSFB={0}, maxPredSFB={1}", new int[]{maxSFB, maxPredSFB}); + /*//if maxSFB 1 ? cor0 * Even(A / var0) : 0; + float k2 = var1 > 1 ? cor1 * Even(A / var1) : 0; + + float pv = Round(k1 * r0 + k2 * r1); + if (output) data[off] += pv * SF_SCALE; + + float e0 = data[off] * INV_SF_SCALE; + float e1 = e0 - k1 * r0; + + state._cor1 = Trunc(ALPHA * cor1 + r1 * e1); + state._var1 = Trunc(ALPHA * var1 + 0.5f * (r1 * r1 + e1 * e1)); + state._cor0 = Trunc(ALPHA * cor0 + r0 * e0); + state._var0 = Trunc(ALPHA * var0 + 0.5f * (r0 * r0 + e0 * e0)); + + state._r1 = Trunc(A * (r0 - k1 * e0)); + state._r0 = Trunc(A * e0); + } + + private float Round(float pf) + { + return IntBitsToFloat((int)(FloatToIntBits(pf) + 0x00008000 & 0xFFFF0000)); + } + + private float Even(float pf) + { + int i = FloatToIntBits(pf); + i = (int)(i + 0x00007FFF + (i & 0x00010000 >> 16) & 0xFFFF0000); + return IntBitsToFloat(i); + } + + private float Trunc(float pf) + { + return IntBitsToFloat((int)(FloatToIntBits(pf) & 0xFFFF0000)); + } + + private static int FloatToIntBits(float f) + { + return BitConverter.ToInt32(BitConverter.GetBytes(f), 0); + } + + private static float IntBitsToFloat(int i) + { + return BitConverter.ToSingle(BitConverter.GetBytes(i), 0); + } + } +} diff --git a/SharpJaad.AAC/Tools/IS.cs b/SharpJaad.AAC/Tools/IS.cs new file mode 100644 index 0000000..90a951b --- /dev/null +++ b/SharpJaad.AAC/Tools/IS.cs @@ -0,0 +1,56 @@ +using SharpJaad.AAC.Huffman; +using SharpJaad.AAC.Syntax; + +namespace SharpJaad.AAC.Tools +{ + public class IS + { + public static void Process(CPE cpe, float[] specL, float[] specR) + { + ICStream ics = cpe.GetRightChannel(); + ICSInfo info = ics.GetInfo(); + int[] offsets = info.GetSWBOffsets(); + int windowGroups = info.GetWindowGroupCount(); + int maxSFB = info.GetMaxSFB(); + int[] sfbCB = ics.getSfbCB(); + int[] sectEnd = ics.GetSectEnd(); + float[] scaleFactors = ics.GetScaleFactors(); + + int w, i, j, c, end, off; + int idx = 0, groupOff = 0; + float scale; + for (int g = 0; g < windowGroups; g++) + { + for (i = 0; i < maxSFB;) + { + if (sfbCB[idx] == HCB.INTENSITY_HCB || sfbCB[idx] == HCB.INTENSITY_HCB2) + { + end = sectEnd[idx]; + for (; i < end; i++, idx++) + { + c = sfbCB[idx] == HCB.INTENSITY_HCB ? 1 : -1; + if (cpe.IsMSMaskPresent()) + c *= cpe.IsMSUsed(idx) ? -1 : 1; + scale = c * scaleFactors[idx]; + for (w = 0; w < info.GetWindowGroupLength(g); w++) + { + off = groupOff + w * 128 + offsets[i]; + for (j = 0; j < offsets[i + 1] - offsets[i]; j++) + { + specR[off + j] = specL[off + j] * scale; + } + } + } + } + else + { + end = sectEnd[idx]; + idx += end - i; + i = end; + } + } + groupOff += info.GetWindowGroupLength(g) * 128; + } + } + } +} diff --git a/SharpJaad.AAC/Tools/ISScaleTable.cs b/SharpJaad.AAC/Tools/ISScaleTable.cs new file mode 100644 index 0000000..d8faf3e --- /dev/null +++ b/SharpJaad.AAC/Tools/ISScaleTable.cs @@ -0,0 +1,263 @@ +namespace SharpJaad.AAC.Tools +{ + public static class ISScaleTable + { + public static float[] SCALE_TABLE = { + 1.0f, + 0.8408964152537146f, + 0.7071067811865476f, + 0.5946035575013605f, + 0.5f, + 0.4204482076268573f, + 0.35355339059327373f, + 0.29730177875068026f, + 0.25f, + 0.21022410381342865f, + 0.17677669529663687f, + 0.14865088937534013f, + 0.125f, + 0.10511205190671433f, + 0.08838834764831843f, + 0.07432544468767006f, + 0.0625f, + 0.05255602595335716f, + 0.044194173824159216f, + 0.03716272234383503f, + 0.03125f, + 0.02627801297667858f, + 0.022097086912079608f, + 0.018581361171917516f, + 0.015625f, + 0.01313900648833929f, + 0.011048543456039804f, + 0.009290680585958758f, + 0.0078125f, + 0.006569503244169645f, + 0.005524271728019902f, + 0.004645340292979379f, + 0.00390625f, + 0.0032847516220848227f, + 0.002762135864009951f, + 0.0023226701464896895f, + 0.001953125f, + 0.0016423758110424114f, + 0.0013810679320049755f, + 0.0011613350732448448f, + 9.765625E-4f, + 8.211879055212057E-4f, + 6.905339660024878E-4f, + 5.806675366224224E-4f, + 4.8828125E-4f, + 4.1059395276060284E-4f, + 3.452669830012439E-4f, + 2.903337683112112E-4f, + 2.44140625E-4f, + 2.0529697638030142E-4f, + 1.7263349150062194E-4f, + 1.451668841556056E-4f, + 1.220703125E-4f, + 1.0264848819015071E-4f, + 8.631674575031097E-5f, + 7.25834420778028E-5f, + 6.103515625E-5f, + 5.1324244095075355E-5f, + 4.3158372875155485E-5f, + 3.62917210389014E-5f, + 3.0517578125E-5f, + 2.5662122047537677E-5f, + 2.1579186437577742E-5f, + 1.81458605194507E-5f, + 1.52587890625E-5f, + 1.2831061023768839E-5f, + 1.0789593218788871E-5f, + 9.07293025972535E-6f, + 7.62939453125E-6f, + 6.415530511884419E-6f, + 5.394796609394436E-6f, + 4.536465129862675E-6f, + 3.814697265625E-6f, + 3.2077652559422097E-6f, + 2.697398304697218E-6f, + 2.2682325649313374E-6f, + 1.9073486328125E-6f, + 1.6038826279711048E-6f, + 1.348699152348609E-6f, + 1.1341162824656687E-6f, + 9.5367431640625E-7f, + 8.019413139855524E-7f, + 6.743495761743044E-7f, + 5.670581412328344E-7f, + 4.76837158203125E-7f, + 4.009706569927762E-7f, + 3.371747880871522E-7f, + 2.835290706164172E-7f, + 2.384185791015625E-7f, + 2.004853284963881E-7f, + 1.685873940435761E-7f, + 1.417645353082086E-7f, + 1.1920928955078125E-7f, + 1.0024266424819405E-7f, + 8.429369702178806E-8f, + 7.08822676541043E-8f, + 5.9604644775390625E-8f, + 5.0121332124097026E-8f, + 4.214684851089403E-8f, + 3.544113382705215E-8f, + 2.9802322387695312E-8f, + 2.5060666062048513E-8f, + 2.1073424255447014E-8f, + 1.7720566913526073E-8f, + 1.4901161193847656E-8f, + 1.2530333031024257E-8f, + 1.0536712127723507E-8f, + 8.860283456763037E-9f, + 7.450580596923828E-9f, + 6.265166515512128E-9f, + 5.2683560638617535E-9f, + 4.430141728381518E-9f, + 3.725290298461914E-9f, + 3.132583257756064E-9f, + 2.6341780319308768E-9f, + 2.215070864190759E-9f, + 1.862645149230957E-9f, + 1.566291628878032E-9f, + 1.3170890159654384E-9f, + 1.1075354320953796E-9f, + 9.313225746154785E-10f, + 7.83145814439016E-10f, + 6.585445079827192E-10f, + 5.537677160476898E-10f, + 4.6566128730773926E-10f, + 3.91572907219508E-10f, + 3.292722539913596E-10f, + 2.768838580238449E-10f, + 2.3283064365386963E-10f, + 1.95786453609754E-10f, + 1.646361269956798E-10f, + 1.3844192901192245E-10f, + 1.1641532182693481E-10f, + 9.7893226804877E-11f, + 8.23180634978399E-11f, + 6.922096450596122E-11f, + 5.820766091346741E-11f, + 4.89466134024385E-11f, + 4.115903174891995E-11f, + 3.461048225298061E-11f, + 2.9103830456733704E-11f, + 2.447330670121925E-11f, + 2.0579515874459975E-11f, + 1.7305241126490306E-11f, + 1.4551915228366852E-11f, + 1.2236653350609626E-11f, + 1.0289757937229987E-11f, + 8.652620563245153E-12f, + 7.275957614183426E-12f, + 6.118326675304813E-12f, + 5.144878968614994E-12f, + 4.3263102816225765E-12f, + 3.637978807091713E-12f, + 3.0591633376524064E-12f, + 2.572439484307497E-12f, + 2.1631551408112883E-12f, + 1.8189894035458565E-12f, + 1.5295816688262032E-12f, + 1.2862197421537484E-12f, + 1.0815775704056441E-12f, + 9.094947017729282E-13f, + 7.647908344131016E-13f, + 6.431098710768742E-13f, + 5.407887852028221E-13f, + 4.547473508864641E-13f, + 3.823954172065508E-13f, + 3.215549355384371E-13f, + 2.7039439260141103E-13f, + 2.2737367544323206E-13f, + 1.911977086032754E-13f, + 1.6077746776921855E-13f, + 1.3519719630070552E-13f, + 1.1368683772161603E-13f, + 9.55988543016377E-14f, + 8.038873388460928E-14f, + 6.759859815035276E-14f, + 5.6843418860808015E-14f, + 4.779942715081885E-14f, + 4.019436694230464E-14f, + 3.379929907517638E-14f, + 2.8421709430404007E-14f, + 2.3899713575409425E-14f, + 2.009718347115232E-14f, + 1.689964953758819E-14f, + 1.4210854715202004E-14f, + 1.1949856787704712E-14f, + 1.004859173557616E-14f, + 8.449824768794095E-15f, + 7.105427357601002E-15f, + 5.974928393852356E-15f, + 5.02429586778808E-15f, + 4.2249123843970474E-15f, + 3.552713678800501E-15f, + 2.987464196926178E-15f, + 2.51214793389404E-15f, + 2.1124561921985237E-15f, + 1.7763568394002505E-15f, + 1.493732098463089E-15f, + 1.25607396694702E-15f, + 1.0562280960992619E-15f, + 8.881784197001252E-16f, + 7.468660492315445E-16f, + 6.2803698347351E-16f, + 5.281140480496309E-16f, + 4.440892098500626E-16f, + 3.7343302461577226E-16f, + 3.14018491736755E-16f, + 2.6405702402481546E-16f, + 2.220446049250313E-16f, + 1.8671651230788613E-16f, + 1.570092458683775E-16f, + 1.3202851201240773E-16f, + 1.1102230246251565E-16f, + 9.335825615394307E-17f, + 7.850462293418875E-17f, + 6.601425600620387E-17f, + 5.551115123125783E-17f, + 4.667912807697153E-17f, + 3.925231146709437E-17f, + 3.300712800310193E-17f, + 2.7755575615628914E-17f, + 2.3339564038485766E-17f, + 1.9626155733547187E-17f, + 1.6503564001550966E-17f, + 1.3877787807814457E-17f, + 1.1669782019242883E-17f, + 9.813077866773593E-18f, + 8.251782000775483E-18f, + 6.938893903907228E-18f, + 5.834891009621442E-18f, + 4.906538933386797E-18f, + 4.1258910003877416E-18f, + 3.469446951953614E-18f, + 2.917445504810721E-18f, + 2.4532694666933983E-18f, + 2.0629455001938708E-18f, + 1.734723475976807E-18f, + 1.4587227524053604E-18f, + 1.2266347333466992E-18f, + 1.0314727500969354E-18f, + 8.673617379884035E-19f, + 7.293613762026802E-19f, + 6.133173666733496E-19f, + 5.157363750484677E-19f, + 4.3368086899420177E-19f, + 3.646806881013401E-19f, + 3.066586833366748E-19f, + 2.5786818752423385E-19f, + 2.1684043449710089E-19f, + 1.8234034405067005E-19f, + 1.533293416683374E-19f, + 1.2893409376211693E-19f, + 1.0842021724855044E-19f, + 9.117017202533503E-20f, + 7.66646708341687E-20f + }; + } +} diff --git a/SharpJaad.AAC/Tools/LTPrediction.cs b/SharpJaad.AAC/Tools/LTPrediction.cs new file mode 100644 index 0000000..7d9e283 --- /dev/null +++ b/SharpJaad.AAC/Tools/LTPrediction.cs @@ -0,0 +1,179 @@ +using SharpJaad.AAC.Filterbank; +using SharpJaad.AAC.Syntax; +using System; +using System.Linq; + +namespace SharpJaad.AAC.Tools +{ + public class LTPrediction + { + private static readonly float[] CODEBOOK = + { + 0.570829f, + 0.696616f, + 0.813004f, + 0.911304f, + 0.984900f, + 1.067894f, + 1.194601f, + 1.369533f + }; + + private bool _isPresent = false; + + private int _frameLength; + private int[] _states; + private int _coef, _lag, _lastBand; + private bool _lagUpdate; + private bool[] _shortUsed, _shortLagPresent, _longUsed; + private int[] _shortLag; + + public LTPrediction(int frameLength) + { + _frameLength = frameLength; + _states = new int[4 * frameLength]; + } + + public bool IsPresent() + { + return _isPresent; + } + + public void Decode(BitStream input, ICSInfo info, Profile profile) + { + _lag = 0; + + _isPresent = input.ReadBool(); + if (!_isPresent) + { + return; + } + + if (profile.Equals(Profile.AAC_LD)) + { + _lagUpdate = input.ReadBool(); + if (_lagUpdate) _lag = input.ReadBits(10); + } + else _lag = input.ReadBits(11); + if (_lag > _frameLength << 1) throw new AACException("LTP lag too large: " + _lag); + _coef = input.ReadBits(3); + + int windowCount = info.GetWindowCount(); + + if (info.IsEightShortFrame()) + { + _shortUsed = new bool[windowCount]; + _shortLagPresent = new bool[windowCount]; + _shortLag = new int[windowCount]; + for (int w = 0; w < windowCount; w++) + { + if (_shortUsed[w] = input.ReadBool()) + { + _shortLagPresent[w] = input.ReadBool(); + if (_shortLagPresent[w]) _shortLag[w] = input.ReadBits(4); + } + } + } + else + { + _lastBand = Math.Min(info.GetMaxSFB(), Constants.MAX_LTP_SFB); + _longUsed = new bool[_lastBand]; + + for (int i = 0; i < _lastBand; i++) + { + _longUsed[i] = input.ReadBool(); + } + } + } + + public void SetPredictionUnused(int sfb) + { + if (_longUsed != null) _longUsed[sfb] = false; + } + + public void Process(ICStream ics, float[] data, FilterBank filterBank, SampleFrequency sf) + { + if (!_isPresent) + return; + + ICSInfo info = ics.GetInfo(); + + if (!info.IsEightShortFrame()) + { + int samples = _frameLength << 1; + float[] input = new float[2048]; + float[] output = new float[2048]; + + for (int i = 0; i < samples; i++) + { + input[i] = _states[samples + i - _lag] * CODEBOOK[_coef]; + } + + filterBank.ProcessLTP(info.GetWindowSequence(), info.GetWindowShape(ICSInfo.CURRENT), + info.GetWindowShape(ICSInfo.PREVIOUS), input, output); + + if (ics.IsTNSDataPresent()) ics.GetTNS().Process(ics, output, sf, true); + + int[] swbOffsets = info.GetSWBOffsets(); + int swbOffsetMax = info.GetSWBOffsetMax(); + int low, high, bin; + for (int sfb = 0; sfb < _lastBand; sfb++) + { + if (_longUsed[sfb]) + { + low = swbOffsets[sfb]; + high = Math.Min(swbOffsets[sfb + 1], swbOffsetMax); + + for (bin = low; bin < high; bin++) + { + data[bin] += output[bin]; + } + } + } + } + } + + public void UpdateState(float[] time, float[] overlap, Profile profile) + { + int i; + if (profile.Equals(Profile.AAC_LD)) + { + for (i = 0; i < _frameLength; i++) + { + _states[i] = _states[i + _frameLength]; + _states[_frameLength + i] = _states[i + _frameLength * 2]; + _states[_frameLength * 2 + i] = (int)Math.Round(time[i]); + _states[_frameLength * 3 + i] = (int)Math.Round(overlap[i]); + } + } + else + { + for (i = 0; i < _frameLength; i++) + { + _states[i] = _states[i + _frameLength]; + _states[_frameLength + i] = (int)Math.Round(time[i]); + _states[_frameLength * 2 + i] = (int)Math.Round(overlap[i]); + } + } + _isPresent = false; + } + + public static bool IsLTPProfile(Profile profile) + { + return profile.Equals(Profile.AAC_LTP) || profile.Equals(Profile.ER_AAC_LTP) || profile.Equals(Profile.AAC_LD); + } + + public void Copy(LTPrediction ltp) + { + Array.Copy(ltp._states, 0, _states, 0, _states.Length); + _coef = ltp._coef; + _lag = ltp._lag; + _lastBand = ltp._lastBand; + _lagUpdate = ltp._lagUpdate; + _shortUsed = ltp._shortUsed.ToArray(); + _shortLagPresent = ltp._shortLagPresent.ToArray(); + _shortLag = ltp._shortLag.ToArray(); + _longUsed = ltp._longUsed.ToArray(); + } + } +} diff --git a/SharpJaad.AAC/Tools/MS.cs b/SharpJaad.AAC/Tools/MS.cs new file mode 100644 index 0000000..77bebb6 --- /dev/null +++ b/SharpJaad.AAC/Tools/MS.cs @@ -0,0 +1,42 @@ +using SharpJaad.AAC.Huffman; +using SharpJaad.AAC.Syntax; + +namespace SharpJaad.AAC.Tools +{ + public class MS + { + public static void Process(CPE cpe, float[] specL, float[] specR) + { + ICStream ics = cpe.GetLeftChannel(); + ICSInfo info = ics.GetInfo(); + int[] offsets = info.GetSWBOffsets(); + int windowGroups = info.GetWindowGroupCount(); + int maxSFB = info.GetMaxSFB(); + int[] sfbCBl = ics.getSfbCB(); + int[] sfbCBr = cpe.GetRightChannel().getSfbCB(); + int groupOff = 0; + int g, i, w, j, idx = 0; + + for (g = 0; g < windowGroups; g++) + { + for (i = 0; i < maxSFB; i++, idx++) + { + if (cpe.IsMSUsed(idx) && sfbCBl[idx] < HCB.NOISE_HCB && sfbCBr[idx] < HCB.NOISE_HCB) + { + for (w = 0; w < info.GetWindowGroupLength(g); w++) + { + int off = groupOff + w * 128 + offsets[i]; + for (j = 0; j < offsets[i + 1] - offsets[i]; j++) + { + float t = specL[off + j] - specR[off + j]; + specL[off + j] += specR[off + j]; + specR[off + j] = t; + } + } + } + } + groupOff += info.GetWindowGroupLength(g) * 128; + } + } + } +} diff --git a/SharpJaad.AAC/Tools/MSMask.cs b/SharpJaad.AAC/Tools/MSMask.cs new file mode 100644 index 0000000..52d3eb5 --- /dev/null +++ b/SharpJaad.AAC/Tools/MSMask.cs @@ -0,0 +1,10 @@ +namespace SharpJaad.AAC.Tools +{ + public enum MSMask : int + { + TYPE_ALL_0 = 0, + TYPE_USED = 1, + TYPE_ALL_1 = 2, + TYPE_RESERVED = 3 + } +} diff --git a/SharpJaad.AAC/Tools/TNS.cs b/SharpJaad.AAC/Tools/TNS.cs new file mode 100644 index 0000000..3133250 --- /dev/null +++ b/SharpJaad.AAC/Tools/TNS.cs @@ -0,0 +1,63 @@ +using SharpJaad.AAC.Syntax; + +namespace SharpJaad.AAC.Tools +{ + public class TNS + { + private static int TNS_MAX_ORDER = 20; + private static int[] SHORT_BITS = { 1, 4, 3 }, LONG_BITS = { 2, 6, 5 }; + //bitstream + private int[] _nFilt; + private int[,] _length, _order; + private bool[,] _direction; + private float[,,] _coef; + + public TNS() + { + _nFilt = new int[8]; + _length = new int[8, 4]; + _direction = new bool[8, 4]; + _order = new int[8, 4]; + _coef = new float[8, 4, TNS_MAX_ORDER]; + } + + public void Decode(BitStream input, ICSInfo info) + { + int windowCount = info.GetWindowCount(); + int[] bits = info.IsEightShortFrame() ? SHORT_BITS : LONG_BITS; + + int w, i, filt, coefLen, coefRes, coefCompress, tmp; + for (w = 0; w < windowCount; w++) + { + if ((_nFilt[w] = input.ReadBits(bits[0])) != 0) + { + coefRes = input.ReadBit(); + + for (filt = 0; filt < _nFilt[w]; filt++) + { + _length[w, filt] = input.ReadBits(bits[1]); + + if ((_order[w, filt] = input.ReadBits(bits[2])) > 20) throw new AACException("TNS filter out of range: " + _order[w, filt]); + else if (_order[w, filt] != 0) + { + _direction[w, filt] = input.ReadBool(); + coefCompress = input.ReadBit(); + coefLen = coefRes + 3 - coefCompress; + tmp = 2 * coefCompress + coefRes; + + for (i = 0; i < _order[w, filt]; i++) + { + _coef[w, filt, i] = TNSTables.TNS_TABLES[tmp][input.ReadBits(coefLen)]; + } + } + } + } + } + } + + public void Process(ICStream ics, float[] spec, SampleFrequency sf, bool decode) + { + //TODO... + } + } +} diff --git a/SharpJaad.AAC/Tools/TNSTables.cs b/SharpJaad.AAC/Tools/TNSTables.cs new file mode 100644 index 0000000..26b27dd --- /dev/null +++ b/SharpJaad.AAC/Tools/TNSTables.cs @@ -0,0 +1,31 @@ +namespace SharpJaad.AAC.Tools +{ + public static class TNSTables + { + public static float[] TNS_COEF_1_3 = + { + 0.00000000f, -0.43388373f, 0.64278758f, 0.34202015f, + }; + public static float[] TNS_COEF_0_3 = + { + 0.00000000f, -0.43388373f, -0.78183150f, -0.97492790f, + 0.98480773f, 0.86602539f, 0.64278758f, 0.34202015f, + }; + public static float[] TNS_COEF_1_4 = + { + 0.00000000f, -0.20791170f, -0.40673664f, -0.58778524f, + 0.67369562f, 0.52643216f, 0.36124167f, 0.18374951f, + }; + public static float[] TNS_COEF_0_4 = + { + 0.00000000f, -0.20791170f, -0.40673664f, -0.58778524f, + -0.74314481f, -0.86602539f, -0.95105654f, -0.99452192f, + 0.99573416f, 0.96182561f, 0.89516330f, 0.79801720f, + 0.67369562f, 0.52643216f, 0.36124167f, 0.18374951f, + }; + public static float[][] TNS_TABLES = + { + TNS_COEF_0_3, TNS_COEF_0_4, TNS_COEF_1_3, TNS_COEF_1_4 + }; + } +} diff --git a/SharpJaad.AAC/Transport/ADIFHeader.cs b/SharpJaad.AAC/Transport/ADIFHeader.cs new file mode 100644 index 0000000..5ae9804 --- /dev/null +++ b/SharpJaad.AAC/Transport/ADIFHeader.cs @@ -0,0 +1,67 @@ +using SharpJaad.AAC.Syntax; + +namespace SharpJaad.AAC.Transport +{ + public sealed class ADIFHeader + { + private const long ADIF_ID = 0x41444946; //'ADIF' + private long _id; + private bool _copyrightIDPresent; + private byte[] _copyrightID; + private bool _originalCopy, _home, _bitstreamType; + private int _bitrate; + private int _pceCount; + private int[] _adifBufferFullness; + private PCE[] _pces; + + public static bool IsPresent(BitStream input) + { + return input.PeekBits(32) == ADIF_ID; + } + + private ADIFHeader() + { + _copyrightID = new byte[9]; + } + + public static ADIFHeader ReadHeader(BitStream input) + { + ADIFHeader h = new ADIFHeader(); + h.Decode(input); + return h; + } + + private void Decode(BitStream input) + { + int i; + _id = input.ReadBits(32); //'ADIF' + _copyrightIDPresent = input.ReadBool(); + if (_copyrightIDPresent) + { + for (i = 0; i < 9; i++) + { + _copyrightID[i] = (byte)input.ReadBits(8); + } + } + _originalCopy = input.ReadBool(); + _home = input.ReadBool(); + _bitstreamType = input.ReadBool(); + _bitrate = input.ReadBits(23); + _pceCount = input.ReadBits(4) + 1; + _pces = new PCE[_pceCount]; + _adifBufferFullness = new int[_pceCount]; + for (i = 0; i < _pceCount; i++) + { + if (_bitstreamType) _adifBufferFullness[i] = -1; + else _adifBufferFullness[i] = input.ReadBits(20); + _pces[i] = new PCE(); + _pces[i].Decode(input); + } + } + + public PCE GetFirstPCE() + { + return _pces[0]; + } + } +} diff --git a/UI/Controls/TrackListControl.axaml.cs b/UI/Controls/TrackListControl.axaml.cs index a9de64d..80eb16a 100644 --- a/UI/Controls/TrackListControl.axaml.cs +++ b/UI/Controls/TrackListControl.axaml.cs @@ -169,13 +169,11 @@ public bool IsQueueContext static o => o.SearchingText, static (o, v) => o.SearchingText = v); - private string _searchingText = "Searching..."; - public string SearchingText { - get => _searchingText; - private set => SetAndRaise(SearchingTextProperty, ref _searchingText, value); - } + get; + private set => SetAndRaise(SearchingTextProperty, ref field, value); + } = "Searching..."; public static readonly DirectProperty LoadingMoreTextProperty = AvaloniaProperty.RegisterDirect( @@ -183,13 +181,11 @@ public string SearchingText static o => o.LoadingMoreText, static (o, v) => o.LoadingMoreText = v); - private string _loadingMoreText = "Searching for more"; - public string LoadingMoreText { - get => _loadingMoreText; - private set => SetAndRaise(LoadingMoreTextProperty, ref _loadingMoreText, value); - } + get; + private set => SetAndRaise(LoadingMoreTextProperty, ref field, value); + } = "Searching for more"; public static readonly DirectProperty EndOfListTextProperty = AvaloniaProperty.RegisterDirect( @@ -197,26 +193,22 @@ public string LoadingMoreText static o => o.EndOfListText, static (o, v) => o.EndOfListText = v); - private string _endOfListText = "End of list"; - public string EndOfListText { - get => _endOfListText; - private set => SetAndRaise(EndOfListTextProperty, ref _endOfListText, value); - } + get; + private set => SetAndRaise(EndOfListTextProperty, ref field, value); + } = "End of list"; public static readonly DirectProperty ScrollVisibilityProperty = AvaloniaProperty.RegisterDirect( nameof(ScrollVisibility), static o => o.ScrollVisibility); - private ScrollBarVisibility _scrollVisibility = ScrollBarVisibility.Disabled; - public ScrollBarVisibility ScrollVisibility { - get => _scrollVisibility; - private set => SetAndRaise(ScrollVisibilityProperty, ref _scrollVisibility, value); - } + get; + private set => SetAndRaise(ScrollVisibilityProperty, ref field, value); + } = ScrollBarVisibility.Disabled; #endregion @@ -568,16 +560,14 @@ private void CleanupDragStyles() private sealed class DragDataTransferItem : IDataTransferItem { - private readonly IReadOnlyList _formats; - public int TrackIndex { get; } public DragDataTransferItem(int trackIndex) { TrackIndex = trackIndex; - _formats = [TrackIndexDataFormat]; + Formats = [TrackIndexDataFormat]; } - public IReadOnlyList Formats => _formats; + public IReadOnlyList Formats { get; } public object? TryGetRaw(DataFormat format) { @@ -591,13 +581,11 @@ public DragDataTransferItem(int trackIndex) private sealed class DragDataTransfer(int trackIndex) : IDataTransfer { - private readonly IReadOnlyList _formats = [TrackIndexDataFormat]; - private readonly IReadOnlyList _items = [new DragDataTransferItem(trackIndex)]; private bool _disposed; public int TrackIndex { get; } = trackIndex; - public IReadOnlyList Formats => _formats; - public IReadOnlyList Items => _items; + public IReadOnlyList Formats { get; } = [TrackIndexDataFormat]; + public IReadOnlyList Items { get; } = [new DragDataTransferItem(trackIndex)]; public void Dispose() { From b13367cbbd9dc343e20d6da374da349d78b31d95 Mon Sep 17 00:00:00 2001 From: Scream034 Date: Wed, 18 Feb 2026 04:35:16 +0500 Subject: [PATCH 13/20] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA=20=D0=B0=D1=83?= =?UTF-8?q?=D0=B4=D0=B8=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Localization/en.json | 41 +++- Assets/Localization/ru.json | 36 +++ Core/Audio/AudioPlayer.cs | 34 +-- Core/Audio/AudioStreamInfo.cs | 3 +- Core/Models/AppSettings.cs | 66 +++++ Core/Services/AudioEngine.cs | 229 ++++++++++++++--- Features/Player/PlayerBarView.axaml | 39 ++- Features/Player/PlayerBarView.axaml.cs | 260 +++++++++++++------- Features/Player/PlayerBarViewModel.cs | 215 ++++++---------- Features/Settings/SettingsView.axaml | 155 +++++++++++- Features/Settings/SettingsViewModel.cs | 327 ++++++++++++++++++------- 11 files changed, 1029 insertions(+), 376 deletions(-) diff --git a/Assets/Localization/en.json b/Assets/Localization/en.json index 142724c..845057d 100644 --- a/Assets/Localization/en.json +++ b/Assets/Localization/en.json @@ -68,6 +68,45 @@ "Search_NoLocalFiles": "No local files in library. Download some tracks or add local files.", "Search_OfflineMode": "Searching local library only", + "VolumeCurve_Linear": "Linear", + "VolumeCurve_Quadratic": "Quadratic (Recommended)", + "VolumeCurve_Logarithmic": "Logarithmic", + "VolumeCurve_Cubic": "Cubic", + "VolumeCurve_SpeedOfLight": "Speed of Light ⚡", + + "Settings_VolumeCurve": "Volume Curve", + "Settings_VolumeCurve_Desc": "How volume slider maps to actual loudness", + + "Settings_VolumeBoost": "Volume Boost", + "Settings_VolumeBoost_Tooltip": "Allow volume above 100%. When disabled, higher max volume only increases precision for fine-tuning.", + + "Settings_SmoothVolume": "Smooth Volume Changes", + "Settings_SmoothVolume_Tooltip": "Fade volume instead of instant changes. Prevents audio pops and clicks.", + + "Settings_AudioNormalization": "Audio Normalization", + "Settings_AudioNormalization_Tooltip": "Automatically balance volume levels between different tracks. May affect dynamics.", + + "Audio_VolumeSection": "🔊 Volume", + "Audio_ProcessingSection": "⚙️ Processing", + "Audio_QualitySection": "🎵 Quality", + + "Settings_RememberFormat": "Remember Track Format", + "Settings_RememberFormatDesc": "Save preferred audio format per track", + + "Settings_AudioQualityDesc": "Preferred streaming quality when available", + + "Settings_SmoothLoading": "Smooth Loading Animations", + "Settings_SmoothLoadingDesc": "Show smooth skeleton loaders instead of instant content", + + "General_AutoPasteDesc": "Automatically play when YouTube URL is pasted", + "General_DiscordDesc": "Show currently playing track in Discord status", + + "Settings_SearchCache": "Cache Search Results", + "Settings_SearchCacheDesc": "Store search results for faster repeated searches", + "Settings_SearchCacheTtl": "Cache Duration", + + "Settings_Playback": "Playback", + "Settings_Title": "Settings", "Settings_Account_Language": "Account & Language", "Settings_Network": "Network & Streaming", @@ -99,8 +138,6 @@ "Settings_Audio": "Audio", "Settings_AudioQuality": "Quality", - "Settings_AudioQualityDesc": "Preferred audio format/bitrate.", - "Settings_RememberFormat": "Remember format per track", "Settings_SearchBatchSize": "Search batch size", "Settings_SearchBatchSizeDesc": "Number of tracks to load when scrolling in search results", "Settings_UserAgentDesc": "Insert the User-Agent from your browser to prevent cookie expiration.", diff --git a/Assets/Localization/ru.json b/Assets/Localization/ru.json index f45dd46..e4fa37b 100644 --- a/Assets/Localization/ru.json +++ b/Assets/Localization/ru.json @@ -68,6 +68,42 @@ "Search_NoLocalFiles": "В библиотеке нет локальных файлов. Скачайте треки или добавьте локальные файлы.", "Search_OfflineMode": "Поиск только по локальной библиотеке", + "VolumeCurve_Linear": "Линейная", + "VolumeCurve_Quadratic": "Квадратичная (Рекомендуется)", + "VolumeCurve_Logarithmic": "Логарифмическая", + "VolumeCurve_Cubic": "Кубическая", + "VolumeCurve_SpeedOfLight": "Скорость света ⚡", + + "Settings_VolumeCurve": "Кривая громкости", + "Settings_VolumeCurve_Desc": "Как слайдер громкости соотносится с реальной громкостью", + + "Settings_VolumeBoost": "Усиление громкости", + "Settings_VolumeBoost_Tooltip": "Разрешить громкость выше 100%. Если выключено, больший максимум только увеличивает точность настройки.", + + "Settings_SmoothVolume": "Плавное изменение громкости", + "Settings_SmoothVolume_Tooltip": "Плавный переход вместо мгновенного изменения. Предотвращает щелчки и артефакты.", + + "Settings_AudioNormalization": "Нормализация звука", + "Settings_AudioNormalization_Tooltip": "Автоматически выравнивать громкость между треками. Может влиять на динамику.", + + "Audio_VolumeSection": "🔊 Громкость", + "Audio_ProcessingSection": "⚙️ Обработка", + "Audio_QualitySection": "🎵 Качество", + + "Settings_RememberFormatDesc": "Сохранять предпочитаемый формат для каждого трека", + + "Settings_SmoothLoading": "Плавные анимации загрузки", + "Settings_SmoothLoadingDesc": "Показывать плавные скелетоны вместо мгновенного появления", + + "General_AutoPasteDesc": "Автоматически воспроизводить при вставке YouTube ссылки", + "General_DiscordDesc": "Показывать текущий трек в статусе Discord", + + "Settings_SearchCache": "Кэшировать результаты поиска", + "Settings_SearchCacheDesc": "Хранить результаты для быстрого повторного поиска", + "Settings_SearchCacheTtl": "Время хранения", + + "Settings_Playback": "Воспроизведение", + "Settings_Title": "Настройки", "Settings_Account_Language": "Аккаунт и язык", "Settings_Network": "Сеть и стриминг", diff --git a/Core/Audio/AudioPlayer.cs b/Core/Audio/AudioPlayer.cs index e53f7f8..6e65d40 100644 --- a/Core/Audio/AudioPlayer.cs +++ b/Core/Audio/AudioPlayer.cs @@ -62,12 +62,17 @@ public sealed class AudioPlayer : IAsyncDisposable, IDisposable public AudioPlayerEvents Events => _events; public AudioStreamInfo StreamInfo => _currentStreamInfo; + /// + /// Gain множитель. 0..1 = нормальный диапазон, >1 = boost. + /// NAudio backend получает min(volume, 1), SIMD boost применяется в AudioCallback. + /// public float Volume { get => _volume; set { - _volume = Math.Clamp(value, 0f, 2f); + // Разрешаем до 4.0 для boost (MaxGain в AudioEngine) + _volume = Math.Clamp(value, 0f, 4f); if (_backend != null) _backend.Volume = Math.Min(_volume, 1f); } @@ -131,13 +136,6 @@ public AudioPlayer(AudioPlayerOptions? options = null) #region Public Methods - /// - /// Начинает воспроизведение. - /// - /// URL потока. - /// ID трека. - /// Подсказка битрейта (kbps). 0 = автоопределение. - /// Токен отмены. public async Task PlayAsync(string url, string? trackId = null, int bitrateHint = 0, CancellationToken ct = default) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -363,7 +361,6 @@ private AudioStreamInfo BuildStreamInfo(IAudioSource source, string? trackId) } } - // bitrateHint имеет приоритет if (_currentBitrateHint > 0) bitrate = _currentBitrateHint; @@ -594,8 +591,10 @@ private int AudioCallback(Span buffer) { Interlocked.Add(ref _playedSamples, read); + // Применяем boost если gain > 1.0 + // NAudio backend уже применил gain до 1.0 через свой Volume if (_volume > 1.0f) - ApplyVolumeSimd(buffer[..read], _volume); + ApplyBoostSimd(buffer[..read], _volume); } if (read < buffer.Length) @@ -604,27 +603,32 @@ private int AudioCallback(Span buffer) return read / (_decoder?.Channels ?? 2); } - private static void ApplyVolumeSimd(Span data, float volume) + /// + /// Применяет boost усиление через SIMD. + /// Вызывается только при gain > 1.0 (boost режим). + /// Soft clipping для защиты от жёсткого клиппинга. + /// + private static void ApplyBoostSimd(Span data, float gain) { - if (MathF.Abs(volume - 1.0f) < 0.001f) return; + if (MathF.Abs(gain - 1.0f) < 0.001f) return; int i = 0; if (Vector.IsHardwareAccelerated) { - var vecVol = new Vector(volume); + var vecGain = new Vector(gain); var vecMin = new Vector(-1.0f); var vecMax = new Vector(1.0f); var vectors = MemoryMarshal.Cast>(data); for (int j = 0; j < vectors.Length; j++) - vectors[j] = Vector.Min(Vector.Max(vectors[j] * vecVol, vecMin), vecMax); + vectors[j] = Vector.Min(Vector.Max(vectors[j] * vecGain, vecMin), vecMax); i = vectors.Length * Vector.Count; } for (; i < data.Length; i++) - data[i] = Math.Clamp(data[i] * volume, -1f, 1f); + data[i] = Math.Clamp(data[i] * gain, -1f, 1f); } #endregion diff --git a/Core/Audio/AudioStreamInfo.cs b/Core/Audio/AudioStreamInfo.cs index 507a505..f2b3d4d 100644 --- a/Core/Audio/AudioStreamInfo.cs +++ b/Core/Audio/AudioStreamInfo.cs @@ -1,4 +1,3 @@ -// Core/Audio/AudioStreamInfo.cs namespace LMP.Core.Audio; /// @@ -21,6 +20,6 @@ public sealed record AudioStreamInfo public bool IsValid => !string.IsNullOrEmpty(Codec) && Bitrate > 0; public string FormatDisplay => IsValid - ? $"{Container}/{Codec}/{Bitrate}kbps" + ? $"{Container}/{Codec}/{Bitrate:F0}kbps" : "Loading..."; } \ No newline at end of file diff --git a/Core/Models/AppSettings.cs b/Core/Models/AppSettings.cs index 3b7a804..12a777d 100644 --- a/Core/Models/AppSettings.cs +++ b/Core/Models/AppSettings.cs @@ -16,6 +16,30 @@ public enum InternetProfile Ultra // Максимальное кэширование / Локальная сеть } +/// +/// Тип кривой интерполяции громкости. +/// +public enum VolumeCurveType +{ + /// Линейная: volume = t + Linear, + + /// Квадратичная: volume = t² (по умолчанию, перцептивно линейная) + Quadratic, + + /// Логарифмическая: volume = log2(1 + t) / log2(2) + Logarithmic, + + /// Кубическая: volume = t³ + Cubic, + + /// + /// "Скорость света": экспоненциальный рост в конце. + /// Формула: volume = (e^(t*2) - 1) / (e² - 1) + /// + SpeedOfLight +} + public sealed class ProxySettings { public bool Enabled { get; set; } = false; @@ -56,6 +80,43 @@ public sealed class StorageSettings public bool AutoSaveToDownloads { get; set; } = false; } +/// +/// Настройки аудио системы. +/// +public sealed class AudioSettings +{ + /// + /// Включить boost громкости выше 100%. + /// Если false — MaxVolume просто увеличивает точность (больше шагов). + /// + public bool VolumeBoostEnabled { get; set; } = true; + + /// + /// Кривая интерполяции громкости. + /// + public VolumeCurveType VolumeCurve { get; set; } = VolumeCurveType.Quadratic; + + /// + /// Плавное изменение громкости (fade при изменении). + /// + public bool SmoothVolumeEnabled { get; set; } = false; + + /// + /// Скорость плавного изменения громкости (мс на полный переход). + /// + public int SmoothVolumeDurationMs { get; set; } = 150; + + /// + /// Нормализация громкости (выравнивание уровней между треками). + /// + public bool NormalizationEnabled { get; set; } = false; + + /// + /// Целевой уровень нормализации в LUFS. + /// + public float NormalizationTargetLufs { get; set; } = -14f; +} + public enum RepeatMode { None, @@ -84,6 +145,11 @@ public sealed class AppSettings public AudioQualityPreference QualityPreference { get; set; } = AudioQualityPreference.BestAvailable; public bool RememberTrackFormat { get; set; } = true; + /// + /// Расширенные настройки аудио. + /// + public AudioSettings Audio { get; set; } = new(); + // === Network === public InternetProfile InternetProfile { get; set; } = InternetProfile.Medium; // Добавляем выбор клиента (по умолчанию VR, так как он сейчас работает) diff --git a/Core/Services/AudioEngine.cs b/Core/Services/AudioEngine.cs index 5c1085f..0c3185d 100644 --- a/Core/Services/AudioEngine.cs +++ b/Core/Services/AudioEngine.cs @@ -15,6 +15,22 @@ public sealed class AudioEngine : ViewModelBase, IDisposable private const int MaxConsecutiveErrors = 3; private const int MaxHistorySize = 100; + /// + /// Базовый диапазон громкости в единицах VM. + /// Используется только при VolumeBoostEnabled = true. + /// + private const int VolumeNormalRange = 200; + + /// + /// Максимальный gain (аппаратное ограничение). + /// + private const float MaxGain = 4.0f; + + /// + /// Интервал обновления smooth volume (мс). + /// + private const int SmoothVolumeUpdateIntervalMs = 16; + #endregion #region Dependencies @@ -31,6 +47,7 @@ public sealed class AudioEngine : ViewModelBase, IDisposable private readonly CancellationTokenSource _lifetimeCts = new(); private readonly Lock _queueLock = new(); private readonly Lock _seekDebounce = new(); + private readonly Lock _volumeLock = new(); private volatile int _session; #endregion @@ -42,6 +59,12 @@ public sealed class AudioEngine : ViewModelBase, IDisposable private bool _volumeInitialized; private CancellationTokenSource? _lastSeekCts; + // Smooth volume + private float _currentGain; + private float _targetGain; + private CancellationTokenSource? _smoothVolumeCts; + private Task? _smoothVolumeTask; + #endregion #region Queue @@ -55,7 +78,6 @@ public sealed class AudioEngine : ViewModelBase, IDisposable #region Properties [Reactive] public TrackInfo? CurrentTrack { get; private set; } - [Reactive] public AudioStreamInfo StreamInfo { get; private set; } = AudioStreamInfo.Empty; public bool IsPlaying => _player.State == PlaybackState.Playing; @@ -125,12 +147,7 @@ public AudioEngine(YoutubeProvider youtube, LibraryService library) _player.Events.ErrorOccurred += err => RaiseOnUI(() => OnError?.Invoke(err.Message)); _player.Events.StreamInfoChanged += OnStreamInfoReceived; _player.Events.BufferStateChanged += state => - { - Log.Debug($"[AudioEngine] BufferStateChanged received: progress={state.Progress:F1}%, " + - $"ranges={state.Ranges.Count}"); - RaiseOnUI(() => OnBufferStateChanged?.Invoke(state)); - }; ShuffleEnabled = library.Settings.ShuffleEnabled; RepeatMode = library.Settings.RepeatMode; @@ -138,7 +155,7 @@ public AudioEngine(YoutubeProvider youtube, LibraryService library) var savedVolume = library.Settings.Volume; _volumePercent = savedVolume > 0 ? NormalizeVolume(savedVolume) : 60; - ApplyVolume(); + ApplyVolume(instant: true); _commandQueue = Channel.CreateBounded>(new BoundedChannelOptions(16) { @@ -160,7 +177,6 @@ private void OnStreamInfoReceived(AudioStreamInfo info) { StreamInfo = info; OnStreamInfoChanged?.Invoke(info); - Log.Debug($"[AudioEngine] Stream info: {info.FormatDisplay}"); }); } @@ -227,10 +243,30 @@ public void ShuffleQueue() public void UpdateAudioSettings() { - ApplyVolume(); + ApplyVolume(instant: false); RaiseOnUI(() => OnMaxVolumeChanged?.Invoke(_library.Settings.MaxVolumeLimit)); } + /// + /// Вызывается при изменении MaxVolumeLimit. + /// Пересчитывает текущую громкость чтобы она не превышала новый лимит. + /// + public void OnMaxVolumeLimitChanged(int newMaxVolume) + { + lock (_volumeLock) + { + // Если текущая громкость больше нового лимита — урезаем + if (_volumePercent > newMaxVolume) + { + _volumePercent = newMaxVolume; + Log.Info($"[AudioEngine] Volume clamped to new MaxVolume: {newMaxVolume}"); + } + } + + ApplyVolume(instant: true); + RaiseOnUI(() => OnMaxVolumeChanged?.Invoke(newMaxVolume)); + } + public static Task ReinitializeWithProfileAsync(InternetProfile profile) { Log.Info($"[AudioEngine] Profile switched to {profile}. (No-op in Native Engine)"); @@ -242,9 +278,6 @@ public static void NotifyAppMinimized() GC.Collect(1, GCCollectionMode.Optimized, false); } - /// - /// Переключает качество потока с сохранением позиции. - /// public async Task SwitchQualityAsync(string container, int bitrate) { if (CurrentTrack == null) return; @@ -272,7 +305,6 @@ await EnqueueCommandAsync(async () => try { await _player.StopAsync(); - track.StreamUrl = ""; var streamInfo = await _youtube.RefreshStreamUrlAsync(track, true, CancellationToken.None); @@ -283,18 +315,15 @@ await EnqueueCommandAsync(async () => return; } - // Пробрасываем bitrate hint для точного cacheKey await _player.PlayAsync(streamInfo.Value.Url, track.Id, bitrate, CancellationToken.None); - // Seek сразу после PlayAsync, не через delay if (pos.TotalSeconds > 1) { - // Ждём пока декодер начнёт работать await WaitForPlayerReadyAsync(TimeSpan.FromSeconds(2)); await _player.SeekAsync(pos); } - Log.Info($"[AudioEngine] Quality switched to {container}/{bitrate}kbps at {pos.TotalSeconds:F1}s"); + Log.Info($"[AudioEngine] Quality switched to {container}/{bitrate}kbps"); } catch (Exception ex) { @@ -304,9 +333,6 @@ await EnqueueCommandAsync(async () => }); } - /// - /// Ждёт пока плеер будет в состоянии Playing или Paused. - /// private async Task WaitForPlayerReadyAsync(TimeSpan timeout) { var sw = System.Diagnostics.Stopwatch.StartNew(); @@ -315,13 +341,11 @@ private async Task WaitForPlayerReadyAsync(TimeSpan timeout) var state = _player.State; if (state == PlaybackState.Playing || state == PlaybackState.Paused) return; - await Task.Delay(50); } } public long GetDownloadedBytes() => _player.GetDownloadedBytes(); - public IReadOnlyList<(double Start, double End)> GetBufferedRanges() => _player.GetBufferedRanges(); #endregion @@ -372,7 +396,8 @@ await EnqueueCommandAsync(async () => if (shouldPlay) { if (_player.State == PlaybackState.Paused) _player.Resume(); - else if (_player.State == PlaybackState.Stopped && CurrentTrack != null) await PlayCurrentIndexAsync(_session); + else if (_player.State == PlaybackState.Stopped && CurrentTrack != null) + await PlayCurrentIndexAsync(_session); } else _player.Pause(); }); @@ -445,7 +470,6 @@ private async Task PlayCurrentIndexAsync(int session) string? streamUrl = track.StreamUrl; int bitrateHint = track.TransientBitrate; - // Проверяем кэш var cached = AudioSourceFactory.FindAnyCachedTrack(track.Id); if (cached != null && string.IsNullOrEmpty(streamUrl)) @@ -459,7 +483,6 @@ private async Task PlayCurrentIndexAsync(int session) var streamInfo = await _youtube.RefreshStreamUrlAsync(track, false, CancellationToken.None); if (streamInfo == null) throw new Exception("Failed to resolve URL"); streamUrl = streamInfo.Value.Url; - // Используем битрейт из stream info если не задан явно if (bitrateHint <= 0) bitrateHint = streamInfo.Value.Bitrate; } @@ -562,20 +585,149 @@ private void OnPlayerTrackEnded() public void SaveVolumeNow() => _library.UpdateSettings(s => s.Volume = _volumePercent); public float GetVolume() => _volumePercent; + /// + /// Устанавливает громкость мгновенно. + /// Значение в единицах VM (0..MaxVolume). + /// public void SetVolumeInstant(float value) { - _volumePercent = Math.Clamp((int)Math.Round(value), 0, 100); - ApplyVolume(); + int maxVol = _library.Settings.MaxVolumeLimit; + if (maxVol < 100) maxVol = 100; + + lock (_volumeLock) + { + _volumePercent = Math.Clamp((int)Math.Round(value), 0, maxVol); + } + + ApplyVolume(instant: true); } - private void ApplyVolume() + /// + /// Применяет текущий _volumePercent к плееру. + /// + /// true = мгновенное изменение, false = smooth если включено + private void ApplyVolume(bool instant) { - float linearPercent = _volumePercent / 100f; - float perceivedVolume = linearPercent * linearPercent; + var settings = _library.Settings; + var audioSettings = settings.Audio; + int maxVolume = settings.MaxVolumeLimit; + if (maxVolume < 100) maxVolume = 100; - float gain = MathF.Pow(10f, Math.Clamp(_library.Settings.TargetGainDb, -20f, 20f) / 20f); + float gain; - _player.Volume = perceivedVolume * gain; + if (_volumePercent <= 0) + { + gain = 0f; + } + else if (audioSettings.VolumeBoostEnabled) + { + // Режим boost: первые VolumeNormalRange единиц = 0..1, далее boost + gain = ComputeBoostGain(_volumePercent, maxVolume, audioSettings.VolumeCurve); + } + else + { + // Режим точной настройки: 0..maxVolume → 0..1 + float t = (float)_volumePercent / maxVolume; + gain = ApplyVolumeCurve(t, audioSettings.VolumeCurve); + } + + // Применяем пользовательский gain из настроек + float targetGainDb = Math.Clamp(settings.TargetGainDb, -20f, 20f); + float settingsGain = MathF.Pow(10f, targetGainDb / 20f); + + gain *= settingsGain; + gain = Math.Clamp(gain, 0f, MaxGain); + + _targetGain = gain; + + if (instant || !audioSettings.SmoothVolumeEnabled) + { + _currentGain = gain; + _player.Volume = gain; + Log.Debug($"[AudioEngine] Volume instant: {_volumePercent} → gain={gain:F3}"); + } + else + { + StartSmoothVolumeTransition(gain, audioSettings.SmoothVolumeDurationMs); + } + } + + /// + /// Вычисляет gain в режиме boost. + /// + private static float ComputeBoostGain(int volumePercent, int maxVolume, VolumeCurveType curve) + { + if (volumePercent <= VolumeNormalRange) + { + // Нормальный диапазон: 0..200 → 0..1 + float t = volumePercent / (float)VolumeNormalRange; + return ApplyVolumeCurve(t, curve); + } + else + { + // Boost диапазон: >200 → 1.0 + линейный прирост + float boostUnits = volumePercent - VolumeNormalRange; + float boostGain = boostUnits / VolumeNormalRange; + return 1.0f + boostGain; + } + } + + /// + /// Применяет выбранную кривую к нормализованному значению t (0..1). + /// + private static float ApplyVolumeCurve(float t, VolumeCurveType curve) + { + t = Math.Clamp(t, 0f, 1f); + + return curve switch + { + VolumeCurveType.Linear => t, + VolumeCurveType.Quadratic => t * t, + VolumeCurveType.Logarithmic => MathF.Log2(1f + t), + VolumeCurveType.Cubic => t * t * t, + VolumeCurveType.SpeedOfLight => (MathF.Exp(t * 2f) - 1f) / (MathF.Exp(2f) - 1f), + _ => t * t + }; + } + + /// + /// Запускает плавный переход громкости. + /// + private void StartSmoothVolumeTransition(float targetGain, int durationMs) + { + _smoothVolumeCts?.Cancel(); + _smoothVolumeCts?.Dispose(); + _smoothVolumeCts = new CancellationTokenSource(); + + var ct = _smoothVolumeCts.Token; + var startGain = _currentGain; + var startTime = DateTime.UtcNow; + var duration = TimeSpan.FromMilliseconds(durationMs); + + _smoothVolumeTask = Task.Run(async () => + { + try + { + while (!ct.IsCancellationRequested) + { + var elapsed = DateTime.UtcNow - startTime; + var progress = (float)(elapsed.TotalMilliseconds / duration.TotalMilliseconds); + + if (progress >= 1f) + { + _currentGain = targetGain; + _player.Volume = targetGain; + break; + } + + _currentGain = startGain + (targetGain - startGain) * progress; + _player.Volume = _currentGain; + + await Task.Delay(SmoothVolumeUpdateIntervalMs, ct); + } + } + catch (OperationCanceledException) { } + }, ct); } public void InitializeVolumeFromSettings() @@ -590,7 +742,9 @@ public void InitializeVolumeFromSettings() } else if (savedVolume > 1) { - _volumePercent = Math.Clamp((int)savedVolume, 0, 100); + int maxVol = _library.Settings.MaxVolumeLimit; + if (maxVol < 100) maxVol = 100; + _volumePercent = Math.Clamp((int)savedVolume, 0, maxVol); } else { @@ -598,9 +752,9 @@ public void InitializeVolumeFromSettings() } _volumeInitialized = true; - ApplyVolume(); + ApplyVolume(instant: true); - Log.Info($"[AudioEngine] Volume initialized: {_volumePercent}%"); + Log.Info($"[AudioEngine] Volume initialized: {_volumePercent} units"); } private async Task VolumeSaveLoopAsync() @@ -613,7 +767,7 @@ private async Task VolumeSaveLoopAsync() } private static int NormalizeVolume(float saved) => - Math.Clamp((int)saved, 0, 100); + Math.Clamp((int)saved, 0, 1000); #endregion @@ -736,6 +890,9 @@ protected override void Dispose(bool disposing) _lastSeekCts?.Dispose(); } + _smoothVolumeCts?.Cancel(); + _smoothVolumeCts?.Dispose(); + _lifetimeCts.Cancel(); _player.Dispose(); } diff --git a/Features/Player/PlayerBarView.axaml b/Features/Player/PlayerBarView.axaml index 4f4b245..2414dba 100644 --- a/Features/Player/PlayerBarView.axaml +++ b/Features/Player/PlayerBarView.axaml @@ -188,7 +188,7 @@ - + + + + + + + + + + + @@ -370,6 +401,7 @@ Name="ProgressBar" HorizontalAlignment="Left" Background="{DynamicResource AccentBrush}" + Classes="progress-bar" CornerRadius="2" /> @@ -621,10 +653,9 @@ - + - + /// Code-behind для панели управления плеером. -/// Обрабатывает взаимодействие с seek/volume слайдерами и визуальные обновления. /// public partial class PlayerBarView : UserControl { @@ -21,21 +20,16 @@ public partial class PlayerBarView : UserControl private const double VolumeThumbDiameter = 12.0; private const double VolumeThumbRadius = VolumeThumbDiameter / 2.0; - private const double VolumeSliderHeightPerPercent = 0.8; private const double VolumeSliderMinHeight = 60.0; + private const double VolumeSliderMaxHeight = 200.0; - private const int VolumeScrollStep = 1; private const int VolumePopupCloseDelayMs = 400; private const int VolumeTooltipHideDelayMs = 1500; private const int SparkAnimationIntervalMs = 16; private const double SparkSpeed = 6.0; private const double SparkWidth = 80.0; - - /// - /// Минимальная ширина сегмента буфера в пикселях. - /// Нужна чтобы даже маленький чанк был виден. - /// private const double MinSegmentWidthPx = 2.0; + private const double VolumePopupContentWidth = 28.0; #endregion @@ -46,20 +40,18 @@ public partial class PlayerBarView : UserControl private bool _isVolumePopupHovered; private bool _isVolumeButtonHovered; private bool _isWindowActive = true; + private bool _isSuspended; + + /// + /// Флаг: тултип громкости активен (показывать слева от слайдера). + /// + private bool _isVolumeTooltipActive; private double _seekDragRatio; private double _sparkPosition = -SparkWidth; + private double _cachedSeekWidth; - /// - /// Пул Border'ов для сегментов буфера. - /// Используем абсолютное позиционирование внутри Grid через Margin. - /// private readonly List _bufferSegments = []; - - /// - /// Кэшированная кисть для сегментов буфера. - /// Создаётся один раз, не пересоздаётся каждый кадр. - /// private IBrush? _bufferBrushCache; private DispatcherTimer? _volumePopupCloseTimer; @@ -88,6 +80,7 @@ private void SetupEventHandlers() VolumeButton.PointerEntered += OnVolumeButtonEntered; VolumeButton.PointerExited += OnVolumeButtonExited; VolumePopup.Opened += OnVolumePopupOpened; + VolumePopup.Closed += OnVolumePopupClosed; _formatFlyout = FormatButton.Flyout; if (_formatFlyout != null) @@ -118,6 +111,7 @@ private void SetupTimers() }; _volumeTooltipHideTimer.Tick += (_, _) => { + _isVolumeTooltipActive = false; VolumeTooltipPopup.IsOpen = false; _volumeTooltipHideTimer?.Stop(); }; @@ -162,6 +156,7 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e VolumeButton.PointerEntered -= OnVolumeButtonEntered; VolumeButton.PointerExited -= OnVolumeButtonExited; VolumePopup.Opened -= OnVolumePopupOpened; + VolumePopup.Closed -= OnVolumePopupClosed; _currentViewModel?.PropertyChanged -= OnViewModelPropertyChanged; _currentViewModel = null; @@ -169,7 +164,17 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e ClearBufferSegments(); } - private void OnWindowActivated(object? sender, EventArgs e) => _isWindowActive = true; + private void OnWindowActivated(object? sender, EventArgs e) + { + _isWindowActive = true; + + if (!_isSuspended) + { + UpdateSeekVisual(); + UpdateBufferVisual(); + UpdatePlayingGlow(); + } + } private void OnWindowDeactivated(object? sender, EventArgs e) { @@ -188,6 +193,7 @@ protected override void OnDataContextChanged(EventArgs e) { _currentViewModel = vm; vm.PropertyChanged += OnViewModelPropertyChanged; + vm.RegisterView(this); InitializeVolumeSlider(vm); @@ -196,7 +202,6 @@ protected override void OnDataContextChanged(EventArgs e) else StopSparkAnimation(); - // Инициализируем буфер — ВАЖНО сделать после присвоения DataContext InvalidateBufferBrushCache(); UpdateBufferVisual(); } @@ -206,12 +211,6 @@ private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.Pr { if (sender is not PlayerBarViewModel vm) return; - // *** ДИАГНОСТИКА для буфера *** - if (e.PropertyName == nameof(PlayerBarViewModel.BufferedRanges)) - { - Log.Debug($"[PlayerBarView] PropertyChanged: BufferedRanges, count={vm.BufferedRanges.Count}"); - } - if (e.PropertyName == nameof(PlayerBarViewModel.IsLoading)) { if (vm.IsLoading) @@ -221,7 +220,16 @@ private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.Pr return; } - if (!_isWindowActive) return; + if (e.PropertyName == nameof(PlayerBarViewModel.IsTrackResetting)) + { + if (vm.IsTrackResetting) + ApplySliderReset(); + else + RemoveSliderReset(); + return; + } + + if (_isSuspended || !_isWindowActive) return; switch (e.PropertyName) { @@ -236,7 +244,6 @@ private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.Pr case nameof(PlayerBarViewModel.BufferedRanges): case nameof(PlayerBarViewModel.IsFullyBuffered): - Log.Debug($"[PlayerBarView] Calling UpdateBufferVisual, ranges={vm.BufferedRanges.Count}"); UpdateBufferVisual(); break; @@ -252,12 +259,56 @@ private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.Pr } } + private void ApplySliderReset() + { + ProgressBar.Classes.Add("hidden"); + ProgressBar.Width = 0; + SeekThumb.Classes.Add("hidden"); + SeekCursor.Classes.Add("hidden"); + HideAllBufferSegments(); + PlayingGlow.Width = 0; + } + + private void RemoveSliderReset() + { + ProgressBar.Classes.Remove("hidden"); + SeekThumb.Classes.Remove("hidden"); + SeekCursor.Classes.Remove("hidden"); + + UpdateSeekVisual(); + UpdateBufferVisual(); + UpdatePlayingGlow(); + } + + public void OnSuspend() + { + _isSuspended = true; + StopSparkAnimation(); + PlayingGlow.Classes.Add("suspended"); + CloseAllPopups(); + } + + public void OnResume() + { + _isSuspended = false; + PlayingGlow.Classes.Remove("suspended"); + + if (_currentViewModel?.IsLoading == true) + StartSparkAnimation(); + + _cachedSeekWidth = SeekContainer.Bounds.Width; + UpdateSeekVisual(); + UpdateBufferVisual(); + UpdatePlayingGlow(); + UpdateVolumeVisual(); + } + private void InitializeVolumeSlider(PlayerBarViewModel vm) { try { int maxVolume = vm.MaxVolume > 0 ? vm.MaxVolume : 100; - double height = Math.Max(VolumeSliderMinHeight, maxVolume * VolumeSliderHeightPerPercent); + double height = ComputeVolumeSliderHeight(maxVolume); VolumeSliderPanel.Height = height; int volume = Math.Clamp(vm.Volume, 0, maxVolume); @@ -266,6 +317,8 @@ private void InitializeVolumeSlider(PlayerBarViewModel vm) VolumeBar.Height = height * ratio; double thumbTop = height * (1 - ratio) - VolumeThumbRadius; VolumeThumb.Margin = new Thickness(0, Math.Max(0, thumbTop), 0, 0); + + UpdateVolumePopupOffset(); } catch (Exception ex) { @@ -282,7 +335,7 @@ private void InitializeVolumeSlider(PlayerBarViewModel vm) private void StartSparkAnimation() { - if (_sparkAnimationTimer == null) return; + if (_sparkAnimationTimer == null || _isSuspended) return; _sparkPosition = -SparkWidth; SparkRunner.Margin = new Thickness(_sparkPosition, 0, 0, 0); @@ -302,9 +355,9 @@ private void StopSparkAnimation() private void OnSparkAnimationTick(object? sender, EventArgs e) { - if (!_isWindowActive) return; + if (!_isWindowActive || _isSuspended) return; - double containerWidth = SeekContainer.Bounds.Width; + double containerWidth = _cachedSeekWidth > 0 ? _cachedSeekWidth : SeekContainer.Bounds.Width; if (containerWidth <= 0) containerWidth = 600; _sparkPosition += SparkSpeed; @@ -323,16 +376,24 @@ private void OnSeekContainerPropertyChanged(object? sender, AvaloniaPropertyChan { if (e.Property.Name == nameof(Bounds)) { - UpdateSeekVisual(); - UpdateBufferVisual(); - UpdatePlayingGlow(); + _cachedSeekWidth = SeekContainer.Bounds.Width; + + if (!_isSuspended) + { + UpdateSeekVisual(); + UpdateBufferVisual(); + UpdatePlayingGlow(); + } } } private void OnVolumeSliderPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if (e.Property.Name == nameof(Bounds) || e.Property.Name == nameof(Height)) + { UpdateVolumeVisual(); + UpdateVolumePopupOffset(); + } } private void OnVolumePopupOpened(object? sender, EventArgs e) @@ -342,27 +403,47 @@ private void OnVolumePopupOpened(object? sender, EventArgs e) if (VolumeSliderPanel.Height <= 0) UpdateVolumeSliderHeight(vm.MaxVolume); UpdateVolumeVisual(); + UpdateVolumePopupOffset(); } } + private void OnVolumePopupClosed(object? sender, EventArgs e) + { + // Скрываем тултип при закрытии popup + _isVolumeTooltipActive = false; + VolumeTooltipPopup.IsOpen = false; + } + + private void UpdateVolumePopupOffset() + { + double buttonWidth = VolumeButton.Width; + if (double.IsNaN(buttonWidth) || buttonWidth <= 0) buttonWidth = 38; + + double popupWidth = VolumePopupContentWidth + 2; + double offset = (buttonWidth - popupWidth) / 2.0; + + VolumePopup.HorizontalOffset = offset; + } + #endregion #region Buffer Segments Visual - /// - /// Обновляет визуал сегментированного буфера. - /// private void UpdateBufferVisual() { if (DataContext is not PlayerBarViewModel vm) return; - // Берём ширину из SeekContainer — он всегда имеет правильные размеры - double width = SeekContainer.Bounds.Width; + double width = _cachedSeekWidth > 0 ? _cachedSeekWidth : SeekContainer.Bounds.Width; if (width <= 0) return; + if (vm.IsTrackResetting) + { + HideAllBufferSegments(); + return; + } + var ranges = vm.BufferedRanges; - // Полностью загружено, но ranges пустые if ((ranges == null || ranges.Count == 0) && vm.IsFullyBuffered) ranges = [(0.0, 1.0)]; @@ -386,15 +467,14 @@ private void UpdateBufferVisual() start = Math.Clamp(start, 0, 1); end = Math.Clamp(end, start, 1); - double left = start * width; - double segWidth = (end - start) * width; + double left = Math.Round(start * width); + double segWidth = Math.Round((end - start) * width); if (segWidth < MinSegmentWidthPx && segWidth > 0) segWidth = MinSegmentWidthPx; if (left + segWidth > width) segWidth = width - left; - // Canvas абсолютное позиционирование Canvas.SetLeft(segment, left); Canvas.SetTop(segment, 0); segment.Width = Math.Max(0, segWidth); @@ -462,14 +542,12 @@ private IBrush GetBufferBrush() #region Seek Visual - /// - /// Обновляет визуал seek-слайдера (позиция воспроизведения). - /// private void UpdateSeekVisual() { if (DataContext is not PlayerBarViewModel vm) return; + if (vm.IsTrackResetting) return; - double width = SeekContainer.Bounds.Width; + double width = _cachedSeekWidth > 0 ? _cachedSeekWidth : SeekContainer.Bounds.Width; double duration = vm.DurationSeconds; if (width <= 0 || duration <= 0) return; @@ -481,14 +559,12 @@ private void UpdateSeekVisual() Canvas.SetLeft(SeekThumb, position - SeekThumbRadius); } - /// - /// Обновляет эффект свечения за прогресс-баром. - /// private void UpdatePlayingGlow() { if (DataContext is not PlayerBarViewModel vm) return; + if (vm.IsTrackResetting) return; - double width = SeekContainer.Bounds.Width; + double width = _cachedSeekWidth > 0 ? _cachedSeekWidth : SeekContainer.Bounds.Width; double duration = vm.DurationSeconds; if (width <= 0 || duration <= 0) return; @@ -519,16 +595,28 @@ private void UpdateSeekPreview(double x) => #region Volume Visual - private void UpdateVolumeSliderHeight(int maxVolume) + private static double ComputeVolumeSliderHeight(int maxVolume) { if (maxVolume <= 0) maxVolume = 100; - double height = Math.Max(VolumeSliderMinHeight, maxVolume * VolumeSliderHeightPerPercent); + const double baseHeight = 80.0; + double extra = maxVolume > 100 + ? 40.0 * Math.Log2(1 + (maxVolume - 100) / 200.0) + : 0; + + double height = baseHeight + extra; + return Math.Clamp(height, VolumeSliderMinHeight, VolumeSliderMaxHeight); + } + + private void UpdateVolumeSliderHeight(int maxVolume) + { + double height = ComputeVolumeSliderHeight(maxVolume); if (double.IsNaN(height) || double.IsInfinity(height)) height = VolumeSliderMinHeight; VolumeSliderPanel.Height = height; + UpdateVolumePopupOffset(); } private void UpdateVolumeVisual() @@ -540,7 +628,7 @@ private void UpdateVolumeVisual() if (height <= 0 || double.IsNaN(height)) { - height = VolumeSliderMinHeight; + height = ComputeVolumeSliderHeight(maxVolume); VolumeSliderPanel.Height = height; } @@ -570,9 +658,22 @@ private void UpdateVolumeVisualInternal(double ratio, double height) VolumeThumb.Margin = new Thickness(0, thumbTop, 0, 0); } - private void UpdateVolumeTooltip(int currentVolume, int maxVolume) + /// + /// Показывает тултип громкости слева от слайдера. + /// + private void ShowVolumeTooltip(int currentVolume, int maxVolume, double ratio) { VolumeTooltipText.Text = $"{currentVolume}% / {maxVolume}%"; + + double height = VolumeSliderPanel.Height; + double yOffset = height * (1 - ratio) - (height / 2); + VolumeTooltipPopup.VerticalOffset = yOffset; + + _isVolumeTooltipActive = true; + VolumeTooltipPopup.IsOpen = true; + + _volumeTooltipHideTimer?.Stop(); + _volumeTooltipHideTimer?.Start(); } #endregion @@ -584,6 +685,7 @@ private void CloseAllPopups() SeekTooltipPopup.IsOpen = false; VolumeTooltipPopup.IsOpen = false; VolumePopup.IsOpen = false; + _isVolumeTooltipActive = false; } private void ShowSeekPreview() => PreviewFill.Classes.Add("active"); @@ -661,7 +763,7 @@ private void OnSeekAreaMoved(object? sender, PointerEventArgs e) return; } - double width = SeekContainer.Bounds.Width; + double width = _cachedSeekWidth > 0 ? _cachedSeekWidth : SeekContainer.Bounds.Width; if (width <= 0) return; double x = Math.Clamp(point.Position.X, 0, width); @@ -712,7 +814,7 @@ private void OnSeekAreaPressed(object? sender, PointerPressedEventArgs e) vm.StartSeek(); SeekContainer.Classes.Add("dragging"); - double width = SeekContainer.Bounds.Width; + double width = _cachedSeekWidth > 0 ? _cachedSeekWidth : SeekContainer.Bounds.Width; if (width <= 0) return; double x = Math.Clamp(point.Position.X, 0, width); @@ -785,10 +887,9 @@ private void OnVolumeScroll(object? sender, PointerWheelEventArgs e) { if (DataContext is not PlayerBarViewModel vm) return; - _volumeTooltipHideTimer?.Stop(); - VolumeTooltipPopup.IsOpen = true; - - int delta = e.Delta.Y > 0 ? VolumeScrollStep : -VolumeScrollStep; + // Получаем шаг из ViewModel + int step = vm.GetVolumeScrollStep(); + int delta = e.Delta.Y > 0 ? step : -step; int newVolume = Math.Clamp(vm.Volume + delta, 0, vm.MaxVolume); if (newVolume != vm.Volume) @@ -797,16 +898,11 @@ private void OnVolumeScroll(object? sender, PointerWheelEventArgs e) vm.OnVolumeChangeComplete(); } - UpdateVolumeTooltip(newVolume, vm.MaxVolume); - - double height = VolumeSliderPanel.Height; + // Показываем тултип слева от слайдера double ratio = Math.Clamp((double)newVolume / vm.MaxVolume, 0, 1); - double yOffset = height * (1 - ratio) - (height / 2); - - VolumeTooltipPopup.VerticalOffset = yOffset; + ShowVolumeTooltip(newVolume, vm.MaxVolume, ratio); e.Handled = true; - _volumeTooltipHideTimer?.Start(); } private void OnVolumeAreaMoved(object? sender, PointerEventArgs e) @@ -829,24 +925,15 @@ private void OnVolumeAreaMoved(object? sender, PointerEventArgs e) double ratio = 1 - (y / height); int volumePercent = (int)(ratio * vm.MaxVolume); - UpdateVolumeTooltip(volumePercent, vm.MaxVolume); - - double yOffset = height * (1 - ratio) - (height / 2); - VolumeTooltipPopup.VerticalOffset = yOffset; - if (_isDraggingVolume) { UpdateVolumeVisualInternal(ratio, height); vm.Volume = volumePercent; - VolumeTooltipPopup.IsOpen = true; + ShowVolumeTooltip(volumePercent, vm.MaxVolume, ratio); } else if (hitBox.IsPointerOver) { - VolumeTooltipPopup.IsOpen = true; - } - else - { - VolumeTooltipPopup.IsOpen = false; + ShowVolumeTooltip(volumePercent, vm.MaxVolume, ratio); } } @@ -879,11 +966,7 @@ private void OnVolumeAreaPressed(object? sender, PointerPressedEventArgs e) UpdateVolumeVisualInternal(ratio, height); vm.Volume = newVolume; - UpdateVolumeTooltip(newVolume, vm.MaxVolume); - double yOffset = height * (1 - ratio) - (height / 2); - VolumeTooltipPopup.VerticalOffset = yOffset; - - VolumeTooltipPopup.IsOpen = true; + ShowVolumeTooltip(newVolume, vm.MaxVolume, ratio); } private void OnVolumeAreaReleased(object? sender, PointerReleasedEventArgs e) @@ -899,8 +982,11 @@ private void OnVolumeAreaReleased(object? sender, PointerReleasedEventArgs e) private void OnVolumeAreaExited(object? sender, PointerEventArgs e) { - if (!_isDraggingVolume) + if (!_isDraggingVolume && !_isVolumeTooltipActive) + { + _volumeTooltipHideTimer?.Stop(); VolumeTooltipPopup.IsOpen = false; + } } private void OnVolumeAreaCaptureLost(object? sender, PointerCaptureLostEventArgs e) => @@ -911,7 +997,8 @@ private void CompleteVolumeDrag(IPointer pointer) _isDraggingVolume = false; pointer.Capture(null); VolumeThumb.Classes.Remove("dragging"); - VolumeTooltipPopup.IsOpen = false; + + // Не скрываем тултип сразу — пусть таймер скроет } private void CancelVolumeDrag() @@ -922,6 +1009,7 @@ private void CancelVolumeDrag() VolumeThumb.Classes.Remove("dragging"); } + _isVolumeTooltipActive = false; VolumeTooltipPopup.IsOpen = false; UpdateVolumeVisual(); } diff --git a/Features/Player/PlayerBarViewModel.cs b/Features/Player/PlayerBarViewModel.cs index 612b04a..dd5b6aa 100644 --- a/Features/Player/PlayerBarViewModel.cs +++ b/Features/Player/PlayerBarViewModel.cs @@ -28,6 +28,7 @@ public sealed class PlayerBarViewModel : ViewModelBase private const int CopyHighlightDurationMs = 800; private const int PositionUpdateThrottleMs = 50; private const int BufferUpdateIntervalMs = 300; + private const int TrackResetVisualDelayMs = 150; #endregion @@ -57,6 +58,8 @@ public sealed class PlayerBarViewModel : ViewModelBase private int _lastVolumeBeforeMute = 50; + private WeakReference? _viewRef; + #endregion #region Properties - Playback State @@ -68,14 +71,10 @@ public sealed class PlayerBarViewModel : ViewModelBase [Reactive] public bool HasTrack { get; private set; } [Reactive] public bool IsLiked { get; private set; } [Reactive] public bool IsNavigating { get; private set; } + [Reactive] public bool IsTrackResetting { get; private set; } - /// Заголовок трека или placeholder. public string SafeTitle => CurrentTrack?.Title ?? L["Player_NotPlaying"]; - - /// Автор трека. public string SafeAuthor => CurrentTrack?.Author ?? ""; - - /// URL миниатюры. public string? SafeThumbnail => CurrentTrack?.ThumbnailUrl; #endregion @@ -86,7 +85,6 @@ public sealed class PlayerBarViewModel : ViewModelBase [Reactive] public int TotalTracksInQueue { get; private set; } [Reactive] public bool HasQueueToShuffle { get; private set; } - /// Отображаемый номер трека (1-based). public string CurrentTrackIndexDisplay => (CurrentTrackIndex + 1).ToString(); #endregion @@ -104,25 +102,9 @@ public sealed class PlayerBarViewModel : ViewModelBase #region Properties - Buffer Progress - /// - /// Прогресс буферизации в процентах (0-100). - /// [Reactive] public double BufferProgressPercent { get; private set; } - - /// - /// Закэшированные диапазоны для отрисовки сегментов. - /// Формат: список (startPercent, endPercent) от 0 до 1. - /// [Reactive] public IReadOnlyList<(double Start, double End)> BufferedRanges { get; private set; } = []; - - /// - /// Использовать сегментную визуализацию (для прерывистого кэша). - /// public bool UseSegmentedBuffer => BufferedRanges.Count > 1; - - /// - /// Трек полностью закэширован. - /// [Reactive] public bool IsFullyBuffered { get; private set; } #endregion @@ -134,22 +116,12 @@ public sealed class PlayerBarViewModel : ViewModelBase [Reactive] public bool IsVolumePopupOpen { get; set; } [Reactive] public bool IsVolumePreviewVisible { get; set; } - /// Громкость выключена. public bool IsMuted => Volume < 1; - - /// Низкая громкость (1-33%). public bool IsVolumeLow => Volume >= 1 && Volume <= 33; - - /// Средняя громкость (34-66%). public bool IsVolumeMedium => Volume > 33 && Volume <= 66; - - /// Высокая громкость (67-100%). public bool IsVolumeHigh => Volume > 66 && Volume <= 100; - - /// Усиленная громкость (>100%). public bool IsVolumeBoosted => Volume > 100; - /// Кисть для отображения процента громкости. public IBrush VolumePercentBrush { get @@ -161,17 +133,13 @@ public IBrush VolumePercentBrush { if (app.Resources.TryGetResource("SystemWarnOrangeBrush", app.ActualThemeVariant, out var warnBrush) && warnBrush is IBrush warn) - { return warn; - } return new SolidColorBrush(Color.Parse("#FFB86C")); } if (app.Resources.TryGetResource("TextPrimaryBrush", app.ActualThemeVariant, out var textBrush) && textBrush is IBrush text) - { return text; - } return Brushes.White; } } @@ -182,7 +150,6 @@ public IBrush VolumePercentBrush [Reactive] public bool ShuffleEnabled { get; private set; } [Reactive] public RepeatMode RepeatMode { get; set; } - [Reactive] public bool IsRepeatHintVisible { get; private set; } [Reactive] public string RepeatHintText { get; private set; } = ""; @@ -192,7 +159,6 @@ public IBrush VolumePercentBrush [Reactive] public bool IsLikeHintVisible { get; private set; } [Reactive] public string LikeHintText { get; private set; } = ""; - [Reactive] public bool IsCopyHintVisible { get; private set; } [Reactive] public bool IsCopyHighlighted { get; private set; } @@ -204,28 +170,20 @@ public IBrush VolumePercentBrush [Reactive] public bool ShowStreamInfo { get; private set; } [Reactive] public string DownloadSpeedText { get; private set; } = ""; - /// Доступные форматы потока. public ObservableCollection AvailableFormats { get; } = []; #endregion #region Properties - Tooltips - /// Тултип кнопки Shuffle. public string ShuffleTooltip => L.Get("Player_Shuffle", "Shuffle"); - - /// Тултип кнопки Previous. public string PreviousTooltip => L.Get("Player_Previous", "Previous"); - - /// Тултип кнопки Next. public string NextTooltip => L.Get("Player_Next", "Next"); - /// Тултип кнопки Play/Pause. public string PlayPauseTooltip => IsPlaying ? L.Get("Player_Pause", "Pause") : L.Get("Player_Play", "Play"); - /// Тултип кнопки Repeat. public string RepeatTooltip => RepeatMode switch { RepeatMode.None => L.Get("Player_Repeat_Off", "Repeat Off"), @@ -234,34 +192,27 @@ public IBrush VolumePercentBrush _ => "" }; - /// Тултип кнопки Like. public string LikeTooltip => IsLiked ? L.Get("Track_Unlike", "Remove from Liked") : L.Get("Track_Like", "Add to Liked"); - /// Тултип кнопки Copy. public string CopyTooltip => L.Get("Track_CopyLink", "Copy Link"); - /// Тултип кнопки Mute. public string MuteTooltip => IsMuted ? L.Get("Player_Unmute", "Unmute") : L.Get("Player_Mute", "Mute"); - /// Тултип номера трека: "Трек X из Y". public string TrackNumberTooltip => string.Format( L.Get("Player_TrackNumber", "Track {0} of {1}"), CurrentTrackIndex + 1, TotalTracksInQueue); - /// Тултип длительности. public string DurationTooltip { get { if (IsLoading || DurationSeconds <= 0) - { return L.Get("Player_Loading_Duration", "Loading duration..."); - } string posStr = FormatTime(Position); string durStr = FormatTime(Duration); @@ -315,7 +266,6 @@ public PlayerBarViewModel( LocalizationService.Instance.LanguageChanged += OnLanguageChanged; // ИНИЦИАЛИЗАЦИЯ ИЗ НАСТРОЕК - Observable.FromEvent( h => _library.OnInitialized += h, h => _library.OnInitialized -= h) @@ -325,7 +275,6 @@ public PlayerBarViewModel( .DisposeWith(Disposables); // AUDIO ENGINE EVENTS - Observable.FromEvent, (bool, bool)>( h => (p, u) => h((p, u)), h => _audio.OnPlaybackStateChanged += h, @@ -345,10 +294,10 @@ public PlayerBarViewModel( .Subscribe(_ => UpdateQueueState()) .DisposeWith(Disposables); - // Throttle position updates для экономии CPU Observable.FromEvent, TimeSpan>( h => _audio.OnPositionChanged += h, h => _audio.OnPositionChanged -= h) + .Where(_ => !_isSuspended) .Throttle(TimeSpan.FromMilliseconds(PositionUpdateThrottleMs)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(pos => @@ -360,9 +309,7 @@ public PlayerBarViewModel( this.RaisePropertyChanged(nameof(DurationTooltip)); } if (IsSeekBusy && !IsLoading) - { IsSeekBusy = false; - } }) .DisposeWith(Disposables); @@ -387,7 +334,6 @@ public PlayerBarViewModel( .Subscribe(UpdateStreamInfo) .DisposeWith(Disposables); - // BUFFER PROGRESS — через событие, не через интервал Observable.FromEvent, BufferState>( h => _audio.OnBufferStateChanged += h, h => _audio.OnBufferStateChanged -= h) @@ -398,7 +344,6 @@ public PlayerBarViewModel( .DisposeWith(Disposables); // CACHE & LIBRARY EVENTS - Observable.FromEvent, (string, string, int, bool)>( h => (t, c, b, d) => h((t, c, b, d)), h => _cacheManager.OnFormatCached += h, @@ -420,7 +365,6 @@ public PlayerBarViewModel( .DisposeWith(Disposables); // VOLUME BINDING - this.WhenAnyValue(x => x.Volume) .Subscribe(v => { @@ -428,9 +372,7 @@ public PlayerBarViewModel( RaiseVolumePropertiesChanged(); if (_isInitialized && v > 0) - { _library.UpdateSettings(s => s.LastVolume = v); - } }) .DisposeWith(Disposables); @@ -442,7 +384,6 @@ public PlayerBarViewModel( .Subscribe(_ => this.RaisePropertyChanged(nameof(LikeTooltip))) .DisposeWith(Disposables); - // Обновляем тултипы при изменении индекса/количества this.WhenAnyValue(x => x.CurrentTrackIndex, x => x.TotalTracksInQueue) .Subscribe(_ => this.RaisePropertyChanged(nameof(TrackNumberTooltip))) .DisposeWith(Disposables); @@ -460,7 +401,6 @@ public PlayerBarViewModel( .DisposeWith(Disposables); // TIMERS - _fallbackPositionTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) }; _fallbackPositionTimer.Tick += (_, _) => FallbackPositionUpdate(); _fallbackPositionTimer.Start(); @@ -470,7 +410,6 @@ public PlayerBarViewModel( _speedUpdateTimer.Start(); // NAVIGATION SUBJECTS - _nextSubject .Throttle(TimeSpan.FromMilliseconds(NavigationDebounceMs)) .ObserveOn(RxApp.MainThreadScheduler) @@ -492,7 +431,6 @@ public PlayerBarViewModel( .DisposeWith(Disposables); // COMMANDS - var canNavigate = this.WhenAnyValue(x => x.HasTrack, x => x.IsNavigating, x => x.IsLoading, (hasTrack, isNav, loading) => hasTrack && !isNav && !loading); @@ -547,6 +485,8 @@ public PlayerBarViewModel( if (IsMuted) { int restoreVolume = _lastVolumeBeforeMute > 0 ? _lastVolumeBeforeMute : 50; + // Ограничиваем восстанавливаемую громкость текущим MaxVolume + restoreVolume = Math.Min(restoreVolume, MaxVolume); Volume = restoreVolume; Log.Debug($"[PlayerBar] Unmuted, restored volume: {restoreVolume}"); } @@ -585,23 +525,24 @@ public PlayerBarViewModel( if (option == null) return; foreach (var f in AvailableFormats) f.IsActive = false; option.IsActive = true; + + BeginTrackReset(); + await _audio.SwitchQualityAsync(option.Container, (int)option.Bitrate); })); } + internal void RegisterView(PlayerBarView view) + { + _viewRef = new WeakReference(view); + } + #endregion #region Buffer Progress - /// - /// Обрабатывает обновление состояния буфера от AudioEngine. - /// private void HandleBufferStateChanged(BufferState state) { - Log.Debug($"[PlayerBarVM] HandleBufferStateChanged: progress={state.Progress:F1}%, " + - $"ranges={state.Ranges.Count}, suspended={_isSuspended}"); - - // Если трек загружен локально — всегда 100% if (CurrentTrack?.IsDownloaded == true) { if (!IsFullyBuffered) @@ -617,25 +558,19 @@ private void HandleBufferStateChanged(BufferState state) BufferProgressPercent = state.Progress; IsFullyBuffered = state.IsFullyBuffered; - // Обновляем ranges только если они реально изменились var newRanges = state.Ranges; if (!RangesEqual(BufferedRanges, newRanges)) { - Log.Debug($"[PlayerBarVM] Updating BufferedRanges: {newRanges.Count} ranges"); BufferedRanges = newRanges; this.RaisePropertyChanged(nameof(UseSegmentedBuffer)); - this.RaisePropertyChanged(nameof(BufferedRanges)); // Явно! - } - else - { - Log.Debug("[PlayerBarVM] Ranges unchanged, skipping update"); + this.RaisePropertyChanged(nameof(BufferedRanges)); } + + if (IsTrackResetting && newRanges.Count > 0 && state.Progress > 0) + EndTrackReset(); } - /// - /// Сравнивает два списка диапазонов для предотвращения лишних перерисовок. - /// private static bool RangesEqual( IReadOnlyList<(double Start, double End)> a, IReadOnlyList<(double Start, double End)> b) @@ -645,20 +580,14 @@ private static bool RangesEqual( for (int i = 0; i < a.Count; i++) { - // Используем epsilon для сравнения double if (Math.Abs(a[i].Start - b[i].Start) > 0.001 || Math.Abs(a[i].End - b[i].End) > 0.001) - { return false; - } } return true; } - /// - /// Принудительное обновление буфера (для resume и т.д.). - /// private void ForceUpdateBufferProgress() { if (!HasTrack || CurrentTrack == null) return; @@ -681,6 +610,29 @@ private void ForceUpdateBufferProgress() #endregion + #region Track Reset Visual + + private void BeginTrackReset() + { + IsTrackResetting = true; + + BufferProgressPercent = 0; + BufferedRanges = []; + IsFullyBuffered = false; + Position = TimeSpan.Zero; + PositionSeconds = 0; + + this.RaisePropertyChanged(nameof(UseSegmentedBuffer)); + } + + private async void EndTrackReset() + { + await Task.Delay(TrackResetVisualDelayMs); + IsTrackResetting = false; + } + + #endregion + #region Helpers private static string FormatTime(TimeSpan time) @@ -728,7 +680,7 @@ private void OnLibraryInitialized() RaiseVolumePropertiesChanged(); UpdateQueueState(); - Log.Info($"[PlayerBar] Initialized from settings: MaxVol={MaxVolume}, Vol={Volume}, Repeat={RepeatMode}, Shuffle={ShuffleEnabled}"); + Log.Info($"[PlayerBar] Initialized from settings: MaxVol={MaxVolume}, Vol={Volume}"); } #endregion @@ -790,9 +742,7 @@ private async Task LoadFormatsAsync() (int)f.Bitrate == cached.Bitrate); if (!f.IsDownloaded) - { f.IsDownloaded = _cacheManager.IsFormatCached(CurrentTrack.Id, f.Container, (int)f.Bitrate); - } f.IsActive = string.Equals(f.Codec, currentFormat, StringComparison.OrdinalIgnoreCase) && (int)f.Bitrate == currentBitrate; @@ -825,9 +775,7 @@ private void OnFormatCached(string trackId, string container, int bitrate, bool } if (!found && AvailableFormats.Count > 0) - { _ = LoadFormatsAsync(); - } } #endregion @@ -881,15 +829,16 @@ private void HandleMaxVolumeChanged(int newMax) int oldMax = MaxVolume; MaxVolume = newMax; + // Пересчитываем текущую громкость if (Volume > MaxVolume) { Volume = MaxVolume; + Log.Info($"[PlayerBar] Volume clamped from {Volume} to {MaxVolume}"); } + // Пересчитываем сохранённую громкость для mute/unmute if (_lastVolumeBeforeMute > MaxVolume) - { _lastVolumeBeforeMute = MaxVolume; - } RaiseVolumePropertiesChanged(); Log.Info($"[PlayerBar] MaxVolume changed: {oldMax} -> {newMax}"); @@ -897,6 +846,8 @@ private void HandleMaxVolumeChanged(int newMax) private void HandleTrackChanged(TrackInfo? track) { + BeginTrackReset(); + CurrentTrack = track; HasTrack = track != null; @@ -911,9 +862,6 @@ private void HandleTrackChanged(TrackInfo? track) AvailableFormats.Clear(); - Position = TimeSpan.Zero; - PositionSeconds = 0; - if (track != null) { Duration = track.Duration; @@ -922,18 +870,12 @@ private void HandleTrackChanged(TrackInfo? track) var storedTrack = _library.GetTrack(track.Id); IsLiked = storedTrack?.IsLiked ?? track.IsLiked; - // Сбрасываем буфер для нового трека if (track.IsDownloaded) { BufferProgressPercent = 100; BufferedRanges = [(0.0, 1.0)]; IsFullyBuffered = true; - } - else - { - BufferProgressPercent = 0; - BufferedRanges = []; - IsFullyBuffered = false; + EndTrackReset(); } ShowStreamInfo = true; @@ -943,16 +885,11 @@ private void HandleTrackChanged(TrackInfo? track) { Duration = TimeSpan.Zero; DurationSeconds = 1; - PositionSeconds = 0; - - // Полный сброс буфера - BufferProgressPercent = 0; - BufferedRanges = []; - IsFullyBuffered = false; ShowStreamInfo = false; StreamInfo = ""; IsLiked = false; + IsTrackResetting = false; } this.RaisePropertyChanged(nameof(UseSegmentedBuffer)); @@ -1005,6 +942,9 @@ private void UpdateStreamInfo(AudioStreamInfo info) f.IsActive = string.Equals(f.Codec, info.Codec, StringComparison.OrdinalIgnoreCase) && (int)f.Bitrate == info.Bitrate; } + + if (IsTrackResetting) + EndTrackReset(); } else { @@ -1017,6 +957,8 @@ private void UpdateStreamInfo(AudioStreamInfo info) private void UpdateDownloadSpeed() { + if (_isSuspended) return; + if (!HasTrack || CurrentTrack?.IsDownloaded == true || IsFullyBuffered) { DownloadSpeedText = ""; @@ -1043,7 +985,7 @@ private void UpdateDownloadSpeed() private void FallbackPositionUpdate() { - if (!HasTrack || _isSeeking || _justFinishedSeeking) return; + if (!HasTrack || _isSeeking || _justFinishedSeeking || _isSuspended) return; var realDur = _audio.TotalDuration; @@ -1065,9 +1007,6 @@ private void FallbackPositionUpdate() #region Public Interaction - /// - /// Начинает операцию seek (перетаскивание ползунка). - /// public void StartSeek() { _isSeeking = true; @@ -1075,15 +1014,9 @@ public void StartSeek() _wasPlayingBeforeSeek = IsPlaying; if (_wasPlayingBeforeSeek) - { _ = _audio.SetPlaybackStateAsync(false); - } } - /// - /// Обновляет позицию во время seek. - /// - /// Новая позиция в секундах. public void UpdateSeekPosition(double seconds) { if (!_isSeeking) return; @@ -1093,9 +1026,6 @@ public void UpdateSeekPosition(double seconds) this.RaisePropertyChanged(nameof(DurationTooltip)); } - /// - /// Завершает операцию seek. - /// public async void EndSeek() { if (!HasTrack) @@ -1115,39 +1045,42 @@ public async void EndSeek() IsSeekBusy = true; await _audio.SeekAsync(TimeSpan.FromSeconds(target)); + + ForceUpdateBufferProgress(); + await Task.Delay(300); if (_wasPlayingBeforeSeek) - { await _audio.SetPlaybackStateAsync(true); - } IsSeekBusy = false; _justFinishedSeeking = false; } - /// - /// Отменяет операцию seek. - /// public void CancelSeek() { _isSeeking = false; _justFinishedSeeking = false; if (_wasPlayingBeforeSeek) - { _ = _audio.SetPlaybackStateAsync(true); - } } - /// - /// Сохраняет громкость немедленно. - /// public void OnVolumeChangeComplete() { _audio.SaveVolumeNow(); } + /// + /// Вычисляет шаг изменения громкости для скролла. + /// + public int GetVolumeScrollStep() + { + // Чем больше MaxVolume, тем больше шаг чтобы реальный шаг был ~0.5% + if (MaxVolume <= 100) return 1; + return Math.Max(1, MaxVolume / 200); + } + #endregion #region LifeCycle @@ -1157,6 +1090,10 @@ protected override void OnSuspend() _isSuspended = true; _fallbackPositionTimer.Stop(); _speedUpdateTimer.Stop(); + + if (_viewRef?.TryGetTarget(out var view) == true) + view.OnSuspend(); + Log.Debug("[PlayerBar] Suspended"); } @@ -1166,10 +1103,12 @@ protected override void OnResume() _fallbackPositionTimer.Start(); _speedUpdateTimer.Start(); - // Обновляем данные которые могли измениться FallbackPositionUpdate(); ForceUpdateBufferProgress(); + if (_viewRef?.TryGetTarget(out var view) == true) + view.OnResume(); + Log.Debug("[PlayerBar] Resumed"); } @@ -1180,9 +1119,7 @@ protected override void Dispose(bool disposing) LocalizationService.Instance.LanguageChanged -= OnLanguageChanged; if (_isInitialized && Volume > 0) - { _library.UpdateSettings(s => s.LastVolume = Volume); - } _audio.SaveVolumeNow(); _fallbackPositionTimer.Stop(); diff --git a/Features/Settings/SettingsView.axaml b/Features/Settings/SettingsView.axaml index 19576d5..af17149 100644 --- a/Features/Settings/SettingsView.axaml +++ b/Features/Settings/SettingsView.axaml @@ -62,6 +62,23 @@ + + + + + @@ -414,17 +431,69 @@ + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -438,8 +507,37 @@ + + + + - + + + + + + + + + + + + + + + + + + + + + + @@ -452,9 +550,34 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -463,12 +586,34 @@ - + + + + + + + + + + + + + + + + + + + + +