alda-lang / alda

A music programming language for musicians. :notes:
https://alda.io
Eclipse Public License 2.0
5.59k stars 286 forks source link

MusicXML Importer - Unpitched Percussion #362

Closed Scowluga closed 3 years ago

Scowluga commented 3 years ago

We want to add support for unpitched percussion instruments in MusicXML import. These unpitched instruments work differently from normal instruments. See https://en.wikipedia.org/wiki/General_MIDI#Percussion.

Handling Percussion Parts

All unpitched notes use the midi-percussion instrument. One possible way to handle this is to import all of these notes to a single instance of midi-percussion, but with different voices. This works since Alda supports an unbounded number of voices (instead of the usual 4).

I've gone with what I think is a better solution. I spawn separate midi-percussion instruments for each MusicXML part. This maintains the score-partwise integrity where each score-part corresponds to one Alda part. To differentiate the now multiple midi-percussion parts, I use the Alda aliasing mechanism. This is a lot more clear now, as different parts can still have voices. This is now easily differentiable since all MusicXML percussion parts have separate Alda midi-percussion parts.

Now obviously any midi-percussion part is different than normal musicXMLParts. So I've added two parameters that provide additional information for unpitched percussion parts:

  type musicXMLPart struct {
      // ...
      unpitched map[string]int32 
      alias string
  }

Now when we reach an unpitched note, we will be able to access this unpitched mapping in the current part to determine the correct note number (pitch).

Handling Note Pitches

Ok now we have a number, how do we convert this to an Alda note pitch? One option is to use the normal model.PitchIdentifier and create some sort of translation.

However, Alda supports the model.MidiNoteNumber pitch structure. This is precisely what we want. I believe it makes more sense to import into this direct note number pitch instead of doing some mapping. Perhaps the mapping can be accomplished elsewhere. That would not be the job of the importer.

I think it's actually going to be interesting to see how this plays out in code generation. Since model.MidiNoteNumber can only arise from MusicXML import or from Lisp lists, we can even hardcode this code generation. This is a discussion for next time.

Testing - Dealing with Barlines

When testing we run into a familiar enemy: the barline. Alda parsers model.Barline into the []model.DurationComponent of the most recent update with a duration. This is so we can deal with ties across barlines. We've run into this problem multiple times.

This has worked before because we made the importer also generate barlines the same way. The problem now is with testing. The Alda language does not support a natural way of generating model.MidiNoteNumber as a pitch. The only way to do this is using Lisp lists. But when using Lisp lists Alda no longer parses the barline into the same note.

As a workaround, I've refactored the MusicXML importer testing framework to include a possibility of just defining the expected []model.ScoreUpdate. This makes sense anyways. I've also added some helper functions to make generating unpitched percussion score updates easier.

There is an argument that I'm overcomplicating the testing framework. There could be simpler ways of testing, perhaps doing a preprocessing path to deal with barlines. However I'd argue that although my solution leads to a lot of code, the code is quite simple. And the point of tests is simplicity, even if it leads to a lot of code.