// SPDX-License-Identifier: Unlicense

#include "song.h"

#include "base64.h"
#include "file.h"
#include "global.h"
#include "ini.h"
#include "json.h"
#include "psarc.h"
#include "sfx.h"
#include "shred.h"
#include "sng.h"
#include "string_.h"
#include "tones.h"

#include <string.h>

#undef max
#undef min
#include <algorithm>

Song::Info Song::loadSongIni(const std::map<std::u8string, std::map<std::u8string, std::u8string>>& iniContent, const char8_t* filename)
{
  DEBUG_PRINT("%s\n", reinterpret_cast<const char*>(filename));

  ASSERT(iniContent.contains(u8"Song"));

  Song::Info songInfo;

  const std::map<std::u8string, std::u8string>& song = iniContent.at(u8"Song");

  if (song.contains(u8"Album"))
    songInfo.albumName = song.at(u8"Album");
  if (song.contains(u8"Artist"))
    songInfo.artistName = song.at(u8"Artist");
  songInfo.songLength = timeNS_From_String(song.at(u8"Length").c_str());
  if (song.contains(u8"SpotifyTrackId"))
  {
    ASSERT(song.at(u8"SpotifyTrackId").size() == 22);
    strcpy(songInfo.spotifyTrackId, reinterpret_cast<const char*>(song.at(u8"SpotifyTrackId").c_str()));
  }
  songInfo.songName = song.at(u8"Song");
  if (song.contains(u8"Year"))
    songInfo.songYear = atoi(reinterpret_cast<const char*>(song.at(u8"Year").c_str()));
  if (song.contains(u8"Track"))
    songInfo.track = atoi(reinterpret_cast<const char*>(song.at(u8"Track").c_str()));

  if (song.contains(u8"DelayBegin"))
    songInfo.shredDelayBegin = timeNS_From_String(song.at(u8"DelayBegin").c_str());
  if (song.contains(u8"DelayEnd"))
    songInfo.shredDelayEnd = timeNS_From_String(song.at(u8"DelayEnd").c_str());

  for (const auto& [arrangement, ini] : iniContent)
  {
    if (arrangement == u8"Song")
      continue;

    Arrangement::Info arrangementInfo;
    arrangementInfo.arrangementName = reinterpret_cast<const char8_t*>(arrangement.c_str());
    arrangementInfo.persistentId = std::u8string(filename) + u8'|' + arrangement;
    arrangementInfo.isBass = arrangementInfo.arrangementName.starts_with(u8"Bass");

    if (ini.contains(u8"Tuning"))
    {
      const std::vector<std::u8string> tuning = String::split(ini.at(u8"Tuning"), u8',');
      {
        i32 i = 0;
        for (; i < tuning.size(); ++i)
          arrangementInfo.tuning.string[i] = i8(atoi2(tuning[i].c_str()));
        for (; i < Const::highwayInstrumentGuitarStringCount; ++i)
          arrangementInfo.tuning.string[i] = -128;
      }
    }

    if (ini.contains(u8"BassPick"))
    {
      arrangementInfo.bassPick = bool(atoi2(ini.at(u8"BassPick").c_str()));
    }
    if (ini.contains(u8"CentOffset"))
    {
      arrangementInfo.centOffset = f32(atof2(ini.at(u8"CentOffset").c_str()));
    }
    if (ini.contains(u8"CapoFret"))
    {
      arrangementInfo.capoFret = i8(atoi2(ini.at(u8"CapoFret").c_str()));
    }

    songInfo.arrangementInfos.push_back(arrangementInfo);
  }

  return songInfo;
}

static void addMissingVocalLineBreaks(std::vector<Song::Vocal>& vocals)
{
  TimeNS lastTimeNS = vocals[0].timeNS;
  for (u64 i = 1; i < vocals.size(); ++i)
  {
    const TimeNS pause = vocals[i].timeNS - lastTimeNS;
    if (pause >= 1500_ms)
      vocals[i - 1].lyric += '+';
    lastTimeNS = vocals[i].timeNS;
  }
}

#ifdef SHR3D_SHRED
Song::Info Song::ShredParser::loadSongInfo(const char8_t* shredFilepath, const Shred::Info& shredInfo)
{
  DEBUG_PRINT("%s\n", reinterpret_cast<const char*>(shredFilepath));

  Song::Info songInfo = Song::loadSongIni(shredInfo.songIni, File::filename(shredFilepath));
  songInfo.loadState = LoadState::complete;

  ASSERT(songInfo.arrangementInfos.size() != 0);

  return songInfo;
}

static i8 getChordNoteIndex(const std::vector<std::u8string>& valueSplit, i8 string)
{
  i8 chordNoteIndex = 0;
  i8 minString = -1;

  for (const std::u8string& value : valueSplit)
  {
    if (u8'0' <= value[0] && value[0] <= u8'9')
    {
      i8 stringC = value[0] - u8'0';
      if (stringC < string)
      {
        if (minString < stringC)
        {
          ++chordNoteIndex;
          minString = stringC;
        }
      }
    }
  }

  ASSERT(chordNoteIndex >= 0);
  ASSERT(chordNoteIndex <= 5);

  return chordNoteIndex;
}

