fluidsynthsoundcontroller.cpp 9.46 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/****************************************************************************
**
** Copyright (C) 2016 by Sandro S. Andrade <sandroandrade@kde.org>
**
** 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 2 of
** the License or (at your option) version 3 or any later version
** accepted by the membership of KDE e.V. (or its successor approved
** by the membership of KDE e.V.), which shall act as a proxy
** defined in Section 14 of version 3 of the license.
**
** 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 <http://www.gnu.org/licenses/>.
**
****************************************************************************/

23
#include "fluidsynthsoundcontroller.h"
24

25 26
#include <QFile>
#include <QDir>
27
#include <QtMath>
28 29 30
#include <QDebug>
#include <QJsonObject>
#include <QStandardPaths>
31

32 33
#include <utils/xdgdatadirs.h>

34
unsigned int FluidSynthSoundController::m_initialTime = 0;
35

36 37
FluidSynthSoundController::FluidSynthSoundController(QObject *parent)
    : Minuet::ISoundController(parent),
38 39
      m_audioDriver(0),
      m_sequencer(0),
Tom Moebert's avatar
Tom Moebert committed
40 41
      m_song(0),
      m_unregisteringEvent(0)
42
{
Sandro Andrade's avatar
Sandro Andrade committed
43
    m_tempo = 60;
44

45
    m_settings = new_fluid_settings();
Tom Moebert's avatar
Tom Moebert committed
46 47
    fluid_settings_setint(m_settings, "synth.reverb.active", 0);
    fluid_settings_setint(m_settings, "synth.chorus.active", 0);
48 49

    m_synth = new_fluid_synth(m_settings);
50

51 52
    fluid_synth_cc(m_synth, 1, 100, 0);

53
#ifdef Q_OS_WIN
54 55
    const QString sf_path = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("minuet/soundfonts/GeneralUser-v1.47.sf2"));
#else
56 57 58 59 60 61 62 63 64 65 66 67 68
    QString sf_path = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("soundfonts/GeneralUser-v1.47.sf2"));
#ifdef Q_OS_MACOS
    if (sf_path.isEmpty()) {
        const QStringList xdgDataDirs = Utils::getXdgDataDirs();
        for (const auto &dirPath : xdgDataDirs) {
            const QFile testFile(QDir(dirPath).absoluteFilePath(QStringLiteral("minuet/soundfonts/GeneralUser-v1.47.sf2")));
            if (testFile.exists()) {
                sf_path = testFile.fileName();
                break;
            }
        }
    }
#endif
69
#endif
70

71
    int fluid_res = fluid_synth_sfload(m_synth, sf_path.toLatin1(), 1);
72
    if (fluid_res == FLUID_FAILED)
73
        qCritical() << "Error when loading soundfont in:" << sf_path;
74

Tom Moebert's avatar
Tom Moebert committed
75 76 77
    m_unregisteringEvent = new_fluid_event();
    fluid_event_set_source(m_unregisteringEvent, -1);

78
    resetEngine();
79 80
}

81
FluidSynthSoundController::~FluidSynthSoundController()
82
{
83 84 85
    deleteEngine();
    if (m_synth) delete_fluid_synth(m_synth);
    if (m_settings) delete_fluid_settings(m_settings);
Tom Moebert's avatar
Tom Moebert committed
86
    if (m_unregisteringEvent) delete_fluid_event(m_unregisteringEvent);
87 88
}

89
void FluidSynthSoundController::setPitch(qint8 pitch)
90
{
91
    if (m_pitch != pitch) { return; }
92 93 94 95 96
    m_pitch = pitch;
    fluid_synth_cc(m_synth, 1, 101, 0);
    fluid_synth_cc(m_synth, 1, 6, 12);
    float accurate_pitch = (m_pitch + 12) * (2.0 / 3) * 1024;
    fluid_synth_pitch_bend(m_synth, 1, qMin(qRound(accurate_pitch), 16 * 1024 - 1));
97 98
}

