daveyeh

Untitled

Jul 14th, 2021 (edited)
577
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 9.32 KB | None | 0 0
  1. /*
  2. Audition.h
  3. 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.
  4.  
  5. things to hate about this class:
  6.  
  7. - 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.
  8.  
  9. - 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
  10.    
  11. - those callbacks should instead just be methods that are called elsewhere (e.g. by a value tree hookup external to the class)
  12.  
  13. - 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)
  14.  
  15. - 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)
  16.  
  17. - 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
  18. */
  19.  
  20. struct Audition : public Timer, public aix::ValueTreeListener
  21. {
  22.     Audition()
  23.     {
  24.         formatManager.registerBasicFormats();
  25.  
  26.         setDefaultSample();
  27.  
  28.         transportSource.setLooping (false);
  29.     }
  30.  
  31.     void prepare (ProcessSpec spec)
  32.     {
  33.         sideBuffer.setSize (spec.numChannels, spec.maximumBlockSize, false, false, true);
  34.         transportSource.prepareToPlay (spec.maximumBlockSize, spec.sampleRate);
  35.     }
  36.  
  37.     void process (AudioBuffer<float>& buffer)
  38.     {
  39.         for (auto channel = 0; channel < sideBuffer.getNumChannels(); ++channel)
  40.             sideBuffer.copyFrom (channel, 0, buffer, channel, 0, buffer.getNumSamples());
  41.  
  42.         AudioBuffer<float> pointed (sideBuffer.getArrayOfWritePointers(),
  43.                                     sideBuffer.getNumChannels(),
  44.                                     buffer.getNumSamples());
  45.         if (playFlag.exchange (false))
  46.         {
  47.             transportSource.start();
  48.             transportSource.setPosition (0.0);
  49.         }
  50.         AudioSourceChannelInfo info (pointed);
  51.         transportSource.getNextAudioBlock (info);
  52.  
  53.         for (auto channel = 0; channel < sideBuffer.getNumChannels(); ++channel)
  54.             buffer.addFrom (channel, 0, sideBuffer, channel, 0, buffer.getNumSamples());
  55.     }
  56.  
  57.     // this timer is how the looping happens
  58.     void timerCallback() override { playFlag.store (true); }
  59.  
  60.     /*
  61.         i don't think this class should manage loading a file into an audio buffer, that's something
  62.         we should probably have a aixtools object for. instead it could take an audiobuffer in.
  63.         the trimming i mention in the next bit could happen if the audio buffer is too long
  64.     */
  65.     void setNewFile (File& file)
  66.     {
  67.         // bad bad. this should've been used to init a unique_ptr
  68.         auto* reader = formatManager.createReaderFor (file);
  69.  
  70.         // if the file didn't exist or was bad for whatever reason, the reader would've failed,
  71.         // so this existsAsFile() check is unnecessary noise
  72.         if (file.existsAsFile() && reader != nullptr)
  73.         {
  74.             if (reader->lengthInSamples > reader->sampleRate)
  75.             {
  76.                 /*
  77.                     i had to step through this block to remember what i was doing here,
  78.                     so that's a double bad. first bad for not commenting the purpose,
  79.                     and second bad for poor variable names.
  80.                    
  81.                     what this does is limit/trim an audition sample length to 4 seconds,
  82.                     and then applies a fade out at the end. this should definitely have been
  83.                     a separate function, ideally a free one in the cpp.
  84.                 */
  85.                 AudioBuffer<float> buffer;
  86.                
  87.                 auto               sampleRate = static_cast<int> (reader->sampleRate);
  88.                 auto numSamples = sampleRate * 4; // this could've been "maxSampleLengthLimit"
  89.                 buffer.setSize (reader->numChannels, numSamples);
  90.                 reader->read (&buffer, 0, numSamples, 0, true, true);
  91.                 auto tenMsInSamples = (10.0f / 1000.0f * static_cast<float> (sampleRate)); // outputFadeInSamples
  92.                 for (auto channel = 0; channel < buffer.getNumChannels(); ++channel)
  93.                     buffer.applyGainRamp (channel,
  94.                                           buffer.getNumSamples() - tenMsInSamples,
  95.                                           tenMsInSamples,
  96.                                           1.0f,
  97.                                           0.0f);
  98.                 delete reader; // this would be the unique ptr release call instead
  99.                
  100.                 auto newSource = std::make_unique<MemoryAudioSource> (buffer, true);
  101.                 newSource->setLooping (false);
  102.                 transportSource.setSource (newSource.get(), 0, nullptr, sampleRate);
  103.                 memorySource = std::move (newSource);
  104.                 memorySource->setLooping (false);
  105.             }
  106.             else
  107.             {
  108.                 /* some ugly non-DRYness going on here with the stuff right above */
  109.                 auto newSource = std::make_unique<AudioFormatReaderSource> (reader, true);
  110.                 newSource->setLooping (false);
  111.                 transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);
  112.                 readerSource = std::move (newSource);
  113.             }
  114.             transportSource.setGain (1.0f);
  115.         }
  116.         else
  117.         {
  118.             /*
  119.                 so this is stupid but was necessary in order to have the save state correct.
  120.                 the failure of a file load should be the responsibility of the mentioned file audiobuffer
  121.                 loading class, and the state management would fall on the container class for these two
  122.                 (audition and file loader)
  123.             */
  124.             getTree().setProperty (auditionFileIdt, "default", nullptr);
  125.         }
  126.     }
  127.  
  128.     /*
  129.         since the default data would likely be different from project to project,
  130.         that should probably just be something managed externally
  131.     */
  132.     void setDefaultSample()
  133.     {
  134.         auto reader = formatManager.createReaderFor (
  135.             std::make_unique<MemoryInputStream> (BinaryData::snare_sample_wav,
  136.                                                  BinaryData::snare_sample_wavSize,
  137.                                                  false));
  138.         auto newSource = std::make_unique<AudioFormatReaderSource> (reader, true);
  139.         transportSource.setSource (newSource.get(), 0, nullptr, reader->sampleRate);
  140.         transportSource.setGain (Decibels::decibelsToGain (-3.0f));
  141.         readerSource = std::move (newSource);
  142.     }
  143.  
  144.     /*
  145.         this right here shouldn't be a function of this class,
  146.         instead you could have the class that holds this audition class
  147.         also hold an aix::ValueTreeCallbacks class and use it to listen to and
  148.         call the relevant functions
  149.     */
  150.     void valueTreePropertyChanged (ValueTree& tree, const Identifier& property) override
  151.     {
  152.         /*
  153.             these property == checks use Identifiers that were defined elsewhere in the project.
  154.             that gives this a dumb and hidden project specific dependency (some property definition header somewhere)
  155.             and also locks you into a ValueTree structure with the property names
  156.         */
  157.         if (property == auditionFileIdt)
  158.         {
  159.             auto path = tree.getProperty (property).toString();
  160.        
  161.             // string parsing no
  162.             if (path.containsOnly ("default"))
  163.                 setDefaultSample();
  164.             else
  165.             {
  166.                 File f (path);
  167.                 setNewFile (f);
  168.             }
  169.         }
  170.         else if (property == auditionLoopIdt)
  171.         {
  172.             loop = tree.getProperty (property);
  173.             if (!loop)
  174.                 stopTimer();
  175.         }
  176.     }
  177.  
  178.     /*
  179.         this and the stop() should have doxygen comments explaining where it's
  180.         safe to call these functions from. anything that governs audio parameters
  181.         should probably have comments like that. in this case they're safe to call
  182.         from anywhere, but you'd have to go into this file to know that for sure
  183.     */
  184.     void play()
  185.     {
  186.         playFlag.store (true);
  187.         if (loop)
  188.             startTimer (4000);
  189.     }
  190.  
  191.     void stop() { transportSource.stop(); stopTimer(); }
  192.    
  193. private:
  194.     juce::AudioFormatManager                       formatManager;
  195.     std::unique_ptr<MemoryAudioSource>             memorySource;
  196.     std::unique_ptr<juce::AudioFormatReaderSource> readerSource;
  197.     juce::AudioTransportSource                     transportSource;
  198.     std::atomic<bool>                              playFlag { false };
  199.     bool                                           loop { false };
  200.     AudioBuffer<float>                             sideBuffer;
  201. };
  202.  
Add Comment
Please, Sign In to add comment