std::unordered_map<ArrangementIndex, Song::Track> Song::ShredParser::loadTracks(const Shred::Info& shredInfo)
{
  std::unordered_map<ArrangementIndex, Song::Track> songTracks(shredInfo.arrangements.size());

  ArrangementIndex arrangementIndex = 0;
  for (auto& [arranementName, ini] : shredInfo.arrangements)
  {
    Track track;

    if (ini.contains(u8"Beats"))
    {
      for (const auto& [key, value] : ini.at(u8"Beats"))
      {
        Beat beat
        {
          .timeNS = timeNS_From_String(key),
          .isPrimary = (value == u8"1") ? true : false
        };
        track.beats.push_back(beat);
      }
    }

    if (ini.contains(u8"ChordTemplates"))
    {
      for (const auto& [key, value] : ini.at(u8"ChordTemplates"))
      {
        ChordTemplate_deprecated chordTemplate{};

        const std::vector<std::u8string> valueSplit = String::split(value, u8',');
        if (valueSplit.size() >= 2)
        {
          for (const std::u8string& keyColonValue : valueSplit)
          {
            const std::vector<std::u8string> keyValue = String::split(keyColonValue, u8':');
            if (keyValue.size() == 2)
            {
              if (keyValue[0] == u8"name")
                chordTemplate.name = keyValue[1];
              else if (keyValue[0] == u8"finger0")
                chordTemplate.finger[5] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"finger1")
                chordTemplate.finger[4] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"finger2")
                chordTemplate.finger[3] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"finger3")
                chordTemplate.finger[2] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"finger4")
                chordTemplate.finger[1] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"finger5")
                chordTemplate.finger[0] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"fret0")
                chordTemplate.fret[5] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"fret1")
                chordTemplate.fret[4] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"fret2")
                chordTemplate.fret[3] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"fret3")
                chordTemplate.fret[2] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"fret4")
                chordTemplate.fret[1] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"fret5")
                chordTemplate.fret[0] = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
            }
          }
        }

        track.chordTemplates_deprecated.push_back(chordTemplate);
      }
    }

    if (ini.contains(u8"LeveledSections"))
    {
      for (const auto& [key, value] : ini.at(u8"LeveledSections"))
      {
        const std::vector<std::u8string> keySplit = String::split(key, u8'-');
        ASSERT(keySplit.size() == 2);

        LeveledSection leveledSection{
          .startTimeNS = timeNS_From_String(keySplit[0]),
          .endTimeNS = timeNS_From_String(keySplit[1]),
          .maxLevel = atoi2(value.c_str())
        };

        track.leveledSections.push_back(leveledSection);
      }
    }

    //if (ini.contains(u8"PhraseIterations"))
    //{
    //  for (const auto& [key, value] : ini.at(u8"PhraseIterations"))
    //  {
    //    PhraseIteration phraseIteration{
    //      .timeNS = timeNS_From_String(key)
    //    };

    //    const std::vector<std::u8string> keyValue = String::split(value, u8':');
    //    ASSERT(keyValue.size() == 2);

    //    if (keyValue[0] == u8"phraseId")
    //      phraseIteration.phraseId = atoi(reinterpret_cast<const char*>(keyValue[1].c_str()));

    //    track.phraseIterations.push_back(phraseIteration);
    //  }
    //}

    i32 i = 0;
  next:
    Level level;

    const std::u8string levelSection = u8"Level" + to_string(i);

    {
      const std::u8string levelAnchorsSection = levelSection + u8"Anchors";
      if (ini.contains(levelAnchorsSection))
      {
        for (const auto& [key, value] : ini.at(levelAnchorsSection))
        {
          Anchor anchor
          {
            .timeNS = timeNS_From_String(key),
            //.endTimeNS = timeNS_From_String(keySplit[1])
          };

          const std::vector<std::u8string> valueSplit = String::split(value, u8',');
          ASSERT(valueSplit.size() == 2);


          for (const std::u8string& keyColonValue : valueSplit)
          {
            const std::vector<std::u8string> keyValue = String::split(keyColonValue, u8':');
            ASSERT(keyValue.size() == 2);

            if (keyValue[0] == u8"fret")
              anchor.fret = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
            else if (keyValue[0] == u8"width")
              anchor.width = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
          }

          level.anchors.push_back(anchor);
        }
      }
    }

    {
      const std::u8string levelNotesSection = levelSection + u8"Notes";

      if (ini.contains(levelNotesSection))
      {
        for (const auto& [key, value] : ini.at(levelNotesSection))
        {
          Note note{
            .timeNS = timeNS_From_String(key)
          };

          const std::vector<std::u8string> valueSplit = String::split(value, u8',');
          ASSERT(valueSplit.size() >= 2);

          { // resize bendValues
            i32 bendValueSize = 0;
            for (const std::u8string& keyColonValue : valueSplit)
            {
              const std::vector<std::u8string> keyValue = String::split(keyColonValue, ':');
              if (keyValue[0].starts_with(u8"bend") && keyValue[0] != u8"bend")
              {
                const std::u8string numberStr = keyValue[0].substr(4, keyValue[0].size() - 8);
                i32 number = atoi(reinterpret_cast<const char*>(numberStr.c_str()));
                bendValueSize = max_(bendValueSize, number + 1);
              }
            }
            note.bendValues.resize(bendValueSize);
          }

          for (const std::u8string& keyColonValue : valueSplit)
          {
            const std::vector<std::u8string> keyValue = String::split(keyColonValue, u8':');
            if (keyValue.size() == 2)
            {
              if (keyValue[0] == u8"fret")
                note.fret = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"string")
                note.string = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"sustain")
              {
                note.sustainNS = timeNS_From_String(keyValue[1]);
              }
              else if (keyValue[0] == u8"finger")
                note.finger = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"slideUnpitchTo")
                note.slideUnpitchTo = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"slideTo")
                note.slideTo = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"bend")
                note.maxBend = f32(atof(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0] == u8"vibrato")
                note.vibrato = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
              else if (keyValue[0].starts_with(u8"bend"))
              {
                //i64 numberEnd = keyValue[0].size() - 8;
                const std::u8string indexStr = keyValue[0].substr(4, keyValue[0].size() - 8);
                i32 index = atoi(reinterpret_cast<const char*>(indexStr.c_str()));

                if (keyValue[0].ends_with(u8"time"))
                {
                  note.bendValues[index].timeNS = timeNS_From_String(keyValue[1]);
                  //note.bendValues[index].time = TimeNS_To_Seconds(note.bendValues[index].timeNS);
                }
                else if (keyValue[0].ends_with(u8"step"))
                  note.bendValues[index].step = f32(atof(reinterpret_cast<const char*>(keyValue[1].c_str())));
                else
                  ASSERT(false);
              }
            }
            else if (keyValue.size() == 1)
            {
              if (keyValue[0] == u8"palmMute")
                note.palmMute = true;
              else if (keyValue[0] == u8"accent")
                note.accent = true;
              else if (keyValue[0] == u8"linkNext")
                note.linkNext_ = true;
              else if (keyValue[0] == u8"hammerOn")
                note.hammerOn = true;
              else if (keyValue[0] == u8"harmonic")
                note.harmonic = true;
              else if (keyValue[0] == u8"hopo")
                note.hopo = true;
              else if (keyValue[0] == u8"ignore")
                note.ignore = true;
              else if (keyValue[0] == u8"mute")
                note.frethandMute = true;
              else if (keyValue[0] == u8"palmMute")
                note.palmMute = true;
              else if (keyValue[0] == u8"pluck")
                note.pluck = true;
              else if (keyValue[0] == u8"pullOff")
                note.pullOff = true;
              else if (keyValue[0] == u8"slap")
                note.slap = true;
              else if (keyValue[0] == u8"tremolo")
                note.tremolo = true;
              else if (keyValue[0] == u8"pinchHarmonic")
                note.pinchHarmonic = true;
              else if (keyValue[0] == u8"pickDirection")
                note.pickDirection = true;
              else if (keyValue[0] == u8"rightHand")
                note.rightHand = true;
              else if (keyValue[0] == u8"tap")
                note.tap = true;
              else if (keyValue[0] == u8"rotation")
                note.rotation = true;
              else
                ASSERT(false);
            }
            else
            {
              ASSERT(false);
            }

          }

          level.notes.push_back(note);
        }
      }
    }

    std::vector<std::vector<std::u8string>> chrodNotes2;
    {
      if (ini.contains(u8"ChordNotes"))
      {
        for (const auto& [key, value] : ini.at(u8"ChordNotes"))
        {
          const std::vector<std::u8string> valueSplit = String::split(value, u8',');
          if (valueSplit.size() >= 1)
          {
            std::vector<std::u8string> stringMods(6);

            for (const std::u8string& noteMod : valueSplit)
            {
              i32 modString = noteMod[0] - '0';
              std::u8string mod = noteMod.substr(1);

              if (stringMods[modString].size() == 0)
                stringMods[modString] += mod;
              else
                stringMods[modString] += u8',' + mod;
            }
            chrodNotes2.push_back(stringMods);
          }
          else
            chrodNotes2.push_back({});
        }
      }
    }

    {
      const std::u8string levelChordsSection = levelSection + u8"Chords";

      if (ini.contains(levelChordsSection))
      {
        for (const auto& [key, value] : ini.at(levelChordsSection))
        {
          //Note note;
          Chord chord{
            .timeNS = timeNS_From_String(key),
            .strumUp = true
          };
          const std::vector<std::u8string> valueSplit = String::split(value, u8',');
          ASSERT(valueSplit.size() >= 2);

          i32 chordNoteCount = 0;
          for (const std::u8string& value2 : valueSplit)
          {
            if (value2.substr(1).starts_with(u8"fret:"))
              ++chordNoteCount;
          }
          chord.chordNotes.resize(chordNoteCount);
          for (Song::Note& note : chord.chordNotes)
          {
            note.timeNS = chord.timeNS;
            //note.time = chord.time;
            note.pickDirection = false;
          }

          for (const std::u8string& keyColonValue : valueSplit)
          {
            const std::vector<std::u8string> keyValue = String::split(keyColonValue, u8':');

            ASSERT(keyValue.size() >= 1);
            ASSERT(keyValue.size() <= 2);

            if (keyValue.size() == 2)
            {
              if (keyValue[0] == u8"sustain")
              {
                for (Song::Note& note : chord.chordNotes)
                {
                  note.sustainNS = timeNS_From_String(keyValue[1]);
                }
              }
              else if (keyValue[0] == u8"chordId")
              {
                chord.chordId_deprecated = atoi(reinterpret_cast<const char*>(keyValue[1].c_str()));

                //const Song::ChordTemplate& chordTemplate = track.chordTemplates[chord.chordId];

                //for (i32 k = 0; k < 6; ++k)
                //{
                //  if (chordTemplate.fret[k] >= 0)
                //  {
                //    Song::Note note{};
                //    note.linkNext = /*sngInfo.arrangements[i].notes[j].linkNext*/false;
                //    note.fret = chordTemplate.fret[k];
                //    note.hopo = /*sngInfo.arrangements[i].notes[j].hopo*/false;
                //    note.string = k;
                //    chord.chordNotes.push_back(note);
                //  }
                //}
              }
              else if (keyValue[0] == u8"chordNoteId")
              {
                //for (i32 k = 0; k < chord.chordNotes.size(); ++k)
                //{
                //  if (chrodNotes2[atoi(keyValue[1].c_str())].size() >= 1)
                //  {
                //    const i32 string = chord.chordNotes[k].string;
                //    const std::string& modString = chrodNotes2[atoi(keyValue[1].c_str())][string];

                //    const std::vector<std::string> valueSplit = String::split(modString, ',');

                //    for (const std::string& noteMod : valueSplit)
                //    {
                //      if (noteMod == "palmMute")
                //        chord.chordNotes[k].palmMute = true;
                //      else if (noteMod == "frethandMute")
                //        chord.chordNotes[k].frethandMute = true;
                //      else if (noteMod == "hammerOn")
                //        chord.chordNotes[k].hammerOn = true;
                //      else if (noteMod == "pullOff")
                //        chord.chordNotes[k].pullOff = true;
                //      else if (noteMod == "harmonic")
                //        chord.chordNotes[k].harmonic = true;
                //      else if (noteMod == "pinchHarmonic")
                //        chord.chordNotes[k].pinchHarmonic = true;
                //      else if (noteMod == "tremolo")
                //        chord.chordNotes[k].tremolo = true;
                //      else if (noteMod == "accent")
                //        chord.chordNotes[k].accent = true;
                //      else if (noteMod == "linkNext")
                //        chord.chordNotes[k].linkNext = true;
                //      //else if (noteMod == "highDensity")
                //      //  chord.chordNotes[k].highDensity = true;
                //      //else if (noteMod == "strumUp")
                //      //  chord.chordNotes[k].strumUp = true;
                //      else
                //        ASSERT(false);
                //    }
                //  }
                //}
              }
              else if (keyValue[0] == u8"name")
              {
                chord.name = keyValue[1];
              }
              else if (u8'0' <= keyValue[0][0] && keyValue[0][0] <= u8'9') // 
              {
                const i8 string = keyValue[0][0] - u8'0';
                const std::u8string key2 = keyValue[0].substr(1);

                const i8 k = getChordNoteIndex(valueSplit, string);

                chord.chordNotes[k].string = string;
                if (key2 == u8"fret")
                  chord.chordNotes[k].fret = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
                else if (key2 == u8"finger")
                  chord.chordNotes[k].finger = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
                else if (key2 == u8"slideTo")
                  chord.chordNotes[k].slideTo = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
                else if (key2 == u8"slideUnpitchTo")
                  chord.chordNotes[k].slideUnpitchTo = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
                else if (key2 == u8"vibrato")
                  chord.chordNotes[k].vibrato = i8(atoi(reinterpret_cast<const char*>(keyValue[1].c_str())));
                else
                  ASSERT(false);
              }
              else
                ASSERT(false);
            }
            else if (keyValue.size() == 1)
            {
              if (keyValue[0] == u8"linkNext")
                chord.linkNext_ = true;
              else if (keyValue[0] == u8"accent")
                chord.accent = true;
              else if (keyValue[0] == u8"fretHandMute")
                chord.fretHandMute = true;
              else if (keyValue[0] == u8"highDensity")
                chord.highDensity = true;
              else if (keyValue[0] == u8"ignore")
                chord.ignore = true;
              else if (keyValue[0] == u8"palmMute")
                chord.palmMute = true;
              else if (keyValue[0] == u8"strumUp")
                chord.strumUp = true;
              else if (u8'0' <= keyValue[0][0] && keyValue[0][0] <= u8'9')
              {
                const i8 string = keyValue[0][0] - u8'0';
                const std::u8string key2 = keyValue[0].substr(1);

                const i8 k = getChordNoteIndex(valueSplit, string);

                chord.chordNotes[k].string = string;

                if (key2 == u8"palmMute")
                  chord.chordNotes[k].palmMute = true;
                else if (key2 == u8"frethandMute")
                  chord.chordNotes[k].frethandMute = true;
                else if (key2 == u8"pullOff")
                  chord.chordNotes[k].pullOff = true;
                else if (key2 == u8"harmonic")
                  chord.chordNotes[k].harmonic = true;
                else if (key2 == u8"pinchHarmonic")
                  chord.chordNotes[k].pinchHarmonic = true;
                else if (key2 == u8"tremolo")
                  chord.chordNotes[k].tremolo = true;
                else if (key2 == u8"accent")
                  chord.chordNotes[k].accent = true;
                else if (key2 == u8"ignore")
                  chord.chordNotes[k].ignore = true;
                else if (key2 == u8"linkNext")
                  chord.chordNotes[k].linkNext_ = true;
              }
              else
              {
                ASSERT(false);
              }
            }
            else
            {
              ASSERT(false);
            }
          }

          //level.notes.push_back(note);
          level.chords.push_back(chord);
        }
      }
    }

    {
      const std::u8string levelArpeggiosSection = levelSection + u8"Arpeggios";

      if (ini.contains(levelArpeggiosSection))
      {
        for (const auto& [key, value] : ini.at(levelArpeggiosSection))
        {
          const std::vector<std::u8string> keySplit = String::split(key, u8'-');
          ASSERT(keySplit.size() == 2);

          Arpeggio arpeggio{
            .startTimeNS = timeNS_From_String(keySplit[0]),
            .endTimeNS = timeNS_From_String(keySplit[1]),
          };

          const std::vector<std::u8string> valueSplit = String::split(value, u8',');
          ASSERT(valueSplit.size() >= 1);

          //i32 arpeggioNoteCount = 0;
          //for (const std::u8string& value2 : valueSplit)
          //{
          //  if (value2.substr(1).starts_with(u8"fret:"))
          //    ++arpeggioNoteCount;
          //}
          //arpeggio.arpeggioNoteElements.resize(arpeggioNoteCount);

          for (const std::u8string& keyColonValue : valueSplit)
          {
            const std::vector<std::u8string> keyValue = String::split(keyColonValue, u8':');
            ASSERT(keyValue.size() == 2);

            if (keyValue[0] == u8"name")
            {
              arpeggio.name = keyValue[1];
            }
            else if (u8'0' <= keyValue[0][0] && keyValue[0][0] <= u8'9') // 
            {
              const i8 string = keyValue[0][0] - u8'0';
              const std::u8string key2 = keyValue[0].substr(1);

              const i8 k = getChordNoteIndex(valueSplit, string);

              if (key2 == u8"finger")
                arpeggio.finger[k] = i8(atoi2(keyValue[1].c_str()));
              else
                ASSERT(false);
            }
            else
              ASSERT(false);
          }
          level.arpeggios.push_back(arpeggio);
        }
      }
    }

    {
      const std::u8string levelSustainsSection = levelSection + u8"Sustains";

      if (ini.contains(levelSustainsSection))
      {
        for (const auto& [key, value] : ini.at(levelSustainsSection))
        {

          const std::vector<std::u8string> keySplit = String::split(key, u8'-');
          ASSERT(keySplit.size() == 2);

          Sustain sustain{
            .startTimeNS = timeNS_From_String(keySplit[0]),
            .endTimeNS = timeNS_From_String(keySplit[1]),
          };

          const std::vector<std::u8string> valueSplit = String::split(value, u8',');
          if (valueSplit.size() >= 1)
          {
            for (const std::u8string& keyColonValue : valueSplit)
            {
              const std::vector<std::u8string> keyValue = String::split(keyColonValue, u8':');
              ASSERT(keyValue.size() == 2);

              if (keyValue[0] == u8"name")
                sustain.name = keyValue[1];
              else if (u8'0' <= keyValue[0][0] && keyValue[0][0] <= u8'9') // 
              {
                const i8 string = keyValue[0][0] - u8'0';
                const std::u8string key2 = keyValue[0].substr(1);

                const i8 k = getChordNoteIndex(valueSplit, string);

                if (key2 == u8"finger")
                  sustain.finger[k] = i8(atoi2(keyValue[1].c_str()));
                else
                  ASSERT(false);
              }
              else
                ASSERT(false);
            }
          }

          level.sustains.push_back(sustain);
        }
      }
    }

    if (level.anchors.size() >= 1 || level.chords.size() >= 1 || level.sustains.size() >= 1 || level.notes.size() >= 1)
    {
      track.levels.push_back(level);
      ++i;
      goto next;
    }

    if (ini.contains(u8"Tones"))
    {
      for (const auto& [key, value] : ini.at(u8"Tones"))
      {
        Tone tone
        {
          .timeNS = timeNS_From_String(key),
          .id = atoi(reinterpret_cast<const char*>(value.c_str()))
        };
        track.tones.push_back(tone);
      }
    }

    songTracks[arrangementIndex] = std::move(track);
    ++arrangementIndex;
  }

  return songTracks;
}

