diff --git a/src/activities/piano_composition/MelodyList.qml b/src/activities/piano_composition/MelodyList.qml index 4ca512651..537ec8674 100644 --- a/src/activities/piano_composition/MelodyList.qml +++ b/src/activities/piano_composition/MelodyList.qml @@ -1,141 +1,142 @@ /* 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" + text: qsTr("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 + + 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 - + + 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 { - 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) + 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 7fc9ef738..0ef679214 100644 --- a/src/activities/piano_composition/MultipleStaff.qml +++ b/src/activities/piano_composition/MultipleStaff.qml @@ -1,169 +1,170 @@ /* 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 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++) { + var staveNotes = staves.itemAt(i).notes + for(var j = 0; j < staveNotes.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(); if(whiteNotes.indexOf(noteStr) != -1) addNote("" + (whiteNotes.indexOf(noteStr) + 1), type, "", false); else if (blackNotesSharp.indexOf(melody[i][0]) != -1) { addNote("" + (-1 * blackNotesSharp.indexOf(noteStr) - 1), type, "sharp", false); } else { 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'; if(currentNote == 0) { staves.itemAt(currentPlayedStaff).initMetronome(); } // 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() } } } } diff --git a/src/activities/piano_composition/Piano_composition.qml b/src/activities/piano_composition/Piano_composition.qml index 5677cd0d0..f46a47b93 100644 --- a/src/activities/piano_composition/Piano_composition.qml +++ b/src/activities/piano_composition/Piano_composition.qml @@ -1,352 +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.8 height: horizontalLayout ? parent.height * 0.5 : parent.height * 0.3 nbStaves: 3 - clef: clefType == "bass" ? "bass" : "treble" + clef: clefType nbMaxNotesPerStaff: 8 noteIsColored: true isMetronomeDisplayed: true anchors.right: parent.right 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: 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.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: 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.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: { 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 } } } 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/piano_composition.js b/src/activities/piano_composition/piano_composition.js index 33244dc53..91b0d2feb 100644 --- a/src/activities/piano_composition/piano_composition.js +++ b/src/activities/piano_composition/piano_composition.js @@ -1,69 +1,89 @@ /* 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 +.import "qrc:/gcompris/src/core/core.js" as Core 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"] +var userDir = "file://" + GCompris.ApplicationInfo.getSharedWritablePath() + "/" + "piano_composition" +var userFile = "file://" + GCompris.ApplicationInfo.getSharedWritablePath() + "/" + "piano_composition" + "/melodies.json" +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); + var notes = items.staff2.getAllNotes() + if (!items.file.exists(userDir)) { + if (!items.file.mkpath(userDir)) + console.error("Could not create directory " + userDir); else - console.debug("Created directory " + userFile); + console.debug("Created directory " + userDir); + } + + if (!items.file.write(JSON.stringify(notes), userFile)) { + Core.showMessageDialog(items.background, + qsTr("Error saving melody to your file (%1)") + .arg(userFile), + "", null, "", null, null); + } + else { + Core.showMessageDialog(items.background, + qsTr("Saved melody to your file (%1)") + .arg(userFile), + "", null, "", null, null); } } 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(); }