99
void FluidSynthSoundController::setVolume(quint8 volume)
100
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
101
    if (m_volume != volume) { return; }
102 103
    m_volume = volume;
    fluid_synth_cc(m_synth, 1, 7, m_volume * 127 / 200);
104 105
}

106
void FluidSynthSoundController::setTempo (quint8 tempo)
107
{
108
    m_tempo = tempo;
109 110
}

111
void FluidSynthSoundController::prepareFromExerciseOptions(QJsonArray selectedExerciseOptions)
112
{
113 114 115 116 117 118 119 120 121 122 123 124 125 126
    QList<fluid_event_t *> *song = new QList<fluid_event_t *>;
    m_song.reset(song);

    if (m_playMode == "rhythm")
        for (int i = 0; i < 4; ++i)
            appendEvent(9, 80, 127, 1000*(60.0/m_tempo));

    for (int i = 0; i < selectedExerciseOptions.size(); ++i) {
        QString sequence = selectedExerciseOptions[i].toObject()[QStringLiteral("sequence")].toString();

        unsigned int chosenRootNote = selectedExerciseOptions[i].toObject()[QStringLiteral("rootNote")].toString().toInt();
        if (m_playMode != "rhythm") {
            appendEvent(1, chosenRootNote, 127, 1000*(60.0/m_tempo));
            foreach(const QString &additionalNote, sequence.split(' '))
127
                appendEvent(1, chosenRootNote + additionalNote.toInt(), 127, ((m_playMode == "scale") ? 1000:4000)*(60.0/m_tempo));
128 129
        }
        else {
130
            //appendEvent(9, 80, 127, 1000*(60.0/m_tempo));
131 132 133 134 135 136 137 138 139 140 141
            foreach(QString additionalNote, sequence.split(' ')) { // krazy:exclude=foreach
                float dotted = 1;
                if (additionalNote.endsWith('.')) {
                    dotted = 1.5;
                    additionalNote.chop(1);
                }
                unsigned int duration = dotted*1000*(60.0/m_tempo)*(4.0/additionalNote.toInt());
                appendEvent(9, 37, 127, duration);
            }
        }
    }
142 143 144 145 146 147 148
    //if (m_playMode == "rhythm")
    //    appendEvent(9, 80, 127, 1000*(60.0/m_tempo));

    fluid_event_t *event = new_fluid_event();
    fluid_event_set_source(event, -1);
    fluid_event_all_notes_off(event, 1);
    m_song->append(event);
149 150
}

151
void FluidSynthSoundController::prepareFromMidiFile(const QString &fileName)
152 153 154 155
{
    Q_UNUSED(fileName)
}

156
void FluidSynthSoundController::play()
157
{
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
    if (!m_song.data())
        return;

    if (m_state != PlayingState) {
        unsigned int now = fluid_sequencer_get_tick(m_sequencer);
        foreach(fluid_event_t *event, *m_song.data()) {
            if (fluid_event_get_type(event) != FLUID_SEQ_ALLNOTESOFF || m_playMode != "chord") {
                fluid_event_set_dest(event, m_synthSeqID);
                fluid_sequencer_send_at(m_sequencer, event, now, 1);
            }
            fluid_event_set_dest(event, m_callbackSeqID);
            fluid_sequencer_send_at(m_sequencer, event, now, 1);
            now += (m_playMode == "rhythm") ? fluid_event_get_duration(event):
                (m_playMode == "scale")  ? 1000*(60.0/m_tempo):0;
        }
        setState(PlayingState);
174
    }
175 176
}

177
void FluidSynthSoundController::pause()
178 179 180
{
}

181
void FluidSynthSoundController::stop()
182
{
183 184 185 186 187 188 189 190 191 192
    if (m_state != StoppedState) {
        fluid_event_t *event = new_fluid_event();
        fluid_event_set_source(event, -1);
        fluid_event_all_notes_off(event, 1);
        fluid_event_set_dest(event, m_synthSeqID);
        fluid_sequencer_send_now(m_sequencer, event);
        resetEngine();
    }
}