std::vector<Song::Vocal> Song::ShredParser::loadVocals(const Shred::Info& shredInfo)
{
  if (shredInfo.lyricsIni.size() == 0)
    return {};

  const std::vector<Ini::KeyValue>& lyrics = shredInfo.lyricsIni.at(u8"Lyrics");

  std::vector<Song::Vocal> vocals(lyrics.size());

  i32 linebreaks = 0;
  for (i32 i = 0; i < lyrics.size(); ++i)
  {
    const std::u8string& times = lyrics[i].key;

    const std::vector<std::u8string> timeSplit = String::split(times, u8',');
    ASSERT(timeSplit.size() == 2);

    vocals[i].timeNS = timeNS_From_String(timeSplit[0]);
    vocals[i].lengthNS = timeNS_From_String(timeSplit[1]);
    vocals[i].lyric.assign(lyrics[i].value.begin(), lyrics[i].value.end());

    if (vocals[i].lyric[vocals[i].lyric.size() - 1] == '+')
      ++linebreaks;
  }

  if (linebreaks <= 1)
    addMissingVocalLineBreaks(vocals);

  return vocals;
}

void Song::ShredParser::loadSongTones(const SongIndex songIndex, const Song::Info& songInfo, const Shred::Info& shredInfo)
{
  const std::vector<Arrangement::Info>& arrangementInfos = songInfo.arrangementInfos;

  //ASSERT(Global::songStats.contains(arrangementInfos[0].persistentId));

  { // add empty tonebanks for each arrangement
    for (ArrangementIndex arrangementIndex = 0; arrangementIndex < arrangementInfos.size(); ++arrangementIndex)
    {
      if (Global::songStats[arrangementInfos[arrangementIndex].persistentId].sfxBankIndex == -1)
      {
        Global::songStats.at(arrangementInfos[arrangementIndex].persistentId).sfxBankIndex = Global::firstEmptyNegativeToneBank;
        Global::bankIndex2SongIndex[Global::firstEmptyNegativeToneBank] = songIndex;
        --Global::firstEmptyNegativeToneBank;
      }
    }
  }

  if (!shredInfo.tonesIni.empty())
    Tones::loadSongToneFile(songInfo.arrangementInfos, shredInfo.tonesIni);
}

#endif // SHR3D_SHRED

