diff --git a/aegisub/src/audio_renderer.cpp b/aegisub/src/audio_renderer.cpp index f05227efd..1cd6ee70a 100644 --- a/aegisub/src/audio_renderer.cpp +++ b/aegisub/src/audio_renderer.cpp @@ -35,161 +35,35 @@ #include "audio_renderer.h" +#include "include/aegisub/audio_provider.h" #include #include -#include - - -/// @class AudioRendererBitmapCache -/// @brief Caches bitmaps of rendered audio at a specific resolution -/// -/// Used internally by the AudioRenderer class. -class AudioRendererBitmapCache { - /// Width of cached bitmaps - int width; - /// Height of cached bitmaps - int height; - - /// Memory size of each cached image, for cache management - size_t bytes_per_image; - - // Cached bitmaps - // Intentionally not doxygened, too internal - struct CachedBitmap { - // The bitmap itself - wxBitmap bmp; - // "Timestamp" of last access (not necessarily related to any clocks) - int last_access; - - CachedBitmap(wxBitmap bmp = wxBitmap()) : bmp(bmp), last_access(0) { } - ~CachedBitmap() { } - - bool operator < (const CachedBitmap &other) const { return last_access < other.last_access; } - }; - - typedef std::map CacheType; - CacheType cache; - -public: - /// @brief Constructor - /// - /// Does nothing. - AudioRendererBitmapCache(); - - /// @brief Destructor - /// - /// Does nothing. - ~AudioRendererBitmapCache(); - - /// @brief Age the cache, purging old items - /// @param max_size Maximum allowed cache size, in bytes - /// - /// Purges the least recently used bitmaps to bring the cache size below max_size. - /// Aging is a somewhat expensive operation and should only be done once in a while. - /// - /// If max_size is specified to 0, the cache will be entirely cleared in a fast manner. - void Age(size_t max_size); - - /// @brief Change the size of cached images - /// @param new_width New width of images to cache - /// @param new_height New height of images to cache - /// - /// Clears and re-initialises the cache. - void Resize(int new_width, int new_height); - - /// @brief Retrieve an image from cache - /// @param bmp [out] Bitmap to return the image in - /// @param key Key to request the image for - /// @param timestamp Timestamp to use for cache age management - /// @return Returns false if the image had to be created in cache; if the return is - /// false, the consumer must draw into the returned bitmap. - /// - /// Example: - /// @code - /// wxBitmap bmp; - /// if (!cache->Get(bmp, key, timestamp)) - /// RenderBitmap(bmp); - /// // Use bmp - /// @endcode - /// - /// The timestamp passed should never decrease between calls to the same cache. - /// - /// The key has no inherent meaning to the cache. - bool Get(wxBitmap &bmp, int key, int timestamp); -}; - -AudioRendererBitmapCache::AudioRendererBitmapCache() +AudioRendererBitmapCacheBitmapFactory::AudioRendererBitmapCacheBitmapFactory(AudioRenderer *_renderer) { - Resize(0, 0); + assert(_renderer != 0); + renderer = _renderer; } -AudioRendererBitmapCache::~AudioRendererBitmapCache() +wxBitmap *AudioRendererBitmapCacheBitmapFactory::ProduceBlock(int i) { - // Nothing to do + (void)i; + return new wxBitmap(renderer->cache_bitmap_width, renderer->pixel_height, 32); } -void AudioRendererBitmapCache::Age(size_t max_size) +void AudioRendererBitmapCacheBitmapFactory::DisposeBlock(wxBitmap *bmp) { - if (max_size == 0) - { - cache.clear(); - return; - } - - /// @todo Make this faster than O(n^2) - - size_t max_items = max_size / bytes_per_image; - - while (cache.size() > max_items) - { - // Find the oldest item and remove it - CacheType::iterator next = cache.begin(); - CacheType::iterator oldest = next; - while (++next != cache.end()) - { - if (next->second.last_access < oldest->second.last_access) - oldest = next; - } - cache.erase(oldest); - } + delete bmp; } -void AudioRendererBitmapCache::Resize(int new_width, int new_height) +size_t AudioRendererBitmapCacheBitmapFactory::GetBlockSize() const { - Age(0); - - width = new_width; - height = new_height; - - // Assuming 32 bpp - // This probably isn't completely accurate, but just a reasonable approximation - bytes_per_image = sizeof(CachedBitmap) + width*height*4; -} - - -bool AudioRendererBitmapCache::Get(wxBitmap &bmp, int key, int timestamp) -{ - CacheType::iterator item = cache.find(key); - bool found = true; - - if (item == cache.end()) - { - cache[key] = CachedBitmap(wxBitmap(width, height, 32)); - item = cache.find(key); - assert(item != cache.end()); - found = false; - } - - item->second.last_access = timestamp; - bmp = item->second.bmp; - - return found; + return sizeof(wxBitmap) + renderer->cache_bitmap_width * renderer->pixel_height * 4; } @@ -197,9 +71,8 @@ bool AudioRendererBitmapCache::Get(wxBitmap &bmp, int key, int timestamp) AudioRenderer::AudioRenderer() : cache_bitmap_width(32) // arbitrary value for now -, bitmaps_normal(new AudioRendererBitmapCache) -, bitmaps_selected(new AudioRendererBitmapCache) -, cache_clock(0) +, bitmaps_normal(256, AudioRendererBitmapCacheBitmapFactory(this)) +, bitmaps_selected(256, AudioRendererBitmapCacheBitmapFactory(this)) , cache_maxsize(0) , renderer(0) , provider(0) @@ -218,7 +91,14 @@ AudioRenderer::~AudioRenderer() void AudioRenderer::SetSamplesPerPixel(int _pixel_samples) { + if (pixel_samples == _pixel_samples) return; + pixel_samples = _pixel_samples; + + if (renderer) + renderer->SetSamplesPerPixel(pixel_samples); + + ResetBlockCount(); } @@ -227,8 +107,22 @@ void AudioRenderer::SetHeight(int _pixel_height) if (pixel_height == _pixel_height) return; pixel_height = _pixel_height; - bitmaps_normal->Resize(cache_bitmap_width, pixel_height); - bitmaps_selected->Resize(cache_bitmap_width, pixel_height); + Invalidate(); +} + + +void AudioRenderer::SetAmplitudeScale(float _amplitude_scale) +{ + if (amplitude_scale == _amplitude_scale) return; + + // A scaling of 0 or a negative scaling makes no sense + assert(_amplitude_scale > 0); + + amplitude_scale = _amplitude_scale; + + if (renderer) + renderer->SetAmplitudeScale(amplitude_scale); + Invalidate(); } @@ -240,11 +134,14 @@ void AudioRenderer::SetRenderer(AudioRendererBitmapProvider *_renderer) renderer->SetProvider(0); renderer = _renderer; - bitmaps_normal->Age(0); - bitmaps_selected->Age(0); + Invalidate(); if (renderer) + { renderer->SetProvider(provider); + renderer->SetAmplitudeScale(amplitude_scale); + renderer->SetSamplesPerPixel(pixel_samples); + } } @@ -253,11 +150,23 @@ void AudioRenderer::SetAudioProvider(AudioProvider *_provider) if (provider == _provider) return; provider = _provider; - bitmaps_normal->Age(0); - bitmaps_selected->Age(0); + Invalidate(); if (renderer) renderer->SetProvider(provider); + + ResetBlockCount(); +} + + +void AudioRenderer::ResetBlockCount() +{ + if (provider) + { + size_t num_bitmaps = (size_t)((provider->GetNumSamples() + pixel_samples - 1) / pixel_samples); + bitmaps_normal.SetBlockCount(num_bitmaps); + bitmaps_selected.SetBlockCount(num_bitmaps); + } } @@ -267,16 +176,18 @@ wxBitmap AudioRenderer::GetCachedBitmap(int i, bool selected) assert(renderer); // Pick the cache to use - AudioRendererBitmapCache *cache = (selected ? bitmaps_selected : bitmaps_normal).get(); + AudioRendererBitmapCache *cache = selected ? &bitmaps_selected : &bitmaps_normal; - wxBitmap bmp; - if (!cache->Get(bmp, i, cache_clock)) + bool created = false; + wxBitmap *bmp = cache->Get(i, &created); + assert(bmp); + if (created) { - renderer->Render(bmp, i*cache_bitmap_width, selected); + renderer->Render(*bmp, i*cache_bitmap_width, selected); } - assert(bmp.IsOk()); - return bmp; + assert(bmp->IsOk()); + return *bmp; } @@ -344,9 +255,16 @@ void AudioRenderer::Render(wxDC &dc, wxPoint origin, int start, int length, bool } if (selected) - bitmaps_selected->Age(cache_maxsize); + bitmaps_selected.Age(cache_maxsize); else - bitmaps_normal->Age(cache_maxsize); + bitmaps_normal.Age(cache_maxsize); +} + + +void AudioRenderer::Invalidate() +{ + bitmaps_normal.Age(0); + bitmaps_selected.Age(0); } @@ -371,3 +289,13 @@ void AudioRendererBitmapProvider::SetSamplesPerPixel(int _pixel_samples) OnSetSamplesPerPixel(); } + +void AudioRendererBitmapProvider::SetAmplitudeScale(float _amplitude_scale) +{ + if (amplitude_scale == _amplitude_scale) return; + + amplitude_scale = _amplitude_scale; + + OnSetAmplitudeScale(); +} + diff --git a/aegisub/src/audio_renderer.h b/aegisub/src/audio_renderer.h index 64c33147a..283b7132b 100644 --- a/aegisub/src/audio_renderer.h +++ b/aegisub/src/audio_renderer.h @@ -37,17 +37,51 @@ #include -#include #include #include +#include "block_cache.h" // Some forward declarations for outside stuff class AudioProvider; // Forwards declarations for internal stuff -class AudioRendererBitmapCache; class AudioRendererBitmapProvider; +class AudioRenderer; + + + +/// @class AudioRendererBitmapCacheBitmapFactory +/// @brief Produces wxBitmap objects for DataBlockCache storage for the audio renderer +struct AudioRendererBitmapCacheBitmapFactory { + /// The audio renderer we're producing bitmaps for + AudioRenderer *renderer; + + /// @brief Constructor + /// @param renderer The audio renderer to produce bitmaps for + AudioRendererBitmapCacheBitmapFactory(AudioRenderer *renderer); + + /// @brief Create a new bitmap + /// @param i Unused + /// @return A fresh wxBitmap + /// + /// Produces a wxBitmap with dimensions pulled from our master AudioRenderer. + wxBitmap *ProduceBlock(int i); + + /// @brief Delete a bitmap + /// @param bmp The bitmap to delete + /// + /// Deletes said bitmap. + void DisposeBlock(wxBitmap *bmp); + + /// @brief Calculate the size of bitmaps + /// @return The size of bitmaps created + size_t GetBlockSize() const; +}; + +/// The type of a bitmap cache +typedef DataBlockCache AudioRendererBitmapCache; + /// @class AudioRenderer @@ -57,20 +91,22 @@ class AudioRendererBitmapProvider; /// /// To implement a new audio renderer, see AudioRendererBitmapProvider. class AudioRenderer { + friend AudioRendererBitmapCacheBitmapFactory; + /// Horizontal zoom level, samples per pixel int pixel_samples; - /// Vertical zoom level, height in pixels + /// Rendering height in pixels int pixel_height; + /// Vertical zoom level/amplitude scale + float amplitude_scale; /// Width of bitmaps to store in cache const int cache_bitmap_width; /// Cached bitmaps for normal audio ranges - std::auto_ptr bitmaps_normal; + AudioRendererBitmapCache bitmaps_normal; /// Cached bitmaps for marked (selected) audio ranges - std::auto_ptr bitmaps_selected; - /// The "clock" used for cache aging, increased on each render operation - int cache_clock; + AudioRendererBitmapCache bitmaps_selected; /// The maximum allowed size of the cache, in bytes size_t cache_maxsize; @@ -89,6 +125,13 @@ class AudioRenderer { /// if the cache doesn't have it. wxBitmap GetCachedBitmap(int i, bool selected); + /// @brief Update the block count in the bitmap caches + /// + /// Should be called when the width of the virtual bitmap has changed, i.e. + /// when the samples-per-pixel resolution or the number of audio samples + /// has changed. + void ResetBlockCount(); + public: /// @brief Constructor /// @@ -108,20 +151,35 @@ public: /// Changing the zoom level invalidates all cached bitmaps. void SetSamplesPerPixel(int pixel_samples); - /// @brief Set vertical zoom + /// @brief Set rendering height /// @param pixel_height Height in pixels to render at /// - /// Changing the zoom level invalidates all cached bitmaps. + /// Changing the rendering height invalidates all cached bitmaps. void SetHeight(int pixel_height); + /// @brief Set vertical zoom + /// @param amplitude_scale Scaling factor + /// + /// Changing the scaling factor invalidates all cached bitmaps. + /// + /// A scaling factor of 1.0 is no scaling, a factor of 0.5 causes the audio to be + /// rendered as if it had half its actual amplitude, a factor of 2 causes the audio + /// to be rendered as if it had double amplitude. (The exact meaning of the scaling + /// depends on the bitmap provider used.) + void SetAmplitudeScale(float amplitude_scale); + /// @brief Get horizontal zoom /// @return Audio samples per pixel rendering at int GetSamplesPerPixel() const { return pixel_samples; } - /// @brief Get vertical zoom + /// @brief Get rendering height /// @return Height in pixels rendering at int GetHeight() const { return pixel_height; } + /// @brief Get vertical zoom + /// @return The amplitude scaling factor + float GetAmplitudeScale() const { return amplitude_scale; } + /// @brief Change renderer /// @param renderer New renderer to use /// @@ -166,6 +224,16 @@ public: /// The first audio sample rendered is start*pixel_samples, and the number /// of audio samples rendered is length*pixel_samples. void Render(wxDC &dc, wxPoint origin, int start, int length, bool selected); + + /// @brief Invalidate all cached data + /// + /// Invalidates all cached bitmaps for another reason, usually as a signal that + /// implementation-defined data in the bitmap provider have been changed. + /// + /// If the consumer of audio rendering changes properties of the bitmap renderer + /// that will affect the rendered images, it should call this function to ensure + /// the cache is kept consistent. + void Invalidate(); }; @@ -179,6 +247,8 @@ protected: AudioProvider *provider; /// Horizontal zoom in samples per pixel int pixel_samples; + /// Vertical zoom/amplitude scale factor + float amplitude_scale; /// @brief Called when the audio provider changes /// @@ -190,6 +260,11 @@ protected: /// Implementations can override this method to do something when the horizontal zoom is changed virtual void OnSetSamplesPerPixel() { } + /// @brief Called when vertical zoom changes + /// + /// Implementations can override this method to do something when the vertical zoom is changed + virtual void OnSetAmplitudeScale() { } + public: /// @brief Constructor AudioRendererBitmapProvider() : provider(0), pixel_samples(0) { }; @@ -209,7 +284,12 @@ public: /// @brief Change audio provider /// @param provider Audio provider to change to void SetProvider(AudioProvider *provider); + /// @brief Change horizontal zoom /// @param pixel_samples Samples per pixel to zoom to void SetSamplesPerPixel(int pixel_samples); + + /// @brief Change vertical zoom + /// @param amplitude_scale Scaling factor to zoom to + void SetAmplitudeScale(float amplitude_scale); }; diff --git a/aegisub/src/block_cache.h b/aegisub/src/block_cache.h index 353f70d93..43a28d2f4 100644 --- a/aegisub/src/block_cache.h +++ b/aegisub/src/block_cache.h @@ -40,6 +40,7 @@ #include +#include @@ -104,8 +105,11 @@ class DataBlockCache { /// Type of an array of blocks typedef std::vector BlockArray; + /// DOCME struct MacroBlock { + /// How many times data in the macroblock has been accessed int access_count; + /// The blocks contained in the macroblock BlockArray blocks; }; @@ -131,7 +135,7 @@ class DataBlockCache { MacroBlock &mb = data[mb_index]; mb.access_count = 0; - for (size_t bi = 0; bi < blocks.size(); ++bi) + for (size_t bi = 0; bi < mb.blocks.size(); ++bi) { BlockT *b = mb.blocks[bi]; if (!b) @@ -153,11 +157,7 @@ public: DataBlockCache(size_t block_count, BlockFactoryT factory = BlockFactoryT()) : factory(factory) { - macroblock_size = 1 << MacroblockExponent; - - macroblock_index_mask = ~(((~0) >> MacroblockExponent) << MacroblockExponent); - - data.resize( (block_count + macroblock_size - 1) >> MacroblockExponent ); + SetBlockCount(block_count); } @@ -171,6 +171,23 @@ public: } + /// @brief Change the number of blocks in cache + /// @param block_count New number of blocks to hold + /// + /// This will completely de-allocate the cache and re-allocate it with the new block count. + void SetBlockCount(size_t block_count) + { + if (data.size() > 0) + Age(0); + + macroblock_size = 1 << MacroblockExponent; + + macroblock_index_mask = ~(((~0) >> MacroblockExponent) << MacroblockExponent); + + data.resize( (block_count + macroblock_size - 1) >> MacroblockExponent ); + } + + /// @brief Clean up the cache /// @param max_size Target maximum size of the cache in bytes /// @@ -202,7 +219,7 @@ public: access_data.reserve(data.size()); for (MacroBlockArray::iterator mb = data.begin(); mb != data.end(); ++mb) access_data.push_back(AccessData(mb)); - access_data.sort(); + std::sort(access_data.begin(), access_data.end()); // Sum up data size until we hit the max size_t cur_size = 0;