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"