diff --git a/cmake/CodeQualityUtils.cmake b/cmake/CodeQualityUtils.cmake index 78f79734d..9135786eb 100644 --- a/cmake/CodeQualityUtils.cmake +++ b/cmake/CodeQualityUtils.cmake @@ -1,25 +1,25 @@ # These tools are run (for now) only if BUILD_TESTING is ON # Another useful tool could be clazy compiler too # Run cppcheck on the code find_program(CPPCHECK_EXE NAMES cppcheck) if(CPPCHECK_EXE) set(CMAKE_CXX_CPPCHECK ${CPPCHECK_EXE}) list( APPEND CMAKE_CXX_CPPCHECK "--enable=all" "--inconclusive" "--force" "--inline-suppr" ) endif() # Run clang-tidy find_program(CLANG_TIDY_EXE NAMES clang-tidy) if(CLANG_TIDY_EXE) set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE}) list( APPEND CMAKE_CXX_CLANG_TIDY - "-checks=*,-fuchsia*,-google*,-hicpp*,-llvm*,-cppcoreguidelines-*,-modernize-use-auto,-readability-braces-around-statements,-readability-static-accessed-through-instance" + "-checks=*,-fuchsia*,-google*,-hicpp*,-llvm*,-cppcoreguidelines-*,-modernize-use-auto,-readability-braces-around-statements,-readability-static-accessed-through-instance,-readability-magic-numbers" ) endif() diff --git a/src/core/synth/generator.cpp b/src/core/synth/generator.cpp index 70132f527..f79ddd990 100644 --- a/src/core/synth/generator.cpp +++ b/src/core/synth/generator.cpp @@ -1,268 +1,269 @@ /* miniSynth - A Simple Software Synthesizer Copyright (C) 2015 Ville Räisänen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "generator.h" #include Generator::Generator(const QAudioFormat &_format, QObject *parent) : QIODevice(parent), format(_format) { linSyn = new LinearSynthesis(Waveform::MODE_SIN); curtime = 0; defaultEnv.attackTime = 100; defaultEnv.decayTime = 400; defaultEnv.releaseTime = 100; defaultEnv.initialAmpl = 0; defaultEnv.peakAmpl = 1; defaultEnv.sustainAmpl = 0.8; mod_waveform = new Waveform(Waveform::MODE_SIN); synthData = new qreal[maxUsedBytes]; for (unsigned int indMaxRead = 0; indMaxRead < maxUsedBytes; indMaxRead++) { synthData[indMaxRead] = 0; } } Generator::~Generator() { delete linSyn; delete [] synthData; delete mod_waveform; } void Generator::start() { open(QIODevice::ReadOnly); } void Generator::stop() { close(); } void Generator::addWave(unsigned char note, unsigned char vel) { Wave wav; wav.state = wav.STATE_ATTACK; wav.note = note; wav.vel = vel; wav.state_age = 0; wav.age = 0; wav.env = defaultEnv; m_lock.lock(); waveList.push_back(wav); m_lock.unlock(); } qint64 Generator::readData(char *data, qint64 len) { // QAudioOutput tends to ask large packets of data, which can lead to a // large delay between noteOn requests and the generation of audio. Thus, // in order to provide more responsive interface, the packet size is // limited to 2048 bytes ~ 1024 samples. if (len > maxUsedBytes) len = maxUsedBytes; generateData(len); memcpy(data, m_buffer.constData(), len); curtime += (qreal)len/(m_samplingRate*2); return len; } // Not used. qint64 Generator::writeData(const char *data, qint64 len) { Q_UNUSED(data); Q_UNUSED(len); return 0; } // Doesn't seem to be called by QAudioOutput. qint64 Generator::bytesAvailable() const { return m_buffer.size() + QIODevice::bytesAvailable(); } void Generator::noteOn(unsigned char chan, unsigned char note, unsigned char vel) { // Velocity of 255 is assumed since a "pleasant" relationship between the // velocity in the MIDI event and the parameters of the corresponding Wave // cannot be currently selected by the user. if (vel > 0) vel = 255; addWave(note, vel); Q_UNUSED(chan); } void Generator::noteOff(unsigned char chan, unsigned char note) { QMutableListIterator i(waveList); while (i.hasNext()) { Wave wav = i.next(); if (wav.note == note && wav.state != Wave::STATE_RELEASE) { // To avoid discontinuity in the envelope, the initial value for // the release part of the envelope should be equal to current // value. wav.env.sustainAmpl = wav.env.eval(wav.state_age, wav.state); wav.state = Wave::STATE_RELEASE; wav.state_age = 0; } i.setValue(wav); } Q_UNUSED(chan); } void -Generator::setMode(int _mode) { +Generator::setMode(unsigned int _mode) { delete linSyn; linSyn = new LinearSynthesis(_mode); curtime = 0; } void Generator::setTimbre(QVector &litudes, QVector &phases) { linSyn->setTimbre(amplitudes, phases); } void Generator::generateData(qint64 len) { unsigned int numSamples = len/2; m_buffer.resize(len); // Raw synthesized data is assembled into synthData. memset(synthData, 0, numSamples*sizeof(qreal)); // All samples for each active note in waveList are synthesized separately. m_lock.lock(); QMutableListIterator i(waveList); while (i.hasNext()) { Wave wav = i.next(); - qreal attackTime = 0.001*(qreal)wav.env.attackTime, - releaseTime = 0.001*(qreal)wav.env.releaseTime; + qreal attackTime = 0.001*(qreal)wav.env.attackTime; + qreal releaseTime = 0.001*(qreal)wav.env.releaseTime; qreal freq = 8.175 * 0.5 * qPow(2, ((qreal)wav.note)/12); qreal ampl = 0.5*((qreal)wav.vel)/256; - qreal stateAge = wav.state_age, - wavAge = wav.age; + qreal stateAge = wav.state_age; + qreal wavAge = wav.age; const qreal step = 1.f / m_samplingRate; qreal samplePerStep = 0.f; for (unsigned int sample = 0; sample < numSamples; sample++, samplePerStep += step) { qreal t = curtime + samplePerStep; qreal envt = stateAge + samplePerStep; qreal modt = wavAge + samplePerStep; // Handle timed change of state in the ADSR-envelopes ATTACK->DECAY // and RELEASE->OFF. switch(wav.state) { case ADSREnvelope::STATE_ATTACK: if (envt > attackTime) { stateAge -= attackTime; wav.state = ADSREnvelope::STATE_DECAY; wav.state_age -= attackTime; envt = stateAge + samplePerStep; } break; case ADSREnvelope::STATE_RELEASE: if (envt > releaseTime) { stateAge = 0; wav.state = ADSREnvelope::STATE_OFF; } break; } if (wav.state == ADSREnvelope::STATE_OFF) { break; } else { - qreal freqmod = 0, amod = 0; + qreal freqmod = 0; + qreal amod = 0; // Compute modulation waves. if (mod.FM_freq > 0) { qreal envVal = mod.useEnvelope ? wav.env.eval(envt, wav.state) : 1; if (mod.propFreq) { freqmod = mod.FM_ampl * envVal* mod_waveform->eval(2*M_PI*mod.FM_freq*freq*modt); } else { freqmod = mod.FM_ampl * mod_waveform->eval(2*M_PI*mod.FM_freq*modt); } } if (mod.AM_freq > 0) { amod = (1 - qExp(-modt/mod.AM_time))*mod.AM_ampl * mod_waveform->eval(2*M_PI*mod.AM_freq*t); } // Evaluate the output wave for the current note and add to the // output obtained with other notes. qreal envVal = wav.env.eval(envt, wav.state); qreal newVal = envVal * (ampl + amod) * 0.5 * linSyn->evalTimbre(2*M_PI*(freq+freqmod)*(modt+100)); synthData[sample] += newVal; } } wav.age += (qreal)numSamples/m_samplingRate; if (wav.state != ADSREnvelope::STATE_OFF) { wav.state_age += (qreal)numSamples/m_samplingRate; i.setValue(wav); } else { i.remove(); } } m_lock.unlock(); // Convert data from qreal to qint16. const int channelBytes = format.sampleSize() / 8; unsigned char *ptr = reinterpret_cast(m_buffer.data()); for (unsigned int sample = 0; sample < numSamples; sample++) { if (synthData[sample] > 1) synthData[sample] = 1; if (synthData[sample] < -1) synthData[sample] = -1; qint16 value = static_cast(synthData[sample] * 32767); qToLittleEndian(value, ptr); ptr += channelBytes; } } void Generator::setEnvelope(ADSREnvelope &env) { defaultEnv = env; } void Generator::setModulation(Modulation &modulation) { if (modulation.mode != mod_waveform->mode) { delete mod_waveform; mod_waveform = new Waveform(modulation.mode); } mod = modulation; } void Generator::setPreset(Preset &preset) { setModulation(preset.mod); setMode(preset.waveformMode); setTimbre(preset.timbreAmplitudes, preset.timbrePhases); setEnvelope(preset.env); } diff --git a/src/core/synth/generator.h b/src/core/synth/generator.h index 3f6bf21f6..7f7e1001d 100644 --- a/src/core/synth/generator.h +++ b/src/core/synth/generator.h @@ -1,103 +1,103 @@ /* miniSynth - A Simple Software Synthesizer Copyright (C) 2015 Ville Räisänen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef GENERATOR_H #define GENERATOR_H #include #include #include #include #include #include #include #include "linearSynthesis.h" #include "modulation.h" #include "ADSRenvelope.h" #include "preset.h" // The state of each active note is described with an Wave object. Wave // objects are assembled into the QList waveList and removed once // they reach the state STATE_OFF. class Wave { public: enum {STATE_OFF, STATE_ATTACK, STATE_DECAY, STATE_RELEASE}; unsigned char note, vel, state; qreal state_age, age; ADSREnvelope env; }; // The synthesizer is implemented as a QIODevice and is connected to // a QAudioOutput in mainWindow.cpp. QAudioOutput reads data from the // synthersizer using the function readData(data, size). readData // returns maximum of 2048 samples generated with generateData(len). class Generator : public QIODevice { Q_OBJECT public: explicit Generator(const QAudioFormat &_format, QObject *parent = 0); ~Generator(); void start (); void stop (); void setState (); void addWave (unsigned char note, unsigned char vel); qint64 readData(char *data, qint64 len); qint64 writeData(const char *data, qint64 len); qint64 bytesAvailable() const; void generateData(qint64 len); public slots: void noteOn (unsigned char chan, unsigned char note, unsigned char vel); void noteOff (unsigned char chan, unsigned char note); // Slots for manipulation of the current patch. - void setMode (int _mode); + void setMode (unsigned int _mode); void setTimbre (QVector &litudes, QVector &phases); void setEnvelope (ADSREnvelope &env); void setModulation(Modulation &modulation); void setPreset (Preset &preset); private: QAudioFormat format; QByteArray m_buffer; // State of the synthesizer qreal curtime; QList waveList; // Parameters of the current patch LinearSynthesis *linSyn; ADSREnvelope defaultEnv; Modulation mod; Waveform *mod_waveform; static const int m_samplingRate = 22050; static const int maxUsedBytes = 2048; qreal *synthData; QMutex m_lock; }; #endif // GENERATOR_H diff --git a/src/core/synth/linearSynthesis.cpp b/src/core/synth/linearSynthesis.cpp index c5796b5ab..4c9c621a9 100644 --- a/src/core/synth/linearSynthesis.cpp +++ b/src/core/synth/linearSynthesis.cpp @@ -1,68 +1,68 @@ /* miniSynth - A Simple Software Synthesizer Copyright (C) 2015 Ville Räisänen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "linearSynthesis.h" #include LinearSynthesis::LinearSynthesis(unsigned int mode, unsigned int size) : Waveform(mode, size) { numHarmonics = 16; timbreAmplitudes = new int[numHarmonics]; timbrePhases = new int[numHarmonics]; timbreAmplitudes[0] = 100; } void LinearSynthesis::setTimbre(QVector &litudes, QVector &phases) { Q_ASSERT(amplitudes.size() == phases.size()); delete[] timbreAmplitudes; delete[] timbrePhases; numHarmonics = amplitudes.size(); timbreAmplitudes = new int[numHarmonics]; timbrePhases = new int[numHarmonics]; for(int i = 0 ; i < numHarmonics ; ++ i) { timbreAmplitudes[i] = amplitudes[i]; timbrePhases[i] = phases[i]; } } LinearSynthesis::~LinearSynthesis() { delete[] timbreAmplitudes; delete[] timbrePhases; } qreal LinearSynthesis::evalTimbre(qreal t) { qreal val = 0; for (unsigned int harm = 0; harm < numHarmonics; harm++) { - int qa_int = timbreAmplitudes[harm], - qp_int = timbrePhases[harm]; + int qa_int = timbreAmplitudes[harm]; + int qp_int = timbrePhases[harm]; if (qa_int > 0) { - qreal qa = (qreal)qa_int/100, - qp = (2*M_PI*(qreal)qp_int)/360; + qreal qa = (qreal)qa_int/100; + qreal qp = (2*M_PI*(qreal)qp_int)/360; val += qa * eval(((qreal)harm + 1) * t - qp); } } return val; } diff --git a/src/core/synth/waveform.cpp b/src/core/synth/waveform.cpp index 993e69a55..4c91f0bc5 100644 --- a/src/core/synth/waveform.cpp +++ b/src/core/synth/waveform.cpp @@ -1,126 +1,127 @@ /* miniSynth - A Simple Software Synthesizer Copyright (C) 2015 Ville Räisänen This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "waveform.h" #include #include Waveform::Waveform(unsigned int mode, unsigned int size) { waveTable = new qreal[size]; tableSize = size; this->mode = mode; for (unsigned int sample = 0; sample < tableSize; sample++) { qreal u = (2*M_PI * (qreal)sample) / ((qreal)tableSize); switch(mode) { case MODE_SIN: waveTable[sample] = waveSin(u); break; case MODE_SAW: waveTable[sample] = waveSaw(u); break; case MODE_SAW2: waveTable[sample] = waveSaw2(u); break; case MODE_SQU: waveTable[sample] = waveSqu(u); break; } } } Waveform::~Waveform() { delete [] waveTable; waveTable = nullptr; } qreal Waveform::waveSin(qreal t) { return qSin(t); } qreal Waveform::waveSaw(qreal t) { qreal tmod = (qreal)(fmod((double)t, 2*M_PI) - M_PI); return tmod / M_PI; } qreal Waveform::waveSaw2(qreal t) { qreal tmod = (qreal)(fmod((double)t, 2*M_PI) - M_PI); return 1 - 2 * qAbs(tmod) / M_PI; } qreal Waveform::waveSqu(qreal t) { qreal tmod = (qreal)fmod((double)t, 2*M_PI); if (tmod < M_PI) { return 1; } return -1; } qreal Waveform::eval(qreal t) { qreal tmod = fmod((double)t, 2*M_PI); if (tmod < 0) tmod += 2*M_PI; // Position indexed by a continuous variable does not generally fall on // integer-valued points. Here indF is the "continuous-valued position" // of the argument and is somewhere between the integers qFloor(indF) and // qCeil(indF). When indF is not an integer, we use linear interpolation // to obtain the appropriate value. qreal indF = ((qreal)tableSize) * tmod / (2*(qreal)M_PI); if (indF == (qreal)tableSize) indF = 0; Q_ASSERT(indF >= 0); Q_ASSERT(indF < (qreal)tableSize); unsigned int ind_min = (unsigned int) qFloor(indF); unsigned int ind_max = (unsigned int) qCeil(indF); Q_ASSERT(ind_min <= ind_max); Q_ASSERT(ind_max <= tableSize); Q_ASSERT(ind_min < tableSize); qreal indmod = indF - (qreal)ind_min; Q_ASSERT(indmod < 1 && indmod >= 0); - qreal value_next, value_prev; + qreal value_next; + qreal value_prev; if (ind_min == ind_max) { return waveTable[ind_min]; } if (ind_max == tableSize) { value_prev = waveTable[ind_min]; value_next = waveTable[0]; return indmod * value_next + (1-indmod) * value_prev; } if (ind_min < ind_max) { Q_ASSERT(ind_max < tableSize); value_prev = waveTable[ind_min]; value_next = waveTable[ind_max]; return indmod * value_next + (1-indmod) * value_prev; } // This shouldn't be reached; qCritical("Wave Table Interpolation Failed"); QCoreApplication::exit(-1); return 0; }