diff --git a/src/activities/piano_composition/BpmMeter.qml b/src/activities/piano_composition/BpmMeter.qml index 953b321a8..ff03beb6c 100644 --- a/src/activities/piano_composition/BpmMeter.qml +++ b/src/activities/piano_composition/BpmMeter.qml @@ -1,186 +1,188 @@ /* GCompris - BpmMeter.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (Qt Quick port) * Aman Kumar Gupta (Qt Quick port) * Timothée Giet (refactoring) * * 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 3 of the License, or * (at your option) any later version. * * 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.6 import GCompris 1.0 import "../../core" Item { id: bpmMeter width: optionsRow.iconsWidth * 2 height: optionsRow.iconsWidth visible: bpmVisible Rectangle { id: bpmBg color: "yellow" opacity: 0.1 border.width: 2 border.color: "black" width: optionsRow.iconsWidth height: optionsRow.iconsWidth anchors.left: parent.left radius: 10 } GCText { //: BPM is the abbreviation for Beats Per Minute. text: qsTr("%1 BPM").arg(multipleStaff.bpmValue) width: 0.9 * bpmBg.width height: bpmBg.height verticalAlignment: Text.AlignVCenter anchors.centerIn: bpmBg fontSizeMode: Text.Fit } Image { id: bpmDown source: "qrc:/gcompris/src/core/resource/bar_down.svg" width: iconsWidth height: iconsWidth * 0.5 sourceSize.width: width fillMode: Image.PreserveAspectFit anchors.bottom: parent.bottom anchors.left: bpmBg.right Timer { id: decreaseBpm interval: 500 repeat: true onTriggered: { bpmDecreased() interval = 1 } onRunningChanged: { if(!running) interval = 500 } } MouseArea { id: mouseDown anchors.fill: parent hoverEnabled: true onPressed: { bpmDown.scale = 0.85 bpmDecreased() decreaseBpm.start() } onReleased: { decreaseBpm.stop() bpmDown.scale = 1 + bpmChanged() } } states: [ State { name: "notclicked" PropertyChanges { target: bpmDown scale: 1.0 } }, State { name: "clicked" when: mouseDown.pressed PropertyChanges { target: bpmDown scale: 0.9 } }, State { name: "hover" when: mouseDown.containsMouse PropertyChanges { target: bpmDown scale: 1.1 } } ] Behavior on scale { NumberAnimation { duration: 70 } } Behavior on opacity { PropertyAnimation { duration: 200 } } } Image { id: bpmUp source: "qrc:/gcompris/src/core/resource/bar_up.svg" width: iconsWidth height: bpmDown.height sourceSize.width: width fillMode: Image.PreserveAspectFit anchors.top: parent.top anchors.left: bpmBg.right Timer { id: increaseBpm interval: 500 repeat: true onTriggered: { bpmIncreased() interval = 1 } onRunningChanged: { if(!running) interval = 500 } } MouseArea { id: mouseUp anchors.fill: parent hoverEnabled: true onPressed: { bpmUp.scale = 0.85 bpmIncreased() increaseBpm.start() } onReleased: { increaseBpm.stop() bpmUp.scale = 1 + bpmChanged() } } states: [ State { name: "notclicked" PropertyChanges { target: bpmUp scale: 1.0 } }, State { name: "clicked" when: mouseUp.pressed PropertyChanges { target: bpmUp scale: 0.9 } }, State { name: "hover" when: mouseUp.containsMouse PropertyChanges { target: bpmUp scale: 1.1 } } ] Behavior on scale { NumberAnimation { duration: 70 } } Behavior on opacity { PropertyAnimation { duration: 200 } } } } diff --git a/src/activities/piano_composition/OptionsRow.qml b/src/activities/piano_composition/OptionsRow.qml index 80aad9fe2..084955bf5 100644 --- a/src/activities/piano_composition/OptionsRow.qml +++ b/src/activities/piano_composition/OptionsRow.qml @@ -1,208 +1,209 @@ /* GCompris - OptionsRow.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (Qt Quick port) * Aman Kumar Gupta (Qt Quick port) * Timothée Giet (refactoring) * * 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 3 of the License, or * (at your option) any later version. * * 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.6 import GCompris 1.0 import "../../core" Grid { id: optionsRow columns: 2 //: Whole note, Half note, Quarter note and Eighth note are the different length notes in the musical notation. readonly property var noteLengthName: [[qsTr("Whole note"), "Whole"], [qsTr("Half note"), "Half"], [qsTr("Quarter note"), "Quarter"], [qsTr("Eighth note"), "Eighth"]] //: Whole rest, Half rest, Quarter rest and Eighth rest are the different length rests (silences) in the musical notation. readonly property var restAddedMessage: [qsTr("Added whole rest"), qsTr("Added half rest"), qsTr("Added quarter rest"), qsTr("Added eighth rest")] readonly property var translatedRestNames: [qsTr("Whole rest"), qsTr("Half rest"), qsTr("Quarter rest"), qsTr("Eighth rest")] readonly property var lyricsOrPianoModes: [[qsTr("Piano"), "piano"], [qsTr("Lyrics"), "lyrics"]] property real iconsWidth: score.height * 1.2 property alias noteOptionsIndex: noteOptions.currentIndex property alias lyricsOrPianoModeIndex: lyricsOrPianoModeOption.currentIndex property alias keyOption: keyOption property alias bpmMeter: bpmMeter property alias restOptionIndex: restOptions.currentIndex property bool restOptionsVisible: false property bool noteOptionsVisible: false property bool playButtonVisible: false property bool clearButtonVisible: false property bool undoButtonVisible: false property bool openButtonVisible: false property bool saveButtonVisible: false property bool changeAccidentalStyleButtonVisible: false property bool lyricsOrPianoModeOptionVisible: false property bool bpmVisible: false signal undoButtonClicked signal clearButtonClicked signal openButtonClicked signal saveButtonClicked signal playButtonClicked signal bpmIncreased signal bpmDecreased + signal bpmChanged signal emitOptionMessage(string message) BpmMeter { id: bpmMeter } BarButton { id: playButton source: "qrc:/gcompris/src/activities/piano_composition/resource/play.svg" sourceSize.width: optionsRow.iconsWidth visible: playButtonVisible onClicked: { optionsRow.playButtonClicked() emitOptionMessage(qsTr("Play melody")) multipleStaff.play() } } BarButton { id: clearButton source: "qrc:/gcompris/src/activities/piano_composition/resource/erase.svg" sourceSize.width: optionsRow.iconsWidth visible: clearButtonVisible onClicked: clearButtonClicked() } BarButton { id: undoButton source: "qrc:/gcompris/src/activities/piano_composition/resource/undo.svg" sourceSize.width: optionsRow.iconsWidth visible: undoButtonVisible onClicked: { emitOptionMessage(qsTr("Undo")) undoButtonClicked() } } KeyOption { id: keyOption } Item { id: rests width: optionsRow.iconsWidth * 2 height: optionsRow.iconsWidth visible: restOptionsVisible Rectangle { color: "yellow" opacity: 0.1 border.width: 2 border.color: "black" anchors.fill: parent radius: 10 } SwitchableOptions { id: restOptions readonly property string restTypeImage: (optionsRow.noteLengthName[currentIndex][1]).toLowerCase() source: "qrc:/gcompris/src/activities/piano_composition/resource/%1Rest.svg".arg(restTypeImage) nbOptions: optionsRow.noteLengthName.length onClicked: { background.restType = optionsRow.noteLengthName[currentIndex][1] emitOptionMessage(optionsRow.translatedRestNames[currentIndex]) } width: optionsRow.iconsWidth * 0.9 sourceSize.width: width visible: restOptionsVisible anchors.topMargin: -3 anchors.left: parent.left anchors.leftMargin: 5 } BarButton { id: addRestButton width: restOptions.width sourceSize.width: width source: "qrc:/gcompris/src/activities/piano_composition/resource/add.svg" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter visible: restOptions.visible onClicked: { emitOptionMessage(optionsRow.restAddedMessage[restOptionIndex]) parent.scale = 1 background.addMusicElementAndPushToStack(restType.toLowerCase(), "Rest") } } } BarButton { id: changeAccidentalStyleButton source: changeAccidentalStyleButtonVisible ? (piano.useSharpNotation ? "qrc:/gcompris/src/activities/piano_composition/resource/blacksharp.svg" : "qrc:/gcompris/src/activities/piano_composition/resource/blackflat.svg") : "" sourceSize.width: optionsRow.iconsWidth visible: changeAccidentalStyleButtonVisible onClicked: { piano.useSharpNotation = !piano.useSharpNotation //: Sharp notes and Flat notes represents the accidental style of the notes in the music. emitOptionMessage(piano.useSharpNotation ? qsTr("Sharp notes") : qsTr("Flat notes")) } } SwitchableOptions { id: noteOptions source: "qrc:/gcompris/src/activities/piano_composition/resource/genericNote%1.svg".arg(optionsRow.noteLengthName[currentIndex][1]) nbOptions: optionsRow.noteLengthName.length currentIndex: 2 onClicked: { background.currentType = optionsRow.noteLengthName[currentIndex][1] emitOptionMessage(optionsRow.noteLengthName[currentIndex][0]) } visible: noteOptionsVisible } BarButton { id: openButton source: "qrc:/gcompris/src/activities/piano_composition/resource/open.svg" sourceSize.width: optionsRow.iconsWidth visible: openButtonVisible onClicked: openButtonClicked() } BarButton { id: saveButton source: "qrc:/gcompris/src/activities/piano_composition/resource/save.svg" sourceSize.width: optionsRow.iconsWidth visible: saveButtonVisible onClicked: saveButtonClicked() } SwitchableOptions { id: lyricsOrPianoModeOption nbOptions: optionsRow.lyricsOrPianoModes.length source: "qrc:/gcompris/src/activities/piano_composition/resource/%1.svg".arg(optionsRow.lyricsOrPianoModes[currentIndex][1]) visible: lyricsOrPianoModeOptionVisible onClicked: emitOptionMessage(optionsRow.lyricsOrPianoModes[currentIndex][0]) } } diff --git a/src/activities/play_rhythm/PlayRhythm.qml b/src/activities/play_rhythm/PlayRhythm.qml index 6e3c42494..80ff63787 100644 --- a/src/activities/play_rhythm/PlayRhythm.qml +++ b/src/activities/play_rhythm/PlayRhythm.qml @@ -1,322 +1,326 @@ /* GCompris - PlayRhythm.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Aman Kumar Gupta (Qt Quick port) * * 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 3 of the License, or * (at your option) any later version. * * 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.6 import GCompris 1.0 import "../../core" import "../piano_composition" import "play_rhythm.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true property bool horizontalLayout: width > height pageComponent: Image { id: background anchors.fill: parent fillMode: Image.PreserveAspectCrop signal start signal stop property string backgroundImagesUrl: ":/gcompris/src/activities/piano_composition/resource/background/" property var backgroundImages: directory.getFiles(backgroundImagesUrl) Directory { id: directory } source: { if(items.bar.level === 0) return "qrc" + backgroundImagesUrl + backgroundImages[0] else return "qrc" + backgroundImagesUrl + backgroundImages[(items.bar.level - 1) % backgroundImages.length] } Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property alias parser: parser property alias score: score property alias multipleStaff: multipleStaff property alias iAmReady: iAmReady property alias introductoryAudioTimer: introductoryAudioTimer property alias metronomeOscillation: metronomeOscillation property bool isMetronomeVisible: false property bool isWrongRhythm: false } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string clefType: "Treble" property bool isRhythmPlaying: false Keys.onSpacePressed: tempo.tempoPressed() Rectangle { id: instructionBox radius: 10 width: background.width * 0.7 height: background.height / 9 anchors.horizontalCenter: parent.horizontalCenter opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { id: instructionText color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: items.isMetronomeVisible ? qsTr("Use the metronome to estimate the time intervals and play the rhythm correctly.") : qsTr("Follow the vertical line and click on the tempo or press space key and play the rhythm correctly.") } } Timer { id: introductoryAudioTimer interval: 3500 onRunningChanged: { if(running) Activity.isIntroductoryAudioPlaying = true else { Activity.isIntroductoryAudioPlaying = false Activity.initSubLevel() } } } JsonParser { id: parser } Rectangle { anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 onClicked: { Activity.initLevel() } } Score { id: score anchors.top: background.top anchors.bottom: undefined numberOfSubLevels: 3 width: horizontalLayout ? parent.width / 10 : (parent.width - instructionBox.x - instructionBox.width - 1.5 * anchors.rightMargin) } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.6 : parent.width * 0.9 height: horizontalLayout ? parent.height * 1.1 : parent.height * 0.76 bpmValue: 90 nbStaves: 1 clef: clefType isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: horizontalLayout ? 0 : parent.height * 0.1 centerNotesPosition: true firstCenteredNotePosition: width / (2 * (musicElementModel.count - 1)) spaceBetweenNotes: width / (2.5 * (musicElementModel.count - 1)) enableNotesSound: false onPulseMarkerAnimationFinished: background.isRhythmPlaying = false onPlayDrumSound: { if(background.isRhythmPlaying && !metronomeOscillation.running) items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav") } } Image { id: tempo source: "qrc:/gcompris/src/activities/play_rhythm/resource/drumhead.png" width: horizontalLayout ? parent.width / 7 : parent.width / 4 height: width / 2 anchors.top: metronome.top anchors.horizontalCenter: parent.horizontalCenter MouseArea { anchors.fill: parent enabled: !background.isRhythmPlaying && !bonus.isPlaying && (!items.isWrongRhythm || multipleStaff.isPulseMarkerRunning) onPressed: tempo.tempoPressed() onReleased: tempo.scale = 1 } function tempoPressed() { tempo.scale = 0.85 if(!multipleStaff.isMusicPlaying) { Activity.currentNote = 0 multipleStaff.play() } if(!metronomeOscillation.running) items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav") Activity.checkAnswer(multipleStaff.pulseMarkerX) } } Image { id: metronome source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_stand.svg" fillMode: Image.PreserveAspectFit sourceSize.width: parent.width / 3 sourceSize.height: parent.height / 4 width: sourceSize.width height: sourceSize.height anchors.bottom: bar.top anchors.bottomMargin: 20 visible: items.isMetronomeVisible MouseArea { anchors.fill: parent onClicked: { if(metronomeOscillation.running) metronomeOscillation.stop() else metronomeOscillation.start() } } Image { id: metronomeNeedle source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_needle.svg" fillMode: Image.PreserveAspectFit width: parent.height height: parent.height anchors.centerIn: parent transformOrigin: Item.Bottom SequentialAnimation { id: metronomeOscillation loops: Animation.Infinite onStopped: metronomeNeedle.rotation = 0 RotationAnimator { target: metronomeNeedle from: 0 to: 12 direction: RotationAnimator.Shortest duration: 463 } ScriptAction { script: items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav") } RotationAnimator { target: metronomeNeedle from: 12 to: 0 direction: RotationAnimator.Shortest duration: 463 } RotationAnimator { target: metronomeNeedle from: 0 to: 348 direction: RotationAnimator.Shortest duration: 463 } ScriptAction { script: items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav") } RotationAnimator { target: metronomeNeedle from: 348 to: 0 direction: RotationAnimator.Shortest duration: 463 } } } } OptionsRow { id: optionsRow anchors.verticalCenter: tempo.verticalCenter anchors.left: tempo.right bpmVisible: true onBpmDecreased: { if(multipleStaff.bpmValue - 1 >= 1) multipleStaff.bpmValue-- } onBpmIncreased: { multipleStaff.bpmValue++ } + onBpmChanged: { + Activity.initSubLevel() + background.isRhythmPlaying = true + } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { background.isRhythmPlaying = true Activity.initSubLevel() } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } }