diff --git a/data/definitions/scales-major-and-its-modes-definitions.json b/data/definitions/scales-major-and-its-modes-definitions.json index b4a946e..ebff72f 100644 --- a/data/definitions/scales-major-and-its-modes-definitions.json +++ b/data/definitions/scales-major-and-its-modes-definitions.json @@ -1,39 +1,39 @@ { "definitions": [ { - "tags": ["scale", "ascending", "major"], + "tags": ["scale", "ascending", "major", "diatonic"], "name": "Ionian", "sequence": "2 4 5 7 9 11 12" }, { - "tags": ["scale", "ascending", "major"], + "tags": ["scale", "ascending", "major", "diatonic"], "name": "Dorian", "sequence": "2 3 5 7 9 10 12" }, { - "tags": ["scale", "ascending", "major"], + "tags": ["scale", "ascending", "major", "diatonic"], "name": "Phrygian", "sequence": "1 3 5 7 8 10 12" }, { - "tags": ["scale", "ascending", "major"], + "tags": ["scale", "ascending", "major", "diatonic"], "name": "Lydian", "sequence": "2 4 6 7 9 11 12" }, { - "tags": ["scale", "ascending", "major"], + "tags": ["scale", "ascending", "major", "diatonic"], "name": "Mixolydian", "sequence": "2 4 5 7 9 10 12" }, { - "tags": ["scale", "ascending", "major"], + "tags": ["scale", "ascending", "major", "diatonic"], "name": "Aeolian", "sequence": "2 3 5 7 8 10 12" }, { - "tags": ["scale", "ascending", "major"], + "tags": ["scale", "ascending", "major", "diatonic"], "name": "Locrian", "sequence": "1 3 5 6 8 10 12" } ] } diff --git a/data/exercises/chords-root-position-exercises.json b/data/exercises/chords-root-position-exercises.json index 44bae74..becc7b0 100644 --- a/data/exercises/chords-root-position-exercises.json +++ b/data/exercises/chords-root-position-exercises.json @@ -1,69 +1,69 @@ { "exercises": [ { "name": "Chords", - "root": "21..104", + "root": "41..67", "playMode": "chord", "userMessage": "Hear the chord and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-chords.svg", "children": [ { "name": "Root Position", "and-tags": ["chord", "root-position"], "children": [ { "name": "Minor and Major Chords", "and-tags": ["triad"], "or-tags": ["minor", "major"] }, { "name": "Minor 7 and Dominant 7 Chords", "and-tags": ["7"], "or-tags": ["minor", "dominant"] }, { "name": "Diminished and Augmented Chords", "and-tags": ["triad"], "or-tags": ["diminished", "augmented"] }, { "name": "Minor 9 and Major 9 Chords", "and-tags": ["9"], "or-tags": ["minor", "major"] }, { "name": "Major 7(b9) and Major maj7(9) Chords", "and-tags": ["7", "9"], "or-tags": ["major"] }, { "name": "Major Seventh, Diminished Seventh, and Half Diminished Seventh Chords", "and-tags": ["7"], "or-tags": ["major", "diminished"] }, { "name": "Chords with 9 in their name", "or-tags": ["9"] }, { "name": "Altered Chords", "and-tags": ["altered"] }, { "name": "Chords with 7 in their name", "and-tags": ["7"] }, { "name": "Minor, Major, Diminished, and Augmented Chords", "or-tags": ["minor", "major", "diminished", "augmented"] }, { "name": "Lots of Chords" } ] } ] } ] } diff --git a/data/exercises/intervals-ascending-melodic-exercises.json b/data/exercises/intervals-ascending-melodic-exercises.json index 62cdee5..40acd26 100644 --- a/data/exercises/intervals-ascending-melodic-exercises.json +++ b/data/exercises/intervals-ascending-melodic-exercises.json @@ -1,80 +1,80 @@ { "exercises": [ { "name": "Intervals", - "root": "21..104", + "root": "41..67", "playMode": "scale", "userMessage": "Hear the interval and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-intervals.svg", "children": [ { "name": "Ascending Melodic Intervals", "and-tags": ["interval", "ascending"], "children": [ { "name": "Seconds", "or-tags": ["2"] }, { "name": "Thirds", "or-tags": ["3"] }, { "name": "Fourths and Fifths", "or-tags": ["4", "5"] }, { "name": "Sixths", "or-tags": ["6"] }, { "name": "Sevenths", "or-tags": ["7"] }, { "name": "Ninths", "or-tags": ["9"] }, { "name": "Tenths", "or-tags": ["10"] }, { "name": "Tritone and Sevenths", "or-tags": ["tritone", "7"] }, { "name": "Fourths, Fifths, and Octave", "or-tags": ["4", "5", "8"] }, { "name": "Seconds and Thirds", "or-tags": ["2", "3"] }, { "name": "Sixths and Sevenths", "or-tags": ["6", "7"] }, { "name": "Sevenths and Ninths", "or-tags": ["7", "9"] }, { "name": "Second to Octave", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8"] }, { "name": "Second to Tenth", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8", "9", "10"] }, { "name": "Second to 15th", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8", "9", "10", "11", "8+tritone", "12", "13", "14", "double8"] } ] } ] } ] } diff --git a/data/exercises/intervals-descending-melodic-exercises.json b/data/exercises/intervals-descending-melodic-exercises.json index 20a0101..a856471 100644 --- a/data/exercises/intervals-descending-melodic-exercises.json +++ b/data/exercises/intervals-descending-melodic-exercises.json @@ -1,80 +1,80 @@ { "exercises": [ { "name": "Intervals", - "root": "21..104", + "root": "41..67", "playMode": "scale", "userMessage": "Hear the interval and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-intervals.svg", "children": [ { "name": "Descending Melodic Intervals", "and-tags": ["interval", "descending"], "children": [ { "name": "Seconds", "or-tags": ["2"] }, { "name": "Thirds", "or-tags": ["3"] }, { "name": "Fourths and Fifths", "or-tags": ["4", "5"] }, { "name": "Sixths", "or-tags": ["6"] }, { "name": "Sevenths", "or-tags": ["7"] }, { "name": "Ninths", "or-tags": ["9"] }, { "name": "Tenths", "or-tags": ["10"] }, { "name": "Tritone and Sevenths", "or-tags": ["tritone", "7"] }, { "name": "Fourths, Fifths, and Octave", "or-tags": ["4", "5", "8"] }, { "name": "Seconds and Thirds", "or-tags": ["2", "3"] }, { "name": "Sixths and Sevenths", "or-tags": ["6", "7"] }, { "name": "Sevenths and Ninths", "or-tags": ["7", "9"] }, { "name": "Second to Octave", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8"] }, { "name": "Second to Tenth", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8", "9", "10"] }, { "name": "Second to 15th", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8", "9", "10", "11", "8+tritone", "12", "13", "14", "double8"] } ] } ] } ] } diff --git a/data/exercises/intervals-harmonic-exercises.json b/data/exercises/intervals-harmonic-exercises.json index 6a6a7fa..7514bdd 100644 --- a/data/exercises/intervals-harmonic-exercises.json +++ b/data/exercises/intervals-harmonic-exercises.json @@ -1,80 +1,80 @@ { "exercises": [ { "name": "Intervals", - "root": "21..104", + "root": "41..67", "playMode": "chord", "userMessage": "Hear the interval and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-intervals.svg", "children": [ { "name": "Harmonic Intervals", "and-tags": ["interval", "harmonic"], "children": [ { "name": "Seconds", "or-tags": ["2"] }, { "name": "Thirds", "or-tags": ["3"] }, { "name": "Fourths and Fifths", "or-tags": ["4", "5"] }, { "name": "Sixths", "or-tags": ["6"] }, { "name": "Sevenths", "or-tags": ["7"] }, { "name": "Ninths", "or-tags": ["9"] }, { "name": "Tenths", "or-tags": ["10"] }, { "name": "Tritone and Sevenths", "or-tags": ["tritone", "7"] }, { "name": "Fourths, Fifths, and Octave", "or-tags": ["4", "5", "8"] }, { "name": "Seconds and Thirds", "or-tags": ["2", "3"] }, { "name": "Sixths and Sevenths", "or-tags": ["6", "7"] }, { "name": "Sevenths and Ninths", "or-tags": ["7", "9"] }, { "name": "Second to Octave", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8"] }, { "name": "Second to Tenth", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8", "9", "10"] }, { "name": "Second to 15th", "or-tags": ["2", "3", "4", "tritone", "5", "6", "7", "8", "9", "10", "11", "8+tritone", "12", "13", "14", "double8"] } ] } ] } ] } diff --git a/data/exercises/scales-bebop-exercises.json b/data/exercises/scales-bebop-exercises.json index a0b9a0a..4e1ed14 100644 --- a/data/exercises/scales-bebop-exercises.json +++ b/data/exercises/scales-bebop-exercises.json @@ -1,18 +1,18 @@ { "exercises": [ { "name": "Scales", - "root": "21..104", + "root": "11..67", "playMode": "scale", "userMessage": "Hear the scale and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-scales.svg", "children": [ { "name": "Bebop Scales", "and-tags": ["scale", "bebop"] } ] } ] } diff --git a/data/exercises/scales-harmonic-major-and-its-modes-exercises.json b/data/exercises/scales-harmonic-major-and-its-modes-exercises.json index 2562e55..5a603c9 100644 --- a/data/exercises/scales-harmonic-major-and-its-modes-exercises.json +++ b/data/exercises/scales-harmonic-major-and-its-modes-exercises.json @@ -1,18 +1,18 @@ { "exercises": [ { "name": "Scales", - "root": "21..104", + "root": "41..67", "playMode": "scale", "userMessage": "Hear the scale and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-scales.svg", "children": [ { "name": "Harmonic Major Scale and its Modes", "and-tags": ["scale", "harmonic", "major"] } ] } ] } diff --git a/data/exercises/scales-harmonic-minor-and-its-modes-exercises.json b/data/exercises/scales-harmonic-minor-and-its-modes-exercises.json index e59837e..1b46d1c 100644 --- a/data/exercises/scales-harmonic-minor-and-its-modes-exercises.json +++ b/data/exercises/scales-harmonic-minor-and-its-modes-exercises.json @@ -1,18 +1,18 @@ { "exercises": [ { "name": "Scales", - "root": "21..104", + "root": "41..67", "playMode": "scale", "userMessage": "Hear the scale and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-scales.svg", "children": [ { "name": "Harmonic Minor Scale and its Modes", "and-tags": ["scale", "harmonic", "minor"] } ] } ] } diff --git a/data/exercises/scales-major-and-its-modes-exercises.json b/data/exercises/scales-major-and-its-modes-exercises.json index 84daa71..03f2199 100644 --- a/data/exercises/scales-major-and-its-modes-exercises.json +++ b/data/exercises/scales-major-and-its-modes-exercises.json @@ -1,18 +1,18 @@ { "exercises": [ { "name": "Scales", - "root": "21..104", + "root": "41..67", "playMode": "scale", "userMessage": "Hear the scale and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-scales.svg", "children": [ { "name": "Major Scale and its Modes", - "and-tags": ["scale", "major"] + "and-tags": ["scale", "major", "diatonic"] } ] } ] } diff --git a/data/exercises/scales-pentatonic-major-and-its-modes-exercises.json b/data/exercises/scales-pentatonic-major-and-its-modes-exercises.json index b913b11..4eb230b 100644 --- a/data/exercises/scales-pentatonic-major-and-its-modes-exercises.json +++ b/data/exercises/scales-pentatonic-major-and-its-modes-exercises.json @@ -1,18 +1,18 @@ { "exercises": [ { "name": "Scales", - "root": "21..104", + "root": "41..67", "playMode": "scale", "userMessage": "Hear the scale and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-scales.svg", "children": [ { "name": "Pentatonic Major Scale and its Modes", "and-tags": ["scale", "pentatonic", "major"] } ] } ] } diff --git a/data/exercises/scales-simmetric-exercises.json b/data/exercises/scales-simmetric-exercises.json index 6800e77..c053be0 100644 --- a/data/exercises/scales-simmetric-exercises.json +++ b/data/exercises/scales-simmetric-exercises.json @@ -1,18 +1,18 @@ { "exercises": [ { "name": "Scales", - "root": "21..104", + "root": "41..67", "playMode": "scale", "userMessage": "Hear the scale and then choose an answer from options below", "numberOfSelectedOptions": 1, "_icon": "minuet-scales.svg", "children": [ { "name": "Simmetric Scales", "and-tags": ["scale", "simmetric"] } ] } ] } diff --git a/src/app/qml.qrc b/src/app/qml.qrc index a78fb9e..395f30f 100644 --- a/src/app/qml.qrc +++ b/src/app/qml.qrc @@ -1,44 +1,53 @@ qml/Main.qml qml/AboutDialog.qml qml/MinuetMenu.qml qml/MinuetMenuContainer.qml qml/ImageItemDelegate.qml +android/qml/MinuetMenuContainer.qml qml/ExerciseView.qml qml/PianoView/BlackKey.qml qml/PianoView/Octave.qml qml/PianoView/PianoView.qml qml/PianoView/WhiteKey.qml + + qml/SheetMusicView/Bravura.otf + qml/SheetMusicView/BravuraText.qml + qml/SheetMusicView/Clef.qml + qml/SheetMusicView/Note.qml + qml/SheetMusicView/Score.qml + qml/SheetMusicView/Sequence.qml + qml/SheetMusicView/SheetMusicView.qml + qml/images/minuet-background.png qml/images/minuet-drawer.png qml/images/menu.png qml/images/keyboard_arrow_left.png qml/images/more_vert.png icons/64-apps-minuet.png icons/22-actions-minuet-chords.svg icons/22-actions-minuet-intervals.svg icons/22-actions-minuet-rhythms.svg icons/22-actions-minuet-scales.svg ../../data/exercise-images/current-rhythm.png ../../data/exercise-images/eighth-dot-sixteenth.png ../../data/exercise-images/eighth-eighth.png ../../data/exercise-images/eighth-sixteenth-sixteenth.png ../../data/exercise-images/quarter.png ../../data/exercise-images/sixteenth-eighth-dot.png ../../data/exercise-images/sixteenth-eighth-sixteenth.png ../../data/exercise-images/sixteenth-sixteenth-eighth.png ../../data/exercise-images/sixteenth-sixteenth-sixteenth-sixteenth.png ../../data/exercise-images/unknown-rhythm.png qtquickcontrols2.conf +android/qtquickcontrols2.conf +windows/qtquickcontrols2.conf diff --git a/src/app/qml/ExerciseView.qml b/src/app/qml/ExerciseView.qml index 16b4aea..f761a45 100644 --- a/src/app/qml/ExerciseView.qml +++ b/src/app/qml/ExerciseView.qml @@ -1,449 +1,476 @@ /**************************************************************************** ** ** Copyright (C) 2016 by Sandro S. Andrade ** ** 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 2 of ** the License or (at your option) version 3 or any later version ** accepted by the membership of KDE e.V. (or its successor approved ** by the membership of KDE e.V.), which shall act as a proxy ** defined in Section 14 of version 3 of the license. ** ** 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.7 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 import QtQuick.Window 2.0 Item { id: exerciseView visible: currentExercise != undefined property var currentExercise QtObject { id: internal property int currentAnswer property var colors: ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f", "#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a", "#ffff99", "#b15928"] property Item rightAnswerRectangle property variant userAnswers: [] property bool answersAreRight property bool giveUp property bool isTest: false property int correctAnswers: 0 property int currentExercise: 0 property int maximumExercises: 10 onCurrentAnswerChanged: { for (var i = 0; i < yourAnswersParent.children.length; ++i) yourAnswersParent.children[i].destroy() yourAnswersParent.children = "" for (var i = 0; i < currentAnswer; ++i) answerOption.createObject(yourAnswersParent, {"model": userAnswers[i].model, "index": userAnswers[i].index, "position": i, "color": userAnswers[i].color, "border.width": 2}) } } onCurrentExerciseChanged: { clearUserAnswers() for (var i = 0; i < answerGrid.children.length; ++i) answerGrid.children[i].destroy() answerGrid.children = "" if (currentExercise != undefined) { var currentExerciseOptions = currentExercise["options"]; if (currentExerciseOptions != undefined) { var length = currentExerciseOptions.length for (var i = 0; i < length; ++i) answerOption.createObject(answerGrid, {"model": currentExerciseOptions[i], "index": i, "color": internal.colors[i%24]}) } messageText.text = i18n("Click 'New Question' to start!") exerciseView.state = "waitingForNewQuestion" } } function clearUserAnswers() { pianoView.clearAllMarks() + sheetMusicView.clearAllMarks() for (var i = 0; i < yourAnswersParent.children.length; ++i) yourAnswersParent.children[i].destroy() yourAnswersParent.children = "" internal.currentAnswer = 0 internal.userAnswers = [] } function checkAnswers() { var rightAnswers = core.exerciseController.selectedExerciseOptions internal.answersAreRight = true for (var i = 0; i < currentExercise.numberOfSelectedOptions; ++i) { if (internal.userAnswers[i].name != rightAnswers[i].name) { yourAnswersParent.children[i].border.color = "red" internal.answersAreRight = false } else { yourAnswersParent.children[i].border.color = "green" if (internal.isTest) internal.correctAnswers++ } } messageText.text = (internal.giveUp) ? i18n("Here is the answer") : (internal.answersAreRight) ? i18n("Congratulations, you answered correctly!"):i18n("Oops, not this time! Try again!") if (internal.currentExercise == internal.maximumExercises) { messageText.text = i18n("You answered correctly %1%", internal.correctAnswers * 100 / internal.maximumExercises) resetTest() } if (currentExercise.numberOfSelectedOptions == 1) highlightRightAnswer() else exerciseView.state = "waitingForNewQuestion" internal.giveUp = false } function highlightRightAnswer() { var chosenExercises = core.exerciseController.selectedExerciseOptions for (var i = 0; i < answerGrid.children.length; ++i) { if (answerGrid.children[i].model.name != chosenExercises[0].name) { answerGrid.children[i].opacity = 0.25 } else { internal.rightAnswerRectangle = answerGrid.children[i] answerGrid.children[i].opacity = 1 } } + var array = [core.exerciseController.chosenRootNote()] internal.rightAnswerRectangle.model.sequence.split(' ').forEach(function(note) { pianoView.noteMark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, internal.rightAnswerRectangle.color) + array.push(core.exerciseController.chosenRootNote() + parseInt(note)) }) + console.log("RODANDO:" + array) + sheetMusicView.model = array animation.start() } function resetTest() { internal.isTest = false internal.correctAnswers = 0 internal.currentExercise = 0 } function nextTestExercise() { for (var i = 0; i < answerGrid.children.length; ++i) answerGrid.children[i].opacity = 1 pianoView.clearAllMarks() + sheetMusicView.clearAllMarks() clearUserAnswers() generateNewQuestion(true) core.soundController.play() } function generateNewQuestion () { clearUserAnswers() if (internal.isTest) messageText.text = i18n("Question %1 out of %2", internal.currentExercise + 1, internal.maximumExercises) else messageText.text = "" core.exerciseController.randomlySelectExerciseOptions() var chosenExercises = core.exerciseController.selectedExerciseOptions core.soundController.prepareFromExerciseOptions(chosenExercises) - if (currentExercise["playMode"] != "rhythm") + if (currentExercise["playMode"] != "rhythm") { pianoView.noteMark(0, core.exerciseController.chosenRootNote(), 0, "white") + sheetMusicView.model = [core.exerciseController.chosenRootNote()] + sheetMusicView.clef.type = (core.exerciseController.chosenRootNote() >= 60) ? 0:1 + } exerciseView.state = "waitingForAnswer" if (internal.isTest) internal.currentExercise++ } ColumnLayout { anchors.fill: parent spacing: Screen.width >= 1024 ? 20:10 Text { id: userMessage Layout.preferredWidth: parent.width horizontalAlignment: Text.AlignHCenter font.pointSize: Screen.width >= 1024 ? 18:14 anchors.horizontalCenter: parent.horizontalCenter wrapMode: Text.WordWrap text: (currentExercise != undefined) ? i18nc("technical term, do you have a musician friend?", currentExercise["userMessage"]):"" } Text { id: messageText font.pointSize: Screen.width >= 1024 ? 18:14 Layout.preferredWidth: parent.width horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } Row { anchors.horizontalCenter: parent.horizontalCenter spacing: 10 Button { id: newPlayQuestionButton width: 120; height: 40 text: (exerciseView.state == "waitingForNewQuestion") ? i18n("New Question"):i18n("Play Question") enabled: !animation.running onClicked: { if (exerciseView.state == "waitingForNewQuestion") { generateNewQuestion() } core.soundController.play() } } Button { id: giveUpButton width: 120; height: 40 text: i18n("Give Up") enabled: exerciseView.state == "waitingForAnswer" && !animation.running onClicked: { if (internal.isTest) internal.correctAnswers-- internal.giveUp = true var rightAnswers = core.exerciseController.selectedExerciseOptions internal.userAnswers = [] for (var i = 0; i < currentExercise.numberOfSelectedOptions; ++i) { for (var j = 0; j < answerGrid.children.length; ++j) { if (answerGrid.children[j].model.name == rightAnswers[i].name) { internal.userAnswers.push({"name": rightAnswers[i].name, "model": answerGrid.children[j].model, "index": j, "color": internal.colors[j]}) break } } } internal.currentAnswer = currentExercise.numberOfSelectedOptions checkAnswers() } } Button { id: testButton width: 120; height: 40 text: internal.isTest ? i18n("Stop Test") : i18n("Start Test") enabled: true onClicked: { if (!internal.isTest) { resetTest() internal.isTest = true generateNewQuestion() if (internal.isTest) core.soundController.play() } else { resetTest() exerciseView.state = "waitingForNewQuestion" messageText.text = i18n("Click 'New Question' to start") } } } } GroupBox { id: availableAnswers title: i18n("Available Answers") anchors.horizontalCenter: parent.horizontalCenter Layout.preferredWidth: parent.width Layout.fillHeight: true Flickable { anchors.fill: parent contentHeight: answerGrid.height clip: true Grid { id: answerGrid anchors.centerIn: parent spacing: 10 columns: Math.max(1, parent.width / (((currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 120:119) + spacing)) Component { id: answerOption Rectangle { id: answerRectangle property var model property int index property int position width: (currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 120:119 height: (currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 40:59 Text { id: option property string originalText: model.name visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" text: i18nc("technical term, do you have a musician friend?", model.name) width: parent.width - 4 anchors.centerIn: parent horizontalAlignment: Qt.AlignHCenter color: "black" wrapMode: Text.Wrap } Image { id: rhythmImage anchors.centerIn: parent visible: currentExercise != undefined && currentExercise["playMode"] == "rhythm" source: (currentExercise != undefined && currentExercise["playMode"] == "rhythm") ? "exercise-images/" + model.name + ".png":"" fillMode: Image.Pad } MouseArea { anchors.fill: parent onClicked: { if (exerciseView.state == "waitingForAnswer" && !animation.running) { onExited() internal.userAnswers.push({"name": option.originalText, "model": answerRectangle.model, "index": answerRectangle.index, "color": answerRectangle.color}) internal.currentAnswer++ if (internal.currentAnswer == currentExercise.numberOfSelectedOptions) checkAnswers() } } - hoverEnabled: Qt.platform.os != "android" && - !animation.running + hoverEnabled: Qt.platform.os != "android" && !animation.running onEntered: { answerRectangle.color = Qt.darker(answerRectangle.color, 1.1) if (currentExercise["playMode"] != "rhythm" && exerciseView.state == "waitingForAnswer") { if (parent.parent == answerGrid) { + var array = [core.exerciseController.chosenRootNote()] model.sequence.split(' ').forEach(function(note) { + array.push(core.exerciseController.chosenRootNote() + parseInt(note)) pianoView.noteMark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, internal.colors[answerRectangle.index]) }) + sheetMusicView.model = array } } else { var rightAnswers = core.exerciseController.selectedExerciseOptions if (parent.parent == yourAnswersParent && internal.userAnswers[position].name != rightAnswers[position].name) { parent.border.color = "green" for (var i = 0; i < answerGrid.children.length; ++i) { if (answerGrid.children[i].model.name == rightAnswers[position].name) { parent.color = answerGrid.children[i].color break } } rhythmImage.source = "exercise-images/" + rightAnswers[position].name + ".png" } } } onExited: { answerRectangle.color = internal.colors[answerRectangle.index] if (currentExercise["playMode"] != "rhythm") { if (parent.parent == answerGrid) { if (!animation.running) model.sequence.split(' ').forEach(function(note) { pianoView.noteUnmark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0) }) + sheetMusicView.model = [core.exerciseController.chosenRootNote()] } } else { var rightAnswers = core.exerciseController.selectedExerciseOptions if (parent.parent == yourAnswersParent && internal.userAnswers[position].name != rightAnswers[position].name) { parent.border.color = "red" parent.color = internal.userAnswers[position].color rhythmImage.source = "exercise-images/" + internal.userAnswers[position].name + ".png" } } } } } } } ScrollIndicator.vertical: ScrollIndicator { active: true } } } GroupBox { id: yourAnswers title: i18n("Your Answer(s)") Layout.preferredWidth: parent.width anchors.horizontalCenter: parent.horizontalCenter contentHeight: ((currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 40:59) Flickable { width: (currentExercise != undefined) ? Math.min(parent.width, internal.currentAnswer*130):0; height: parent.height anchors.horizontalCenter: parent.horizontalCenter contentWidth: (currentExercise != undefined) ? internal.currentAnswer*130:0 boundsBehavior: Flickable.StopAtBounds clip: true Row { id: yourAnswersParent anchors.centerIn: parent spacing: Screen.width >= 1024 ? 10:5 } ScrollIndicator.horizontal: ScrollIndicator { active: true } } } Button { id: backspaceButton text: i18n("Backspace") anchors.horizontalCenter: parent.horizontalCenter visible: currentExercise != undefined && currentExercise["playMode"] == "rhythm" enabled: internal.currentAnswer > 0 && internal.currentAnswer < currentExercise.numberOfSelectedOptions onClicked: { internal.userAnswers.pop() internal.currentAnswer-- } } - PianoView { - id: pianoView + Flickable { + + id: pianoViewFlickable + Layout.preferredWidth: parent.width/2 - 10 + anchors { left: parent.left; bottom: parent.bottom; bottomMargin: 10 } + PianoView { + id: pianoView + visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" + } + } + + SheetMusicView { + id: sheetMusicView + anchors { right: parent.right; verticalCenter: pianoViewFlickable.verticalCenter } visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" - anchors.horizontalCenter: parent.horizontalCenter } } states: [ State { name: "waitingForNewQuestion" }, State { name: "waitingForAnswer" StateChangeScript { script: { for (var i = 0; i < answerGrid.children.length; ++i) { answerGrid.children[i].opacity = 1 } } } } ] ParallelAnimation { id: animation loops: 2 SequentialAnimation { PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: -45; duration: 200 } PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: 45; duration: 200 } PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: 0; duration: 200 } } SequentialAnimation { PropertyAnimation { target: internal.rightAnswerRectangle; property: "scale"; to: 1.2; duration: 300 } PropertyAnimation { target: internal.rightAnswerRectangle; property: "scale"; to: 1.0; duration: 300 } } onStopped: { exerciseView.state = internal.isTest ? "waitingForAnswer" : "waitingForNewQuestion" if (internal.isTest) { nextTestExercise() if (internal.currentExercise == internal.maximumExercises+1) internal.isTest = false } } } Connections { target: core.exerciseController onSelectedExerciseOptionsChanged: pianoView.clearAllMarks() } + Connections { + target: core.exerciseController + onSelectedExerciseOptionsChanged: sheetMusicView.clearAllMarks() + } } diff --git a/src/app/qml/SheetMusicView/Bravura.otf b/src/app/qml/SheetMusicView/Bravura.otf new file mode 100644 index 0000000..adae4b8 Binary files /dev/null and b/src/app/qml/SheetMusicView/Bravura.otf differ diff --git a/src/app/qml/SheetMusicView/BravuraText.qml b/src/app/qml/SheetMusicView/BravuraText.qml new file mode 100644 index 0000000..f6ac2df --- /dev/null +++ b/src/app/qml/SheetMusicView/BravuraText.qml @@ -0,0 +1,3 @@ +import QtQuick 2.7 + +Text { font.pixelSize: 40; font.family: bravura.name } diff --git a/src/app/qml/SheetMusicView/Clef.qml b/src/app/qml/SheetMusicView/Clef.qml new file mode 100644 index 0000000..4584364 --- /dev/null +++ b/src/app/qml/SheetMusicView/Clef.qml @@ -0,0 +1,14 @@ +import QtQuick 2.7 + +BravuraText { + property int type: 0 // [0 treble, 1 bass] + + objectName: "symbol" + + anchors { + left: parent.children[0].left; + bottom: parent.children[0].bottom; + bottomMargin: (type == 0) ? 10:30 + } + text: (type == 0) ? "\ue050":"\ue062" +} diff --git a/src/app/qml/SheetMusicView/Note.qml b/src/app/qml/SheetMusicView/Note.qml new file mode 100644 index 0000000..4b0fcd4 --- /dev/null +++ b/src/app/qml/SheetMusicView/Note.qml @@ -0,0 +1,101 @@ +import QtQuick 2.7 + +BravuraText { + id: note + + property int number; + property int octave; + property int midiKey; + property int rhythm: 4; + property int accident: 0 // [-2 double flat, -1 flat, 1 sharp, 2 double sharp] + property bool spaced: true + + objectName: "symbol" + + onNumberChanged: { + if (!internal.updating) { + internal.updating = true; + midiKey = 24 + (12*(octave-1))+number+accident; + internal.updating = false + } + } + onOctaveChanged: { + if (!internal.updating) { + internal.updating = true; + midiKey = 24 + (12*(octave-1))+number+accident; + internal.updating = false + } + } + onAccidentChanged: { + if (!internal.updating) { + internal.updating = true; + midiKey = 24 + (12*(octave-1))+number+accident; + internal.updating = false + } + } + onMidiKeyChanged: { + if (!internal.updating) { + internal.updating = true; + number = (midiKey % 12); + octave = (((midiKey-24) - number) / 12) + 1; + accident = internal.accidentMap[number][1]; + internal.updating = false + } + } + + QtObject { + id: internal + + property bool updating: false; + property var accidentMap: [ + // [vertical offset, accident] + [0, 0], + [0, 1], + [1, 0], + [1, 1], + [2, 0], + [3, 0], + [3, 1], + [4, 0], + [4, 1], + [5, 0], + [5, 1], + [6, 0], + ] + + function itemIndex(item) { + if (item.parent == null) + return -1 + var siblings = item.parent.children + for (var i = siblings.length - 1; i >=0 ; i--) + if (siblings[i] == item) + return i + return -1 // will never happen + } + function previousItem(item) { + if (item.parent == null) + return null + var siblings = item.parent.children + for (var i = itemIndex(item) - 1; i >=0 ; i--) + if (siblings[i].objectName == "symbol") + return item.parent.children[i] + return null + } + + property var rhythmTable: { "1": "\ue1d2", "2": "\ue1d3", "4": "\ue1d5", "8": "\ue1d7", "16": "\ue1d9", "32": "\ue1db", "64": "\ue1dd" } + } + + anchors { + bottom: parent.children[0].bottom; + bottomMargin: { + (parent.clef.type == 0) ? + ((internal.accidentMap[number][0])*5)+(octave-4)*35 + : + -10+((internal.accidentMap[number][0])*5)+(octave-2)*35; + } + left: spaced ? internal.previousItem(note).right:internal.previousItem(note).left; + leftMargin: spaced ? parent.spacing:0 + } + text: internal.rhythmTable[rhythm] + ((accident == -1) ? "\ue260":(accident == 1) ? "\ue262":(accident == -2) ? "\ue264":(accident == 2) ? "\ue263":"") + font.pixelSize: 35 +} diff --git a/src/app/qml/SheetMusicView/Score.qml b/src/app/qml/SheetMusicView/Score.qml new file mode 100644 index 0000000..46299ee --- /dev/null +++ b/src/app/qml/SheetMusicView/Score.qml @@ -0,0 +1,16 @@ +import QtQuick 2.7 + +Item { + property int spacing + property Clef clef + + objectName: "score" + + Row { + spacing: 0 + Repeater { + model: 25 + BravuraText { text: "\ue014" } + } + } +} diff --git a/src/app/qml/SheetMusicView/Sequence.qml b/src/app/qml/SheetMusicView/Sequence.qml new file mode 100644 index 0000000..786435b --- /dev/null +++ b/src/app/qml/SheetMusicView/Sequence.qml @@ -0,0 +1,11 @@ +import QtQuick 2.7 + +Repeater { + id: repeater + property bool spaced: true + + Note { + midiKey: modelData + spaced: (index == 0) ? true:repeater.spaced + } +} diff --git a/src/app/qml/SheetMusicView/SheetMusicView.qml b/src/app/qml/SheetMusicView/SheetMusicView.qml new file mode 100644 index 0000000..6c4519b --- /dev/null +++ b/src/app/qml/SheetMusicView/SheetMusicView.qml @@ -0,0 +1,21 @@ +import QtQuick 2.7 + +Score { + property alias model: sequence.model + property alias clef: clef + + function clearAllMarks() { + clef.type = 0 + sequence.model = [] + } + + width: childrenRect.width; height: childrenRect.height + FontLoader { id: bravura; source: "Bravura.otf" } + antialiasing: true + spacing: 10 + +// clef: clef + Clef { id: clef; type: 1 } + + Sequence { id: sequence; spaced: true } +}