New features :

- Note collisions are now highlighted in red, sometimes highlights weird spots but otherwise totally serviceable as is

Small fixes :
- The timeline now correctly scrubs to before the song starts
- Claps now do happen after rewinding with the arrow keys
This commit is contained in:
Stepland 2019-03-24 21:51:33 +01:00
parent ddc6ff8bb3
commit a314f1efe5
4 changed files with 181 additions and 77 deletions

View File

@ -88,10 +88,13 @@ void EditorState::setPlaybackAndMusicPosition(sf::Time newPosition) {
} else if (newPosition > chartRuntime) {
newPosition = chartRuntime;
}
previousPos = sf::seconds(newPosition.asSeconds() - 1.f/60.f);
playbackPosition = newPosition;
if (music) {
if (playbackPosition.asSeconds() >= 0 and playbackPosition < music->getDuration()) {
music->setPlayingOffset(playbackPosition);
} else {
music->stop();
}
}
}
@ -105,48 +108,56 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
float squareSize = ImGui::GetWindowSize().x / 4.f;
float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x;
int ImGuiIndex = 0;
if (chart) {
int ImGuiIndex = 0;
for (auto note : visibleNotes) {
for (auto const& note : visibleNotes) {
float note_offset = (playbackPosition.asSeconds() - getSecondsAt(note.getTiming()));
auto frame = static_cast<long long int>(std::floor(note_offset * 30.f));
int x = note.getPos()%4;
int y = note.getPos()/4;
int x = note.getPos() % 4;
int y = note.getPos() / 4;
if (note.getLength() == 0) {
auto t = marker.getSprite(markerEndingState,note_offset);
// Display normal notes
auto t = marker.getSprite(markerEndingState, note_offset);
if (t) {
ImGui::SetCursorPos({x*squareSize,TitlebarHeight + y*squareSize});
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushID(ImGuiIndex);
ImGui::Image(*t,{squareSize,squareSize});
ImGui::Image(*t, {squareSize, squareSize});
ImGui::PopID();
++ImGuiIndex;
}
} else {
float tail_end_in_seconds = getSecondsAt(note.getTiming()+note.getLength());
// Display long notes
float tail_end_in_seconds = getSecondsAt(note.getTiming() + note.getLength());
float tail_end_offset = playbackPosition.asSeconds() - tail_end_in_seconds;
if (playbackPosition.asSeconds() < tail_end_in_seconds) {
// Before or During the long note
int triangle = note.getTail_pos_as_note_pos();
auto triangle_x = static_cast<float>(triangle%4);
auto triangle_y = static_cast<float>(triangle/4);
auto triangle_x = static_cast<float>(triangle % 4);
auto triangle_y = static_cast<float>(triangle / 4);
AffineTransform<float> x_trans(0.0f,ticksToSeconds(note.getLength()),triangle_x, static_cast<float>(x));
AffineTransform<float> y_trans(0.0f,ticksToSeconds(note.getLength()),triangle_y, static_cast<float>(y));
AffineTransform<float> x_trans(0.0f, ticksToSeconds(note.getLength()), triangle_x,
static_cast<float>(x));
AffineTransform<float> y_trans(0.0f, ticksToSeconds(note.getLength()), triangle_y,
static_cast<float>(y));
triangle_x = x_trans.clampedTransform(note_offset);
triangle_y = y_trans.clampedTransform(note_offset);
auto tail_tex = playfield.longNoteMarker.getTailTexture(note_offset,note.getTail_pos());
auto tail_tex = playfield.longNoteMarker.getTailTexture(note_offset, note.getTail_pos());
if (tail_tex) {
ImVec2 cursorPos;
@ -156,37 +167,37 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
// Before the note : tail goes from triangle tip to note edge
switch (note.getTail_pos()%4) {
switch (note.getTail_pos() % 4) {
// going down
case 0:
cursorPos.x = x*squareSize;
cursorPos.y = (triangle_y+1)*squareSize;
cursorPos.x = x * squareSize;
cursorPos.y = (triangle_y + 1) * squareSize;
texSize.x = squareSize;
texSize.y = (y - triangle_y - 1)*squareSize;
texSize.y = (y - triangle_y - 1) * squareSize;
break;
// going left (to the left, to the left ...)
// going left (to the left, to the left ...)
case 1:
cursorPos.x = (x+1)*squareSize;
cursorPos.y = y*squareSize;
texSize.x = (triangle_x - x - 1)*squareSize;
cursorPos.x = (x + 1) * squareSize;
cursorPos.y = y * squareSize;
texSize.x = (triangle_x - x - 1) * squareSize;
texSize.y = squareSize;
break;
// going up
// going up
case 2:
cursorPos.x = x*squareSize;
cursorPos.y = (y+1)*squareSize;
cursorPos.x = x * squareSize;
cursorPos.y = (y + 1) * squareSize;
texSize.x = squareSize;
texSize.y = (triangle_y - y - 1)*squareSize;
texSize.y = (triangle_y - y - 1) * squareSize;
break;
// going right
// going right
case 3:
cursorPos.x = (triangle_x+1)*squareSize;
cursorPos.y = y*squareSize;
texSize.x = (x - triangle_x - 1)*squareSize;
cursorPos.x = (triangle_x + 1) * squareSize;
cursorPos.y = y * squareSize;
texSize.x = (x - triangle_x - 1) * squareSize;
texSize.y = squareSize;
break;
@ -198,37 +209,37 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
// During the note : tail goes from triangle base to note edge
switch (note.getTail_pos()%4) {
switch (note.getTail_pos() % 4) {
// going down
case 0:
cursorPos.x = x*squareSize;
cursorPos.y = (triangle_y + 0.9f)*squareSize;
cursorPos.x = x * squareSize;
cursorPos.y = (triangle_y + 0.9f) * squareSize;
texSize.x = squareSize;
texSize.y = (y - triangle_y - 0.9f)*squareSize;
texSize.y = (y - triangle_y - 0.9f) * squareSize;
break;
// going left (to the left, to the left ...)
// going left (to the left, to the left ...)
case 1:
cursorPos.x = (x+1)*squareSize;
cursorPos.y = y*squareSize;
texSize.x = (triangle_x - x - 0.9f)*squareSize;
cursorPos.x = (x + 1) * squareSize;
cursorPos.y = y * squareSize;
texSize.x = (triangle_x - x - 0.9f) * squareSize;
texSize.y = squareSize;
break;
// going up
// going up
case 2:
cursorPos.x = x*squareSize;
cursorPos.y = (y+1)*squareSize;
cursorPos.x = x * squareSize;
cursorPos.y = (y + 1) * squareSize;
texSize.x = squareSize;
texSize.y = (triangle_y - y - 0.9f)*squareSize;
texSize.y = (triangle_y - y - 0.9f) * squareSize;
break;
// going right
// going right
case 3:
cursorPos.x = (triangle_x + 0.9f)*squareSize;
cursorPos.y = y*squareSize;
texSize.x = (x - triangle_x - 0.9f)*squareSize;
cursorPos.x = (triangle_x + 0.9f) * squareSize;
cursorPos.y = y * squareSize;
texSize.x = (x - triangle_x - 0.9f) * squareSize;
texSize.y = squareSize;
break;
@ -242,44 +253,45 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
ImGui::SetCursorPos(cursorPos);
ImGui::PushID(ImGuiIndex);
ImGui::Image(*tail_tex,texSize);
ImGui::Image(*tail_tex, texSize);
ImGui::PopID();
++ImGuiIndex;
Toolbox::displayIfHasValue(
playfield.longNoteMarker.getSquareBackgroundTexture(note_offset, note.getTail_pos()),
{x*squareSize,TitlebarHeight + y*squareSize},
{squareSize,squareSize},
playfield.longNoteMarker.getSquareBackgroundTexture(note_offset,
note.getTail_pos()),
{x * squareSize, TitlebarHeight + y * squareSize},
{squareSize, squareSize},
ImGuiIndex
);
);
Toolbox::displayIfHasValue(
playfield.longNoteMarker.getSquareOutlineTexture(note_offset, note.getTail_pos()),
{x*squareSize,TitlebarHeight + y*squareSize},
{squareSize,squareSize},
{x * squareSize, TitlebarHeight + y * squareSize},
{squareSize, squareSize},
ImGuiIndex
);
Toolbox::displayIfHasValue(
playfield.longNoteMarker.getTriangleTexture(note_offset, note.getTail_pos()),
{triangle_x*squareSize,TitlebarHeight + triangle_y*squareSize},
{squareSize,squareSize},
{triangle_x * squareSize, TitlebarHeight + triangle_y * squareSize},
{squareSize, squareSize},
ImGuiIndex
);
Toolbox::displayIfHasValue(
playfield.longNoteMarker.getSquareHighlightTexture(note_offset, note.getTail_pos()),
{x*squareSize,TitlebarHeight + y*squareSize},
{squareSize,squareSize},
{x * squareSize, TitlebarHeight + y * squareSize},
{squareSize, squareSize},
ImGuiIndex
);
// Display the beginning marker
auto t = marker.getSprite(markerEndingState,note_offset);
auto t = marker.getSprite(markerEndingState, note_offset);
if (t) {
ImGui::SetCursorPos({x*squareSize,TitlebarHeight + y*squareSize});
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushID(ImGuiIndex);
ImGui::Image(*t,{squareSize,squareSize});
ImGui::Image(*t, {squareSize, squareSize});
ImGui::PopID();
++ImGuiIndex;
}
@ -287,13 +299,13 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
} else {
// Display the ending marker
// After long note end : Display the ending marker
if (tail_end_offset > 0.0f) {
auto t = marker.getSprite(markerEndingState,tail_end_offset);
auto t = marker.getSprite(markerEndingState, tail_end_offset);
if (t) {
ImGui::SetCursorPos({x*squareSize,TitlebarHeight + y*squareSize});
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushID(ImGuiIndex);
ImGui::Image(*t,{squareSize,squareSize});
ImGui::Image(*t, {squareSize, squareSize});
ImGui::PopID();
++ImGuiIndex;
}
@ -303,7 +315,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
}
}
// display buttons over
// Display button grid
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
ImGui::PushID(x+4*y);
@ -318,6 +330,83 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
ImGui::PopID();
}
}
// Check for collisions then display them
if (chart) {
std::array<std::vector<Note>, 16> notes_per_position = {};
std::array<bool, 16> collisions = {};
for (auto const& note : visibleNotes) {
notes_per_position[note.getPos()].push_back(note);
}
for (int i = 0; i < 16; ++i) {
int size = notes_per_position.at(i).size();
if (size > 1) {
collisions.at(i) = true;
} else if (size == 1) {
auto note = notes_per_position.at(i).at(0);
int lower_bound = static_cast<int>(getTicksAt(getSecondsAt(note.getTiming())-1.f));
int upper_bound = static_cast<int>(getTicksAt(getSecondsAt(note.getTiming())+1.f));
auto current_note_it = chart->ref.Notes.find(note);
if (current_note_it != chart->ref.Notes.end()) {
// backwards
if (current_note_it != chart->ref.Notes.begin()) {
auto other_note_it = current_note_it;
--other_note_it;
while (other_note_it != chart->ref.Notes.begin()) {
if (other_note_it->getPos() == note.getPos()) {
if (other_note_it->getTiming() > lower_bound) {
collisions.at(i) = true;
break;
} else {
break;
}
}
--other_note_it;
}
}
// forwards
auto other_note_it = current_note_it;
++other_note_it;
while (other_note_it != chart->ref.Notes.end()) {
if (other_note_it->getPos() == note.getPos()) {
if (other_note_it->getTiming() < upper_bound) {
collisions.at(i) = true;
break;
} else {
break;
}
}
++other_note_it;
}
}
} else {
collisions.at(i) = false;
}
}
for (int i = 0; i < 16; ++i) {
if (collisions.at(i)) {
int x = i%4;
int y = i/4;
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushID(ImGuiIndex);
ImGui::Image(playfield.note_collision, {squareSize, squareSize});
ImGui::PopID();
++ImGuiIndex;
}
}
}
}
ImGui::End();
@ -446,7 +535,6 @@ void EditorState::displayTimeline() {
ImGui::SetCursorPos({0,0});
if(ImGui::VSliderFloat("",ImGui::GetContentRegionMax(),&slider_pos,0.f,1.f,"")) {
setPlaybackAndMusicPosition(sf::seconds(scroll.backwards_transform(slider_pos)));
this->lastTimingTicked = -1;
}
}
}

View File

@ -38,4 +38,9 @@ Widgets::Playfield::Playfield() {
throw std::runtime_error("Unable to load texture " + button_path);
}
button_pressed.setSmooth(true);
if (!note_collision.loadFromFile(button_path,{576,0,192,192})) {
std::cerr << "Unable to load texture " << button_path;
throw std::runtime_error("Unable to load texture " + button_path);
}
note_collision.setSmooth(true);
}

View File

@ -33,6 +33,7 @@ namespace Widgets {
Playfield();
sf::Texture button;
sf::Texture button_pressed;
sf::Texture note_collision;
LNMarker longNoteMarker;
private:

View File

@ -10,7 +10,6 @@
int main(int argc, char** argv) {
// TODO : Highlight crossing notes
// TODO : Different noise for chords
// TODO : Density graph on the timeline
// TODO : Pitch control (playback speed factor)
@ -96,9 +95,13 @@ int main(int argc, char** argv) {
if (editorState->musicVolume < 10) {
editorState->musicVolume++;
editorState->updateMusicVolume();
std::stringstream ss;
ss << "Music Volume : " << editorState->musicVolume*10 << "%";
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
}
}
} else {
// TODO : there is something weird with the way I'm doing this, going back with the key often makes it go back twice
if (editorState and editorState->chart) {
float floatTicks = editorState->getTicks();
int prevTick = static_cast<int>(floorf(floatTicks));
@ -119,6 +122,9 @@ int main(int argc, char** argv) {
if (editorState->musicVolume > 0) {
editorState->musicVolume--;
editorState->updateMusicVolume();
std::stringstream ss;
ss << "Music Volume : " << editorState->musicVolume*10 << "%";
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
}
}
} else {
@ -134,27 +140,33 @@ int main(int argc, char** argv) {
case sf::Keyboard::Left:
if (editorState and editorState->chart) {
editorState->snap = Toolbox::getPreviousDivisor(editorState->chart->ref.getResolution(),editorState->snap);
std::stringstream ss;
ss << "Snap : " << Toolbox::toOrdinal(4*editorState->snap);
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
}
break;
case sf::Keyboard::Right:
if (editorState and editorState->chart) {
editorState->snap = Toolbox::getNextDivisor(editorState->chart->ref.getResolution(),editorState->snap);
std::stringstream ss;
ss << "Snap : " << Toolbox::toOrdinal(4*editorState->snap);
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
}
break;
case sf::Keyboard::F3:
playBeatTick = not playBeatTick;
if (playBeatTick) {
notificationsQueue.push(std::make_shared<TextNotification>("Beat tick: on"));
notificationsQueue.push(std::make_shared<TextNotification>("Beat tick : on"));
} else {
notificationsQueue.push(std::make_shared<TextNotification>("Beat tick: off"));
notificationsQueue.push(std::make_shared<TextNotification>("Beat tick : off"));
}
break;
case sf::Keyboard::F4:
playNoteTick = not playNoteTick;
if (playNoteTick) {
notificationsQueue.push(std::make_shared<TextNotification>("Note tick: on"));
notificationsQueue.push(std::make_shared<TextNotification>("Note tick : on"));
} else {
notificationsQueue.push(std::make_shared<TextNotification>("Note tick: off"));
notificationsQueue.push(std::make_shared<TextNotification>("Note tick : off"));
}
break;
case sf::Keyboard::Space:
@ -177,6 +189,7 @@ int main(int argc, char** argv) {
case sf::Keyboard::S:
if (event.key.control) {
ESHelper::save(*editorState);
notificationsQueue.push(std::make_shared<TextNotification>("Saved file"));
}
break;
case sf::Keyboard::Y:
@ -249,14 +262,11 @@ int main(int argc, char** argv) {
for (auto note : editorState->visibleNotes) {
float noteTiming = editorState->getSecondsAt(note.getTiming());
if (noteTiming >= editorState->previousPos.asSeconds()
and noteTiming <= editorState->playbackPosition.asSeconds()
and note.getTiming() > editorState->lastTimingTicked) {
and noteTiming <= editorState->playbackPosition.asSeconds()) {
noteTickSound.play();
editorState->lastTimingTicked = note.getTiming();
break;
}
}
} else {
editorState->lastTimingTicked = -1;
}
if (editorState->playbackPosition >= editorState->chartRuntime) {
editorState->playing = false;
@ -314,7 +324,7 @@ int main(int argc, char** argv) {
if (c) {
editorState->showNewChartDialog = false;
if(editorState->fumen.Charts.try_emplace(c->dif_name,*c).second) {
editorState->chart->ref = editorState->fumen.Charts[c->dif_name];
editorState->chart.emplace(editorState->fumen.Charts.at(c->dif_name));
}
}
} else {