#ifdef SHR3D_PSARC
static void readPsarcHsanAttribute(Json::object_element* it, Arrangement::Info& arrangementInfo, std::u8string& artistName, std::u8string& songName, std::u8string& albumName, i32& songYear, TimeNS& songLengthNS)
{
  if (0 == strcmp(it->name->string, "AlbumArt"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.albumArt = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "AlbumName"))
  {
    ASSERT(it->value->type == Json::type_string);
    Json::string* string = (Json::string*)it->value->payload;
    ASSERT(albumName.size() == 0 || albumName == reinterpret_cast<const char8_t*>(string->string));
    albumName = reinterpret_cast<const char8_t*>(string->string);
    return;
  }
  if (0 == strcmp(it->name->string, "AlbumNameSort"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.albumNameSort = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "ArrangementName"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.arrangementName = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "ArtistName"))
  {
    ASSERT(it->value->type == Json::type_string);
    Json::string* string = (Json::string*)it->value->payload;
    ASSERT(artistName.size() == 0 || artistName == reinterpret_cast<const char8_t*>(string->string));
    artistName = reinterpret_cast<const char8_t*>(string->string);
    return;
  }
  if (0 == strcmp(it->name->string, "ArtistNameSort"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.artistNameSort = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "BassPick"))
  {
    ASSERT(it->value->type == Json::type_number);
    Json::number* number = (Json::number*)it->value->payload;
    arrangementInfo.bassPick = bool(atoi(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "CapoFret"))
  {
    ASSERT(it->value->type == Json::type_number);
    Json::number* number = (Json::number*)it->value->payload;
    ASSERT(std::string(number->number).ends_with(".0")); // no idea why capo is a float
    arrangementInfo.capoFret = i8(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "CentOffset"))
  {
    ASSERT(it->value->type == Json::type_number);
    Json::number* number = (Json::number*)it->value->payload;
    arrangementInfo.centOffset = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "DLC"))
  {
    //ASSERT(it->value->type == Json::type_true || it->value->type == Json::type_false);
    //arrangementInfo.dLC = it->value->type == Json::type_true;
    return;
  }
  if (0 == strcmp(it->name->string, "DLCKey"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.dLCKey = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "DNA_Chords"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.dNA_Chords = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "DNA_Riffs"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.dNA_Riffs = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "DNA_Solo"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.dNA_Solo = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "EasyMastery"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.easyMastery = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "LeaderboardChallengeRating"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.leaderboardChallengeRating = atoi(number->number);
    return;
  }
  if (0 == strcmp(it->name->string, "ManifestUrn"))
  {
    ASSERT(it->value->type == Json::type_string);
    Json::string* string = (Json::string*)it->value->payload;
    const char8_t* str = reinterpret_cast<const char8_t*>(string->string);

    i32 i = string->string_size - 1;
    for (; i >= 0; --i)
    {
      if (str[i] == u8'_')
        break;
    }

    arrangementInfo.arrangementName = &str[i + 1];
    ASSERT(arrangementInfo.arrangementName.size() >= 1);
    ASSERT('a' <= arrangementInfo.arrangementName[0] && arrangementInfo.arrangementName[0] <= 'z');
    arrangementInfo.arrangementName[0] -= 32;
    return;
  }
  if (0 == strcmp(it->name->string, "MasterID_RDV"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.masterID_RDV = atoi(number->number);
    return;
  }
  if (0 == strcmp(it->name->string, "Metronome"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.metronome = atoi(number->number);
    return;
  }
  if (0 == strcmp(it->name->string, "MediumMastery"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.mediumMastery = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "NotesEasy"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.notesEasy = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "NotesHard"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.notesHard = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "NotesMedium"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.notesMedium = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "Representative"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.representative = atoi(number->number);
    return;
  }
  if (0 == strcmp(it->name->string, "RouteMask"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.routeMask = atoi(number->number);
    return;
  }
  if (0 == strcmp(it->name->string, "Shipping"))
  {
    //ASSERT(it->value->type == Json::type_true || it->value->type == Json::type_false);
    //arrangementInfo.shipping = it->value->type == Json::type_true;
    return;
  }
  if (0 == strcmp(it->name->string, "SKU"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.sKU = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "SongDiffEasy"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.notesMedium = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "SongDiffHard"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.notesMedium = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "SongDiffMed"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.notesMedium = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "SongDifficulty"))
  {
    //ASSERT(it->value->type == Json::type_number);
    //Json::number* number = (Json::number*)it->value->payload;
    //arrangementInfo.notesMedium = f32(atof(number->number));
    return;
  }
  if (0 == strcmp(it->name->string, "SongKey"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.songKey = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "SongLength"))
  {
    ASSERT(it->value->type == Json::type_number);
    Json::number* number = (Json::number*)it->value->payload;
    songLengthNS = timeNS_From_String(reinterpret_cast<const char8_t*>(number->number));
    //arrangementInfo.songLength = TimeNS_To_Seconds(songLengthNS);
    return;
  }
  if (0 == strcmp(it->name->string, "SongName"))
  {
    ASSERT(it->value->type == Json::type_string);
    Json::string* string = (Json::string*)it->value->payload;
    ASSERT(songName.size() == 0 || songName == reinterpret_cast<const char8_t*>(string->string));
    songName = reinterpret_cast<const char8_t*>(string->string);
    return;
  }
  if (0 == strcmp(it->name->string, "SongNameSort"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.songNameSort = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "SongYear"))
  {
    ASSERT(it->value->type == Json::type_number);
    Json::number* number = (Json::number*)it->value->payload;
    ASSERT(songYear == 0 || songYear == atoi(number->number));
    songYear = atoi(number->number);
    return;
  }
  if (0 == strcmp(it->name->string, "JapaneseSongName"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.japaneseSongName = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "JapaneseArtist"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.japaneseArtist = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "JapaneseArtistName"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //arrangementInfo.japaneseArtistName = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "Tuning"))
  {
    Json::value* tuning_value = it->value;
    ASSERT(tuning_value->type == Json::type_object);

    Json::object* tuning_o = (Json::object*)tuning_value->payload;

    Json::object_element* string = tuning_o->start;
    {
      ASSERT(0 == strcmp(string->name->string, "string0"));
      ASSERT(string->value->type == Json::type_number);
      Json::number* number = (Json::number*)string->value->payload;
      arrangementInfo.tuning.string[5] = i8(atoi(number->number));
    }
    string = string->next;
    {
      ASSERT(0 == strcmp(string->name->string, "string1"));
      ASSERT(string->value->type == Json::type_number);
      Json::number* number = (Json::number*)string->value->payload;
      arrangementInfo.tuning.string[4] = i8(atoi(number->number));
    }
    string = string->next;
    {
      ASSERT(0 == strcmp(string->name->string, "string2"));
      ASSERT(string->value->type == Json::type_number);
      Json::number* number = (Json::number*)string->value->payload;
      arrangementInfo.tuning.string[3] = i8(atoi(number->number));
    }
    string = string->next;
    {
      ASSERT(0 == strcmp(string->name->string, "string3"));
      ASSERT(string->value->type == Json::type_number);
      Json::number* number = (Json::number*)string->value->payload;
      arrangementInfo.tuning.string[2] = i8(atoi(number->number));
    }
    string = string->next;
    {
      ASSERT(0 == strcmp(string->name->string, "string4"));
      ASSERT(string->value->type == Json::type_number);
      Json::number* number = (Json::number*)string->value->payload;
      arrangementInfo.tuning.string[1] = i8(atoi(number->number));
    }
    string = string->next;
    {
      ASSERT(0 == strcmp(string->name->string, "string5"));
      ASSERT(string->value->type == Json::type_number);
      Json::number* number = (Json::number*)string->value->payload;
      arrangementInfo.tuning.string[0] = i8(atoi(number->number));
    }

    return;
  }
  if (0 == strcmp(it->name->string, "PersistentID"))
  {
    //ASSERT(it->value->type == Json::type_string);
    //Json::string* string = (Json::string*)it->value->payload;
    //ASSERT(string->string_size == 32);
    //arrangementInfo.persistentID = string->string;
    return;
  }
  if (0 == strcmp(it->name->string, "JapaneseVocal"))
  {
    //ASSERT(it->value->type == Json::type_true || it->value->type == Json::type_false);
    //arrangementInfo.japaneseVocal = it->value->type == Json::type_true;
    return;
  }

  ASSERT(false);
}

static std::u8string getDisplayChordName(const std::u8string& name)
{
  std::u8string tName = name;

  if (const size_t pos = tName.find(u8"min"); pos != std::string::npos)
    tName.replace(tName.find(u8"min"), sizeof(u8"min") - 1, u8"m");

  if (const size_t pos = tName.find(u8"Maj"); pos != std::string::npos)
    tName.replace(tName.find(u8"Maj"), sizeof(u8"Maj") - 1, u8"");

  return tName;
}

Song::Info Song::PsarcParser::loadSongInfo(const Psarc::Info& psarcInfo, const std::u8string& psarcFilename)
{
  DEBUG_PRINT("Song::PsarcParser::loadSongInfo()\n");

  Song::Info songInfo;

  for (i32 i = 0; i < psarcInfo.tocEntries.size(); ++i)
  {
    const Psarc::Info::TOCEntry& tocEntry = psarcInfo.tocEntries[i];
    if (tocEntry.name.ends_with(u8".hsan"))
    {
      songInfo.arrangementInfos = readPsarcHsan(tocEntry.content, songInfo.artistName, songInfo.songName, songInfo.albumName, songInfo.songYear, songInfo.songLength);
      patchPsarcArrangementInfo(songInfo.arrangementInfos, psarcFilename);
    }
    else if (tocEntry.name.ends_with(u8"_256.dds")) {
      songInfo.psarcAlbumCover256_tocIndex = i;
    }
  }

  songInfo.loadState = LoadState::complete;

  ASSERT(songInfo.arrangementInfos.size() != 0);

  return songInfo;
}

static void parseChordMask(Sng::NoteMask noteMask, Song::Chord& chord)
{
  // Remove flags from know techniques
  if (bool(noteMask & Sng::NoteMask::chord))
    noteMask &= ~Sng::NoteMask::chord;
  if (bool(noteMask & Sng::NoteMask::chordNotes))
    noteMask &= ~Sng::NoteMask::chordNotes;
  if (bool(noteMask & Sng::NoteMask::sustain))
    noteMask &= ~Sng::NoteMask::sustain;
  if (bool(noteMask & Sng::NoteMask::doubleStop))
    noteMask &= ~Sng::NoteMask::doubleStop;
  if (bool(noteMask & Sng::NoteMask::arpeggio))
    noteMask &= ~Sng::NoteMask::arpeggio;

  // TODO: make changes to fix inacurrate 'strum ='

  chord.strumUp = true;
  //if (bool(noteMask & Sng::NoteMask::strum))
  //{
  //  noteMask &= ~Sng::NoteMask::strum;
  //  chord.strum = 1; // up //TODO: Wrong, need research about it later
  //}

  if (noteMask == Sng::NoteMask::undefined)
    return;

  // Setup techniques and remove flags
  if (bool(noteMask & Sng::NoteMask::parent))
  {
    //noteMask &= ~Sng::NoteMask::parent;
    chord.linkNext_ = true;
  }
  if (bool(noteMask & Sng::NoteMask::accent))
  {
    //noteMask &= ~Sng::NoteMask::accent;
    chord.accent = true;
  }
  if (bool(noteMask & Sng::NoteMask::frethandMute))
  {
    //noteMask &= ~Sng::NoteMask::frethandMute;
    chord.fretHandMute = true;
  }
  if (bool(noteMask & Sng::NoteMask::highDensity))
  {
    //noteMask &= ~Sng::NoteMask::highDensity;
    chord.highDensity = true;
  }
  if (bool(noteMask & Sng::NoteMask::ignore))
  {
    //noteMask &= ~Sng::NoteMask::ignore;
    chord.ignore = true;
  }
  if (bool(noteMask & Sng::NoteMask::palmMute))
  {
    //noteMask &= ~Sng::NoteMask::palmMute;
    chord.palmMute = true;
  }
}

static void parseNoteMask(Sng::NoteMask noteMask, Song::Note& note)
{
  // Remove flags from markers (complex techniques will be setup later)
  if (bool(noteMask & Sng::NoteMask::single))
    noteMask &= ~Sng::NoteMask::single;
  if (bool(noteMask & Sng::NoteMask::open))
    noteMask &= ~Sng::NoteMask::open;
  if (bool(noteMask & Sng::NoteMask::leftHand))
    noteMask &= ~Sng::NoteMask::leftHand;
  if (bool(noteMask & Sng::NoteMask::slide))
    noteMask &= ~Sng::NoteMask::slide;
  if (bool(noteMask & Sng::NoteMask::sustain))
    noteMask &= ~Sng::NoteMask::sustain;
  if (bool(noteMask & Sng::NoteMask::slideUnpitchedTo))
    noteMask &= ~Sng::NoteMask::slideUnpitchedTo;

  // Setup boolean techniques and remove flag (can be override later)
  note.linkNext_ = bool(noteMask & Sng::NoteMask::parent);
  note.maxBend = bool(noteMask & Sng::NoteMask::bend); //Will be setup later
  note.pluck = bool(noteMask & Sng::NoteMask::pluck);
  note.slap = bool(noteMask & Sng::NoteMask::slap);
  note.tap = bool(noteMask & Sng::NoteMask::tap);
  note.vibrato = bool(noteMask & Sng::NoteMask::vibrato);
  note.accent = bool(noteMask & Sng::NoteMask::accent);
  note.hammerOn = bool(noteMask & Sng::NoteMask::hammerOn);
  note.pullOff = bool(noteMask & Sng::NoteMask::pullOff);

  // FIXME: either hopo is missing or it does not exist

  note.harmonic = bool(noteMask & Sng::NoteMask::harmonic);
  note.frethandMute = bool(noteMask & Sng::NoteMask::mute);
  note.palmMute = bool(noteMask & Sng::NoteMask::palmMute);
  note.tremolo = bool(noteMask & Sng::NoteMask::tremolo);
  note.pinchHarmonic = bool(noteMask & Sng::NoteMask::pinchHarmonic);
  note.rightHand = bool(noteMask & Sng::NoteMask::rightHand);
  note.ignore = bool(noteMask & Sng::NoteMask::ignore);

  note.rotation = bool(noteMask & Sng::NoteMask::rotation);
}

static std::vector<Song::Note> parseChordNotes(const Sng::Info::Arrangement::Note& aNote, const Sng::Info::Chord& chordTemplate, const Sng::Info::ChordNotes& chordNotes, const bool isBass)
{
  std::vector<Song::Note> songChordNotes;

  for (i8 i = 0; i < 6; ++i)
  {
    if (chordTemplate.frets[i] >= 0)
    {
      Song::Note note{};
      note.timeNS = timeNS_From_Seconds(aNote.time);
      note.linkNext_ = false;
      note.isLinked = false;
      note.accent = false;
      note.maxBend = 0.0f;
      note.fret = chordTemplate.frets[i];
      note.hammerOn = false;
      note.harmonic = false;
      note.hopo = false;
      note.ignore = false;
      note.finger = chordTemplate.fingers[i];
      note.frethandMute = false;
      note.palmMute = false;
      note.pluck = false;
      note.pullOff = false;
      note.slap = false;
      note.slideTo = -1;
      note.string = (isBass ? 3 : 5) - i;
      ASSERT(note.string >= 0);
      note.sustainNS = timeNS_From_Seconds(aNote.sustain);
      note.tremolo = false;
      note.pinchHarmonic = false;
      note.pickDirection = false;
      note.rightHand = false;
      note.slideUnpitchTo = -1;
      note.tap = false;
      note.vibrato = 0;

      if (chordNotes.noteMask[i] != Sng::NoteMask::undefined)
      {
        parseNoteMask(chordNotes.noteMask[i], note);

        note.slideTo = chordNotes.slideTo[i];
        note.slideUnpitchTo = chordNotes.slideUnpitchTo[i];
        note.vibrato = i8(chordNotes.vibrato[i]);

        note.bendValues.resize(chordNotes.bendData[i].UsedCount);
        for (i32 k = 0; k < chordNotes.bendData[i].UsedCount; ++k)
        {
          note.bendValues[k].timeNS = timeNS_From_Seconds(chordNotes.bendData[i].bendData32[k].time);
          ASSERT(chordNotes.bendData[i].bendData32[k].step * 2.0f == std::floor(chordNotes.bendData[i].bendData32[k].step * 2.0f)); // Check if other values than 0, 0.5, 1.0, ... are allowed
          note.bendValues[k].step = chordNotes.bendData[i].bendData32[k].step;
          note.maxBend = max_(note.maxBend, note.bendValues[k].step);
        }
      }


      songChordNotes.push_back(note);
    }
  }

  return songChordNotes;
}

static Song::Track load_sng_track(const Psarc::Info::TOCEntry& tocEntry, const bool isBass)
{
  Song::Track songTrack;

  const Sng::Info sngInfo = Sng::parse(tocEntry.content);

  songTrack.tones.resize(sngInfo.tone.size());
  for (i32 i = 0; i < sngInfo.tone.size(); ++i)
  {
    songTrack.tones[i].timeNS = timeNS_From_Seconds(sngInfo.tone[i].time);
    songTrack.tones[i].id = sngInfo.tone[i].toneId;
  }
  songTrack.beats.resize(sngInfo.bpm.size());
  {
    i16 previousMeasure = -1;
    for (i32 i = 0; i < sngInfo.bpm.size(); ++i)
    {
      songTrack.beats[i].timeNS = timeNS_From_Seconds(sngInfo.bpm[i].time);
      if (previousMeasure != sngInfo.bpm[i].measure)
      {
        songTrack.beats[i].isPrimary = sngInfo.bpm[i].measure >= 0;
        previousMeasure = sngInfo.bpm[i].measure;
      }
      else
      {
        songTrack.beats[i].isPrimary = false;
      }
    }
  }

  { // Phrases -> Level
    for (i32 i = 0; i < sngInfo.phraseIteration.size(); ++i)
    {
      //const TimeNS startTime = timeNS_From_Seconds(sngInfo.phraseIteration[i].startTime);
      //const TimeNS endTime = timeNS_From_Seconds(sngInfo.phraseIteration[i].nextPhraseTime);
      //ASSERT(endTime > 0);
      const i32 phraseId = sngInfo.phraseIteration[i].phraseId;
      ASSERT(phraseId >= 0);
      ASSERT(phraseId < sngInfo.phrase.size());
      const i32 maxDifficulty = sngInfo.phrase[phraseId].maxDifficulty;
      if (maxDifficulty >= 1)
      {
        Song::LeveledSection leveledSection
        {
          .startTimeNS = timeNS_From_Seconds(sngInfo.phraseIteration[i].startTime),
          .endTimeNS = timeNS_From_Seconds(sngInfo.phraseIteration[i].nextPhraseTime),
          .maxLevel = sngInfo.phrase[phraseId].maxDifficulty
        };
        songTrack.leveledSections.push_back(leveledSection);
      }
    }
  }

  songTrack.chordTemplates_deprecated.resize(sngInfo.chord.size());
  for (i32 i = 0; i < sngInfo.chord.size(); ++i)
  {
    songTrack.chordTemplates_deprecated[i].name = getDisplayChordName(sngInfo.chord[i].name);
    //songTrack.chordTemplates_deprecated[i].displayName = sngInfo.chord[i].name;

    if (isBass)
    {
      ASSERT(sngInfo.chord[i].frets[5] == -1);
      ASSERT(sngInfo.chord[i].frets[4] == -1);
      songTrack.chordTemplates_deprecated[i].fret[0] = sngInfo.chord[i].frets[3];
      songTrack.chordTemplates_deprecated[i].fret[1] = sngInfo.chord[i].frets[2];
      songTrack.chordTemplates_deprecated[i].fret[2] = sngInfo.chord[i].frets[1];
      songTrack.chordTemplates_deprecated[i].fret[3] = sngInfo.chord[i].frets[0];
      ASSERT(sngInfo.chord[i].fingers[5] == -1);
      ASSERT(sngInfo.chord[i].fingers[4] == -1);
      songTrack.chordTemplates_deprecated[i].finger[0] = sngInfo.chord[i].fingers[3];
      songTrack.chordTemplates_deprecated[i].finger[1] = sngInfo.chord[i].fingers[2];
      songTrack.chordTemplates_deprecated[i].finger[2] = sngInfo.chord[i].fingers[1];
      songTrack.chordTemplates_deprecated[i].finger[3] = sngInfo.chord[i].fingers[0];
    }
    else
    {
      songTrack.chordTemplates_deprecated[i].fret[0] = sngInfo.chord[i].frets[5];
      songTrack.chordTemplates_deprecated[i].fret[1] = sngInfo.chord[i].frets[4];
      songTrack.chordTemplates_deprecated[i].fret[2] = sngInfo.chord[i].frets[3];
      songTrack.chordTemplates_deprecated[i].fret[3] = sngInfo.chord[i].frets[2];
      songTrack.chordTemplates_deprecated[i].fret[4] = sngInfo.chord[i].frets[1];
      songTrack.chordTemplates_deprecated[i].fret[5] = sngInfo.chord[i].frets[0];
      songTrack.chordTemplates_deprecated[i].finger[0] = sngInfo.chord[i].fingers[5];
      songTrack.chordTemplates_deprecated[i].finger[1] = sngInfo.chord[i].fingers[4];
      songTrack.chordTemplates_deprecated[i].finger[2] = sngInfo.chord[i].fingers[3];
      songTrack.chordTemplates_deprecated[i].finger[3] = sngInfo.chord[i].fingers[2];
      songTrack.chordTemplates_deprecated[i].finger[4] = sngInfo.chord[i].fingers[1];
      songTrack.chordTemplates_deprecated[i].finger[5] = sngInfo.chord[i].fingers[0];
    }
  }
  songTrack.levels.resize(sngInfo.arrangements.size());
  for (i32 i = 0; i < sngInfo.arrangements.size(); ++i)
  {
    songTrack.levels[i].anchors.resize(sngInfo.arrangements[i].anchors.size());
    for (i32 j = 0; j < sngInfo.arrangements[i].anchors.size(); ++j)
    {
      songTrack.levels[i].anchors[j].timeNS = timeNS_From_Seconds(sngInfo.arrangements[i].anchors[j].startBeatTime);
      //songTrack.levels[i].anchors[j].endTimeNS = timeNS_From_Seconds(sngInfo.arrangements[i].anchors[j].endBeatTime);
      songTrack.levels[i].anchors[j].fret = sngInfo.arrangements[i].anchors[j].fretId;
      songTrack.levels[i].anchors[j].width = i8(sngInfo.arrangements[i].anchors[j].width);
    }
    for (i32 j = 0; j < sngInfo.arrangements[i].notes.size(); ++j)
    {
      if (sngInfo.arrangements[i].notes[j].chordId == -1)
      {
        ASSERT(sngInfo.arrangements[i].notes[j].stringIndex != -1);
        ASSERT(sngInfo.arrangements[i].notes[j].fretId != -1);

        Song::Note note{};
        note.timeNS = timeNS_From_Seconds(sngInfo.arrangements[i].notes[j].time);
        note.fret = sngInfo.arrangements[i].notes[j].fretId;
        note.string = (isBass ? 3 : 5) - sngInfo.arrangements[i].notes[j].stringIndex;
        ASSERT(note.string >= 0);
        note.pickDirection = sngInfo.arrangements[i].notes[j].pickDirection != -1;
        parseNoteMask(sngInfo.arrangements[i].notes[j].noteMask, note);
        note.finger = sngInfo.arrangements[i].notes[j].leftHand;
        note.slideTo = sngInfo.arrangements[i].notes[j].slideTo;
        note.slideUnpitchTo = sngInfo.arrangements[i].notes[j].slideUnpitchTo;
        note.tap = sngInfo.arrangements[i].notes[j].tap != -1;
        note.slap = sngInfo.arrangements[i].notes[j].slap != -1;
        note.pluck = sngInfo.arrangements[i].notes[j].pluck != -1;
        note.vibrato = i8(sngInfo.arrangements[i].notes[j].vibrato);
        note.sustainNS = timeNS_From_Seconds(sngInfo.arrangements[i].notes[j].sustain);
        note.maxBend = sngInfo.arrangements[i].notes[j].maxBend;
        note.bendValues.resize(sngInfo.arrangements[i].notes[j].bendData.size());
        for (i32 k = 0; k < sngInfo.arrangements[i].notes[j].bendData.size(); ++k)
        {
          note.bendValues[k].timeNS = timeNS_From_Seconds(sngInfo.arrangements[i].notes[j].bendData[k].time);
          ASSERT(sngInfo.arrangements[i].notes[j].bendData[k].step * 2.0f == std::floor(sngInfo.arrangements[i].notes[j].bendData[k].step * 2.0f)); // Check if other values than 0, 0.5, 1.0, ... are allowed
          note.bendValues[k].step = sngInfo.arrangements[i].notes[j].bendData[k].step;
        }
        songTrack.levels[i].notes.push_back(note);
      }
      else
      {
        ASSERT(bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::chord));
        ASSERT(sngInfo.arrangements[i].notes[j].stringIndex == -1);
        ASSERT(sngInfo.arrangements[i].notes[j].fretId == -1);

        Song::Chord chord{};
        chord.timeNS = timeNS_From_Seconds(sngInfo.arrangements[i].notes[j].time);
        chord.chordId_deprecated = sngInfo.arrangements[i].notes[j].chordId;
        chord.linkNext_ = false;
        chord.isLinked = false;
        chord.accent = false;
        chord.fretHandMute = false;
        chord.highDensity = false;
        chord.ignore = false;
        chord.palmMute = false;
        chord.strumUp = false;

        parseChordMask(sngInfo.arrangements[i].notes[j].noteMask, chord);

        if (sngInfo.arrangements[i].notes[j].chordNotesId != -1)
        {
          ASSERT(sngInfo.chordNotes.size() > sngInfo.arrangements[i].notes[j].chordNotesId);
          chord.chordNotes = parseChordNotes(sngInfo.arrangements[i].notes[j], sngInfo.chord[chord.chordId_deprecated], sngInfo.chordNotes[sngInfo.arrangements[i].notes[j].chordNotesId], isBass);
        }
        else
        {
          const Song::ChordTemplate_deprecated& chordTemplate = songTrack.chordTemplates_deprecated[chord.chordId_deprecated];

          for (i8 k = 0; k < 6; ++k)
          {
            if (chordTemplate.fret[k] >= 0)
            {
              Song::Note note{};
              note.timeNS = timeNS_From_Seconds(sngInfo.arrangements[i].notes[j].time);
              //note.time = sngInfo.arrangements[i].notes[j].time;
              note.linkNext_ = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::parent);
              note.accent = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::accent);
              note.maxBend = sngInfo.arrangements[i].notes[j].maxBend;
              note.fret = chordTemplate.fret[k];
              note.hammerOn = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::hammerOn);
              note.harmonic = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::harmonic);
              note.hopo = /*sngInfo.arrangements[i].notes[j].hopo*/false;
              note.ignore = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::ignore);
              note.finger = sngInfo.arrangements[i].notes[j].leftHand;
              note.frethandMute = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::frethandMute);
              note.palmMute = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::palmMute);
              note.pluck = sngInfo.arrangements[i].notes[j].pluck != -1;
              note.pullOff = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::pullOff);
              note.slap = sngInfo.arrangements[i].notes[j].slap != -1;
              note.slideTo = sngInfo.arrangements[i].notes[j].slideTo;
              note.string = k;
              note.sustainNS = timeNS_From_Seconds(sngInfo.arrangements[i].notes[j].sustain);
              note.tremolo = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::tremolo);
              note.pinchHarmonic = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::pinchHarmonic);
              note.pickDirection = sngInfo.arrangements[i].notes[j].pickDirection != -1;
              note.rightHand = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::rightHand);
              note.slideUnpitchTo = sngInfo.arrangements[i].notes[j].slideUnpitchTo;
              note.tap = bool(sngInfo.arrangements[i].notes[j].noteMask & Sng::NoteMask::tap);
              note.vibrato = i8(sngInfo.arrangements[i].notes[j].vibrato);
              note.bendValues.resize(sngInfo.arrangements[i].notes[j].bendData.size());
              for (i32 l = 0; l < sngInfo.arrangements[i].notes[j].bendData.size(); ++l)
              {
                note.bendValues[l].timeNS = timeNS_From_Seconds(sngInfo.arrangements[i].notes[j].bendData[l].time);
                ASSERT(sngInfo.arrangements[i].notes[j].bendData[l].step * 2.0f == std::floor(sngInfo.arrangements[i].notes[j].bendData[l].step * 2.0f)); // Check if other values than 0, 0.5, 1.0, ... are allowed
                note.bendValues[l].step = sngInfo.arrangements[i].notes[j].bendData[l].step;
              }
              chord.chordNotes.push_back(note);
            }
          }
        }
        songTrack.levels[i].chords.push_back(chord);
      }
    }
    {
      songTrack.levels[i].sustains.resize(sngInfo.arrangements[i].fingerprints1.size());
      for (i32 j = 0; j < sngInfo.arrangements[i].fingerprints1.size(); ++j)
      {
        ASSERT(!bool(sngInfo.chord[sngInfo.arrangements[i].fingerprints1[j].chordId].mask & Sng::ChordMask::arpeggio)); // for chords
        songTrack.levels[i].sustains[j].startTimeNS = timeNS_From_Seconds(sngInfo.arrangements[i].fingerprints1[j].startTime);
        songTrack.levels[i].sustains[j].endTimeNS = timeNS_From_Seconds(sngInfo.arrangements[i].fingerprints1[j].endTime);
        //songTrack.levels[i].sustains[j].chordId_deprectated = sngInfo.arrangements[i].fingerprints1[j].chordId;
        songTrack.levels[i].sustains[j].name = getDisplayChordName(sngInfo.chord[sngInfo.arrangements[i].fingerprints1[j].chordId].name);
      }
    }
    {
      songTrack.levels[i].arpeggios.resize(sngInfo.arrangements[i].fingerprints2.size());
      for (i32 j = 0; j < sngInfo.arrangements[i].fingerprints2.size(); ++j)
      {
        ASSERT(bool(sngInfo.chord[sngInfo.arrangements[i].fingerprints2[j].chordId].mask & Sng::ChordMask::arpeggio)); // for arpeggios
        songTrack.levels[i].arpeggios[j].startTimeNS = timeNS_From_Seconds(sngInfo.arrangements[i].fingerprints2[j].startTime);
        songTrack.levels[i].arpeggios[j].endTimeNS = timeNS_From_Seconds(sngInfo.arrangements[i].fingerprints2[j].endTime);
        //songTrack.levels[i].arpeggios[j].chordId_deprectated = sngInfo.arrangements[i].fingerprints2[j].chordId;
        songTrack.levels[i].arpeggios[j].name = getDisplayChordName(sngInfo.chord[sngInfo.arrangements[i].fingerprints2[j].chordId].name);

      }
    }
  }

  return songTrack;
}

