Skip to content

Instantly share code, notes, and snippets.

@alanwhite
Created December 20, 2021 08:37
Show Gist options
  • Save alanwhite/cc9759bd656f8594533a909416bba5ec to your computer and use it in GitHub Desktop.
Save alanwhite/cc9759bd656f8594533a909416bba5ec to your computer and use it in GitHub Desktop.
Base class with utility methods for classes implementing automatic beaming algos
package xyz.arwhite.music.helpers;
import java.util.ArrayList;
import java.util.List;
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;
/**
* Contains methods common to any autobeam algorithm implementation.
*
* @author Alan R. White
*
*/
abstract class AbstractAutoBeam {
/**
* Analyzes and updates notes for all parts in a measure to set their beams appropriately.
*
* @param mcx the musical context to apply containing time signature and other needed info
* @param measure the measure containing the parts to be managed
*/
public void forMeasure(MusicalContext mcx, MeasureModel measure) {
measure.getParts().forEach(part -> this.forPart(mcx, part));
}
/**
* Clears any existing beams in a part, if not manually set, then sub class must
* analyze and update notes in the part to set their beams appropriately.
*
* @param mcx the musical context to apply containing time signature and other needed info
* @param part the part containing the notes to be managed
*/
protected void forPart(MusicalContext mcx, PartModel part) {
// first clear beams on all notes in the part that are not manually set
part.getStaffElements().stream()
.filter(note -> note instanceof BaseNoteModel)
.filter(note -> !((BaseNoteModel) note).isManualBeams())
.forEach(note -> ((BaseNoteModel) note).setBeams(Optional.empty()));
}
/**
* Updates a note at the start of a beat with the correct beams.
*
* @param notes
* @param index
*/
protected void firstNoteInBeat(List<BaseNoteModel> notes, int index) {
var note = notes.get(index);
var nextTailCount = this.getNextNoteTails(notes, index);
var noteTailCount = this.tailsPerNote(notes.get(index));
var commonTails = Math.min(nextTailCount,noteTailCount);
if ( noteTailCount > 0 )
note.setBeams(Optional.of(new ArrayList<BeamEnum>()));
var beamIndex = 0;
// do a BEGIN for each one in common with next
while( beamIndex < commonTails )
note.getBeams().get().add(beamIndex++,BeamEnum.BEGIN);
// do a hook forward for any left
while( beamIndex < noteTailCount )
note.getBeams().get().add(beamIndex++,BeamEnum.FORWARD_HOOK);
}
/**
* Updates a note at the end of a beat with the correct beams
*
* @param notes
* @param index
*/
protected void lastNoteInBeat(List<BaseNoteModel> notes, int index) {
var note = notes.get(index);
var priorTailCount = this.getPriorNoteTails(notes, index);
var noteTailCount = this.tailsPerNote(note);
var commonTails = Math.min(priorTailCount,noteTailCount);
if ( noteTailCount > 0 )
note.setBeams(Optional.of(new ArrayList<BeamEnum>()));
var beamIndex = 0;
// do an END for each one in common with next
while( beamIndex < commonTails )
note.getBeams().get().add(beamIndex++,BeamEnum.END);
// do a hook forward for any left
while( beamIndex < noteTailCount )
note.getBeams().get().add(beamIndex++,BeamEnum.BACKWARD_HOOK);
}
/**
* Obtain the tails the next note has.
*
* @param part contains the notes we need to work through
* @param index the index of the note we want to inspect forward from
* @return number of tails of next note or zero if no next note
*/
protected int getNextNoteTails(List<BaseNoteModel> notes, int index) {
if ( notes.size() <= index+1 )
return 0;
return tailsPerNote(notes.get(index+1));
}
/**
* Obtain the tails the prior note has.
*
* @param part contains the notes we need to work through
* @param index the index of the note we want to inspect backward from
* @return number of tails of prior note or zero if no prior note
*/
protected int getPriorNoteTails(List<BaseNoteModel> notes, int index) {
if ( index == 0 )
return 0;
return tailsPerNote(notes.get(index-1));
}
/**
* Determine the prior note's beam at a given level, protecting against non-existence
*
* @param notes
* @param index
* @param beamIndex
* @return
*/
protected BeamEnum getPriorNoteBeam(List<BaseNoteModel> notes, int index, int beamIndex) {
// if no prior make logic behave like it needs to start a new beam or hook forward
if ( index == 0 )
return BeamEnum.NONE;
if ( notes.get(index-1).getBeams().isEmpty() )
return BeamEnum.NONE;
var beams = notes.get(index-1).getBeams().get();
if ( beamIndex >= beams.size() )
return BeamEnum.NONE;
return beams.get(beamIndex);
}
/**
* Breaks down a flat list of notes in a measure into groups by beat
*
* @param staffElements elements in a measure including notes
* @param divsPerBeat number of divisions set in a beat in this score
* @return a list of beats, each beat containing a list of notes
*/
public List<List<BaseNoteModel>> noteElementsByBeat(
List<StaffElementModel> staffElements, int divsPerBeat) {
// ferret out any non-notes
List<BaseNoteModel> notes = staffElements.stream()
.filter(element -> (element instanceof BaseNoteModel))
.map(element -> (BaseNoteModel) element)
.collect(Collectors.toList());
int beatCursor = 0;
List<List<BaseNoteModel>> beats = new ArrayList<>();
List<BaseNoteModel> currentBeat = null;
for (int noteIndex = 0; noteIndex < notes.size(); noteIndex++) {
var note = notes.get(noteIndex);
if ( beatCursor >= divsPerBeat )
beatCursor = 0;
if ( beatCursor == 0 ) {
currentBeat = new ArrayList<BaseNoteModel>();
beats.add(currentBeat);
}
currentBeat.add(note);
beatCursor += note.getDuration();
}
return beats;
}
/**
* DRY helper to calculate the tails on a note.
*
* @param note that whose tail count we want
* @return the tail count
*/
protected int tailsPerNote(BaseNoteModel note) {
var noteValue = note.getNoteValue();
return switch(noteValue) {
case BaseNoteModel.quaver -> 1;
case BaseNoteModel.semiQuaver -> 2;
case BaseNoteModel.demiSemiQuaver -> 3;
case BaseNoteModel.hemiDemiSemiQuaver -> 4;
default -> 0;
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment