Warning, /education/gcompris/src/activities/play_rhythm/PlayRhythm.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - PlayRhythm.qml
0002  *
0003  * SPDX-FileCopyrightText: 2018 Aman Kumar Gupta <gupta2140@gmail.com>
0004  *
0005  * Authors:
0006  *   Beth Hadley <bethmhadley@gmail.com> (GTK+ version)
0007  *   Aman Kumar Gupta <gupta2140@gmail.com> (Qt Quick port)
0008  *
0009  *   SPDX-License-Identifier: GPL-3.0-or-later
0010  */
0011 import QtQuick 2.12
0012 import GCompris 1.0
0013 
0014 import "../../core"
0015 import "../piano_composition"
0016 import "play_rhythm.js" as Activity
0017 
0018 ActivityBase {
0019     id: activity
0020 
0021     onStart: focus = true
0022     onStop: {}
0023     isMusicalActivity: true
0024 
0025     property bool horizontalLayout: width >= height
0026 
0027     pageComponent: Rectangle {
0028         id: background
0029         anchors.fill: parent
0030         color: "#ABCDEF"
0031         signal start
0032         signal stop
0033 
0034         // if audio is disabled, we display a dialog to tell users this activity requires audio anyway
0035         property bool audioDisabled: false
0036 
0037         Component.onCompleted: {
0038             activity.start.connect(start)
0039             activity.stop.connect(stop)
0040         }
0041 
0042         // Add here the QML items you need to access in javascript
0043         QtObject {
0044             id: items
0045             property Item main: activity.main
0046             property GCSfx audioEffects: activity.audioEffects
0047             property alias background: background
0048             property int currentLevel: activity.currentLevel
0049             property alias bonus: bonus
0050             property alias parser: parser
0051             property alias score: score
0052             property alias multipleStaff: multipleStaff
0053             property alias iAmReady: iAmReady
0054             property alias introductoryAudioTimer: introductoryAudioTimer
0055             property alias metronomeOscillation: metronomeOscillation
0056             property alias answerFeedbackTimer: answerFeedbackTimer
0057             property bool isMetronomeVisible: false
0058             property bool isWrongRhythm: false
0059             property bool buttonsBlocked: false
0060             property bool crashPlayed: false
0061         }
0062 
0063         onStart: {
0064             if(!ApplicationSettings.isAudioVoicesEnabled || !ApplicationSettings.isAudioEffectsEnabled) {
0065                     background.audioDisabled = true;
0066             }
0067             Activity.start(items);
0068         }
0069         onStop: { Activity.stop() }
0070 
0071         property string clefType: "Treble"
0072         property bool isRhythmPlaying: false
0073         property int metronomeSpeed: 60000 / multipleStaff.bpmValue - 53
0074         property real weightOffset: metronome.height * multipleStaff.bpmValue * 0.004
0075 
0076         Keys.onSpacePressed: if(!background.isRhythmPlaying && !items.buttonsBlocked)
0077                                 tempo.tempoPressed();
0078         Keys.onTabPressed: if(metronome.visible && metronomeOscillation.running)
0079                              metronomeOscillation.stop();
0080                           else if(metronome.visible && !metronomeOscillation.running)
0081                                   metronomeOscillation.start();
0082         Keys.onEnterPressed: Activity.initSubLevel();
0083         Keys.onReturnPressed: Activity.initSubLevel();
0084         Keys.onUpPressed: optionsRow.bpmIncreased();
0085         Keys.onDownPressed: optionsRow.bpmDecreased();
0086         Keys.onReleased: {
0087             if(iAmReady.visible) {
0088                 iAmReady.visible = false;
0089                 Activity.initLevel();
0090             } else if(event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
0091                 bpmChangeDelay.restart();
0092             }
0093         }
0094 
0095         Rectangle {
0096             id: instructionBox
0097             radius: 10
0098             width: background.width * 0.7
0099             height: background.height / 9
0100             anchors.horizontalCenter: parent.horizontalCenter
0101             opacity: 0.8
0102             border.width: 6
0103             color: "white"
0104             border.color: "#87A6DD"
0105 
0106             GCText {
0107                 id: instructionText
0108                 color: "black"
0109                 z: 3
0110                 anchors.fill: parent
0111                 anchors.rightMargin: parent.width * 0.02
0112                 anchors.leftMargin: parent.width * 0.02
0113                 horizontalAlignment: Text.AlignHCenter
0114                 verticalAlignment: Text.AlignVCenter
0115                 fontSizeMode: Text.Fit
0116                 wrapMode: Text.WordWrap
0117                 text: items.isMetronomeVisible ? qsTr("Use the metronome to estimate the time intervals and play the rhythm correctly.")
0118                                                : qsTr("Follow the vertical line and click on the drum or press space key to play the rhythm correctly.")
0119             }
0120         }
0121 
0122         Timer {
0123             id: introductoryAudioTimer
0124             interval: 3500
0125             onRunningChanged: {
0126                 if(running)
0127                     Activity.isIntroductoryAudioPlaying = true
0128                 else {
0129                     Activity.isIntroductoryAudioPlaying = false
0130                     Activity.initSubLevel()
0131                 }
0132             }
0133         }
0134 
0135         Timer {
0136             id: bpmChangeDelay
0137             interval: 500
0138             onTriggered: {
0139                 Activity.initSubLevel();
0140                 background.isRhythmPlaying = true;
0141             }
0142         }
0143 
0144         Timer {
0145             id: answerFeedbackTimer
0146             interval: 1000
0147             onRunningChanged: {
0148                 if (!running) {
0149                     if(!items.crashPlayed)
0150                         Activity.answerFeedback();
0151                     else {
0152                         items.crashPlayed = false;
0153                         Activity.initSubLevel();
0154                     }
0155                 }
0156             }
0157         }
0158 
0159         JsonParser {
0160             id: parser
0161         }
0162 
0163         Rectangle {
0164             anchors.fill: parent
0165             color: "black"
0166             opacity: 0.3
0167             visible: iAmReady.visible
0168             z: 10
0169             MouseArea {
0170                 anchors.fill: parent
0171             }
0172         }
0173 
0174         ReadyButton {
0175             id: iAmReady
0176             focus: true
0177             z: 10
0178             onClicked: {
0179                 Activity.initLevel()
0180             }
0181         }
0182 
0183         Score {
0184             id: score
0185             anchors.top: background.top
0186             anchors.bottom: undefined
0187             numberOfSubLevels: 3
0188             width: horizontalLayout ? parent.width / 10 : (parent.width - instructionBox.x - instructionBox.width - 1.5 * anchors.rightMargin)
0189             onStop: Activity.nextSubLevel()
0190         }
0191 
0192         MultipleStaff {
0193             id: multipleStaff
0194             width: horizontalLayout ? parent.width * 0.6 : parent.width * 0.9
0195             height: horizontalLayout ? parent.height * 1.1 : parent.height * 0.76
0196             bpmValue: 90
0197             nbStaves: 1
0198             clef: clefType
0199             isFlickable: false
0200             anchors.horizontalCenter: parent.horizontalCenter
0201             anchors.top: parent.top
0202             anchors.topMargin: horizontalLayout ? 0 : parent.height * 0.1
0203             centerNotesPosition: true
0204             firstCenteredNotePosition: width / (2 * (musicElementModel.count - 1))
0205             spaceBetweenNotes: width / (2.5 * (musicElementModel.count - 1))
0206             enableNotesSound: false
0207             onPulseMarkerAnimationFinished: background.isRhythmPlaying = false
0208             onPlayDrumSound: {
0209                 if(background.isRhythmPlaying && !metronomeOscillation.running)
0210                     GSynth.generate(60, 100)
0211             }
0212         }
0213 
0214         Image {
0215             id: tempo
0216             source: "qrc:/gcompris/src/activities/play_rhythm/resource/drumhead.svg"
0217             width: horizontalLayout ? parent.width / 7 : parent.width / 4
0218             sourceSize.width: width
0219             fillMode: Image.PreserveAspectFit
0220             anchors.top: metronome.top
0221             anchors.horizontalCenter: parent.horizontalCenter
0222             transform: Scale {
0223                 id: tempoScale
0224                 origin.y: tempo.height
0225                 yScale: 1
0226                 SequentialAnimation on yScale {
0227                     id: tempoAnim
0228                     PropertyAnimation { to: 0.5; duration: 50 }
0229                     PropertyAnimation { to: 1; duration: 50 }
0230                 }
0231             }
0232             MouseArea {
0233                 anchors.fill: parent
0234                 enabled: !background.isRhythmPlaying && !items.buttonsBlocked
0235                 onPressed: tempo.tempoPressed()
0236             }
0237 
0238             function tempoPressed() {
0239                 tempoAnim.start()
0240                 if(!multipleStaff.isMusicPlaying && Activity.currentNote == 0) {
0241                     multipleStaff.play()
0242                 }
0243                 GSynth.generate(60, 100)
0244                 Activity.checkAnswer(multipleStaff.pulseMarkerX)
0245             }
0246         }
0247 
0248         Image {
0249             id: metronome
0250             source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_stand.svg"
0251             fillMode: Image.PreserveAspectFit
0252             sourceSize.width: parent.width / 3
0253             sourceSize.height: parent.height / 4
0254             width: sourceSize.width
0255             height: sourceSize.height
0256             anchors.bottom: bar.top
0257             anchors.bottomMargin: 20
0258             visible: items.isMetronomeVisible
0259             MouseArea {
0260                 anchors.fill: parent
0261                 onClicked: {
0262                     if(metronomeOscillation.running)
0263                         metronomeOscillation.stop()
0264                     else
0265                         metronomeOscillation.start()
0266                 }
0267             }
0268 
0269             Image {
0270                 id: metronomeNeedle
0271                 source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_needle.svg"
0272                 fillMode: Image.PreserveAspectFit
0273                 width: parent.height
0274                 height: parent.height
0275                 anchors.centerIn: parent
0276                 transformOrigin: Item.Bottom
0277                 SequentialAnimation {
0278                     id: metronomeOscillation
0279                     loops: Animation.Infinite
0280                     onStarted: metronomeNeedle.rotation = 12
0281                     onStopped: metronomeNeedle.rotation = 0
0282                     ScriptAction {
0283                         script: items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav")
0284                     }
0285                     RotationAnimator {
0286                         target: metronomeNeedle
0287                         from: 12
0288                         to: 348
0289                         direction: RotationAnimator.Shortest
0290                         duration: metronomeSpeed
0291                     }
0292                     ScriptAction {
0293                         script: items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav")
0294                     }
0295                     RotationAnimator {
0296                         target: metronomeNeedle
0297                         from: 348
0298                         to: 12
0299                         direction: RotationAnimator.Shortest
0300                         duration: metronomeSpeed
0301                     }
0302                 }
0303                 Image {
0304                     id: metronomeWeight
0305                     source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_weight.svg"
0306                     fillMode: Image.PreserveAspectFit
0307                     width: parent.height
0308                     height: parent.height
0309                     anchors.horizontalCenter: parent.horizontalCenter
0310                     anchors.verticalCenter: parent.verticalCenter
0311                     anchors.verticalCenterOffset: weightOffset
0312                 }
0313 
0314             }
0315             Image {
0316                 id: metronomeFront
0317                 source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_front.svg"
0318                 fillMode: Image.PreserveAspectFit
0319                 width: parent.height
0320                 height: parent.height
0321                 anchors.centerIn: parent
0322             }
0323         }
0324 
0325         OptionsRow {
0326             id: optionsRow
0327             anchors.verticalCenter: tempo.verticalCenter
0328             anchors.left: tempo.right
0329 
0330             bpmVisible: true
0331             onBpmDecreased: {
0332                 if(multipleStaff.bpmValue >= 51)
0333                     multipleStaff.bpmValue--
0334             }
0335             onBpmIncreased: {
0336                 if(multipleStaff.bpmValue <= 179)
0337                 multipleStaff.bpmValue++
0338             }
0339             onBpmChanged: {
0340                 bpmChangeDelay.restart();
0341             }
0342         }
0343 
0344         DialogHelp {
0345             id: dialogHelp
0346             onClose: home()
0347         }
0348 
0349         Bar {
0350             id: bar
0351             level: items.currentLevel + 1
0352             content: BarEnumContent { value: help | home | level | reload }
0353             onHelpClicked: {
0354                 displayDialog(dialogHelp)
0355             }
0356             onPreviousLevelClicked: Activity.previousLevel()
0357             onNextLevelClicked: Activity.nextLevel()
0358             onHomeClicked: activity.home()
0359             onReloadClicked: {
0360                 Activity.initSubLevel()
0361             }
0362         }
0363 
0364         Bonus {
0365             id: bonus
0366             Component.onCompleted: {
0367                 win.connect(Activity.nextLevel)
0368             }
0369         }
0370 
0371         Loader {
0372             id: audioNeededDialog
0373             sourceComponent: GCDialog {
0374                 parent: activity
0375                 isDestructible: false
0376                 message: qsTr("This activity requires sound, so it will play some sounds even if the audio voices or effects are disabled in the main configuration.")
0377                 button1Text: qsTr("Quit")
0378                 button2Text: qsTr("Continue")
0379                 onButton1Hit: activity.home();
0380                 onClose: {
0381                     background.audioDisabled = false;
0382                 }
0383             }
0384             anchors.fill: parent
0385             focus: true
0386             active: background.audioDisabled
0387             onStatusChanged: if (status == Loader.Ready) item.start()
0388         }
0389     }
0390 }