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 #363

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. Basically, all unpitched instruments use the same midi-percussion instrument, but each correspond to a specific pitch. So "pitch" for the midi-percussion instrument does not actually correspond to a pitch, but instead a different unpitched instrument.

Handling Percussion Parts

One possible way to handle unpitched percussion parts is to import all of them into a single instance of midi-percussion, but with many different voices. This works since Alda supports an unbounded number of voices (instead of the usual 4). This works, but runs into some problems. The biggest one is that is it not easy to separate voices between different parts. Voices 1-4 could be for one part, then 5-7 could be for a separate part, etc. There is no part-wise differentiation in this single midi-percussion part, so the grouping of voices can be confusing. All the voices seem the same, but really they should be grouped by parts.

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, and each voice corresponds to a part.

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. The example percussion.alda file showcases this.

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 with standardizeBarlines

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.

I've decided to solve this problem once and for all by introducing the standardizeBarlines method which processes []model.ScoreUpdates and places barlines in a standard location. I've extracted both standardizeBarlines and evaluateLisp into the utility file. Postprocessing will need to standardize barlines within the importer.

daveyarwood commented 3 years ago

...although... :smile: After having begun to look at #364, I wonder if maybe this post-processing step might be a better place to apply the transformation where we turn notes expressed as MIDI note numbers into octave changes and notes expressed as letters + accidentals + durations. It seems like your idea with the post-processing step in the importer is to take the more "direct" output of the MusicXML importer and massage it into a series of ScoreUpdates that will be translated into more idiomatic Alda.

I don't have a strong preference either way, but I think I would tend to lean towards doing this as a post-processing step of the MusicXML importer. It might be advantageous if we kept the code gen part more general (i.e. less specific to importing from MusicXML), so that we could potentially reuse it as-is sometime down the road if we decide to implement other kinds of importers.

Thoughts?

Scowluga commented 3 years ago

I'm not opposed to doing the mapping in the postprocessor. The only thing stopping us would be if there is eventually going to be some Alda representation that allows you to define notes using their midi pitches (directly, not using Lisp). I don't think this will be the case, so I'll go ahead and add a mapping in the postprocessor (optimizer).