diff --git a/src/activities/note_names/NoteNames.qml b/src/activities/note_names/NoteNames.qml index 95eddcced..abb2eba11 100755 --- a/src/activities/note_names/NoteNames.qml +++ b/src/activities/note_names/NoteNames.qml @@ -1,463 +1,463 @@ /* GCompris - NoteNames.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (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.Controls 1.0 import GCompris 1.0 import "../../core" import "../piano_composition" import "note_names.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" property bool keyboardMode: false property bool horizontalLayout: background.width > background.height ? true : false signal start signal stop 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 alias background: background property GCAudio audioEffects: activity.audioEffects property alias staff: staff property alias bar: bar property alias bonus: bonus property alias gridRepeater: gridRepeater property alias bottomNotesRepeater: bottomNotesRepeater property alias okButton: okButton property alias score: score property string staffLength: "long" readonly property string clef: items.bar.level > 10 ? "bass" : "treble" } onStart: { Activity.start(items) } onStop: { Activity.stop() } Rectangle { id: instructionBox radius: 10 width: background.width / 1.7 height: horizontalLayout ? background.height / 5 : background.height / 5 anchors.horizontalCenter: parent.horizontalCenter opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { id: instructionText visible: !staffText.visible color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.1 anchors.leftMargin: parent.width * 0.1 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: [2, 3, 4, 12, 13, 14].indexOf(bar.level) !== -1 ? qsTr("Click on the note name to match the pitch. Then click OK to check.") : [5, 6, 7, 15, 16, 17].indexOf(bar.level) !== -1 ? qsTr("Now there are sharp notes. These pitches are raised a half step.") : // [8, 9, 10, 18, 19, 20] qsTr("Now there are flat notes. These pitches are lowered a half step.") } GCText { id: staffText visible: bar.level == 1 || bar.level == 11 height: background.height / 5 width: background.width / 1.5 z: 3 color: "black" fontSizeMode: Text.Fit anchors.fill: parent anchors.rightMargin: parent.width * 0.1 anchors.leftMargin: parent.width * 0.1 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap text: bar.level == 1 ? qsTr("These are the eight basic notes in treble clef. They form the C Major Scale.") : qsTr("These are the eight basic notes in bass clef. They also form the C Major Scale. Notice that the note positions are different than in treble clef.") } } Rectangle { id: playScale width: horizontalLayout ? parent.width * 0.3 : parent.width * 0.35 height: 30 * ApplicationInfo.ratio color: "#d8ffffff" border.color: "#2a2a2a" border.width: 3 radius: 8 z: 5 anchors.top: instructionBox.bottom anchors.topMargin: 15 anchors.left: background.left anchors.leftMargin: background.width * 0.15 visible: bar.level == 1 || bar.level == 11 GCText { id: playScaleText anchors.centerIn: parent text: qsTr("Play scale") fontSizeMode: Text.Fit wrapMode: Text.Wrap } MouseArea { id: playScaleArea anchors.fill: parent onClicked: { // items.staff.eraseAllNotes() // for(var i = 0; i < Activity.bottomNotes.length; i++) { // var noteToPlay = 'qrc:/gcompris/src/activities/piano_composition/resource/' + items.clef + '_pitches/' + '1' + '/' + Activity.bottomNotes[i].note + '.wav'; // items.staff.addNote(noteToPlay, 4, "", true); // items.audioEffects.append(noteToPlay) // } - items.staff.play() + staff.play() } } states: [ State { name: "notclicked" PropertyChanges { target: playScale scale: 1.0 } }, State { name: "clicked" when: playScaleArea.pressed PropertyChanges { target: playScale scale: 0.9 } }, State { name: "hover" when: playScaleArea.containsMouse PropertyChanges { target: playScale scale: 1.1 } } ] Behavior on scale { NumberAnimation { duration: 70 } } } Rectangle { id: playButton width: horizontalLayout ? parent.width * 0.3 : parent.width * 0.35 height: 30 * ApplicationInfo.ratio color: "#d8ffffff" border.color: "#2a2a2a" border.width: 3 radius: 8 z: 5 anchors.top: instructionBox.bottom anchors.topMargin: 15 anchors.leftMargin: 30 anchors.left: playScale.right visible: bar.level == 1 || bar.level == 11 GCText { id: playButtonText anchors.centerIn: parent text: qsTr("Start levels") fontSizeMode: Text.Fit wrapMode: Text.Wrap } MouseArea { id: playButtonArea anchors.fill: parent onClicked: { Activity.nextLevel() } } states: [ State { name: "notclicked" PropertyChanges { target: playButton scale: 1.0 } }, State { name: "clicked" when: playButtonArea.pressed PropertyChanges { target: playButton scale: 0.9 } }, State { name: "hover" when: playButtonArea.containsMouse PropertyChanges { target: playButton scale: 1.1 } } ] Behavior on scale { NumberAnimation { duration: 70 } } } MultipleStaff { id: staff nbStaves: 1 clef: bar.level <= 10 ? "treble" : "bass" height: background.height / 4 width: bar.level == 1 || bar.level == 11 ? background.width * 0.8 : background.width / 2 anchors { bottom: parent.bottom bottomMargin: bar.level != 11 ? parent.height * 0.35 : parent.height * 0.25 horizontalCenter: bar.level == 1 || bar.level == 11 ? parent.horizontalCenter : undefined left: parent.left leftMargin: horizontalLayout ? parent.width * 0.1 : parent.width * 0.05 } nbMaxNotesPerStaff: bar.level == 1 || bar.level == 11 ? 8 : 1 firstNoteX: bar.level == 1 || bar.level == 11 ? width / 5 : width / 2 } Grid { id: bottomNotesGrid rows: 1 spacing: horizontalLayout ? background.width * 0.03 : background.width * 0.01 anchors.bottom: background.bottom anchors.bottomMargin: background.height * 0.01 anchors.horizontalCenter: parent.horizontalCenter height: staff.height visible: (bar.level == 1 || bar.level == 11) ? true : false property int itemWidth: horizontalLayout ? background.width * 0.05 : background.width * 0.1 property int itemHeight: itemWidth Repeater { id: bottomNotesRepeater model: Activity.bottomNotes Rectangle { id: notes color: dummyNote.noteColorMap[Activity.bottomNotes[index].note] width: bottomNotesGrid.itemWidth height: bottomNotesGrid.itemHeight radius: width / 5 border.color: "black" GCText { id: bottomNotesText text: parseInt(Activity.bottomNotes[0].note) > 0 ? dummyNote.whiteNoteName[Activity.bottomNotes[index].note] : dummyNote.blackNoteName[Activity.bottomNotes[index].note] anchors.centerIn: parent fontSizeMode: Text.Fit horizontalAlignment: Text.AlignHCenter } MouseArea { id: buttonClick anchors.fill: parent onClicked: { select() } } function select() { grid.currentIndex = index var noteToPlay = 'qrc:/gcompris/src/activities/piano_composition/resource/' + items.clef + '_pitches/' + '1' + '/' + Activity.bottomNotes[index].note + '.wav' items.audioEffects.play(noteToPlay) } } } } DialogHelp { id: dialogHelp onClose: home() } Keys.onPressed: { if(event.key === Qt.Key_Space) { grid.currentItem.select() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onEnterPressed: { grid.currentItem.select(); Activity.checkAnswer(okButton.currentAnswer); } Keys.onReturnPressed: { grid.currentItem.select(); Activity.checkAnswer(okButton.currentAnswer); } Keys.onRightPressed: grid.moveCurrentIndexRight(); Keys.onLeftPressed: grid.moveCurrentIndexLeft(); Keys.onDownPressed: grid.moveCurrentIndexDown(); Keys.onUpPressed: grid.moveCurrentIndexUp(); ListModel { id: gridRepeater } GridView { id: grid visible: instructionText.visible anchors { left: staff.right right: background.right leftMargin: 15 * ApplicationInfo.ratio top: instructionBox.bottom topMargin: horizontalLayout ? 15 * ApplicationInfo.ratio : 15 * ApplicationInfo.ratio bottom: background.bottom } keyNavigationWraps: true interactive: false model: gridRepeater cellWidth: itemWidth + 10 cellHeight: itemHeight + 10 height: staff.height property int itemWidth: 60 property int itemHeight: itemWidth delegate: Rectangle { id: highlightRectangle width: grid.itemWidth * 1.15 height: grid.itemHeight * 1.15 radius: width / 5 color: index === grid.currentIndex ? "red" : "transparent" border.color: index === grid.currentIndex ? "white" : "transparent" Rectangle { id: noteRectangle color: staff.noteIsColored ? dummyNote.noteColorMap[note] : "white" width: grid.itemWidth height: grid.itemHeight radius: width / 5 border.color: "black" anchors.centerIn: parent visible: true GCText { id: noteText text: parseInt(note) > 0 ? dummyNote.whiteNoteName[note] : dummyNote.blackNoteName[note] anchors.centerIn: parent fontSizeMode: Text.Fit horizontalAlignment: Text.AlignHCenter } MouseArea { id: buttonClick anchors.fill: parent onClicked: select() } } function select() { grid.currentIndex = index var noteToPlay = 'qrc:/gcompris/src/activities/piano_composition/resource/' + items.clef + '_pitches/' + '1' + '/' + note + '.wav' items.audioEffects.play(noteToPlay); okButton.currentAnswer = note } } } // Never visible, only used to access the note names, colors Note { id: dummyNote visible: false value: "1" blackType: [8, 9, 10, 18, 19, 20].indexOf(bar.level) === -1 ? "sharp" : "flat" } Image { id: okButton visible: instructionText.visible source:"qrc:/gcompris/src/core/resource/bar_ok.svg" width: Math.min(parent.width,parent.height) * 0.15 height: Math.min(parent.width,parent.height) * 0.15 anchors { left: parent.left bottom: parent.bottom topMargin: horizontalLayout ? 0.1 * parent.height : 0.01 * parent.height bottomMargin: parent.height * 0.2 leftMargin: 20 } property string currentAnswer: "" MouseArea { anchors.fill: parent onClicked: { if(okButton.currentAnswer !== "") Activity.checkAnswer(okButton.currentAnswer); okButton.currentAnswer = "" } } } Bar { id: bar content: BarEnumContent { value: (bar.level == 1 || bar.level == 11) ? (help | home | level ) : (help | home | level | reload) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.reloadLevel() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } Score { id: score anchors { bottom: parent.bottom right: parent.right bottomMargin: parent.height * 0.2 } visible: bar.level !== 1 && bar.level !== 11 } } } diff --git a/src/activities/piano_composition/MelodyList.qml b/src/activities/piano_composition/MelodyList.qml new file mode 100644 index 000000000..4ca512651 --- /dev/null +++ b/src/activities/piano_composition/MelodyList.qml @@ -0,0 +1,141 @@ +/* GCompris - MelodyList.qml + * + * Copyright (C) 2017 Divyam Madaan + * + * 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.js" as Activity +import "melodies.js" as Dataset + +Rectangle { + id: dialogBackground + color: "#696da3" + border.color: "black" + border.width: 1 + z: 1000 + anchors.fill: parent + visible: false + signal close + property alias melodiesModel: melodiesModel + property bool horizontalLayout: dialogBackground.width > dialogBackground.height ? true : false + + ListModel { + id: melodiesModel + } + + Row { + spacing: 2 + Item { width: 10; height: 1 } + + Column { + spacing: 10 + anchors.top: parent.top + Item { width: 1; height: 10 } + Rectangle { + id: titleRectangle + color: "#e6e6e6" + radius: 6.0 + width: dialogBackground.width - 30 + height: title.height * 1.2 + border.color: "black" + border.width: 2 + + GCText { + id: title + text: "Melodies" + width: dialogBackground.width - 30 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "black" + fontSize: 20 + font.weight: Font.DemiBold + wrapMode: Text.WordWrap + } + } + Rectangle { + color: "#e6e6e6" + radius: 6.0 + width: dialogBackground.width - 30 + height: dialogBackground.height - 100 + border.color: "black" + border.width: 2 + anchors.margins: 100 + + Flickable { + anchors.fill: parent + contentWidth: melodiesGrid.width + contentHeight: melodiesGrid.height + flickableDirection: Flickable.VerticalFlick + clip: true + Grid { + id: melodiesGrid + rows: 10 + columns: horizontalLayout ? 4 : 3 + spacing: 40 + + Repeater { + id: melodiesRepeater + model: melodiesModel + + Item { + id: melodiesItem + width: dialogBackground.width > dialogBackground.height ? dialogBackground.width * 0.2 : dialogBackground.width * 0.25 + height: dialogBackground.height * 0.2 + + Rectangle { + anchors.fill: parent + color: "orange" + radius: 10 + + Rectangle { + width: parent.width - anchors.margins + height: parent.height - anchors.margins + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.margins: parent.height/4 + radius: 10 + color: "#E8E8E8" //paper white + + GCText { + anchors.fill: parent + text: title + fontSizeMode: Text.Fit + wrapMode: Text.WordWrap + } + + MouseArea { + anchors.fill: parent + onClicked: { + items.staff2.loadFromData(melody) + } + } + } + } + } + } + } + } + } + Item { width: 1; height: 10 } + } + } + GCButtonCancel { + onClose: parent.close() + } +} diff --git a/src/activities/piano_composition/MultipleStaff.qml b/src/activities/piano_composition/MultipleStaff.qml index 220403ed0..7fc9ef738 100644 --- a/src/activities/piano_composition/MultipleStaff.qml +++ b/src/activities/piano_composition/MultipleStaff.qml @@ -1,157 +1,169 @@ /* GCompris - MultipleStaff.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (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 GCompris 1.0 import "../../core" Item { id: multipleStaff property int nbStaves property string clef property int distanceBetweenStaff: 20 property int currentStaff: 0 property int nbMaxNotesPerStaff: 6 property int firstNoteX: width / 5 property bool noteIsColored property bool isMetronomeDisplayed: false Column { spacing: parent.height * 0.05 Repeater { id: staves model: nbStaves Staff { id: staff clef: multipleStaff.clef height: (multipleStaff.height - distanceBetweenStaff * (nbStaves - 1)) / nbStaves width: multipleStaff.width y: index * (height + distanceBetweenStaff) lastPartition: index == nbStaves - 1 nbMaxNotesPerStaff: multipleStaff.nbMaxNotesPerStaff noteIsColored: multipleStaff.noteIsColored isMetronomeDisplayed: multipleStaff.isMetronomeDisplayed firstNoteX: multipleStaff.firstNoteX } } } function addNote(newValue_, newType_, newBlackType_, highlightWhenPlayed_) { if(staves.itemAt(currentStaff).notes.count > nbMaxNotesPerStaff) { if(currentStaff + 1 >= nbStaves) { return } else currentStaff++ } staves.itemAt(currentStaff).addNote(newValue_, newType_, newBlackType_, highlightWhenPlayed_); } function play() { musicTimer.currentPlayedStaff = 0; musicTimer.currentNote = 0; - musicTimer.interval = 500; + musicTimer.interval = 500 for(var v = 1 ; v < currentStaff ; ++ v) staves.itemAt(v).showMetronome = false; // Only display metronome if we want to staves.itemAt(0).showMetronome = isMetronomeDisplayed; musicTimer.start(); } function eraseAllNotes() { for(var v = 0 ; v <= currentStaff ; ++ v) staves.itemAt(v).eraseAllNotes(); currentStaff = 0; } + function getAllNotes() { + var melody = [] + for(var i = 0; i < nbStaves; i ++) { + for(var j = 0; j < staves.itemAt(i).notes.count; j++) { + melody.push({ + "type": staves.itemAt(i).notes.get(j).type, + "note": staves.itemAt(i).notes.get(j).mValue + }) + } + } + return melody + } + property var whiteNotes: ["C", "D", "E", "F", "G", "A", "B", "2C", "2D", "2E", "2F"] property var blackNotesSharp: ["C#", "D#", "F#", "G#", "A#", "2C#"] property var blackNotesFlat: ["DB", "EB", "GB", "AB", "BB"] function loadFromData(data) { eraseAllNotes() var melody = data.split(" "); multipleStaff.clef = melody[0]; for(var i = 1 ; i < melody.length ; ++ i) { var noteLength = melody[i].length; - var type = parseInt(melody[i][noteLength-1]); - var noteStr = melody[i].substr(0, noteLength-1).toUpperCase(); + var type = parseInt(melody[i][noteLength - 1]); + var noteStr = melody[i].substr(0, noteLength - 1).toUpperCase(); if(whiteNotes.indexOf(noteStr) != -1) - addNote(""+(whiteNotes.indexOf(noteStr)+1), type, "", false); + addNote("" + (whiteNotes.indexOf(noteStr) + 1), type, "", false); else if (blackNotesSharp.indexOf(melody[i][0]) != -1) { - addNote(""+(-1*blackNotesSharp.indexOf(noteStr)-1), type, "sharp", false); + addNote("" + (-1 * blackNotesSharp.indexOf(noteStr) - 1), type, "sharp", false); } else { - addNote(""+(-1*blackNotesFlat.indexOf(noteStr)-1), type, "flat", false); + addNote("" + (-1 * blackNotesFlat.indexOf(noteStr) - 1), type, "flat", false); } print(melody[i]); } } Timer { id: musicTimer property int currentPlayedStaff: 0 property int currentNote: 0 onRunningChanged: { if(!running && staves.itemAt(currentPlayedStaff).notes.get(currentNote) !== undefined) { var currentType = staves.itemAt(currentPlayedStaff).notes.get(currentNote).mType var note = staves.itemAt(currentPlayedStaff).notes.get(currentNote).mValue // TODO some notes does not play if they are played in the rcc directly... var noteToPlay = 'qrc:/gcompris/src/activities/piano_composition/resource/' + multipleStaff.clef + '_pitches/' + currentType + '/' + note + '.wav'; - items.audioEffects.play(noteToPlay); - staves.itemAt(currentPlayedStaff).notesRepeater.itemAt(currentNote).play() if(currentNote == 0) { staves.itemAt(currentPlayedStaff).initMetronome(); } - musicTimer.interval = staves.itemAt(currentPlayedStaff).notes.get(currentNote).mDuration; - currentNote ++; +// musicTimer.interval = staves.itemAt(currentPlayedStaff).notes.get(currentNote).mDuration; + if(staves.itemAt(currentPlayedStaff).notes.get(currentNote) !== undefined) { + musicTimer.interval = staves.itemAt(currentPlayedStaff).notes.get(currentNote).mDuration; + items.audioEffects.play(noteToPlay); + print("will play next " + JSON.stringify(staves.itemAt(currentPlayedStaff).notes.get(currentNote))); + staves.itemAt(currentPlayedStaff).notesRepeater.itemAt(currentNote).play() + currentNote ++; + } if(currentNote > nbMaxNotesPerStaff) { currentNote = 0; currentPlayedStaff ++; if(currentPlayedStaff < nbStaves && currentNote < staves.itemAt(currentPlayedStaff).notes.count) { print("play next staff"); staves.itemAt(currentPlayedStaff).showMetronome = isMetronomeDisplayed; if(currentPlayedStaff > 0) staves.itemAt(currentPlayedStaff - 1).showMetronome = false; staves.itemAt(currentPlayedStaff).playNote(currentNote); - musicTimer.start(); } } - else if(staves.itemAt(currentPlayedStaff).notes.get(currentNote) !== undefined) { - print("will play next " + JSON.stringify(staves.itemAt(currentPlayedStaff).notes.get(currentNote))); - staves.itemAt(currentPlayedStaff).notesRepeater.itemAt(currentNote).play() - musicTimer.start(); - } + musicTimer.start() } } } } diff --git a/src/activities/piano_composition/Note.qml b/src/activities/piano_composition/Note.qml index 5fadb4c7c..a7346c7eb 100644 --- a/src/activities/piano_composition/Note.qml +++ b/src/activities/piano_composition/Note.qml @@ -1,122 +1,122 @@ /* GCompris - Note.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (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 QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" Item { id: note property string value property bool noteIsColored: true property int type: wholeNote property string noteType: type == wholeNote ? "whole" : type == halfNote ? "half" : type == quarterNote ? "quarter" : type == eighthNote ? "eighth" : "" property string blackType: "" // empty, "flat" or "sharp" //width: noteImage.width //height: noteImage.height readonly property int wholeNote: 1 readonly property int halfNote: 2 readonly property int quarterNote: 4 readonly property int eighthNote: 8 readonly property int noteDuration: 2000 / type property var noteColorMap: { "1": "#FF0000", "2": "#FF7F00", "3": "#FFFF00", "4": "#32CD32", "5": "#6495ED", "6": "#D02090", "7": "#FF1493", "8": "#FF0000", "9": "#FF7F00", "10": "#FFFF00", "11": "#32CD32", "-1": "#FF6347", "-2": "#FFD700", "-3": "#20B2AA", "-4": "#8A2BE2", "-5": "#FF00FF" } property var whiteNoteName: { "1": qsTr("C"), "2": qsTr("D"), "3": qsTr("E"), "4": qsTr("F"), "5": qsTr("G"), "6": qsTr("A"), "7": qsTr("B"), "8": qsTr("C") } property var blackNoteName: blackType == "flat" ? flatNoteName : sharpNoteName property var sharpNoteName: { "-1": qsTr("C#"), "-2": qsTr("D#"), "-3": qsTr("F#"), "-4": qsTr("G#"), "-5": qsTr("A#")} property var flatNoteName: { "-1": qsTr("Db"), "-2": qsTr("Eb"), "-3": qsTr("Gb"), "-4": qsTr("Ab"), "-5": qsTr("Bb")} property bool highlightWhenPlayed: false property alias highlightTimer: highlightTimer Image { id: blackTypeImage source: blackType !== "" ? "qrc:/gcompris/src/activities/piano_composition/resource/black" + blackType + ".svg" : "" visible: value[0] === '-' sourceSize.width: noteImage.width/2.5 anchors.right: noteImage.left anchors.rightMargin: -width/2 anchors.bottom: noteImage.bottom anchors.bottomMargin: -height/2 fillMode: Image.PreserveAspectFit } Image { id: highlightImage - source: "qrc:/gcompris/src/activities/piano_composition/resource/note-highlight.svg" + source: "qrc:/gcompris/src/activities/piano_composition/resource/note_highlight.png" visible: false sourceSize.width: noteImage.width height: noteImage.height / 2 anchors.bottom: noteImage.bottom } Rectangle { id: highlightRectangle width: noteImage.width * 0.9 height: noteImage.height * 0.9 color: "red" opacity: 0.6 border.color: "white" radius: width / 6 visible: false } Image { id: noteImage source: "qrc:/gcompris/src/activities/piano_composition/resource/" + noteType + "-note.svg" sourceSize.width: 200 width: note.width height: note.height } // If the result is not good enough maybe have a rectangle and use opacity mask with a note ColorOverlay { anchors.fill: noteImage source: noteImage color: noteColorMap[value] // make image like it lays under red glass visible: noteIsColored } Timer { id: highlightTimer - interval: 1500 + interval: noteDuration onRunningChanged: { highlightRectangle.visible = running // highlightImage.visible = running } } } diff --git a/src/activities/piano_composition/Piano_composition.qml b/src/activities/piano_composition/Piano_composition.qml index 4e432f8f4..5677cd0d0 100644 --- a/src/activities/piano_composition/Piano_composition.qml +++ b/src/activities/piano_composition/Piano_composition.qml @@ -1,320 +1,352 @@ /* GCompris - Piano_composition.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (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.Controls 1.0 +import GCompris 1.0 import "../../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 ? true : false 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) { playNote('1') } if(event.key === Qt.Key_2) { playNote('2') } if(event.key === Qt.Key_3) { playNote('3') } if(event.key === Qt.Key_4) { playNote('4') } if(event.key === Qt.Key_5) { playNote('5') } if(event.key === Qt.Key_6) { playNote('6') } if(event.key === Qt.Key_7) { playNote('7') } if(event.key === Qt.Key_8) { playNote('8') } if(event.key === Qt.Key_F1 && bar.level >= 4) { playNote('-1') } if(event.key === Qt.Key_F2 && bar.level >= 4) { playNote('-2') } if(event.key === Qt.Key_F3 && bar.level >= 4) { playNote('-3') } if(event.key === Qt.Key_F4 && bar.level >= 4) { playNote('-4') } if(event.key === Qt.Key_F5 && bar.level >= 4) { playNote('-5') } if(event.key === Qt.Key_Delete) { staff2.eraseAllNotes() } if(event.key === Qt.Key_Space) { staff2.play() } } function playNote(note) { staff2.addNote(note, currentType, piano.useSharpNotation ? "sharp" : "flat", false) var noteToPlay = 'qrc:/gcompris/src/activities/piano_composition/resource/' + 'bass' + '_pitches/' + currentType + '/' + note + '.wav'; items.audioEffects.play(noteToPlay) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCAudio audioEffects: activity.audioEffects property alias bar: bar property alias bonus: bonus property alias staff2: staff2 property string staffLength: "short" + property alias melodyList: melodyList + property alias file: file } onStart: { Activity.start(items) } onStop: { Activity.stop() } property int currentType: 1 property string clefType: bar.level == 2 ? "bass" : "treble" + File { + id: file + onError: console.error("File error: " + msg) + } + + MelodyList { + id: melodyList + onClose: { + visible = false + piano.enabled = true + bar.visible = true + } + } + MultipleStaff { id: staff2 - width: horizontalLayout ? parent.width * 0.50 : parent.height * 0.4 - height: parent.height * 0.5 + width: horizontalLayout ? parent.width * 0.50 : parent.height * 0.8 + height: horizontalLayout ? parent.height * 0.5 : parent.height * 0.3 nbStaves: 3 clef: clefType == "bass" ? "bass" : "treble" nbMaxNotesPerStaff: 8 noteIsColored: true isMetronomeDisplayed: true anchors.right: parent.right - anchors.top: instructionBox.bottom - anchors.topMargin: parent.height * 0.13 + anchors.top: horizontalLayout ? instructionBox.bottom : piano.bottom + anchors.topMargin: horizontalLayout ? parent.height * 0.13 : parent.height * 0.05 anchors.rightMargin: 20 } Rectangle { id: instructionBox radius: 10 - width: background.width / 1.9 - height: horizontalLayout ? background.height / 5 : background.height / 4 + width: horizontalLayout ? background.width / 1.9 : background.width * 0.95 + height: horizontalLayout ? background.height / 5 : 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.1 anchors.leftMargin: parent.width * 0.1 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: Activity.instructions[bar.level - 1] } } Piano { id: piano - width: horizontalLayout ? parent.width * 0.4 : parent.width * 0.45 - height: horizontalLayout ? parent.height * 0.45 : parent.width * 0.4 - anchors.left: parent.left + width: horizontalLayout ? parent.width * 0.4 : parent.width * 0.8 + height: horizontalLayout ? parent.height * 0.45 : parent.width * 0.3 + anchors.horizontalCenter: horizontalLayout ? undefined : parent.horizontalCenter + anchors.left: horizontalLayout ? parent.left : undefined anchors.leftMargin: horizontalLayout ? parent.width * 0.06 : parent.height * 0.01 - anchors.top: instructionBox.bottom - anchors.topMargin: parent.height * 0.15 + anchors.top: optionsRow.bottom blackLabelsVisible: [4, 5, 6, 7, 8].indexOf(items.bar.level) == -1 ? false : true useSharpNotation: bar.level == 5 ? false : true onNoteClicked: { onlyNote.value = note staff2.addNote(note, currentType, piano.useSharpNotation ? "sharp" : "flat", false) var noteToPlay = 'qrc:/gcompris/src/activities/piano_composition/resource/' + clefType + '_pitches/' + currentType + '/' + note + '.wav'; items.audioEffects.play(noteToPlay) } Note { id: onlyNote value: "1" type: currentType visible: false } } Row { id: optionsRow - anchors.bottom: staff2.top + anchors.top: instructionBox.bottom + anchors.topMargin: 10 spacing: 15 anchors.horizontalCenter: parent.horizontalCenter Image { id: wholeNote source: "qrc:/gcompris/src/activities/piano_composition/resource/whole-note.svg" sourceSize.width: 50 visible: bar.level == 1 || bar.level == 2 ? false : true MouseArea { anchors.fill: parent onClicked: currentType = onlyNote.wholeNote } } Image { id: halfNote source: "qrc:/gcompris/src/activities/piano_composition/resource/half-note.svg" sourceSize.width: 50 visible: wholeNote.visible MouseArea { anchors.fill: parent onClicked: currentType = onlyNote.halfNote } } Image { id: quarterNote source: "qrc:/gcompris/src/activities/piano_composition/resource/quarter-note.svg" sourceSize.width: 50 visible: wholeNote.visible MouseArea { anchors.fill: parent onClicked: currentType = onlyNote.quarterNote } } Image { id: eighthNote source: "qrc:/gcompris/src/activities/piano_composition/resource/eighth-note.svg" sourceSize.width: 50 visible: wholeNote.visible MouseArea { anchors.fill: parent onClicked: currentType = onlyNote.eighthNote } } Image { id: playButton source: "qrc:/gcompris/src/activities/piano_composition/resource/play.svg" sourceSize.width: 50 MouseArea { anchors.fill: parent onClicked: staff2.play() } } Image { id: clefButton source: clefType == "bass" ? "qrc:/gcompris/src/activities/piano_composition/resource/bassClefButton.svg" : "qrc:/gcompris/src/activities/piano_composition/resource/trebbleClefButton.svg" sourceSize.width: 50 visible: bar.level > 2 MouseArea { anchors.fill: parent onClicked: { staff2.eraseAllNotes() clefType = (clefType == "bass") ? "treble" : "bass" } } } Image { id: clearButton source: "qrc:/gcompris/src/activities/piano_composition/resource/edit-clear.svg" sourceSize.width: 50 MouseArea { anchors.fill: parent onClicked: staff2.eraseAllNotes() } } Image { id: openButton source: "qrc:/gcompris/src/activities/piano_composition/resource/open.svg" sourceSize.width: 50 visible: bar.level == 6 || bar.level == 7 MouseArea { anchors.fill: parent - onClicked: loadMelody() + onClicked: { + 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 + } } } + Image { + id: saveButton + source: "qrc:/gcompris/src/activities/piano_composition/resource/save.svg" + sourceSize.width: 50 + visible: bar.level == 6 || bar.level == 7 + MouseArea { + anchors.fill: parent + onClicked: { + Activity.saveMelody() + } + } + } Image { id: changeAccidentalStyleButton source: piano.useSharpNotation ? "qrc:/gcompris/src/activities/piano_composition/resource/blacksharp.svg" : "qrc:/gcompris/src/activities/piano_composition/resource/blackflat.svg" visible: bar.level >= 4 MouseArea { anchors.fill: parent onClicked: piano.useSharpNotation = !piano.useSharpNotation } } } - function loadMelody() { - var data = Dataset.get(); - var selectedMusic = data.filter(function(item) { return item.title === 'Frère jacques'; }); - staff2.loadFromData(selectedMusic[0]["melody"]); - } - 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/piano_composition/Staff.qml b/src/activities/piano_composition/Staff.qml index 43bebd5bb..a3f8ae1ac 100644 --- a/src/activities/piano_composition/Staff.qml +++ b/src/activities/piano_composition/Staff.qml @@ -1,196 +1,205 @@ /* GCompris - Staff.qml * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (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 GCompris 1.0 import "../../core" Item { id: staff property Item main: activity.main property int verticalDistanceBetweenLines: height / nbLines // todo width/nbLines * smth property string clef width: 400 height: 100 // Stave property int nbLines: 5 property bool lastPartition: false property bool isMetronomeDisplayed: false property bool showMetronome: false property alias notes: notes property int firstNoteX: defaultFirstNoteX property int defaultFirstNoteX: clefImage.width property int nbMaxNotesPerStaff property bool noteIsColored property alias notesRepeater: notesRepeater Image { id: clefImage source: clef ? "qrc:/gcompris/src/activities/piano_composition/resource/" + clef + "Clef.svg" : "" - sourceSize.width: (nbLines-2)*verticalDistanceBetweenLines + sourceSize.width: (nbLines - 2) * verticalDistanceBetweenLines } Repeater { model: nbLines Rectangle { width: staff.width height: 5 border.width: 5 color: "black" x: 0 y: index * verticalDistanceBetweenLines } } Rectangle { width: 5 border.width: 5 height: (nbLines - 1) * verticalDistanceBetweenLines + 5 color: "black" x: staff.width y: 0 } // end of partition line Rectangle { width: 5 border.width: 5 height: (nbLines - 1) * verticalDistanceBetweenLines + 5 visible: lastPartition color: "black" x: staff.width - 10 y: 0 } ListModel { id: notes } Rectangle { id: metronome width: 5 border.width: 10 height: (nbLines - 1) * verticalDistanceBetweenLines + 5 visible: isMetronomeDisplayed && showMetronome color: "red" x: firstNoteX - width/2 y: 0 Behavior on x { SmoothedAnimation { id: metronomeAnimation duration: -1 } } } property var mDuration function initMetronome() { var staffDuration = 0; for(var v = 0 ; v < notes.count ; ++ v) { staffDuration += notes.get(v).mDuration; } metronomeAnimation.velocity = 1; mDuration = staffDuration; metronome.x = staff.width; print("total duration " + staffDuration) print("total distance " + metronome.x) } function addNote(newValue_, newType_, newBlackType_, highlightWhenPlayed_) { + var duration + if(newType_ == 1) + duration = 2000/newType_ + else if(newType_ == 2) + duration = 3000/newType_ + else if(newType_ == 4) + duration = 4000/newType_ + else + duration = 6500/newType_ notes.append({"mValue": newValue_, "mType": newType_, - "mBlackType": newBlackType_, "mDuration": 2000/newType_, + "mBlackType": newBlackType_, "mDuration": duration, "mHighlightWhenPlayed": highlightWhenPlayed_}); } function playNote(noteId) { metronomeAnimation.velocity = staff.width * 1000 / (notes.get(noteId).mDuration * notes.count); print("velocity " + metronomeAnimation.velocity) } function eraseAllNotes() { notes.clear(); } property int noteWidth: (staff.width - 10 - clefImage.width) / 10 Row { id: notesRow x: firstNoteX - noteWidth/2 Repeater { id: notesRepeater model: notes Note { value: mValue type: mType blackType: mBlackType highlightWhenPlayed: mHighlightWhenPlayed noteIsColored: staff.noteIsColored width: (notes.count == 1 && items.staffLength === "long") ? Math.min(items.background.width,items.background.height) * 0.1 : noteWidth height: staff.height MouseArea { anchors.fill: parent onClicked: { print(items.staffLength) print(items.background.width,items.background.height) } } function play() { - if(highlightWhenPlayed) { +// if(highlightWhenPlayed) { highlightTimer.start(); - } +// } } y: { var shift = 0; if(clef === "bass") { shift = -3 * verticalDistanceBetweenLines } if(blackType !== "") { if(blackType === "flat") { shift += - verticalDistanceBetweenLines } else { shift += - verticalDistanceBetweenLines / 2 } } if(mValue > 0) { return (nbLines - 2) * verticalDistanceBetweenLines - (parseInt(mValue) - 1) * verticalDistanceBetweenLines/2 + shift } else if(mValue >= -2) return (nbLines - 3) * verticalDistanceBetweenLines - (Math.abs(parseInt(mValue)) - 1) * verticalDistanceBetweenLines/2 + shift else return (nbLines - 3) * verticalDistanceBetweenLines - (Math.abs(parseInt(mValue))) * verticalDistanceBetweenLines/2 + shift } } } spacing: 0 } } diff --git a/src/activities/piano_composition/piano_composition.js b/src/activities/piano_composition/piano_composition.js index 01707007c..33244dc53 100644 --- a/src/activities/piano_composition/piano_composition.js +++ b/src/activities/piano_composition/piano_composition.js @@ -1,56 +1,69 @@ /* GCompris - piano_composition.js * * Copyright (C) 2016 Johnny Jazeix * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (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.0 as Quick +.import GCompris 1.0 as GCompris var currentLevel = 0 var numberOfLevel = 7 var items +var userFile = "file://" + GCompris.ApplicationInfo.getSharedWritablePath() + "/" + "piano_composition" var instructions = ["This is the treble cleff staff for high pitched notes", "This is the bass cleff staff for low pitched notes", "Click on the note symbols to write different length notes such as quarter notes, half notes and whole notes", "The black keys are sharp and flat keys, have a # sign.", "Each black key has two names: flat and sharp. Flat notes have b sign","Now you can load music", "Now you can compose your own music"] function start(items_) { items = items_ currentLevel = 0 initLevel() } +function saveMelody() { + print(items.staff2.getAllNotes()) + if (!items.file.exists(userFile)) { + if (!items.file.mkpath(userFile)) + console.error("Could not create directory " + userFile); + else + console.debug("Created directory " + userFile); + } +} + function stop() { } function initLevel() { items.bar.level = currentLevel + 1 } function nextLevel() { items.staff2.eraseAllNotes() if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { + items.staff2.eraseAllNotes() if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } diff --git a/src/activities/piano_composition/resource/note_highlight.png b/src/activities/piano_composition/resource/note_highlight.png new file mode 100644 index 000000000..6118df30e Binary files /dev/null and b/src/activities/piano_composition/resource/note_highlight.png differ diff --git a/src/activities/piano_composition/resource/save.svg b/src/activities/piano_composition/resource/save.svg new file mode 100644 index 000000000..8e94cdf4f --- /dev/null +++ b/src/activities/piano_composition/resource/save.svg @@ -0,0 +1,747 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Save + + + Jakub Steiner + + + + + hdd + hard drive + save + io + store + + + + + http://jimmac.musichall.cz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +