Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- Audition.h
- this class is meant to allow for loading of a sample to play through the plug. it can loop, and it comes with a default sample it can play.
- things to hate about this class:
- - being a value tree listener and having some functionality (loop and file loading) that can only be controlled via an external value tree is obtuse and annoying.
- - the value tree property callback for the file path uses string parsing as a means to choose between loading an external file or loading the compiled default sample. that's really gross. "how do i get this class to load its default sample ? oh obviously i pass the word 'default' into the file path property" <- very unintuitive, cryptic even
- - those callbacks should instead just be methods that are called elsewhere (e.g. by a value tree hookup external to the class)
- - this shouldn't inherit from timer. favor composition over inheritance. e.g. use a TimerAction member. as it is now, anything could start/stop its timer. (you could use private inheritance, but private inheritance is probably a code smell)
- - this should have an overridden destructor. it’s not technically necessary but it’s good practice because your compiler will warn you if a base class destructor wasn’t marked virtual (which would cause UB)
- - also this should've been a header and cpp, i was lazy. things should pretty much always be header and cpp but i obv get the convenience and use header only often, especially when prototyping. but for final stuff, i'll generally end up regretting not separating it from the start
- */
- struct Audition : public Timer, public aix::ValueTreeListener
- {
- Audition()
- {
- formatManager.registerBasicFormats();
- setDefaultSample();
- transportSource.setLooping (false);
- }
- void prepare (ProcessSpec spec)
- {
- sideBuffer.setSize (spec.numChannels, spec.maximumBlockSize, false, false, true);
- transportSource.prepareToPlay (spec.maximumBlockSize, spec.sampleRate);
- }
- void process (AudioBuffer<float>& buffer)
- {
- for (auto channel = 0; channel < sideBuffer.getNumChannels(); ++channel)
- sideBuffer.copyFrom (channel, 0, buffer, channel, 0, buffer.getNumSamples());
- AudioBuffer<float> pointed (sideBuffer.getArrayOfWritePointers(),
- sideBuffer.getNumChannels(),
- buffer.getNumSamples());
- if (playFlag.exchange (false))
- {
- transportSource.start();
- transportSource.setPosition (0.0);
- }
- AudioSourceChannelInfo info (pointed);
- transportSource.getNextAudioBlock (info);
- for (auto channel = 0; channel < sideBuffer.getNumChannels(); ++channel)
- buffer.addFrom (channel, 0, sideBuffer, channel, 0, buffer.getNumSamples());
- }
- // this timer is how the looping happens
- void timerCallback() override { playFlag.store (true); }
- /*
- i don't think this class should manage loading a file into an audio buffer, that's something
- we should probably have a aixtools object for. instead it could take an audiobuffer in.
- the trimming i mention in the next bit could happen if the audio buffer is too long
- */
- void setNewFile (File& file)
- {
- // bad bad. this should've been used to init a unique_ptr
- auto* reader = formatManager.createReaderFor (file);
- // if the file didn't exist or was bad for whatever reason, the reader would've failed,
- // so this existsAsFile() check is unnecessary noise
- if (file.existsAsFile() && reader != nullptr)
- {
- if (reader->lengthInSamples > reader->sampleRate)
- {
- /*
- i had to step through this block to remember what i was doing here,
- so that's a double bad. first bad for not commenting the purpose,
- and second bad for poor variable names.
- what this does is limit/trim an audition sample length to 4 seconds,
- and then applies a fade out at the end. this should definitely have been
- a separate function, ideally a free one in the cpp.
- */
- AudioBuffer<float> buffer;
- auto sampleRate = static_cast<int> (reader->sampleRate);
- auto numSamples = sampleRate * 4; // this could've been "maxSampleLengthLimit"
- buffer.setSize (reader->numChannels, numSamples);
- reader->read (&buffer, 0, numSamples, 0, true, true);
- auto tenMsInSamples = (10.0f / 1000.0f * static_cast<float> (sampleRate)); // outputFadeInSamples
- for (auto channel = 0; channel < buffer.getNumChannels(); ++channel)
- buffer.applyGainRamp (channel,
- buffer.getNumSamples() - tenMsInSamples,
- tenMsInSamples,
- 1.0f,
- 0.0f);
- delete reader; // this would be the unique ptr release call instead
- auto newSource = std::make_unique<MemoryAudioSource> (buffer, true);
- newSource->setLooping (false);
- transportSource.setSource (newSource.get(), 0, nullptr, sampleRate);
- memorySource = std::move (newSource);
- memorySource->setLooping (false);
- }
- else
- {
- /* some ugly non-DRYness going on here with the stuff right above */
- auto newSource = std::make_unique<AudioFormatReaderSource> (reader, true);
- newSource->setLooping (false);
- transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);
- readerSource = std::move (newSource);
- }
- transportSource.setGain (1.0f);
- }
- else
- {
- /*
- so this is stupid but was necessary in order to have the save state correct.
- the failure of a file load should be the responsibility of the mentioned file audiobuffer
- loading class, and the state management would fall on the container class for these two
- (audition and file loader)
- */
- getTree().setProperty (auditionFileIdt, "default", nullptr);
- }
- }
- /*
- since the default data would likely be different from project to project,
- that should probably just be something managed externally
- */
- void setDefaultSample()
- {
- auto reader = formatManager.createReaderFor (
- std::make_unique<MemoryInputStream> (BinaryData::snare_sample_wav,
- BinaryData::snare_sample_wavSize,
- false));
- auto newSource = std::make_unique<AudioFormatReaderSource> (reader, true);
- transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);
- transportSource.setGain (Decibels::decibelsToGain (-3.0f));
- readerSource = std::move (newSource);
- }
- /*
- this right here shouldn't be a function of this class,
- instead you could have the class that holds this audition class
- also hold an aix::ValueTreeCallbacks class and use it to listen to and
- call the relevant functions
- */
- void valueTreePropertyChanged (ValueTree& tree, const Identifier& property) override
- {
- /*
- these property == checks use Identifiers that were defined elsewhere in the project.
- that gives this a dumb and hidden project specific dependency (some property definition header somewhere)
- and also locks you into a ValueTree structure with the property names
- */
- if (property == auditionFileIdt)
- {
- auto path = tree.getProperty (property).toString();
- // string parsing no
- if (path.containsOnly ("default"))
- setDefaultSample();
- else
- {
- File f (path);
- setNewFile (f);
- }
- }
- else if (property == auditionLoopIdt)
- {
- loop = tree.getProperty (property);
- if (!loop)
- stopTimer();
- }
- }
- /*
- this and the stop() should have doxygen comments explaining where it's
- safe to call these functions from. anything that governs audio parameters
- should probably have comments like that. in this case they're safe to call
- from anywhere, but you'd have to go into this file to know that for sure
- */
- void play()
- {
- playFlag.store (true);
- if (loop)
- startTimer (4000);
- }
- void stop() { transportSource.stop(); stopTimer(); }
- private:
- juce::AudioFormatManager formatManager;
- std::unique_ptr<MemoryAudioSource> memorySource;
- std::unique_ptr<juce::AudioFormatReaderSource> readerSource;
- juce::AudioTransportSource transportSource;
- std::atomic<bool> playFlag { false };
- bool loop { false };
- AudioBuffer<float> sideBuffer;
- };
Add Comment
Please, Sign In to add comment