std::unordered_map<ArrangementIndex, Song::Track> Song::PsarcParser::loadTracks(const Psarc::Info& psarcInfo, const std::vector<Arrangement::Info>& arrangementInfos)
{
  std::unordered_map<ArrangementIndex, Song::Track> songTracks(arrangementInfos.size());

  Global::songTrackLevelAdjusted.clear();
  for (ArrangementIndex i = 0; i < arrangementInfos.size(); ++i)
  {
    const Arrangement::Info& arrangementInfo = arrangementInfos[i];

    std::u8string sngFileEnd = u8"_" + arrangementInfo.arrangementName + u8".sng";
    ASSERT(u8'A' <= sngFileEnd[1] && sngFileEnd[1] <= u8'Z');
    sngFileEnd[1] += 32;
    ASSERT(u8'a' <= sngFileEnd[1] && sngFileEnd[1] <= u8'z');

    for (const Psarc::Info::TOCEntry& tocEntry : psarcInfo.tocEntries)
    {
      if (tocEntry.name.ends_with(sngFileEnd) && sngFileEnd != u8"_vocals.sng")
      {
        songTracks[i] = load_sng_track(tocEntry, arrangementInfo.isBass);
        break;
      }
    }
  }

  return songTracks;
}

static std::vector<Song::Vocal> load_sng_vocals(const Psarc::Info::TOCEntry& tocEntry)
{
  Song::Track songTrack;

  const Sng::Info sngInfo = Sng::parse(tocEntry.content);

  std::vector<Song::Vocal> vocals(sngInfo.vocal.size());
  i32 linebreaks = 0;
  for (i32 i = 0; i < sngInfo.vocal.size(); ++i)
  {
    vocals[i].timeNS = timeNS_From_Seconds(sngInfo.vocal[i].time);
    vocals[i].lengthNS = timeNS_From_Seconds(sngInfo.vocal[i].length);
    vocals[i].lyric = reinterpret_cast<const char8_t*>(sngInfo.vocal[i].lyric);
    //ASSERT(sngInfo.vocal[i].note == 0);
    //vocals[i].note = sngInfo.vocal[i].note;
    if (vocals[i].lyric[vocals[i].lyric.size() - 1] == '+')
      ++linebreaks;
  }

  if (linebreaks <= 1)
    addMissingVocalLineBreaks(vocals);

  return vocals;
}

