Groove.java

1
package com.github.dakusui.symfonion.song;
2
3
import com.github.dakusui.symfonion.compat.json.CompatJsonUtils;
4
import com.github.dakusui.symfonion.utils.Fraction;
5
import com.google.gson.JsonArray;
6
import com.google.gson.JsonElement;
7
import com.google.gson.JsonObject;
8
9
import java.util.LinkedList;
10
import java.util.List;
11
12
import static com.github.dakusui.symfonion.compat.exceptions.CompatExceptionThrower.illegalFormatException;
13
import static com.github.dakusui.symfonion.compat.exceptions.SymfonionIllegalFormatException.NOTE_LENGTH_EXAMPLE;
14
import static com.github.dakusui.symfonion.utils.Fraction.ZERO;
15
import static com.github.valid8j.classic.Requires.requireNonNull;
16
import static com.github.valid8j.fluent.Expectations.*;
17
import static com.github.valid8j.pcond.forms.Functions.parameter;
18
import static java.util.Collections.singletonList;
19
20
/**
21
 * A class that models a musical "groove", which gives slightly different stresses and lengths of notes in a score.
22
 * A groove is modeled as a sequence of beats.
23
 */
24
public class Groove {
25
  public static final int        TICKS_FOR_QUARTER_NOTE = 384;
26
  private final       List<Beat> beats;
27
28
  private final int resolution;
29
30
31
  /**
32
   * Creates an object of this class.
33
   *
34
   * @param beats Beats with which this groove is defined.
35
   */
36
  Groove(List<Beat> beats) {
37
    this.resolution = TICKS_FOR_QUARTER_NOTE;
38
    this.beats      = requireNonNull(beats);
39
  }
40
41
  public int calculateGrooveAccent(Fraction relPosInStroke, Fraction relativePositionInBar) {
42
    Unit unit = resolveRelativePositionInStroke(this, relPosInStroke, relativePositionInBar);
43 1 1. calculateGrooveAccent : replaced int return with 0 for com/github/dakusui/symfonion/song/Groove::calculateGrooveAccent → SURVIVED
    return unit.accentDelta();
44
  }
45
46
  public long calculateAbsolutePositionInTicks(Fraction relativePositionInStroke, Fraction relativePositionInBar, long barPositionInTicks) {
47
    Unit unit                         = resolveRelativePositionInStroke(this, relativePositionInStroke, relativePositionInBar);
48
    long relativePositionInBarInTicks = unit.pos();
49 2 1. calculateAbsolutePositionInTicks : replaced long return with 0 for com/github/dakusui/symfonion/song/Groove::calculateAbsolutePositionInTicks → KILLED
2. calculateAbsolutePositionInTicks : Replaced long addition with subtraction → KILLED
    return barPositionInTicks + relativePositionInBarInTicks;
50
  }
51
52
  public Fraction length() {
53
    Fraction ret = ZERO;
54
    for (Beat beat : beats) {
55
      ret = Fraction.add(ret, beat.length());
56
    }
57 1 1. length : replaced return value with null for com/github/dakusui/symfonion/song/Groove::length → KILLED
    return ret;
58
  }
59
60
  /**
61
   * Resolves a position and accent, where a note at the given offset from a bar should be played, if this `Groove` object
62
   * is applied.
63
   *
64
   * @param offset An offset from the beginning of a bar.
65
   * @return An object that indicates how a note at `offset` should be played.
66
   */
67
  public Unit resolve(Fraction offset) {
68
    assert preconditions(value(offset).toBe().notNull());
69 1 1. resolve : replaced return value with null for com/github/dakusui/symfonion/song/Groove::resolve → KILLED
    return computeUnit(offset, this.length(), this.beats, this.resolution);
70
  }
71
72
  private static Unit computeUnit(Fraction offset, Fraction grooveLength, List<Beat> grooveBeats, int grooveResolution) {
73
    Fraction rest = foldPosition(offset.clone(), grooveLength);
74
    long     pos  = 0;
75 1 1. computeUnit : negated conditional → KILLED
    Fraction shift = offset.isNegative() ? Fraction.subtract(rest, offset) : ZERO;
76
    int i = 0;
77 2 1. computeUnit : changed conditional boundary → KILLED
2. computeUnit : negated conditional → KILLED
    while (Fraction.compare(rest, ZERO) > 0) {
78 2 1. computeUnit : changed conditional boundary → KILLED
2. computeUnit : negated conditional → KILLED
      if (i >= grooveBeats.size()) {
79
        break;
80
      }
81
      Beat beat = grooveBeats.get(i);
82
      rest = Fraction.subtract(rest, beat.length);
83 1 1. computeUnit : Replaced long addition with subtraction → KILLED
      pos += beat.ticks;
84 1 1. computeUnit : Changed increment from 1 to -1 → KILLED
      i++;
85
    }
86
    long p;
87
    int  d = 0;
88 1 1. computeUnit : negated conditional → KILLED
    if (Fraction.compare(rest, ZERO) == 0) {
89 2 1. computeUnit : changed conditional boundary → KILLED
2. computeUnit : negated conditional → KILLED
      if (i < grooveBeats.size()) {
90
        d = grooveBeats.get(i).accent;
91
      }
92
      p = pos;
93 2 1. computeUnit : changed conditional boundary → SURVIVED
2. computeUnit : negated conditional → KILLED
    } else if (Fraction.compare(rest, ZERO) < 0) {
94 1 1. computeUnit : Replaced integer subtraction with addition → KILLED
      Beat beat = grooveBeats.get(i - 1);
95 2 1. computeUnit : Replaced double multiplication with division → KILLED
2. computeUnit : Replaced double addition with subtraction → KILLED
      p = (long) (pos + Fraction.div(rest, beat.length).doubleValue() * beat.ticks);
96
    } else {
97 2 1. computeUnit : Replaced long addition with subtraction → KILLED
2. computeUnit : Replaced double multiplication with division → KILLED
      p = (pos + (long) (rest.doubleValue() * grooveResolution));
98
    }
99 3 1. computeUnit : Replaced double multiplication with division → KILLED
2. computeUnit : Replaced long subtraction with addition → KILLED
3. computeUnit : replaced return value with null for com/github/dakusui/symfonion/song/Groove::computeUnit → KILLED
    return new Unit(p - (long) (shift.doubleValue() * grooveResolution), d);
100
  }
101
102
  private static Fraction foldPosition(Fraction position, Fraction grooveLength) {
103
    assert preconditions(value(position).toBe().notNull(), value(grooveLength).toBe().notNull(), value(grooveLength).invokeStatic(Fraction.class, "compare", parameter(), ZERO).asInteger().toBe().greaterThan(0));
104
    Fraction ret = position;
105 1 1. foldPosition : negated conditional → TIMED_OUT
    while (ret.isNegative()) ret = Fraction.add(grooveLength, ret);
106
    assert postconditions(value(ret).toBe().notNull(), value(ret).invokeStatic(Fraction.class, "compare", parameter(), ZERO).asInteger().toBe().greaterThanOrEqualTo(0));
107 1 1. foldPosition : replaced return value with null for com/github/dakusui/symfonion/song/Groove::foldPosition → KILLED
    return ret;
108
  }
109
110
  /**
111
   * // @formatter:off
112
   * Creates a new `Groove` object from a given JsonArray object.
113
   *
114
   * The `grooveDef` should look like as follows:
115
   *
116
   * [source, JSON]
117
   * .grooveDef
118
   * ----
119
   * [
120
   *   {
121
   *     "length": "1/16",
122
   *     "ticks": 24,
123
   *     "accent": 10
124
   *   },
125
   *   "..."
126
   * ]
127
   * ----
128
   *
129
   * @param grooveDef A definition of a groove.
130
   * @return A new groove object
131
   * // @formatter:on
132
   */
133
  public static Groove createGroove(JsonArray grooveDef) {
134
    Groove.Builder b = new Groove.Builder();
135
    for (JsonElement elem : grooveDef) {
136
      JsonObject cur    = CompatJsonUtils.requireJsonObject(elem);
137
      String     len    = CompatJsonUtils.asString(cur, Keyword.length);
138
      long       ticks  = CompatJsonUtils.asLong(cur, Keyword.ticks);
139
      int        accent = CompatJsonUtils.asInt(cur, Keyword.accent);
140
141
      Fraction f = PartMeasure.parseNoteLength(len);
142 1 1. createGroove : negated conditional → KILLED
      if (f == null) {
143
        throw illegalFormatException(CompatJsonUtils.asJsonElement(cur, Keyword.length), NOTE_LENGTH_EXAMPLE);
144
      }
145
      b.add(f, ticks, accent);
146
    }
147 1 1. createGroove : replaced return value with null for com/github/dakusui/symfonion/song/Groove::createGroove → KILLED
    return b.build();
148
  }
149
150
  static Groove defaultGrooveOf(Fraction barLength) {
151 1 1. defaultGrooveOf : replaced return value with null for com/github/dakusui/symfonion/song/Groove::defaultGrooveOf → KILLED
    return new Groove(singletonList(new Beat(barLength, (long) Fraction.multi(barLength, new Fraction(TICKS_FOR_QUARTER_NOTE, 1)).doubleValue(), 0)));
152
  }
153
154
  private static Unit resolveRelativePositionInStroke(Groove groove, Fraction relativePositionInStroke, Fraction relativePositionInBar) {
155 1 1. resolveRelativePositionInStroke : replaced return value with null for com/github/dakusui/symfonion/song/Groove::resolveRelativePositionInStroke → KILLED
    return groove.resolve(Fraction.add(relativePositionInBar, relativePositionInStroke));
156
  }
157
158
  /**
159
   * A builder for `Groove` class.
160
   */
161
  public static class Builder {
162
    final List<Beat> beats = new LinkedList<>();
163
164
    /**
165
     * Creates an object of this class.
166
     */
167
    public Builder() {
168
    }
169
170
    /**
171
     * Adds a beat to this groove.
172
     *
173
     * @param fraction A "logical" length of a beat.
174
     * @param ticks    Indicates how many ticks should the `fraction` be played
175
     * @param accent   A delta how should the `beat` be stressed. This can be negative.
176
     * @return This object.
177
     */
178
    public Builder add(Fraction fraction, long ticks, int accent) {
179
      beats.add(new Beat(fraction, ticks, accent));
180 1 1. add : replaced return value with null for com/github/dakusui/symfonion/song/Groove$Builder::add → KILLED
      return this;
181
    }
182
183
    /**
184
     * Builds an object of `Groove`.
185
     *
186
     * @return A new `Groove` object.
187
     */
188
    public Groove build() {
189 1 1. build : replaced return value with null for com/github/dakusui/symfonion/song/Groove$Builder::build → KILLED
      return new Groove(beats);
190
    }
191
  }
192
193
  /**
194
   * A record that models each element in a groove.
195
   *
196
   * @param length A length of a beat on a score.
197
   * @param ticks  Actual length of the beat in ticks.
198
   * @param accent An accent of the groove.
199
   */
200
  record Beat(Fraction length, long ticks, int accent) {
201
  }
202
203
  /**
204
   * A class that stores a result of {@link Groove#resolve(Fraction)}.
205
   *
206
   * @param pos         A position at which a given note should be played. Ticks from the beginning of the bar.
207
   * @param accentDelta A delta that indicates how much a given note should be stressed.
208
   */
209
  public record Unit(long pos, int accentDelta) {
210
  }
211
}
212

