diff --git a/src/App.cpp b/src/app/App.cpp similarity index 69% rename from src/App.cpp rename to src/app/App.cpp index 3748747..dc7ffce 100644 --- a/src/App.cpp +++ b/src/app/App.cpp @@ -3,367 +3,34 @@ #include -#include "device/AudioDeviceManager.hpp" -#include "device/MidiDeviceManager.hpp" -#include "plugin/vst3/Vst3Plugin.hpp" -#include "plugin/vst3/Vst3PluginFactory.hpp" -#include "misc/StrCnv.hpp" -#include "misc/MathUtil.hpp" -#include "misc/TransitionalVolume.hpp" -#include "misc/LockFactory.hpp" -#include "misc/Algorithm.hpp" -#include "resource/ResourceHelper.hpp" #include "App.hpp" -#include "gui/Gui.hpp" -#include "gui/PCKeyboardInput.hpp" -#include "gui/DeviceSettingDialog.hpp" -#include "gui/PluginEditor.hpp" -#include "gui/AboutDialog.hpp" -#include "processor/EventBuffer.hpp" -#include "file/Config.hpp" -#include "file/ProjectFile.hpp" +#include "../device/AudioDeviceManager.hpp" +#include "../device/MidiDeviceManager.hpp" +#include "../plugin/vst3/Vst3Plugin.hpp" +#include "../plugin/vst3/Vst3PluginFactory.hpp" +#include "../misc/StrCnv.hpp" +#include "../misc/MathUtil.hpp" +#include "../misc/TransitionalVolume.hpp" +#include "../misc/LockFactory.hpp" +#include "../misc/Algorithm.hpp" +#include "../resource/ResourceHelper.hpp" +#include "../gui/Gui.hpp" +#include "../gui/PCKeyboardInput.hpp" +#include "../gui/DeviceSettingDialog.hpp" +#include "../gui/PluginEditor.hpp" +#include "../gui/AboutDialog.hpp" +#include "../processor/EventBuffer.hpp" +#include "../file/Config.hpp" +#include "../file/ProjectFile.hpp" +#include "./NoteStatus.hpp" +#include "./TestSynth.hpp" NS_HWM_BEGIN -SampleCount const kSampleRate = 44100; -SampleCount const kBlockSize = 256; double const kAudioOutputLevelMinDB = -48.0; double const kAudioOutputLevelMaxDB = 0.0; Int32 kAudioOutputLevelTransientMillisec = 30; -struct alignas(4) NoteStatus { - enum Type : char { - kNull, - kNoteOn, - kNoteOff, - }; - - static NoteStatus CreateNoteOn(UInt8 velocity) - { - assert(velocity != 0); - return NoteStatus { kNoteOn, velocity, 0 }; - } - - static NoteStatus CreateNoteOff(UInt8 off_velocity) - { - return NoteStatus { kNoteOff, off_velocity, 0 }; - } - - static NoteStatus CreateNull() - { - return NoteStatus {}; - } - - bool is_null() const { return type_ == kNull; } - bool is_note_on() const { return type_ == kNoteOn; } - bool is_note_off() const { return type_ == kNoteOff; } - UInt8 get_velocity() const { return velocity_; } - - bool operator==(NoteStatus const &rhs) const - { - auto to_tuple = [](auto const &self) { - return std::tie(self.type_, self.velocity_); - }; - - return to_tuple(*this) == to_tuple(rhs); - } - - bool operator!=(NoteStatus const &rhs) const - { - return !(*this == rhs); - } - - Type type_ = Type::kNull; - UInt8 velocity_ = 0; - UInt16 dummy_padding_ = 0; -}; - -using KeyboardStatus = std::array, 128>; - -struct TestSynth -{ - struct WaveTable - { - WaveTable(int length_as_shift_size) - { - assert(length_as_shift_size <= 31); - - shift_ = length_as_shift_size; - wave_data_.resize(1 << shift_); - } - - //! Get the table value of the specified position. - //! @param pos is a normalized pos [0.0 - 1.0) - AudioSample GetValueSimple(double pos) const - { - auto const k = 1 << shift_; - auto const sample_pos = (SampleCount)std::round(pos * k) & (k - 1); - assert(0 <= sample_pos && sample_pos < wave_data_.size()); - return wave_data_[sample_pos]; - } - - std::vector wave_data_; - int shift_; - }; - - using WaveTablePtr = std::shared_ptr; - using OscillatorType = App::TestWaveformType; - - //! This function calculates the PolyBLEPs - //! @param t 現在の位相。[0..1]で、[0..2*PI]を表す - //! @param dt 目的のピッチに対応する角速度を正規化周波数で表したもの。 - static - double poly_blep(double t, double dt) - { - // t-t^2/2 +1/2 - // 0 < t <= 1 - // discontinuities between 0 & 1 - if (t < dt) - { - t /= dt; - return t + t - t * t - 1.0; - } - - // t^2/2 +t +1/2 - // -1 <= t <= 0 - // discontinuities between -1 & 0 - else if (t > 1.0 - dt) - { - t = (t - 1.0) / dt; - return t * t + t + t + 1.0; - } - - // no discontinuities - // 0 otherwise - else return 0.0; - } - - static - double note_number_to_freq(int n) { - return 440 * pow(2, (n - 69) / 12.0); - }; - - static - WaveTablePtr GenerateSinTable() - { - auto table = std::make_unique(16); - auto data = table->wave_data_.data(); - auto const len = table->wave_data_.size(); - for(int smp = 0; smp < len; ++smp) { - data[smp] = sin(2 * M_PI * smp / (double)len); - } - - return table; - } - - static - WaveTable const * GetSinTable() - { - static WaveTablePtr table = GenerateSinTable(); - return table.get(); - } - - struct Voice - { - Voice() = default; - - Voice(OscillatorType ot, - double sample_rate, - double freq, - Int32 num_attack_samples, - Int32 num_release_samples) - { - num_attack_samples_ = num_attack_samples; - num_release_samples_ = num_release_samples; - ot_ = ot; - freq_ = freq; - sample_rate_ = sample_rate; - } - - double get_current_gain() const - { - double const kVolumeRange = 48.0; - - double gain = 1.0; - if(state_ == State::kAttack) { - assert(attack_sample_pos_ < num_attack_samples_); - if(attack_sample_pos_ == 0) { - return 0; // gain == 0 - } else { - gain = DBToLinear((attack_sample_pos_ / (double)num_attack_samples_) * kVolumeRange - kVolumeRange); - } - } else if(state_ == State::kSustain) { - // do nothing. - } else if(state_ == State::kRelease) { - assert(release_sample_pos_ < num_release_samples_); - auto attack_pos_ratio = attack_sample_pos_ / (double)num_attack_samples_; - auto release_pos_ratio = Clamp((release_sample_pos_ / (double)num_release_samples_) + (1 - attack_pos_ratio), - -1.0, 1.0); - gain = DBToLinear(release_pos_ratio * -kVolumeRange); - } - - return gain; - } - - //! generate one sample and advance one step. - AudioSample generate() - { - if(is_alive() == false) { return 0; } - - auto const gain = get_current_gain(); - - auto const dt = freq_ / sample_rate_; - auto const t = t_; - - double smp = 0; - - // based on http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/ - if(ot_ == OscillatorType::kSine) { - auto const wave = GetSinTable(); - assert(wave); - smp = wave->GetValueSimple(t); - } else if(ot_ == OscillatorType::kSaw) { - smp = ((2 * t) - 1.0) - poly_blep(t, dt); - } else if(ot_ == OscillatorType::kSquare) { - smp = t < 0.5 ? 1.0 : -1.0; - smp += poly_blep(t, dt); - smp -= poly_blep(fmod(t + 0.5, 1.0), dt); - } else if(ot_ == OscillatorType::kTriangle) { - smp = t < 0.5 ? 1.0 : -1.0; - smp += poly_blep(t, dt); - smp -= poly_blep(fmod(t + 0.5, 1.0), dt); - - auto dt2pi = dt * 2 * M_PI; - smp = dt2pi * smp + (1 - dt2pi) * last_smp_; - last_smp_ = smp; - } - - // advance one step - t_ += dt; - while(t_ >= 1.0) { - t_ -= 1.0; - } - - advance_envelope(); - - double const master_volume = 0.5; - return smp * gain * master_volume; - } - - bool is_alive() const { return state_ != State::kFinished; } - - void start() - { - t_ = 0; - last_smp_ = 0; - attack_sample_pos_ = 0; - release_sample_pos_ = 0; - state_ = State::kAttack; - } - - void force_stop() - { - state_ = State::kFinished; - } - - void request_to_stop() - { - if(state_ != State::kFinished) { - state_ = State::kRelease; - } - } - - private: - Int32 num_attack_samples_ = 0; - Int32 attack_sample_pos_ = 0; - Int32 num_release_samples_ = 0; - Int32 release_sample_pos_ = 0; - enum class State { - kAttack, - kSustain, - kRelease, - kFinished, - }; - State state_ = State::kFinished; - - OscillatorType ot_; - double freq_; - double sample_rate_; - double t_ = 0; - double last_smp_ = 0; - - void advance_envelope() - { - if(state_ == State::kAttack) { - attack_sample_pos_ += 1; - if(attack_sample_pos_ == num_attack_samples_) { - state_ = State::kSustain; - } - } else if(state_ == State::kSustain) { - // do nothing. - } else if(state_ == State::kRelease) { - release_sample_pos_ += 1; - if(release_sample_pos_ == num_release_samples_) { - state_ = State::kFinished; - } - } - } - }; - - std::array voices_; - using KeyboardStatus = std::array, 128>; - KeyboardStatus keys_; - double sample_rate_ = 0; - std::atomic ot_; - Int32 num_attack_samples_ = 0; - Int32 num_release_samples_ = 0; - - // デバイスダイアログを閉じて、デバイスを開始する前に段階で呼び出す - void SetSampleRate(double sample_rate) - { - sample_rate_ = sample_rate; - num_attack_samples_ = std::round(sample_rate * 0.03); - num_release_samples_ = std::round(sample_rate * 0.03); - - for(auto &v: voices_) { v.force_stop(); } - - GetSinTable(); // force generate the sin table. - } - - void SetOscillatorType(OscillatorType ot) - { - ot_.store(ot); - } - - OscillatorType GetOscillatorType() const - { - return ot_.load(); - } - - void Process(AudioSample *dest, SampleCount length) - { - for(int n = 0; n < 128; ++n) { - auto st = keys_[n].load(); - if(st.is_note_on() && voices_[n].is_alive() == false) { - voices_[n] = Voice { - ot_, sample_rate_, note_number_to_freq(n), - num_attack_samples_, num_release_samples_ - }; - voices_[n].start(); - } else if(st.is_note_off()) { - voices_[n].request_to_stop(); - } - - if(voices_[n].is_alive() == false) { continue; } - - auto &v = voices_[n]; - for(int smp = 0; smp < length; ++smp) { - dest[smp] += v.generate(); - } - } - } -}; - bool OpenAudioDevice(Config const &conf) { if(conf.audio_output_device_name_.empty()) { @@ -1015,14 +682,20 @@ double App::GetAudioOutputLevel() const void App::SetAudioOutputLevel(double db) { pimpl_->output_level_.set_target_db(db); + pimpl_->pocls_.Invoke([db](auto *li) { + li->OnAudioOutputLevelChanged(db); + }); } -void App::SetTestWaveformType(TestWaveformType wt) +void App::SetTestWaveformType(OscillatorType ot) { - pimpl_->test_synth_.SetOscillatorType(wt); + pimpl_->test_synth_.SetOscillatorType(ot); + pimpl_->pocls_.Invoke([ot](auto *li) { + li->OnTestWaveformTypeChanged(ot); + }); } -App::TestWaveformType App::GetTestWaveformType() const +OscillatorType App::GetTestWaveformType() const { return pimpl_->test_synth_.GetOscillatorType(); } @@ -1120,6 +793,13 @@ void App::LoadProjectFile(String path_to_load) hwm::dout << "failed to load project file: " << e.what() << std::endl; } + if(file.oscillator_type_) { + SetTestWaveformType(*file.oscillator_type_); + } + + SetAudioOutputLevel(file.audio_output_level_); + EnableAudioInput(file.is_audio_input_enabled_); + if(file.vst3_plugin_path_.empty()) { return; } if(!LoadVst3Module(file.vst3_plugin_path_)) { @@ -1140,17 +820,15 @@ void App::LoadProjectFile(String path_to_load) GetPlugin()->LoadData(dump); - if(file.editor_type_.empty() == false) { + if(file.editor_type_) { auto frame = IMainFrame::GetInstance(); wxCommandEvent ev(wxEVT_COMMAND_MENU_SELECTED); ev.SetId(IMainFrame::kID_View_PluginEditor); ev.SetEventObject(frame); frame->ProcessWindowEvent(ev); - using VT = IPluginEditorFrame::ViewType; auto editor = IPluginEditorFrame::GetInstance(); - auto const vt = (file.editor_type_ == L"generic" ? VT::kGeneric : VT::kDedicated); - editor->SetViewType(vt); + editor->SetViewType(*file.editor_type_); } } @@ -1159,6 +837,7 @@ void App::SaveProjectFile(String path_to_save) ProjectFile file; file.ScanAudioDeviceStatus(); file.ScanPluginStatus(); + file.ScanAppStatus(); std::ofstream ofs; #if defined(_MSC_VER) diff --git a/src/App.hpp b/src/app/App.hpp similarity index 87% rename from src/App.hpp rename to src/app/App.hpp index 4307542..4644aa4 100644 --- a/src/App.hpp +++ b/src/app/App.hpp @@ -3,9 +3,10 @@ #include #include -#include "./misc/SingleInstance.hpp" -#include "./plugin/vst3/Vst3Plugin.hpp" -#include "./file/Config.hpp" +#include "../misc/SingleInstance.hpp" +#include "../plugin/vst3/Vst3Plugin.hpp" +#include "../file/Config.hpp" +#include "./OscillatorType.hpp" NS_HWM_BEGIN @@ -58,13 +59,20 @@ class App using PluginLoadListenerService = IListenerService; PluginLoadListenerService & GetPluginLoadListenerService(); - //! プラグインのロード/アンロード状態の変更通知を受け取るリスナークラス + //! Appクラス再生系パラメータの変更通知を受け取るリスナークラス class PlaybackOptionChangeListener : public IListenerBase { protected: PlaybackOptionChangeListener() {} public: + //! オーディオ出力レベルを変更したときに呼ばれるコールバック + virtual void OnAudioOutputLevelChanged(double new_level) {} + + //! テスト波形のタイプを変更したときに呼ばれるコールバック + virtual void OnTestWaveformTypeChanged(OscillatorType new_osc_type) {} + //! オーディオデバイスの状態が変更になって、オーディオ入力が可能かどうかが変化したときに呼ばれるコールバック virtual void OnAudioInputAvailabilityChanged(bool available) {} + //! オーディオ入力が可能な状態で、その有効/無効を切り替えたときに呼ばれるコールバック virtual void OnAudioInputEnableStateChanged(bool enabled) {} }; @@ -96,15 +104,8 @@ class App //! オーディオ出力レベルを変更する。 void SetAudioOutputLevel(double db); - enum class TestWaveformType : Int32 { - kSine, - kSaw, - kSquare, - kTriangle, - }; - - void SetTestWaveformType(TestWaveformType wt); - TestWaveformType GetTestWaveformType() const; + void SetTestWaveformType(OscillatorType wt); + OscillatorType GetTestWaveformType() const; //! 再生中のノートを返す。 std::bitset<128> GetPlayingNotes(); diff --git a/src/app/NoteStatus.hpp b/src/app/NoteStatus.hpp new file mode 100644 index 0000000..1f00d72 --- /dev/null +++ b/src/app/NoteStatus.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +NS_HWM_BEGIN + +struct alignas(4) NoteStatus { + enum Type : char { + kNull, + kNoteOn, + kNoteOff, + }; + + static NoteStatus CreateNoteOn(UInt8 velocity) + { + assert(velocity != 0); + return NoteStatus { kNoteOn, velocity, 0 }; + } + + static NoteStatus CreateNoteOff(UInt8 off_velocity) + { + return NoteStatus { kNoteOff, off_velocity, 0 }; + } + + static NoteStatus CreateNull() + { + return NoteStatus {}; + } + + bool is_null() const { return type_ == kNull; } + bool is_note_on() const { return type_ == kNoteOn; } + bool is_note_off() const { return type_ == kNoteOff; } + UInt8 get_velocity() const { return velocity_; } + + bool operator==(NoteStatus const &rhs) const + { + auto to_tuple = [](auto const &self) { + return std::tie(self.type_, self.velocity_); + }; + + return to_tuple(*this) == to_tuple(rhs); + } + + bool operator!=(NoteStatus const &rhs) const + { + return !(*this == rhs); + } + + Type type_ = Type::kNull; + UInt8 velocity_ = 0; + UInt16 dummy_padding_ = 0; +}; + +using KeyboardStatus = std::array, 128>; + +NS_HWM_END diff --git a/src/app/OscillatorType.hpp b/src/app/OscillatorType.hpp new file mode 100644 index 0000000..ed19ee5 --- /dev/null +++ b/src/app/OscillatorType.hpp @@ -0,0 +1,12 @@ +#pragma once + +NS_HWM_BEGIN + +enum class OscillatorType : Int32 { + kSine, + kSaw, + kSquare, + kTriangle, +}; + +NS_HWM_END diff --git a/src/app/TestSynth.cpp b/src/app/TestSynth.cpp new file mode 100644 index 0000000..947feab --- /dev/null +++ b/src/app/TestSynth.cpp @@ -0,0 +1,244 @@ +#include "TestSynth.hpp" +#include "../misc/MathUtil.hpp" + +NS_HWM_BEGIN + +struct TestSynth::WaveTable +{ + WaveTable(int length_as_shift_size) + { + assert(length_as_shift_size <= 31); + + shift_ = length_as_shift_size; + wave_data_.resize(1 << shift_); + } + + //! Get the table value of the specified position. + //! @param pos is a normalized pos [0.0 - 1.0) + AudioSample GetValueSimple(double pos) const + { + auto const k = 1 << shift_; + auto const sample_pos = (SampleCount)std::round(pos * k) & (k - 1); + assert(0 <= sample_pos && sample_pos < wave_data_.size()); + return wave_data_[sample_pos]; + } + + std::vector wave_data_; + int shift_; +}; + +double TestSynth::poly_blep(double t, double dt) +{ + // t-t^2/2 +1/2 + // 0 < t <= 1 + // discontinuities between 0 & 1 + if (t < dt) + { + t /= dt; + return t + t - t * t - 1.0; + } + + // t^2/2 +t +1/2 + // -1 <= t <= 0 + // discontinuities between -1 & 0 + else if (t > 1.0 - dt) + { + t = (t - 1.0) / dt; + return t * t + t + t + 1.0; + } + + // no discontinuities + // 0 otherwise + else return 0.0; +} + +double TestSynth::note_number_to_freq(int n) { + return 440 * pow(2, (n - 69) / 12.0); +}; + +TestSynth::WaveTablePtr TestSynth::GenerateSinTable() +{ + auto table = std::make_unique(16); + auto data = table->wave_data_.data(); + auto const len = table->wave_data_.size(); + for(int smp = 0; smp < len; ++smp) { + data[smp] = sin(2 * M_PI * smp / (double)len); + } + + return table; +} + +TestSynth::WaveTable const * TestSynth::GetSinTable() +{ + static WaveTablePtr table = GenerateSinTable(); + return table.get(); +} + +TestSynth::Voice::Voice(OscillatorType ot, + double sample_rate, + double freq, + Int32 num_attack_samples, + Int32 num_release_samples) +{ + num_attack_samples_ = num_attack_samples; + num_release_samples_ = num_release_samples; + ot_ = ot; + freq_ = freq; + sample_rate_ = sample_rate; +} + +double TestSynth::Voice::get_current_gain() const +{ + double const kVolumeRange = 48.0; + + double gain = 1.0; + if(state_ == State::kAttack) { + assert(attack_sample_pos_ < num_attack_samples_); + if(attack_sample_pos_ == 0) { + return 0; // gain == 0 + } else { + gain = DBToLinear((attack_sample_pos_ / (double)num_attack_samples_) * kVolumeRange - kVolumeRange); + } + } else if(state_ == State::kSustain) { + // do nothing. + } else if(state_ == State::kRelease) { + assert(release_sample_pos_ < num_release_samples_); + auto attack_pos_ratio = attack_sample_pos_ / (double)num_attack_samples_; + auto release_pos_ratio = Clamp((release_sample_pos_ / (double)num_release_samples_) + (1 - attack_pos_ratio), + -1.0, 1.0); + gain = DBToLinear(release_pos_ratio * -kVolumeRange); + } + + return gain; +} + +//! generate one sample and advance one step. +AudioSample TestSynth::Voice::generate() +{ + if(is_alive() == false) { return 0; } + + auto const gain = get_current_gain(); + + auto const dt = freq_ / sample_rate_; + auto const t = t_; + + double smp = 0; + + // based on http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/ + if(ot_ == OscillatorType::kSine) { + auto const wave = GetSinTable(); + assert(wave); + smp = wave->GetValueSimple(t); + } else if(ot_ == OscillatorType::kSaw) { + smp = ((2 * t) - 1.0) - poly_blep(t, dt); + } else if(ot_ == OscillatorType::kSquare) { + smp = t < 0.5 ? 1.0 : -1.0; + smp += poly_blep(t, dt); + smp -= poly_blep(fmod(t + 0.5, 1.0), dt); + } else if(ot_ == OscillatorType::kTriangle) { + smp = t < 0.5 ? 1.0 : -1.0; + smp += poly_blep(t, dt); + smp -= poly_blep(fmod(t + 0.5, 1.0), dt); + + auto dt2pi = dt * 2 * M_PI; + smp = dt2pi * smp + (1 - dt2pi) * last_smp_; + last_smp_ = smp; + } + + // advance one step + t_ += dt; + while(t_ >= 1.0) { + t_ -= 1.0; + } + + advance_envelope(); + + double const master_volume = 0.5; + return smp * gain * master_volume; +} + +bool TestSynth::Voice::is_alive() const { return state_ != State::kFinished; } + +void TestSynth::Voice::start() +{ + t_ = 0; + last_smp_ = 0; + attack_sample_pos_ = 0; + release_sample_pos_ = 0; + state_ = State::kAttack; +} + +void TestSynth::Voice::force_stop() +{ + state_ = State::kFinished; +} + +void TestSynth::Voice::request_to_stop() +{ + if(state_ != State::kFinished) { + state_ = State::kRelease; + } +} + +void TestSynth::Voice::advance_envelope() +{ + if(state_ == State::kAttack) { + attack_sample_pos_ += 1; + if(attack_sample_pos_ == num_attack_samples_) { + state_ = State::kSustain; + } + } else if(state_ == State::kSustain) { + // do nothing. + } else if(state_ == State::kRelease) { + release_sample_pos_ += 1; + if(release_sample_pos_ == num_release_samples_) { + state_ = State::kFinished; + } + } +} + +void TestSynth::SetSampleRate(double sample_rate) +{ + sample_rate_ = sample_rate; + num_attack_samples_ = std::round(sample_rate * 0.03); + num_release_samples_ = std::round(sample_rate * 0.03); + + for(auto &v: voices_) { v.force_stop(); } + + GetSinTable(); // force generate the sin table. +} + +void TestSynth::SetOscillatorType(OscillatorType ot) +{ + ot_.store(ot); +} + +OscillatorType TestSynth::GetOscillatorType() const +{ + return ot_.load(); +} + +void TestSynth::Process(AudioSample *dest, SampleCount length) +{ + for(int n = 0; n < 128; ++n) { + auto st = keys_[n].load(); + if(st.is_note_on() && voices_[n].is_alive() == false) { + voices_[n] = Voice { + ot_, sample_rate_, note_number_to_freq(n), + num_attack_samples_, num_release_samples_ + }; + voices_[n].start(); + } else if(st.is_note_off()) { + voices_[n].request_to_stop(); + } + + if(voices_[n].is_alive() == false) { continue; } + + auto &v = voices_[n]; + for(int smp = 0; smp < length; ++smp) { + dest[smp] += v.generate(); + } + } +} + +NS_HWM_END diff --git a/src/app/TestSynth.hpp b/src/app/TestSynth.hpp new file mode 100644 index 0000000..b3d021b --- /dev/null +++ b/src/app/TestSynth.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "./NoteStatus.hpp" +#include "./OscillatorType.hpp" + +NS_HWM_BEGIN + +struct TestSynth +{ + struct WaveTable; + using WaveTablePtr = std::shared_ptr; + + //! This function calculates the PolyBLEPs + //! @param t 現在の位相。[0..1]で、[0..2*PI]を表す + //! @param dt 目的のピッチに対応する角速度を正規化周波数で表したもの。 + static + double poly_blep(double t, double dt); + + static + double note_number_to_freq(int n); + + static + WaveTablePtr GenerateSinTable(); + + static + WaveTable const * GetSinTable(); + + struct Voice + { + Voice() = default; + + Voice(OscillatorType ot, + double sample_rate, + double freq, + Int32 num_attack_samples, + Int32 num_release_samples); + + double get_current_gain() const; + + //! generate one sample and advance one step. + AudioSample generate(); + + bool is_alive() const; + void start(); + void force_stop(); + void request_to_stop(); + + private: + Int32 num_attack_samples_ = 0; + Int32 attack_sample_pos_ = 0; + Int32 num_release_samples_ = 0; + Int32 release_sample_pos_ = 0; + enum class State { + kAttack, + kSustain, + kRelease, + kFinished, + }; + State state_ = State::kFinished; + + OscillatorType ot_; + double freq_; + double sample_rate_; + double t_ = 0; + double last_smp_ = 0; + + void advance_envelope(); + }; + + std::array voices_; + using KeyboardStatus = std::array, 128>; + KeyboardStatus keys_; + double sample_rate_ = 0; + std::atomic ot_; + Int32 num_attack_samples_ = 0; + Int32 num_release_samples_ = 0; + + // デバイスダイアログを閉じて、デバイスを開始する前に段階で呼び出す + void SetSampleRate(double sample_rate); + + void SetOscillatorType(OscillatorType ot); + OscillatorType GetOscillatorType() const; + + void Process(AudioSample *dest, SampleCount length); +}; + +NS_HWM_END diff --git a/src/file/Config.cpp b/src/file/Config.cpp index d594c02..5dedac6 100644 --- a/src/file/Config.cpp +++ b/src/file/Config.cpp @@ -55,7 +55,7 @@ void Config::ScanAudioDeviceStatus() std::ostream & operator<<(std::ostream &os, Config const &self) { #define WRITE_MEMBER(name) \ - << (#name + std::string(" = ")) << std::quoted(to_s(self. name ## _)) << "\n" +<< write_line(#name, to_s(self. name ## _)) << "\n" os << "format = " << kConfigFileFormatID_v1 << "\n" @@ -80,7 +80,7 @@ std::istream & operator>>(std::istream &is, Config &self) { is.exceptions(std::ios::badbit); - auto const lines = get_lines(is); + auto const lines = read_lines(is); if(auto val = find_value(lines, "format")) { if(*val != kConfigFileFormatID_v1) { @@ -88,44 +88,26 @@ std::istream & operator>>(std::istream &is, Config &self) } } -#define READ_MEMBER(key, func) \ - if(auto val = find_value(lines, #key)) { self. key ## _ = func(*val); } +#define READ_MEMBER(key) \ +if(auto val = find_value(lines, #key)) { from_s(*val, self. key ## _); } - READ_MEMBER(audio_input_driver_type, [](std::string const &str) { - return to_audio_driver_type(str).value_or(AudioDriverType::kUnknown); - }); + READ_MEMBER(audio_input_driver_type); + READ_MEMBER(audio_input_device_name); + READ_MEMBER(audio_input_channel_count); + READ_MEMBER(audio_output_driver_type); + READ_MEMBER(audio_output_device_name); + READ_MEMBER(audio_output_channel_count); - READ_MEMBER(audio_input_device_name, [](std::string const &str) { - return to_wstr(str); - }); + READ_MEMBER(sample_rate); - READ_MEMBER(audio_input_channel_count, [](std::string const &str) { - return std::max(stoi_or(str, 0), 0); - }); + self.sample_rate_ = Clamp(self.sample_rate_, + kSupportedSampleRateMin, + kSupportedSampleRateMax); - READ_MEMBER(audio_output_driver_type, [](std::string const &str) { - return to_audio_driver_type(str).value_or(AudioDriverType::kUnknown); - }); - - READ_MEMBER(audio_output_device_name, [](std::string const &str) { - return to_wstr(str); - }); - - READ_MEMBER(audio_output_channel_count, [](std::string const &str) { - return std::max(stoi_or(str, 0), 0); - }); - - READ_MEMBER(sample_rate, [](std::string const &str) { - return Clamp(stof_or(str, kSupportedSampleRateDefault), - kSupportedSampleRateMin, - kSupportedSampleRateMax); - }); - - READ_MEMBER(block_size, [](std::string const &str) { - return Clamp(stoi_or(str, kSupportedBlockSizeDefault), - kSupportedBlockSizeMin, - kSupportedBlockSizeMax); - }); + READ_MEMBER(block_size); + self.block_size_ = Clamp(self.block_size_, + kSupportedBlockSizeMin, + kSupportedBlockSizeMax); #undef READ_MEMBER diff --git a/src/file/ProjectFile.cpp b/src/file/ProjectFile.cpp index 0be7043..d435671 100644 --- a/src/file/ProjectFile.cpp +++ b/src/file/ProjectFile.cpp @@ -4,7 +4,7 @@ #include "./Util.hpp" #include "./ProjectFile.hpp" -#include "../App.hpp" +#include "../app/App.hpp" #include "../misc/StringAlgo.hpp" #include "../misc/MathUtil.hpp" #include "../device/AudioDeviceManager.hpp" @@ -40,7 +40,7 @@ void ProjectFile::ScanPluginStatus() vst3_plugin_path_.clear(); vst3_plugin_proc_data_.clear(); vst3_plugin_edit_data_.clear(); - editor_type_ = L""; + editor_type_ = std::nullopt; auto factory = app->GetPluginFactory(); if(!factory) { @@ -67,19 +67,26 @@ void ProjectFile::ScanPluginStatus() if(!editor) { // do nothing. } else { - auto vt = editor->GetViewType(); - if(vt == IPluginEditorFrame::ViewType::kDedicated) { - editor_type_ = L"dedicated"; - } else { - editor_type_ = L"generic"; - } + editor_type_ = editor->GetViewType(); } } +void ProjectFile::ScanAppStatus() +{ + oscillator_type_ = std::nullopt; + audio_output_level_ = 0.0; + is_audio_input_enabled_ = false; + + auto app = App::GetInstance(); + oscillator_type_ = app->GetTestWaveformType(); + audio_output_level_ = app->GetAudioOutputLevel(); + is_audio_input_enabled_ = app->IsAudioInputEnabled(); +} + std::ostream & operator<<(std::ostream &os, ProjectFile const &self) { #define WRITE_MEMBER(name) \ -<< (#name + std::string(" = ")) << std::quoted(to_s(self. name ## _)) << "\n" +<< write_line(#name, to_s(self. name ## _)) << "\n" os << "format = " << kProjectFileFormatID_v1 << "\n" @@ -92,6 +99,9 @@ std::ostream & operator<<(std::ostream &os, ProjectFile const &self) WRITE_MEMBER(editor_type) WRITE_MEMBER(sample_rate) WRITE_MEMBER(block_size) + WRITE_MEMBER(oscillator_type) + WRITE_MEMBER(audio_output_level) + WRITE_MEMBER(is_audio_input_enabled) ; #undef WRITE_MEMBER @@ -103,59 +113,42 @@ std::istream & operator>>(std::istream &is, ProjectFile &self) { is.exceptions(std::ios::badbit); - auto const lines = get_lines(is); + auto const lines = read_lines(is); if(auto val = find_value(lines, "format")) { if(*val != kProjectFileFormatID_v1) { throw Config::FailedToParse("Unknown format."); } } + +#define READ_MEMBER(key) \ +if(auto val = find_value(lines, #key)) { from_s(*val, self. key ## _); } -#define READ_MEMBER(key, func) \ -if(auto val = find_value(lines, #key)) { self. key ## _ = func(*val); } + READ_MEMBER(vst3_plugin_path); + READ_MEMBER(vst3_plugin_cid); + READ_MEMBER(vst3_plugin_proc_data); + READ_MEMBER(vst3_plugin_edit_data); + READ_MEMBER(editor_type); - READ_MEMBER(vst3_plugin_path, [](std::string const &str) { - return to_wstr(str); - }); + READ_MEMBER(sample_rate); + self.sample_rate_ = Clamp(self.sample_rate_, + kSupportedSampleRateMin, + kSupportedSampleRateMax); - READ_MEMBER(vst3_plugin_cid, [](std::string const &str) { - auto buf = base64_decode(str); - - ClassInfo::CID cid; - if(buf.size() == cid.size()) { - std::copy_n(buf.data(), cid.size(), cid.data()); - } - - return cid; - }); - - READ_MEMBER(vst3_plugin_proc_data, [](std::string const &str) { - return std::vector(base64_decode(str)); - }); - - READ_MEMBER(vst3_plugin_edit_data, [](std::string const &str) { - return std::vector(base64_decode(str)); - }); - - READ_MEMBER(editor_type, [](std::string const &str) { - if(str == "generic" || str == "dedicated") { - return to_wstr(str); - } else { - return String(); - } - }); - - READ_MEMBER(sample_rate, [](std::string const &str) { - return Clamp(stof_or(str, kSupportedSampleRateDefault), - kSupportedSampleRateMin, - kSupportedSampleRateMax); - }); - - READ_MEMBER(block_size, [](std::string const &str) { - return Clamp(stoi_or(str, kSupportedBlockSizeDefault), - kSupportedBlockSizeMin, - kSupportedBlockSizeMax); - }); + READ_MEMBER(block_size); + self.block_size_ = Clamp(self.block_size_, + kSupportedBlockSizeMin, + kSupportedBlockSizeMax); + + READ_MEMBER(oscillator_type); + + auto app = App::GetInstance(); + READ_MEMBER(audio_output_level); + self.audio_output_level_ = Clamp(self.audio_output_level_, + app->GetAudioOutputMinLevel(), + app->GetAudioOutputMaxLevel()); + + READ_MEMBER(is_audio_input_enabled); #undef READ_MEMBER diff --git a/src/file/ProjectFile.hpp b/src/file/ProjectFile.hpp index 8cf70f3..0c2028c 100644 --- a/src/file/ProjectFile.hpp +++ b/src/file/ProjectFile.hpp @@ -2,6 +2,8 @@ #include #include "../device/DeviceType.hpp" +#include "../gui/PluginViewType.hpp" +#include "../app/OscillatorType.hpp" NS_HWM_BEGIN @@ -12,15 +14,20 @@ struct ProjectFile ClassInfo::CID vst3_plugin_cid_; std::vector vst3_plugin_proc_data_; std::vector vst3_plugin_edit_data_; - - String editor_type_; // L"generic" or L"dedicated" or L"" + + std::optional editor_type_; double sample_rate_ = kSupportedSampleRateDefault; Int32 block_size_ = kSupportedBlockSizeDefault; + std::optional oscillator_type_; + double audio_output_level_ = 0.0; // as dB. + bool is_audio_input_enabled_ = false; + //! 現在のオーディオデバイスの状態を読み込み void ScanAudioDeviceStatus(); void ScanPluginStatus(); + void ScanAppStatus(); //! ostreamにコンフィグデータを書き出し friend diff --git a/src/file/Util.cpp b/src/file/Util.cpp index 47280cf..e154730 100644 --- a/src/file/Util.cpp +++ b/src/file/Util.cpp @@ -1,8 +1,10 @@ +#define INSTANCIATE_STRING_CONVERSION_FUNCTIONS + #include #include #include "Util.hpp" -#include "../Misc/StringAlgo.hpp" +#include "../misc/StringAlgo.hpp" #include @@ -33,7 +35,7 @@ std::string get_trimmed_line(std::istream &is) return trim(str); } -std::vector get_lines(std::istream &is) +std::vector read_lines(std::istream &is) { std::vector lines; @@ -45,9 +47,10 @@ std::vector get_lines(std::istream &is) return lines; } -std::optional find_value(std::vector const &lines, std::string key) +std::optional find_value(std::vector const &lines, + std::string const &key) { - key = trim(key); + assert(key == trim(key)); std::regex re("^\\s*" + key + "\\s*=\\s*(.*)$"); std::smatch m; for(auto const &line: lines) { @@ -62,6 +65,20 @@ std::optional find_value(std::vector const &lines, std return std::nullopt; } +std::ostream & operator<<(std::ostream &os, write_line_object const &self) +{ + assert(self.key.find(' ') == std::string::npos && + self.key.find('\t') == std::string::npos); + + return os << self.key << " = " << std::quoted(self.value); +} + +write_line_object write_line(std::string const &key, + std::string const &value) +{ + return write_line_object { key, value }; +} + std::string base64_encode(char const *data, size_t length) { if(length == 0) { return {}; } @@ -74,7 +91,7 @@ std::string base64_encode(std::vector const &data) return base64_encode(data.data(), data.size()); } -std::vector base64_decode(std::string const &data) +std::optional> base64_decode(std::string const &data) { size_t error_pos = -1; auto buf = wxBase64Decode(data.data(), data.size(), @@ -83,10 +100,145 @@ std::vector base64_decode(std::string const &data) if(error_pos != -1) { hwm::dout << "Failed to decode base64 data at: " << error_pos << std::endl; - return {}; + return std::nullopt; } return std::vector((char *)buf.GetData(), (char *)buf.GetData() + buf.GetDataLen()); } +template<> +std::string to_s(String const &s) +{ + return to_utf8(s); +} + +template<> +std::string to_s(AudioDriverType const &v) +{ + return to_string(v); +} + +template<> +std::string to_s(ClassInfo::CID const &v) +{ + return base64_encode(v.data(), v.size()); +} + +template<> +std::string to_s(std::vector const &v) +{ + return base64_encode(v); +} + +template<> +std::string to_s(PluginViewType const &v) +{ + switch(v) { + case PluginViewType::kGeneric: + return "generic"; + case PluginViewType::kDedicated: + return "dedicated"; + default: + assert(false && "unsupported"); + return ""; + } +} + +template<> +std::string to_s(OscillatorType const &v) +{ + switch(v) { + case OscillatorType::kSine: + return "sine"; + case OscillatorType::kSaw: + return "saw"; + case OscillatorType::kSquare: + return "square"; + case OscillatorType::kTriangle: + return "triangle"; + default: + assert(false && "unsupported"); + return ""; + } +} + +template<> bool from_s(std::string const &str, String &v) +{ + v = to_wstr(str); + return true; +} + +template<> bool from_s(std::string const &str, AudioDriverType &v) +{ + auto tmp = to_audio_driver_type(str); + if(tmp) { + v = *tmp; + return true; + } else { + return false; + } +} + +template<> bool from_s(std::string const &str, std::vector &v) +{ + auto tmp = base64_decode(str); + if(tmp) { + v = std::move(*tmp); + return true; + } else { + return false; + } +} + +template<> bool from_s(std::string const &str, ClassInfo::CID &v) +{ + std::vector decoded; + if(from_s(str, decoded) == false) { + return false; + } + + if(decoded.size() == v.size()) { + std::copy_n(decoded.begin(), decoded.size(), v.begin()); + return true; + } else { + return false; + } +} + +template<> bool from_s(std::string const &str, PluginViewType &v) +{ + if(str == "generic") { + v = PluginViewType::kGeneric; + return true; + } else if(str == "dedicated") { + v = PluginViewType::kDedicated; + return true; + } else { + return false; + } + + assert(false && "never reach here"); +} + +template<> bool from_s(std::string const &str, OscillatorType &v) +{ + if(str == "sine") { + v = OscillatorType::kSine; + return true; + } else if(str == "saw") { + v = OscillatorType::kSaw; + return true; + } else if(str == "square") { + v = OscillatorType::kSquare; + return true; + } else if(str == "triangle") { + v = OscillatorType::kTriangle; + return true; + } else { + return false; + } + + assert(false && "never reach here"); +} + NS_HWM_END diff --git a/src/file/Util.hpp b/src/file/Util.hpp index 9cf85bf..3f58d5b 100644 --- a/src/file/Util.hpp +++ b/src/file/Util.hpp @@ -6,6 +6,8 @@ #include #include "../device/DeviceType.hpp" #include "../plugin/vst3/Vst3PluginFactory.hpp" +#include "../gui/PluginViewType.hpp" +#include "../app/OscillatorType.hpp" NS_HWM_BEGIN @@ -15,36 +17,77 @@ double stof_or(std::string const &str, double default_value); std::string get_trimmed_line(std::istream &is); -std::vector get_lines(std::istream &is); +std::vector read_lines(std::istream &is); -std::optional find_value(std::vector const &lines, std::string key); +std::optional find_value(std::vector const &lines, std::string const &key); + +struct write_line_object { + std::string const key; + std::string const value; + + friend + std::ostream & operator<<(std::ostream &os, write_line_object const &self); +}; + +write_line_object write_line(std::string const &key, std::string const &value); std::string base64_encode(std::vector const &data); std::string base64_encode(char const *data, size_t length); -std::vector base64_decode(std::string const &data); +std::optional> base64_decode(std::string const &data); template -std::string to_s(T const &s) { +std::string to_s(T const &v) { std::stringstream ss; - ss << s; + ss << v; return ss.str(); } -template<> -inline -std::string to_s(String const &s) { return to_utf8(s); } +template +std::string to_s(std::optional const &v) { + if(v) { + return to_s(*v); + } else { + return ""; + } +} + +template +bool from_s(std::string const &str, T &v) +{ + std::istringstream ss(str); + ss >> v; + return !!ss; +} + +template +bool from_s(std::string const &str, std::optional &v) +{ + T tmp; + if(from_s(str, tmp)) { + v = tmp; + return true; + } else { + return false; + } +} + +#if !defined(INSTANCIATE_STRING_CONVERSION_FUNCTIONS) -template<> -inline -std::string to_s(AudioDriverType const &s) { return to_string(s); } +extern template std::string to_s(String const &s); +extern template std::string to_s(AudioDriverType const &v); +extern template std::string to_s(ClassInfo::CID const &v); +extern template std::string to_s(std::vector const &v); +extern template std::string to_s(PluginViewType const &v); +extern template std::string to_s(OscillatorType const &v); -template<> -inline -std::string to_s(ClassInfo::CID const &s) { return base64_encode(s.data(), s.size()); } +extern template bool from_s(std::string const &str, String &s); +extern template bool from_s(std::string const &str, AudioDriverType &v); +extern template bool from_s(std::string const &str, std::vector &v); +extern template bool from_s(std::string const &str, ClassInfo::CID &v); +extern template bool from_s(std::string const &str, PluginViewType &v); +extern template bool from_s(std::string const &str, OscillatorType &v); -template<> -inline -std::string to_s>(std::vector const &s) { return base64_encode(s); } +#endif NS_HWM_END diff --git a/src/gui/Gui.cpp b/src/gui/Gui.cpp index ccb1d8f..bd8f274 100644 --- a/src/gui/Gui.cpp +++ b/src/gui/Gui.cpp @@ -1,5 +1,5 @@ #include "Gui.hpp" -#include "../App.hpp" +#include "../app/App.hpp" #include #include @@ -100,6 +100,11 @@ class HeaderPanel { btn_enable_input_->SetValue(enabled); } + + void OnAudioOutputLevelChanged(double new_level) override + { + sl_volume_->SetValue(new_level * kVolumeSliderScale); + } }; class MainWindow @@ -551,19 +556,19 @@ class MainFrame }, kID_Playback_EnableAudioInputs); Bind(wxEVT_COMMAND_MENU_SELECTED, [](auto &ev) { - App::GetInstance()->SetTestWaveformType(App::TestWaveformType::kSine); + App::GetInstance()->SetTestWaveformType(OscillatorType::kSine); }, kID_Playback_Waveform_Sine); Bind(wxEVT_COMMAND_MENU_SELECTED, [](auto &ev) { - App::GetInstance()->SetTestWaveformType(App::TestWaveformType::kSaw); + App::GetInstance()->SetTestWaveformType(OscillatorType::kSaw); }, kID_Playback_Waveform_Saw); Bind(wxEVT_COMMAND_MENU_SELECTED, [](auto &ev) { - App::GetInstance()->SetTestWaveformType(App::TestWaveformType::kSquare); + App::GetInstance()->SetTestWaveformType(OscillatorType::kSquare); }, kID_Playback_Waveform_Square); Bind(wxEVT_COMMAND_MENU_SELECTED, [](auto &ev) { - App::GetInstance()->SetTestWaveformType(App::TestWaveformType::kTriangle); + App::GetInstance()->SetTestWaveformType(OscillatorType::kTriangle); }, kID_Playback_Waveform_Triangle); Bind(wxEVT_COMMAND_MENU_SELECTED, [](auto &ev) { @@ -575,6 +580,22 @@ class MainFrame OnOpenEditor(); }, kID_View_PluginEditor); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent &ev) { + auto const app = App::GetInstance(); + auto const wt = app->GetTestWaveformType(); + + if( (ev.GetId() == kID_Playback_Waveform_Sine && wt == OscillatorType::kSine) || + (ev.GetId() == kID_Playback_Waveform_Saw && wt == OscillatorType::kSaw) || + (ev.GetId() == kID_Playback_Waveform_Square && wt == OscillatorType::kSquare) || + (ev.GetId() == kID_Playback_Waveform_Triangle && wt == OscillatorType::kTriangle) + ) + { + ev.Check(true); + } else { + ev.Check(false); + } + }, kID_Playback_Waveform_Sine, kID_Playback_Waveform_Triangle); + Bind(wxEVT_UPDATE_UI, [this](auto &ev) { ev.Enable(wnd_->CanOpenEditor()); }, kID_View_PluginEditor); diff --git a/src/gui/Keyboard.cpp b/src/gui/Keyboard.cpp index f56147d..c0db4b9 100644 --- a/src/gui/Keyboard.cpp +++ b/src/gui/Keyboard.cpp @@ -2,7 +2,7 @@ #include -#include "../App.hpp" +#include "../app/App.hpp" #include "../resource/ResourceHelper.hpp" #include "./Util.hpp" #include "../misc/MathUtil.hpp" diff --git a/src/gui/PCKeyboardInput.cpp b/src/gui/PCKeyboardInput.cpp index 575ff3f..49a8d54 100644 --- a/src/gui/PCKeyboardInput.cpp +++ b/src/gui/PCKeyboardInput.cpp @@ -1,6 +1,6 @@ #include "PCKeyboardInput.hpp" -#include "../App.hpp" +#include "../app/App.hpp" NS_HWM_BEGIN diff --git a/src/gui/PluginEditor.cpp b/src/gui/PluginEditor.cpp index 69a4a41..b6131a1 100644 --- a/src/gui/PluginEditor.cpp +++ b/src/gui/PluginEditor.cpp @@ -1,6 +1,6 @@ #include "PluginEditor.hpp" -#include "../App.hpp" +#include "../app/App.hpp" #include "./UnitData.hpp" #include "./PCKeyboardInput.hpp" #include @@ -417,7 +417,7 @@ class PluginEditorControl IListenerService & GetListeners() { return listeners_; } - using VT = IPluginEditorFrame::ViewType; + using VT = PluginViewType; //! 現在のViewTypeを返す VT GetViewType() const { @@ -733,13 +733,13 @@ class PluginEditorFrame } //! 現在のViewTypeを返す - ViewType GetViewType() const override + PluginViewType GetViewType() const override { return control_->GetViewType(); } //! ViewTypeの切り替えに失敗した場合はfalseを返す - bool SetViewType(ViewType type) override + bool SetViewType(PluginViewType type) override { return control_->SetViewType(type); } diff --git a/src/gui/PluginEditor.hpp b/src/gui/PluginEditor.hpp index 03ecdef..2156ef6 100644 --- a/src/gui/PluginEditor.hpp +++ b/src/gui/PluginEditor.hpp @@ -2,6 +2,7 @@ #include #include "../plugin/vst3/Vst3Plugin.hpp" +#include "./PluginViewType.hpp" NS_HWM_BEGIN @@ -24,21 +25,16 @@ struct IPluginEditorFrame template IPluginEditorFrame(Args&&...); - enum class ViewType { - kGeneric, - kDedicated, - }; - //! 現在のViewTypeを返す virtual - ViewType GetViewType() const = 0; + PluginViewType GetViewType() const = 0; //! プラグインのViewTypeを切り替える。 /*! @return ViewTypeの切り替えに失敗した場合はfalseを返す。 * 切り替えに成功したり、すでに目的のViewTypeになっている場合はtrueを返す。 */ virtual - bool SetViewType(ViewType type) = 0; + bool SetViewType(PluginViewType type) = 0; virtual void OnResizePlugView() = 0; diff --git a/src/gui/PluginViewType.hpp b/src/gui/PluginViewType.hpp new file mode 100644 index 0000000..55ab788 --- /dev/null +++ b/src/gui/PluginViewType.hpp @@ -0,0 +1,10 @@ +#pragma once + +NS_HWM_BEGIN + +enum class PluginViewType { + kGeneric, + kDedicated, +}; + +NS_HWM_END