std::vector<Song::Vocal> Song::PsarcParser::loadVocals(const Psarc::Info& psarcInfo)
{
  for (const Psarc::Info::TOCEntry& tocEntry : psarcInfo.tocEntries)
  {
    if (tocEntry.name.ends_with(u8"_vocals.sng"))
      return load_sng_vocals(tocEntry);
  }
  return {};
}

std::vector<Arrangement::Info> Song::PsarcParser::readPsarcHsan(const std::vector<u8>& hsanData, std::u8string& artistName, std::u8string& songName, std::u8string& albumName, i32& songYear, TimeNS& songLength)
{
  std::vector<Arrangement::Info> arrangementInfos;

  Json::value* root = Json::parse(hsanData.data(), hsanData.size());
  ASSERT(root != nullptr);
  ASSERT(root->type == Json::type_object);

  Json::object* object = (Json::object*)root->payload;
  ASSERT(object->length >= 2);

  Json::object_element* entries = object->start;

  ASSERT(0 == strcmp(entries->name->string, "Entries"));

  Json::value* entries_value = entries->value;
  ASSERT(entries_value->type == Json::type_object);

  Json::object* id_o = (Json::object*)entries_value->payload;

  Json::object_element* id = id_o->start;
  do
  {
    ASSERT(id->name->string_size == 32);

    Arrangement::Info entry;

    Json::value* id_value = id->value;

    ASSERT(id_value->type == Json::type_object);

    Json::object* attributes_o = (Json::object*)id_value->payload;
    ASSERT(attributes_o->length == 1);

    {
      Json::object_element* attributes = attributes_o->start;
      ASSERT(0 == strcmp(attributes->name->string, "Attributes"));

      {
        Json::value* attributes_value = attributes->value;
        ASSERT(attributes_value->type == Json::type_object);

        Json::object* attribute = (Json::object*)attributes_value->payload;

        Json::object_element* it = attribute->start;
        do
        {
          readPsarcHsanAttribute(it, entry, artistName, songName, albumName, songYear, songLength);
          it = it->next;
        } while (it != nullptr);
      }

      if (entry.arrangementName != u8"Vocals" && entry.arrangementName != u8"Jvocals")
      {
        arrangementInfos.push_back(entry);
      }
    }
    id = id->next;
  } while (id != nullptr);

  delete root;

  return arrangementInfos;
}