Mutations

43

1.1
Location : calculateGrooveAccent
Killed by : none
replaced int return with 0 for com/github/dakusui/symfonion/song/Groove::calculateGrooveAccent → SURVIVED
Covering tests

49

1.1
Location : calculateAbsolutePositionInTicks
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced long return with 0 for com/github/dakusui/symfonion/song/Groove::calculateAbsolutePositionInTicks → KILLED

2.2
Location : calculateAbsolutePositionInTicks
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
Replaced long addition with subtraction → KILLED

57

1.1
Location : length
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
replaced return value with null for com/github/dakusui/symfonion/song/Groove::length → KILLED

69

1.1
Location : resolve
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
replaced return value with null for com/github/dakusui/symfonion/song/Groove::resolve → KILLED

75

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[method:pickupNotation_noteOnAtCorrectTick()]
negated conditional → KILLED

77

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
changed conditional boundary → KILLED

2.2
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
negated conditional → KILLED

78

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B06()]
changed conditional boundary → KILLED

2.2
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B02()]
negated conditional → KILLED

83

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A05()]
Replaced long addition with subtraction → KILLED

84

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A05()]
Changed increment from 1 to -1 → KILLED

88

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
negated conditional → KILLED

89

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A05()]
changed conditional boundary → KILLED

