Song.java

package com.github.dakusui.symfonion.song;

import com.github.dakusui.json.JsonException;
import com.github.dakusui.json.JsonUtils;
import com.github.dakusui.logias.Logias;
import com.github.dakusui.logias.lisp.Context;
import com.github.dakusui.symfonion.exceptions.ExceptionThrower;
import com.github.dakusui.symfonion.exceptions.SymfonionException;
import com.github.dakusui.symfonion.utils.Utils;
import com.github.dakusui.valid8j.Requires;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import java.util.*;

import static com.github.dakusui.symfonion.exceptions.ExceptionThrower.*;
import static com.github.dakusui.symfonion.exceptions.ExceptionThrower.ContextKey.JSON_ELEMENT_ROOT;
import static com.github.dakusui.symfonion.exceptions.SymfonionTypeMismatchException.ARRAY;
import static com.github.dakusui.symfonion.exceptions.SymfonionTypeMismatchException.OBJECT;
import static com.github.dakusui.valid8j.Requires.requireNonNull;

public class Song {

  public static class Builder {
    private final Context logiasContext;
    private final JsonObject json;

    public Builder(Context logiasContext, JsonObject jsonObject) {
      this.logiasContext = requireNonNull(logiasContext);
      this.json = requireNonNull(jsonObject);
    }

    private static Context loadMidiDeviceProfile(JsonObject json, Context logiasContext) throws SymfonionException, JsonException {
      JsonElement tmp = JsonUtils.asJsonObjectWithDefault(json, new JsonObject(), Keyword.$settings);
      if (!tmp.isJsonObject()) {
        throw typeMismatchException(tmp, OBJECT);
      }
      String profileName = JsonUtils.asStringWithDefault(tmp.getAsJsonObject(), "", Keyword.$mididevice);
      Logias logias = new Logias(logiasContext);
      if (!"".equals(profileName)) {
        JsonObject deviceDef = JsonUtils.toJson(Utils.loadResource(profileName + ".json")).getAsJsonObject();
        Iterator<String> i = JsonUtils.keyIterator(deviceDef);
        while (i.hasNext()) {
          String k = i.next();
          JsonElement v = deviceDef.get(k);
          logiasContext.bind(k, logias.run(logias.buildSexp(v)));
        }
      }
      return logiasContext;
    }

    private static List<Bar> initSequence(JsonObject json, Map<String, Groove> grooves, Map<String, Pattern> patterns) throws SymfonionException, JsonException {
      List<Bar> bars = new LinkedList<>();
      JsonElement tmp = JsonUtils.asJsonElement(json, Keyword.$sequence);
      if (!tmp.isJsonArray()) {
        throw typeMismatchException(tmp, ARRAY);
      }
      JsonArray seqJson = tmp.getAsJsonArray();
      int len = seqJson.getAsJsonArray().size();
      for (int i = 0; i < len; i++) {
        JsonElement barJson = seqJson.get(i);
        if (!barJson.isJsonObject()) {
          throw typeMismatchException(seqJson, OBJECT);
        }
        Bar bar = new Bar(barJson.getAsJsonObject(), json, grooves, patterns);
        bars.add(bar);
      }
      return bars;
    }

    private static Map<String, NoteMap> initNoteMaps(JsonObject json) throws SymfonionException, JsonException {
      Map<String, NoteMap> noteMaps = new HashMap<>();
      final JsonObject noteMapsJSON = JsonUtils.asJsonObjectWithDefault(json, new JsonObject(), Keyword.$notemaps);

      Iterator<String> i = JsonUtils.keyIterator(noteMapsJSON);
      noteMaps.put(Keyword.$normal.toString(), NoteMap.defaultNoteMap);
      noteMaps.put(Keyword.$percussion.toString(), NoteMap.defaultPercussionMap);
      while (i.hasNext()) {
        String name = i.next();
        NoteMap cur = new NoteMap(JsonUtils.asJsonObject(noteMapsJSON, name));
        noteMaps.put(name, cur);
      }
      return noteMaps;
    }