void Song::PsarcParser::patchPsarcArrangementInfo(std::vector<Arrangement::Info>& arrangementInfos, const std::u8string& psarcFilename)
{
  for (Arrangement::Info& arrangementInfo : arrangementInfos)
  {
    arrangementInfo.persistentId = psarcFilename + u8'|' + arrangementInfo.arrangementName;
    // fix bass tuning
    arrangementInfo.isBass = arrangementInfo.arrangementName.starts_with(u8"Bass");
    if (arrangementInfo.isBass)
    {
      arrangementInfo.tuning.string[0] = arrangementInfo.tuning.string[2];
      arrangementInfo.tuning.string[1] = arrangementInfo.tuning.string[3];
      arrangementInfo.tuning.string[2] = arrangementInfo.tuning.string[4];
      arrangementInfo.tuning.string[3] = arrangementInfo.tuning.string[5];
      arrangementInfo.tuning.string[4] = -128;
      arrangementInfo.tuning.string[5] = -128;
    }
  }
}
#endif // SHR3D_PSARC

static void loadTrackSectionWithLevel(const Song::Track& track, Song::TrackLevelAdjusted& trackLvlAd, const TimeNS startTime, const TimeNS endTime, const i32 difficulty)
{
  for (i32 j = 0; j < track.levels[difficulty].notes.size(); ++j)
  {
    const Song::Note& note = track.levels[difficulty].notes[j];

    if (startTime <= note.timeNS && note.timeNS < endTime)
      trackLvlAd.notes.push_back(note);
  }
  for (i32 j = 0; j < track.levels[difficulty].chords.size(); ++j)
  {
    const Song::Chord& chord = track.levels[difficulty].chords[j];

    if (startTime <= chord.timeNS && chord.timeNS < endTime)
      trackLvlAd.chords.push_back(chord);
  }
  for (i32 j = 0; j < track.levels[difficulty].anchors.size(); ++j)
  {
    const Song::Anchor& anchor = track.levels[difficulty].anchors[j];

    if (startTime <= anchor.timeNS && anchor.timeNS < endTime)
      trackLvlAd.anchors.push_back(anchor);
  }
  for (i32 j = 0; j < track.levels[difficulty].sustains.size(); ++j)
  {
    const Song::Sustain& sustain = track.levels[difficulty].sustains[j];

    if (startTime <= sustain.startTimeNS && sustain.startTimeNS < endTime)
      trackLvlAd.sustains.push_back(sustain);
  }
  for (i32 j = 0; j < track.levels[difficulty].arpeggios.size(); ++j)
  {
    const Song::Arpeggio& arpeggio = track.levels[difficulty].arpeggios[j];

    if (startTime <= arpeggio.startTimeNS && arpeggio.startTimeNS < endTime)
      trackLvlAd.arpeggios.push_back(arpeggio);
  }
}

Song::TrackLevelAdjusted Song::loadTrackLevelAdjusted(const Song::Track& track, const std::vector<i32>& level)
{
  ASSERT(track.leveledSections.size() == level.size());

  Song::TrackLevelAdjusted trackLvlAd;

  {
    TimeNS nextTime = 0;
    for (i32 i = 0; i < track.leveledSections.size(); ++i)
    {
      if (nextTime < track.leveledSections[i].startTimeNS)
        loadTrackSectionWithLevel(track, trackLvlAd, nextTime, track.leveledSections[i].startTimeNS, 0);

      loadTrackSectionWithLevel(track, trackLvlAd, track.leveledSections[i].startTimeNS, track.leveledSections[i].endTimeNS, level[i]);
      nextTime = track.leveledSections[i].endTimeNS;
    }
    loadTrackSectionWithLevel(track, trackLvlAd, nextTime, I64::max, 0);
  }

  ASSERT(trackLvlAd.anchors.size() > 0);

  { // set isLinked state on notes and chords
    { // notes
      for (i32 i = 1; i < trackLvlAd.notes.size(); ++i)
      {
        Note& possibleLinkedNote = trackLvlAd.notes[i];
        ASSERT(!possibleLinkedNote.isLinked);

        for (i32 j = 0; j < i; ++j)
        {
          const Note& previousNote = trackLvlAd.notes[j];

          ASSERT(previousNote.timeNS <= possibleLinkedNote.timeNS); // not sure if this is actually true.

          if (!previousNote.linkNext_)
            continue;

          if (previousNote.string != possibleLinkedNote.string)
            continue;

          if (abs((previousNote.timeNS + previousNote.sustainNS) - possibleLinkedNote.timeNS) > 10_ms) // previous note needs to end roughly when the the other note starts
            continue;

          if (previousNote.slideTo == possibleLinkedNote.fret
            || previousNote.slideUnpitchTo == possibleLinkedNote.fret
            || previousNote.fret == possibleLinkedNote.fret)
          {
            possibleLinkedNote.isLinked = true;
            break;
          }
        }

        if (possibleLinkedNote.isLinked)
          continue;

        for (i32 j = 0; j < trackLvlAd.chords.size(); ++j)
        {
          const Chord& chord = trackLvlAd.chords[j];

          for (i32 k = 0; k < chord.chordNotes.size(); ++k)
          {
            const Note& chordNote = chord.chordNotes[k];

            if (!chordNote.linkNext_)
              continue;

            if (chordNote.string != possibleLinkedNote.string)
              continue;

            if (abs((chordNote.timeNS + chordNote.sustainNS) - possibleLinkedNote.timeNS) > 10_ms) // previous note needs to end roughly when the the other note starts
              continue;

            if (chordNote.slideTo == possibleLinkedNote.fret
              || chordNote.slideUnpitchTo == possibleLinkedNote.fret
              || chordNote.fret == possibleLinkedNote.fret)
            {
              possibleLinkedNote.isLinked = true;
              break;
            }
          }
        }
      }
    }
    // there are some linked chords in Led-Zeppelin_In-My-Time-of-Dying. These are not implemented.
  }

  return trackLvlAd;
}

