diff --git a/src/activities/note_names/ActivityInfo.qml b/src/activities/note_names/ActivityInfo.qml index 6796f9b52..cfd22ad2b 100644 --- a/src/activities/note_names/ActivityInfo.qml +++ b/src/activities/note_names/ActivityInfo.qml @@ -1,38 +1,38 @@ /* GCompris - ActivityInfo.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * 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: "note_names/NoteNames.qml" difficulty: 4 icon: "note_names/note_names.svg" author: "Aman Kumar Gupta <gupta2140@gmail.com>" demo: true title: qsTr("Name that Note") description: qsTr("Learn the names of the notes, in bass and treble clef.") //intro: "Identify the note and press the correct piano key" goal: qsTr("To develop a good understanding of note position and naming convention. To prepare for the piano player and composition activity.") - prerequisite: qsTr("None") + prerequisite: "" manual: qsTr("Identify the notes correctly and score a 100% to complete a level.") credit: "" section: "discovery sound" createdInVersion: 9500 } diff --git a/src/activities/note_names/NoteNames.qml b/src/activities/note_names/NoteNames.qml index 2fb689de5..db5b13f54 100644 --- a/src/activities/note_names/NoteNames.qml +++ b/src/activities/note_names/NoteNames.qml @@ -1,414 +1,405 @@ /* GCompris - NoteNames.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * 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 "../../core" import "../piano_composition" import "note_names.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property bool horizontalLayout: width > height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { + var keyNoteBindings = {} + keyNoteBindings[Qt.Key_1] = 'C' + keyNoteBindings[Qt.Key_2] = 'D' + keyNoteBindings[Qt.Key_3] = 'E' + keyNoteBindings[Qt.Key_4] = 'F' + keyNoteBindings[Qt.Key_5] = 'G' + keyNoteBindings[Qt.Key_6] = 'A' + keyNoteBindings[Qt.Key_7] = 'B' + if(!introMessage.visible && !iAmReady.visible && !messageBox.visible && multipleStaff.musicElementModel.count - 1) { - // If the key pressed matches the note, pass the correct answer as parameter, else pass a wrong answer. - if(event.key === Qt.Key_1) { - isCorrectKey('C') - } - else if(event.key === Qt.Key_2) { - isCorrectKey('D') - } - else if(event.key === Qt.Key_3) { - isCorrectKey('E') - } - else if(event.key === Qt.Key_4) { - isCorrectKey('F') - } - else if(event.key === Qt.Key_5) { - isCorrectKey('G') - } - else if(event.key === Qt.Key_6) { - isCorrectKey('A') - } - else if(event.key === Qt.Key_7) { - isCorrectKey('B') + if(keyNoteBindings[event.key]) { + // If the key pressed matches the note, pass the correct answer as parameter. + isCorrectKey(keyNoteBindings[event.key]) } else if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { doubleOctave.currentOctaveNb-- } else if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { doubleOctave.currentOctaveNb++ } } } function isCorrectKey(key) { if(Activity.newNotesSequence[Activity.currentNoteIndex][0] === key) Activity.correctAnswer() else items.displayNoteNameTimer.start() } // 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 bar: bar property alias multipleStaff: multipleStaff property alias doubleOctave: doubleOctave property alias bonus: bonus property alias iAmReady: iAmReady property alias messageBox: messageBox property alias addNoteTimer: addNoteTimer property alias dataset: dataset property alias progressBar: progressBar property alias introMessage: introMessage property bool isTutorialMode: true property alias displayNoteNameTimer: displayNoteNameTimer } Loader { id: dataset asynchronous: false source: "qrc:/gcompris/src/activities/note_names/resource/dataset_01.qml" } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string clefType: "Treble" Timer { id: displayNoteNameTimer interval: 2000 onRunningChanged: { if(running) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() messageBox.visible = true } else { messageBox.visible = false if(progressBar.percentage != 100 && Activity.newNotesSequence.length) { Activity.wrongAnswer() addNoteTimer.resume() } } } } Rectangle { id: messageBox width: label.width + 20 height: label.height + 20 border.width: 5 border.color: "black" anchors.centerIn: multipleStaff radius: 10 z: 11 visible: false onVisibleChanged: text = Activity.targetNotes[0] == undefined ? "" : items.isTutorialMode ? qsTr("New note: %1").arg(Activity.targetNotes[0]) : Activity.newNotesSequence[Activity.currentNoteIndex] property string text GCText { id: label anchors.centerIn: parent fontSize: mediumSize text: parent.text } MouseArea { anchors.fill: parent enabled: items.isTutorialMode onClicked: { items.multipleStaff.pauseNoteAnimation() items.multipleStaff.musicElementModel.remove(1) Activity.showTutorial() } } } Rectangle { id: colorLayer anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 visible: !introMessage.visible onVisibleChanged: { messageBox.visible = false } onClicked: { Activity.initLevel() } } IntroMessage { id: introMessage anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: 12 } AdvancedTimer { id: addNoteTimer onTriggered: { Activity.noteIndexToDisplay = (Activity.noteIndexToDisplay + 1) % Activity.newNotesSequence.length Activity.displayNote(Activity.newNotesSequence[Activity.noteIndexToDisplay]) } } ProgressBar { id: progressBar height: 20 * ApplicationInfo.ratio width: parent.width / 4 property int percentage: 0 value: percentage maximumValue: 100 visible: !items.isTutorialMode anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 10 } GCText { anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" - //: %1 is replaced by a number (parent.value) and is followed by a % symbol. + //: The following translation represents percentage. text: qsTr("%1%").arg(parent.value) z: 2 } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.78 height: horizontalLayout ? parent.height * 0.9 : parent.height * 0.7 nbStaves: 1 clef: clefType notesColor: "red" isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: progressBar.height + 20 flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 2.7 noteAnimationEnabled: true onNoteAnimationFinished: { if(!items.isTutorialMode) displayNoteNameTimer.start() } } // We present a pair of two joint piano keyboard octaves. Item { id: doubleOctave width: parent.width * 0.95 height: horizontalLayout ? parent.height * 0.22 : 2 * parent.height * 0.18 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 30 readonly property int nbJointKeyboards: 2 readonly property int maxNbOctaves: 3 property int currentOctaveNb: 0 property var coloredKeyLabels: [] Repeater { id: octaveRepeater anchors.fill: parent model: doubleOctave.nbJointKeyboards PianoOctaveKeyboard { id: pianoKeyboard width: horizontalLayout ? octaveRepeater.width / 2 : octaveRepeater.width height: horizontalLayout ? octaveRepeater.height : octaveRepeater.height / 2 blackLabelsVisible: false blackKeysEnabled: blackLabelsVisible whiteKeysEnabled: !messageBox.visible && multipleStaff.musicElementModel.count > 1 onNoteClicked: Activity.checkAnswer(note) currentOctaveNb: doubleOctave.currentOctaveNb anchors.top: (index === 1) ? octaveRepeater.top : undefined anchors.topMargin: horizontalLayout ? 0 : -15 anchors.bottom: (index === 0) ? octaveRepeater.bottom : undefined anchors.right: (index === 1) ? octaveRepeater.right : undefined coloredKeyLabels: doubleOctave.coloredKeyLabels labelsColor: "red" // The octaves sets corresponding to respective clef types are in pairs for the joint piano keyboards at a time when displaying. whiteKeyNoteLabelsBass: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(0, 4), // F1 to B1 whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18) // C3 to B3 ] } else { return [ whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25) // C4 to B4 ] } } whiteKeyNoteLabelsTreble: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32) // C5 to B5 ] } else { return [ whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32), // C5 to B5 whiteKeyNoteLabelsArray.slice(32, 34) // C6 to D6 ] } } } } } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb > 0) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top left: doubleOctave.left leftMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb-- } } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb < doubleOctave.maxNbOctaves - 1) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top right: doubleOctave.right rightMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb++ } } } OptionsRow { id: optionsRow visible: false } 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: { iAmReady.visible = true Activity.initLevel() } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/note_names/resource/dataset_01.qml b/src/activities/note_names/resource/dataset_01.qml index 06438c087..37036e39b 100644 --- a/src/activities/note_names/resource/dataset_01.qml +++ b/src/activities/note_names/resource/dataset_01.qml @@ -1,105 +1,105 @@ /* GCompris - dataset_01.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (Qt Quick port) * 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.1 +import QtQuick 2.6 QtObject { property var levels: [ { "clef": "Treble", "sequence": ["C4", "G4"] }, { "clef": "Bass", "sequence": ["C3", "F3"] }, { "clef": "Treble", "sequence": ["B3", "D4", "F4", "A4"] }, { "clef": "Bass", "sequence": ["B2", "D3","E3", "G3"] }, { "clef": "Treble", "sequence": ["C5", "G5"] }, { "clef": "Bass", "sequence": ["C2", "F2"] }, { "clef": "Treble", "sequence": ["B4", "D5", "F5", "A5"] }, { "clef": "Bass", "sequence": ["B1", "D2","E2", "G2"] }, { "clef": "Treble", "sequence": ["E4", "E5"] }, { "clef": "Bass", "sequence": ["B3", "D4"] }, { "clef": "Treble", "sequence": ["G3", "C6"] }, { "clef": "Bass", "sequence": ["A2", "A3", "C4", "E4"] }, { "clef": "Treble", "sequence": ["F3", "A3"] }, { "clef": "Bass", "sequence": ["G4", "A4"] }, { "clef": "Treble", "sequence": ["B5", "D5"] }, { "clef": "Bass", "sequence": ["B4"] }, { "clef": "Treble", "sequence": ["D3", "E3"] }, { "clef": "Bass", "sequence": ["F1", "G1", "A1"] } ] property string objective: qsTr("This activity will teach you to read notes from F1 in bass clef up to D6 in treble clef.
For each level you will learn new notes and train the one you have already learned.
Reference notes are colored in red and will help you to read the notes placed around them.") property var referenceNotes: { "Treble": ["C", "G"], "Bass": ["F", "C"] } } diff --git a/src/activities/piano_composition/Piano_composition.qml b/src/activities/piano_composition/Piano_composition.qml index 0a9ed09a9..5afb14976 100644 --- a/src/activities/piano_composition/Piano_composition.qml +++ b/src/activities/piano_composition/Piano_composition.qml @@ -1,446 +1,431 @@ /* GCompris - Piano_composition.qml * * Copyright (C) 2016 Johnny Jazeix * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (Qt Quick port) * 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 "../../core" import "qrc:/gcompris/src/core/core.js" as Core import "piano_composition.js" as Activity import "melodies.js" as Dataset ActivityBase { id: activity onStart: focus = true onStop: {} property bool horizontalLayout: background.width > background.height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { - if(event.key === Qt.Key_1) { - piano.keyRepeater.itemAt(0).whiteKey.keyPressed() - } - if(event.key === Qt.Key_2) { - piano.keyRepeater.itemAt(1).whiteKey.keyPressed() - } - if(event.key === Qt.Key_3) { - piano.keyRepeater.itemAt(2).whiteKey.keyPressed() - } - if(event.key === Qt.Key_4) { - piano.keyRepeater.itemAt(3).whiteKey.keyPressed() - } - if(event.key === Qt.Key_5) { - piano.keyRepeater.itemAt(4).whiteKey.keyPressed() - } - if(event.key === Qt.Key_6) { - piano.keyRepeater.itemAt(5).whiteKey.keyPressed() - } - if(event.key === Qt.Key_7) { - piano.keyRepeater.itemAt(6).whiteKey.keyPressed() - } - if(event.key === Qt.Key_F1 && piano.blackKeysEnabled) { - findBlackKey(1) - } - if(event.key === Qt.Key_F2 && piano.blackKeysEnabled) { - findBlackKey(2) - } - if(event.key === Qt.Key_F3 && piano.blackKeysEnabled) { - findBlackKey(3) - } - if(event.key === Qt.Key_F4 && piano.blackKeysEnabled) { - findBlackKey(4) - } - if(event.key === Qt.Key_F5 && piano.blackKeysEnabled) { - findBlackKey(5) + 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_F1] = 1 + keyboardBindings[Qt.Key_F2] = 2 + keyboardBindings[Qt.Key_F3] = 3 + keyboardBindings[Qt.Key_F4] = 4 + keyboardBindings[Qt.Key_F5] = 5 + + if(event.key >= Qt.Key_1 && event.key <= Qt.Key_7) { + 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]) } if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { piano.currentOctaveNb-- } if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { piano.currentOctaveNb++ } if(event.key === Qt.Key_Delete) { optionsRow.clearButtonClicked() } if(event.key === Qt.Key_Backspace) { optionsRow.undoButtonClicked() } if(event.key === Qt.Key_Space) { multipleStaff.play() } } 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 bar: bar property alias bonus: bonus property alias multipleStaff: multipleStaff property string staffLength: "short" property alias melodyList: melodyList property alias file: file property alias piano: piano property alias optionsRow: optionsRow property alias lyricsArea: lyricsArea } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string currentType: "Quarter" property string restType: "Quarter" property string clefType: bar.level == 2 ? "Bass" : "Treble" property bool isLyricsMode: (optionsRow.lyricsOrPianoModeIndex === 1) && optionsRow.lyricsOrPianoModeOptionVisible File { id: file onError: console.error("File error: " + msg) } Item { id: clickedOptionMessage signal show(string message) onShow: { messageText.text = message messageAnimation.stop() messageAnimation.start() } width: horizontalLayout ? parent.width / 12 : parent.width / 6 height: width * 0.4 visible: false anchors.top: optionsRow.bottom anchors.horizontalCenter: optionsRow.horizontalCenter z: 5 Rectangle { id: messageRectangle width: messageText.contentWidth + 5 height: messageText.height + 5 anchors.centerIn: messageText color: "black" opacity: 0.5 border.width: 3 border.color: "black" radius: 15 } GCText { id: messageText anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit color: "white" } SequentialAnimation { id: messageAnimation onStarted: clickedOptionMessage.visible = true PauseAnimation { duration: 1000 } NumberAnimation { targets: [messageRectangle, messageText] property: "opacity" to: 0 duration: 200 } onStopped: { clickedOptionMessage.visible = false messageRectangle.opacity = 0.5 messageText.opacity = 1 } } } MelodyList { id: melodyList onClose: { visible = false piano.enabled = true bar.visible = true focus = false activity.focus = true } } Rectangle { id: instructionBox radius: 10 width: background.width * 0.98 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: Activity.instructions[bar.level - 1].text } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.50 : parent.width * 0.8 height: horizontalLayout ? parent.height * 0.58 : parent.height * 0.3 nbStaves: 2 clef: clefType coloredNotes: ['C','D', 'E', 'F', 'G', 'A', 'B'] anchors.right: horizontalLayout ? parent.right: undefined anchors.horizontalCenter: horizontalLayout ? undefined : parent.horizontalCenter anchors.top: instructionBox.bottom anchors.topMargin: parent.height * 0.1 anchors.rightMargin: parent.width * 0.043 noteHoverEnabled: true onNoteClicked: { if(selectedIndex === noteIndex) selectedIndex = -1 else { selectedIndex = noteIndex background.clefType = musicElementModel.get(selectedIndex).soundPitch_ playNoteAudio(musicElementModel.get(selectedIndex).noteName_, musicElementModel.get(selectedIndex).noteType_, background.clefType, musicElementRepeater.itemAt(selectedIndex).duration) } } } GCButtonScroll { id: multipleStaffFlickButton anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.verticalCenter: multipleStaff.verticalCenter width: horizontalLayout ? parent.width * 0.033 : parent.width * 0.06 height: width * heightRatio onUp: multipleStaff.flickableStaves.flick(0, multipleStaff.height * 1.3) onDown: multipleStaff.flickableStaves.flick(0, -multipleStaff.height * 1.3) upVisible: multipleStaff.flickableStaves.visibleArea.yPosition > 0 downVisible: (multipleStaff.flickableStaves.visibleArea.yPosition + multipleStaff.flickableStaves.visibleArea.heightRatio) < 1 } PianoOctaveKeyboard { id: piano width: horizontalLayout ? parent.width * 0.34 : parent.width * 0.7 height: horizontalLayout ? parent.height * 0.40 : parent.width * 0.26 anchors.horizontalCenter: horizontalLayout ? undefined : parent.horizontalCenter anchors.left: horizontalLayout ? parent.left : undefined anchors.leftMargin: parent.width * 0.04 anchors.top: horizontalLayout ? multipleStaff.top : multipleStaff.bottom anchors.topMargin: horizontalLayout ? parent.height * 0.08 : parent.height * 0.025 blackLabelsVisible: [3, 4, 5, 6, 7, 8].indexOf(items.bar.level) == -1 ? false : true useSharpNotation: bar.level != 4 blackKeysEnabled: bar.level > 2 visible: !background.isLyricsMode currentOctaveNb: (background.clefType === "Bass") ? 0 : 1 onNoteClicked: { parent.addMusicElementAndPushToStack(note, currentType) } } function addMusicElementAndPushToStack(noteName, noteType, elementType) { if(noteType === "Rest") elementType = "rest" else if(elementType == undefined) elementType = "note" var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.addMusicElement(elementType, noteName, noteType, false, true, background.clefType) } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: piano.width / 7 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (piano.currentOctaveNb > 0) && piano.visible anchors { verticalCenter: piano.verticalCenter right: piano.left } MouseArea { anchors.fill: parent onClicked: piano.currentOctaveNb-- } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: piano.width / 7 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (piano.currentOctaveNb < piano.maxNbOctaves - 1) && piano.visible anchors { verticalCenter: piano.verticalCenter left: piano.right } MouseArea { anchors.fill: parent onClicked: piano.currentOctaveNb++ } } LyricsArea { id: lyricsArea } OptionsRow { id: optionsRow anchors.top: instructionBox.bottom anchors.topMargin: 10 anchors.horizontalCenter: parent.horizontalCenter noteOptionsVisible: bar.level > 4 playButtonVisible: true clefButtonVisible: bar.level > 2 clearButtonVisible: true undoButtonVisible: true openButtonVisible: bar.level > 6 saveButtonVisible: bar.level > 6 changeAccidentalStyleButtonVisible: bar.level >= 4 lyricsOrPianoModeOptionVisible: bar.level > 6 restOptionsVisible: bar.level > 5 bpmVisible: true onUndoButtonClicked: { Activity.undoChange() } onClearButtonClicked: { if((multipleStaff.musicElementModel.count > 1) && multipleStaff.selectedIndex === -1) { Core.showMessageDialog(main, qsTr("You have not selected any note. Do you want to erase all the notes?"), qsTr("Yes"), function() { Activity.undoStack = [] lyricsArea.resetLyricsArea() multipleStaff.eraseAllNotes() multipleStaff.nbStaves = 2 }, qsTr("No"), null, null ) } else if((multipleStaff.musicElementModel.count > 1) && !multipleStaff.musicElementModel.get(multipleStaff.selectedIndex).isDefaultClef_) { var noteIndex = multipleStaff.selectedIndex var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.eraseNote(noteIndex) } } onOpenButtonClicked: { melodyList.melodiesModel.clear() var dataset = Dataset.get() for(var i = 0; i < dataset.length; i++) { melodyList.melodiesModel.append(dataset[i]) } piano.enabled = false bar.visible = false melodyList.visible = true melodyList.forceActiveFocus() } onSaveButtonClicked: Activity.saveMelody() onClefAdded: { var insertingIndex = multipleStaff.selectedIndex === -1 ? multipleStaff.musicElementModel.count : multipleStaff.selectedIndex var tempModel = multipleStaff.createNotesBackup() Activity.pushToStack(tempModel) multipleStaff.addMusicElement("clef", "", "", false, false, background.clefType) if(background.clefType === "Bass") piano.currentOctaveNb = 0 else piano.currentOctaveNb = 1 } onBpmDecreased: { if(multipleStaff.bpmValue - 1 >= 1) multipleStaff.bpmValue-- } onBpmIncreased: { multipleStaff.bpmValue++ } onEmitOptionMessage: clickedOptionMessage.show(message) } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/play_piano/ActivityInfo.qml b/src/activities/play_piano/ActivityInfo.qml index 6fd6f82cf..f227e6cd0 100644 --- a/src/activities/play_piano/ActivityInfo.qml +++ b/src/activities/play_piano/ActivityInfo.qml @@ -1,44 +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.
Each level has 5 sublevels.") + 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/PlayPiano.qml b/src/activities/play_piano/PlayPiano.qml index 5c404f306..2749d5feb 100644 --- a/src/activities/play_piano/PlayPiano.qml +++ b/src/activities/play_piano/PlayPiano.qml @@ -1,367 +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) { - piano.keyRepeater.itemAt(0).whiteKey.keyPressed() - } - if(event.key === Qt.Key_2) { - piano.keyRepeater.itemAt(1).whiteKey.keyPressed() - } - if(event.key === Qt.Key_3) { - piano.keyRepeater.itemAt(2).whiteKey.keyPressed() - } - if(event.key === Qt.Key_4) { - piano.keyRepeater.itemAt(3).whiteKey.keyPressed() - } - if(event.key === Qt.Key_5) { - piano.keyRepeater.itemAt(4).whiteKey.keyPressed() - } - if(event.key === Qt.Key_6) { - piano.keyRepeater.itemAt(5).whiteKey.keyPressed() - } - if(event.key === Qt.Key_7) { - piano.keyRepeater.itemAt(6).whiteKey.keyPressed() - } - if(event.key === Qt.Key_8) { - piano.keyRepeater.itemAt(7).whiteKey.keyPressed() - } - if(event.key === Qt.Key_F1 && piano.blackKeysEnabled) { - findBlackKey(1) - } - if(event.key === Qt.Key_F2 && piano.blackKeysEnabled) { - findBlackKey(2) - } - if(event.key === Qt.Key_F3 && piano.blackKeysEnabled) { - findBlackKey(3) - } - if(event.key === Qt.Key_F4 && piano.blackKeysEnabled) { - findBlackKey(4) + if(event.key >= Qt.Key_1 && event.key <= Qt.Key_8) { + piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() } - if(event.key === Qt.Key_F5 && piano.blackKeysEnabled) { - findBlackKey(5) + else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { + if(piano.blackKeysEnabled) + findBlackKey(keyboardBindings[event.key]) } - if(event.key === Qt.Key_Space) { + else if(event.key === Qt.Key_Space) { multipleStaff.play() } - if(event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { + 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) + 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) } } }