diff --git a/src/activities/play_piano/ActivityInfo.qml b/src/activities/play_piano/ActivityInfo.qml new file mode 100644 --- /dev/null +++ b/src/activities/play_piano/ActivityInfo.qml @@ -0,0 +1,44 @@ +/* GCompris - ActivityInfo.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 GCompris 1.0 + +ActivityInfo { + name: "play_piano/PlayPiano.qml" + difficulty: 1 + icon: "play_piano/play_piano.svg" + author: "Aman Kumar Gupta <gupta2140@gmail.com>" + demo: true + //: Activity title + title: qsTr("Play piano") + //: Help title + description: "" + //intro: "Click on the keyboard keys that match the notes that you see and hear" + //: Help goal + goal: qsTr("Understand how the piano keyboard can play music as written on the musical staff.") + //: Help prerequisite + prerequisite: qsTr("Knowledge of musical notation and musical staff. Play the activity named 'Piano Composition' first.") + //: Help manual + manual: qsTr("The notes you see will be played to you. Click on the corresponding keys on the keyboard that match the notes you hear and see.
Levels 1-5 will offer treble clef to practice and levels 6-10 will offer bass clef.") + credit: "" + section: "discovery sound_group" + createdInVersion: 9500 +} diff --git a/src/activities/play_piano/CMakeLists.txt b/src/activities/play_piano/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/activities/play_piano/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/play_piano *.qml *.svg *.js *.json resource/*) diff --git a/src/activities/play_piano/PlayPiano.qml b/src/activities/play_piano/PlayPiano.qml new file mode 100644 --- /dev/null +++ b/src/activities/play_piano/PlayPiano.qml @@ -0,0 +1,350 @@ +/* GCompris - PlayPiano.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 QtQuick.Controls 1.5 +import GCompris 1.0 +import QtMultimedia 5.0 + +import "../../core" +import "../piano_composition" +import "play_piano.js" as Activity + +ActivityBase { + id: activity + + onStart: focus = true + onStop: {} + + 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) + } + + Keys.onPressed: { + var keyboardBindings = {} + keyboardBindings[Qt.Key_1] = 0 + keyboardBindings[Qt.Key_2] = 1 + keyboardBindings[Qt.Key_3] = 2 + keyboardBindings[Qt.Key_4] = 3 + keyboardBindings[Qt.Key_5] = 4 + keyboardBindings[Qt.Key_6] = 5 + keyboardBindings[Qt.Key_7] = 6 + keyboardBindings[Qt.Key_8] = 7 + keyboardBindings[Qt.Key_F1] = 1 + keyboardBindings[Qt.Key_F2] = 2 + keyboardBindings[Qt.Key_F3] = 3 + keyboardBindings[Qt.Key_F4] = 4 + keyboardBindings[Qt.Key_F5] = 5 + + if(piano.whiteKeysEnabled && !iAmReady.visible) { + if(event.key >= Qt.Key_1 && event.key <= Qt.Key_8) { + piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() + } + else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { + if(piano.blackKeysEnabled) + findBlackKey(keyboardBindings[event.key]) + } + else if(event.key === Qt.Key_Space) { + multipleStaff.play() + } + else if(event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { + Activity.undoPreviousAnswer() + } + } + } + + function findBlackKey(keyNumber) { + for(var i = 0; keyNumber; i++) { + if(piano.keyRepeater.itemAt(i) == undefined) + break + if(piano.keyRepeater.itemAt(i).blackKey.visible) + keyNumber-- + if(keyNumber === 0) + piano.keyRepeater.itemAt(i).blackKey.keyPressed() + } + } + + // Add here the QML items you need to access in javascript + QtObject { + id: items + property Item main: activity.main + property alias background: background + property GCSfx audioEffects: activity.audioEffects + property alias multipleStaff: multipleStaff + property alias piano: piano + property alias bar: bar + property alias bonus: bonus + property alias score: score + property alias iAmReady: iAmReady + property alias introductoryAudioTimer: introductoryAudioTimer + property alias parser: parser + property string mode: "coloredNotes" + } + + onStart: { + dialogActivityConfig.getInitialConfiguration() + Activity.start(items) + } + onStop: { Activity.stop() } + + property string clefType: (items.bar.level <= 5) ? "Treble" : "Bass" + + Timer { + id: introductoryAudioTimer + interval: 4000 + 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: 5 + width: horizontalLayout ? parent.width / 10 : (parent.width - instruction.x - instruction.width - 1.5 * anchors.rightMargin) + } + + Rectangle { + id: instruction + radius: 10 + width: background.width * 0.6 + height: background.height / 9 + anchors.horizontalCenter: parent.horizontalCenter + opacity: 0.8 + border.width: 6 + color: "white" + border.color: "#87A6DD" + + GCText { + 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: qsTr("Click on the piano keys that match the given notes.") + } + } + + MultipleStaff { + id: multipleStaff + width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.8 + height: horizontalLayout ? parent.height * 0.85 : parent.height * 0.58 + nbStaves: 1 + clef: clefType + coloredNotes: (items.mode === "coloredNotes") ? ['C', 'D', 'E', 'F', 'G', 'A', 'B'] : [] + isFlickable: false + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: instruction.bottom + anchors.topMargin: horizontalLayout ? parent.height * 0.02 : parent.height * 0.15 + onNoteClicked: { + playNoteAudio(musicElementModel.get(noteIndex).noteName_, musicElementModel.get(noteIndex).noteType_, musicElementModel.get(noteIndex).soundPitch_) + } + centerNotesPosition: true + } + + PianoOctaveKeyboard { + id: piano + width: horizontalLayout ? parent.width * 0.3 : parent.width * 0.7 + height: horizontalLayout ? parent.height * 0.3 : parent.width * 0.26 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: bar.top + anchors.bottomMargin: 20 + blackLabelsVisible: ([4, 5, 9, 10].indexOf(items.bar.level) != -1) + blackKeysEnabled: blackLabelsVisible && !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running + whiteKeysEnabled: !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running + whiteKeyNoteLabelsTreble: [ whiteKeyNoteLabelsArray.slice(18, 26) ] + whiteKeyNoteLabelsBass: [ whiteKeyNoteLabelsArray.slice(11, 19)] + onNoteClicked: { + multipleStaff.playNoteAudio(note, "Quarter", clefType, 1000) + Activity.checkAnswer(note) + } + useSharpNotation: true + } + + Rectangle { + id: optionDeck + width: optionsRow.changeAccidentalStyleButtonVisible ? optionsRow.iconsWidth * 5 : optionsRow.iconsWidth * 4 + height: optionsRow.iconsWidth * 1.7 + border.width: 2 + border.color: "black" + color: "black" + opacity: 0.5 + radius: 10 + y: horizontalLayout ? background.height / 2 - height : instruction.height + 10 + x: horizontalLayout ? background.width - width - 25 : background.width / 2 - width / 2 + } + + OptionsRow { + id: optionsRow + anchors.top: optionDeck.top + anchors.topMargin: 15 + anchors.horizontalCenter: optionDeck.horizontalCenter + iconsWidth: horizontalLayout ? Math.min(50, (background.width - piano.x - piano.width) / 5) : 45 + + playButtonVisible: true + undoButtonVisible: true + + onUndoButtonClicked: Activity.undoPreviousAnswer() + } + + ExclusiveGroup { + id: configOptions + } + + DialogActivityConfig { + id: dialogActivityConfig + content: Component { + Column { + id: column + spacing: 5 + width: dialogActivityConfig.width + height: dialogActivityConfig.height + + property alias coloredNotesModeBox: coloredNotesModeBox + property alias colorlessNotesModeBox: colorlessNotesModeBox + + GCDialogCheckBox { + id: coloredNotesModeBox + width: column.width - 50 + text: qsTr("Display colored notes.") + checked: items.mode === "coloredNotes" + exclusiveGroup: configOptions + onCheckedChanged: { + if(coloredNotesModeBox.checked) { + items.mode = "coloredNotes" + } + } + } + + GCDialogCheckBox { + id: colorlessNotesModeBox + width: coloredNotesModeBox.width + text: qsTr("Display colorless notes.") + checked: items.mode === "colorlessNotes" + exclusiveGroup: configOptions + onCheckedChanged: { + if(colorlessNotesModeBox.checked) { + items.mode = "colorlessNotes" + } + } + } + } + } + onLoadData: { + if(dataToSave && dataToSave["mode"]) + items.mode = dataToSave["mode"] + } + onSaveData: dataToSave["mode"] = items.mode + onClose: { + home() + } + onVisibleChanged: { + multipleStaff.eraseAllNotes() + iAmReady.visible = true + } + } + + DialogHelp { + id: dialogHelp + onClose: home() + } + + Bar { + id: bar + content: BarEnumContent { value: help | home | level | config | reload } + onHelpClicked: displayDialog(dialogHelp) + onPreviousLevelClicked: Activity.previousLevel() + onNextLevelClicked: Activity.nextLevel() + onHomeClicked: activity.home() + onConfigClicked: { + dialogActivityConfig.active = true + displayDialog(dialogActivityConfig) + } + onReloadClicked: { + multipleStaff.eraseAllNotes() + iAmReady.visible = true + } + } + + Bonus { + id: bonus + Component.onCompleted: win.connect(Activity.nextSubLevel) + } + } +} diff --git a/src/activities/play_piano/dataset.json b/src/activities/play_piano/dataset.json new file mode 100644 --- /dev/null +++ b/src/activities/play_piano/dataset.json @@ -0,0 +1,74 @@ +{ + "levels": [ + [ + "Treble E4Quarter C4Quarter E4Quarter", + "Treble D4Quarter D4Quarter C4Quarter", + "Treble D4Quarter C4Quarter D4Quarter", + "Treble D4Quarter E4Quarter E4Quarter", + "Treble D4Quarter C4Quarter C4Quarter" + ], + [ + "Treble G4Quarter F4Quarter D4Quarter", + "Treble E4Quarter A4Quarter A4Quarter", + "Treble G4Quarter G4Quarter C4Quarter", + "Treble C4Quarter E4Quarter E4Quarter", + "Treble E4Quarter C4Quarter C4Quarter" + ], + [ + "Treble F4Quarter C4Quarter C4Quarter D4Quarter", + "Treble E4Quarter A4Quarter E4Quarter G4Quarter", + "Treble G4Quarter F4Quarter A4Quarter B4Quarter", + "Treble B4Quarter G4Quarter G4Quarter B4Quarter", + "Treble B4Quarter C4Quarter G4Quarter E4Quarter" + ], + [ + "Treble D4Quarter C5Quarter C#4Quarter E4Quarter", + "Treble G4Quarter A4Quarter A4Quarter B4Quarter", + "Treble B4Quarter C4Quarter D4Quarter D4Quarter", + "Treble F4Quarter D#4Quarter C#4Quarter A4Quarter", + "Treble C#4Quarter B4Quarter B4Quarter E4Quarter" + ], + [ + "Treble F#4Quarter D#4Quarter D#4Quarter A#4Quarter", + "Treble D4Quarter G4Quarter G4Quarter G4Quarter", + "Treble C#4Quarter C5Quarter E4Quarter F4Quarter", + "Treble C4Quarter D#4Quarter B4Quarter G#4Quarter", + "Treble G#4Quarter G4Quarter A4Quarter C5Quarter" + ], + [ + "Bass C3Quarter D3Quarter D3Quarter", + "Bass C3Quarter C3Quarter D3Quarter", + "Bass D3Quarter D3Quarter D3Quarter", + "Bass C3Quarter D3Quarter C3Quarter", + "Bass E3Quarter E3Quarter D3Quarter" + ], + [ + "Bass D3Quarter A3Quarter F3Quarter", + "Bass E3Quarter C3Quarter G3Quarter", + "Bass F3Quarter D3Quarter C3Quarter", + "Bass F3Quarter G3Quarter D3Quarter", + "Bass F3Quarter F3Quarter G3Quarter" + ], + [ + "Bass F3Quarter A3Quarter D3Quarter A3Quarter", + "Bass E3Quarter G3Quarter A3Quarter A3Quarter", + "Bass C3Quarter B3Quarter G3Quarter G3Quarter", + "Bass C3Quarter F3Quarter C4Quarter C4Quarter", + "Bass F3Quarter C3Quarter B3Quarter E3Quarter" + ], + [ + "Bass E3Quarter C4Quarter C#3Quarter B3Quarter", + "Bass G3Quarter G3Quarter B3Quarter B3Quarter", + "Bass F3Quarter A3Quarter F3Quarter D#3Quarter", + "Bass E3Quarter C3Quarter D#3Quarter B3Quarter", + "Bass E3Quarter E3Quarter B3Quarter E3Quarter" + ], + [ + "Bass G#3Quarter A#3Quarter E3Quarter F3Quarter", + "Bass F3Quarter D3Quarter F#3Quarter A#3Quarter", + "Bass G#3Quarter G3Quarter C3Quarter G#3Quarter", + "Bass C3Quarter C4Quarter C4Quarter D3Quarter", + "Bass D#3Quarter C3Quarter C#3Quarter C4Quarter" + ] + ] +} diff --git a/src/activities/play_piano/play_piano.js b/src/activities/play_piano/play_piano.js new file mode 100644 --- /dev/null +++ b/src/activities/play_piano/play_piano.js @@ -0,0 +1,116 @@ +/* GCompris - play_piano.js + * + * 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 . + */ +.pragma library +.import QtQuick 2.6 as Quick +.import "qrc:/gcompris/src/core/core.js" as Core + +var currentLevel = 0 +var currentSubLevel = 0 +var numberOfLevel = 10 +var noteIndexAnswered +var items +var levels +var incorrectAnswers = [] +var isIntroductoryAudioPlaying = false + +function start(items_) { + items = items_ + currentLevel = 0 + levels = items.parser.parseFromUrl("qrc:/gcompris/src/activities/play_piano/dataset.json").levels + items.introductoryAudioTimer.start() + initLevel() +} + +function stop() { + items.multipleStaff.stopAudios() +} + +function initLevel() { + items.bar.level = currentLevel + 1 + currentSubLevel = 0 + Core.shuffle(levels[currentLevel]) + nextSubLevel() +} + +function initSubLevel() { + var currentSubLevelMelody = levels[currentLevel][currentSubLevel - 1] + noteIndexAnswered = -1 + items.multipleStaff.loadFromData(currentSubLevelMelody) + + if(!isIntroductoryAudioPlaying && !items.iAmReady.visible) + items.multipleStaff.play() +} + +function nextSubLevel() { + currentSubLevel++ + incorrectAnswers = [] + items.score.currentSubLevel = currentSubLevel + if(currentSubLevel > levels[currentLevel].length) + nextLevel() + else + initSubLevel() +} + +function undoPreviousAnswer() { + if(noteIndexAnswered >= 0) { + items.multipleStaff.revertAnswer(noteIndexAnswered + 1) + if(incorrectAnswers.indexOf(noteIndexAnswered) != -1) + incorrectAnswers.pop() + + noteIndexAnswered-- + } +} + +function checkAnswer(noteName) { + var currentSubLevelNotes = levels[currentLevel][currentSubLevel - 1].split(' ') + if(noteIndexAnswered < (currentSubLevelNotes.length - 2)) { + noteIndexAnswered++ + var currentNote = currentSubLevelNotes[noteIndexAnswered + 1] + if((noteName + "Quarter") === currentNote) + items.multipleStaff.indicateAnsweredNote(true, noteIndexAnswered + 1) + else { + incorrectAnswers.push(noteIndexAnswered) + items.multipleStaff.indicateAnsweredNote(false, noteIndexAnswered + 1) + } + + if(noteIndexAnswered === (currentSubLevelNotes.length - 2)) { + if(incorrectAnswers.length === 0) + items.bonus.good("flower") + else + items.bonus.bad("flower") + } + } +} + +function nextLevel() { + if(numberOfLevel <= ++currentLevel) { + currentLevel = 0 + } + initLevel() +} + +function previousLevel() { + if(--currentLevel < 0) { + currentLevel = numberOfLevel - 1 + } + initLevel() +} diff --git a/src/activities/play_piano/play_piano.svg b/src/activities/play_piano/play_piano.svg new file mode 100644 --- /dev/null +++ b/src/activities/play_piano/play_piano.svg @@ -0,0 +1,619 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +