Skip to content

Instantly share code, notes, and snippets.

@alanwhite
Last active December 20, 2021 08:36
Show Gist options
  • Save alanwhite/4ac7a5f1dba69d21aad043e591dd3279 to your computer and use it in GitHub Desktop.
Save alanwhite/4ac7a5f1dba69d21aad043e591dd3279 to your computer and use it in GitHub Desktop.
Algo that implements a common beam style for music, where the primary subdivision of the beat is used to direct hooks
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;
/**
* Respects the primary subdivision of a beat, with hooks preferred over joining beams over a subdivision.
* This is the COMMON policy for automatic beam generation.
*
* @author Alan R. White
*
*/
public class AutoBeamCommon 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);
// and the divisions per primary subdivision of the beat
var divsPerSubDiv = Divisions.divisionsPerPrimarySubDivOfBeat(mcx);
// break notes into beat groups
var beatList = noteElementsByBeat(part.getStaffElements(),divsPerBeat);
// work out beams in each beat
beatList.forEach(notes -> beamBeat(notes,divsPerBeat,divsPerSubDiv));
}
/**
* 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 divsPerSubDiv) {
int beatCursor = 0;
int subDivCursor = 0;
for (int index = 0; index < notes.size(); index++) {
var note = notes.get(index);
if ( beatCursor >= divsPerBeat ) {
beatCursor = 0;
subDivCursor = 0;
}
if ( subDivCursor >= divsPerSubDiv )
subDivCursor = subDivCursor - divsPerSubDiv;
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 if ( subDivCursor + note.getDuration() >= divsPerSubDiv )
// last note in a subdiv but not in beat
lastNoteInSubDiv(notes,index);
else if ( subDivCursor == 0 )
// first note in a subdivision but not in beat
firstNoteInSubDiv(notes,index);
else
// otherwise we're mid beat
middleNoteInBeat(notes,index);
beatCursor += note.getDuration();
subDivCursor += 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);
}
}
/**
* Updates a note at the start of a subdivision with the correct beams
*
* @param notes
* @param index
*/
private void firstNoteInSubDiv(List<BaseNoteModel> notes, int index) {
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
/*
* Except this is a problem if the prior note had decided it was to hook back instead of begin
* It's almost like we need to know what decision the prior note took.
*
* We could inspect and if we want to do a continue, and the prior is hook back we don't incr beamIndex
* and don't do anything. Then the subsequent logic should kick in and we do a begin. Which won't work
* because the logic looks for beyond the common flags.
*
* So if we inspect the prior and see it's a hook back, we need to do a begin or hook forward depending on
* if we have a partner flag in the next note. Or refactor the thinking here.
*
* What if the next note isn't in our subdiv? That's a problem for all of this. Park for now.
*/
while( beamIndex < commonAllTails ) {
if ( getPriorNoteBeam(notes,index,beamIndex) == BeamEnum.BACKWARD_HOOK ) {
// if next has a flag at beamIndex level then it's a BEGIN, otherwise forward hook
// it must have as it's within the common tails count
note.getBeams().get().add(beamIndex++,BeamEnum.BEGIN);
} else
note.getBeams().get().add(beamIndex++,BeamEnum.CONTINUE);
}
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 ( commonPrior > commonAllTails ) {
// if we have some in common with prior but not next do 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 forward
while( beamIndex < noteTailCount )
note.getBeams().get().add(beamIndex++,BeamEnum.FORWARD_HOOK);
}
}
/**
* Updates a note that is at the end of a subdivision with the correct beams
*
* @param notes
* @param index
*/
private void lastNoteInSubDiv(List<BaseNoteModel> notes, int index) {
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 ) {
// and then if we have any leftover hook forward
while( beamIndex < noteTailCount )
note.getBeams().get().add(beamIndex++,BeamEnum.BACKWARD_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