Application targets for the dn-m project.
dn-m / musicxml Goto Github PK
View Code? Open in Web Editor NEWImplementation of the musicXML specification in Swift
License: MIT License
Implementation of the musicXML specification in Swift
License: MIT License
Can you help me please. I write a music editor. Please tell me whether it is realistic to play music from this format in Audio Toolbox iOS? And how can I do this? Thank you.
The LilyPond test suite has relatively good coverage, but some real-world examples will be nice. For example, Beethoven string quartets.
This will also raise the stakes w/r/t performance.
As of 0.1.0, SWXMLHash
is used to deserialize musicXML strings into a traversable data structure. This package works for deserialization, though provides no API for serializing custom data structures into the XML format.
A goal of this project is to provide APIs both for reading, as well as writing, musicXML files.
It might make sense to conform the model types to Codable, for decoding as well as encoding. This will require building a custom XML Decoder, which would perhaps be the Swiftiest way to achieve this.
In order for #154, and 0.3.0 to happen, we need to implement encoding for all types.
Knowing what we know now (which is considerably more than when we started with the decoding process), @bwetherfield, is there anything we can do with Sourcery to jump start this process (I know you've already got a start on it)?
We need to keep track of test coverage with something like codecov.io.
Without an Xcode project, a little tweaking is necessary.
The Attributes
element can occur anywhere (downbeat of a measure, mid-measure). As such, we need to store these attributes by offset and Part
.
struct Score {
typealias Offset = // ?
let attributes: [Pair<ScorePart,Offset>: Set<Attribute>]
}
As per the musicXML spec, a score can be traversed in two ways:
"time-wise"
(through measures which contain parts)"part-wise"
(through parts which contain measures)As of 0.1.0, only a primitive "part-wise"
implementation is made, as this is the mode of traversal for the "Hello World" example.
The affected types are Measure
and Part
, which both must have part-wise and time-wise variants implemented.
Underlying each type could be a Traversal
enum, in the same way that MusicXML.Score is currently implemented.
Currently, attribute groups (e.g., print-style-align
) are implemented as structs (e.g., PrintStyleAlign
).
As such, they are nested into their parent type. However, the properties of attribute groups exist on the top-level of the parent type. This adds considerable complexity to the encoding/decoding of types.
Instead of nesting attribute groups as properties in complex types, we should declare attribute groups as protocols, and manually implement the properties in each type.
In other words, instead of:
struct SomeType {
let printStyleAlign: PrintStyleAlign
}
Try this:
protocol PrintStyleAlign {
var defaultX: Double? { get }
var defaultY: Double? { get }
...
}
struct SomeType: PrintStyleAlign {
var defaultX: Double?
var defaultY: Double?
...
var somethingElse: String?
}
This will require some additional keystrokes, but will make the encode/decode process much more straightforward. We can probably find/replace the current let printStyleAlign: PrintStyleAlign
with the list of required properties.
Alternatives: Use a class-based inheritance model. I would rather not dip into reference semantics just for convenience.
The Accidental
structure is defined in MusicXML as:
<!ELEMENT accidental (#PCDATA)>
<!ATTLIST accidental
cautionary %yes-no; #IMPLIED
editorial %yes-no; #IMPLIED
%level-display;
%print-style;
%smufl;
>
An example of this in the wild is:
<accidental editorial="yes" cautionary="yes">flat</accidental>
Due to CoreOffice/XMLCoder#12, elements which have both values (e.g., the #PCDATA
which is "sharp"
, "flat"
, etc.) and attributes (e.g., cautionary
, editorial
) are not yet decodable.
The DirectionType.dynamics
case currently holds onto a single Dynamics
as its associated value.
In the MusicXML spec, however, there should be able to be a sequence of Dynamics
values. In #126, we were able to decode a single Dynamics
value, which seems to be the vastly more common case.
@DJBen, @bwetherfield, let me know if you've got thoughts on how to deal with this.
The Direction
enum is structured similarly to Notation
, and thus, like #56, the Directions
property will not decode properly.
Copy the Partwise and Timewise .xml
rhythm-centric test files into the test suite as String
values.
Start by creating a new directory called 03_Rhythm
in each of the Tests/Partwise
and Tests/Timewise
directories.
Add new XCTestCase
subclasses called Partwise_03_Rhythm
/Timewise_03_Rhythm
located in their respective parent directories.
For each test, add a new file to the Tests
directory, with the naming like Partwise_03_Rhythm_A_Durations.swift
.
In that file, extend the Partwise_03_Rhythm
XCTestCase
subclass with a computed String
property with the name A_Durations
:
extension Partwise_03_Rhythm {
var A_Durations: String {
"""
<xml/><goes/><here/>
"""
}
}
Partwise_03_Rhythm
XCTestCase
subclass, add a test:class Partwise_03_Rhythm: XCTestCase {
func test_03_Rhythm() throws {
let _ = try MusicXML(string: A_Durations)
// repeat for each tests
}
}
These not-so-rigorous tests will expose glaring decoding issues. We can move onto details incrementally.
Add some examples of parsing, manipulating, and exporting very basic MusicXML samples to the README.
Since every page of the musicXML docs contains an example, would it be possible to scrape this, for total code coverage, similar to what we have done with the LilyPond tests. I am not sure if there is a public repo for the docs - perhaps that would be an easier place to start!
Many sample MusicXML files from the Lilypond project have been added in Tests/LilypondTestSuite
.
Because Swift Package Manager does not yet support bundled resources, convert each of these to an equivalently-named .swift
file, which contains a String
version of the XML
. This seems like good practice in any case, as it also removes one layer of testing (that the extraction of resources from a bundle succeeds).
Traversing a MusicXML
score either in a partwise or timewise fashion has its benefits.
Let's enable conversion between Partwise
and Timewise
elements.
I propose that we are in-too-deep with regards to the structure of our MusicXML
.
The current state-of-the-art is like this:
let musicXML = MusicXML(Score(traversal: .partwise(partwise)))
I don't think MusicXML
is giving us anything at the moment.
Instead, the top level could be Score
, and the decoding / encoding should happen in relation to that.
let musicXML = Score(.partwise(partwise), documentAttributes: ...)
let score = try Score(string: "...")
try score.write(to: url)
These initializers aren't valid yet, but possibly should be.
The dot
element is potentially (and often) empty. Because of XMLCoder #123, arrays of empty elements are not yet decodable.
The music-data
element in MusicXML is a collection of any number of values within a set of possible types
<!ENTITY % music-data
"(note | backup | forward | direction | attributes |
harmony | figured-bass | print | sound | barline |
grouping | link | bookmark)*">
This is representable as an enum
with associated values in Swift (really, it's a union type):
enum MusicDatum {
case note(Note)
case attributes(Attributes)
...
}
Due to CoreOffice/XMLCoder#25, arrays of enums with associated values are not yet decodable.
Currently both Timewise
and Partwise
have a property header: Header
. Isn't this better suited at the Score
level?
This fix presented #120 does not address cases like the one below. As per the spec, it would be possible to see something like this:
<key>
<key-step>B</key-step>
<key-alter>-1</key-alter>
<key-step>E</key-step>
<key-alter>-2</key-alter>
<key-accidental>slash-flat</key-accidental>
<key-step>A</key-step>
<key-alter>-2</key-alter>
<key-accidental>slash-flat</key-accidental>
<key-step>F</key-step>
<key-alter>2</key-alter>
</key>
With this fix, this example would not parse correctly.
@DJBen presented:
See my latest PR #124, I encountered the same issue with harmony having imploded group that is a list: harmony-chord.
I've tried many decoder combinations but none avail. My final solution is to parse them into a list of enums like below
enum KeyComponent { case keyStep(KeyStep) case keyAlter(KeyAlter) case keyAccidental(KeyAccidental) }
and reassemble it into multiple
Keys
(see #124).
@bwetherfield adds:
That looks good! Would something like this be possible to enforce order?
while !valuesContainer.isAtEnd { do { keyComponents.append(.keyStep(try valuesContainer.decode(KeyStep.self))) keyComponents.append(.keyAlter(try valuesContainer.decode(KeyAlter.self))) keyComponents.append(.keyAccidental(try valuesContainer.decodeIfPresent(KeyAccidental.self))) } catch { break } }
This would work with:
enum KeyComponent { case keyStep(KeyStep) case keyAlter(KeyAlter) case keyAccidental(KeyAccidental?) }
There are many types which have properties named value
, which are generally "Simple Types", and are what XMLCoder
calls "Coding Key Value Intrinsic". In XML parlance, complex types "extend" "Simple Types" with attributes.
For these types, we can remove the attribute label in initializers, as these types are required and usually self-explanatory.
For example, the complex type stem
"extends" the stem-value
type.
I suggest we implement initializers for types like this:
extension Stem {
init(_ value: StemValue, ...) {
self.value = value
...
}
}
Which make the call site look something like this:
let stem = Stem(.up)
@DJBen, does this work for or against any of your thoughts on the matter?
This may be related to #41, wherein empty elements don't currently play nice with XMLCoder
.
In this case the <chord/>
element does not even make it to the XMLKeyedDecodingContainer
when it is the first element. However, if it is moved below <pitch>
, it works.
The Attributes
structure is contained within the MusicData
structure, not the Measure
structure.
Currently, Key.NonTraditional
is defined as such:
struct NonTraditional {
public var step: Step
public var alter: Double
public var accidental: AccidentalValue
}
However, NonTraditional
should hold an array of these values (step
, alter
, and accidental
), instead of a single instance. Like so:
struct NonTraditional {
struct AlteredTone {
let step: Step
let alter: Double
let accidental: AccidentalValue
}
let alteredTones: [AlteredTone]
}
This should resolve the currently non-parsing 13c-KeySignatures-NonTraditional.xml
test.
Currently we have no way to know the top element to be whether score-partwise
or score-timewise
. We work around by attempting score-partwise
decoder first and upon catching any error, falling back to score-timewise
.
This sweeps potential decoding issues under the rug. For example, if our decoder has a bug when decoding partwise score, it will be caught by this logic and attempting to use score-timewise
to decode it and fails unsurprisingly, completely swallowing any errors and making our debugging process difficult.
I inquired in XMLCoder for ways to decode <!DOCTYPE>
, and get negative answers. I am thinking to use a simple regex matcher to extract top-level tag name. It should be robust enough given all music xml DTD declarations are fairly consistent.
What do you think? Looking forward to your opinions.
Currently, Measure
contains an array of Attribute
values. The Attribute
values are not actually applicable to a Measure
specifically, but can be defined at any point (incl. mid-measure).
Instead, model Measure
around these parameters:
<!ATTLIST measure
number CDATA #REQUIRED
text CDATA #IMPLIED
implicit %yes-no; #IMPLIED
non-controlling %yes-no; #IMPLIED
width %tenths; #IMPLIED
%optional-unique-id;
>
Note
has a complex structure. In order to create one, you need to dive down a couple enums deep.
Let's provide convenience initializers for each of these different Kind
s. Though it expands combinatorially, it may be nice to provide a convenience initializer for each PitchUnpitchedOrRest
variant of each Kind
.
Now that we are disabling .trimValueWhitespaces
from our decoder, it may make sense to create an XMLDecoder
subclass MusicXMLDecoder
.
This way we can use MusicXMLDecoder
when testing atomic units, so we know we are working with the same thing when decoding a whole MusicXML
object.
This will also allow us to wipe XMLCoder
from testing, which has seemed slightly out of place.
The Après un rêve
test sample is not decoding properly. Let's smoke it out.
The XML(En|De)Coder
is able to convert(To|From)KebabCase
automatically.
We are currently doing this manually within the CodingKeys
enum of each type (as necessary). We could standardize this by configuring XML(En|De)Coder
to do it for us.
This seems like it lives at the border of YAGNI, but @bwetherfield has found in his auto-choice-coding-key Sourcery expeditions that human error can be corrected by large-scale automations.
@bwetherfield, @DJBen, whaddya think?
A Normal
note may have 0
, 1
, or 2
<tie>
elements.
We should represent this as a struct with two optional fields:
struct Ties {
let start: Tie?
let stop: Tie?
}
and add a ties
property to Note.Normal
.
There are a few structures which are just bags of attribute groups (e.g., EmptyPrintStyleAlign
, EmptyFont
, etc.)
These things seem like implementation details that should only be knowable within the encoding / decoding processes, and not really appropriate to be part of the API.
We could either replace these Empty*
types with the attribute groups themselves (e.g., PrintStyleAlign
), or perhaps create custom types?
Now that all of the LilyPond tests parse without throwing up, we should assert that all of the tests continue to parse without throwing up.
We currently only test that we can decode the LilyPond test suite without throwing errors. Now, let's up the ante by making the LilyPond tests perform the following maneuver:
MusicXML
modelMusicXML
modelAs @jsbean commented on issue #98 and in README, to make this project truly usable by other third party projects, we want to expose publicly available initializers, otherwise the structs are not initializable.
Currently most of the structs are public, but lack initializers. Swift auto-synthesizes internal default initializer for us.
We never run into initialization issues that often because our tests uses @testable import
, which grant internal level access of the structs. However when I try to use MusicXML
as a dependency from a third party project, I get all kinds of
ClassName is inaccessible due to 'internal' protection level
Thus I want to envision a standard of initializers that we all follow, which is of course subject to debate.
Each struct should provide a default public initializer that covers all the parameters in each struct.
1.1. For optional fields, the public initializer will provide a default value of nil.
1.2. Alternative initializers can be kept internal for now.
Declare all properties const (by using let
instead of var
). Unless there are necessary places that requires var
(which we should discuss case by case), there are no need for mutability in most of the cases. We currently use var
before because we don't want to provide nil
to the auto-synthesized initializer. But now we define our default initializer and default optional fields to nil
, there are no reason to use var
anymore.
Here's an example that sums up the standard in 1 and 2.
public struct Example {
public let requiredField: String
public let optionalField: Int?
public init(requiredField: String, optionalField: Int? = nil) {
self.requiredField = requiredField
self.optionalField = optionalField
}
}
// Caller
Example(requiredField: "aaa")
Example(requiredField: "aaa", optionalField: 2)
import
, not @testable import
, aiming to verify that each public struct is initializable publicly.Here's an example.
import XCTest
// Do not use @testable
import MusicXML
class NoteInitializerTests: XCTestCase {
func testPitch() {
_ = Pitch(step: .d, octave: 3)
_ = Pitch(step: .d, alter: 1, octave: 4)
}
}
If we all agree, we can make improvement toward this direction and enforce how future struct are initialized. I welcome your input and comments.
Due to XMLCoder #122, choice element cases like Notations.Notation.tuplet(Tuplet)
, where the associated type (Tuple
) has attributes, are not decoded properly.
Essentially all of the Notation
cases are currently unsupported, because they use the same construct.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.