public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { return(new SlamVolumeEvent() { Position = pos, Volume = reader.ReadSingleBE() }); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { return(new LaserApplicationEvent() { Position = pos, Application = (LaserApplication)reader.ReadUInt8() }); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { return(new SpinImpulseEvent() { Position = pos, Direction = (AngularDirection)reader.ReadUInt8() }); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { byte flags = reader.ReadUInt8(); var obj = new AnalogObject() { Position = pos, Duration = dur }; if ((flags & 0x01) != 0) { obj.RangeExtended = true; } obj.InitialValue = reader.ReadSingleBE(); obj.FinalValue = reader.ReadSingleBE(); obj.Shape = (CurveShape)reader.ReadUInt8(); switch (obj.Shape) { case CurveShape.Linear: break; case CurveShape.Cosine: case CurveShape.ThreePoint: obj.CurveA = reader.ReadSingleBE(); obj.CurveB = reader.ReadSingleBE(); break; } return(obj); }
public Entity AddEntity(HybridLabel lane, string entityType, tick_t position, tick_t duration) { var entity = (Entity)Activator.CreateInstance(Entity.GetEntityTypeById(entityType)); entity.Position = position; entity.Duration = duration; AddEntity(lane, entity); return(entity); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { var laserIndex = (LaserIndex)reader.ReadUInt8(); float gain = reader.ReadSingleBE(); return(new LaserFilterGainEvent() { Position = pos, LaserIndex = laserIndex, Gain = gain }); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { var evt = new SwingImpulseEvent() { Position = pos }; evt.Direction = (AngularDirection)reader.ReadUInt8(); evt.Amplitude = reader.ReadSingleBE(); return(evt); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { var evt = new LaserParamsEvent() { Position = pos }; evt.LaserIndex = (LaserIndex)reader.ReadUInt8(); evt.Params.Function = (LaserFunction)reader.ReadUInt8(); evt.Params.Scale = (LaserScale)reader.ReadUInt8(); return(evt); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { var evt = new WobbleImpulseEvent() { Position = pos }; evt.Direction = (LinearDirection)reader.ReadUInt8(); evt.Amplitude = reader.ReadSingleBE(); evt.Frequency = reader.ReadUInt16BE(); evt.Decay = (Decay)reader.ReadUInt8(); return(evt); }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { byte flags = reader.ReadUInt8(); bool hasSample = flags != 0; var obj = new ButtonObject() { Position = pos, Duration = dur }; if (hasSample) { obj.Sample = reader.ReadStringUTF8(); } return(obj); }
public ButtonJudge(Chart chart, HybridLabel label) : base(chart, label) { tick_t tickStep = (Chart.MaxBpm >= 255 ? 2.0 : 1.0) / (4 * 4); tick_t tickMargin = 2 * tickStep; foreach (var entity in chart[label]) { var button = (ButtonEntity)entity; if (button.IsInstant) { m_stateTicks.Add(new StateTick(button, button.AbsolutePosition, JudgeState.ChipAwaitPress)); m_scoreTicks.Add(new ScoreTick(button, button.AbsolutePosition, TickKind.Chip)); } else { m_stateTicks.Add(new StateTick(button, button.AbsolutePosition, JudgeState.HoldAwaitPress)); // end state is placed at the last score tick int numTicks = MathL.FloorToInt((double)(button.Duration - tickMargin) / (double)tickStep); if (numTicks <= 0) { m_scoreTicks.Add(new ScoreTick(button, button.AbsolutePosition + button.AbsoluteDuration / 2, TickKind.Hold)); m_stateTicks.Add(new StateTick(button, button.AbsolutePosition + button.AbsoluteDuration / 2, JudgeState.HoldAwaitRelease)); } else { for (int i = 0; i < numTicks; i++) { tick_t pos = button.Position + tickMargin + tickStep * i; m_scoreTicks.Add(new ScoreTick(button, chart.CalcTimeFromTick(pos), TickKind.Hold)); if (i == numTicks - 1) { m_stateTicks.Add(new StateTick(button, chart.CalcTimeFromTick(pos), JudgeState.HoldAwaitRelease)); } } } } } }
public override ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects) { var laserIndex = (LaserIndex)reader.ReadUInt8(); ushort effectID = reader.ReadUInt16BE(); EffectDef effect; if (effectID == ushort.MaxValue) { effect = null; } else { effect = effects[effectID]; } return(new LaserFilterKindEvent() { Position = pos, LaserIndex = laserIndex, Effect = effect }); }
protected override void ObjectEnteredJudgement(ChartObject obj) { if (AutoPlay && !obj.IsInstant) { m_ticks.Add(new Tick(obj, obj.AbsolutePosition, true, true)); } if (obj.IsInstant) { var chipTick = new Tick(obj, obj.AbsolutePosition, false); m_ticks.Add(chipTick); } else { tick_t step = (Chart.MaxBpm >= 255 ? 2.0 : 1.0) / (4 * 4); tick_t margin = 2 * step; int numTicks = MathL.FloorToInt((double)(obj.Duration - margin) / (double)step); if (numTicks == 0) { m_ticks.Add(new Tick(obj, obj.AbsolutePosition + obj.AbsoluteDuration / 2, true)); } else { tick_t pos = obj.Position + margin; for (int i = 0; i < numTicks; i++) { time_t timeAtTick = Chart.CalcTimeFromTick(pos + i * step); m_ticks.Add(new Tick(obj, timeAtTick, true)); } } } if (AutoPlay && !obj.IsInstant) { m_ticks.Add(new Tick(obj, obj.AbsoluteEndPosition, true, true)); } }
public TempButtonState(tick_t pos) { StartPosition = pos; }
public void RemoveEntityAtTick(HybridLabel lane, tick_t tick) => Chart[lane].Remove(Chart[lane].Find(tick, false));
public abstract ChartObject DeserializeSubclass(tick_t pos, tick_t dur, BinaryReader reader, ChartEffectTable effects);
public void ForEachEntityInRangeTicks(HybridLabel laneLabel, tick_t startTick, tick_t endTick, bool includeDuration, DynValue function) => Chart[laneLabel].ForEachInRange(startTick, endTick, includeDuration, entity => Script.Call(function, entity));
public Entity?GetEntityAtTick(HybridLabel laneLabel, tick_t tick, bool includeDuration) => Chart[laneLabel].Find(tick, includeDuration);
public Entity?GetEntityAtTick(HybridLabel laneLabel, tick_t tick) => Chart[laneLabel].Find(tick, true);
public time_t CalcTimeFromTick(tick_t tick) => Chart.CalcTimeFromTick(tick);
public Chart DeserializeChart(ChartInfo chartInfo, Stream inStream) { var reader = new BinaryReader(inStream, Encoding.UTF8); uint magicCheck = reader.ReadUInt32BE(); if (magicCheck != MAGIC) { throw new ChartFormatException($"Invalid input stream given."); } uint versionCheck = reader.ReadUInt8(); if (versionCheck > VERSION) { throw new ChartFormatException($"Input stream cannot be read by this serializer: the version is too high."); } ushort streamCount = reader.ReadUInt16BE(); var chart = new Chart(streamCount) { Info = chartInfo }; chart.Offset = chartInfo.ChartOffset; int effectCount = reader.ReadUInt16BE(); var effectTable = new ChartEffectTable(); for (int i = 0; i < effectCount; i++) { var effect = DeserializeEffectDef(reader); effectTable.Add(effect); } ushort controlPointCount = reader.ReadUInt16BE(); for (int i = 0; i < controlPointCount; i++) { tick_t position = reader.ReadDoubleBE(); double bpm = reader.ReadDoubleBE(); int beatCount = reader.ReadUInt8(); int beatKind = reader.ReadUInt8(); double mult = reader.ReadDoubleBE(); var cp = chart.ControlPoints.GetOrCreate(position, false); cp.BeatsPerMinute = bpm; cp.BeatCount = beatCount; cp.BeatKind = beatKind; cp.SpeedMultiplier = mult; } for (int s = 0; s < streamCount; s++) { var stream = chart[s]; uint ucount = reader.ReadUInt32BE(); if (ucount > int.MaxValue) { throw new ChartFormatException($"Too many objects declared in stream { s }."); } int count = (int)ucount; for (int i = 0; i < count; i++) { byte objId = reader.ReadUInt8(); var serializer = GetSerializerByID(objId); byte flags = reader.ReadUInt8(); bool hasDuration = (flags & 0x01) != 0; tick_t position = reader.ReadDoubleBE(); tick_t duration = hasDuration ? reader.ReadDoubleBE() : 0; ChartObject obj; if (serializer != null) { obj = serializer.DeserializeSubclass(position, duration, reader, effectTable); } else { obj = new ChartObject() { Position = position, Duration = duration, } }; stream.Add(obj); } } return(chart); }
public static Chart CreateChartFromXml(Stream inStream) { using var reader = XmlReader.Create(inStream); var chart = MusecloneChartFactory.Instance.CreateNew(); var timingInfo = new Dictionary <int, TimingInfo>(); var eventInfos = new List <EventInfo>(); EventInfo?curEvent = null; reader.MoveToContent(); #region Read Timing Information and Event Creation while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.EndElement: if (reader.Name == "event") { //Logger.Log($"End event block: {curEvent!.StartTimeMillis}, {curEvent!.EndTimeMillis}, {curEvent!.Type}, {curEvent!.Kind}"); eventInfos.Add(curEvent !); curEvent = null; } break; case XmlNodeType.Element: { if (reader.Name == "tempo") { int time = 0, deltaTime = 0, value = 500_000; long bpm = 120_00; while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "time": { //string type = reader["__type"]; reader.Read(); // <time ...> time = reader.ReadContentAsInt(); } break; case "delta_time": { //string type = reader["__type"]; reader.Read(); // <delta_time ...> deltaTime = reader.ReadContentAsInt(); } break; case "val": { //string type = reader["__type"]; reader.Read(); // <delta_time ...> value = reader.ReadContentAsInt(); } break; case "bpm": { //string type = reader["__type"]; reader.Read(); // <delta_time ...> bpm = reader.ReadContentAsLong(); } break; } } else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "tempo") { //Logger.Log($"End tempo block: {time}, {deltaTime}, {value}, {bpm}"); if (!timingInfo.TryGetValue(time, out var info)) { timingInfo[time] = info = new TimingInfo(); } info.MusecaWhen = time; info.BeatsPerMinute = bpm / 100.0; break; } } } else if (reader.Name == "sig_info") { int time = 0, deltaTime = 0, num = 4, denomi = 4; while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "time": { //string type = reader["__type"]; reader.Read(); // <time ...> time = reader.ReadContentAsInt(); } break; case "delta_time": { //string type = reader["__type"]; reader.Read(); // <delta_time ...> deltaTime = reader.ReadContentAsInt(); } break; case "num": { //string type = reader["__type"]; reader.Read(); // <delta_time ...> num = reader.ReadContentAsInt(); } break; case "denomi": { //string type = reader["__type"]; reader.Read(); // <delta_time ...> denomi = reader.ReadContentAsInt(); } break; } } else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "sig_info") { //Logger.Log($"End sig_info block: {time}, {deltaTime}, {num}, {denomi}"); if (!timingInfo.TryGetValue(time, out var info)) { timingInfo[time] = info = new TimingInfo(); } info.Numerator = num; info.Denominator = denomi; break; } } } switch (reader.Name) { case "event": curEvent = new EventInfo(); break; case "stime_ms": { reader.Read(); curEvent !.StartTimeMillis = reader.ReadContentAsLong(); } break; case "etime_ms": { reader.Read(); curEvent !.EndTimeMillis = reader.ReadContentAsLong(); } break; case "type": { reader.Read(); curEvent !.Type = reader.ReadContentAsInt(); } break; case "kind": { reader.Read(); curEvent !.Kind = reader.ReadContentAsInt(); } break; } } break; } } #endregion #region Construct :theori Timing Data foreach (var info in from pair in timingInfo orderby pair.Key select pair.Value) { tick_t position = chart.CalcTickFromTime(info.MusecaWhen / 1000.0); var cp = chart.ControlPoints.GetOrCreate(position, true); if (info.BeatsPerMinute > 0) { cp.BeatsPerMinute = info.BeatsPerMinute; } if (info.Numerator != 0 && info.Denominator != 0 && (cp.BeatCount != info.Numerator || cp.BeatKind != info.Denominator)) { cp = chart.ControlPoints.GetOrCreate(MathL.Ceil((double)position), true); cp.BeatCount = info.Numerator; cp.BeatKind = info.Denominator; } } #endregion #region Determine timing info from beat events if (false && timingInfo.Count == 0) { var barEvents = from e in eventInfos where e.Kind == 11 || e.Kind == 12 select e; // bpm related long lastBeatDurationMillis = 0, lastBeatStartMillis = 0, lastBpmStartMillis = 0; int bpmTotalBeats = 0; long runningBeatDurationTotal = 0; int numBeatsCounted = 0; // sig related int n = 4; int measure = 0, totalMeasures = 0, beatCount = 0, totalBeatsInMeasure = 0; long sigMeasureStartMillis = 0, sigCurrentMeasureStartMillis = 0, sigOffsetMillis = 0; tick_t offset = 0; var bpmChanges = new Dictionary <int, (long Millis, double BeatsPerMinute)>(); var timeSigChanges = new Dictionary <int, (int Numerator, int Denominator)>(); foreach (var e in barEvents) { long millis = e.StartTimeMillis; if (totalBeatsInMeasure > 0) { lastBpmStartMillis = lastBeatStartMillis; long beatDuration = millis - lastBeatStartMillis; if (lastBeatDurationMillis != 0) { // TODO(local): if this is within like a millisecond then maybe it's fine if (Math.Abs(beatDuration - lastBeatDurationMillis) > 10) { bpmChanges[bpmTotalBeats] = (lastBpmStartMillis, 60_000.0 / (runningBeatDurationTotal / (double)numBeatsCounted)); lastBpmStartMillis = lastBeatStartMillis; numBeatsCounted = 0; runningBeatDurationTotal = 0; } } lastBeatDurationMillis = beatDuration; } bpmTotalBeats++; numBeatsCounted++; runningBeatDurationTotal += millis - lastBeatStartMillis; lastBeatStartMillis = millis; if (e.Kind == 11) // measure marker { totalMeasures++; if (totalBeatsInMeasure == 0) // first one in the chart { sigOffsetMillis = millis; sigMeasureStartMillis = millis; beatCount++; totalBeatsInMeasure++; } else // check that the previous beat count matches `n` { if (beatCount != n) { timeSigChanges[totalMeasures - 1] = (beatCount, 4); totalBeatsInMeasure = 0; measure = 0; } // continue as normal totalBeatsInMeasure++; measure++; beatCount = 1; sigCurrentMeasureStartMillis = millis; } } else // beat marker { beatCount++; totalBeatsInMeasure++; } } bpmChanges[bpmTotalBeats] = (lastBeatStartMillis, 60_000.0 / (runningBeatDurationTotal / (double)numBeatsCounted)); chart.Offset = sigOffsetMillis / 1_000.0; foreach (var(measureIndex, (num, denom)) in timeSigChanges) { tick_t position = measureIndex; var cp = chart.ControlPoints.GetOrCreate(position, true); cp.BeatCount = num; cp.BeatKind = denom; } foreach (var(beatIndex, (timeMillis, bpm)) in bpmChanges) { int beatsLeft = beatIndex; tick_t? where = null; foreach (var cp in chart.ControlPoints) { if (!cp.HasNext) { where = cp.Position + (double)beatsLeft / cp.BeatCount; break; } else { int numBeatsInCp = (int)(cp.BeatCount * (double)(cp.Next.Position - cp.Position)); if (beatsLeft > numBeatsInCp) { beatsLeft -= numBeatsInCp; } else { where = cp.Position + (double)beatsLeft / cp.BeatCount; break; } } } if (where.HasValue) { var cp = chart.ControlPoints.GetOrCreate(where.Value, true); cp.BeatsPerMinute = bpm; } //else Logger.Log($"Bpm change at beat {beatIndex} (timeMillis) could not be created for bpm {bpm}"); } } #endregion var noteInfos = from e in eventInfos where e.Kind != 11 && e.Kind != 12 && e.Kind != 14 && e.Kind != 15 select e; foreach (var entity in noteInfos) { tick_t startTicks = chart.CalcTickFromTime(entity.StartTimeMillis / 1000.0); tick_t endTicks = chart.CalcTickFromTime(entity.EndTimeMillis / 1000.0); const int q = 192; startTicks = MathL.Round((double)(startTicks * q)) / q; endTicks = MathL.Round((double)(endTicks * q)) / q; tick_t durTicks = endTicks - startTicks; if (entity.Kind == 1 && entity.Type == 5) { chart[5].Add <ButtonEntity>(startTicks, durTicks); } else { switch (entity.Kind) { // "chip" tap note case 0: chart[entity.Type].Add <ButtonEntity>(startTicks); break; // hold tap note, ignore foot pedal bc handled above case 1: chart[entity.Type - 6].Add <ButtonEntity>(startTicks, durTicks); break; // large spinner case 2: { var e = chart[entity.Type % 6].Add <SpinnerEntity>(startTicks, durTicks); e.Large = true; } break; // large spinner left case 3: { var e = chart[entity.Type % 6].Add <SpinnerEntity>(startTicks, durTicks); e.Direction = LinearDirection.Left; e.Large = true; } break; // large spinner right case 4: { var e = chart[entity.Type % 6].Add <SpinnerEntity>(startTicks, durTicks); e.Direction = LinearDirection.Right; e.Large = true; } break; // small spinner case 5: { var e = chart[entity.Type].Add <SpinnerEntity>(startTicks); } break; // small spinner left case 6: { var e = chart[entity.Type].Add <SpinnerEntity>(startTicks); e.Direction = LinearDirection.Left; } break; // small spinner right case 7: { var e = chart[entity.Type].Add <SpinnerEntity>(startTicks); e.Direction = LinearDirection.Right; } break; } } } return(chart); }
public void WriteValue(tick_t value) => m_writer.WriteValue((double)value);
public TempLaserState(tick_t pos, ControlPoint cp) { StartPosition = pos; ControlPoint = cp; }
public LaserJudge(Chart chart, HybridLabel label) : base(chart, label) { tick_t tickStep = (Chart.MaxBpm >= 255 ? 2.0 : 1.0) / (4 * 4); // score ticks first foreach (var entity in chart[label]) { var root = (AnalogEntity)entity; if (root.PreviousConnected != null) { continue; } // now we're working with the root var start = root; while (start != null) { var next = start; while (next.NextConnected is AnalogEntity a && !a.IsInstant) { next = a; } Debug.Assert(next.Position >= start.Position); if (next.Position > start.Position) { Debug.Assert(!next.IsInstant); } tick_t startPos = start.Position; bool endsWithSlam = next.NextConnected is AnalogEntity && next.IsInstant; int numTicks = MathL.Max(1, MathL.FloorToInt((double)(next.EndPosition - startPos) / (double)tickStep)); if (endsWithSlam && next.EndPosition - (startPos + tickStep * numTicks) < tickStep) { numTicks--; } for (int i = 0; i < numTicks; i++) { tick_t pos = startPos + i * tickStep; var kind = (i == 0 && start.IsInstant) ? TickKind.Slam : TickKind.Segment; m_scoreTicks.Add(new ScoreTick(root, chart.CalcTimeFromTick(pos), kind)); } start = next.NextConnected as AnalogEntity; } } // state ticks seconds foreach (var entity in chart[label]) { var root = (AnalogEntity)entity; if (root.PreviousConnected != null) { continue; } time_t cursorResetTime = root.AbsolutePosition - m_cursorResetDistance; time_t laserBeginTime = root.AbsolutePosition - m_directionChangeRadius; if (root.Previous is AnalogEntity p) { cursorResetTime = MathL.Max((double)p.AbsoluteEndPosition, (double)cursorResetTime); laserBeginTime = MathL.Max((double)p.AbsoluteEndPosition, (double)laserBeginTime); } m_stateTicks.Add(new StateTick(root, root, cursorResetTime, JudgeState.CursorReset)); m_stateTicks.Add(new StateTick(root, root, laserBeginTime, JudgeState.LaserBegin)); if (root.DirectionSign != 0) { m_stateTicks.Add(new StateTick(root, root, root.AbsolutePosition, JudgeState.SwitchDirection)); } if (root.NextConnected is AnalogEntity segment) { while (segment != null) { if (segment.DirectionSign != ((AnalogEntity)segment.Previous).DirectionSign) { m_stateTicks.Add(new StateTick(root, segment, segment.AbsolutePosition, JudgeState.SwitchDirection)); } else if (segment.IsInstant) { m_stateTicks.Add(new StateTick(root, segment, segment.AbsolutePosition, JudgeState.SameDirectionSlam)); } if (segment.NextConnected == null) { m_stateTicks.Add(new StateTick(root, segment, segment.AbsoluteEndPosition, JudgeState.LaserEnd)); } segment = segment.NextConnected as AnalogEntity; } } else { m_stateTicks.Add(new StateTick(root, root, root.AbsoluteEndPosition, JudgeState.LaserEnd)); } } }
public static Chart ToVoltex(this KshChart ksh) { Logger.Log("ksh.convert start"); bool hasActiveEffects = !(ksh.Metadata.MusicFile != null && ksh.Metadata.MusicFileNoFx != null); Logger.Log("ksh.convert effects disabled"); var chart = new Chart(StreamIndex.COUNT) { Offset = ksh.Metadata.OffsetMillis / 1_000.0 }; chart.Info = new ChartInfo() { SongTitle = ksh.Metadata.Title, SongArtist = ksh.Metadata.Artist, SongFileName = ksh.Metadata.MusicFile ?? ksh.Metadata.MusicFileNoFx, SongVolume = ksh.Metadata.MusicVolume, ChartOffset = chart.Offset, Charter = ksh.Metadata.EffectedBy, JacketFileName = ksh.Metadata.JacketPath, JacketArtist = ksh.Metadata.Illustrator, BackgroundFileName = ksh.Metadata.Background, BackgroundArtist = "Unknown", DifficultyLevel = ksh.Metadata.Level, DifficultyIndex = ksh.Metadata.Difficulty.ToDifficultyIndex(ksh.FileName), DifficultyName = ksh.Metadata.Difficulty.ToDifficultyString(ksh.FileName), DifficultyNameShort = ksh.Metadata.Difficulty.ToShortString(ksh.FileName), DifficultyColor = ksh.Metadata.Difficulty.GetColor(ksh.FileName), }; { if (double.TryParse(ksh.Metadata.BeatsPerMinute, out double bpm)) { chart.ControlPoints.Root.BeatsPerMinute = bpm; } var laserParams = chart[StreamIndex.LaserParams].Add <LaserParamsEvent>(0); laserParams.LaserIndex = LaserIndex.Both; var laserGain = chart[StreamIndex.LaserFilterGain].Add <LaserFilterGainEvent>(0); laserGain.LaserIndex = LaserIndex.Both; if (!hasActiveEffects) { laserGain.Gain = 0.0f; } else { laserGain.Gain = ksh.Metadata.PFilterGain / 100.0f; } var laserFilter = chart[StreamIndex.LaserFilterKind].Add <LaserFilterKindEvent>(0); laserFilter.LaserIndex = LaserIndex.Both; laserFilter.Effect = ksh.FilterDefines[ksh.Metadata.FilterType]; var slamVolume = chart[StreamIndex.SlamVolume].Add <SlamVolumeEvent>(0); if (!hasActiveEffects) { slamVolume.Volume = 0.0f; } else { slamVolume.Volume = ksh.Metadata.SlamVolume / 100.0f; } } var lastCp = chart.ControlPoints.Root; var buttonStates = new TempButtonState[6]; var laserStates = new TempLaserState[2]; bool[] laserIsExtended = new bool[2] { false, false }; PathPointEvent lastTiltEvent = null; foreach (var tickRef in ksh) { var tick = tickRef.Tick; int blockOffset = tickRef.Block; tick_t chartPos = blockOffset + (double)tickRef.Index / tickRef.MaxIndex; foreach (var setting in tick.Settings) { string key = setting.Key; switch (key) { case "beat": { if (!setting.Value.ToString().TrySplit('/', out string n, out string d)) { n = d = "4"; Logger.Log($"ksh.convert error: { setting.Value } is not a valid time signature. Defaulting to 4/4."); } tick_t pos = MathL.Ceil((double)chartPos); ControlPoint cp = chart.ControlPoints.GetOrCreate(pos, true); cp.BeatCount = int.Parse(n); cp.BeatKind = int.Parse(d); lastCp = cp; Logger.Log($"ksh.convert time signature { cp.BeatCount }/{ cp.BeatKind }"); } break; case "t": { ControlPoint cp = chart.ControlPoints.GetOrCreate(chartPos, true); cp.BeatsPerMinute = double.Parse(setting.Value.ToString()); lastCp = cp; Logger.Log($"ksh.convert bpm { cp.BeatsPerMinute }"); } break; case "fx-l": case "fx-r": { if (hasActiveEffects) { var effectEvent = chart[StreamIndex.EffectKind].Add <EffectKindEvent>(chartPos); effectEvent.EffectIndex = key == "fx-l" ? 4 : 5; effectEvent.Effect = (string)setting.Value.Value == "" ? null : ksh.FxDefines[setting.Value.ToString()]; Logger.Log($"ksh.convert set { key } { effectEvent.Effect?.GetType().Name ?? "nothing" }"); } else { Logger.Log($"ksh.convert effects disabled for { key }"); } } break; case "fx-l_param1": { Logger.Log($"ksh.convert skipping fx-l_param1."); } break; case "fx-r_param1": { Logger.Log($"ksh.convert skipping fx-r_param1."); } break; case "pfiltergain": { if (hasActiveEffects) { var laserGain = chart[StreamIndex.LaserFilterGain].Add <LaserFilterGainEvent>(chartPos); laserGain.LaserIndex = LaserIndex.Both; laserGain.Gain = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert set { key } { setting.Value }"); } else { Logger.Log($"ksh.convert effects disabled for { key }"); } } break; case "filtertype": { if (hasActiveEffects) { var laserFilter = chart[StreamIndex.LaserFilterKind].Add <LaserFilterKindEvent>(chartPos); laserFilter.LaserIndex = LaserIndex.Both; laserFilter.Effect = (string)setting.Value.Value == "" ? null : ksh.FilterDefines[setting.Value.ToString()]; Logger.Log($"ksh.convert set { key } { laserFilter.Effect?.GetType().Name ?? "nothing" }"); } else { Logger.Log($"ksh.convert effects disabled for { key }"); } } break; case "filter-l": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. case "filter-r": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. { Logger.Log($"ksh.convert skipping { key }."); } break; case "filter-l_gain": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. case "filter-r_gain": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. { Logger.Log($"ksh.convert skipping { key }."); } break; case "chokkakuvol": { if (hasActiveEffects) { var slamVoume = chart[StreamIndex.SlamVolume].Add <SlamVolumeEvent>(chartPos); slamVoume.Volume = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert set { key } { setting.Value }"); } else { Logger.Log($"ksh.convert effects disabled for { key }"); } } break; case "laserrange_l": { laserIsExtended[0] = true; } break; case "laserrange_r": { laserIsExtended[1] = true; } break; case "zoom_bottom": { var point = chart[StreamIndex.Zoom].Add <PathPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert zoom { setting.Value }"); } break; case "zoom_top": { var point = chart[StreamIndex.Pitch].Add <PathPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert pitch { setting.Value }"); } break; case "zoom_side": { var point = chart[StreamIndex.Offset].Add <PathPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert offset { setting.Value }"); } break; case "roll": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. { var point = chart[StreamIndex.Roll].Add <PathPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 360.0f; Logger.Log($"ksh.convert custom manual tilt { setting.Value }"); } break; case "tilt": { var laserApps = chart[StreamIndex.LaserApplication].Add <LaserApplicationEvent>(chartPos); string v = setting.Value.ToString(); if (v.StartsWith("keep_")) { laserApps.Application = LaserApplication.Additive | LaserApplication.KeepMax; v = v.Substring(5); } var laserParams = chart[StreamIndex.LaserParams].Add <LaserParamsEvent>(chartPos); laserParams.LaserIndex = LaserIndex.Both; bool disableTilt = true; switch (v) { default: { if (int.TryParse(v, out int manualValue)) { disableTilt = false; if (lastTiltEvent == null) { var startPoint = chart[StreamIndex.Roll].Add <PathPointEvent>(chartPos); startPoint.Value = 0; } var point = chart[StreamIndex.Roll].Add <PathPointEvent>(chartPos); point.Value = -manualValue * 14 / 360.0f; lastTiltEvent = point; } } goto case "zero"; case "zero": laserParams.Params.Function = LaserFunction.Zero; break; case "normal": laserParams.Params.Scale = LaserScale.Normal; break; case "bigger": laserParams.Params.Scale = LaserScale.Bigger; break; case "biggest": laserParams.Params.Scale = LaserScale.Biggest; break; } if (disableTilt && lastTiltEvent != null) { } } break; case "fx_sample": { Logger.Log($"ksh.convert skipping fx_sample."); } break; case "stop": { Logger.Log($"ksh.convert skipping stop."); } break; case "lane_toggle": { Logger.Log($"ksh.convert skipping lane_toggle."); } break; } } for (int b = 0; b < 6; b++) { bool isFx = b >= 4; var data = isFx ? tick.Fx[b - 4] : tick.Bt[b]; var fxKind = data.FxKind; void CreateHold(tick_t endPos) { var state = buttonStates[b]; var startPos = state.StartPosition; var button = chart[b].Add <ButtonObject>(startPos, endPos - startPos); } switch (data.State) { case KshButtonState.Off: { if (buttonStates[b] != null) { CreateHold(chartPos); } buttonStates[b] = null; } break; case KshButtonState.Chip: case KshButtonState.ChipSample: { //System.Diagnostics.Trace.WriteLine(b); chart[b].Add <ButtonObject>(chartPos); } break; case KshButtonState.Hold: { if (buttonStates[b] == null) { buttonStates[b] = new TempButtonState(chartPos); } } break; } } for (int l = 0; l < 2; l++) { var data = tick.Laser[l]; var state = data.State; tick_t CreateSegment(tick_t endPos, float endAlpha) { var startPos = laserStates[l].StartPosition; float startAlpha = laserStates[l].StartAlpha; var duration = endPos - startPos; //if (duration <= tick_t.FromFraction(1, 32 * lastCp.BeatCount / lastCp.BeatKind)) if (laserStates[l].HiResTickCount <= 6 && startAlpha != endAlpha) { duration = 0; if (laserStates[l].PreviousSlamDuration != 0) { var cDuration = laserStates[l].PreviousSlamDuration; var connector = chart[l + 6].Add <AnalogObject>(startPos, cDuration); connector.InitialValue = startAlpha; connector.FinalValue = startAlpha; connector.RangeExtended = laserIsExtended[l]; startPos += cDuration; } } var analog = chart[l + 6].Add <AnalogObject>(startPos, duration); analog.InitialValue = startAlpha; analog.FinalValue = endAlpha; analog.RangeExtended = laserIsExtended[l]; analog.Shape = laserStates[l].Shape; analog.CurveA = laserStates[l].CurveA; analog.CurveB = laserStates[l].CurveB; return(startPos + duration); } switch (state) { case KshLaserState.Inactive: { if (laserStates[l] != null) { laserStates[l] = null; laserIsExtended[l] = false; } } break; case KshLaserState.Lerp: { laserStates[l].HiResTickCount += (192 * lastCp.BeatCount / lastCp.BeatKind) / tickRef.MaxIndex; } break; case KshLaserState.Position: { var alpha = data.Position; var startPos = chartPos; tick_t prevSlamDuration = 0; if (laserStates[l] != null) { startPos = CreateSegment(chartPos, alpha.Alpha); if (startPos != chartPos) { prevSlamDuration = chartPos - startPos; } } var ls = laserStates[l] = new TempLaserState(startPos, lastCp) { StartAlpha = alpha.Alpha, HiResTickCount = (192 * lastCp.BeatCount / lastCp.BeatKind) / tickRef.MaxIndex, PreviousSlamDuration = prevSlamDuration, }; for (int i = tick.Comments.Count - 1; i >= 0; i--) { string c = tick.Comments[i]; if (!c.StartsWith("LaserShape ")) { continue; } c = c.Substring("LaserShape ".Length).Trim(); if (c.StartsWith("ThreePoint")) { float a = 0.5f, b = 0.5f; if (c != "ThreePoint" && c.TrySplit(' ', out string tp, out string sa, out string sb)) { float.TryParse(sa, out a); float.TryParse(sb, out b); } ls.Shape = CurveShape.ThreePoint; ls.CurveA = a; ls.CurveB = b; } else if (c == "Cosine") { ls.Shape = CurveShape.Cosine; } else { continue; } break; } } break; } } switch (tick.Add.Kind) { case KshAddKind.None: break; case KshAddKind.Spin: { tick_t duration = tick_t.FromFraction(tick.Add.Duration * 2, 192); var spin = chart[StreamIndex.HighwayEffect].Add <SpinImpulseEvent>(chartPos, duration); spin.Direction = (AngularDirection)tick.Add.Direction; } break; case KshAddKind.Swing: { tick_t duration = tick_t.FromFraction(tick.Add.Duration * 2, 192); var swing = chart[StreamIndex.HighwayEffect].Add <SwingImpulseEvent>(chartPos, duration); swing.Direction = (AngularDirection)tick.Add.Direction; swing.Amplitude = tick.Add.Amplitude * 70 / 100.0f; } break; case KshAddKind.Wobble: { tick_t duration = tick_t.FromFraction(tick.Add.Duration, 192); var wobble = chart[StreamIndex.HighwayEffect].Add <WobbleImpulseEvent>(chartPos, duration); wobble.Direction = (LinearDirection)tick.Add.Direction; wobble.Amplitude = tick.Add.Amplitude / 250.0f; wobble.Decay = (Decay)tick.Add.Decay; wobble.Frequency = tick.Add.Frequency; } break; } } Logger.Log("ksh.convert end"); return(chart); } }
public static Chart ToVoltex(this KshChart ksh, ChartInfo?info = null) { Logger.Log($"ksh.convert start"); bool hasActiveEffects = !(ksh.Metadata.MusicFile != null && ksh.Metadata.MusicFileNoFx != null); Logger.Log($"ksh.convert effects disabled"); var chart = NeuroSonicChartFactory.Instance.CreateNew(); chart.Offset = (ksh.Metadata.OffsetMillis) / 1_000.0; // if info is non-null, set information exists as well. chart.Info = info ?? new ChartInfo() { SongTitle = ksh.Metadata.Title, SongArtist = ksh.Metadata.Artist, SongFileName = ksh.Metadata.MusicFile ?? ksh.Metadata.MusicFileNoFx ?? "??", SongVolume = ksh.Metadata.MusicVolume, ChartOffset = chart.Offset, Charter = ksh.Metadata.EffectedBy, JacketFileName = ksh.Metadata.JacketPath, JacketArtist = ksh.Metadata.Illustrator, BackgroundFileName = ksh.Metadata.Background, BackgroundArtist = "Unknown", DifficultyLevel = ksh.Metadata.Level, DifficultyIndex = ksh.Metadata.Difficulty.ToDifficultyIndex(ksh.FileName), DifficultyName = ksh.Metadata.Difficulty.ToDifficultyString(ksh.FileName), DifficultyNameShort = ksh.Metadata.Difficulty.ToShortString(ksh.FileName), DifficultyColor = ksh.Metadata.Difficulty.GetColor(ksh.FileName), }; { if (double.TryParse(ksh.Metadata.BeatsPerMinute, out double bpm)) { chart.ControlPoints.Root.BeatsPerMinute = bpm; } var laserParams = chart[NscLane.LaserEvent].Add <LaserParamsEvent>(0); laserParams.LaserIndex = LaserIndex.Both; var laserGain = chart[NscLane.LaserEvent].Add <LaserFilterGainEvent>(0); laserGain.LaserIndex = LaserIndex.Both; if (!hasActiveEffects) { laserGain.Gain = 0.0f; } else { laserGain.Gain = ksh.Metadata.PFilterGain / 100.0f; } var laserFilter = chart[NscLane.LaserEvent].Add <LaserFilterKindEvent>(0); laserFilter.LaserIndex = LaserIndex.Both; laserFilter.Effect = new KshEffectRef(ksh.Metadata.FilterType, null).CreateEffectDef(ksh.FilterDefines); var slamVolume = chart[NscLane.LaserEvent].Add <SlamVolumeEvent>(0); slamVolume.Volume = ksh.Metadata.SlamVolume / 100.0f; } double modeBpm; if (ksh.Metadata.HiSpeedBpm != null) { modeBpm = ksh.Metadata.HiSpeedBpm.Value; } else { var bpms = new List <(tick_t, double)>(); tick_t lastTick = 0; foreach (var tickRef in ksh) { var tick = tickRef.Tick; int blockOffset = tickRef.Block; tick_t chartPos = blockOffset + (double)tickRef.Index / tickRef.MaxIndex; foreach (var setting in tick.Settings) { if (setting.Key == "t") { double bpm = double.Parse(setting.Value.ToString()); if (bpms.Count > 0 && bpm == bpms[bpms.Count - 1].Item2) { continue; } bpms.Add((chartPos, bpm)); } } lastTick = chartPos; } var bpmDurs = new Dictionary <double, tick_t>(); tick_t longest = -1; double result = 120.0; for (int i = 0; i < bpms.Count; i++) { bool last = i == bpms.Count - 1; var(when, bpm) = bpms[i]; tick_t duration; if (last) { duration = lastTick - when; } else { duration = bpms[i].Item1 - when; } if (bpmDurs.TryGetValue(bpm, out tick_t accum)) { bpmDurs[bpm] = accum + duration; } else { bpmDurs[bpm] = duration; } if (bpmDurs[bpm] > longest) { longest = bpmDurs[bpm]; result = bpm; } } modeBpm = result; } var lastCp = chart.ControlPoints.Root; var buttonStates = new TempButtonState[6]; var laserStates = new TempLaserState[2]; bool[] laserIsExtended = new bool[2] { false, false }; GraphPointEvent lastTiltEvent = null; foreach (var tickRef in ksh) { var tick = tickRef.Tick; int blockOffset = tickRef.Block; tick_t chartPos = blockOffset + (double)tickRef.Index / tickRef.MaxIndex; string[] chipHitSounds = new string[6]; float[] chipHitSoundsVolume = new float[6]; foreach (var setting in tick.Settings) { string key = setting.Key; switch (key) { case "beat": { if (!setting.Value.ToString().TrySplit('/', out string n, out string d)) { n = d = "4"; Logger.Log($"ksh.convert({ chartPos }) error: { setting.Value } is not a valid time signature. Defaulting to 4/4."); } tick_t pos = MathL.Ceil((double)chartPos); ControlPoint cp = chart.ControlPoints.GetOrCreate(pos, true); cp.BeatCount = int.Parse(n); cp.BeatKind = int.Parse(d); lastCp = cp; Logger.Log($"ksh.convert({ chartPos }) time signature { cp.BeatCount }/{ cp.BeatKind }"); } break; case "t": { ControlPoint cp = chart.ControlPoints.GetOrCreate(chartPos, true); cp.BeatsPerMinute = double.Parse(setting.Value.ToString()); cp.SpeedMultiplier = cp.BeatsPerMinute / modeBpm; lastCp = cp; Logger.Log($"ksh.convert({ chartPos }) bpm { cp.BeatsPerMinute }"); } break; case "fx-l": case "fx-r": { if (hasActiveEffects) { var effectEvent = chart[NscLane.ButtonEvent].Add <EffectKindEvent>(chartPos); effectEvent.EffectIndex = key == "fx-l" ? 4 : 5; effectEvent.Effect = (setting.Value.Value as KshEffectRef)?.CreateEffectDef(ksh.FxDefines); Logger.Log($"ksh.convert({ chartPos }) set { key } { effectEvent.Effect?.GetType().Name ?? "nothing" }"); } else { Logger.Log($"ksh.convert({ chartPos }) effects disabled for { key }"); } } break; case "fx-l_se": case "fx-r_se": { string chipFx = (string)setting.Value.Value; float volume = 1.0f; if (chipFx.TrySplit(';', out string fxName, out string volStr)) { chipFx = fxName; if (volStr.Contains(';')) { volStr = volStr.Substring(0, volStr.IndexOf(';')); } volume = int.Parse(volStr) / 100.0f; } int i = key == "fx-l_se" ? 4 : 5; chipHitSoundsVolume[i] = volume; chipHitSounds[i] = chipFx; } break; case "fx-l_param1": { Logger.Log($"ksh.convert({ chartPos }) skipping fx-l_param1."); } break; case "fx-r_param1": { Logger.Log($"ksh.convert({ chartPos }) skipping fx-r_param1."); } break; case "pfiltergain": { if (hasActiveEffects) { var laserGain = chart[NscLane.LaserEvent].Add <LaserFilterGainEvent>(chartPos); laserGain.LaserIndex = LaserIndex.Both; laserGain.Gain = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) set { key } { setting.Value }"); } else { Logger.Log($"ksh.convert({ chartPos }) effects disabled for { key }"); } } break; case "filtertype": { if (hasActiveEffects) { var laserFilter = chart[NscLane.LaserEvent].Add <LaserFilterKindEvent>(chartPos); laserFilter.LaserIndex = LaserIndex.Both; laserFilter.Effect = (setting.Value.Value as KshEffectRef)?.CreateEffectDef(ksh.FilterDefines); Logger.Log($"ksh.convert({ chartPos }) set { key } { laserFilter.Effect?.GetType().Name ?? "nothing" }"); } else { Logger.Log($"ksh.convert({ chartPos }) effects disabled for { key }"); } } break; case "filter-l": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. case "filter-r": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. { Logger.Log($"ksh.convert({ chartPos }) skipping { key }."); } break; case "filter-l_gain": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. case "filter-r_gain": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. { Logger.Log($"ksh.convert({ chartPos }) skipping { key }."); } break; case "chokkakuvol": { var slamVoume = chart[NscLane.LaserEvent].Add <SlamVolumeEvent>(chartPos); slamVoume.Volume = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) set { key } { setting.Value }"); } break; case "laserrange_l": { laserIsExtended[0] = true; } break; case "laserrange_r": { laserIsExtended[1] = true; } break; case "zoom_bottom": { var point = chart[NscLane.CameraZoom].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) zoom { setting.Value }"); } break; case "zoom_top": { var point = chart[NscLane.CameraPitch].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) pitch { setting.Value }"); } break; case "zoom_side": { var point = chart[NscLane.CameraOffset].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) offset { setting.Value }"); } break; case "split_0": { var point = chart[NscLane.Split0].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) split 0 (l-a) { setting.Value }"); } break; case "split_1": { var point = chart[NscLane.Split1].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) split 1 (a-b) { setting.Value }"); } break; case "center_split": case "split_2": { var point = chart[NscLane.Split2].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) split 2 (b-c) { setting.Value }"); } break; case "split_3": { var point = chart[NscLane.Split3].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) split 3 (c-d) { setting.Value }"); } break; case "split_4": { var point = chart[NscLane.Split4].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 100.0f; Logger.Log($"ksh.convert({ chartPos }) split 4 (d-r) { setting.Value }"); } break; case "roll": // NOTE(local): This is an extension, not originally supported in KSH. Used primarily for development purposes, but may also be exported to KSH should someone want to export back to that format. { var point = chart[NscLane.CameraTilt].Add <GraphPointEvent>(chartPos); point.Value = setting.Value.ToInt() / 360.0f; Logger.Log($"ksh.convert({ chartPos }) custom manual tilt { setting.Value }"); } break; case "tilt": { var laserApps = chart[NscLane.LaserEvent].Add <LaserApplicationEvent>(chartPos); string v = setting.Value.ToString(); if (v.StartsWith("keep_")) { laserApps.Application = LaserApplication.Additive | LaserApplication.KeepMax; v = v.Substring(5); } var laserParams = chart[NscLane.LaserEvent].Add <LaserParamsEvent>(chartPos); laserParams.LaserIndex = LaserIndex.Both; bool disableTilt = true; switch (v) { default: { if (int.TryParse(v, out int manualValue)) { disableTilt = false; if (lastTiltEvent == null) { var startPoint = chart[NscLane.CameraTilt].Add <GraphPointEvent>(chartPos); startPoint.Value = 0; } var point = chart[NscLane.CameraTilt].Add <GraphPointEvent>(chartPos); point.Value = -manualValue * 14 / 360.0f; lastTiltEvent = point; } } goto case "zero"; case "zero": laserParams.Params.Function = LaserFunction.Zero; break; case "normal": laserParams.Params.Scale = LaserScale.Normal; break; case "bigger": laserParams.Params.Scale = LaserScale.Bigger; break; case "biggest": laserParams.Params.Scale = LaserScale.Biggest; break; } if (disableTilt && lastTiltEvent != null) { } } break; case "fx_sample": { Logger.Log($"ksh.convert({ chartPos }) skipping fx_sample."); } break; case "stop": { ControlPoint cp = chart.ControlPoints.GetOrCreate(chartPos, true); // TODO(local): this breaks when there's another control point between here and there chart.ControlPoints.GetOrCreate(chartPos + int.Parse(setting.Value.ToString()) / 192.0, true); cp.StopChart = true; lastCp = cp; Logger.Log($"ksh.convert({ chartPos }) stop { int.Parse(setting.Value.ToString()) / 192.0 }"); } break; case "lane_toggle": { Logger.Log($"ksh.convert({ chartPos }) skipping lane_toggle."); } break; } } for (int b = 0; b < 6; b++) { bool isFx = b >= 4; var data = isFx ? tick.Fx[b - 4] : tick.Bt[b]; void CreateHold(tick_t endPos) { var state = buttonStates[b]; var startPos = state.StartPosition; var button = chart[(HybridLabel)b].Add <ButtonEntity>(startPos, endPos - startPos); } switch (data.State) { case KshButtonState.Off: { if (buttonStates[b] != null) { CreateHold(chartPos); } buttonStates[b] = null; } break; case KshButtonState.Chip: case KshButtonState.ChipSample: { var chip = chart[(HybridLabel)b].Add <ButtonEntity>(chartPos); chip.Sample = chipHitSounds[b]; chip.SampleVolume = chipHitSoundsVolume[b]; } break; case KshButtonState.Hold: { if (buttonStates[b] == null) { buttonStates[b] = new TempButtonState(chartPos); } } break; } } for (int l = 0; l < 2; l++) { var data = tick.Laser[l]; var state = data.State; tick_t CreateSegment(tick_t endPos, float endAlpha) { var startPos = laserStates[l].StartPosition; float startAlpha = laserStates[l].StartAlpha; var duration = endPos - startPos; //if (duration <= tick_t.FromFraction(1, 32 * lastCp.BeatCount / lastCp.BeatKind)) if (laserStates[l].HiResTickCount <= 6 && startAlpha != endAlpha) { duration = 0; if (laserStates[l].PreviousSlamDuration != 0) { var cDuration = laserStates[l].PreviousSlamDuration; var connector = chart[(HybridLabel)(l + 6)].Add <AnalogEntity>(startPos, cDuration); connector.InitialValue = startAlpha; connector.FinalValue = startAlpha; connector.RangeExtended = laserIsExtended[l]; startPos += cDuration; } } var analog = chart[(HybridLabel)(l + 6)].Add <AnalogEntity>(startPos, duration); analog.InitialValue = startAlpha; analog.FinalValue = endAlpha; analog.RangeExtended = laserIsExtended[l]; analog.Shape = laserStates[l].Shape; analog.CurveA = laserStates[l].CurveA; analog.CurveB = laserStates[l].CurveB; return(startPos + duration); } switch (state) { case KshLaserState.Inactive: { if (laserStates[l] != null) { laserStates[l] = null; laserIsExtended[l] = false; } } break; case KshLaserState.Lerp: { laserStates[l].HiResTickCount += (192 * lastCp.BeatCount / lastCp.BeatKind) / tickRef.MaxIndex; } break; case KshLaserState.Position: { var alpha = data.Position; var startPos = chartPos; tick_t prevSlamDuration = 0; if (laserStates[l] != null) { startPos = CreateSegment(chartPos, alpha.Alpha); if (startPos != chartPos) { prevSlamDuration = chartPos - startPos; } } var ls = laserStates[l] = new TempLaserState(startPos, lastCp) { StartAlpha = alpha.Alpha, HiResTickCount = (192 * lastCp.BeatCount / lastCp.BeatKind) / tickRef.MaxIndex, PreviousSlamDuration = prevSlamDuration, CurveResolution = 0, }; for (int i = tick.Comments.Count - 1; i >= 0; i--) { string c = tick.Comments[i]; if (!c.StartsWith("LaserShape ")) { continue; } c = c.Substring("LaserShape ".Length).Trim(); if (c.StartsWith("ThreePoint")) { float a = 0.5f, b = 0.5f; if (c != "ThreePoint" && c.TrySplit(' ', out string tp, out string sa, out string sb)) { float.TryParse(sa, out a); float.TryParse(sb, out b); } ls.Shape = CurveShape.ThreePoint; ls.CurveA = a; ls.CurveB = b; } else if (c.StartsWith("Cosine")) { float a = 0.0f; if (c != "Cosine" && c.TrySplit(' ', out string tp, out string sa)) { float.TryParse(sa, out a); } ls.Shape = CurveShape.Cosine; ls.CurveA = a; } else { continue; } break; } } break; } } switch (tick.Add.Kind) { case KshAddKind.None: break; case KshAddKind.Spin: { tick_t duration = tick_t.FromFraction(tick.Add.Duration * 2, 192); var spin = chart[NscLane.HighwayEvent].Add <SpinImpulseEvent>(chartPos, duration); spin.Direction = (AngularDirection)tick.Add.Direction; } break; case KshAddKind.Swing: { tick_t duration = tick_t.FromFraction(tick.Add.Duration * 2, 192); var swing = chart[NscLane.HighwayEvent].Add <SwingImpulseEvent>(chartPos, duration); swing.Direction = (AngularDirection)tick.Add.Direction; swing.Amplitude = tick.Add.Amplitude * 70 / 100.0f; } break; case KshAddKind.Wobble: { tick_t duration = tick_t.FromFraction(tick.Add.Duration, 192); var wobble = chart[NscLane.HighwayEvent].Add <WobbleImpulseEvent>(chartPos, duration); wobble.Direction = (LinearDirection)tick.Add.Direction; wobble.Amplitude = tick.Add.Amplitude / 250.0f; wobble.Decay = (Decay)tick.Add.Decay; wobble.Frequency = tick.Add.Frequency; } break; } } Logger.Log($"ksh.convert end"); return(chart); }