Rappel : suite à la réponse d'Apple comme quoi mon application ne permet que de dessiner, mon challenge est désormais d'implémenter des fonctions spécifiques à l'écriture musicale (optimisation graphique), ainsi que les fonctions de sauvegarde ou d'annulation.
- on peut dessiner à main levée sur l'écran, en temps réel,
- orientations successives du tracé reconnues en temps réel,
- reconnaissance du tracé "Point",
- reconnaissance du tracé "Squiggle" (gribouilli),
- reconnaissance des séquences : note "ronde", "blanche" (transformation d'une ronde), "noire" (transformation d'une blanche), "croche"/"double croche"/"triple croche" (ajout de drapeaux à une noire),
- reconnaissance des séquences : silences "pause"/"demi pause", "soupir", "soupir"/"demi soupir"/"triple soupir" (ajout de drapeaux à un demi-soupir),
- architecture compatible avec MusicXML
- affichage d'une "debug view"
mettant en évidence la fin d'analyse d'un tracé,
Voir l'origine du projet et les versions antérieures (0.1 -> 0.15) : page 1
- API: récupération d'un objet (WM_View *) à la fin d'un tracé (if succeed)
plutôt qu'un code action,
- API: sérialisation de WM_View (pour sauver/restaurer).
- API : rajout d'un flag "temporaire" aux tracés, permet d'obliger
une vue créée à être validée ultérieurement (e.g. une ligne de portée par une
barre de mesure)
- API : la debugView prend des couleurs distinctes selon
OK/Temporaire/Non reconnu (le tracé n'appartient pas à la liste définie par
l'application) -> vert/orange/rouge,
- tracés temporaires : lorsqu'une vue est créée et valide, toutes les vues
temporaires non utilisées sont supprimées,
- API, création de vues groupées : le dernier tracé est placé à la fin de la liste
des sous-vues de celle retournée (important pour sa récupération),
- API: sérialisation de WM_GSRecognizerView,
- tri des lignes horizontales selon leur hauteur, ajustement de leur position
verticale (distribution graphique),
- ajustement des extrémités des lignes de portées,
- paramètre "dirty", variant de 0.0 à 1.0, pour l'ajustement des lignes de
portées,
- ajustement horizontal de la Bar Line sur la gauche ; paramètre dirty pour cet
ajustement.
- optimisation (réduction) du nombre des points d'un tracé,
- amélioration des tracés pour les lignes de portée et les barres
(transformation en courbe de Bézier) ; à faire pour
les autres symboles.
- API : UNDO (non testé sur application),,
- ajustement des notes sur les lignes et interlignes (la note
est associée à une ligne ou interligne).
- API : ajout (en DEBUG) de l'affichage du status de la direction en cours
d'analyse,
- FIX : ajout de strokes ("parcours") pour la reconnaissance du tracés de
notes,
- amélioration du tracés des notes (rondes, etc...) ; à faire
pour les hampes et les drapeaux,
- gros travail sur les interpolations : utilisation de splines
quadratiques, ré-écriture de l'algorithme de Douglas-Peucker, recherche
des paramètres de la spline d'après ses dérivées, ...,
- DEBUG : les points d'ancrage des splines sont mis en évidence (croix).
- FIX :le rapport hauteur/largeur des notes est appliqué aux portées d'une seule ligne ; remarque
: dans ce cas les tailles ne sont pas modifiées
- FIX : hampe dessinable dans les 2 sens ("Stem" en anglais)
- Hampe : distinction sens du tracé et position, par exemple un tracé
vers le haut, finissant sur une note, donne un stem down. Implique de distinguer les deux types d'objet hampe. Pratique pour la positionner à droite ou à gauche de la note, mais surtout,
requis par MusicXML.
- FIX : la reconnaissance de l'objet Flag (drapeau) tient forcément compte qu'il y a 2 types de hampes.
- FIX : la hampe doit rejoindre la note (à droite ou à gauche) à une hauteur qui n'est pas la hauteur moyenne de la note (le point gauche est plus bas que le point droit)
- position horizontale des notes : tient compte des notes superposées dans le temps (devant
être jouées simultanément)
- Optimisation graphique du contour des notes : le calcul des vecteurs aux points de contrôle
du dessins (ancres), ne doit pas tenir compte que des autres points de contrôle,
mais de points choisis dans tout le tracé selon leur distance.
- Ajout de strokes ("parcours") pour la reconnaissance des drapeaux (croches),
- Amélioration graphique : drapeaux
- API (DEBUG): Suppression du log des points pour un path non reconnu, s'il est validé en "squiggle",
- Amélioration graphique : gestion de la triple croche ("Thirty-Second Note")
- API, optimisation (spline) : séparation de la fonction qui associe des points de référence à chaque
ancre, appelée avant le calcul des vecteurs
- gestion de plusieurs drapeaux : la taille n'est pas la même selon que la hampe descende ou monte,
- utilisation de la technologie swizzle (iOS specific = modification dynamique d'une fonction) pour l'optimisation des appels,
- API : meilleure distinction entre la validation du stroke réalisé par dessin, et lorsque celui-ci est modifié donné par l'application, par exemple le premier n'est pas animé,
- ajustement de l'écart entre les flags d'une même note,
- en fonction de la taille d'un drapeau il peut être nécessaire d'allonger la hampe ; important pour plusieurs drapeaux mais s'applique au cas d'un seul,
- rallongement de la hampe demandé par le drapeau : le paramètre "dirty" est assez casse-pieds car chaque
utilise le sien ;
par exemple un drapeau peut demander à la hampe d'ajuster sa taille, ce qu'elle ne va faire
que partiellement, en fonction de son propre paramètre : la position et la taille du nouveau drapeau devront donc tenir compte de la taille
résultante.
- FIX important concernant un comportement iOS qui n'apparaît que sur la machine ("device") et pas l'émulateur ! Un article
détaille ce cas sur le site de l'application (cf. infra),
- FIX : les dessins étaient tronqués si on tournait
l'écran après que l'application ait démarré,
- suppression de l'affichage la barre de status (état de piles, du réseau, ...),
- barre de navigation rendue transparente,
- API : FIX sur le calcul des vecteurs aux extrémités de courbes non bouclées,
- amélioration graphique : gestion de la "Pause" ;
- alignement horizontal : les silences doivent être considérés comme des notes, mais pas toujours (éventualité
de superposition pour ces dernières),
- amélioration graphique : gestion de la "Demi-pause"
- l'alignement horizontal des notes/silences tient compte également des barres de mesures (chevauchement interdit),
- modification de la gestion des portées pour contenir plusieurs barres (mais leur capture n'est pas encore gérée)
- lorsque le recouvrement d'objet (avec celui à sa gauche) produit son déplacement (vers la droite), on teste
si celui à sa droite ne devient pas recouvert, ceci récursivement,
- lorsque sur une ligne le dernier objet est déplacé vers la droite, on vérifie s'il ne faut pas augmenter les longueurs de lignes,
- l'alignement horizontal des notes peut les faire se chevaucher (note "simultanées" / "accords"),
- chevauchement des notes uniquement si elles se trouvent sur deux lignes distinctes ;
dans le cas contraire, faut-il annuler la saisie ou décaler la suivante ? -> décaler
- le déplacement à droite d'une note autre que celle crée, par suite de la création d'un objet quelconque, ne doit pas tester
si elle chevauche celle à sa gauche (qui vient peut-etre d'être déplacée d'ou apparition de chevauchements inopinés) ; par contre
elle doit vérifier avant de se déplacer, que celle à sa gauche ne devait pas la chevaucher (sinon suppression du chevauchement éventuel),
- le chevauchement de notes peut être partiel, s'il y a moins d'une ligne d'écart entre les notes (ligne et interligne)
- FIX : crash si note dessinée à gauche de la barre ; faut-il annuler la saisie, corriger la position de la barre lors de sa création, ou
autoriser la saisie ? -> autoriser la saisie à gauche de la première barre de mesure
-> résolu.
- FIX : les demi-pauses pouvaient se poser sur... une interligne ! correction identique pour les pause ;
- si des lignes sont rallongées à droite (suite à un décalage d'objets), il faut agrandir le layer de la portée
sinon il est impossible de dessiner dans la partie rajoutée (puisque l'interprétation d'un tracé par l'API est contextuelle)
- API : la conversion de tracés en texte (exemple "{ dWest, dSouthWest, dSouth }") devient disponible en Release,
- FIX : lors du test de fusion, les notes immédiatement à gauche ou à droite
de la dernière dessinée étaient testées pour savoir si elles étaient
sur la même ligne (fusion annulée, cf. supra) ou à une ligne d'écart (fusion partielle = légèrement décalées), or ces dernières pouvant êtres
déjà fusionnées il pouvait se trouver une note sur la même ligne qui ne soit pas à proximité immédiate -> identifier
chacun des groupes et tester chacune de leur note.
- UNDO (API): ok pour objets temporaire (suppression),
- UNDO (API): ok pour Portées (suppression de la barre de mesure
+ les lignes horizontales redeviennent "temporaires"
et permettent à nouveau de créer une Portée !),
- UNDO (API): ok pour Rondes (suppression)
- UNDO (API): ok pour Blanche (suppression de la Hampe + la Ronde redevient une Blanche)
- UNDO (API): ok pour Noire (suppression du squiggle + la Noire redevient une Blanche)
- UNDO (API): ok pour Croche (Suppression du Drapeau ok ; Fix la Croche doit redevenir une noire -> ok) ; ok doubles et quadruples Croches
- UNDO (API): ok pour Pauses / Demi-Pauses (suppression), ok pour le
Squiggle (suppression)
remarque : pour la fonction annuler l'application ne gère pas encore :
- gestion d'erreur (API) : lorsqu'un tracé échouait, il était difficile de savoir s'il était non reconnu, ou s'il s'agissait d'un Squiggle
hors contexte, l'erreur générée le précise désormais.
- gestion d'erreur (API) : localisation des chaînes dans une table dédiée, pour ne pas interférer avec celles de l'application,
- amélioration graphique : gestion des Soupirs ; gestion des Demi-Soupirs,
- gestion des silences : modification des définitions d'actions dans l'application, pour les objets générés par l'API :
le Sixteenth Rest (quart de soupir) | devient un | Quarter Rest (demi soupir) + un flag ("drapeau"), | |
le ThirtySecond Rest (8ème de soupir) | devient un | Quarter Rest (demi soupir) + 2 x flag, | |
le SixtyFourth Rest (16ème de soupir) | devient un | Quarter Rest (demi soupir) + 3 x flag. |
- API: la suppression des vues temporaires est effectuée à chaque validation
d'un objet ; remarque : elles sont donc toujours conservées lorsqu'un tracé
ultérieur échoue.
- amélioration graphique : gestion des 1/4 Soupirs ; gestion des 1/8 Soupirs ;
- le tracé pour les soupirs ou demi-soupirs, est divisé en deux parties (nommées
"Stem" et "Flag") pour plus de commodité dans la gestion des tailles (la largeur
de chaque partie doit être la même), et produit une nouvelle spline par concaténation pour l'affichage.
- gestion des décalage à droite lors d'insertion de 1/4 ou 1/8 de Soupirs sur une portée.
- amélioration graphique : 16èmes de Soupirs,
- certains conflits avec l'API Apple apparaissent lorsque l'on veut manipuler des splines concaténées, d'où modification du schéma de l'appli :
c'est la vue qui est séparée en deux, et le quart de soupir devient dont un quart de soupir partiel (stem seulement) + flag :
Afficher le Log
Extrait du log - hiérarchie des vues pour un soupir ("quarter rest") avant et après découpage :
Views frames:
Main view
0x7d882600 Staff {{15, 45}, {245, 110}}
0x7d0a3400 Line {{5, 50}, {235, 10}}
0x7d889a00 Barline {{30, 0}, {10, 110}}
0x7d87f600 Quarter Rest (V1, temp) {{87, 34}, {65.5, 38}}
Views frames:
Main view
0x7d882600 Staff {{15, 45}, {245, 110}}
0x7d0a3400 Line {{5, 50}, {235, 10}}
0x7d889a00 Barline {{30, 0}, {10, 110}}
0x7d87f600 Quarter Rest (V1) {{87, 34}, {65.5, 38}}
0x7c8b1600 Rest Flag {{0, 0}, {65.5, 12.5}}
- même chose pour le demi-soupir (Eight Rest)
- API : implémentation de la fonction permettant d'obtenir ce résultat, c'est-à-dire d'exécuter d'une Action avec en paramètres une vue et une liste de points,
- API : implémentation de "- (NSString *)description"
pour WM_Action ;
on peut désormais utiliser "po" dans le debugger, et obtenir par exemple
(lldb) po actionForStemUp
In condition : Whole Note (-> Half Note) ; parent : no condition
(lldb) po strokeForHLine.actions
<__NSArrayM 0x7c12fe00>(
In condition : Stem (up) (-> unchanged) ; parent : Quarter Note (-> Eighth Note),
In condition : Stem (down) (-> unchanged) ; parent : Quarter Note (-> Eighth Note)
)
(lldb) po strokeForUndo.actions
<__NSArrayI 0x7c041510>(
In condition : unspecified
)
- FIX sur l'ajustement de taille du squiggle des notes : les points de référence des notes n'étaient pas modifiés lorsqu'elles était déplacées,
- API (DEBUG) : la liste des points trouvée (si désirée, à fixer dans le .pch) est affichée avant la liste des "Views frames",
- API : lorsqu'un tracé reconnu est "temporaire", son tracé est en orange
(et non plus seulement sa "debug view", qui disparait après 1s),
- UNDO (API): OK pour Soupirs / Demi-Soupirs,
- UNDO (API): OK pour quart / 8ème /16ème de Soupir, c'est à dire concernant l'ajout de flags,
- FIX (API), reconnaissance : le principe des éléments classés "failed" (ne pas confondre avec "ignored") est, que lorsqu'un élément
est de direction "indéterminée" (incertain car trop court, ou ambigu par son orientation), il doit être mis de côté pour être,
soit ajouté au "failed" suivant (puis testé à nouveau), soit supprimé si une meilleure orientation est trouvée. Il
est de même utilisé à la fin lorsque la reconnaissance échoué.
Il arrive, lorsqu'il se trouve à la fin, que la reconnaissance réussisse et qu'il
n'en soit pas tenu compte,
alors que sa longueur est importante. Le résultat est donc différent de celui escompté :
Il faut impérativement le tester ; dans cet exemple la reconnaissance échoue car
celà vaut mieux qu'un résultat erroné. Il sera possible dans une version
ultérieure de tester les deux possibilités d'orientation (heuristique).
Il est important qu'un élément failed "puisse" être "oublié", par exemple dans le cas ci-dessous (exemple réel obtenu par debugPoint:) :
- FIX, ajustement horizontal entre silences : lorsque le stem (qui est penché) s'allonge vers le bas (donc vers la gauche)
à cause de l'ajout de flags, il peut déborder sur un objet à sa gauche -> recalculer s'il faut le déplacer vers la droite
- Bar Lines : on peut rajouter des barres de mesure en milieu de portée.
Remarque : la durée des mesures entre deux barres (somme des durées des symboles, e.g. 4 temps) n'est pas vérifiée (version
ultérieure).
Remarque 2 : les lignes de portée sont éventuellement rallongées à chaque fois qu'un symbole est dessiné, elles ne
devraient pas obligatoirement dépasser
du dernier lorsque celui-ci, étant une barre de mesure, définit ainsi la fin de la portée (version
ultérieure) ;
- FIX: la création d'un stem sur les notes doit être prioritaire à celui des barres de mesures
(sinon création intempestive de barres, d'autant plus génante que l'utilisateur ne s'en rend pas compte...),
- Help Screen (Aide) : ajout de la version iPad,
versions localisées (4 version en tout : { iPad, iPhone } x { En, Fr })
- essai d'implémentation d'une scroll view : comme elle intercepte les "gestures" pour permettre
les déplacements de son contenu sur l'écran, les tracés sont également interceptés -> il faudrait faire une implémentation explicite pour pouvoir continuer à dessiner
(définition plus fine des actions) -> version future
- REDO, niveau API (non testé dans l'application)
- le REDO fonctionne pour l'application ; Undo, ou Undo suivi de Redo,
doivent permettre de continuer à dessiner -> ok
- RemoveAll :
- conflits lors de la suppression des portées -> modification du schéma de l'application :
plutôt que maintenir dans l'application une liste de Portées (mvScoreStaves) + une de symboles (mvScoreSymbols), on gère
une seule instance de MSScore :
@class MSScore; // contains: { stave(s) }
@class MSStaff; // contains: { line(s), staffSymbol(s) } ; barLine(s) are now in staffSymbol(s)
MSScore correspond ainsi au document entier, rendant plus facile la gestion future de documents multiples,
- la nouvelle hiérarchie permet de traiter le removeAll comme une sérialisation ; tests -> OK (dont silences et portées)
- FIX: lorsque une portée était agrandies, sa vue prenait la taille de la fenêtre, ce qui gênait l'identification de la portée
à considérer lors d'une nouvelle création d'objet
- Save/Load issue : pour récupérer la hiérarchie des vues il faut
appeler encodeObject: au plus haut niveau,
soit sur la recognizerView,
impliquant alors de sauver/recharger inutilement le recognizer -> le recognizer est désormais instancié par l'application et
non plus par la recognizerView,
un setter a été défini pour cette dernière. Cette solution a été choisie plutôt que définir une globale
ou variable de classe, afin de maintenir la possibilité d'avoir plusieurs recognizers par application (exemple : un deuxième pour
la saisie de texte ; non testé)
- FIX (API) : le squiggle des notes est tronqué -> le dernier segment ("failed")
était ignoré, le rajouter -> Ok
- Save/Load (API) : implémentation de encodeWithCoder:/initWithCoder:
sur WM_GSRecognizerView et WM_View,
- Save/Load : implémentation de encodeWithCoder:/initWithCoder:
pour MSScore
(qui décrit le "document" entier), MSStaff, MSLine, MSBarLine ;
tests : sauver toute la partition + tout effacer + tout restaurer -> OK ; affichage dans le log (avant/après restauration) OK
- Save/Load : vérifier que l'on peut dessiner sur la vue restaurée -> OK ; les lignes "temporaires" sont respectées :
couleur + utilisables comme prévu
- Save/Load : implémentation de encodeWithCoder/initWithCoder pour MSNote, MSStem, MSHead, MSFlag,
- API, definition de "- (NSString *)description" pour WM_View ;
permet d'utiliser "po nomObjet" ou même "po adresseObjet"
dans la console gdb/lldb,
EXEMPLE
(lldb) # dans WM_View.m :
(lldb) po self
0x7bb5ae00 Whole Note {{110, 82.5}, {100.5, 70.5}}
(lldb) po self.superview
0x7c2c7800 Staff {{34, 48.5}, {432.5, 159}}
(lldb) # ailleur :
(lldb) po 0x7bb5ae00
0x7bb5ae00 Whole Note {{76, 34}, {100.5, 70.5}}
(lldb) pri [0x7bb5ae00 getStroke].bounds
error: warning: receiver type 'int' is not 'id' or interface pointer, consider casting it to 'id'
error: no known method '-getStroke'; cast the message send to the method's return type
error: 1 errors parsing expression
(lldb) pri ((UIBezierPath *)[0x7bb5ae00 getStroke]).bounds
(CGRect) $58 = origin=(x=114.998085, y=87.3339538) size=(width=90.5161667, height=60.7788543)
- hiérarchie des vues (API) : insertion d'une ScrollView
au dessus des autres ; fonctionnement de l'appli OK, save/load OK ;
défilement non géré (attendre les gestures)
- FIX: après l'affichage de l'aide, le menu (le "?") est trop bas, surtout "iphone 6 Plus"
- Save/Load : pb de synchronisation des courbes de Bézier après restauration (décalage) -> au lieu de les gérer
(déplacement, taille, ...) dans l'appli et de passer une copie à l'API, on travaille directement sur l'API en lui demande à l'API de s'en charger
- FIX API: les coordonnées des vues sont arrondies car l'affichage de la sélection (en cours) ou de leur cadre (debug) est plus esthétiques ainsi
- FIX: l'annulation (Undo) de la création d'un objet ne remonte pas à l'application, ce qui avait des effets pénibles sur les flags
(allongement systématique de la hampe à chaque création/Undo de drapeaux) -> l'objet est marqué "supprimé" (et "restauré" lors du Redo) ;
- Save: le marquage "supprimé" est vérifié avant la sauvegarde, côté application ; pour l'API rien à changer.
- Save/Load : implémentation de encodeWithCoder/initWithCoder pour
MSRest_WholeAndHalf, MSRest_WholeAndHalf_Sq, _MSRest_QuarterAndEight, MSRest_Flag, MSRest_EightAndMore,
- FIX: mauvaise gestion de la scrollView lors de l'agrandissement des portées ou de la rotation -> OK
- FIX API: important correctif concernant un conflit entre le redimensionnement d'une vue et l'animation des vues contenues
- FIX: interférence entre Save/Load et Undo/Redo (les objets supprimés n'étant pas sauvés, la liste les contenant devenait invalide)
- pb scrollView, dimensions après rotation -> OK
- FIX API (Save/Load) : des objets étaient sauvés alors qu'ils n'étaient pas à jour (s'ils appartiennent à une vue qui a changé de taille
leurs coordonnées changent par rapport à leur parent pour que justement ils ne changent pas de position sur l'écran, ce qui donc ne se voyait pas) ;
- FIX Flags, lorsque le stem était tracé vers la note (stem supérieur tracé vers le bas ou le stem inférieur vers le haut)
- activation de la fonction Trash (Poubelle, i.e. "Tout supprimer"),
- localisation : l'aide est affichée automatiquement en Anglais et Français, mais doit s'afficher en Anglais pour les autres langues
(selon paramétrage dans "Réglages") -> OK sur l'appareil (mais pas sur l'émulateur ?)
- appel des fonctions Save/Load respectivement à la mise en veille de l'application et à son lancement,
- mise en évidence de l'activation de la fonction Annuler, par affichage de la debug view en bleu
- interdire la création des barLines lorsqu'elles sont trop courtes ! (confusion
encore trop fréquente avec le tracé de flags) -> doivent désormais recouvrir la remière et la dernière ligne ; rappel : le tracé est d'abord testé sur la portée, puis en cas d'échec sur les autres objets
- crash si l'on effectue juste un clic sur une blanche : l'API le considérant comme un squiggle (blanche->noire),
l'application tentait de le redimensionner, ce qui n'a aucun sens ;
-> FIXED niveau application ; l'API conserve les tracés réduits à un point ; pb retrouvé sur les pauses (fixed) ;
- pb sur les soupirs (et demi-soupirs): la division en vues faisaient que les segments n'étaient pas accolés
visuellement -> FIXED
- décallage à droite des soupirs : le flag ne suivait pas -> FIXED
- décallage des pauses : le squiggle ne suivait pas
- FIX: en raison de la désactivation de la fonction Sélection (inutile pour cette version),
des squiggle de taille nulle pouvaient modifier des objets (exemple :blanche->noire),
alors que le résultat n'était pas vraiment apparent.
- SOUMISSION à l'appStore (estimated size 33,1 MB)
Les commentaires présents dans cette page sont ceux utilisés dans GIT, épurés des détails
techniques.
Les animations de type vidéo, sont des captures du "Simulateur" iPhone/iPad (sur iMac), découpées et retranscrites en temps réel.
Les animations de type gif animé, sont des copies d'écran du "Simulateur",
assemblées (e.g. sur Photoshop).
Wan More, mon site Professionnel ; myScore n'est pas encore disponible au téléchargement.