Skip to content

Instantly share code, notes, and snippets.

@alanwhite
Last active December 20, 2021 08:36
Show Gist options
  • Save alanwhite/ccf7f34599159ffcdf8564b89c04cdb2 to your computer and use it in GitHub Desktop.
Save alanwhite/ccf7f34599159ffcdf8564b89c04cdb2 to your computer and use it in GitHub Desktop.
Simple algo to automatically add beams to music
package xyz.arwhite.music.helpers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import xyz.arwhite.music.models.BaseNoteModel;
import xyz.arwhite.music.models.BeamEnum;
import xyz.arwhite.music.models.MeasureModel;
import xyz.arwhite.music.models.PartModel;
import xyz.arwhite.music.models.StaffElementModel;
import xyz.arwhite.music.models.TimeSigModel;
public class AutoBeamSimple extends AbstractAutoBeam {
/**
* Analyzes and updates notes in a part to set their beams appropriately. Note durations must be
* accurately set beforehand.
*
* @param mcx the musical context to apply containing time signature and other needed info
* @param part the part containing the notes to be managed
*/
public void forPart(MusicalContext mcx, PartModel part) {
super.forPart(mcx, part);
// work out the divisions per beat
var divsPerBeat = Divisions.divisionsPerBeat(mcx);
// break notes into beat groups
var beatList = noteElementsByBeat(part.getStaffElements(),divsPerBeat);
// work out beams in each beat
beatList.forEach(notes -> beamBeat(notes,divsPerBeat));
}
/**
* Analyzes and updates notes in a beat within a measure
*
* @param notes in the beat
* @param divsPerBeat number of divisions in a beat
*/
private void beamBeat(List<BaseNoteModel> notes, int divsPerBeat) {
int beatCursor = 0;
for (int index = 0; index < notes.size(); index++) {
var note = notes.get(index);
if ( beatCursor >= divsPerBeat )
beatCursor = 0;
if ( beatCursor == 0 )
// it's the start of the beat
firstNoteInBeat(notes,index);
else if ( beatCursor + note.getDuration() >= divsPerBeat )
// it's the end of a beat
lastNoteInBeat(notes,index);
else
// otherwise we're mid beat
middleNoteInBeat(notes,index);
beatCursor += note.getDuration();
}
}
/**
* Updates a note in the middle of a beat with the correct beams.
*
* @param notes
* @param index
*/
private void middleNoteInBeat(List<BaseNoteModel> notes, int index) {
// see how many we have in common with prior and next
var note = notes.get(index);
var nextTailCount = this.getNextNoteTails(notes, index);
var priorTailCount = this.getPriorNoteTails(notes, index);
var noteTailCount = this.tailsPerNote(note);
var commonPrior = Math.min(priorTailCount,noteTailCount);
var commonNext = Math.min(nextTailCount,noteTailCount);
var commonAllTails = Math.min(nextTailCount,Math.min(priorTailCount,noteTailCount));
if ( noteTailCount > 0 )
note.setBeams(Optional.of(new ArrayList<BeamEnum>()));
var beamIndex = 0;
// do an CONTINUE for each one in common with next and prior
while( beamIndex < commonAllTails )
note.getBeams().get().add(beamIndex++,BeamEnum.CONTINUE);
if ( commonPrior > commonAllTails ) {
// if we have some in common with prior but not next do an end
while( beamIndex < commonPrior )
note.getBeams().get().add(beamIndex++,BeamEnum.END);
// and then if we have any leftover hook backwards
while( beamIndex < noteTailCount )
note.getBeams().get().add(beamIndex++,BeamEnum.BACKWARD_HOOK);
} else if ( commonNext > commonAllTails ) {
// else if we have some in common with next but not prior do a begin
while( beamIndex < commonNext )
note.getBeams().get().add(beamIndex++,BeamEnum.BEGIN);
// and then if we have any leftover hook forward
while( beamIndex < noteTailCount )
note.getBeams().get().add(beamIndex++,BeamEnum.FORWARD_HOOK);
} else if ( noteTailCount > priorTailCount ) {
// else if we still have more tails than prior do a hook backward
while( beamIndex < noteTailCount )
note.getBeams().get().add(beamIndex++,BeamEnum.BACKWARD_HOOK);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment