forked from NaiJi/project-kyoku
162 lines
3.7 KiB
C++
162 lines
3.7 KiB
C++
#pragma once
|
|
|
|
#include <set>
|
|
#include <memory>
|
|
#include <algorithm>
|
|
|
|
#include "core/time.h"
|
|
#include "core/note.h"
|
|
|
|
namespace kku
|
|
{
|
|
|
|
template <class TNote, class = std::enable_if_t<std::is_base_of<Note, TNote>::value>>
|
|
class Timeline
|
|
{
|
|
public:
|
|
explicit Timeline() :
|
|
_current_offset(0)
|
|
{}
|
|
|
|
typedef typename std::set<TNote*>::const_iterator Iterator;
|
|
|
|
void recalculate(const microsec& offset)
|
|
{
|
|
_current_offset = offset;
|
|
expire(_top_note);
|
|
|
|
if (!_timeline.empty())
|
|
{
|
|
Iterator head_iterator = _timeline.begin();
|
|
|
|
while (!isExpired(head_iterator))
|
|
{
|
|
if ((*head_iterator)->getPerfectOffset() >= offset)
|
|
{
|
|
Iterator pre_head = head_iterator;
|
|
--pre_head;
|
|
|
|
_top_note = !isExpired(pre_head) && (*pre_head)->isActive(offset)
|
|
? pre_head
|
|
: head_iterator;
|
|
break;
|
|
}
|
|
|
|
++head_iterator;
|
|
}
|
|
|
|
if (isExpired(_top_note))
|
|
_top_note = _timeline.begin();
|
|
}
|
|
}
|
|
|
|
void setNotes(const std::set<TNote*, NotePtrComparator>& notes)
|
|
{
|
|
_timeline = std::move(notes);
|
|
|
|
recalculate(_current_offset);
|
|
|
|
if (isExpired(_top_note))
|
|
return;
|
|
}
|
|
|
|
void insertNote(TNote* note)
|
|
{
|
|
_top_note = _timeline.insert(note).first;
|
|
recalculate(_current_offset);
|
|
update(_current_offset);
|
|
}
|
|
|
|
void insertNotes(const std::set<TNote*, NotePtrComparator>& notes)
|
|
{
|
|
_timeline.insert(notes.begin(), notes.end());
|
|
recalculate(_current_offset);
|
|
update(_current_offset);
|
|
|
|
}
|
|
|
|
inline void clear()
|
|
{
|
|
for (auto& note : _timeline)
|
|
delete note;
|
|
|
|
_timeline.clear();
|
|
}
|
|
|
|
void update(const microsec& offset)
|
|
{
|
|
_current_offset = offset;
|
|
updateTopNote(_current_offset);
|
|
}
|
|
|
|
Iterator getActiveNote(const microsec& music_offset) noexcept
|
|
{
|
|
Iterator return_note = _timeline.end();
|
|
|
|
auto note_iterator = _top_note;
|
|
while (!isExpired(note_iterator))
|
|
{
|
|
const auto& note = *note_iterator;
|
|
if (note->isActive(music_offset))
|
|
{
|
|
return_note = note_iterator;
|
|
break;
|
|
}
|
|
else if (note->getPerfectOffset() > music_offset)
|
|
break;
|
|
|
|
++note_iterator;
|
|
}
|
|
|
|
return return_note;
|
|
}
|
|
|
|
inline Iterator getNoteBy(const microsec& music_offset) noexcept
|
|
{
|
|
return std::find_if(_timeline.begin(), _timeline.end(),
|
|
[music_offset](const auto& note)
|
|
{
|
|
return note->getPerfectOffset() == music_offset;
|
|
});
|
|
}
|
|
|
|
inline bool isExpired(const Iterator& iterator) const noexcept
|
|
{
|
|
return iterator == _timeline.end();
|
|
}
|
|
|
|
inline void expire(Iterator& iterator) noexcept
|
|
{
|
|
iterator = _timeline.end();
|
|
}
|
|
|
|
inline Iterator getTopNote() const noexcept
|
|
{
|
|
return _top_note;
|
|
}
|
|
|
|
private:
|
|
std::set<TNote*, NotePtrComparator> _timeline;
|
|
microsec _current_offset;
|
|
|
|
inline void updateTopNote(const microsec& music_offset) noexcept
|
|
{
|
|
if (isExpired(_top_note))
|
|
return;
|
|
|
|
const auto& top_note = *_top_note;
|
|
|
|
bool already_played = top_note->getPerfectOffset() < music_offset
|
|
&& !top_note->isActive(music_offset);
|
|
|
|
if (already_played)
|
|
++_top_note;
|
|
}
|
|
|
|
Iterator _top_note;
|
|
};
|
|
|
|
}
|
|
|
|
|