Skip to content

Commit

Permalink
[director] Added variant BPM support.
Browse files Browse the repository at this point in the history
  • Loading branch information
hozuki committed Oct 22, 2016
1 parent 5a72958 commit a7de7f2
Show file tree
Hide file tree
Showing 42 changed files with 1,250 additions and 332 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Newtonsoft.Json;
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace DereTore.Applications.StarlightDirector.Entities {
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public sealed class BarParams {

[Obsolete("This property is not used since v0.5.0 alpha. Please consider Note with Note.Type == VariantBpm instead.")]
public double? UserDefinedBpm { get; internal set; }

public int? UserDefinedGridPerSignature { get; internal set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@
<Compile Include="EntityID.cs" />
<Compile Include="Extensions\BarExtensions.cs" />
<Compile Include="Extensions\NoteExtensions.cs" />
<Compile Include="Extensions\ScoreExtensions.cs" />
<Compile Include="FlickGroupModificationResult.cs" />
<Compile Include="Gaming\MusicAttribute.cs" />
<Compile Include="Note.cs" />
<Compile Include="NoteExtraParams.cs" />
<Compile Include="NoteFlickType.cs" />
<Compile Include="NoteIDs.cs" />
<Compile Include="NotePosition.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
namespace DereTore.Applications.StarlightDirector.Entities.Extensions {
using System;
using System.Collections.Generic;
using System.Linq;

namespace DereTore.Applications.StarlightDirector.Entities.Extensions {
public static class BarExtensions {

public static double GetStartTime(this Bar bar) {
Expand All @@ -12,17 +16,47 @@ public static double GetStartTime(this Bar bar) {
if (i >= index) {
break;
}
time += b.GetLength();
time += b.GetTimeLength();
++i;
}
return time;
}

public static double GetLength(this Bar bar) {
var bpm = bar.GetActualBpm();
public static double GetTimeLength(this Bar bar) {
var startBpm = bar.GetStartBpm();
var signature = bar.GetActualSignature();
var seconds = DirectorHelper.BpmToSeconds(bpm);
return seconds * signature;
if (bar.Notes.All(note => note.Type != NoteType.VariantBpm)) {
// If BPM doesn't change in this measure, things are simple.
var seconds = DirectorHelper.BpmToSeconds(startBpm);
return seconds * signature;
}
// If not, we have to do some math.
var bpmPairs = new List<Tuple<int, double>> {
new Tuple<int, double>(0, startBpm)
};
var lastBpm = startBpm;
foreach (var note in bar.Notes.Where(note => note.Type == NoteType.VariantBpm)) {
var newBpm = note.ExtraParams.NewBpm;
bpmPairs.Add(new Tuple<int, double>(note.IndexInGrid, newBpm));
lastBpm = newBpm;
}
var totalGridCount = GetTotalGridCount(bar);
bpmPairs.Add(new Tuple<int, double>(totalGridCount, lastBpm));
bpmPairs.Sort((t1, t2) => t1.Item1.CompareTo(t2.Item1));
var length = 0d;
Tuple<int, double> lastBpmPair = null;
foreach (var bpmPair in bpmPairs) {
if (lastBpmPair == null) {
lastBpmPair = bpmPair;
continue;
}
var deltaGridCount = bpmPair.Item1 - lastBpmPair.Item1;
var seconds = DirectorHelper.BpmToSeconds(bpmPair.Item2);
var timeSlice = seconds * signature * deltaGridCount / totalGridCount;
length += timeSlice;
lastBpmPair = bpmPair;
}
return length;
}

public static int GetActualSignature(this Bar bar) {
Expand All @@ -37,8 +71,19 @@ public static int GetTotalGridCount(this Bar bar) {
return GetActualSignature(bar) * GetActualGridPerSignature(bar);
}

public static double GetActualBpm(this Bar bar) {
return bar.Params?.UserDefinedBpm ?? bar.Score.Project.Settings.GlobalBpm;
public static double GetStartBpm(this Bar bar) {
var b = bar;
while (b.Index > 0) {
b = b.Score.Bars[b.Index - 1];
if (b.Notes.All(note => note.Type != NoteType.VariantBpm)) {
continue;
}
var list = new List<Note>(b.Notes);
list.Sort(Note.TimingComparison);
var bpmNote = list.FindLast(note => note.Type == NoteType.VariantBpm);
return bpmNote.ExtraParams.NewBpm;
}
return bar.Score.Project.Settings.GlobalBpm;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public static double GetHitTiming(this Note note) {
var barStartTime = bar.GetStartTime();
var signature = bar.GetActualSignature();
var gridCountInBar = bar.GetActualGridPerSignature();
var barLength = bar.GetLength();
var barLength = bar.GetTimeLength();
return barStartTime + barLength * (note.IndexInGrid / (double)(signature * gridCountInBar));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System.Linq;

namespace DereTore.Applications.StarlightDirector.Entities.Extensions {
public static class ScoreExtensions {

public static CompiledScore Compile(this Score score) {
var compiledScore = new CompiledScore();
CompileTo(score, compiledScore);
return compiledScore;
}

public static void CompileTo(this Score score, CompiledScore compiledScore) {
var settings = score.Project.Settings;
FlickGroupIDGenerator.Reset();
var compiledNotes = compiledScore.Notes;
compiledNotes.Clear();
var barTimeStart = settings.StartTimeOffset;

// Clear the GroupID caches.
foreach (var bar in score.Bars) {
foreach (var note in bar.Notes) {
note.GroupID = EntityID.Invalid;
}
}

// We can also use Note.GetNoteHitTiming(), but that requires massive loops. Therefore we manually expand them here.
foreach (var bar in score.Bars) {
var anyVariantBpm = bar.Notes.Any(note => note.Type == NoteType.VariantBpm);
double barTimeLength;
if (anyVariantBpm) {
barTimeLength = CompileBarConstantBpm(bar, barTimeStart, compiledNotes);
} else {
barTimeLength = CompileBarVariantBpm(bar, barTimeStart, compiledNotes);
}
barTimeStart += barTimeLength;
}

// The normal gaming notes.
compiledNotes.Sort(CompiledNote.TimingComparison);
var i = 3;
foreach (var compiledNote in compiledNotes) {
compiledNote.ID = i++;
}

// Special notes are added to their destined positions.
var totalNoteCount = compiledNotes.Count;
var scoreInfoNote = new CompiledNote {
ID = 1,
Type = NoteType.NoteCount,
FlickType = totalNoteCount
};
var songStartNote = new CompiledNote {
ID = 2,
Type = NoteType.MusicStart
};
compiledNotes.Insert(0, scoreInfoNote);
compiledNotes.Insert(1, songStartNote);
var songEndNote = new CompiledNote {
ID = i++,
Type = NoteType.MusicEnd,
HitTiming = barTimeStart
};
compiledNotes.Add(songEndNote);
}

private static double CompileBarConstantBpm(Bar bar, double timeStart, InternalList<CompiledNote> compiledNotes) {
var barTimeLength = bar.GetTimeLength();
var totalGridCount = bar.GetTotalGridCount();
// Sorting is for flick group generation. We have to assure that the start of each group is
// processed first, at least in the group which it is in.
bar.Notes.Sort(Note.TimingComparison);
foreach (var note in bar.Notes) {
var compiledNote = new CompiledNote();
compiledNotes.Add(compiledNote);
SetCommonNoteProperties(note, compiledNote);
CalculateGroupID(note, compiledNote);
// Timing for constant BPM
compiledNote.HitTiming = timeStart + barTimeLength * note.IndexInGrid / totalGridCount;
}
return barTimeLength;
}

private static double CompileBarVariantBpm(Bar bar, double timeStart, InternalList<CompiledNote> compiledNotes) {
var startBpm = bar.GetStartBpm();
var signature = bar.GetActualSignature();
var totalGridCount = bar.GetTotalGridCount();
var barTimeLength = bar.GetTimeLength();
// This sorting is for flick group generation AND Variant BPM notes. See Note.TimingComparison for more details.
bar.Notes.Sort(Note.TimingComparison);
var lastBpm = startBpm;
var lastVBIndex = 0;
var lastVBTiming = 0d;

foreach (var note in bar.Notes) {
var deltaGridCount = note.IndexInGrid - lastVBIndex;
var timePerBeat = DirectorHelper.BpmToSeconds(lastBpm);
if (note.Type == NoteType.VariantBpm) {
lastVBTiming = lastVBTiming + timePerBeat * signature * deltaGridCount / totalGridCount;
lastBpm = note.ExtraParams.NewBpm;
lastVBIndex = note.IndexInGrid;
continue;
}
var compiledNote = new CompiledNote();
compiledNotes.Add(compiledNote);
SetCommonNoteProperties(note, compiledNote);
CalculateGroupID(note, compiledNote);

// Yeah for the maths.
var noteTimingInBar = lastVBTiming + timePerBeat * signature * deltaGridCount / totalGridCount;
compiledNote.HitTiming = timeStart + noteTimingInBar;
}
return barTimeLength;
}

private static void SetCommonNoteProperties(Note note, CompiledNote compiledNote) {
compiledNote.Type = note.Type;
compiledNote.StartPosition = note.StartPosition;
compiledNote.FinishPosition = note.FinishPosition;
compiledNote.FlickType = (int)note.FlickType;
compiledNote.IsSync = note.IsSync;
}

private static void CalculateGroupID(Note note, CompiledNote compiledNote) {
if (note.GroupID != EntityID.Invalid) {
compiledNote.FlickGroupID = note.GroupID;
} else {
int groupID;
FlickGroupModificationResult result;
Note groupStart;
if (!note.TryGetFlickGroupID(out result, out groupID, out groupStart)) {
// No need to set a group ID. E.g. the note is not a flick note.
return;
}
switch (result) {
case FlickGroupModificationResult.Reused:
note.GroupID = compiledNote.FlickGroupID = groupID;
break;
case FlickGroupModificationResult.CreationPending:
groupID = FlickGroupIDGenerator.Next();
groupStart.GroupID = note.GroupID = compiledNote.FlickGroupID = groupID;
break;
default:
break;
}
}
}

private static readonly IntegerIDGenerator FlickGroupIDGenerator = new IntegerIDGenerator(1);

}
}
41 changes: 37 additions & 4 deletions DereTore.Applications.StarlightDirector.Entities/Note.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,14 @@ public Note HoldTarget {

public bool IsTap => Type == NoteType.TapOrFlick && FlickType == NoteFlickType.Tap;

public bool IsGamingNote => Type == NoteType.TapOrFlick || Type == NoteType.Hold;
public bool IsGamingNote => IsTypeGaming(Type);

public bool IsSpecialNote => IsTypeSpecial(Type);

public NoteExtraParams ExtraParams {
get { return (NoteExtraParams)GetValue(ExtraParamsProperty); }
set { SetValue(ExtraParamsProperty, value); }
}

public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(nameof(Type), typeof(NoteType), typeof(Note),
new PropertyMetadata(NoteType.Invalid, OnTypeChanged));
Expand All @@ -193,6 +200,9 @@ public Note HoldTarget {
public static readonly DependencyProperty FinishPositionProperty = DependencyProperty.Register(nameof(FinishPosition), typeof(NotePosition), typeof(Note),
new PropertyMetadata(NotePosition.Nowhere));

public static readonly DependencyProperty ExtraParamsProperty = DependencyProperty.Register(nameof(ExtraParams), typeof(NoteExtraParams), typeof(Note),
new PropertyMetadata(null));

public static readonly Comparison<Note> TimingComparison = (x, y) => {
if (x == null) {
throw new ArgumentNullException(nameof(x));
Expand All @@ -203,11 +213,16 @@ public Note HoldTarget {
if (x.Equals(y)) {
return 0;
}
if (x.Bar == y.Bar) {
return x.IndexInGrid.CompareTo(y.IndexInGrid);
} else {
if (x.Bar != y.Bar) {
return x.Bar.Index.CompareTo(y.Bar.Index);
}
var r = x.IndexInGrid.CompareTo(y.IndexInGrid);
if (r == 0 && x.Type != y.Type && (x.Type == NoteType.VariantBpm || y.Type == NoteType.VariantBpm)) {
// The Variant BPM note is always placed at the end on the same grid line.
return x.Type == NoteType.VariantBpm ? 1 : -1;
} else {
return r;
}
};

public int CompareTo(Note other) {
Expand All @@ -231,6 +246,14 @@ public int CompareTo(Note other) {
return TimingComparison(left, right) < 0;
}

public static bool IsTypeGaming(NoteType type) {
return type == NoteType.TapOrFlick || type == NoteType.Hold;
}

public static bool IsTypeSpecial(NoteType type) {
return type == NoteType.VariantBpm;
}

public static void ConnectSync(Note n1, Note n2) {
if (n1 != null) {
n1.SyncTarget = n2;
Expand Down Expand Up @@ -334,6 +357,16 @@ internal void Reset() {
HoldTarget = null;
}

internal void SetSpecialType(NoteType type) {
switch (type) {
case NoteType.VariantBpm:
break;
default:
throw new ArgumentOutOfRangeException(nameof(type));
}
Type = type;
}

private static void OnTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var note = (Note)obj;
note.IsFlick = note.IsFlickInternal();
Expand Down
Loading

0 comments on commit a7de7f2

Please sign in to comment.