/
reds
/
OpenDAW
Обзор
Документация
Войти
/
reds
/
OpenDAW
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/dsp/MidiSynthesizer.cpp
252 строки
7 KB
OpenDAW Developer
feat(v0.5.0): Add WAV Playback and MIDI Synthesizer
21 фев 2026, 03:00
21 фев 2026, 03:00
95e7c6e
Код
Авторство
О чём код?
#include <opendaw/dsp/MidiSynthesizer.h> #include <cmath> #include <algorithm> namespace opendaw { // ============================================================================ // MidiVoice Implementation // ============================================================================ MidiVoice::MidiVoice() = default; void MidiVoice::startNote(int noteNumber, int velocity, float sampleRate) { noteNumber_ = noteNumber; velocity_ = velocity; sampleRate_ = sampleRate; isActive_ = true; phase_ = 0.0f; frequency_ = getFrequency(noteNumber); state_ = State::Attack; stateLevel_ = 0.0f; samplesInState_ = 0; } void MidiVoice::stopNote() { if (!isActive_) return; state_ = State::Release; stateLevel_ = getEnvelopeLevel(); samplesInState_ = 0; } void MidiVoice::render(float* output, int numSamples) { if (!isActive_) { std::fill(output, output + numSamples, 0.0f); return; } for (int i = 0; i < numSamples; ++i) { // Update envelope float envelopeLevel = getEnvelopeLevel(); // Generate sample float sample = getSampleValue(phase_, oscillatorType_); sample *= envelopeLevel; sample *= (velocity_ / 127.0f); output[i] = sample; // Update phase phase_ += frequency_ / sampleRate_; if (phase_ >= 1.0f) { phase_ -= 1.0f; } // Check if voice should be deactivated if (state_ == State::Release && envelopeLevel < 0.001f) { isActive_ = false; state_ = State::Idle; } } } float MidiVoice::getFrequency(int noteNumber) const { // A4 = 440Hz = MIDI note 69 return 440.0f * std::pow(2.0f, (noteNumber - 69) / 12.0f); } float MidiVoice::getSampleValue(float phase, OscillatorType type) const { switch (type) { case OscillatorType::Sine: return std::sin(2.0f * 3.14159265359f * phase); case OscillatorType::Square: return (phase < 0.5f) ? 1.0f : -1.0f; case OscillatorType::Saw: return 2.0f * phase - 1.0f; case OscillatorType::Triangle: return 4.0f * std::abs(phase - 0.5f) - 1.0f; default: return 0.0f; } } float MidiVoice::getEnvelopeLevel() const { int samplesInState = samplesInState_; switch (state_) { case State::Attack: { int attackSamples = static_cast<int>(envelope_.attack * sampleRate_); if (attackSamples == 0) attackSamples = 1; float level = static_cast<float>(samplesInState) / attackSamples; const_cast<MidiVoice*>(this)->samplesInState_++; if (level >= 1.0f) { const_cast<MidiVoice*>(this)->state_ = State::Decay; const_cast<MidiVoice*>(this)->samplesInState_ = 0; return 1.0f; } return level; } case State::Decay: { int decaySamples = static_cast<int>(envelope_.decay * sampleRate_); if (decaySamples == 0) decaySamples = 1; float targetLevel = envelope_.sustain; float delta = (1.0f - targetLevel) / decaySamples; float level = 1.0f - (delta * samplesInState_); const_cast<MidiVoice*>(this)->samplesInState_++; if (level <= targetLevel) { const_cast<MidiVoice*>(this)->state_ = State::Sustain; const_cast<MidiVoice*>(this)->samplesInState_ = 0; return targetLevel; } return level; } case State::Sustain: return envelope_.sustain; case State::Release: { int releaseSamples = static_cast<int>(envelope_.release * sampleRate_); if (releaseSamples == 0) releaseSamples = 1; float delta = stateLevel_ / releaseSamples; float level = stateLevel_ - (delta * samplesInState_); const_cast<MidiVoice*>(this)->samplesInState_++; return std::max(0.0f, level); } default: return 0.0f; } } // ============================================================================ // MidiSynthesizer Implementation // ============================================================================ MidiSynthesizer::MidiSynthesizer() { // Create 16 voices for polyphony for (int i = 0; i < 16; ++i) { voices_.push_back(std::make_unique<MidiVoice>()); voices_.back()->setOscillatorType(oscillatorType_); voices_.back()->setEnvelope(envelope_); } } void MidiSynthesizer::prepareToPlay(double sampleRate, int blockSize) { sampleRate_ = sampleRate; blockSize_ = blockSize; for (auto& voice : voices_) { voice->setEnvelope(envelope_); } } void MidiSynthesizer::processBlock(AudioBuffer<float>& buffer, const MidiBuffer& midi) { buffer.clear(); // Process MIDI events for (const auto& event : midi.getEvents()) { if (event.isNoteOn() && event.getVelocity() > 0) { // Note on - find free voice MidiVoice* voice = findFreeVoice(); if (voice) { voice->startNote(event.getNoteNumber(), event.getVelocity(), static_cast<float>(sampleRate_)); voice->setOscillatorType(oscillatorType_); } } else if (event.isNoteOff() || (event.isNoteOn() && event.getVelocity() == 0)) { // Note off MidiVoice* voice = findVoiceForNote(event.getNoteNumber()); if (voice) { voice->stopNote(); } } } // Render all active voices for (int ch = 0; ch < buffer.getNumChannels(); ++ch) { float* channelData = buffer.getChannel(ch); std::vector<float> voiceBuffer(buffer.getNumSamples(), 0.0f); for (auto& voice : voices_) { if (voice->isActive()) { voice->render(voiceBuffer.data(), buffer.getNumSamples()); // Mix voice into channel for (int i = 0; i < buffer.getNumSamples(); ++i) { channelData[i] += voiceBuffer[i] * volume_; } } } } } void MidiSynthesizer::reset() { allNotesOff(); } void MidiSynthesizer::allNotesOff() { for (auto& voice : voices_) { voice->stopNote(); } } void MidiSynthesizer::setOscillatorType(OscillatorType type) { oscillatorType_ = type; for (auto& voice : voices_) { voice->setOscillatorType(type); } } void MidiSynthesizer::setEnvelope(const ADSREnvelope& env) { envelope_ = env; for (auto& voice : voices_) { voice->setEnvelope(env); } } MidiVoice* MidiSynthesizer::findFreeVoice() { // Find inactive voice for (auto& voice : voices_) { if (!voice->isActive()) { return voice.get(); } } // All voices active, steal oldest return voices_[0].get(); } MidiVoice* MidiSynthesizer::findVoiceForNote(int noteNumber) { for (auto& voice : voices_) { if (voice->isActive() && voice->getNoteNumber() == noteNumber) { return voice.get(); } } return nullptr; } } // namespace opendaw