| 1 | package com.github.dakusui.symfonion.song; | |
| 2 | ||
| 3 | import com.github.dakusui.symfonion.compat.exceptions.FractionFormatException; | |
| 4 | import com.github.dakusui.symfonion.compat.exceptions.SymfonionException; | |
| 5 | import com.github.dakusui.symfonion.compat.json.CompatJsonException; | |
| 6 | import com.github.dakusui.symfonion.compat.json.CompatJsonUtils; | |
| 7 | import com.github.dakusui.symfonion.utils.Fraction; | |
| 8 | import com.google.gson.JsonArray; | |
| 9 | import com.google.gson.JsonElement; | |
| 10 | import com.google.gson.JsonObject; | |
| 11 | ||
| 12 | import java.util.*; | |
| 13 | import java.util.function.Predicate; | |
| 14 | import java.util.stream.StreamSupport; | |
| 15 | ||
| 16 | import static com.github.dakusui.symfonion.compat.exceptions.CompatExceptionThrower.*; | |
| 17 | import static com.github.dakusui.symfonion.compat.exceptions.SymfonionIllegalFormatException.FRACTION_EXAMPLE; | |
| 18 | import static com.github.dakusui.symfonion.compat.exceptions.SymfonionTypeMismatchException.OBJECT; | |
| 19 | import static com.github.dakusui.symfonion.compat.json.CompatJsonUtils.asJsonElement; | |
| 20 | import static com.github.dakusui.symfonion.compat.json.JsonUtils.findJsonArray; | |
| 21 | import static com.github.dakusui.symfonion.compat.json.JsonUtils.path; | |
| 22 | import static com.github.dakusui.symfonion.utils.Fraction.parseFraction; | |
| 23 | import static com.github.valid8j.classic.Requires.requireNonNull; | |
| 24 | import static java.util.Collections.*; | |
| 25 | ||
| 26 | /** | |
| 27 | * // @formatter:off | |
| 28 | * A class that models a "bar" in a musical score. | |
| 29 | * | |
| 30 | * // @formatter:on | |
| 31 | */ | |
| 32 | public class Bar { | |
| 33 | private final Map<String, NoteMap> noteMaps; | |
| 34 | private final Fraction beats; | |
| 35 | private final Map<String, List<Pattern>> patterns; | |
| 36 | private final Groove groove; | |
| 37 | private final List<String> labels; | |
| 38 | private final JsonObject barJsonObject; | |
| 39 | private final JsonArray partsJsonArray; | |
| 40 | ||
| 41 | ||
| 42 | /** | |
| 43 | * Creates a `Bar` object. | |
| 44 | * | |
| 45 | * // @formatter:off | |
| 46 | * `barJsonObject` stores content of the bar. | |
| 47 | * | |
| 48 | * [source, JSON] | |
| 49 | * .barJsonObject | |
| 50 | * ---- | |
| 51 | * { | |
| 52 | * "beats": "<beatsDefiningString>", | |
| 53 | * "parts": [ | |
| 54 | * { "name": "<partName>", "<inline pattern definition>" }, | |
| 55 | * ... | |
| 56 | * ], | |
| 57 | * "groove": "<grooveName>", | |
| 58 | * "$noteMap": "<noteMapName>", | |
| 59 | * "labels": ["<label1>", "<label2>", "..."] | |
| 60 | * } | |
| 61 | * ---- | |
| 62 | * | |
| 63 | * `root` is used only for composing messages on errors. | |
| 64 | * // @formatter:on | |
| 65 | * | |
| 66 | * @param barJsonObject A JSON object from which a bar is created. | |
| 67 | * @param grooves A map that defines a groove on which this bar should be played. | |
| 68 | * @param noteMaps A note map with which this bar should be played. | |
| 69 | * @param partFilter A predicate that filters parts to be played in this bar. | |
| 70 | * @throws SymfonionException Data processing was failed. | |
| 71 | * @throws CompatJsonException Invalid JSON is given. | |
| 72 | */ | |
| 73 | public Bar(JsonObject barJsonObject, | |
| 74 | Map<String, Groove> grooves, | |
| 75 | Map<String, NoteMap> noteMaps, | |
| 76 | Predicate<String> partFilter) throws SymfonionException, | |
| 77 | CompatJsonException { | |
| 78 | this.noteMaps = requireNonNull(noteMaps); | |
| 79 | this.barJsonObject = requireNonNull(barJsonObject); | |
| 80 | this.beats = extractBeatsFractionFrom(barJsonObject); | |
| 81 | this.groove = resolveGrooveForBar(barJsonObject, this.beats, grooves); | |
| 82 | this.labels = resolveLabelsForBar(barJsonObject); | |
| 83 | this.partsJsonArray = getPartsInBarAsJsonArray(barJsonObject); | |
| 84 | this.patterns = composePatternsMap(this, partFilter, this.partsJsonArray); | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Returns labels attached to this bar object. | |
| 89 | * | |
| 90 | * @return A list of labels. | |
| 91 | */ | |
| 92 | public List<String> labels() { | |
| 93 |
1
1. labels : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::labels → SURVIVED |
return this.labels; |
| 94 | } | |
| 95 | ||
| 96 | /** | |
| 97 | * Returns a set of part names. | |
| 98 | * | |
| 99 | * @return A set of part names. | |
| 100 | */ | |
| 101 | public Set<String> partNames() { | |
| 102 |
1
1. partNames : replaced return value with Collections.emptySet for com/github/dakusui/symfonion/song/Bar::partNames → KILLED |
return unmodifiableSet(this.patterns.keySet()); |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Returns the list of stacked `Pattern` objects for the given `partName`. | |
| 107 | * Multiple entries with the same `$name` in `$parts` are played simultaneously. | |
| 108 | * | |
| 109 | * @param partName A part name for which the patterns should be returned. | |
| 110 | * @return A list of `Pattern` objects (stacked). | |
| 111 | */ | |
| 112 | public List<Pattern> patternsForPart(String partName) { | |
| 113 |
1
1. patternsForPart : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::patternsForPart → KILLED |
return this.patterns.get(partName); |
| 114 | } | |
| 115 | ||
| 116 | /** | |
| 117 | * Returns `Beats` that determine the duration of this object. | |
| 118 | * | |
| 119 | * @return Beats | |
| 120 | */ | |
| 121 | public Fraction beats() { | |
| 122 |
1
1. beats : replaced return value with null for com/github/dakusui/symfonion/song/Bar::beats → KILLED |
return this.beats; |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Returns a `Groove` object that determines "groove" of this bar | |
| 127 | * | |
| 128 | * @return A `Groove` object. | |
| 129 | * @see Groove | |
| 130 | */ | |
| 131 | public Groove groove() { | |
| 132 |
1
1. groove : replaced return value with null for com/github/dakusui/symfonion/song/Bar::groove → KILLED |
return this.groove; |
| 133 | } | |
| 134 | ||
| 135 | /** | |
| 136 | * Find up a `JsonElement` in `$parts` array that has `$name` matching `partName`. | |
| 137 | * Returns the first matching element, or the `$parts` array itself if not found. | |
| 138 | * | |
| 139 | * @param partName partName used for searching. | |
| 140 | * @return A matching JSON element, or the array if not found. | |
| 141 | */ | |
| 142 | public JsonElement lookUpJsonNode(String partName) { | |
| 143 | for (JsonElement elem : this.partsJsonArray) { | |
| 144 |
1
1. lookUpJsonNode : negated conditional → KILLED |
if (elem.isJsonObject()) { |
| 145 | JsonObject obj = elem.getAsJsonObject(); | |
| 146 |
2
1. lookUpJsonNode : negated conditional → KILLED 2. lookUpJsonNode : negated conditional → KILLED |
if (obj.has(Keyword.name.name()) && partName.equals(CompatJsonUtils.asString(obj, Keyword.name))) { |
| 147 |
1
1. lookUpJsonNode : replaced return value with null for com/github/dakusui/symfonion/song/Bar::lookUpJsonNode → KILLED |
return elem; |
| 148 | } | |
| 149 | } | |
| 150 | } | |
| 151 |
1
1. lookUpJsonNode : replaced return value with null for com/github/dakusui/symfonion/song/Bar::lookUpJsonNode → NO_COVERAGE |
return this.partsJsonArray; |
| 152 | } | |
| 153 | ||
| 154 | /** | |
| 155 | * // @formatter:off | |
| 156 | * Composes a map from part name to list of stacked patterns. | |
| 157 | * | |
| 158 | * [source, JSON] | |
| 159 | * .partsJsonArrayInBar | |
| 160 | * ---- | |
| 161 | * [ | |
| 162 | * { "name": "<partName>", "<inline pattern definition>" }, | |
| 163 | * { "name": "<partName>", "<inline pattern definition>" } | |
| 164 | * ] | |
| 165 | * ---- | |
| 166 | * // @formatter:on | |
| 167 | * | |
| 168 | * @param bar A bar object from which the pattern map is created. | |
| 169 | * @param partFilter A predicate that filters a part to be rendered. | |
| 170 | * @param partsJsonArrayInBar A JSON array associated with `$parts` key in the bar JSON object. | |
| 171 | * @return A map from part name to a list of patterns (stacked). | |
| 172 | */ | |
| 173 | private static Map<String, List<Pattern>> composePatternsMap(Bar bar, | |
| 174 | Predicate<String> partFilter, | |
| 175 | JsonArray partsJsonArrayInBar) { | |
| 176 | Map<String, List<Pattern>> ret = new LinkedHashMap<>(); | |
| 177 | for (JsonElement elem : partsJsonArrayInBar) { | |
| 178 |
1
1. composePatternsMap : negated conditional → KILLED |
if (!elem.isJsonObject()) { |
| 179 | throw typeMismatchException(elem, OBJECT); | |
| 180 | } | |
| 181 | JsonObject partObj = elem.getAsJsonObject(); | |
| 182 | String partName = CompatJsonUtils.asString(partObj, Keyword.name); | |
| 183 |
1
1. composePatternsMap : negated conditional → KILLED |
if (!partFilter.test(partName)) |
| 184 | continue; | |
| 185 |
1
1. lambda$composePatternsMap$0 : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::lambda$composePatternsMap$0 → KILLED |
ret.computeIfAbsent(partName, k -> new ArrayList<>()) |
| 186 | .add(Pattern.createPattern(partObj, bar.noteMaps)); | |
| 187 | } | |
| 188 |
1
1. composePatternsMap : replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/Bar::composePatternsMap → KILLED |
return ret; |
| 189 | } | |
| 190 | ||
| 191 | static List<String> resolveLabelsForBar(JsonObject barJsonObject) { | |
| 192 |
1
1. resolveLabelsForBar : negated conditional → KILLED |
if (CompatJsonUtils.hasPath(barJsonObject, Keyword.labels)) { |
| 193 |
1
1. resolveLabelsForBar : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::resolveLabelsForBar → NO_COVERAGE |
return StreamSupport.stream(CompatJsonUtils.asJsonArray(barJsonObject, Keyword.labels).spliterator(), false) |
| 194 |
1
1. lambda$resolveLabelsForBar$1 : replaced return value with "" for com/github/dakusui/symfonion/song/Bar::lambda$resolveLabelsForBar$1 → NO_COVERAGE |
.map(CompatJsonUtils::asString) |
| 195 | .toList(); | |
| 196 | } | |
| 197 | return emptyList(); | |
| 198 | } | |
| 199 | ||
| 200 | private static Groove resolveGrooveForBar(JsonObject barJsonObject, Fraction barLength, Map<String, Groove> grooves) { | |
| 201 | Groove g = Groove.defaultGrooveOf(barLength); | |
| 202 |
1
1. resolveGrooveForBar : negated conditional → KILLED |
if (CompatJsonUtils.hasPath(barJsonObject, Keyword.groove)) { |
| 203 | String grooveName = CompatJsonUtils.asString(barJsonObject, Keyword.groove.name()); | |
| 204 | g = grooves.get(grooveName); | |
| 205 |
1
1. resolveGrooveForBar : negated conditional → KILLED |
if (g == null) { |
| 206 | throw grooveNotDefinedException(asJsonElement(barJsonObject, Keyword.groove), grooveName); | |
| 207 | } | |
| 208 | } | |
| 209 |
1
1. resolveGrooveForBar : replaced return value with null for com/github/dakusui/symfonion/song/Bar::resolveGrooveForBar → KILLED |
return g; |
| 210 | } | |
| 211 | ||
| 212 | static Fraction extractBeatsFractionFrom(JsonObject barJsonObject) { | |
| 213 | Fraction beats; | |
| 214 | try { | |
| 215 | beats = parseFraction(CompatJsonUtils.asString(barJsonObject, Keyword.beats)); | |
| 216 | } catch (FractionFormatException e) { | |
| 217 | throw illegalFormatException(asJsonElement(barJsonObject, Keyword.beats), FRACTION_EXAMPLE); | |
| 218 | } | |
| 219 |
1
1. extractBeatsFractionFrom : negated conditional → SURVIVED |
beats = beats == null ? Fraction.ONE : beats; |
| 220 |
1
1. extractBeatsFractionFrom : replaced return value with null for com/github/dakusui/symfonion/song/Bar::extractBeatsFractionFrom → KILLED |
return beats; |
| 221 | } | |
| 222 | ||
| 223 | /** | |
| 224 | * @param barJsonObject A JSON object that models a bar in a musical score. | |
| 225 | * @return JSON array under `$parts` element | |
| 226 | */ | |
| 227 | private static JsonArray getPartsInBarAsJsonArray(JsonObject barJsonObject) { | |
| 228 |
2
1. lambda$getPartsInBarAsJsonArray$2 : replaced return value with null for com/github/dakusui/symfonion/song/Bar::lambda$getPartsInBarAsJsonArray$2 → NO_COVERAGE 2. getPartsInBarAsJsonArray : replaced return value with null for com/github/dakusui/symfonion/song/Bar::getPartsInBarAsJsonArray → KILLED |
return findJsonArray(barJsonObject, path(Keyword.parts)).orElseThrow(() -> requiredElementMissingException(barJsonObject, |
| 229 | Keyword.parts)); | |
| 230 | } | |
| 231 | ||
| 232 | } | |
Mutations | ||
| 93 |
1.1 |
|
| 102 |
1.1 |
|
| 113 |
1.1 |
|
| 122 |
1.1 |
|
| 132 |
1.1 |
|
| 144 |
1.1 |
|
| 146 |
1.1 2.2 |
|
| 147 |
1.1 |
|
| 151 |
1.1 |
|
| 178 |
1.1 |
|
| 183 |
1.1 |
|
| 185 |
1.1 |
|
| 188 |
1.1 |
|
| 192 |
1.1 |
|
| 193 |
1.1 |
|
| 194 |
1.1 |
|
| 202 |
1.1 |
|
| 205 |
1.1 |
|
| 209 |
1.1 |
|
| 219 |
1.1 |
|
| 220 |
1.1 |
|
| 228 |
1.1 2.2 |