diff --git a/src/app/qml/midiplayer/MidiPlayer.qml b/src/app/qml/midiplayer/MidiPlayer.qml index 1256677..86a0f68 100644 --- a/src/app/qml/midiplayer/MidiPlayer.qml +++ b/src/app/qml/midiplayer/MidiPlayer.qml @@ -1,142 +1,142 @@ /**************************************************************************** ** ** Copyright (C) 2016 by Sandro S. Andrade ** ** 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 . ** ****************************************************************************/ import QtQuick 2.7 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.minuet.isoundbackend 1.0 Rectangle { readonly property alias pitch: pitchSlider.value readonly property alias volume: volumeSlider.value readonly property alias tempo: tempoSlider.value property alias playbackLabel: playbackLabelText.text property int soundBackendState signal playActivated signal pauseActivated signal stopActivated height: childrenRect.height + 15 anchors { left: parent.left; bottom: parent.bottom } color: "black" Rectangle { id: labels width: parent.width; height: 20 anchors.top: parent.top color: theme.viewTextColor Row { width: parent.width anchors { verticalCenter: parent.verticalCenter; left: parent.left; leftMargin: 15 } Text { width: parent.width / 3 font.pointSize: 8 horizontalAlignment: Text.AlignLeft color: theme.viewBackgroundColor text: i18n("Tempo: %1 bpm").arg(Math.round(tempo)) } Text { width: parent.width / 3 font.pointSize: 8 horizontalAlignment: Text.AlignLeft color: theme.viewBackgroundColor text: i18n("Volume: %1%").arg(Math.round(volume)) } Text { width: parent.width / 3 font.pointSize: 8 horizontalAlignment: Text.AlignLeft color: theme.viewBackgroundColor text: i18n("Pitch: %1").arg(Math.round(pitch)) } } - } + } Item { id: item1 width: parent.width / 2 - 8; height: childrenRect.height anchors { left: parent.left; leftMargin: 8; top: labels.bottom; topMargin: 10 } Text { id: playbackLabelText width: parent.width horizontalAlignment: Text.AlignHCenter font.pointSize: 24 color: "#008000" } MultimediaButton { width: playbackLabelText.contentWidth / 2 anchors.horizontalCenterOffset: -30 anchors { top: playbackLabelText.bottom; horizontalCenter: playbackLabelText.horizontalCenter } text: (soundBackendState != ISoundBackend.PlayingState) ? i18n("Play"):i18n("Pause") source: (soundBackendState != ISoundBackend.PlayingState) ? "../images/multimedia-play.png":"../images/multimedia-pause.png" onActivated: { if (soundBackendState == ISoundBackend.StoppedState || soundBackendState == ISoundBackend.PausedState) playActivated() else pauseActivated() } } MultimediaButton { width: playbackLabelText.contentWidth / 2 anchors.horizontalCenterOffset: +30 anchors { top: playbackLabelText.bottom; horizontalCenter: playbackLabelText.horizontalCenter } source: "../images/multimedia-stop.png" text: i18n("Stop") onActivated: stopActivated() } } Item { width: parent.width / 2 - 15; height: item1.height anchors { right: parent.right; rightMargin: 15; verticalCenter: item1.verticalCenter } Row { height: parent.height anchors.right: parent.right spacing: 8 MultimediaSlider { id: pitchSlider source: "../images/multimedia-pitch.png" tooltipText: "Pitch" maximumValue: 12; minimumValue: -12; value: 0 } MultimediaSlider { id: tempoSlider source: "../images/multimedia-speed.png" tooltipText: "Tempo (bpm)" maximumValue: 200; minimumValue: 50; value: 100 } MultimediaSlider { id: volumeSlider source: "../images/multimedia-volume.png" tooltipText: "Volume (%)" - maximumValue: 200; value: 100 + maximumValue: 200; minimumValue: 50; value: 100 } } } } diff --git a/src/plugins/fluidsynthsoundbackend/fluidsynthsoundbackend.cpp b/src/plugins/fluidsynthsoundbackend/fluidsynthsoundbackend.cpp index 5a0cc67..37a1399 100644 --- a/src/plugins/fluidsynthsoundbackend/fluidsynthsoundbackend.cpp +++ b/src/plugins/fluidsynthsoundbackend/fluidsynthsoundbackend.cpp @@ -1,231 +1,238 @@ /**************************************************************************** ** ** Copyright (C) 2016 by Sandro S. Andrade ** ** 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 . ** ****************************************************************************/ #include "fluidsynthsoundbackend.h" #include #include #include #include #include unsigned int FluidSynthSoundBackend::m_initialTime = 0; FluidSynthSoundBackend::FluidSynthSoundBackend(QObject *parent) : Minuet::ISoundBackend(parent), m_audioDriver(0), m_sequencer(0), m_song(0) { m_tempo = 120; m_settings = new_fluid_settings(); fluid_settings_setstr(m_settings, "synth.reverb.active", "no"); fluid_settings_setstr(m_settings, "synth.chorus.active", "no"); m_synth = new_fluid_synth(m_settings); + fluid_synth_cc(m_synth, 1, 100, 0); + int fluid_res = fluid_synth_sfload(m_synth, QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("soundfonts/GeneralUser-v1.47.sf2")).toLatin1(), 1); if (fluid_res == FLUID_FAILED) qDebug() << "Error when loading soundfont!"; resetEngine(); } FluidSynthSoundBackend::~FluidSynthSoundBackend() { deleteEngine(); if (m_synth) delete_fluid_synth(m_synth); if (m_settings) delete_fluid_settings(m_settings); } void FluidSynthSoundBackend::setPitch(qint8 pitch) { - Q_UNUSED(pitch); + 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)); } void FluidSynthSoundBackend::setVolume(quint8 volume) { - Q_UNUSED(volume); + m_volume = volume; + fluid_synth_cc(m_synth, 1, 7, m_volume * 127 / 200); } void FluidSynthSoundBackend::setTempo (quint8 tempo) { - Q_UNUSED(tempo); + m_tempo = tempo; } void FluidSynthSoundBackend::prepareFromExerciseOptions(QJsonArray selectedExerciseOptions) { QList *song = new QList; 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(' ')) appendEvent(1, chosenRootNote + additionalNote.toInt(), 127, ((m_playMode == "scale") ? 1000:4000)*(60.0/m_tempo)); } else { //appendEvent(9, 80, 127, 1000*(60.0/m_tempo)); 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); } } } //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); } void FluidSynthSoundBackend::prepareFromMidiFile(const QString &fileName) { Q_UNUSED(fileName) } void FluidSynthSoundBackend::play() { 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); } } void FluidSynthSoundBackend::pause() { } void FluidSynthSoundBackend::stop() { 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(); } } void FluidSynthSoundBackend::reset() { stop(); m_song.reset(0); } void FluidSynthSoundBackend::appendEvent(int channel, short key, short velocity, unsigned int duration) { 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); } void FluidSynthSoundBackend::sequencerCallback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { Q_UNUSED(seq); // This is safe! FluidSynthSoundBackend *soundBackend = reinterpret_cast(data); 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'); soundBackend->setPlaybackLabel(QStringLiteral("%1:%2.%3").arg(mins, 2, 10, fill).arg(secs, 2, 10, fill).arg(cnts, 2, 10, fill)); break; } case FLUID_SEQ_ALLNOTESOFF: { m_initialTime = 0; soundBackend->setPlaybackLabel(QStringLiteral("00:00.00")); soundBackend->setState(StoppedState); break; } } } void FluidSynthSoundBackend::resetEngine() { deleteEngine(); fluid_settings_setstr(m_settings, "audio.driver", "pulseaudio"); 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) { qDebug() << "Couldn't start audio driver!"; } m_sequencer = new_fluid_sequencer2(0); m_synthSeqID = fluid_sequencer_register_fluidsynth(m_sequencer, m_synth); m_callbackSeqID = fluid_sequencer_register_client (m_sequencer, "Minuet Fluidsynth Sound Backend", &FluidSynthSoundBackend::sequencerCallback, this); m_initialTime = 0; setPlaybackLabel(QStringLiteral("00:00.00")); setState(StoppedState); } void FluidSynthSoundBackend::deleteEngine() { if (m_sequencer) delete_fluid_sequencer(m_sequencer); if (m_audioDriver) delete_fluid_audio_driver(m_audioDriver); } #include "moc_fluidsynthsoundbackend.cpp"