mirror of https://github.com/odrling/Aegisub
Implemented spectrum cache aging/memory limiting.
Originally committed to SVN as r1317.
This commit is contained in:
parent
7b3674abcd
commit
336f0621d7
|
@ -35,6 +35,10 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include <utility>
|
||||||
|
#include <algorithm>
|
||||||
#include "audio_spectrum.h"
|
#include "audio_spectrum.h"
|
||||||
#include "fft.h"
|
#include "fft.h"
|
||||||
#include "colorspace.h"
|
#include "colorspace.h"
|
||||||
|
@ -44,15 +48,55 @@
|
||||||
|
|
||||||
// Audio spectrum FFT data cache
|
// Audio spectrum FFT data cache
|
||||||
|
|
||||||
|
// Spectrum cache basically caches the raw result of FFT
|
||||||
|
class AudioSpectrumCache {
|
||||||
|
public:
|
||||||
|
// Type of a single FFT result line
|
||||||
|
typedef std::vector<float> CacheLine;
|
||||||
|
|
||||||
|
// Types for cache aging
|
||||||
|
typedef unsigned int CacheAccessTime;
|
||||||
|
struct CacheAgeData {
|
||||||
|
CacheAccessTime access_time;
|
||||||
|
unsigned long first_line;
|
||||||
|
unsigned long num_lines; // includes overlap-lines
|
||||||
|
bool operator< (const CacheAgeData& second) { return access_time < second.access_time; }
|
||||||
|
CacheAgeData(CacheAccessTime t, unsigned long first, unsigned long num) : access_time(t), first_line(first), num_lines(num) { }
|
||||||
|
};
|
||||||
|
typedef std::vector<CacheAgeData> CacheAgeList;
|
||||||
|
|
||||||
|
// Get the overlap'th overlapping FFT in FFT group i, generating it if needed
|
||||||
|
virtual CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time) = 0;
|
||||||
|
|
||||||
|
// Get the total number of cache lines currently stored in this cache node's sub tree
|
||||||
|
virtual size_t GetManagedLineCount() = 0;
|
||||||
|
|
||||||
|
// Append to a list of last access times to the cache
|
||||||
|
virtual void GetLineAccessTimes(CacheAgeList &ages) = 0;
|
||||||
|
|
||||||
|
// Delete the cache storage starting with the given line id
|
||||||
|
// Return true if the object called on is empty and can safely be deleted too
|
||||||
|
virtual bool KillLine(unsigned long line_id) = 0;
|
||||||
|
|
||||||
|
// Set the FFT size used
|
||||||
|
static void SetLineLength(unsigned long new_length)
|
||||||
|
{
|
||||||
|
line_length = new_length;
|
||||||
|
null_line.resize(new_length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~AudioSpectrumCache() {};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// A cache line containing only zero-values
|
||||||
|
static CacheLine null_line;
|
||||||
|
// The FFT size
|
||||||
|
static unsigned long line_length;
|
||||||
|
};
|
||||||
|
|
||||||
AudioSpectrumCache::CacheLine AudioSpectrumCache::null_line;
|
AudioSpectrumCache::CacheLine AudioSpectrumCache::null_line;
|
||||||
unsigned long AudioSpectrumCache::line_length;
|
unsigned long AudioSpectrumCache::line_length;
|
||||||
|
|
||||||
void AudioSpectrumCache::SetLineLength(unsigned long new_length)
|
|
||||||
{
|
|
||||||
line_length = new_length;
|
|
||||||
null_line.resize(new_length, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Bottom level FFT cache, holds actual power data itself
|
// Bottom level FFT cache, holds actual power data itself
|
||||||
|
|
||||||
|
@ -62,9 +106,13 @@ private:
|
||||||
unsigned long start, length; // start and end of range
|
unsigned long start, length; // start and end of range
|
||||||
unsigned int overlaps;
|
unsigned int overlaps;
|
||||||
|
|
||||||
|
CacheAccessTime last_access;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created)
|
CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time)
|
||||||
{
|
{
|
||||||
|
last_access = access_time;
|
||||||
|
|
||||||
// This check ought to be redundant
|
// This check ought to be redundant
|
||||||
if (i >= start && i-start < length)
|
if (i >= start && i-start < length)
|
||||||
return data[i - start + overlap*length];
|
return data[i - start + overlap*length];
|
||||||
|
@ -72,6 +120,21 @@ public:
|
||||||
return null_line;
|
return null_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t GetManagedLineCount()
|
||||||
|
{
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetLineAccessTimes(CacheAgeList &ages)
|
||||||
|
{
|
||||||
|
ages.push_back(CacheAgeData(last_access, start, data.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KillLine(unsigned long line_id)
|
||||||
|
{
|
||||||
|
return start == line_id;
|
||||||
|
}
|
||||||
|
|
||||||
FinalSpectrumCache(AudioProvider *provider, unsigned long _start, unsigned long _length, unsigned int _overlaps)
|
FinalSpectrumCache(AudioProvider *provider, unsigned long _start, unsigned long _length, unsigned int _overlaps)
|
||||||
{
|
{
|
||||||
start = _start;
|
start = _start;
|
||||||
|
@ -149,7 +212,7 @@ private:
|
||||||
AudioProvider *provider;
|
AudioProvider *provider;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CacheLine &GetLine(unsigned long i, unsigned int overlap, bool &created)
|
CacheLine &GetLine(unsigned long i, unsigned int overlap, bool &created, CacheAccessTime access_time)
|
||||||
{
|
{
|
||||||
if (i >= start && i-start <= length) {
|
if (i >= start && i-start <= length) {
|
||||||
// Determine which sub-cache this line resides in
|
// Determine which sub-cache this line resides in
|
||||||
|
@ -165,12 +228,46 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sub_caches[subcache]->GetLine(i, overlap, created);
|
return sub_caches[subcache]->GetLine(i, overlap, created, access_time);
|
||||||
} else {
|
} else {
|
||||||
return null_line;
|
return null_line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t GetManagedLineCount()
|
||||||
|
{
|
||||||
|
size_t res = 0;
|
||||||
|
for (size_t i = 0; i < sub_caches.size(); ++i) {
|
||||||
|
if (sub_caches[i])
|
||||||
|
res += sub_caches[i]->GetManagedLineCount();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetLineAccessTimes(CacheAgeList &ages)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < sub_caches.size(); ++i) {
|
||||||
|
if (sub_caches[i])
|
||||||
|
sub_caches[i]->GetLineAccessTimes(ages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KillLine(unsigned long line_id)
|
||||||
|
{
|
||||||
|
int sub_caches_left = 0;
|
||||||
|
for (size_t i = 0; i < sub_caches.size(); ++i) {
|
||||||
|
if (sub_caches[i]) {
|
||||||
|
if (sub_caches[i]->KillLine(line_id)) {
|
||||||
|
delete sub_caches[i];
|
||||||
|
sub_caches[i] = 0;
|
||||||
|
} else {
|
||||||
|
sub_caches_left++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sub_caches_left == 0;
|
||||||
|
}
|
||||||
|
|
||||||
IntermediateSpectrumCache(AudioProvider *_provider, unsigned long _start, unsigned long _length, unsigned int _overlaps, int _depth)
|
IntermediateSpectrumCache(AudioProvider *_provider, unsigned long _start, unsigned long _length, unsigned int _overlaps, int _depth)
|
||||||
{
|
{
|
||||||
provider = _provider;
|
provider = _provider;
|
||||||
|
@ -203,6 +300,91 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AudioSpectrumCacheManager {
|
||||||
|
private:
|
||||||
|
IntermediateSpectrumCache *cache_root;
|
||||||
|
unsigned long cache_hits, cache_misses;
|
||||||
|
AudioSpectrumCache::CacheAccessTime cur_time;
|
||||||
|
|
||||||
|
unsigned long max_lines_cached;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AudioSpectrumCache::CacheLine &GetLine(unsigned long i, unsigned int overlap)
|
||||||
|
{
|
||||||
|
bool created = false;
|
||||||
|
AudioSpectrumCache::CacheLine &res = cache_root->GetLine(i, overlap, created, cur_time++);
|
||||||
|
if (created)
|
||||||
|
cache_misses++;
|
||||||
|
else
|
||||||
|
cache_hits++;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Age()
|
||||||
|
{
|
||||||
|
wxLogDebug(_T("AudioSpectrumCacheManager stats: hits=%u, misses=%u, misses%%=%f, managed lines=%u (max=%u)"), cache_hits, cache_misses, cache_misses/float(cache_hits+cache_misses)*100, cache_root->GetManagedLineCount(), max_lines_cached);
|
||||||
|
|
||||||
|
// 0 means no limit
|
||||||
|
if (max_lines_cached == 0)
|
||||||
|
return;
|
||||||
|
// No reason to proceed with complicated stuff if the count is too small
|
||||||
|
// (FIXME: does this really pay off?)
|
||||||
|
if (cache_root->GetManagedLineCount() < max_lines_cached)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get and sort ages
|
||||||
|
AudioSpectrumCache::CacheAgeList ages;
|
||||||
|
cache_root->GetLineAccessTimes(ages);
|
||||||
|
std::sort(ages.begin(), ages.end());
|
||||||
|
|
||||||
|
// Number of lines we have found used so far
|
||||||
|
// When this exceeds max_lines_caches go into kill-mode
|
||||||
|
unsigned long cumulative_lines = 0;
|
||||||
|
// Run backwards through the line age list (the most recently accessed items are at end)
|
||||||
|
AudioSpectrumCache::CacheAgeList::reverse_iterator it = ages.rbegin();
|
||||||
|
|
||||||
|
// Find the point where we have too many lines cached
|
||||||
|
while (cumulative_lines < max_lines_cached) {
|
||||||
|
if (it == ages.rend()) {
|
||||||
|
wxLogDebug(_T("AudioSpectrumCacheManager done aging did not exceed max_lines_cached"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cumulative_lines += it->num_lines;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
// By here, we have exceeded max_lines_cached so backtrack one
|
||||||
|
--it;
|
||||||
|
|
||||||
|
// And now start cleaning up
|
||||||
|
for (; it != ages.rend(); ++it) {
|
||||||
|
cache_root->KillLine(it->first_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxLogDebug(_T("AudioSpectrumCacheManager done aging, managed lines now=%u (max=%u)"), cache_root->GetManagedLineCount(), max_lines_cached);
|
||||||
|
assert(cache_root->GetManagedLineCount() < max_lines_cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioSpectrumCacheManager(AudioProvider *provider, unsigned long line_length, unsigned long num_lines, unsigned int num_overlaps)
|
||||||
|
{
|
||||||
|
cache_hits = cache_misses = 0;
|
||||||
|
cur_time = 0;
|
||||||
|
cache_root = new IntermediateSpectrumCache(provider, 0, num_lines, num_overlaps, 0);
|
||||||
|
|
||||||
|
// option is stored in megabytes, but we want number of bytes
|
||||||
|
unsigned long max_cache_size = Options.AsInt(_T("Audio Spectrum Memory Max")) * 1024 * 1024;
|
||||||
|
unsigned long line_size = sizeof(AudioSpectrumCache::CacheLine::value_type) * line_length;
|
||||||
|
max_lines_cached = max_cache_size / line_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
~AudioSpectrumCacheManager()
|
||||||
|
{
|
||||||
|
delete cache_root;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// AudioSpectrum
|
// AudioSpectrum
|
||||||
|
|
||||||
AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
|
AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
|
||||||
|
@ -228,7 +410,7 @@ AudioSpectrum::AudioSpectrum(AudioProvider *_provider)
|
||||||
num_lines = (unsigned long)_num_lines;
|
num_lines = (unsigned long)_num_lines;
|
||||||
|
|
||||||
AudioSpectrumCache::SetLineLength(line_length);
|
AudioSpectrumCache::SetLineLength(line_length);
|
||||||
cache = new IntermediateSpectrumCache(provider, 0, num_lines, fft_overlaps, 0);
|
cache = new AudioSpectrumCacheManager(provider, line_length, num_lines, fft_overlaps);
|
||||||
|
|
||||||
power_scale = 1;
|
power_scale = 1;
|
||||||
minband = Options.AsInt(_T("Audio Spectrum Cutoff"));
|
minband = Options.AsInt(_T("Audio Spectrum Cutoff"));
|
||||||
|
@ -261,9 +443,6 @@ void AudioSpectrum::RenderRange(__int64 range_start, __int64 range_end, bool sel
|
||||||
unsigned long first_line = (unsigned long)(fft_overlaps * range_start / line_length / 2);
|
unsigned long first_line = (unsigned long)(fft_overlaps * range_start / line_length / 2);
|
||||||
unsigned long last_line = (unsigned long)(fft_overlaps * range_end / line_length / 2);
|
unsigned long last_line = (unsigned long)(fft_overlaps * range_end / line_length / 2);
|
||||||
|
|
||||||
unsigned int cache_hits=0, cache_misses=0;
|
|
||||||
bool was_cache_miss;
|
|
||||||
|
|
||||||
float *power = new float[line_length];
|
float *power = new float[line_length];
|
||||||
|
|
||||||
int last_imgcol_rendered = -1;
|
int last_imgcol_rendered = -1;
|
||||||
|
@ -290,15 +469,12 @@ void AudioSpectrum::RenderRange(__int64 range_start, __int64 range_end, bool sel
|
||||||
if (imgcol <= last_imgcol_rendered)
|
if (imgcol <= last_imgcol_rendered)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
was_cache_miss = false;
|
AudioSpectrumCache::CacheLine &line = cache->GetLine(baseline, overlap);
|
||||||
AudioSpectrumCache::CacheLine &line = cache->GetLine(baseline, overlap, was_cache_miss);
|
|
||||||
++overlap;
|
++overlap;
|
||||||
if (overlap >= fft_overlaps) {
|
if (overlap >= fft_overlaps) {
|
||||||
overlap = 0;
|
overlap = 0;
|
||||||
++baseline;
|
++baseline;
|
||||||
}
|
}
|
||||||
if (was_cache_miss) cache_misses++;
|
|
||||||
else cache_hits++;
|
|
||||||
|
|
||||||
// Apply a "compressed" scaling to the signal power
|
// Apply a "compressed" scaling to the signal power
|
||||||
for (unsigned int j = 0; j < line_length; j++) {
|
for (unsigned int j = 0; j < line_length; j++) {
|
||||||
|
@ -365,7 +541,7 @@ void AudioSpectrum::RenderRange(__int64 range_start, __int64 range_end, bool sel
|
||||||
|
|
||||||
delete[] power;
|
delete[] power;
|
||||||
|
|
||||||
wxLogDebug(_T("Rendered spectrum: %u cache hits, %u misses"), cache_hits, cache_misses);
|
cache->Age();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,36 +38,17 @@
|
||||||
#define AUDIO_SPECTRUM_H
|
#define AUDIO_SPECTRUM_H
|
||||||
|
|
||||||
#include <wx/wxprec.h>
|
#include <wx/wxprec.h>
|
||||||
#include <vector>
|
|
||||||
#include "audio_provider.h"
|
#include "audio_provider.h"
|
||||||
|
|
||||||
|
|
||||||
// Spectrum cache basically caches the raw result of FFT
|
// Specified and implemented in cpp file, interface is private to spectrum code
|
||||||
class AudioSpectrumCache {
|
class AudioSpectrumCacheManager;
|
||||||
public:
|
|
||||||
// Type of a single FFT result line
|
|
||||||
typedef std::vector<float> CacheLine;
|
|
||||||
|
|
||||||
// Get the overlap'th overlapping FFT in FFT group i, generating it if needed
|
|
||||||
virtual CacheLine& GetLine(unsigned long i, unsigned int overlap, bool &created) = 0;
|
|
||||||
|
|
||||||
// Set the FFT size used
|
|
||||||
static void SetLineLength(unsigned long new_length);
|
|
||||||
|
|
||||||
virtual ~AudioSpectrumCache() {};
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// A cache line containing only zero-values
|
|
||||||
static CacheLine null_line;
|
|
||||||
// The FFT size
|
|
||||||
static unsigned long line_length;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class AudioSpectrum {
|
class AudioSpectrum {
|
||||||
private:
|
private:
|
||||||
// Data provider
|
// Data provider
|
||||||
AudioSpectrumCache *cache;
|
AudioSpectrumCacheManager *cache;
|
||||||
|
|
||||||
// Colour pallettes
|
// Colour pallettes
|
||||||
unsigned char colours_normal[256*3];
|
unsigned char colours_normal[256*3];
|
||||||
|
|
|
@ -55,11 +55,13 @@ Please visit http://aegisub.net to download latest version
|
||||||
o Misc. changes and bugfixes in karaoke mode. Using the syllable splitter should be easier now. (jfs)
|
o Misc. changes and bugfixes in karaoke mode. Using the syllable splitter should be easier now. (jfs)
|
||||||
o Fixed loading of Avisynth Scripts as audio. (AMZ)
|
o Fixed loading of Avisynth Scripts as audio. (AMZ)
|
||||||
- Changes to Audio Spectrum: (jfs)
|
- Changes to Audio Spectrum: (jfs)
|
||||||
o The calculated FFT data are now cached, so things should be faster
|
o The calculated FFT data are now cached, so things should be faster.
|
||||||
o Actual signal power is now more accurately represented
|
- The maximum size of the cache can be configured. Default is unlimited, which provides the best performance assuming enough memory is available.
|
||||||
o The palette is changed
|
o The quality of the spectrum can be easier configured, better quality requires more CPU and memory.
|
||||||
o The selection is no longer shown by ugly reverse colour but with a different palette instead
|
o Actual signal power is now more accurately represented.
|
||||||
o Use vertical zoom slider to amplify/dampen displayed signal strength (useful for better visualisation of quiet sections, or easier picking out the dominating frequencies in noisy sections)
|
o The palette is changed.
|
||||||
|
o The selection is no longer shown by ugly reverse colour but with a different palette instead.
|
||||||
|
o Use vertical zoom slider to amplify/dampen displayed signal strength. (Useful for better visualisation of quiet sections, or easier picking out the dominating frequencies in noisy sections.)
|
||||||
- Style Manager changes:
|
- Style Manager changes:
|
||||||
o Automatically select the style of the current line when Style Manager is opened. (jfs)
|
o Automatically select the style of the current line when Style Manager is opened. (jfs)
|
||||||
o Style storages with invalid characters are now caught, and the invalid characters replaced with safe ones. Previously such storages seemed to be created correctly but were never stored to disk. (jfs)
|
o Style storages with invalid characters are now caught, and the invalid characters replaced with safe ones. Previously such storages seemed to be created correctly but were never stored to disk. (jfs)
|
||||||
|
|
|
@ -530,6 +530,7 @@ DialogOptions::DialogOptions(wxWindow *parent)
|
||||||
AddTextControl(audioAdvPage,audioAdvSizer1,_("Spectrum cutoff"),_T("Audio spectrum cutoff"),TEXT_TYPE_NUMBER);
|
AddTextControl(audioAdvPage,audioAdvSizer1,_("Spectrum cutoff"),_T("Audio spectrum cutoff"),TEXT_TYPE_NUMBER);
|
||||||
wxString spectrum_quality_choices[] = { _("0 - Regular quality"), _("1 - Better quality"), _("2 - High quality"), _("3 - Insane quality") };
|
wxString spectrum_quality_choices[] = { _("0 - Regular quality"), _("1 - Better quality"), _("2 - High quality"), _("3 - Insane quality") };
|
||||||
AddComboControl(audioAdvPage,audioAdvSizer1,_("Spectrum quality"),_T("Audio spectrum quality"),wxArrayString(4,spectrum_quality_choices));
|
AddComboControl(audioAdvPage,audioAdvSizer1,_("Spectrum quality"),_T("Audio spectrum quality"),wxArrayString(4,spectrum_quality_choices));
|
||||||
|
AddTextControl(audioAdvPage,audioAdvSizer1,_("Spectrum cache memory max (MB)"),_T("Audio spectrum memory max"),TEXT_TYPE_NUMBER);
|
||||||
audioAdvSizer1->AddGrowableCol(0,1);
|
audioAdvSizer1->AddGrowableCol(0,1);
|
||||||
|
|
||||||
// Main sizer
|
// Main sizer
|
||||||
|
|
|
@ -193,6 +193,7 @@ void OptionsManager::LoadDefaults(bool onlyDefaults) {
|
||||||
// Technically these can do with just the spectrum object being re-created
|
// Technically these can do with just the spectrum object being re-created
|
||||||
SetInt(_T("Audio Spectrum Cutoff"),0);
|
SetInt(_T("Audio Spectrum Cutoff"),0);
|
||||||
SetInt(_T("Audio Spectrum Quality"),0);
|
SetInt(_T("Audio Spectrum Quality"),0);
|
||||||
|
SetInt(_T("Audio Spectrum Memory Max"),0); // megabytes
|
||||||
|
|
||||||
// Automation
|
// Automation
|
||||||
// The path changes only take effect when a script is (re)loaded but Automatic should be good enough, it certainly doesn't warrart a restart
|
// The path changes only take effect when a script is (re)loaded but Automatic should be good enough, it certainly doesn't warrart a restart
|
||||||
|
|
Loading…
Reference in New Issue