750 lines
29 KiB
C#
750 lines
29 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.U2D;
|
|
|
|
namespace ABCUnity
|
|
{
|
|
public class Layout : MonoBehaviour
|
|
{
|
|
[SerializeField] private SpriteAtlas spriteAtlas; // set in editor
|
|
[SerializeField] public Color color = Color.black;
|
|
[SerializeField] public Material NoteMaterial;
|
|
[SerializeField] public TextMeshPro textPrefab;
|
|
[SerializeField] public float staffLinePadding = 0.4f;
|
|
[SerializeField] public float staffLineMargin = 1.0f;
|
|
[SerializeField] public bool overrideLineBreaks = false;
|
|
|
|
#region Callbacks
|
|
public delegate void OnLoaded(ABC.Tune tune);
|
|
public OnLoaded onLoaded;
|
|
#endregion
|
|
|
|
#region TuneSpecificMembers
|
|
List<VoiceLayout> layouts = new List<VoiceLayout>();
|
|
Dictionary<int, Beam> beams;
|
|
public ABC.Tune tune { get; private set; }
|
|
GameObject scoreContainer;
|
|
public Dictionary<int, GameObject> gameObjectMap { get; } = new Dictionary<int, GameObject>();
|
|
public Dictionary<GameObject, ABC.Item> itemMap { get; } = new Dictionary<GameObject, ABC.Item>();
|
|
private Dictionary<int, List<SpriteRenderer>> spriteRendererCache = new Dictionary<int, List<SpriteRenderer>>();
|
|
private TimeSignature timeSignature;
|
|
#endregion
|
|
|
|
private float layoutScale = 1.0f;
|
|
public RectTransform rectTransform { get; private set; }
|
|
private SpriteCache cache;
|
|
private NoteCreator notes;
|
|
private bool multilineLayout;
|
|
Vector2 staffOffset;
|
|
private float horizontalMax;
|
|
|
|
#region Constants
|
|
public const float staffPadding = 0.3f;
|
|
public const float measurePadding = 0.5f;
|
|
public const float noteAdvance = 0.75f;
|
|
const float minimumAdavance = 0.25f;
|
|
const float staffHeight = 2.29f;
|
|
#endregion
|
|
|
|
public void Awake()
|
|
{
|
|
multilineLayout = !overrideLineBreaks;
|
|
rectTransform = GetComponent<RectTransform>();
|
|
cache = new SpriteCache(spriteAtlas, textPrefab);
|
|
layouts.Clear();
|
|
if (beams != null)
|
|
beams.Clear();
|
|
|
|
NoteMaterial = GameObject.Instantiate(NoteMaterial);
|
|
NoteMaterial.color = color;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
if (tune == null) return;
|
|
|
|
GameObject.Destroy(scoreContainer);
|
|
layouts.Clear();
|
|
gameObjectMap.Clear();
|
|
itemMap.Clear();
|
|
spriteRendererCache.Clear();
|
|
|
|
timeSignature = null;
|
|
tune = null;
|
|
}
|
|
|
|
public void LoadString(string abc)
|
|
{
|
|
try
|
|
{
|
|
Clear();
|
|
tune = ABC.Tune.Load(abc);
|
|
LayoutTune();
|
|
onLoaded?.Invoke(tune);
|
|
}
|
|
catch (ABC.ParseException e)
|
|
{
|
|
Debug.Log(e.Message);
|
|
}
|
|
}
|
|
|
|
public void LoadStream(Stream stream)
|
|
{
|
|
try
|
|
{
|
|
Clear();
|
|
tune = ABC.Tune.Load(stream);
|
|
LayoutTune();
|
|
onLoaded?.Invoke(tune);
|
|
}
|
|
catch (ABC.ParseException e)
|
|
{
|
|
Debug.Log(e.Message);
|
|
}
|
|
}
|
|
|
|
public void LoadFile(string path)
|
|
{
|
|
using (FileStream file = File.OpenRead(path))
|
|
{
|
|
LoadStream(file);
|
|
}
|
|
}
|
|
|
|
public GameObject FindItemRootObject(GameObject obj)
|
|
{
|
|
while (!itemMap.ContainsKey(obj))
|
|
obj = obj.transform.parent.gameObject;
|
|
|
|
return obj;
|
|
}
|
|
|
|
public bool SetItemColor(ABC.Item item, Color color)
|
|
{
|
|
if (gameObjectMap.TryGetValue(item.id, out GameObject obj))
|
|
{
|
|
if (!spriteRendererCache.TryGetValue(item.id, out var spriteRenderers))
|
|
{
|
|
spriteRenderers = Util.GatherSpriteRenderers(obj);
|
|
spriteRendererCache[item.id] = spriteRenderers;
|
|
}
|
|
|
|
foreach (var spriteRenderer in spriteRenderers)
|
|
spriteRenderer.color = color;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void ResetItemColors()
|
|
{
|
|
foreach (var spriteRenderes in spriteRendererCache.Values)
|
|
{
|
|
foreach (var spriteRenderer in spriteRenderes)
|
|
spriteRenderer.color = color;
|
|
}
|
|
}
|
|
|
|
public Alignment GetAlignment(int i)
|
|
{
|
|
return layouts[i].alignment;
|
|
}
|
|
|
|
void SetupVoiceLayouts()
|
|
{
|
|
// create the layout structures for each voice
|
|
int measureCount = 0;
|
|
for (int i = 0; i < tune.voices.Count; i++)
|
|
{
|
|
var layout = new VoiceLayout(tune.voices[i]);
|
|
|
|
layouts.Add(layout);
|
|
layout.Init(multilineLayout);
|
|
|
|
if (i == 0)
|
|
measureCount = layout.alignment.measures.Count;
|
|
else if (layout.alignment.measures.Count != measureCount)
|
|
throw new LayoutException("All voices must have the same measure count");
|
|
}
|
|
}
|
|
|
|
void LayoutScoreLine(int lineNum)
|
|
{
|
|
for (int measure = 0; measure < layouts[0].scoreLines[lineNum].measures.Count; measure++)
|
|
{
|
|
float advanceAmount = measurePadding;
|
|
foreach (var layout in layouts)
|
|
{
|
|
layout.beatIndex = 0;
|
|
var scoreLine = layout.scoreLines[lineNum];
|
|
var measureInfo = scoreLine.measures[measure];
|
|
|
|
measureInfo.container = new GameObject("Measure");
|
|
}
|
|
|
|
float maxBeatX = 0.0f;
|
|
|
|
for (int beat = 1; beat <= timeSignature.beatCount; beat++)
|
|
{
|
|
int beatItemIndex = 0;
|
|
|
|
while (true)
|
|
{
|
|
bool more = false;
|
|
float alignment = 0.0f;
|
|
|
|
//layout
|
|
foreach (var layout in layouts)
|
|
{
|
|
var scoreLine = layout.scoreLines[lineNum];
|
|
var layoutMeasure = scoreLine.measures[measure];
|
|
var beatInfo = layoutMeasure.source.beats[layout.beatIndex];
|
|
|
|
// advance
|
|
// in order to preserve alignment, all layouts will advance to the furthest position of the current beat marker
|
|
var delta = maxBeatX - layoutMeasure.insertX;
|
|
layoutMeasure.spacers.Add(delta);
|
|
layoutMeasure.AdvaceInsertPos(delta);
|
|
|
|
// if this beat is the start of a new group of notes render them
|
|
if (beatInfo.beatStart == beat && beatItemIndex < beatInfo.items.Count)
|
|
{
|
|
var element = layoutMeasure.AddItem(beatInfo.items[beatItemIndex]);
|
|
|
|
switch (element.item.type)
|
|
{
|
|
case ABC.Item.Type.Note:
|
|
CreateNoteSprite(layout.voice.clef, element);
|
|
break;
|
|
|
|
case ABC.Item.Type.Chord:
|
|
CreateChordSprite(layout.voice.clef, element);
|
|
break;
|
|
|
|
case ABC.Item.Type.Rest:
|
|
CreateRestSprite(element);
|
|
break;
|
|
|
|
case ABC.Item.Type.MultiMeasureRest:
|
|
CreateMeasureRestSprite(element);
|
|
break;
|
|
}
|
|
|
|
gameObjectMap.Add(element.item.id, element.container);
|
|
itemMap.Add(element.container, element.item);
|
|
|
|
// position
|
|
var beatItem = layoutMeasure.elements[layoutMeasure.elements.Count - 1];
|
|
float alignPos = SetItemReferencePosition(beatItem, layoutMeasure, advanceAmount);
|
|
alignment = Mathf.Max(alignment, alignPos);
|
|
beatItem.container.transform.parent = layoutMeasure.container.transform;
|
|
}
|
|
else
|
|
{
|
|
layoutMeasure.AddItem(null);
|
|
layoutMeasure.spacers[layoutMeasure.spacers.Count - 1] += noteAdvance;
|
|
layoutMeasure.AdvaceInsertPos(noteAdvance);
|
|
}
|
|
}
|
|
|
|
// align
|
|
foreach (var layout in layouts)
|
|
{
|
|
var scoreLine = layout.scoreLines[lineNum];
|
|
var layoutMeasure = scoreLine.measures[measure];
|
|
var beatInfo = layoutMeasure.source.beats[layout.beatIndex];
|
|
if (beatInfo.beatStart == beat && beatItemIndex < beatInfo.items.Count)
|
|
{
|
|
var beatItem = layoutMeasure.elements[layoutMeasure.elements.Count - 1];
|
|
|
|
float alignPos = layoutMeasure.insertX - beatItem.totalWidth + beatItem.prefixAmount;
|
|
float delta = alignment - alignPos;
|
|
layoutMeasure.spacers[layoutMeasure.spacers.Count - 1] += delta;
|
|
layoutMeasure.AdvaceInsertPos(delta);
|
|
|
|
bool allItemsDone = beatItemIndex == beatInfo.items.Count - 1;
|
|
if (allItemsDone)
|
|
layout.beatIndex = Math.Min(layoutMeasure.source.beats.Count - 1, layout.beatIndex + 1);
|
|
else
|
|
more = true;
|
|
}
|
|
|
|
maxBeatX = Math.Max(maxBeatX, layoutMeasure.bounds.size.x);
|
|
}
|
|
|
|
beatItemIndex += 1;
|
|
advanceAmount = noteAdvance;
|
|
if (!more) break;
|
|
}
|
|
}
|
|
|
|
// after all items in the measure are placed, add the measure's bar
|
|
foreach (var layout in layouts)
|
|
{
|
|
var layoutMeasure = layout.scoreLines[lineNum].measures[measure];
|
|
|
|
var delta = maxBeatX - layoutMeasure.insertX;
|
|
layoutMeasure.spacers.Add(delta);
|
|
layoutMeasure.AdvaceInsertPos(delta);
|
|
|
|
var element = layoutMeasure.AddItem(layoutMeasure.source.bar);
|
|
CreateBarSprite(element);
|
|
SetItemReferencePosition(element, layoutMeasure, advanceAmount);
|
|
element.container.transform.parent = layoutMeasure.container.transform;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PositionScoreLine(int lineNum)
|
|
{
|
|
float startX = float.MinValue;
|
|
|
|
foreach (var layout in layouts)
|
|
{
|
|
var scoreLine = layout.scoreLines[lineNum];
|
|
scoreLine.container = new GameObject("ScoreLine");
|
|
LayoutStaff(scoreLine, layout.voice.clef);
|
|
|
|
startX = Mathf.Max(startX, scoreLine.insertX);
|
|
}
|
|
|
|
foreach (var layout in layouts)
|
|
{
|
|
var scoreLine = layout.scoreLines[lineNum];
|
|
scoreLine.AdvaceInsertPos(startX - scoreLine.insertX + staffPadding);
|
|
}
|
|
|
|
if (lineNum == 0)
|
|
{
|
|
foreach (var layout in layouts)
|
|
{
|
|
var scoreLine = layout.scoreLines[lineNum];
|
|
LayoutTimeSignature(scoreLine, layout.voice.initialTimeSignature);
|
|
}
|
|
}
|
|
|
|
var measureWidths = CalculateMeasureWidths(lineNum);
|
|
|
|
foreach (var layout in layouts)
|
|
{
|
|
var scoreLine = layout.scoreLines[lineNum];
|
|
for (int i = 0; i < scoreLine.measures.Count; i++)
|
|
{
|
|
var measure = scoreLine.measures[i];
|
|
|
|
Bounds actualMeasureBounds;
|
|
if (measure.source.isRest)
|
|
actualMeasureBounds = PositionMeasureRest(measure, measureWidths[i]);
|
|
else
|
|
actualMeasureBounds = PositionMeasureItems(measure, measureWidths[i]);
|
|
|
|
measure.container.transform.parent = scoreLine.container.transform;
|
|
measure.container.transform.localPosition = scoreLine.insertPos;
|
|
scoreLine.EncapsulateAppendedBounds(actualMeasureBounds);
|
|
}
|
|
}
|
|
|
|
FinalizeScoreLine(lineNum);
|
|
}
|
|
|
|
float[] CalculateMeasureWidths(int lineNum)
|
|
{
|
|
var scoreLine = layouts[0].scoreLines[lineNum];
|
|
float[] measureWidths = new float[scoreLine.measures.Count];
|
|
float referenceWidth = 0.0f;
|
|
float allocatedSpace = horizontalMax - scoreLine.insertX;
|
|
for (int i = 0; i < scoreLine.measures.Count; i++)
|
|
{
|
|
measureWidths[i] = scoreLine.measures[i].insertX;
|
|
referenceWidth += measureWidths[i];
|
|
}
|
|
|
|
for (int i = 0; i < scoreLine.measures.Count; i++)
|
|
measureWidths[i] = (measureWidths[i] / referenceWidth) * allocatedSpace;
|
|
|
|
return measureWidths;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the position of a full measure rest such that it is centered in the available space.
|
|
/// Positions the bar first, then centers the rest in the remaining space.
|
|
/// </summary>
|
|
Bounds PositionMeasureRest(VoiceLayout.ScoreLine.Measure measure, float allocatedWidth)
|
|
{
|
|
var bar = measure.elements[measure.elements.Count - 1];
|
|
float barX = allocatedWidth - bar.totalWidth;
|
|
bar.container.transform.localPosition = new Vector3(barX, 0.0f, 0.0f);
|
|
|
|
var rest = measure.elements[0];
|
|
var measureCenter = barX / 2.0f;
|
|
var posX = measureCenter - rest.info.totalBounding.size.x / 2.0f;
|
|
rest.container.transform.localPosition = new Vector3(posX, 0.0f, 0.0f);
|
|
|
|
Bounds actualBounds = new Bounds(Vector3.zero, Vector3.zero);
|
|
actualBounds.Encapsulate(new Vector3(allocatedWidth, 0.0f, 0.0f));
|
|
|
|
return actualBounds;
|
|
}
|
|
|
|
Bounds PositionMeasureItems(VoiceLayout.ScoreLine.Measure measure, float actualmeasureWidth)
|
|
{
|
|
Bounds actualBounds = new Bounds(Vector3.zero, Vector3.zero);
|
|
List<Vector3> beamVertices = null;
|
|
|
|
float positionX = 0.0f;
|
|
float deltaWidth = actualmeasureWidth - measure.minWidth;
|
|
float deltaWidth2 = actualmeasureWidth - measure.insertX;
|
|
float spacerAdjust = deltaWidth / (measure.elements.Count - 1);
|
|
|
|
for (int i = 0; i < measure.elements.Count; i++)
|
|
{
|
|
var item = measure.elements[i];
|
|
positionX += measure.spacers[i];
|
|
if (i > 0)
|
|
positionX += spacerAdjust;
|
|
|
|
if (item.item == null)
|
|
continue;
|
|
|
|
Vector3 insertPos = new Vector3(positionX, 0.0f, 0.0f);
|
|
item.container.transform.localPosition = insertPos;
|
|
positionX += item.totalWidth;
|
|
|
|
var actualNoteInfo = item.OffsetNoteInfo(insertPos);
|
|
actualBounds.Encapsulate(actualNoteInfo.totalBounding);
|
|
|
|
var duration = item.item as ABC.Duration;
|
|
if (duration != null && beams.TryGetValue(duration.beam, out Beam beam))
|
|
{
|
|
beam.Update(actualNoteInfo.rootBounding);
|
|
|
|
if (beam.isReadyToCreate)
|
|
{
|
|
if (beam.type == Beam.Type.Angle)
|
|
{
|
|
if (beamVertices == null)
|
|
beamVertices = new List<Vector3>();
|
|
|
|
beam.CreateAngledBeam(beamVertices);
|
|
}
|
|
else
|
|
{
|
|
beam.CreateBasicBeam(cache, measure.container);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (beamVertices != null)
|
|
Beam.CreateMesh(beamVertices, NoteMaterial, measure.container);
|
|
|
|
return actualBounds;
|
|
}
|
|
|
|
class StafflineHeight
|
|
{
|
|
public readonly float min, max;
|
|
public StafflineHeight(float min, float max)
|
|
{
|
|
this.min = min;
|
|
this.max = max;
|
|
}
|
|
|
|
public float val { get { return max - min; } }
|
|
}
|
|
private static readonly Dictionary<ABC.Clef, StafflineHeight> baseStaffValues = new Dictionary<ABC.Clef, StafflineHeight>()
|
|
{
|
|
{ ABC.Clef.Treble, new StafflineHeight(-0.9f, 3.0f) },
|
|
{ ABC.Clef.Bass, new StafflineHeight(0.0f, 2.3f) },
|
|
};
|
|
|
|
float CalculateScoreHeight()
|
|
{
|
|
float height = 0.0f;
|
|
|
|
for (int i = 0; i < layouts[0].scoreLines.Count; i++)
|
|
{
|
|
//compute height of this score line
|
|
for (int l = 0; l < layouts.Count; l++)
|
|
{
|
|
var baseValues = baseStaffValues[layouts[l].voice.clef];
|
|
float min = baseValues.min;
|
|
float max = baseValues.max;
|
|
|
|
foreach (var measure in layouts[l].scoreLines[i].measures)
|
|
{
|
|
min = Mathf.Min(min, measure.bounds.min.y);
|
|
max = Mathf.Max(max, measure.bounds.max.y);
|
|
}
|
|
|
|
var staffSpacer = l == layouts.Count - 1 ? staffLineMargin : staffLinePadding;
|
|
height += (max - min) + staffSpacer;
|
|
}
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
void PrepareScoreLines()
|
|
{
|
|
if (multilineLayout)
|
|
{
|
|
for (int i = 0; i < layouts[0].scoreLines.Count; i++)
|
|
LayoutScoreLine(i);
|
|
}
|
|
else
|
|
{
|
|
LayoutScoreLine(0);
|
|
PartitionScoreLine();
|
|
}
|
|
|
|
float calculatedHeight = CalculateScoreHeight();
|
|
var rectSize = rectTransform.sizeDelta;
|
|
|
|
if (calculatedHeight > rectSize.y)
|
|
layoutScale = rectSize.y / calculatedHeight;
|
|
|
|
horizontalMax = rectSize.x * 1.0f / layoutScale;
|
|
staffOffset = new Vector2(-rectSize.x / 2.0f, 0);
|
|
}
|
|
|
|
void LayoutTune()
|
|
{
|
|
if (tune == null) return;
|
|
|
|
notes = new NoteCreator(cache);
|
|
cache.color = color;
|
|
|
|
timeSignature = GetTimeSignature();
|
|
|
|
Vector3 scale = this.gameObject.transform.localScale;
|
|
this.gameObject.transform.localScale = Vector3.one;
|
|
|
|
beams = Beam.CreateBeams(tune);
|
|
SetupVoiceLayouts();
|
|
|
|
scoreContainer = new GameObject("Score");
|
|
scoreContainer.transform.parent = this.transform;
|
|
scoreContainer.transform.localPosition = Vector3.zero;
|
|
|
|
PrepareScoreLines();
|
|
|
|
for (int i = 0; i < layouts[0].scoreLines.Count; i++)
|
|
PositionScoreLine(i);
|
|
|
|
scoreContainer.transform.localScale = new Vector3(layoutScale, layoutScale, layoutScale);
|
|
this.gameObject.transform.localScale = scale;
|
|
|
|
}
|
|
|
|
/// <summary> Breaks up a single score line into multiple lines based on the horizontal max</summary>
|
|
void PartitionScoreLine()
|
|
{
|
|
// save off the scorelines that were laid out
|
|
int measureCount = layouts[0].scoreLines[0].measures.Count;
|
|
var scoreLines = new List<VoiceLayout.ScoreLine.Measure>[layouts.Count];
|
|
for (int i = 0; i < scoreLines.Length; i++)
|
|
{
|
|
scoreLines[i] = layouts[i].scoreLines[0].measures;
|
|
layouts[i].scoreLines.Clear();
|
|
layouts[i].scoreLines.Add(new VoiceLayout.ScoreLine());
|
|
}
|
|
|
|
float currentWidth = 0.0f;
|
|
|
|
for (int measureIndex = 0; measureIndex < measureCount; measureIndex++)
|
|
{
|
|
float measureWidth = float.MinValue;
|
|
foreach (var scoreLine in scoreLines)
|
|
measureWidth = Mathf.Max(measureWidth, scoreLine[measureIndex].insertX);
|
|
|
|
// ensure that the measure of each scoreline will fit on the current line.
|
|
// If it wont fit make a new line
|
|
foreach (var scoreLine in scoreLines)
|
|
{
|
|
if (currentWidth + scoreLine[measureIndex].insertX > horizontalMax)
|
|
{
|
|
foreach (var layout in layouts)
|
|
layout.scoreLines.Add(new VoiceLayout.ScoreLine());
|
|
|
|
currentWidth = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Add the current measure to the last scoreline
|
|
for (int i = 0; i < scoreLines.Length; i++)
|
|
{
|
|
var scoreLine = layouts[i].scoreLines[layouts[i].scoreLines.Count - 1];
|
|
scoreLine.measures.Add(scoreLines[i][measureIndex]);
|
|
currentWidth += measureWidth;
|
|
}
|
|
}
|
|
}
|
|
|
|
TimeSignature GetTimeSignature()
|
|
{
|
|
TimeSignature result = null;
|
|
|
|
for (int i = 0; i < tune.voices.Count; i++)
|
|
{
|
|
var initialTimeSignature = tune.voices[i].initialTimeSignature;
|
|
if (initialTimeSignature == string.Empty)
|
|
throw new BeatAlignmentException($"Voice {i} does not initially declare a time signature.");
|
|
|
|
var timeSignature = TimeSignature.Parse(initialTimeSignature);
|
|
|
|
if (result == null)
|
|
result = timeSignature;
|
|
else if (!timeSignature.Equals(result))
|
|
throw new LayoutException("All voices should have the same time signature");
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Calculates the final size of the staff and positions it correctly relative to the container.</summary>
|
|
void FinalizeScoreLine(int lineNum)
|
|
{
|
|
//foreach (var layout in layouts)
|
|
for (int i = 0; i < layouts.Count; i++)
|
|
{
|
|
var scoreLine = layouts[i].scoreLines[lineNum];
|
|
AdjustStaffScale(scoreLine);
|
|
|
|
scoreLine.container.transform.parent = scoreContainer.transform;
|
|
scoreLine.container.transform.localPosition = new Vector3(staffOffset.x * (1.0f / layoutScale), staffOffset.y - (scoreLine.bounds.max.y), 0.0f);
|
|
var staffSpacer = i == layouts.Count - 1 ? staffLineMargin : staffLinePadding;
|
|
staffOffset.y -= (scoreLine.bounds.size.y + staffSpacer);
|
|
}
|
|
|
|
RenderConnectorBar(lineNum);
|
|
}
|
|
|
|
void RenderConnectorBar(int lineNum)
|
|
{
|
|
var barSprite = cache.GetSpriteObject("Bar_Line");
|
|
barSprite.transform.parent = scoreContainer.transform;
|
|
|
|
var topScoreLine = layouts[0].scoreLines[lineNum];
|
|
var bottomScoreLine = layouts[layouts.Count - 1].scoreLines[lineNum];
|
|
|
|
var barPos = bottomScoreLine.container.transform.localPosition;
|
|
var topPos = topScoreLine.container.transform.localPosition.y + staffHeight;
|
|
|
|
var targetHeight = topPos - barPos.y;
|
|
var scale = targetHeight / barSprite.bounds.size.y;
|
|
|
|
barSprite.transform.localPosition = barPos;
|
|
barSprite.transform.localScale = new Vector3(1.0f, scale, 1.0f);
|
|
}
|
|
|
|
void LayoutStaff(VoiceLayout.ScoreLine scoreLine, ABC.Clef clef)
|
|
{
|
|
var staffSprite = cache.GetSpriteObject("Staff");
|
|
staffSprite.transform.parent = scoreLine.container.transform;
|
|
staffSprite.transform.localPosition = scoreLine.insertPos;
|
|
scoreLine.bounds = staffSprite.bounds;
|
|
|
|
scoreLine.AdvaceInsertPos(staffPadding);
|
|
var clefSprite = cache.GetSpriteObject($"Clef_{clef}");
|
|
clefSprite.transform.parent = scoreLine.container.transform;
|
|
clefSprite.transform.localPosition = scoreLine.insertPos;
|
|
scoreLine.bounds.Encapsulate(clefSprite.bounds);
|
|
scoreLine.AdvaceInsertPos(staffPadding);
|
|
}
|
|
|
|
void LayoutTimeSignature(VoiceLayout.ScoreLine scoreLine, string timeSignature)
|
|
{
|
|
var container = new GameObject("Time Signature");
|
|
container.transform.parent = scoreLine.container.transform;
|
|
|
|
var timeSignatureInfo = notes.CreateTimeSignature(timeSignature, container);
|
|
|
|
container.transform.localPosition = scoreLine.insertPos;
|
|
scoreLine.EncapsulateAppendedBounds(timeSignatureInfo.totalBounding);
|
|
}
|
|
|
|
void AdjustStaffScale(VoiceLayout.ScoreLine scoreLine)
|
|
{
|
|
var currentStaff = scoreLine.container.transform.GetChild(0);
|
|
var currentWidth = currentStaff.GetComponent<SpriteRenderer>().bounds.size.x;
|
|
var scaleX = scoreLine.insertX / currentWidth;
|
|
currentStaff.transform.localScale = new Vector3(scaleX, 1.0f, 1.0f);
|
|
}
|
|
|
|
float SetItemReferencePosition(VoiceLayout.ScoreLine.Element element, VoiceLayout.ScoreLine.Measure measure, float advanceAmount)
|
|
{
|
|
float spacer = Mathf.Max(minimumAdavance, advanceAmount - element.prefixAmount);
|
|
float alignmentPos = measure.insertX + spacer + element.prefixAmount;
|
|
|
|
measure.spacers[measure.spacers.Count - 1] += spacer;
|
|
measure.AdvaceInsertPos(spacer);
|
|
|
|
measure.EncapsulateAppendedBounds(element.info.totalBounding);
|
|
|
|
return alignmentPos;
|
|
}
|
|
|
|
void CreateChordSprite(ABC.Clef clef, VoiceLayout.ScoreLine.Element element)
|
|
{
|
|
var chordItem = element.item as ABC.Chord;
|
|
element.container = new GameObject("Chord");
|
|
|
|
tune.decorations.TryGetValue(chordItem.id, out var decorations);
|
|
|
|
NoteInfo chordInfo;
|
|
if (beams.TryGetValue(chordItem.beam, out Beam beam))
|
|
chordInfo = notes.CreateChord(chordItem, beam, decorations, element.container);
|
|
else
|
|
chordInfo = notes.CreateChord(chordItem, clef, decorations, element.container);
|
|
|
|
element.info = chordInfo;
|
|
}
|
|
|
|
void CreateNoteSprite(ABC.Clef clef, VoiceLayout.ScoreLine.Element element)
|
|
{
|
|
var noteItem = element.item as ABC.Note;
|
|
element.container = new GameObject("Note");
|
|
|
|
tune.decorations.TryGetValue(noteItem.id, out var decorations);
|
|
|
|
NoteInfo noteInfo;
|
|
if (beams.TryGetValue(noteItem.beam, out Beam beam))
|
|
noteInfo = notes.CreateNote(noteItem, beam, decorations, element.container);
|
|
else
|
|
noteInfo = notes.CreateNote(noteItem, clef, decorations, element.container);
|
|
|
|
element.info = noteInfo;
|
|
}
|
|
|
|
void CreateRestSprite(VoiceLayout.ScoreLine.Element element)
|
|
{
|
|
var restItem = element.item as ABC.Rest;
|
|
element.container = new GameObject("Rest");
|
|
element.info = notes.CreateRest(restItem, element.container);
|
|
}
|
|
|
|
void CreateBarSprite(VoiceLayout.ScoreLine.Element element)
|
|
{
|
|
var barItem = element.item as ABC.Bar;
|
|
element.container = new GameObject("Bar");
|
|
element.info = notes.CreateBar(barItem, element.container);
|
|
}
|
|
|
|
void CreateMeasureRestSprite(VoiceLayout.ScoreLine.Element element)
|
|
{
|
|
var measureRest = element.item as ABC.MultiMeasureRest;
|
|
element.container = new GameObject("Rest");
|
|
element.info = notes.CreateMeasureRest(measureRest, element.container);
|
|
}
|
|
}
|
|
}
|
|
|