2.2
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B01()]
negated conditional → KILLED

93

1.1
Location : computeUnit
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

2.2
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B06()]
negated conditional → KILLED

94

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#13]
Replaced integer subtraction with addition → KILLED

95

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B02()]
Replaced double multiplication with division → KILLED

2.2
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B02()]
Replaced double addition with subtraction → KILLED

97

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B06()]
Replaced long addition with subtraction → KILLED

2.2
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_B06()]
Replaced double multiplication with division → KILLED

99

1.1
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[method:pickupNotation_noteOnAtCorrectTick()]
Replaced double multiplication with division → KILLED

2.2
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[method:pickupNotation_noteOnAtCorrectTick()]
Replaced long subtraction with addition → KILLED

3.3
Location : computeUnit
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
replaced return value with null for com/github/dakusui/symfonion/song/Groove::computeUnit → KILLED

105

1.1
Location : foldPosition
Killed by : none
negated conditional → TIMED_OUT

107

1.1
Location : foldPosition
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
replaced return value with null for com/github/dakusui/symfonion/song/Groove::foldPosition → KILLED

142

1.1
Location : createGroove
Killed by : com.github.dakusui.symfonion.tests.InvalidJsonErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.InvalidJsonErrorTest]/[method:missingSection_sequence()]
negated conditional → KILLED

147

1.1
Location : createGroove
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingNoteMap()]
replaced return value with null for com/github/dakusui/symfonion/song/Groove::createGroove → KILLED

151

1.1
Location : defaultGrooveOf
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with null for com/github/dakusui/symfonion/song/Groove::defaultGrooveOf → KILLED

155

1.1
Location : resolveRelativePositionInStroke
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with null for com/github/dakusui/symfonion/song/Groove::resolveRelativePositionInStroke → KILLED

180

1.1
Location : add
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
replaced return value with null for com/github/dakusui/symfonion/song/Groove$Builder::add → KILLED

189

1.1
Location : build
Killed by : com.github.dakusui.symfonion.tests.song.GrooveTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.song.GrooveTest]/[method:test_A01()]
replaced return value with null for com/github/dakusui/symfonion/song/Groove$Builder::build → KILLED

Active mutators

Tests examined


Report generated by PIT 1.19.1