package com.xenoage.zong.musiclayout.spacer.beam.placement; import com.xenoage.zong.core.music.StaffLines; import com.xenoage.zong.core.music.chord.StemDirection; import com.xenoage.zong.musiclayout.spacer.beam.Anchor; import com.xenoage.zong.musiclayout.spacer.beam.Slant; import com.xenoage.zong.musiclayout.spacer.beam.placement.SingleStaffBeamPlacer.Placement; import com.xenoage.zong.musiclayout.spacer.beam.stem.BeamedStem; import com.xenoage.zong.musiclayout.spacer.beam.stem.BeamedStems; import lombok.val; import material.ExampleResult; import material.ExampleResult.*; import material.Examples; import material.beam.slant.Example; import material.beam.slant.RossBeamSlant; import material.beam.stafftouch.TouchExample; import org.junit.Test; import java.util.List; import static com.xenoage.utils.collections.CList.ilist; import static com.xenoage.utils.collections.CollectionUtils.alist; import static com.xenoage.utils.kernel.Range.range; import static com.xenoage.utils.math.Delta.df; import static com.xenoage.zong.core.music.StaffLines.staff5Lines; import static com.xenoage.zong.core.music.chord.StemDirection.Down; import static com.xenoage.zong.core.music.chord.StemDirection.Up; import static com.xenoage.zong.musiclayout.SLP.slp; import static com.xenoage.zong.musiclayout.notation.BeamNotation.lineHeightIs; import static com.xenoage.zong.musiclayout.spacer.beam.Anchor.fromLp; import static com.xenoage.zong.musiclayout.spacer.beam.placement.SingleStaffBeamPlacer.singleStaffBeamPlacer; import static com.xenoage.zong.musiclayout.spacer.beam.slant.SingleStaffBeamSlanter.singleStaffBeamSlanter; import static java.lang.Math.abs; import static material.ExampleResult.*; import static org.junit.Assert.*; /** * Tests for {@link SingleStaffBeamPlacer}. * * @author Andreas Wenger */ public class SingleStaffBeamPlacerTest { private SingleStaffBeamPlacer testee = singleStaffBeamPlacer; @Test public void getPlacementTest() { //exact result: no rounding required assertEqualsPlacement(new Placement(2, 3.5f), testee.getPlacement(2, 7, 3, 2.3f, 0.75f)); //0.4 quarter spaces (= 0.2 LP) higher: should be rounded down to same result assertEqualsPlacement(new Placement(2, 3.5f), testee.getPlacement(2, 7, 3, 2.3f + 0.2f, 0.75f)); //0.6 quarter spaces (= 0.3 LP) higher: should be rounded up to the next quarter space assertEqualsPlacement(new Placement(2.5f, 4), testee.getPlacement(2, 7, 3, 2.3f + 0.3f, 0.75f)); } private void assertEqualsPlacement(Placement expected, Placement actual) { assertEquals(expected.leftEndLp, actual.leftEndLp, df); assertEquals(expected.rightEndLp, actual.rightEndLp, df); } @Test public void isTouchingStaffTest() { Examples.test(TouchExample.all, (suite, example) -> { boolean expected = example.touch; boolean touch = testee.isTouchingStaff(example.placement, example.stemDir, example.beamHeightIs, example.staffLines); assertEquals(suite.getName() + ": " + example.name, expected, touch); }); } @Test public void getDictatorStemIndexTest_HorizontalOrAscending() { val stemsDown = new BeamedStems(ilist( beamedStem(2, 10, Down), beamedStem(4, 11, Down), beamedStem(6, 13, Down) )); val stemsUp = new BeamedStems(ilist( beamedStem(2, 10, Up), beamedStem(4, 11, Up), beamedStem(6, 13, Up) )); //horizontal line, downstem (find min LP) assertEquals(0, testee.getDictatorStemIndex(Down, stemsDown, 0)); //horizontal line, upstem (find max LP) assertEquals(2, testee.getDictatorStemIndex(Up, stemsUp, 0)); //ascending line, downstem (find min LP) assertEquals(0, testee.getDictatorStemIndex(Down, stemsDown, 0.95f)); assertEquals(1, testee.getDictatorStemIndex(Down, stemsDown, 1.05f)); assertEquals(1, testee.getDictatorStemIndex(Down, stemsDown, 1.95f)); assertEquals(2, testee.getDictatorStemIndex(Down, stemsDown, 2.05f)); //ascending line, upstem (find max LP) assertEquals(2, testee.getDictatorStemIndex(Up, stemsUp, 0.95f)); assertEquals(2, testee.getDictatorStemIndex(Up, stemsUp, 1.05f)); assertEquals(0, testee.getDictatorStemIndex(Up, stemsUp, 1.95f)); assertEquals(0, testee.getDictatorStemIndex(Up, stemsUp, 2.05f)); } @Test public void getDictatorStemIndexTest_Descending() { val stemsDown = new BeamedStems(ilist( beamedStem(2, 13, Down), beamedStem(4, 11, Down), beamedStem(6, 10, Down) )); val stemsUp = new BeamedStems(ilist( beamedStem(2, 13, Up), beamedStem(4, 11, Up), beamedStem(6, 10, Up) )); //descending line, downstem (find min LP) assertEquals(2, testee.getDictatorStemIndex(Down, stemsDown, -0.95f)); assertEquals(1, testee.getDictatorStemIndex(Down, stemsDown, -1.05f)); assertEquals(1, testee.getDictatorStemIndex(Down, stemsDown, -1.95f)); assertEquals(0, testee.getDictatorStemIndex(Down, stemsDown, -2.05f)); //descending line, upstem (find max LP) assertEquals(0, testee.getDictatorStemIndex(Up, stemsUp, -0.95f)); assertEquals(0, testee.getDictatorStemIndex(Up, stemsUp, -1.05f)); assertEquals(2, testee.getDictatorStemIndex(Up, stemsUp, -1.95f)); assertEquals(2, testee.getDictatorStemIndex(Up, stemsUp, -2.05f)); } @Test public void getDistanceToLineIsTest() { int left = 2; int right = 5; float lp = 3; //horizontal line float slant = 0; for (int i : range(left, right)) assertEquals(lp, testee.getDistanceToLineLp(lp, i, slant, left, right), df); //ascending line slant = 2; assertEquals(lp, testee.getDistanceToLineLp(lp, left, slant, left, right), df); assertEquals(lp - 4/3f, testee.getDistanceToLineLp(lp, 3, slant, left, right), df); assertEquals(lp - 4, testee.getDistanceToLineLp(lp, right, slant, left, right), df); //descending line slant = -4; assertEquals(lp, testee.getDistanceToLineLp(lp, left, slant, left, right), df); assertEquals(lp + 8/3f, testee.getDistanceToLineLp(lp, 3, slant, left, right), df); assertEquals(lp + 8, testee.getDistanceToLineLp(lp, right, slant, left, right), df); } @Test public void shortenTest() { //the following tests use arbitrary horizontal positions, but normal spacing (not close spacing) //p104 r1 c1: could be 3.5/sit, but is 3.25/straddle Placement old = new Placement(5f, 5f); BeamedStems stems = new BeamedStems(ilist( beamedStem(5, -2, Up), beamedStem(10, -2, Up))); Placement shortened = testee.shorten(old, Up, stems, 1, staff5Lines); assertEquals(new Placement(4.5f, 4.5f), shortened); //p104 r6 c1: could be 3.75/straddle and 3.5/hang, but is 3.5/sit and 3.25/straddle old = new Placement(-2.5f, -3f); stems = new BeamedStems(ilist( beamedStem(10, 5, Down), beamedStem(15, 4, Down))); shortened = testee.shorten(old, Down, stems, 1, staff5Lines); assertEquals(new Placement(-2f, -2.5f), shortened); //p104 r7 c1: is not shortened, since then a stem would be shorter than 3 spaces old = new Placement(-1f, -0.5f); stems = new BeamedStems(ilist( beamedStem(0, 5, Down), beamedStem(5, 6, Down))); shortened = testee.shorten(old, Down, stems, 1, staff5Lines); assertEquals(old, shortened); //p105 r1 c1: not shortened, because beam line would be within white space old = new Placement(6f, 6f); stems = new BeamedStems(ilist( beamedStem(0, -1, Up), beamedStem(8, -1, Up))); shortened = testee.shorten(old, Up, stems, 1, staff5Lines); assertEquals(old, shortened); //p105 r1 c2: could be 3.5/hang, but is 3.25/staddle old = new Placement(-1f, -1f); stems = new BeamedStems(ilist( beamedStem(10, 6, Down), beamedStem(15, 6, Down))); shortened = testee.shorten(old, Down, stems, 1, staff5Lines); assertEquals(new Placement(-0.5f, -0.5f), shortened); } /** * Tests with examples from Ross. */ @Test public void computeForOneStaffTestRoss() { List<Example> examples = new RossBeamSlant().getExamples(); List<ExampleResult> results = alist(); for (Example example : examples) { //collect data BeamedStems stems = example.getStems(); Slant slant = singleStaffBeamSlanter.compute(stems, 5); //run test Placement offset = testee.compute(slant, stems, 1, StaffLines.staff5Lines); //check result ExampleResult result = check(offset, example); results.add(result); } //success, when 100% of the examples are perfect or at least accepted //print accepted and failed results int perfect = 0, accepted = 0, failed = 0; for (ExampleResult result : results) { if (result.getResult() != Result.Perfect) { System.out.print(result.getExample().getName() + ": "); if (result.getResult() == Result.Accepted) { accepted++; System.out.print("not perfect, but accepted"); } else { failed++; System.out.print("FAILED"); } if (result.getComment() != null) System.out.print("; " + result.getComment()); System.out.println(); } else { perfect++; } } System.out.println(SingleStaffBeamPlacerTest.class.getSimpleName() + ": " + perfect + " perfect, " + accepted + " accepted, " + failed + " failed"); if (failed > 0) fail(); } private float[] getStemsXIs(Example example, int chordsCount) { float[] stemsXIs = new float[chordsCount]; float distance = example.getStemsDistanceIs(); for (int i : range(chordsCount)) stemsXIs[i] = i * distance; return stemsXIs; } private ExampleResult check(Placement actual, Example example) { Placement expected = example.getPlacement(); //check result if (abs(actual.leftEndLp - expected.leftEndLp) < df && abs(actual.rightEndLp - expected.rightEndLp) < df) { //perfect solution return perfect(example); } else { //not the perfect solution, but maybe it is still ok //it is still ok, when the slant is smaller than expected and when //the beam lines have a valid anchor String comment = "expected [" + expected.leftEndLp + "," + expected.rightEndLp + "] but was [" + actual.leftEndLp + "," + actual.rightEndLp + "]"; if (isAcceptedBeam(actual, example.getStemDir(), abs(example.leftNoteLp - example.rightNoteLp))) return accepted(example, comment); else return failed(example, comment); } } /** * Checks, if the given 8th beam line in a 5 line staff is at least accepted. * It is accepted, when it meets the following minimal requirements defined by Ross: * <ul> * <li>When the slant is equal to or less than the maximum allowed slant for * the interval, defined on p. 111, or smaller (p. 98: when in doubt, do not * exceed a slant of one space)</li> * <li>When the beam touches a staff line, use the correct anchors to avoid * white edges (p. 98 bottom). Otherwise, this is not needed (p. 103, * last sentence before the box).</li> * </ul> */ private boolean isAcceptedBeam(Placement actual, StemDirection stemDir, int intervalLp) { //slant must be in allowed playRange of interval (or smaller) float absSlantMax; float absSlantActual = abs(actual.leftEndLp - actual.rightEndLp); if (intervalLp < 1) //unison absSlantMax = 0f; else if (intervalLp < 2) //2nd absSlantMax = 2 * 0.25f; else if (intervalLp < 3) //3rd absSlantMax = 2 * 1; else if (intervalLp < 5) //4th or 5th absSlantMax = 2 * 1.25f; else if (intervalLp < 6) //6th absSlantMax = 2 * 1.5f; else if (intervalLp < 7) //7th absSlantMax = 2 * 1.75f; else //8th or more absSlantMax = 2 * 2f; if (absSlantActual > absSlantMax) return false; //when beam touches staff line, anchors must be correct if (singleStaffBeamPlacer.isTouchingStaff( //method is tested, so we can use it here actual, stemDir, lineHeightIs, staff5Lines)) { Anchor leftAnchor = fromLp(actual.leftEndLp, stemDir); Anchor rightAnchor = fromLp(actual.rightEndLp, stemDir); if (false == singleStaffBeamPlacer.isAnchor8thCorrect( //method is tested, so we can use it here leftAnchor, rightAnchor, actual.getDirection())) return false; } return true; } @Test public void isBeamOutsideStaffTest() { //stem up, above staff assertTrue(testee.isBeamOutsideStaff(Up, 13, 13, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Up, 12.1f, 13, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Up, 13f, 12.1f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Up, 11.9f, 11.9f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Up, 11.9f, 13, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Up, 13f, 11.9f, 5, 2)); //stem up, below staff assertTrue(testee.isBeamOutsideStaff(Up, -1, -1, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Up, -0.1f, -1, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Up, -1, -0.1f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Up, 0.1f, 0.1f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Up, 0.1f, -1, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Up, -1, 0.1f, 5, 2)); //stem down, above staff assertTrue(testee.isBeamOutsideStaff(Down, 9, 9, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Down, 8.1f, 9, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Down, 9, 8.1f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Down, 7.9f, 7.9f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Down, 7.9f, 9, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Down, 9, 7.9f, 5, 2)); //stem down, below staff assertTrue(testee.isBeamOutsideStaff(Down, -5, -5, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Down, -4.1f, -5, 5, 2)); assertTrue(testee.isBeamOutsideStaff(Down, -5, -4.1f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Down, -3.9f, -3.9f, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Down, -3.9f, -5, 5, 2)); assertFalse(testee.isBeamOutsideStaff(Down, -5, -3.9f, 5, 2)); } private BeamedStem beamedStem(float xIs, int endLp, StemDirection stemDir) { return new BeamedStem(xIs, stemDir, slp(0, endLp), slp(0, endLp)); } }