Skip to content

Commit

Permalink
Fixed issues with really complicated tuplet rhythms and lilypond nota…
Browse files Browse the repository at this point in the history
…tion.
  • Loading branch information
myronmarston committed Nov 25, 2008
1 parent c6b2686 commit 5b207c0
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 16 deletions.
58 changes: 43 additions & 15 deletions FractalComposer/src/com/myronmarston/util/Fraction.java
Expand Up @@ -193,7 +193,12 @@ public String toGuidoString() {
*/
public String toLilypondString(Fraction timeLeftInBar, Fraction barLength, Fraction tupletMultiplier) throws IllegalArgumentException, UnsupportedOperationException {
String lilypondString = toLilypondString(timeLeftInBar, barLength, tupletMultiplier, true);


// Lilypond blows up when a note with a tuplet multiplier is tied to another note,
// such as '/times 2/3 { c4 } ~ d4'. We'll remove the tie here, which isn't strictly
// the correct notation, but it's better to have lilypond produce something than nothing...
if (lilypondString.contains("times")) lilypondString = lilypondString.replaceAll(LILYPOND_TIE, " ");

// we should only have the dynamic placeholder once in this string...
assert lilypondString.contains(NotationNote.NOTE_PLACEHOLDER_2) : lilypondString;
assert lilypondString.lastIndexOf(NotationNote.NOTE_PLACEHOLDER_2) == lilypondString.indexOf(NotationNote.NOTE_PLACEHOLDER_2) : lilypondString;
Expand All @@ -206,10 +211,29 @@ public String toLilypondString(Fraction timeLeftInBar, Fraction barLength, Fract
}

private String toLilypondString(Fraction timeLeftInBar, Fraction barLength, Fraction tupletMultiplier, boolean firstNoteInListOfTiedNotes) throws IllegalArgumentException, UnsupportedOperationException {
if (!denomIsPowerOf2()) throw new UnsupportedOperationException("Lilypond does not support note durations that do not have a denominator power of 2. Instead, wrap the note in a Tuplet with the appropriate multiplier.");
if (this.denominator_ > MAX_ALLOWED_DURATION_DENOM) throw new IllegalArgumentException("The given duration (" + this.toString() + ") has a denominator that is outside of the allowed range. The denominator cannot be greater than " + MAX_ALLOWED_DURATION_DENOM + ".");
if (this.denominator_ > MAX_ALLOWED_DURATION_DENOM) throw new IllegalArgumentException("The given duration (" + this.toString() + ") has a denominator that is outside of the allowed range. The denominator cannot be greater than " + MAX_ALLOWED_DURATION_DENOM + ".");
assert timeLeftInBar.compareTo(barLength) <= 0 : timeLeftInBar;


if (!denomIsPowerOf2()) {
// We shouldn't actually get here for tuplet notes--instead, we'll
// get here when a non-tuplet note is split across a bar, splitting
// at an irregular point, i.e. splitting 1/4 into 1/6 | 1/12.

// We'll wrap the note in an appropriate tuplet multiplier in this case.
Fraction desiredFraction = new Fraction(1, MathHelper.getLargestPowerOf2LessThanGivenNumber(this.denominator_));
Fraction tupletMultiplierForThisFraction = this.dividedBy(desiredFraction);
tupletMultiplier = tupletMultiplier.times(tupletMultiplierForThisFraction);

return "\\times " +
tupletMultiplierForThisFraction.toString() +
" { " +
desiredFraction.toLilypondString(timeLeftInBar, barLength, tupletMultiplier, firstNoteInListOfTiedNotes) +
" }";
}

// if there is no time left in the bar, start a whole new bar...
if (timeLeftInBar.numerator_ == 0) timeLeftInBar = barLength;

Fraction tupletScaledTimeLeftInBar = timeLeftInBar.dividedBy(tupletMultiplier);

// this notationDuration should already be tuplet scaled, so compare it to the tupletScaledTimeLeftInBar...
Expand All @@ -225,28 +249,32 @@ private String toLilypondString(Fraction timeLeftInBar, Fraction barLength, Frac
// we only want to put a dynamic or articulation marking on the first
// note of the tied notes, so we have a special place holder for that
String afterFirstNoteString = (firstNoteInListOfTiedNotes ? NotationNote.NOTE_PLACEHOLDER_2 : "");

// take care of the easy cases: notes such as 1/4, 1/8, and
// notes using augmentation dots (3/8, 7/16, etc).
switch ((int) this.numerator_) {
case 1: return NotationNote.NOTE_PLACEHOLDER +
Long.toString(this.denominator_) +
case 1: return NotationNote.NOTE_PLACEHOLDER +
Long.toString(this.denominator_) +
afterFirstNoteString;
case 3: if (this.denominator_ < 2) break;
return NotationNote.NOTE_PLACEHOLDER +
Long.toString(this.denominator_ / 2) +
LILYPOND_AUGMENTATION_CHAR +
return NotationNote.NOTE_PLACEHOLDER +
Long.toString(this.denominator_ / 2) +
LILYPOND_AUGMENTATION_CHAR +
afterFirstNoteString;
case 7: if (this.denominator_ < 4) break;
return NotationNote.NOTE_PLACEHOLDER +
Long.toString(this.denominator_ / 4) +
LILYPOND_AUGMENTATION_CHAR +
LILYPOND_AUGMENTATION_CHAR +
return NotationNote.NOTE_PLACEHOLDER +
Long.toString(this.denominator_ / 4) +
LILYPOND_AUGMENTATION_CHAR +
LILYPOND_AUGMENTATION_CHAR +
afterFirstNoteString;
}
}

// split the duration into two seperate durations that can be tied together
Fraction d1 = this.getLargestPowerOf2FractionThatIsLessThanThis();

// if splitting it this way gives us a duration longer than our time left in the bar,
// just use the time left in the bar instead...
if (d1.compareTo(timeLeftInBar) > 0) d1 = timeLeftInBar;
Fraction d2 = this.minus(d1);
Fraction timeLeftInBar2 = timeLeftInBar.minus(d1);
assert timeLeftInBar2.compareTo(0L) >= 0 : timeLeftInBar2;
Expand Down
18 changes: 18 additions & 0 deletions FractalComposer/test/com/myronmarston/music/OutputManagerTest.java
Expand Up @@ -304,6 +304,24 @@ public void tempoIsCached() throws Exception {
// the output manager should have the tempo at the time it was created
assertEquals(87, outputManager.getTempo());
}

@Test
public void testClairesError() throws Exception {
// Claire created a piece with really complicated rhythms that my Lilypond
// notation code couldn't originally handle. This is a regression test
// to make sure this never breaks...

final FractalPiece fp = new FractalPiece();
fp.setGermString("d4,1/12 c4,1/2 c4,1/12 b4 b4 c4,1/12 d4,p a4,pp");
fp.createDefaultSettings();

FileHelper.createAndUseTempFile("ClaireTest", ".pdf", new FileHelper.TempFileUser() {
@Override
public void useTempFile(String tempFileName) throws Exception {
fp.createPieceResultOutputManager().savePdfFile(tempFileName);
}
});
}

@Test
public void testLastFileNameMethods() throws Exception {
Expand Down
12 changes: 11 additions & 1 deletion FractalComposer/test/com/myronmarston/util/FractionTest.java
Expand Up @@ -94,12 +94,22 @@ public void toLilypondString() {
testToLilypondString("1/4", "1/6", "4/4", "2/3", "%1$s4%2$s");
testToLilypondString("1/4", "1/12", "4/4", "2/3", "%1$s8%2$s ~ %1$s8");

// tuplet-scaled notes that cross the bar line...
testToLilypondString("9/8", "3/4", "3/4", "2/3", "%1$s2.%2$s ~ %1$s4.");
testToLilypondString("3/2", "3/4", "3/4", "2/3", "%1$s2.%2$s ~ %1$s4. ~ %1$s4.");

// when the previous note used up all of the current bar, it should handle having no time left in the bar...
testToLilypondString("1/4", "0/1", "4/4", "1/1", "%1$s4%2$s");

// a single note split into a tuplet because of limited time left in the bar...
// note that no tie (~) is used because Lilypond can't handle tieing tuplets like this.
testToLilypondString("1/4", "1/6", "4/4", "1/1", "\\times 2/3 { %1$s4%2$s } \\times 2/3 { %1$s8 }");

// durations with denoms greater than 64 are not allowed
try {
(new Fraction("1/128")).toLilypondString(new Fraction("4/4"), new Fraction("4/4"), new Fraction(1, 1));
fail();
} catch (IllegalArgumentException ex) { }

}

public static void testToLilypondString(String durationFraction, String timeLeftInBarFraction, String barLengthFraction, String tupletMultiplier, String expectedString) {
Expand Down

0 comments on commit 5b207c0

Please sign in to comment.