Permalink
Browse files

Fixed issues with really complicated tuplet rhythms and lilypond nota…

…tion.
  • Loading branch information...
1 parent c6b2686 commit 5b207c0d75586eaa80646eb83aaabafe1f4c0046 @myronmarston committed Nov 25, 2008
View
58 FractalComposer/src/com/myronmarston/util/Fraction.java
@@ -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;
@@ -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...
@@ -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;
View
18 FractalComposer/test/com/myronmarston/music/OutputManagerTest.java
@@ -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 {
View
12 FractalComposer/test/com/myronmarston/util/FractionTest.java
@@ -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) {

0 comments on commit 5b207c0

Please sign in to comment.