GetDunne Wiki

Notes from the desk of Shane Dunne, software development consultant

User Tools

Site Tools


enum_class_rather_than_typedef_enum

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
enum_class_rather_than_typedef_enum [2017/09/01 13:16]
shane [Applying enum class in VanillaJuce: case 2]
enum_class_rather_than_typedef_enum [2017/09/01 14:01] (current)
shane [Conclusion and analysis]
Line 147: Line 147:
 (The above code was automatically generated by the Projucer.) (The above code was automatically generated by the Projucer.)
 Clearly, I needed to re-think the whole notion of waveform selection. Clearly, I needed to re-think the whole notion of waveform selection.
 +
 +===== Re-thinking waveform selection in VanillaJuce =====
 +The notion of //waveform// has more to it than just "an element of a finite set" (which is what a C++11 "enum class" models):
 +  * The set of waveforms might not always be finite. What if, down the road, I were to add some facility for users to define their own?
 +  * For humans, waveforms are identified by //string constants//, i.e., their names, typically through GUI widgets like //juce::ComboBox// which have an inherent linear //order//. These names are also useful when serializing preset-parameters to XML, because the names remain valid even if the inherent order of the chosen binary representation should change.
 +  * For program code, a more compact, unambiguous representation is needed, e.g. for my //SynthOscillator::getSample()// function, which is called //very often// and needs a quick way to look up the right chunk of code for the selected waveform.
 +
 +The set of available waveforms in a synthesizer is essentially an //ordered collection// of objects which have at least three attributes: a unique integer //index// (basis of the ordering), a human-readable //name//, and some associated //sample-generating code//. I realized that my original instinct to use an integer-indexed representation, with a static array of human-readable names, was correct; I just hadn't implemented it very cleanly.
 +
 +I decided to create a new class //SynthWaveform// to encapsulate the notion of an integer waveform //index// and the relationship between indices and human-readable //names//, and allow only the //SynthOscillator// class to make direct use of the index type (for efficient selection of sample-generating code). Here is  //SynthWaveform//:
 +<code cpp>
 +class SynthWaveform
 +{
 +private:
 +    enum WaveformTypeIndex {
 +        kSine, kTriangle, kSquare, kSawtooth,
 +        kNumberOfWaveformTypes
 +    } index;
 +    
 +    friend class SynthOscillator;
 +
 +public:
 +    // default constructor
 +    SynthWaveform() : index(kSine) {}
 +
 +    // set to default state after construction
 +    void setToDefault() { index = kSine; }
 +
 +    // serialize: get human-readable name of this waveform
 +    String name();
 +
 +    // deserialize: set index based on given name
 +    void setFromName(String wfName);
 +
 +    // convenience funtions to allow selecting SynthWaveform from a juce::comboBox
 +    static void setupComboBox(ComboBox& cb);
 +    void fromComboBox(ComboBox& cb);
 +    void toComboBox(ComboBox& cb);
 +
 +
 +private:
 +    // waveform names: ordered list of string literals
 +    static const char* const wfNames[];
 +};
 +
 +const char* const SynthWaveform::wfNames[] = {
 +    "Sine", "Triangle", "Square", "Sawtooth"
 +};
 +
 +void SynthWaveform::setFromName(String wfName)
 +{
 +    for (int i = 0; i < kNumberOfWaveformTypes; i++)
 +    {
 +        if (wfName == wfNames[i])
 +        {
 +            index = (WaveformTypeIndex)i;
 +            return;
 +        }
 +    }
 +
 +    // Were we given an invalid waveform name?
 +    jassertfalse;
 +}
 +
 +String SynthWaveform::name()
 +{
 +    return wfNames[index];
 +}
 +
 +void SynthWaveform::setupComboBox(ComboBox& cb)
 +{
 +    for (int i = 0; i < kNumberOfWaveformTypes; i++)
 +        cb.addItem(wfNames[i], i + 1);
 +}
 +
 +void SynthWaveform::fromComboBox(ComboBox& cb)
 +{
 +    index = (WaveformTypeIndex)(cb.getSelectedItemIndex());
 +}
 +
 +void SynthWaveform::toComboBox(ComboBox& cb)
 +{
 +    cb.setSelectedItemIndex((int)index);
 +}
 +</code>
 +I have used a traditional C++ ''enum'' type declaration (without the archaic ''typedef''), with full awareness of its near-equivalence to ''int'' and consequent lack of type-safety. I have mitigated the risk considerably, however, by making the //SynthWaveform// type ''private'', accessible only within this class and to the ''friend'' class //SynthOscillator//, which has a genuine pragmatic reason to use it (for efficient selection of code at runtime):
 +
 +<code cpp>
 +float SynthOscillator::getSample()
 +{
 +    float sample = 0.0f;
 +    switch (waveform.index)
 +    {
 +    case SynthWaveform::kSine:
 +        sample = (float)(std::sin(phase * 2.0 * double_Pi));
 +        break;
 +    case SynthWaveform::kSquare:
 +        sample = (phase <= 0.5) ? 1.0f : -1.0f;
 +        break;
 +    case SynthWaveform::kTriangle:
 +        sample = (float)(2.0 * (0.5 - std::fabs(phase - 0.5)) - 1.0);
 +        break;
 +    case SynthWaveform::kSawtooth:
 +        sample = (float)(2.0 * phase - 1.0);
 +        break;
 +    }
 +
 +    phase += phaseDelta;
 +    while (phase > 1.0) phase -= 1.0;
 +
 +    return sample;
 +}
 +</code>
  
 ===== Conclusion and analysis ===== ===== Conclusion and analysis =====
-After eliminating a few old-fashioned ''typedef enum'' declarations and associated codeI didn't reduce my overall line-count---in fact, my code got slightly longer---but I have gained type-safetyand that'good thing.+A C++11 "enum class" is an excellent, type-safe replacement for "typedef enum" when dealing with what amounts to //categorical variables//which are just symbolic names drawn from finite, unordered set.
  
 +For waveform selection in **VanillaJuce**, I realized that I was dealing with something considerably more complicated. My first attempt, to blindly apply the substitution ''typedef enum'' -> ''enum class'' resulted in //increased// line-count, and served to highlight what was already messy code. By re-thinking the representation deeply, I was able to achieve reduced line-count //and// cleaner code.
  
enum_class_rather_than_typedef_enum.1504271818.txt.gz ยท Last modified: 2017/09/01 13:16 by shane