File indexing completed on 2024-05-12 03:52:14

0001 /****************************************************************************
0002 **
0003 ** Copyright (C) 2016 by Sandro S. Andrade <sandroandrade@kde.org>
0004 **
0005 ** This program is free software; you can redistribute it and/or
0006 ** modify it under the terms of the GNU General Public License as
0007 ** published by the Free Software Foundation; either version 2 of
0008 ** the License or (at your option) version 3 or any later version
0009 ** accepted by the membership of KDE e.V. (or its successor approved
0010 ** by the membership of KDE e.V.), which shall act as a proxy
0011 ** defined in Section 14 of version 3 of the license.
0012 **
0013 ** This program is distributed in the hope that it will be useful,
0014 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
0015 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0016 ** GNU General Public License for more details.
0017 **
0018 ** You should have received a copy of the GNU General Public License
0019 ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
0020 **
0021 ****************************************************************************/
0022 
0023 #include "fluidsynthsoundcontroller.h"
0024 
0025 #include <QDebug>
0026 #include <QDir>
0027 #include <QFile>
0028 #include <QJsonObject>
0029 #include <QStandardPaths>
0030 #include <QtMath>
0031 
0032 #include <utils/xdgdatadirs.h>
0033 
0034 unsigned int FluidSynthSoundController::m_initialTime = 0;
0035 
0036 FluidSynthSoundController::FluidSynthSoundController(QObject *parent)
0037     : Minuet::ISoundController(parent), m_audioDriver(nullptr), m_sequencer(nullptr),
0038       m_song(nullptr), m_unregisteringEvent(nullptr)
0039 {
0040     m_tempo = 60;
0041 
0042     m_settings = new_fluid_settings();
0043     fluid_settings_setint(m_settings, "synth.reverb.active", 0);
0044     fluid_settings_setint(m_settings, "synth.chorus.active", 0);
0045 
0046     m_synth = new_fluid_synth(m_settings);
0047 
0048     fluid_synth_cc(m_synth, 1, 100, 0);
0049 
0050 #ifdef Q_OS_WIN
0051     const QString sf_path = QStandardPaths::locate(
0052         QStandardPaths::AppDataLocation, QStringLiteral("minuet/soundfonts/GeneralUser-v1.47.sf2"));
0053 #else
0054     QString sf_path = QStandardPaths::locate(QStandardPaths::AppDataLocation,
0055                                              QStringLiteral("soundfonts/GeneralUser-v1.47.sf2"));
0056 #ifdef Q_OS_MACOS
0057     if (sf_path.isEmpty()) {
0058         const QStringList xdgDataDirs = Utils::getXdgDataDirs();
0059         for (const auto &dirPath : xdgDataDirs) {
0060             const QFile testFile(QDir(dirPath).absoluteFilePath(
0061                 QStringLiteral("minuet/soundfonts/GeneralUser-v1.47.sf2")));
0062             if (testFile.exists()) {
0063                 sf_path = testFile.fileName();
0064                 break;
0065             }
0066         }
0067     }
0068 #endif
0069 #endif
0070 
0071     int fluid_res = fluid_synth_sfload(m_synth, sf_path.toLatin1(), 1);
0072     if (fluid_res == FLUID_FAILED) {
0073         qCritical() << "Error when loading soundfont in:" << sf_path;
0074     }
0075 
0076     m_unregisteringEvent = new_fluid_event();
0077     fluid_event_set_source(m_unregisteringEvent, -1);
0078 
0079     resetEngine();
0080 }
0081 
0082 FluidSynthSoundController::~FluidSynthSoundController()
0083 {
0084     deleteEngine();
0085     if (m_synth) {
0086         delete_fluid_synth(m_synth);
0087     }
0088     if (m_settings) {
0089         delete_fluid_settings(m_settings);
0090     }
0091     if (m_unregisteringEvent) {
0092         delete_fluid_event(m_unregisteringEvent);
0093     }
0094 }
0095 
0096 void FluidSynthSoundController::setPitch(qint8 pitch)
0097 {
0098     if (m_pitch == pitch) {
0099         return;
0100     }
0101     m_pitch = pitch;
0102     fluid_synth_cc(m_synth, 1, 101, 0);
0103     fluid_synth_cc(m_synth, 1, 6, 12);
0104     float accurate_pitch = (m_pitch + 12) * (2.0 / 3) * 1024;
0105     fluid_synth_pitch_bend(m_synth, 1, qMin(qRound(accurate_pitch), 16 * 1024 - 1));
0106 }
0107 
0108 void FluidSynthSoundController::setVolume(quint8 volume)
0109 {
0110     if (m_volume == volume) {
0111         return;
0112     }
0113     m_volume = volume;
0114     fluid_synth_cc(m_synth, 1, 7, m_volume * 127 / 200);
0115 }
0116 
0117 void FluidSynthSoundController::setTempo(quint8 tempo)
0118 {
0119     m_tempo = tempo;
0120 }
0121 
0122 void FluidSynthSoundController::prepareFromExerciseOptions(QJsonArray selectedExerciseOptions)
0123 {
0124     auto *song = new QList<fluid_event_t *>;
0125     m_song.reset(song);
0126 
0127     if (m_playMode == QLatin1String("rhythm")) {
0128         for (int i = 0; i < 4; ++i) {
0129             appendEvent(9, 80, 127, 1000 * (60.0 / m_tempo));
0130         }
0131     }
0132 
0133     for (auto &&selectedExerciseOption : selectedExerciseOptions) {
0134         QString sequence = selectedExerciseOption.toObject()[QStringLiteral("sequence")].toString();
0135 
0136         unsigned int chosenRootNote
0137             = selectedExerciseOption.toObject()[QStringLiteral("rootNote")].toString().toInt();
0138         if (m_playMode != QLatin1String("rhythm")) {
0139             appendEvent(1, chosenRootNote, 127, 1000 * (60.0 / m_tempo));
0140             foreach (const QString &additionalNote, sequence.split(' ')) {
0141                 appendEvent(1, chosenRootNote + additionalNote.toInt(), 127,
0142                             ((m_playMode == QLatin1String("scale")) ? 1000 : 4000)
0143                                 * (60.0 / m_tempo));
0144             }
0145         } else {
0146             // appendEvent(9, 80, 127, 1000*(60.0/m_tempo));
0147             foreach (QString additionalNote, sequence.split(' ')) {  // krazy:exclude=foreach
0148                 float dotted = 1;
0149                 if (additionalNote.endsWith('.')) {
0150                     dotted = 1.5;
0151                     additionalNote.chop(1);
0152                 }
0153                 unsigned int duration
0154                     = dotted * 1000 * (60.0 / m_tempo) * (4.0 / additionalNote.toInt());
0155                 appendEvent(9, 37, 127, duration);
0156             }
0157         }
0158     }
0159     // if (m_playMode == "rhythm")
0160     //    appendEvent(9, 80, 127, 1000*(60.0/m_tempo));
0161 
0162     fluid_event_t *event = new_fluid_event();
0163     fluid_event_set_source(event, -1);
0164     fluid_event_all_notes_off(event, 1);
0165     m_song->append(event);
0166 }
0167 
0168 void FluidSynthSoundController::prepareFromMidiFile(const QString &fileName)
0169 {
0170     Q_UNUSED(fileName)
0171 }
0172 
0173 void FluidSynthSoundController::play()
0174 {
0175     if (!m_song.data()) {
0176         return;
0177     }
0178 
0179     if (m_state != PlayingState) {
0180         unsigned int now = fluid_sequencer_get_tick(m_sequencer);
0181         foreach (fluid_event_t *event, *m_song.data()) {
0182             if (fluid_event_get_type(event) != FLUID_SEQ_ALLNOTESOFF
0183                 || m_playMode != QLatin1String("chord")) {
0184                 fluid_event_set_dest(event, m_synthSeqID);
0185                 fluid_sequencer_send_at(m_sequencer, event, now, 1);
0186             }
0187             fluid_event_set_dest(event, m_callbackSeqID);
0188             fluid_sequencer_send_at(m_sequencer, event, now, 1);
0189             now += (m_playMode == QLatin1String("rhythm"))  ? fluid_event_get_duration(event)
0190                    : (m_playMode == QLatin1String("scale")) ? 1000 * (60.0 / m_tempo)
0191                                                             : 0;
0192         }
0193         setState(PlayingState);
0194     }
0195 }
0196 
0197 void FluidSynthSoundController::pause() {}
0198 
0199 void FluidSynthSoundController::stop()
0200 {
0201     if (m_state != StoppedState) {
0202         fluid_event_t *event = new_fluid_event();
0203         fluid_event_set_source(event, -1);
0204         fluid_event_all_notes_off(event, 1);
0205         fluid_event_set_dest(event, m_synthSeqID);
0206         fluid_sequencer_send_now(m_sequencer, event);
0207         resetEngine();
0208     }
0209 }
0210 
0211 void FluidSynthSoundController::reset()
0212 {
0213     stop();
0214     m_song.reset(nullptr);
0215 }
0216 
0217 void FluidSynthSoundController::appendEvent(int channel, short key, short velocity,
0218                                             unsigned int duration)
0219 {
0220     fluid_event_t *event = new_fluid_event();
0221     fluid_event_set_source(event, -1);
0222     fluid_event_note(event, channel, key, velocity, duration);
0223     m_song->append(event);
0224 }
0225 
0226 void FluidSynthSoundController::sequencerCallback(unsigned int time, fluid_event_t *event,
0227                                                   fluid_sequencer_t *seq, void *data)
0228 {
0229     Q_UNUSED(seq);
0230 
0231     // This is safe!
0232     auto *soundController = reinterpret_cast<FluidSynthSoundController *>(data);
0233 
0234     int eventType = fluid_event_get_type(event);
0235     switch (eventType) {
0236     case FLUID_SEQ_NOTE: {
0237         if (m_initialTime == 0) {
0238             m_initialTime = time;
0239         }
0240         double adjustedTime = (time - m_initialTime) / 1000.0;
0241         int mins = adjustedTime / 60;
0242         int secs = ((int)adjustedTime) % 60;
0243         int cnts = 100 * (adjustedTime - qFloor(adjustedTime));
0244 
0245         static QChar fill('0');
0246         soundController->setPlaybackLabel(QStringLiteral("%1:%2.%3")
0247                                               .arg(mins, 2, 10, fill)
0248                                               .arg(secs, 2, 10, fill)
0249                                               .arg(cnts, 2, 10, fill));
0250         break;
0251     }
0252     case FLUID_SEQ_ALLNOTESOFF: {
0253         m_initialTime = 0;
0254         soundController->setPlaybackLabel(QStringLiteral("00:00.00"));
0255         soundController->setState(StoppedState);
0256         break;
0257     }
0258     }
0259 }
0260 
0261 void FluidSynthSoundController::resetEngine()
0262 {
0263     deleteEngine();
0264 #ifdef Q_OS_LINUX
0265     fluid_settings_setstr(m_settings, "audio.driver", "pulseaudio");
0266 #endif
0267 #ifdef Q_OS_WIN
0268     fluid_settings_setstr(m_settings, "audio.driver", "dsound");
0269 #endif
0270     m_audioDriver = new_fluid_audio_driver(m_settings, m_synth);
0271     if (!m_audioDriver) {
0272         fluid_settings_setstr(m_settings, "audio.driver", "alsa");
0273         m_audioDriver = new_fluid_audio_driver(m_settings, m_synth);
0274     }
0275     if (!m_audioDriver) {
0276         qCritical() << "Couldn't start audio driver!";
0277     }
0278 
0279     m_sequencer = new_fluid_sequencer2(0);
0280     m_synthSeqID = fluid_sequencer_register_fluidsynth(m_sequencer, m_synth);
0281     m_callbackSeqID
0282         = fluid_sequencer_register_client(m_sequencer, "Minuet Fluidsynth Sound Controller",
0283                                           &FluidSynthSoundController::sequencerCallback, this);
0284 
0285     m_initialTime = 0;
0286     setPlaybackLabel(QStringLiteral("00:00.00"));
0287     setState(StoppedState);
0288 }
0289 
0290 void FluidSynthSoundController::deleteEngine()
0291 {
0292     if (m_sequencer) {
0293 #if FLUIDSYNTH_VERSION_MAJOR >= 2
0294         // explicit client unregistering required
0295         fluid_sequencer_unregister_client(m_sequencer, m_callbackSeqID);
0296         fluid_event_set_dest(m_unregisteringEvent, m_synthSeqID);
0297         fluid_event_unregistering(m_unregisteringEvent);
0298         fluid_sequencer_send_now(m_sequencer, m_unregisteringEvent);
0299 #endif
0300         delete_fluid_sequencer(m_sequencer);
0301     }
0302     if (m_audioDriver) {
0303         delete_fluid_audio_driver(m_audioDriver);
0304     }
0305 }
0306 
0307 #include "moc_fluidsynthsoundcontroller.cpp"