use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.
the class ColumnSpacer method compute.
/**
* Computes a {@link ColumnSpacing} from a measure column.
* @param context the current context, with the current {@link MP} and precomputed
* element {@link Notation}s
* @param createLeading true, if a leading spacing has to be created, otherwise false
* @param notations the precomputed notations of the measure and voice elements
*/
public ColumnSpacing compute(Context context, boolean createLeading, Notations notations) {
context.saveMp();
int measureIndex = context.mp.measure;
Column column = context.score.getColumn(measureIndex);
ColumnHeader columnHeader = context.score.getHeader().getColumnHeader(measureIndex);
// compute the optimal spacings for each voice separately
List<List<VoiceSpacing>> voiceSpacingsByStaff = alist();
for (int iStaff : range(column)) {
List<VoiceSpacing> vss = alist();
Measure measure = column.get(iStaff);
for (Voice voice : measure.getVoices()) {
context.mp = MP.atVoice(iStaff, measureIndex, measure.getVoices().indexOf(voice));
VoiceSpacing vs = singleVoiceSpacer.compute(context, notations);
vss.add(vs);
}
voiceSpacingsByStaff.add(vss);
}
// compute the measure elements (like inner clefs) and accordingly updated voice spacings
ArrayList<List<ElementSpacing>> optimalMeasureElementsSpacingsByStaff = alist();
for (int iStaff : range(column)) {
context.mp = MP.atMeasure(iStaff, measureIndex);
List<ElementSpacing> measureSpacing = measureElementsSpacer.compute(context, createLeading, voiceSpacingsByStaff.get(iStaff), notations);
optimalMeasureElementsSpacingsByStaff.add(measureSpacing);
}
// compute the common beat offsets of this measure column
Fraction measureBeats = context.score.getMeasureBeats(measureIndex);
VoiceSpacingsByStaff voiceSpacings = new VoiceSpacingsByStaff(voiceSpacingsByStaff);
List<BeatOffset> beatOffsets = voicesBeatOffsetter.compute(voiceSpacings.getAll(), measureBeats, context.settings.offsetBeatsMinimal);
// recompute beat offsets with respect to barlines
BarlinesBeatOffsetter.Result offsets = barlinesBeatOffsetter.compute(beatOffsets, columnHeader, context.score.getMaxIS());
beatOffsets = offsets.voiceElementOffsets;
List<BeatOffset> barlineOffsets = offsets.barlineOffsets;
// compute the spacings for the whole column, so that equal beats are aligned
ArrayList<List<ElementSpacing>> alignedMeasureElementsSpacingsByStaff = alist();
for (int iStaff : range(column)) {
Measure measure = column.get(iStaff);
// voice spacings
for (int iVoice : range(measure.getVoices())) alignedVoicesSpacer.compute(voiceSpacings.get(iStaff, iVoice), beatOffsets);
// measure elements, based on the aligned voice spacings
context.mp = atMeasure(iStaff, measureIndex);
alignedMeasureElementsSpacingsByStaff.add(measureElementsSpacer.compute(context, createLeading, voiceSpacings.getStaff(iStaff), notations));
}
// compute spacings for each staff
List<MeasureSpacing> measureSpacings = alist(column.size());
for (int iStaff : range(column)) {
// create leading spacing, if needed
LeadingSpacing leadingSpacing = null;
if (createLeading) {
context.mp = atBeat(iStaff, measureIndex, 0, Fraction.Companion.get_0());
leadingSpacing = leadingSpacer.compute(context, notations);
}
// create measure spacing
float interlineSpace = context.score.getInterlineSpace(iStaff);
measureSpacings.add(new MeasureSpacing(atMeasure(iStaff, measureIndex), interlineSpace, voiceSpacings.getStaff(iStaff), alignedMeasureElementsSpacingsByStaff.get(iStaff), leadingSpacing));
}
context.restoreMp();
return new ColumnSpacing(measureIndex, measureSpacings, beatOffsets, barlineOffsets);
}
use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.
the class MeasureElementsSpacer method compute.
List<ElementSpacing> compute(BeatEList<Clef> clefs, @MaybeEmpty BeatEList<Key> keys, @MaybeNull TimeSignature time, boolean existsLeadingSpacing, List<VoiceSpacing> voiceSpacings, int staff, Notations notations, LayoutSettings layoutSettings) {
Key key0 = null;
if (keys.size() > 0 && keys.getFirst().getBeat().equals(Companion.get_0()))
key0 = keys.getFirst().getElement();
if (key0 == null && time == null && (clefs == null || clefs.size() == 0)) {
// nothing to do
return empty;
}
ArrayList<ElementSpacing> ret = alist();
float startOffset = layoutSettings.offsetMeasureStart;
// key and time
// ************
boolean isKey = !existsLeadingSpacing && key0 instanceof TraditionalKey;
boolean isTime = time != null;
if (isKey || isTime) {
float currentOffset = startOffset;
// ***
if (isKey) {
Notation keyNotation = notations.get(key0, staff);
ret.add(new SimpleSpacing(keyNotation, Companion.get_0(), startOffset));
currentOffset += keyNotation.getWidth().getUsedWidth();
}
// ****
if (time != null) {
Notation timeNotation = notations.get(time, staff);
ret.add(new SimpleSpacing(timeNotation, Companion.get_0(), currentOffset + timeNotation.getWidth().symbolWidth / 2));
currentOffset += timeNotation.getWidth().getUsedWidth();
}
// move voice elements, if not enough space before first voice element
ElementSpacing leftSE = getFirstElementSpacing(voiceSpacings);
if (leftSE != null) {
float leftSEx = getLeftX(leftSE);
// existing space
float ES = leftSEx;
// additional needed space
float AS = currentOffset - ES;
if (AS > 0) {
shift(voiceSpacings, AS);
startOffset += AS;
}
}
}
// voice 2: 1 o
if (clefs != null) {
for (BeatE<Clef> ME : clefs) {
Fraction MEb = ME.getBeat();
Notation MEnotation = notations.get(ME.getElement());
float MEwidth = MEnotation.getWidth().getWidth();
// if there is a leading spacing, ignore elements at beat 0
if (existsLeadingSpacing && !MEb.isGreater0())
continue;
// find VE1 and VE2 for the current element
ElementSpacing[] ses = getNearestSpacingElements(MEb, voiceSpacings);
ElementSpacing VE1 = ses[0], VE2 = ses[1];
// if VE1 is unknown, use startOffset. if VE2 is unknown, ignore this element
float VE1x = (VE1 != null ? getRightX(VE1) : startOffset);
if (VE2 == null)
continue;
float VE2x = getLeftX(VE2);
// existing space
float ES = VE2x - VE1x - 2 * layoutSettings.spacings.widthDistanceMin;
if (ES < MEwidth) {
// additional space needed
float AS = MEwidth - ES;
// move all elements at or after ME.beat
VE2x += AS;
shiftAfterBeat(voiceSpacings, AS, MEb);
}
// add measure element
float MEx = VE2x - layoutSettings.spacings.widthDistanceMin - MEwidth / 2;
ret.add(new SimpleSpacing(MEnotation, ME.getBeat(), MEx));
}
}
ret.trimToSize();
return ret;
}
use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.
the class SingleVoiceSpacer method compute.
public VoiceSpacing compute(Context context, Notations notations) {
Voice voice = context.score.getVoice(context.mp);
float is = context.score.getInterlineSpace(context.mp);
Fraction measureBeats = context.score.getMeasureBeats(context.mp.measure);
int staffLinesCount = context.score.getStaff(context.mp).getLinesCount();
return compute(voice, is, measureBeats, staffLinesCount, notations, context.settings);
}
use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.
the class ChordSpacingsReader method readChordSpacings.
/**
* Reads the {@link ChordSpacings} from the given {@link XmlReader} at a child element
* of the "chords" element.
*/
public static ChordSpacings readChordSpacings(XmlReader r) throws IOException {
HashMap<Fraction, Float> durationWidths = map();
// load the duration-to-width mapping
while (r.openNextChildElement()) {
if (r.getElementName().equals("chord")) {
// duration format: x/y, e.g. "1/4"
Fraction duration = Fraction.Companion.fromString(r.getAttributeNotNull("duration"));
// width format: x+y/z, eg. "3+1/2"
float width = Fraction.Companion.fromString(r.getAttributeNotNull("width")).toFloat();
durationWidths.put(duration, width);
}
r.closeElement();
}
return new ChordSpacings(durationWidths);
}
use of com.xenoage.utils.math.Fraction in project Zong by Xenoage.
the class MidiConverter method writeVoice.
/**
* Writes the given voice into the MIDI sequence.
* @param voiceMp the staff, measure and voice index
* @param repetition the index of the current {@link Repetition}
*/
private void writeVoice(MP voiceMp, int repetition) {
val voice = score.getVoice(voiceMp);
for (VoiceElement element : voice.getElements()) {
// ignore rests. only chords are played
if (false == MusicElementType.Chord.is(element))
continue;
val chord = (Chord) element;
// grace chords are not supported yet - TODO: ZONG-104: Play grace chords
if (chord.isGrace())
continue;
// start beat of the element
Fraction duration = chord.getDuration();
val startBeat = voice.getBeat(chord);
val rep = repetitions.get(repetition);
if (false == rep.contains(Companion.time(voiceMp.measure, startBeat)))
// start beat out of range: ignore element
continue;
// MIDI ticks
val startMidiTime = timeMap.getByRepTime(repetition, Companion.time(voiceMp.measure, startBeat));
long startTick = startMidiTime.tick;
long endTick = startTick + durationToTick(duration, resolution);
long stopTick = endTick;
if (false == options.midiSettings.durationFactor.equals(Companion.get_1())) {
// custom duration factor
stopTick = startTick + round((endTick - startTick) * options.midiSettings.durationFactor.toFloat());
}
// play note
if (startTick < stopTick) {
float volume = dynamics.getVolumeAt(voiceMp.withBeat(startBeat), repetition);
int midiVelocity = round(midiMaxValue * volume);
for (Note note : chord.getNotes()) {
addNoteToTrack(note.getPitch(), voiceMp.staff, startTick, stopTick, midiVelocity, 0);
}
}
// TODO Timidity doesn't like the following midi events
/*MetaMessage m = null;
if (musicelement instanceof Clef)
{
Clef c = (Clef) musicelement;
m = createMidiEvent(c, tracknumber);
}
else if (musicelement instanceof NormalTime)
{
NormalTime t = (NormalTime) musicelement;
m = createMidiEvent(t, resolution, tracknumber);
}
else if (musicelement instanceof Key)
{
Key k = (Key) musicelement;
m = createMidiEvent(k, tracknumber);
}
else if (musicelement instanceof Tempo)
{
Tempo tempo = (Tempo)musicelement;
m = MidiTempoConverter.createMetaMessage(tempo);
}
if (m != null)
{
MidiEvent event = new MidiEvent(m, starttick);
track.add(event);
}*-/
currenttickinvoice = endtick;
}*/
}
}
Aggregations