// Copyright 2003 Tom Felker, wore // // VLevel foobar2000 plugin // // VLevel is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // VLevel is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with VLevel; if not, write to the Free Software Foundation, // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // foo_dsp_vlevel.cpp - the foobar2000 plugin, uses VolumeLeveler // based on vlevel-bin.cpp // wore #include "../SDK/foobar2000.h" #include #include #include "volumeleveler.h" class dsp_vlevel : public dsp_i_base { VolumeLeveler *vl; // always == vl->GetChannels() size_t channels; // only applies to our local buffers which are cached across on_chunk() calls size_t samples; // our local buffers value_t *raw_value_buf; value_t **bufs; // only used to SetLengthAndChannels and to create chunks UINT srate; // how long the user wants the buffer to be // make sure GUI resets the flag // strength and max_mult can be set directly on the vl at any time double buffer_seconds; bool buffer_seconds_changed; bool ready() { // could be optimized to vl&&bufs, or expanded for complete sanity checks return vl && bufs && raw_value_buf && channels == vl->GetChannels(); } // insert the contents of our buffers into the stream // can only be called from on_endoftrack(), on_endofplayback(), and on_chunk() void flush_data() { // foobar2000 sometimes calls this when it shouldn't, so this is necessary. if(!ready()) return; // do nothing if there's no audio in the vl // otherwise on_chunk() will make this chunk empty, and foobar2000 complains // next vl version will have GetGoodSamples(){return samples - silence}; if(vl->GetSamples() - vl->GetSilence() == 0) return; // make a new chunk filled with enough silence to flush the buffers. audio_chunk *chunk = insert_chunk(vl->GetSamples() * channels); chunk->check_data_size(vl->GetSamples() * channels); chunk->set_channels(channels); chunk->set_srate(srate); chunk->set_sample_count(0); chunk->pad_with_silence(vl->GetSamples()); // yes, it's that simple. on_chunk(chunk); vl->Flush(); } // only affects the local buffers, not the volumeleveler void allocate_buffers(size_t new_samples, size_t new_channels) { cleanup_buffers(); samples = new_samples; channels = new_channels; raw_value_buf = new value_t[samples * channels]; bufs = new value_t*[channels]; for(size_t ch = 0; ch < channels; ++ch) bufs[ch] = new value_t[samples]; } // only affects the local buffers, not the volumeleveler void cleanup_buffers(void) { if (raw_value_buf!=0) { delete [] raw_value_buf; raw_value_buf=0; } if (bufs!=0) { for(size_t ch = 0; ch < channels; ++ch) delete [] bufs[ch]; delete [] bufs; bufs=0; } samples = 0; } public: dsp_vlevel() { raw_value_buf = 0; bufs = 0; samples = channels = srate = 0; buffer_seconds_changed = true; vl = new VolumeLeveler(); // Preferences buffer_seconds = 2; vl->SetStrength(1); vl->SetMaxMultiplier(25); } ~dsp_vlevel() { cleanup_buffers(); delete vl; } virtual GUID get_guid() { // {EC001F79-9D79-4dc8-B7CB-40818D7A1009} static const GUID guid = { 0xec001f79, 0x9d79, 0x4dc8, { 0xb7, 0xcb, 0x40, 0x81, 0x8d, 0x7a, 0x10, 0x9 } }; return guid; } virtual const char * get_name() { return "VLevel (Perfect seeking)";} virtual bool on_chunk(audio_chunk * chunk) { // we must change the vl's settings if buffer length or channels changes if(chunk->get_srate() != srate || chunk->get_channels() != channels || buffer_seconds_changed) { flush_data(); srate = chunk->get_srate(); channels = chunk->get_channels(); vl->SetSamplesAndChannels((size_t)(buffer_seconds * srate), channels); buffer_seconds_changed = false; } // if this chunk is a different size than last time, we must get new temp buffers if(chunk->get_sample_count() != samples || chunk->get_channels() != channels) allocate_buffers(chunk->get_sample_count(), chunk->get_channels()); assert(ready()); audio_sample *data = chunk->get_data(); // de-interleave the data for(size_t s = 0; s < samples; ++s) for(size_t ch = 0; ch < channels; ++ch) bufs[ch][s] = data[s * channels + ch]; // do the exchange size_t silence_samples = vl->Exchange(bufs, bufs, samples); // interleave the data for(s = silence_samples; s < samples; ++s) for(size_t ch = 0; ch < channels; ++ch) data[(s - silence_samples) * channels + ch] = bufs[ch][s]; // set the amount of good samples in the chunk chunk->set_sample_count(samples - silence_samples); return !chunk->is_empty(); } // this is redundant iff on_endoftrack is always called first. virtual void on_endofplayback() { console::info("on_endofplayback"); flush_data(); } // we output the contents of our buffers virtual void on_endoftrack() { console::info("on_endoftrack"); flush_data(); } // called on seeks, so we drop our data virtual void flush() { console::info("flush"); vl->Flush(); } // this is called very often while playing virtual double get_latency() { return (srate == 0) ? 0 : ((double)(vl->GetSamples() - vl->GetSilence()) / srate); } }; static service_factory_t foo; //wore