193
void FluidSynthSoundController::reset()
194 195 196
{
    stop();
    m_song.reset(0);
197 198
}

199
void FluidSynthSoundController::appendEvent(int channel, short key, short velocity, unsigned int duration) 
200 201 202 203 204 205
{
    fluid_event_t *event = new_fluid_event();
    fluid_event_set_source(event, -1);
    fluid_event_note(event, channel, key, velocity, duration);
    m_song->append(event);
}
206

207
void FluidSynthSoundController::sequencerCallback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data)
208 209 210 211
{
    Q_UNUSED(seq);

    // This is safe!
212
    FluidSynthSoundController *soundController = reinterpret_cast<FluidSynthSoundController *>(data);
213 214 215 216 217 218 219 220 221 222 223 224

    int eventType = fluid_event_get_type(event);
    switch (eventType) {
        case FLUID_SEQ_NOTE: {
            if (m_initialTime == 0)
                m_initialTime = time;
            double adjustedTime = (time - m_initialTime)/1000.0;
            int mins = adjustedTime / 60;
            int secs = ((int)adjustedTime) % 60;
            int cnts = 100*(adjustedTime-qFloor(adjustedTime));

            static QChar fill('0');
225
            soundController->setPlaybackLabel(QStringLiteral("%1:%2.%3").arg(mins, 2, 10, fill).arg(secs, 2, 10, fill).arg(cnts, 2, 10, fill));
226 227 228 229
            break;
        }
        case FLUID_SEQ_ALLNOTESOFF: {
            m_initialTime = 0;
230 231
            soundController->setPlaybackLabel(QStringLiteral("00:00.00"));
            soundController->setState(StoppedState);
232 233 234 235 236
            break;
        }
    }
}

237
void FluidSynthSoundController::resetEngine()
238 239
{
    deleteEngine();
240
#ifdef Q_OS_LINUX
241
    fluid_settings_setstr(m_settings, "audio.driver", "pulseaudio");
242 243 244 245
#endif
#ifdef Q_OS_WIN
    fluid_settings_setstr(m_settings, "audio.driver", "dsound");
#endif
246 247 248 249 250 251
    m_audioDriver = new_fluid_audio_driver(m_settings, m_synth);
    if (!m_audioDriver) {
        fluid_settings_setstr(m_settings, "audio.driver", "alsa");
        m_audioDriver = new_fluid_audio_driver(m_settings, m_synth);
    }
    if (!m_audioDriver) {
252
        qCritical() << "Couldn't start audio driver!";
253 254 255 256
    }

    m_sequencer = new_fluid_sequencer2(0);
    m_synthSeqID = fluid_sequencer_register_fluidsynth(m_sequencer, m_synth);
257
    m_callbackSeqID = fluid_sequencer_register_client (m_sequencer, "Minuet Fluidsynth Sound Controller", &FluidSynthSoundController::sequencerCallback, this);
258 259 260 261 262 263

    m_initialTime = 0;
    setPlaybackLabel(QStringLiteral("00:00.00"));
    setState(StoppedState);
}

264
void FluidSynthSoundController::deleteEngine()
265
{
Tom Moebert's avatar
Tom Moebert committed
266 267 268 269 270 271 272 273 274 275
    if (m_sequencer) {
#if FLUIDSYNTH_VERSION_MAJOR >= 2
        // explicit client unregistering required
        fluid_sequencer_unregister_client(m_sequencer, m_callbackSeqID);
        fluid_event_set_dest(m_unregisteringEvent, m_synthSeqID);
        fluid_event_unregistering(m_unregisteringEvent);
        fluid_sequencer_send_now(m_sequencer, m_unregisteringEvent);
#endif
        delete_fluid_sequencer(m_sequencer);
    }
276 277 278
    if (m_audioDriver) delete_fluid_audio_driver(m_audioDriver);
}