Let me preface by saying that knowing some elementary music theory and music notation may be helpful in grasping the problem at hand.
I'm currently building a Music Notation and Tablature Editor (in Javascript). But I've come to a point where the core parts of the program are more or less there. All functionality I plan to add at this point will really build off the foundation that I've created. As a result, I want to refactor to really solidify my code.
I'm using an API called VexFlow to render notation. Basically I pass the parts of the editor's state to VexFlow to build the graphical representation of the score.
(EDIT: The following diagram is outdated as I began to rework the structure, please see the update at the bottom of the post for the new diagram) Here is a rough and stripped down UML diagram showing you the outline of my program:
In essence, a Part
has many Measure
s which has many Note
s which has many NoteItem
s (yes, this is semantically weird, as a chord is represented as a Note
with multiple NoteItem
s, individual pitches or fret positions). All of the relationships are bi-directional.
There are a few problems with my design because my Measure
class contains the majority of the entire application view logic.
The class holds the data about all VexFlow objects (the graphical representation of the score). It contains the graphical Staff object and the graphical notes. (Shouldn't these be placed somewhere else in the program?)
While
VexFlowFactory
deals with actual creation (and some processing) of most of the VexFlow objects,Measure
still "directs" the creation of all the objects and what order they are supposed to be created in for both the VexFlowStaff and VexFlowNotes.
I'm not looking for a specific answer as you'd need a much deeper understanding of my code. Just a general direction to go in.
Here's a thought I had, create an MeasureView
/NoteView
/PartView
classes that contains the basic VexFlow objects for each class in addition to any extraneous logic for it's creation? but where would these views be contained? Do I create a ScoreView
that is a parallel graphical representation of everything? So that ScoreView.render()
would cascade down PartView
and call render
for each PartView
and casade down into each MeasureView
, etc. Again, I just have no idea what direction to go in. The more I think about it, the more ways to go seem to pop into my head.
I tried to be as concise and simplistic as possible while still getting my problem across. Please feel free to ask me any questions if anything is unclear. It's quite a struggle trying to dumb down a complicated problem to its core parts.
UPDATE:--------------------------------------------------------------------------
So I went ahead and started refactoring with my idea from above. Here is an updated Diagram
@JW01 I understand why bi-directional relationships are potentially bad (difficult to maintain, increased complexity), however, I do believe the increased flexibility is worthwhile and I have implemented it in a maintainable way. Each notation "container" (Score
, Part
, Measure
, Note
- all contain children of some sort) inherits from Container
(this relationship isn't shown in the diagram because the program I use is shitty and it looks super messy). The Container.addItem(item)
method mixes in the Traversable
class methods and parent
property to the item added.
To me this seemed pretty clever and easy to understand. Basically, any item that gets put in a container gets extended functionality. As a mentioned in a previous comment, I may want to pull out a nice music data API out of this, which would allow for "plug-ins" to be easily created. Traversing in both directions would be particularly helpful for functionality like that. Plus these classes are small and maintainable, and unlikely to change.
However, I understand that the less things that depend on these relationships the better. So it's definitely best if I isolate this traversing functionality for use with the Selection
module.
I'm noticing that my View
classes are doing a lot now. Building, rendering, formatting and handling click. It would probably be wise to factor out a an abstract View module composed of a ViewBuilder, ViewFormatter, ViewRenderer, ClickBehavior?