Last active
December 20, 2021 08:36
-
-
Save alanwhite/ccf7f34599159ffcdf8564b89c04cdb2 to your computer and use it in GitHub Desktop.
Simple algo to automatically add beams to music
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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