const char8_t* Song::tuningName(const Tuning& tuning)
{
  if (tuning.string[0] == 0 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == 0
    || tuning.string[0] == 0 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"E Standard";
  if (tuning.string[0] == 0 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == -2
    || tuning.string[0] == 0 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == -2 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"Drop D";
  if (tuning.string[0] == 1 && tuning.string[1] == 1 && tuning.string[2] == 1 && tuning.string[3] == 1 && tuning.string[4] == 1 && tuning.string[5] == 1
    || tuning.string[0] == 1 && tuning.string[1] == 1 && tuning.string[2] == 1 && tuning.string[3] == 1 && -128 && tuning.string[5] == -128)
    return u8"F Standard";
  if (tuning.string[0] == -2 && tuning.string[1] == -2 && tuning.string[2] == -1 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == -2)
    return u8"Open D";
  if (tuning.string[0] == 0 && tuning.string[1] == 2 && tuning.string[2] == 2 && tuning.string[3] == 2 && tuning.string[4] == 0 && tuning.string[5] == 0)
    return u8"Open A";
  if (tuning.string[0] == -2 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == -2 && tuning.string[5] == -2)
    return u8"Opend G";
  if (tuning.string[0] == 0 && tuning.string[1] == 0 && tuning.string[2] == 1 && tuning.string[3] == 2 && tuning.string[4] == 2 && tuning.string[5] == 0)
    return u8"Opend E";
  if (tuning.string[0] == -1 && tuning.string[1] == -1 && tuning.string[2] == -1 && tuning.string[3] == -1 && tuning.string[4] == -1 && tuning.string[5] == -1
    || tuning.string[0] == -1 && tuning.string[1] == -1 && tuning.string[2] == -1 && tuning.string[3] == -1 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"Eb Standard";
  if (tuning.string[0] == -1 && tuning.string[1] == -1 && tuning.string[2] == -1 && tuning.string[3] == -1 && tuning.string[4] == -1 && tuning.string[5] == -3
    || tuning.string[0] == -1 && tuning.string[1] == -1 && tuning.string[2] == -1 && tuning.string[3] == -3 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"Eb Drop Db";
  if (tuning.string[0] == -2 && tuning.string[1] == -2 && tuning.string[2] == -2 && tuning.string[3] == -2 && tuning.string[4] == -2 && tuning.string[5] == -2
    || tuning.string[0] == -2 && tuning.string[1] == -2 && tuning.string[2] == -2 && tuning.string[3] == -2 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"D Standard";
  if (tuning.string[0] == -2 && tuning.string[1] == -2 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == -2)
    return u8"DADGAD";
  if (tuning.string[0] == -2 && tuning.string[1] == -2 && tuning.string[2] == -2 && tuning.string[3] == -2 && tuning.string[4] == -2 && tuning.string[5] == -4
    || tuning.string[0] == -2 && tuning.string[1] == -2 && tuning.string[2] == -2 && tuning.string[3] == -4 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"D Drop C";
  if (tuning.string[0] == -3 && tuning.string[1] == -3 && tuning.string[2] == -3 && tuning.string[3] == -3 && tuning.string[4] == -3 && tuning.string[5] == -3
    || tuning.string[0] == -3 && tuning.string[1] == -3 && tuning.string[2] == -3 && tuning.string[3] == -3 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"C# Standard";
  if (tuning.string[0] == -3 && tuning.string[1] == -3 && tuning.string[2] == -3 && tuning.string[3] == -3 && tuning.string[4] == -3 && tuning.string[5] == -5
    || tuning.string[0] == -3 && tuning.string[1] == -3 && tuning.string[2] == -3 && tuning.string[3] == -5 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"C# Drop B";
  if (tuning.string[0] == -4 && tuning.string[1] == -4 && tuning.string[2] == -4 && tuning.string[3] == -4 && tuning.string[4] == -4 && tuning.string[5] == -4
    || tuning.string[0] == -4 && tuning.string[1] == -4 && tuning.string[2] == -4 && tuning.string[3] == -4 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"C Standard";
  if (tuning.string[0] == -4 && tuning.string[1] == -4 && tuning.string[2] == -4 && tuning.string[3] == -4 && tuning.string[4] == -4 && tuning.string[5] == -6
    || tuning.string[0] == -4 && tuning.string[1] == -4 && tuning.string[2] == -4 && tuning.string[3] == -6 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"C Drop Ab";
  if (tuning.string[0] == -5 && tuning.string[1] == -5 && tuning.string[2] == -5 && tuning.string[3] == -5 && tuning.string[4] == -5 && tuning.string[5] == -5
    || tuning.string[0] == -5 && tuning.string[1] == -5 && tuning.string[2] == -5 && tuning.string[3] == -5 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"B Standard";
  if (tuning.string[0] == -5 && tuning.string[1] == -5 && tuning.string[2] == -5 && tuning.string[3] == -5 && tuning.string[4] == -5 && tuning.string[5] == -7
    || tuning.string[0] == -5 && tuning.string[1] == -5 && tuning.string[2] == -5 && tuning.string[3] == -7 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"B Drop A";
  if (tuning.string[0] == -6 && tuning.string[1] == -6 && tuning.string[2] == -6 && tuning.string[3] == -6 && tuning.string[4] == -6 && tuning.string[5] == -6
    || tuning.string[0] == -6 && tuning.string[1] == -6 && tuning.string[2] == -6 && tuning.string[3] == -6 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"Bb Standard";
  if (tuning.string[0] == -6 && tuning.string[1] == -6 && tuning.string[2] == -6 && tuning.string[3] == -6 && tuning.string[4] == -6 && tuning.string[5] == -8
    || tuning.string[0] == -6 && tuning.string[1] == -6 && tuning.string[2] == -6 && tuning.string[3] == -8 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"Bb Drop Ab";
  if (tuning.string[0] == -7 && tuning.string[1] == -7 && tuning.string[2] == -7 && tuning.string[3] == -7 && tuning.string[4] == -7 && tuning.string[5] == -7
    || tuning.string[0] == -7 && tuning.string[1] == -7 && tuning.string[2] == -7 && tuning.string[3] == -7 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"A Standard";
  if (tuning.string[0] == -7 && tuning.string[1] == -7 && tuning.string[2] == -7 && tuning.string[3] == -7 && tuning.string[4] == -7 && tuning.string[5] == -9
    || tuning.string[0] == -7 && tuning.string[1] == -7 && tuning.string[2] == -7 && tuning.string[3] == -9 && tuning.string[4] == -128 && tuning.string[5] == -128)
    return u8"A Drop G";
  if (tuning.string[0] == 1 && tuning.string[1] == 1 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == 0)
    return u8"All Fourth";
  if (tuning.string[0] == -2 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == -2)
    return u8"Double Drop D";
  if (tuning.string[0] == 0 && tuning.string[1] == 1 && tuning.string[2] == 0 && tuning.string[3] == -2 && tuning.string[4] == 0 && tuning.string[5] == -4)
    return u8"Open C6";
  if (tuning.string[0] == 0 && tuning.string[1] == -4 && tuning.string[2] == 0 && tuning.string[3] == -2 && tuning.string[4] == -2 && tuning.string[5] == -4)
    return u8"Open C5";
  if (tuning.string[0] == 2 && tuning.string[1] == 3 && tuning.string[2] == 2 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == -2)
    return u8"DADADb";
  if (tuning.string[0] == -2 && tuning.string[1] == 1 && tuning.string[2] == -2 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == -2)
    return u8"Open Dm 7";
  if (tuning.string[0] == -2 && tuning.string[1] == 0 && tuning.string[2] == -1 && tuning.string[3] == 0 && tuning.string[4] == 2 && tuning.string[5] == -2)
    return u8"Open Bm";
  if (tuning.string[0] == -2 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == 0)
    return u8"EADGBD";
  if (tuning.string[0] == -2 && tuning.string[1] == -2 && tuning.string[2] == -2 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == -2)
    return u8"Open Dm";
  if (tuning.string[0] == 0 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 2 && tuning.string[5] == -2)
    return u8"DBDGBe";
  if (tuning.string[0] == 0 && tuning.string[1] == -2 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == 0)
    return u8"EADGAe";
  if (tuning.string[0] == -2 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == -2 && tuning.string[5] == 0)
    return u8"EGDGBD";
  if (tuning.string[0] == -1 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == -3 && tuning.string[4] == 0 && tuning.string[5] == 0)
    return u8"EABGBd#";
  if (tuning.string[0] == -1 && tuning.string[1] == 0 && tuning.string[2] == 0 && tuning.string[3] == 0 && tuning.string[4] == 0 && tuning.string[5] == 0)
    return u8"EADGBd#";
  if (tuning.string[0] == -3 && tuning.string[1] == -3 && tuning.string[2] == -2 && tuning.string[3] == -1 && tuning.string[4] == -1 && tuning.string[5] == -3)
    return u8"Open Db/C#";
  if (tuning.string[0] == -5 && tuning.string[1] == -4 && tuning.string[2] == -5 && tuning.string[3] == -5 && tuning.string[4] == -5 && tuning.string[5] == -4)
    return u8"CEADG";
  if (tuning.string[0] == -5 && tuning.string[1] == -4 && tuning.string[2] == -5 && tuning.string[3] == -5 && tuning.string[4] == -5 && tuning.string[5] == -5)
    return u8"BEADG";
  if (tuning.string[0] == -5 && tuning.string[1] == -4 && tuning.string[2] == -5 && tuning.string[3] == -5 && tuning.string[4] == -5 && tuning.string[5] == -7)
    return u8"AEADG";

  //ASSERT(false);

  return u8"Custom Tuning";
}

i32 Song::getCurrentAnchorIndex(const ArrangementIndex selectedArrangementIndex)
{
  for (i32 i = 0; i < i32(Global::songTrackLevelAdjusted[selectedArrangementIndex].anchors.size()); ++i)
  {
    const Song::Anchor& anchor = Global::songTrackLevelAdjusted[selectedArrangementIndex].anchors[i];
    const TimeNS noteTime = -anchor.timeNS + Global::musicTimeElapsedNS;
    if (noteTime < 0)
      return max_(0, i - 1);
  }

  return i32(Global::songTrackLevelAdjusted[selectedArrangementIndex].anchors.size()) - 1;
}