    private static Map<String, Pattern> initPatterns(JsonObject json, Map<String, NoteMap> noteMaps) throws SymfonionException, JsonException {
      Map<String, Pattern> patterns = new HashMap<>();
      JsonObject patternsJSON = JsonUtils.asJsonObjectWithDefault(json, new JsonObject(), Keyword.$patterns);

      Iterator<String> i = JsonUtils.keyIterator(patternsJSON);
      try (ExceptionThrower.Context ignored = context($(JSON_ELEMENT_ROOT, json))) {
        while (i.hasNext()) {
          String name = i.next();
          Pattern cur = Pattern.createPattern(JsonUtils.asJsonObject(patternsJSON, name), noteMaps);
          patterns.put(name, cur);
        }
      }
      return patterns;
    }

    private static Map<String, Part> initParts(JsonObject json) throws SymfonionException, JsonException {
      Map<String, Part> parts = new HashMap<>();
      if (JsonUtils.hasPath(json, Keyword.$parts)) {
        JsonObject instrumentsJSON = JsonUtils.asJsonObject(json, Keyword.$parts);
        Iterator<String> i = JsonUtils.keyIterator(instrumentsJSON);
        while (i.hasNext()) {
          String name = i.next();
          Part cur = new Part(name, JsonUtils.asJsonObject(instrumentsJSON, name));
          parts.put(name, cur);
        }
      }
      return parts;
    }

    private static Map<String, Groove> initGrooves(JsonObject json) throws SymfonionException, JsonException {
      Map<String, Groove> grooves = new HashMap<>();
      if (JsonUtils.hasPath(json, Keyword.$grooves)) {
        JsonObject groovesJSON = JsonUtils.asJsonObject(json, Keyword.$grooves);

        Iterator<String> i = JsonUtils.keyIterator(groovesJSON);
        while (i.hasNext()) {
          String name = i.next();
          Groove cur = Groove.createGroove(JsonUtils.asJsonArray(groovesJSON, name));
          grooves.put(name, cur);
        }
      }
      return grooves;
    }

    public Song build() throws JsonException, SymfonionException {
      try (ExceptionThrower.Context ignored = context($(JSON_ELEMENT_ROOT, json))) {
        Map<String, NoteMap> noteMaps = initNoteMaps(json);
        Map<String, Groove> grooves = initGrooves(json);
        Map<String, Pattern> patterns = initPatterns(json, noteMaps);
        return new Song(
            loadMidiDeviceProfile(json, logiasContext),
            initParts(this.json),
            patterns,
            noteMaps,
            grooves,
            initSequence(json, grooves, patterns)
        );
      }
    }
  }

  private final Context logiasContext;
  private final Map<String, Part> parts;
  private final Map<String, Pattern> patterns;
  private final Map<String, NoteMap> noteMaps;
  private final Map<String, Groove> grooves;
  private final List<Bar> bars;


  public Song(Context logiasContext,
              Map<String, Part> parts,
              Map<String, Pattern> patterns,
              Map<String, NoteMap> noteMaps,
              Map<String, Groove> grooves,
              List<Bar> bars
  ) {
    this.logiasContext = logiasContext;
    this.parts = Requires.requireNonNull(parts);
    this.patterns = Requires.requireNonNull(patterns);
    this.noteMaps = requireNonNull(noteMaps);
    this.grooves = requireNonNull(grooves);
    this.bars = requireNonNull(bars);
  }


  public Pattern pattern(String patternName) {
    return this.patterns.get(patternName);
  }

  public NoteMap noteMap(String noteMapName) {
    return this.noteMaps.get(noteMapName);
  }

  public List<Bar> bars() {
    return Collections.unmodifiableList(this.bars);
  }

  public Set<String> partNames() {
    return Collections.unmodifiableSet(this.parts.keySet());
  }

  public Part part(String name) {
    return this.parts.get(name);
  }

  public Context getLogiasContext() {
    return this.logiasContext;
  }

  public Groove groove(String grooveName) {
    return this.grooves.get(grooveName);
  }
}