diff --git a/changes b/changes index 31acc710985e49825a0b6f93ac8bb28d32341b8f..e3c686f1e695de3c275169fb4d9f9a6a0f6f7af8 100644 --- a/changes +++ b/changes @@ -1,3 +1,6 @@ +1.5.1 alpha2 + - simple metronome and menu for configuring tempo, quantization, etc. + 1.5.0 alpha1 - added support for rhythms to the score - import/export to/from music XML files diff --git a/src/libs/sound/tcommonlistener.h b/src/libs/sound/tcommonlistener.h index 21d9213099ed7b78f0403be8b176f4b0ce9d5110..f57b9b798ff4d3a48ffb39aacd2ec05ce09ee667 100644 --- a/src/libs/sound/tcommonlistener.h +++ b/src/libs/sound/tcommonlistener.h @@ -123,17 +123,17 @@ public: */ TnoteStruct& lastNote() { return m_lastNote; } - float lastChunkPitch() { return m_LastChunkPitch; } + float lastChunkPitch() const { return m_LastChunkPitch; } /** * Returns @p TRUE when @p pitch is in ambitus */ - bool inRange(qreal pitch) { + bool inRange(qreal pitch) const { if (pitch >= m_loPitch && pitch <= m_hiPitch) return true; else return false; } - bool noteWasStarted() { return m_noteWasStarted; } /**< @p TRUE when note started but not finished. */ + bool noteWasStarted() const { return m_noteWasStarted; } /**< @p TRUE when note started but not finished. */ /** * Sets pitch detection method. Currently three are available: @@ -148,7 +148,7 @@ public: * Stores user action when he stopped sniffing himself. */ void setStoppedByUser(bool userStop) { m_stoppedByUser = userStop; } - bool stoppedByUser() { return m_stoppedByUser; } + bool stoppedByUser() const { return m_stoppedByUser; } /** * Returns intonation accuracy sets in global audio settings. diff --git a/src/libs/sound/tsound.cpp b/src/libs/sound/tsound.cpp index e44631f2be5d80f6da109ecfdd13f038889e859c..b07c0642e191cc9fcc2697071f4640bbe6a57cb9 100755 --- a/src/libs/sound/tsound.cpp +++ b/src/libs/sound/tsound.cpp @@ -70,7 +70,7 @@ Tsound::Tsound(QObject* parent) : sniffer = 0; } - QTimer::singleShot(750, [=]{ sniffer->startListening(); }); + QTimer::singleShot(1000, [=]{ sniffer->startListening(); }); } Tsound::~Tsound() @@ -198,23 +198,6 @@ void Tsound::acceptSettings() { } -// void Tsound::setPitchView(TpitchView* pView) { -// m_pitchView = pView; -// m_pitchView->setPitchColor(GLOB->EanswerColor); -// m_pitchView->setMinimalVolume(GLOB->A->minimalVol); -// m_pitchView->setIntonationAccuracy(GLOB->A->intonation); -// m_pitchView->setAudioInput(sniffer); -// -// if (sniffer) { -// #if defined (Q_OS_ANDROID) -// QTimer::singleShot(750, [=]{ m_pitchView->pauseAction()->trigger(); }); -// #else -// QTimer::singleShot(750, [=]{ sniffer->startListening(); }); -// #endif -// } -// } - - void Tsound::prepareToConf() { if (player) { player->stop(); @@ -270,19 +253,37 @@ void Tsound::setQuantization(int q) { } -void Tsound::wait() { -// qDebug("wait"); - if (sniffer) { - sniffer->stopListening(); +bool Tsound::stoppedByUser() const { + return sniffer ? sniffer->stoppedByUser() : false; +} + + +void Tsound::setStoppedByUser(bool sbu) { + if (sniffer && sniffer->stoppedByUser() != sbu) { + sniffer->setStoppedByUser(sbu); + if (sbu) + stopListen(); + else + startListen(); + emit stoppedByUserChanged(); } } -void Tsound::go() { -// qDebug("go"); - if (sniffer /*&& !m_pitchView->isPaused()*/) { +bool Tsound::listening() const { + return sniffer ? sniffer->detectingState() == TcommonListener::e_detecting : false; +} + + +void Tsound::stopListen() { + if (sniffer) + sniffer->stopListening(); +} + + +void Tsound::startListen() { + if (sniffer) sniffer->startListening(); - } } @@ -339,7 +340,7 @@ void Tsound::restoreAfterExam() { // sniffer->setAmbitus(m_prevLoNote, m_prevHiNote); // acceptSettings() has already invoked setDefaultAmbitus() // m_pitchView->setDisabled(false); unPauseSniffing(); - go(); + startListen(); } } @@ -400,11 +401,12 @@ void Tsound::createSniffer() { sniffer = TaudioIN::instance(); else #endif - sniffer = new TaudioIN(GLOB->A); + sniffer = new TaudioIN(GLOB->A); setDefaultAmbitus(); // sniffer->setAmbitus(Tnote(-31), Tnote(82)); // fixed ambitus bounded Tartini capacities connect(sniffer, &TaudioIN::noteStarted, this, &Tsound::noteStartedSlot); connect(sniffer, &TaudioIN::noteFinished, this, &Tsound::noteFinishedSlot); + connect(sniffer, &TaudioIN::stateChanged, [=]{ emit listeningChanged(); }); m_userState = false; // user didn't stop sniffing yet } @@ -480,7 +482,7 @@ void Tsound::noteFinishedSlot(const TnoteStruct& note) { qDebug() << "noteFinishedSlot" << note.duration * 1000 << dur << normDur; if (r.isValid()) { m_detectedNote.setRhythm(r); - qDebug() << "Detected" << m_detectedNote.toText() << m_detectedNote.rtm.string(); + qDebug() << "Detected" << note.duration << normDur << m_detectedNote.toText() << m_detectedNote.rtm.string(); emit noteFinished(); } else { TrhythmList notes; @@ -494,11 +496,10 @@ void Tsound::noteFinishedSlot(const TnoteStruct& note) { else rr.setTie(Trhythm::e_tieCont); m_detectedNote.setRhythm(rr); - qDebug() << "Detected" << n << m_detectedNote.toText() << m_detectedNote.rtm.string(); + qDebug() << "Detected" << note.duration << normDur << n << m_detectedNote.toText() << m_detectedNote.rtm.string(); emit noteFinished(); } } -// emit noteFinished(); emit noteFinishedEntire(note); if (player && GLOB->instrument().type() == Tinstrument::NoInstrument && GLOB->A->playDetected) play(m_detectedNote); diff --git a/src/libs/sound/tsound.h b/src/libs/sound/tsound.h index 163cd3c1cd518c58a298086fac342be665d88194..2dff985523de7fb990cfe3d1da9216ba6991d2a4 100644 --- a/src/libs/sound/tsound.h +++ b/src/libs/sound/tsound.h @@ -52,6 +52,8 @@ class NOOTKASOUND_EXPORT Tsound : public QObject Q_PROPERTY(int tempo READ tempo WRITE setTempo NOTIFY tempoChanged) Q_PROPERTY(int quantization READ quantization WRITE setQuantization NOTIFY quantizationChanged) + Q_PROPERTY(bool stoppedByUser READ stoppedByUser WRITE setStoppedByUser NOTIFY stoppedByUserChanged) + Q_PROPERTY(bool listening READ listening NOTIFY listeningChanged) public: explicit Tsound(QObject *parent = nullptr); @@ -94,9 +96,8 @@ public: void restoreAfterConf(); void acceptSettings(); -// void setPitchView(TpitchView *pView); - void wait(); /**< Stops sniffing. It is called when en exam is starting. */ - void go(); /**< Starts sniffing again. */ + Q_INVOKABLE void stopListen(); + Q_INVOKABLE void startListen(); /** * Returns recently detected note. @@ -117,6 +118,11 @@ public: void setQuantization(int q); int quantization() const { return m_quantVal; } + bool stoppedByUser() const; + void setStoppedByUser(bool sbu); + + bool listening() const; + void pauseSinffing(); void unPauseSniffing(); bool isSnifferPaused(); @@ -145,6 +151,8 @@ signals: void plaingFinished(); void tempoChanged(); void quantizationChanged(); + void stoppedByUserChanged(); + void listeningChanged(); private: void createPlayer(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 59241ba7c1585c9df72a1937946e50a31fcd20c1..f7e60ce4831217ef1c03aa343c9ef6ed4783e506 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -282,7 +282,7 @@ void MainWindow::examMessageSlot(int demand) { if (demand == Torders::e_examAskCreator) QTimer::singleShot(500, [=]{ openLevelCreator(); }); // open creator with delay to give executor time to finish his routines else - m_sound->go(); + m_sound->startListen(); } //########################################################################################## @@ -364,7 +364,7 @@ void MainWindow::createSettingsDialog() { void MainWindow::openLevelCreator(QString levelFile) { if (m_score->isScorePlayed()) m_melButt->playMelodySlot(); // stop playing - m_sound->wait(); // stops pitch detection + m_sound->stopListen(); // stops pitch detection m_sound->stopPlaying(); m_levelCreatorExist = true; TpluginsLoader loader; @@ -390,7 +390,7 @@ void MainWindow::openLevelCreator(QString levelFile) { startExamPlugin(args); } else - m_sound->go(); // restore pitch detection + m_sound->startListen(); // restore pitch detection } @@ -405,12 +405,12 @@ void MainWindow::aboutSlot() { if (m_score->isScorePlayed()) m_melButt->playMelodySlot(); // stop playing - m_sound->wait(); + m_sound->stopListen(); m_sound->stopPlaying(); TpluginsLoader loader; if (loader.load(TpluginsLoader::e_about)) loader.init(QString(), this); - m_sound->go(); + m_sound->startListen(); } @@ -418,13 +418,13 @@ void MainWindow::analyseSlot() { #if !defined (Q_OS_ANDROID) if (m_score->isScorePlayed()) m_melButt->playMelodySlot(); // stop playing - m_sound->wait(); + m_sound->stopListen(); m_sound->stopPlaying(); TpluginsLoader loader; if (loader.load(TpluginsLoader::e_analyzer)) { loader.init(QString(), this); } - m_sound->go(); + m_sound->startListen(); #endif } @@ -489,13 +489,13 @@ void MainWindow::setSingleNoteMode(bool isSingle) { //####################### PROTECTED SLOTS ######################################## //########################################################################################## void MainWindow::showSupportDialog() { - m_sound->wait(); + m_sound->stopListen(); m_sound->stopPlaying(); TpluginsLoader loader; if (loader.load(TpluginsLoader::e_about)) loader.init(QStringLiteral("support"), this); gl->config->setValue("General/supportDate", QDate::currentDate()); - m_sound->go(); + m_sound->startListen(); } @@ -504,9 +504,9 @@ void MainWindow::updaterMessagesSlot(int m) { || m == Torders::e_updaterFinished || m >= Torders::e_updaterError) { m_updaterPlugin->deleteLater(); if (m_updaterStoppedSound) - m_sound->go(); + m_sound->startListen(); } else if (m == Torders::e_updaterSuccess && !m_sound->isSnifferPaused()) { - m_sound->wait(); + m_sound->stopListen(); m_updaterStoppedSound = true; } // It sends Torders::e_updaterSuccess as well but it means that updater window is displayed, @@ -632,7 +632,7 @@ void MainWindow::updateSize(QSize newS) { void MainWindow::closeEvent(QCloseEvent *event) { m_sound->stopPlaying(); - m_sound->wait(); + m_sound->stopListen(); #if !defined (Q_OS_ANDROID) disconnect(m_score, &TmainScore::statusTip, m_statusLabel, &TstatusLabel::messageSlot); disconnect(m_innerWidget, &TmainView::statusTip, m_statusLabel, &TstatusLabel::messageSlot); diff --git a/src/plugins/exam/texamexecutor.cpp b/src/plugins/exam/texamexecutor.cpp index 6c51a9b7ec62fee1a12b8c4483f3d9436c038741..468483fc0288fd4134e9f1d1a7ce9ccfd7d455b7 100755 --- a/src/plugins/exam/texamexecutor.cpp +++ b/src/plugins/exam/texamexecutor.cpp @@ -105,7 +105,7 @@ void TexamExecutor::init(QString examFile, Tlevel *lev) { QString resultText; TstartExamDlg::Eactions userAct; - SOUND->wait(); + SOUND->stopListen(); if (lev) { m_level = *lev; if (Tcore::gl()->E->studentName.isEmpty()) { @@ -541,7 +541,7 @@ void TexamExecutor::askQuestion(bool isAttempt) { // Give a student some time to prepare itself for next question in expert mode // It avoids capture previous played sound as current answer } else - SOUND->wait(); // stop sniffing if answer is not a played sound + SOUND->stopListen(); // stop sniffing if answer is not a played sound TOOLBAR->setForQuestion(curQ->questionAsSound(), curQ->questionAsSound() && curQ->answerAsNote()); m_penalty->startQuestionTime(); @@ -988,7 +988,7 @@ void TexamExecutor::repeatQuestion() { void TexamExecutor::displayCertificate() { m_snifferLocked = true; - SOUND->wait(); + SOUND->stopListen(); m_penalty->pauseTime(); #if !defined (Q_OS_ANDROID) qApp->removeEventFilter(m_supp); // stop grabbing right button and calling checkAnswer() @@ -1062,7 +1062,7 @@ void TexamExecutor::prepareToExam() { if (m_level.canBeSound()) { SOUND->acceptSettings(); if (SOUND->isSniffable()) - SOUND->wait(); + SOUND->stopListen(); if (m_level.requireOctave) SOUND->prepareToExam(m_level.loNote, m_level.hiNote); // when octave are not required do not change ambitus - it is already set to instrument scale @@ -1377,7 +1377,7 @@ void TexamExecutor::stopSound() { if (m_soundTimer->isActive()) m_soundTimer->stop(); SOUND->stopPlaying(); - SOUND->wait(); + SOUND->stopListen(); #if !defined (Q_OS_ANDROID) qApp->removeEventFilter(m_supp); #endif @@ -1507,7 +1507,7 @@ void TexamExecutor::noteOfMelodyFinished(const TnoteStruct& n) { else { m_canvas->playMelodyAgainMessage(); m_canvas->confirmTip(800); - SOUND->wait(); + SOUND->stopListen(); } } } @@ -1516,7 +1516,7 @@ void TexamExecutor::noteOfMelodyFinished(const TnoteStruct& n) { void TexamExecutor::noteOfMelodySelected(int nr) { m_melody->setCurrentIndex(nr); SCORE->selectNote(nr); - SOUND->go(); + SOUND->startListen(); m_canvas->clearConfirmTip(); if (isExercise() && GUITAR->isVisible() && m_exam->curQ()->melody()) // in exercises, display guitar position of clicked note for a hint GUITAR->setFinger(m_exam->curQ()->melody()->note(nr)->g()); @@ -1561,7 +1561,7 @@ void TexamExecutor::startSniffing() { if (SOUND->isSnifferPaused()) SOUND->unPauseSniffing(); else - SOUND->go(); + SOUND->startListen(); } @@ -1686,7 +1686,7 @@ void TexamExecutor::setTitleAndTexts() { void TexamExecutor::unlockAnswerCapturing() { if (m_exam->curQ()->answerAsSound()) - SOUND->go(); + SOUND->startListen(); m_penalty->continueTime(); #if !defined (Q_OS_ANDROID) qApp->installEventFilter(m_supp); // restore grabbing right mouse button diff --git a/src/qml/DialogLoader.qml b/src/qml/DialogLoader.qml index 3c6596b5658f0188f27b5d9642240d56b747e06d..8dc7e261896d15460384906ccc78cd610424a780 100644 --- a/src/qml/DialogLoader.qml +++ b/src/qml/DialogLoader.qml @@ -61,6 +61,7 @@ Dialog { } break } + SOUND.stopListen() open() if (Noo.isAndroid()) { dialogDrawer = drawerComp.createObject(currentDialog) @@ -93,6 +94,7 @@ Dialog { onVisibleChanged: { if (visible === false && currentDialog) { + SOUND.startListen() currentDialog.destroy() currentDialog = null page = 0 diff --git a/src/qml/TtoolBar.qml b/src/qml/TtoolBar.qml index b68697b74c85af41af116adc7c4f632d4d1cdca1..3ea7d0e552597e830dafaf3cf9bcd736de74e730 100644 --- a/src/qml/TtoolBar.qml +++ b/src/qml/TtoolBar.qml @@ -1,5 +1,5 @@ /** This file is part of Nootka (http://nootka.sf.net) * - * Copyright (C) 2017 by Tomasz Bojczuk (seelook@gmail.com) * + * Copyright (C) 2017 by Tomasz Bojczuk (seelapook@gmail.com) * * on the terms of GNU GPLv3 license (http://www.gnu.org/licenses) */ import QtQuick 2.9 @@ -22,67 +22,65 @@ ToolBar { } ToolButton { - id: metro + id: metroButt property TempoMenu menu: null width: settAct.width * 1.2; height: settAct.height x: pitchView.x - width - Rectangle { - id: pend - visible: !metro.menu || metro.menu.tickEnable - y: parent.height / 15; width: parent.width / 15; height: parent.height / 4 - color: activPal.text - SequentialAnimation on x { - id: metroAnim - loops: Animation.Infinite; running: true - NumberAnimation { duration: 60000 / SOUND.tempo; from: 0; to: metro.width - pend.width } - NumberAnimation { duration: 60000 / SOUND.tempo; from: metro.width - pend.width; to: 0 } - } - } onClicked: { if (!menu) { var c = Qt.createComponent("qrc:/TempoMenu.qml") - menu = c.createObject(metro) - menu.onAccepted.connect(metroAnim.restart) + menu = c.createObject(metroButt) } menu.open() } background: Rectangle { anchors.fill: parent; color: activPal.window - border { width: 2; color: activPal.text } + border { width: 2; color: SOUND.listening ? activPal.text : disdPal.text } + } + Rectangle { + id: mic + visible: SOUND.listening + color: activPal.text + y: parent.height / 15; width: parent.width / 5; height: parent.height / 8 + property int phase: 0 + Timer { + running: SOUND.listening && (!metroButt.menu || metroButt.menu.tickEnable); repeat: true + interval: 60000 / SOUND.tempo / 4 + property real elap: 0 + property real lag: 0 + property int phase: 0 + onTriggered: { + var currTime = new Date().getTime() + if (elap > 0) { + elap = currTime - elap + lag += elap - interval + } + elap = currTime + interval = Math.max(60000 / SOUND.tempo / 4 - lag, 1) + lag = 0 + phase++ + if (phase > 4) phase = 0 + mic.x = mic.width * phase + } + } } -// Text { -// id: mic -// anchors.horizontalCenter: parent.horizontalCenter -// font { family: "Nootka"; pixelSize: parent.height / 2 } -// text: "r" -// Timer { -// running: true; repeat: true -// interval: 150 -// onTriggered: { -// if (interval === 150) { -// mic.color = activPal.text -// interval = 60000 / SOUND.tempo - 150 -// } else { -// mic.color = "red" -// interval = 150 -// } -// } -// } -// } Text { y: parent.height / 3 anchors.horizontalCenter: parent.horizontalCenter font { family: "Scorek"; pixelSize: parent.height / 4 } text: "\ue1d5=" + SOUND.tempo + color: SOUND.listening ? activPal.text : disdPal.text } } PitchView { id: pitchView - x: label.x - parent.width * 0.41; y: parent.height * 0.05 + active: SOUND.listening + anchors.right: label.left height: parent.height * 0.9 width: parent.width * 0.4 + onPaused: SOUND.stoppedByUser = !SOUND.stoppedByUser Timer { - repeat: true; interval: 75; running: true + repeat: true; interval: 75; running: SOUND.listening onTriggered: { pitchView.volume = SOUND.inputVol() pitchView.deviation = SOUND.pitchDeviation() diff --git a/src/qml/sound/IntonationBar.qml b/src/qml/sound/IntonationBar.qml index 31e118a5e5fc3d64e68417de3c90aa046aac09f0..b7dbdebc07ab137c7cef2ffaf86b067c10168400 100644 --- a/src/qml/sound/IntonationBar.qml +++ b/src/qml/sound/IntonationBar.qml @@ -18,7 +18,7 @@ Item { id: iRepLeft model: tc.width / tc.divisor Rectangle { - color: deviation < 0 && iRepLeft.model - index <= (deviation * -2 * iRepLeft.model) ? tc.colorAt(iRepLeft.model - index) : activPal.text + color: pitchView.active ? (deviation < 0 && iRepLeft.model - index <= (deviation * -2 * iRepLeft.model) ? tc.colorAt(iRepLeft.model - index) : activPal.text) : disdPal.text width: pitchView.tickWidth radius: pitchView.tickWidth / 2 height: pitchView.tickWidth * 1.5 + ((intoBar.height - pitchView.tickWidth * 4) / iRepLeft.model) * (iRepLeft.model - index) @@ -35,14 +35,14 @@ Item { font.family: "Nootka" font.pixelSize: intoBar.height * 0.8 text: "n" - color: activPal.text + color: pitchView.active ? activPal.text : disdPal.text } Repeater { id: iRepRight model: tc.width / tc.divisor Rectangle { - color: deviation > 0 && index <= (deviation * 2 * iRepRight.model) ? tc.colorAt(index) : activPal.text + color: pitchView.active ? (deviation > 0 && index <= (deviation * 2 * iRepRight.model) ? tc.colorAt(index) : activPal.text) : disdPal.text width: pitchView.tickWidth radius: pitchView.tickWidth / 2 height: pitchView.tickWidth * 1.5 + ((intoBar.height - pitchView.tickWidth * 4) / iRepRight.model) * index diff --git a/src/qml/sound/PitchView.qml b/src/qml/sound/PitchView.qml index af8183ee1d4079fa4caaa58feb5473f8b4dbe065..f191ba9f6ff01ca5dee54b328db26c64c949ce04 100644 --- a/src/qml/sound/PitchView.qml +++ b/src/qml/sound/PitchView.qml @@ -12,8 +12,11 @@ Item { property alias volume : volBar.volume property alias minVol : volBar.minVol property alias deviation : intoBar.deviation + property bool active: false - // private + signal paused() + + // protected property real tickWidth: Screen.pixelDensity * 0.5 property real tickGap: tickWidth * 1.4 diff --git a/src/qml/sound/TempoMenu.qml b/src/qml/sound/TempoMenu.qml index 930a43104f4379f35d8887248b8de61fd1b39f8e..a09711d1440159e4bc01e145dc6190f4530c063f 100644 --- a/src/qml/sound/TempoMenu.qml +++ b/src/qml/sound/TempoMenu.qml @@ -61,4 +61,6 @@ Popup { } } } + onOpened: SOUND.stopListen() + onClosed: SOUND.startListen() } diff --git a/src/qml/sound/VolumeBar.qml b/src/qml/sound/VolumeBar.qml index e90c224b36d4b750516d957ca9f987c13f7bf2fd..1f795df1735fe52784578280fe47654b75480a3d 100644 --- a/src/qml/sound/VolumeBar.qml +++ b/src/qml/sound/VolumeBar.qml @@ -14,11 +14,11 @@ Item { property real volume: 0.05 property real minVol: 0.4 - TtickColors { id: tc; width: volBar.width - minText.width - noteText.width * 2; divisor: pitchView.tickGap + pitchView.tickWidth } MouseArea { id: area + enabled: pitchView.active anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton @@ -35,7 +35,7 @@ Item { id: minText anchors { top: parent.Top; left: parent.Left; verticalCenter: parent.verticalCenter } text: " " + Math.round(minVol * 100) + "% " - color: activPal.text + color: pitchView.active ? activPal.text : disdPal.text font.pixelSize: parent.height / 2 width: parent.height * 1.2 } @@ -44,7 +44,7 @@ Item { id: vRep model: tc.width / tc.divisor Rectangle { - color: index < volume * vRep.model ? tc.colorAt(index) : activPal.text + color: pitchView.active ? (index < volume * vRep.model ? tc.colorAt(index) : activPal.text) : disdPal.text width: index <= minVol * vRep.model ? pitchView.tickWidth / 2 : pitchView.tickWidth radius: pitchView.tickWidth / 2 height: pitchView.tickWidth * 1.5 + ((volBar.height - pitchView.tickWidth * 4) / vRep.model) * index @@ -60,12 +60,10 @@ Item { font.family: "Nootka" font.pixelSize: volBar.height text: "r" - color: activPal.text + color: pitchView.active ? "red" : activPal.text MouseArea { anchors.fill: parent - hoverEnabled: true - onEntered: noteText.color = activPal.highlight - onExited: noteText.color = activPal.text + onClicked: pitchView.paused() } } @@ -77,7 +75,7 @@ Item { color: activPal.shadow radius: 8.0 source: knob - visible: area.pressed || area.containsMouse + visible: pitchView.active && (area.pressed || area.containsMouse) } Rectangle {