public bool Start(ISoundPreviewContext context, int startTick) { if (Playing) { throw new InvalidOperationException(); } if (context == null) { throw new ArgumentNullException("context"); } PreviewContext = context; SoundManager.Register(context.ClapSource.FilePath); SoundManager.Register(context.MusicSource.FilePath); var timeCalculator = new TimeCalculator(context.TicksPerBeat, context.BpmDefinitions); var ticks = new SortedSet <int>(context.GetGuideTicks()).ToList(); TickElement = new LinkedList <int?>(ticks.Where(p => p >= startTick).OrderBy(p => p).Select(p => new int?(p))).First; BpmElement = new LinkedList <BpmChangeEvent>(context.BpmDefinitions.OrderBy(p => p.Tick)).First; EndTick = IsStopAtLastNote ? ticks[ticks.Count - 1] : timeCalculator.GetTickFromTime(SoundManager.GetDuration(context.MusicSource.FilePath)); if (EndTick < startTick) { return(false); } // スタート時まで進める while (TickElement != null && TickElement.Value < startTick) { TickElement = TickElement.Next; } while (BpmElement.Next != null && BpmElement.Next.Value.Tick <= startTick) { BpmElement = BpmElement.Next; } int clapLatencyTick = GetLatencyTick(context.ClapSource.Latency, BpmElement.Value.Bpm); InitialTick = startTick - clapLatencyTick; CurrentTick = InitialTick; StartTick = startTick; double startTime = timeCalculator.GetTimeFromTick(startTick); double headGap = Math.Max(-context.MusicSource.Latency - startTime, 0); elapsedTick = 0; Task.Run(() => { LastSystemTick = Environment.TickCount; SyncControl.Invoke((MethodInvoker)(() => Timer.Start())); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(Math.Max((context.ClapSource.Latency + headGap) / context.Speed, 0))); if (!Playing) { return; } SoundManager.Play(context.MusicSource.FilePath, startTime + context.MusicSource.Latency, context.MusicSource.Volume, context.Speed); }) .ContinueWith(p => { if (p.Exception != null) { Program.DumpExceptionTo(p.Exception, "sound_exception.json"); ExceptionThrown?.Invoke(this, EventArgs.Empty); } }); Playing = true; Started?.Invoke(this, EventArgs.Empty); return(true); }