/* * DISTRHO Kars Plugin, based on karplong by Chris Cannam. * Copyright (C) 2015-2019 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this * permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "DistrhoPluginKars.hpp" START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- DistrhoPluginKars::DistrhoPluginKars() : Plugin(paramCount, 0, 0), // 0 programs, 0 states fSustain(false), fRelease(0.01), fVolume(75.0f), fSampleRate(getSampleRate()), fBlockStart(0) { for (int i=kMaxNotes; --i >= 0;) { fNotes[i].voice = i; fNotes[i].setSampleRate(fSampleRate); } } // ----------------------------------------------------------------------- // Init void DistrhoPluginKars::initParameter(uint32_t index, Parameter& parameter) { switch (index) { case paramSustain: parameter.hints = kParameterIsAutomable|kParameterIsBoolean; parameter.name = "Sustain"; parameter.symbol = "sustain"; parameter.ranges.def = 0.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 1.0f; break; case paramRelease: parameter.hints = kParameterIsAutomable; parameter.name = "Release"; parameter.symbol = "release"; parameter.unit = "s"; parameter.ranges.def = 0.01f; parameter.ranges.min = 0.0f; parameter.ranges.max = 5.0f; break; case paramVolume: parameter.hints = kParameterIsAutomable; parameter.name = "Volume"; parameter.symbol = "volume"; parameter.unit = "%"; parameter.ranges.def = 75.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 100.0f; break; } } // ----------------------------------------------------------------------- // Internal data float DistrhoPluginKars::getParameterValue(uint32_t index) const { switch (index) { case paramSustain: return fSustain ? 1.0f : 0.0f; case paramRelease: return fRelease; case paramVolume: return fVolume; } return 0.0f; } void DistrhoPluginKars::setParameterValue(uint32_t index, float value) { switch (index) { case paramSustain: fSustain = value > 0.5f; break; case paramRelease: fRelease = value; break; case paramVolume: fVolume = value; break; } } // ----------------------------------------------------------------------- // Process void DistrhoPluginKars::activate() { fBlockStart = 0; for (int i=kMaxNotes; --i >= 0;) { fNotes[i].on = kNoteNull; fNotes[i].off = kNoteNull; fNotes[i].velocity = 0; } } /** Handy class to help keep audio buffer in sync with incoming MIDI events. To use it, create a local variable (on the stack) and call nextEvent() until it returns false. @code for (AudioMidiSyncHelper amsh(outputs, frames, midiEvents, midiEventCount); amsh.nextEvent();) { float* const outL = amsh.outputs[0]; float* const outR = amsh.outputs[1]; for (uint32_t i=0; i(midiEvents); for (uint32_t i=0; i < midiEventCount; ++i) rwEvents[i].frame -= totalFramesUsed; } frames = remainingFrames - firstEventFrame; remainingFrames -= frames; remainingMidiEventCount -= midiEventCount; totalFramesUsed += frames; return true; } private: /** @internal */ uint32_t remainingFrames; uint32_t remainingMidiEventCount; uint32_t totalFramesUsed; }; void DistrhoPluginKars::run(const float**, float** outputs, uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount) { uint8_t note, velo; std::memset(outputs[0], 0, sizeof(float)*frames); for (AudioMidiSyncHelper amsh(outputs, frames, midiEvents, midiEventCount); amsh.nextEvent();) { for (uint32_t i=0; i MidiEvent::kDataSize) continue; const uint8_t* data = amsh.midiEvents[i].data; const uint8_t status = data[0] & 0xF0; switch (status) { case 0x90: note = data[1]; velo = data[2]; DISTRHO_SAFE_ASSERT_BREAK(note < 128); // kMaxNotes if (velo > 0) { fNotes[note].on = fBlockStart + amsh.midiEvents[i].frame; fNotes[note].off = kNoteNull; fNotes[note].velocity = velo; break; } // fall through case 0x80: note = data[1]; DISTRHO_SAFE_ASSERT_BREAK(note < 128); // kMaxNotes fNotes[note].off = fBlockStart + amsh.midiEvents[i].frame; break; } } float* const out = amsh.outputs[0]; for (int i=kMaxNotes; --i >= 0;) { if (fNotes[i].on != kNoteNull) addSamples(out, i, amsh.frames); } fBlockStart += amsh.frames; } } void DistrhoPluginKars::addSamples(float* out, int voice, uint32_t frames) { const uint32_t start = fBlockStart; Note& note(fNotes[voice]); if (start < note.on) return; if (start == note.on) { for (int i=note.sizei; --i >= 0;) note.wavetable[i] = (float(rand()) / float(RAND_MAX)) * 2.0f - 1.0f; } const float vgain = float(note.velocity) / 127.0f; bool decay; float gain, sample; uint32_t index, size; for (uint32_t i=0, s=start-note.on; i index) { note.on = kNoteNull; break; } gain = gain * float(index - size) / float(index); } size = uint32_t(note.sizei); decay = s > size; index = s % size; sample = note.wavetable[index]; if (decay) { if (index == 0) sample += note.wavetable[size-1]; else sample += note.wavetable[index-1]; note.wavetable[index] = sample/2; } out[i] += gain * sample * (fVolume / 100.0f); } } // ----------------------------------------------------------------------- Plugin* createPlugin() { return new DistrhoPluginKars(); } // ----------------------------------------------------------------------- END_NAMESPACE_DISTRHO