public bool Start(SoundSource music, int startTick) { if (Playing) { throw new InvalidOperationException(); } if (music == null) { throw new ArgumentNullException("music"); } SoundManager.Register(ClapSource.FilePath); SoundManager.Register(music.FilePath); EndTick = IsStopAtLastNote ? NoteView.Notes.GetLastTick() : GetTickFromTime(SoundManager.GetDuration(music.FilePath), NoteView.ScoreEvents.BPMChangeEvents); if (EndTick < startTick) { return(false); } var tickSet = new HashSet <int>(); var notes = NoteView.Notes; var shortNotesTick = notes.Taps.Cast <TappableBase>().Concat(notes.ExTaps).Concat(notes.Flicks).Concat(notes.Damages).Select(p => p.Tick); var holdsTick = notes.Holds.SelectMany(p => new int[] { p.StartTick, p.StartTick + p.Duration }); var slidesTick = notes.Slides.SelectMany(p => new int[] { p.StartTick }.Concat(p.StepNotes.Where(q => q.IsVisible).Select(q => q.Tick))); var airActionsTick = notes.AirActions.SelectMany(p => p.ActionNotes.Select(q => p.StartTick + q.Offset)); foreach (int tick in shortNotesTick.Concat(holdsTick).Concat(slidesTick).Concat(airActionsTick)) { tickSet.Add(tick); } TickElement = new LinkedList <int?>(tickSet.Where(p => p >= startTick).OrderBy(p => p).Select(p => new int?(p))).First; BPMElement = new LinkedList <BPMChangeEvent>(NoteView.ScoreEvents.BPMChangeEvents.OrderBy(p => p.Tick)).First; // スタート時まで進める while (TickElement != null && TickElement.Value < startTick) { TickElement = TickElement.Next; } while (BPMElement.Next != null && BPMElement.Next.Value.Tick <= startTick) { BPMElement = BPMElement.Next; } int clapLatencyTick = GetLatencyTick(ClapSource.Latency, (double)BPMElement.Value.BPM); InitialTick = startTick - clapLatencyTick; CurrentTick = InitialTick; StartTick = startTick; TimeSpan startTime = GetTimeFromTick(startTick, NoteView.ScoreEvents.BPMChangeEvents); TimeSpan headGap = TimeSpan.FromSeconds(-music.Latency) - startTime; elapsedTick = 0; Task.Run(() => { LastSystemTick = Environment.TickCount; NoteView.Invoke((MethodInvoker)(() => Timer.Start())); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(Math.Max(ClapSource.Latency, 0))); if (headGap.TotalSeconds > 0) { System.Threading.Thread.Sleep(headGap); } if (!Playing) { return; } SoundManager.Play(music.FilePath, startTime + TimeSpan.FromSeconds(music.Latency)); }) .ContinueWith(p => { if (p.Exception != null) { Program.DumpExceptionTo(p.Exception, "sound_exception.json"); ExceptionThrown?.Invoke(this, EventArgs.Empty); } }); Playing = true; return(true); }