Files
Quentin Bray 6ffa63b923 Apply R# clean-up for reliability (#877)
* Apply R# clean-up for reliability

- Migrate to NUnit4 Assert.That
- Update dependencise versions
- Fix LangVersion to 9.0 (until net core migration)
- Reformat code
- Apply 'var' style (easier for refactoring)
- Remove redundant parentheses
- Add braces on every condition blocks (1 line)
- Uses new null checking patterns
- Uses AutoProperties when possible
- Uses string scalar type instead of class
- Clean usings
- Add .orig files in git ignore
- Rename some properties to respect defined conventions and US format definition
	- ID -> Id
	- BPM -> Bpm
	- Finish -> End

* Update R# settings according to the clean-up made
2026-06-22 19:52:06 +02:00

331 lines
9.6 KiB
C#

#region license
// This file is part of Vocaluxe.
//
// Vocaluxe is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vocaluxe is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
namespace VocaluxeLib.Songs
{
public class CVoice
{
private readonly List<CSongLine> _Lines = new List<CSongLine>();
public CVoice() { }
public CVoice(CVoice voice)
{
foreach (var line in voice._Lines)
{
_Lines.Add(new CSongLine(line));
}
}
public CSongLine[] Lines
{
get { return _Lines.ToArray(); }
}
public int NumLines
{
get { return _Lines.Count; }
}
/// <summary>
/// Total song length in beats
/// </summary>
public int Length
{
get
{
if (_Lines.Count == 0)
{
return 0;
}
var result = _Lines[_Lines.Count - 1].EndBeat - _Lines[0].StartBeat;
return result > 0 ? result : 0;
}
}
public int Points
{
get { return _Lines.Sum(line => line.Points); }
}
public int NumLinesWithPoints
{
get { return _Lines.Count(line => line.Points > 0f); }
}
#region Methods
/// <summary>
/// Find last line with StartBeat
/// </summary>
/// <param name="beat"></param>
/// <returns></returns>
public int FindPreviousLine(int beat)
{
//If no line -> No previous line
if (_Lines.Count == 0)
{
return -1;
}
var start = 0;
var end = _Lines.Count - 1;
//Ensure that start.StartBeat<=Beat && end.StartBeat>Beat
if (_Lines[0].StartBeat > beat)
{
return -1;
}
if (_Lines[end].StartBeat <= beat)
{
return end;
}
//Binary search
while (end - start > 1)
{
var mid = (start + end) / 2;
if (_Lines[mid].StartBeat <= beat)
{
start = mid;
}
else
{
end = mid;
}
}
return start;
}
public bool AddLine(CSongLine line, bool updateTimings = true)
{
if (_Lines.Count == 0)
{
_Lines.Add(line);
}
else
{
var insPos = FindPreviousLine(line.StartBeat);
//Check if previous line ends before this one
if (insPos >= 0 && _Lines[insPos].LastNoteBeat > line.StartBeat)
{
return false;
}
//Check if next line starts before this one ends
if (insPos + 1 < _Lines.Count && _Lines[insPos + 1].FirstNoteBeat < line.LastNoteBeat)
{
return false;
}
_Lines.Insert(insPos + 1, line);
}
if (updateTimings)
{
UpdateTimings();
}
return true;
}
public bool AddLine(int startBeat)
{
var insPos = FindPreviousLine(startBeat);
//Check for actual notes (startbeat may be lower than first note)
while (insPos >= 0 && _Lines[insPos].NoteCount > 0 && _Lines[insPos].FirstNoteBeat > startBeat)
{
insPos--;
}
var line = new CSongLine { StartBeat = startBeat };
if (insPos >= 0)
{
var prevLine = _Lines[insPos];
//We already have a line break here
if (prevLine.StartBeat == startBeat && (prevLine.NoteCount == 0 || prevLine.FirstNoteBeat == startBeat))
{
return false;
}
//Maybe we have to split the previous line
while (prevLine.NoteCount > 0 && prevLine.LastNote.StartBeat >= startBeat)
{
//throw new Exception("This should not happen on song loading!");
line.AddNote(prevLine.LastNote);
prevLine.DeleteNote(prevLine.NoteCount - 1);
}
}
_Lines.Insert(insPos + 1, line);
return true;
}
public bool DeleteLine(int index)
{
if (_Lines.Count > index)
{
_Lines.RemoveAt(index);
UpdateTimings();
return true;
}
return false;
}
public void DeleteAllLines()
{
_Lines.Clear();
}
public bool AddNote(CSongNote note, bool updateTimings = true)
{
var lineIndex = FindPreviousLine(note.StartBeat);
if (lineIndex + 1 < _Lines.Count && _Lines[lineIndex + 1].FirstNoteBeat < note.EndBeat) //First note in next line starts before this one ends
{
return false;
}
if (lineIndex < 0)
{
//Note is before ALL lines
if (_Lines.Count > 0)
{
//Add to first line
if (!_Lines[0].AddNote(note))
{
return false;
}
}
else
{
var line = new CSongLine();
line.AddNote(note);
_Lines.Add(line);
}
}
else
{
if (!_Lines[lineIndex].AddNote(note))
{
return false;
}
}
if (updateTimings)
{
UpdateTimings();
}
return true;
}
/*Check this first for consistency (next line might be affected)
* public bool InsertNote(CSongNote note, int lineIndex, bool updateTimings = false)
{
if (_Lines.Count > lineIndex && _Lines[lineIndex].StartBeat <= note.StartBeat)
{
if (!_Lines[lineIndex].AddNote(note))
return false;
if (updateTimings)
UpdateTimings();
return true;
}
return false;
}*/
public void UpdateTimings()
{
if (_Lines.Count == 0)
{
return;
}
_Lines[0].StartBeat = -10000;
for (var i = 1; i < _Lines.Count; i++)
{
var lastNote = _Lines[i - 1].LastNote;
var firstNote = _Lines[i].FirstNote;
if (lastNote != null && firstNote != null)
{
var min = lastNote.EndBeat;
var max = firstNote.StartBeat;
int s;
switch (max - min)
{
case 0:
s = max;
break;
case 1:
s = max;
break;
case 2:
s = max - 1;
break;
case 3:
s = max - 2;
break;
default:
s = min + 2;
break;
}
_Lines[i].StartBeat = s;
_Lines[i - 1].EndBeat = min;
}
else if (firstNote != null)
{
_Lines[i - 1].EndBeat = Math.Min(_Lines[i - 1].StartBeat, firstNote.StartBeat - 2);
_Lines[i].StartBeat = Math.Max(_Lines[i - 1].EndBeat, firstNote.StartBeat - 2);
}
else if (lastNote != null)
{
_Lines[i - 1].EndBeat = lastNote.EndBeat;
_Lines[i].StartBeat = Math.Max(lastNote.EndBeat, _Lines[i].StartBeat); //Prefer current setting
}
else
{
//No note
_Lines[i - 1].EndBeat = _Lines[i - 1].StartBeat; //Assume at least startbeats were set right
}
}
_Lines[_Lines.Count - 1].EndBeat = Math.Max(_Lines[_Lines.Count - 1].LastNoteBeat, _Lines[_Lines.Count - 1].StartBeat); //Use note or (when not set) start beat
}
public void SetMedley(int startBeat, int endBeat)
{
foreach (var line in _Lines)
{
line.SetMedley(startBeat, endBeat);
}
}
#endregion Methods
public int RemoveEmptyLines()
{
return _Lines.RemoveAll(line => line.NoteCount == 0);
}
}
}