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 }
+}