w3c / musicxml

MusicXML specification
479 stars 56 forks source link

Typescript #486

Closed GeorgeTailor closed 1 year ago

GeorgeTailor commented 1 year ago

Hello everyone. I'm surprised no one asked about this before, but have you considered creating official typescript types for this specification?

I know you already have xsds for checking the schema, but typescript seems like a nice option to make MusicXML more JS-friendly. Typescript has exposed a set of types for browser's DOM already, which (imo) is much more complex than MusicXML, as it's an xml on steroids with more than 20 years of backwards compatibility.

mscuthbert commented 1 year ago

Hi George -- I think we're much more likely to do this with MNX which is going to use JSON as a main format. Since we're committed to keeping the DTD for at least one more version (in addition to the XSD), adding another schema for musicxml is too much to commit to at this time, unless there's some perfect XSD to TypeScript .d.ts file that I don't know about?

GeorgeTailor commented 1 year ago

MusicXML is not going anywhere for at least a couple of years for sure, but imo it will take much longer to deprecate musicXML in favor of MNX, given the current state of MNX. Additionally, it will be much easier to migrate any musicXML to MNX, since both can benefit from Typescript types, especially when one of them will be using JSON as a main format.

<midi-instrument id="P1-X4">
  <midi-channel>10</midi-channel>
  <midi-program>1</midi-program>
  <midi-unpitched>39</midi-unpitched>
  <volume>80</volume>
  <pan>0</pan>
</midi-instrument>

Typescript:

interface MidiInstrument {
  id: string;
  midiChannel?: MidiChannel;
  midiProgram?: MidiProgram;
  midiUnpitched?: MidiUnpitched;
  volume?: Volume;
  pan?: Pan;
}
interface MidiChannel {
  value: Range<1, 16>;
}
interface MidiProgram {
  value: Range<1, 128>;
}
interface MidiUnpitched {
  value: Range<1, 128>;
}
interface Volume {
  value: Range<0, 100>;
}
interface Pan {
  value: Range<-180, 180>;
}

type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
  ? Acc[number]
  : Enumerate<N, [...Acc, Acc['length']]>

type Range<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>

Once we have something like this, it is trivial to create a JSON representation of MusicXML (no more headaches for each app to prepare their own types) score and migrate it to MNX, which will be using JSON (hopefully, with Typescript interfaces exposed somehow).

@adrianholovaty do you have any opinion of this?

adrianholovaty commented 1 year ago

@GeorgeTailor Since you asked for my opinion specifically...

I personally would never use that. My philosophy is to treat MusicXML as an interchange format, not a native format — so there's a fair amount of translation that happens when ingesting a MusicXML file. In other words, I don't need to carefully model every data type of every last XML element and attribute, because I only deal with them when importing, after which I throw the data away. I'd bet most MusicXML applications are the same (though of course it's impossible to know for sure).

It would make more sense to have Typescript data available for MNX, as MNX is more intended to be a native format. But either way, I'd consider Typescript data to be a secondary "nice to have" thing as opposed to being essential.

GeorgeTailor commented 1 year ago

There are tons of xml-to-json parsers out there with some minor differences how they treat arrays or similar structures, but you could do something like:

const midiInstrumentXml = ... // get this specific element somehow
const midiInstrument: MidiInstrument = xmlToJson()
console.log(midiInstrument.pen)
                          ^ Error: property `pen` does not exist on type MidiInstrument
// fix the error, proceed translating to whatever object shape you want or just use it directly

When you export your work as MusicXML:

const midiInstrument: MidiInstrument = {
  id: "1"
}
midiInstrument.volume = 120;
              ^ Error: property `volume` out of range 0...100
// fix the error, then just do jsonToXML()
const xmlMidiInstrument = jsonToXML(midiInstrument);

Sure, you could just do jsonToXML(wholeDocument) and then validate it against an xsd, but that's a runtime problem you need to handle and additionally you'll need to load the whole document into memory so you could validate it, with typescript you could check whether you create a proper document during compile-time (that is if MusicXML has an official and updated typescript definitions which reflects current state of XML schema). I think having typescript might ease the granular control of specific elements in MusicXML, which is essential when dealing with such documents in a streaming manner, where memory problems are not an issue as it gets cleaned up rather efficiently. Also, manipulating existing typescript representation of a document becomes a breeze in JS world.

Overall, this would make sure a lot of people have a much easier life maintaining their own translation implementations, although at a cost of higher memory consumption (when not using streams, that is) and people that use MusicXML directly don't need to worry about whether something breaks when they change their code without having to write a plethora of tests cases, which is a nightmare given how many optional and inter-related stuff exists in MusicXML.

Does this make sense?

mscuthbert commented 1 year ago

Hi @GeorgeTailor -- I understand the utility of what you propose for Javascript developers who want to do this, but I can speak candidly that I don't have now nor will have in the years until MNX has taken major mindshare the bandwidth to do this. Keeping the DTD and the Schema up while making sure MNX is an easy transition from MusicXML is my major concern at the moment. Making MNX be easy to support in TypeScript and making MusicXML to MNX an easy conversion will be the roadmap to these features.

GeorgeTailor commented 1 year ago

@mscuthbert I actually started doing this for my project based on this repo that I have found.

That work is truly amazing, however it wasn't updated in a while and some shortcuts were made, so some interfaces are incorrect. I will try to raise a PR with my changes when I am done with it.

mscuthbert commented 1 year ago

I think that the PR should be to add a link to your project. I don't think the w3c Music Notation Working Group can take on the maintenance of this project.

GeorgeTailor commented 1 year ago

if that's the case then it doesn't make sense to have this ticket.