diff --git a/src/audio_display.cpp b/src/audio_display.cpp index 2756ac3ff..3bf67e720 100644 --- a/src/audio_display.cpp +++ b/src/audio_display.cpp @@ -900,6 +900,14 @@ void AudioDisplay::PaintMarkers(wxDC &dc, TimeRange updtime) if (marker->GetFeet() & AudioMarker::Feet_Right) PaintFoot(dc, marker_x, 1); } + + + if (OPT_GET("Timing/Tap To Time")->GetBool()) { + dc.SetBrush(wxBrush(*wxGREEN)); + dc.SetPen(*wxTRANSPARENT_PEN); + int marker_x = RelativeXFromTime(controller->GetTimingController()->GetTapMarkerPosition()); + PaintTapMarker(dc, marker_x); + } } void AudioDisplay::PaintFoot(wxDC &dc, int marker_x, int dir) @@ -910,6 +918,12 @@ void AudioDisplay::PaintFoot(wxDC &dc, int marker_x, int dir) dc.DrawPolygon(3, foot_bot, marker_x, audio_top+audio_height); } +void AudioDisplay::PaintTapMarker(wxDC &dc, int marker_x) +{ + wxPoint arrow[3] = { wxPoint(-foot_size * 2, 0), wxPoint(0, -foot_size * 2), wxPoint(foot_size * 2, 0) }; + dc.DrawPolygon(3, arrow, marker_x, audio_top+audio_height); +} + void AudioDisplay::PaintLabels(wxDC &dc, TimeRange updtime) { std::vector labels; @@ -1239,6 +1253,7 @@ void AudioDisplay::OnAudioOpen(agi::AudioProvider *provider) OPT_SUB("Colour/Audio Display/Waveform", &AudioDisplay::ReloadRenderingSettings, this), OPT_SUB("Audio/Renderer/Spectrum/Quality", &AudioDisplay::ReloadRenderingSettings, this), OPT_SUB("Audio/Renderer/Spectrum/FreqCurve", &AudioDisplay::ReloadRenderingSettings, this), + OPT_SUB("Timing/Tap To Time", &AudioDisplay::OnTapMarkerChanged, this), }); OnTimingController(); } @@ -1264,10 +1279,12 @@ void AudioDisplay::OnTimingController() timing_controller->AddMarkerMovedListener(&AudioDisplay::OnMarkerMoved, this); timing_controller->AddUpdatedPrimaryRangeListener(&AudioDisplay::OnSelectionChanged, this); timing_controller->AddUpdatedStyleRangesListener(&AudioDisplay::OnStyleRangesChanged, this); + timing_controller->AddUpdatedTapMarkerListener(&AudioDisplay::OnTapMarkerChanged, this); OnStyleRangesChanged(); OnMarkerMoved(); OnSelectionChanged(); + OnTapMarkerChanged(); } } @@ -1351,6 +1368,12 @@ void AudioDisplay::OnStyleRangesChanged() RefreshRect(wxRect(0, audio_top, GetClientSize().GetWidth(), audio_height), false); } +void AudioDisplay::OnTapMarkerChanged() +{ + RefreshRect(wxRect(0, audio_top, GetClientSize().GetWidth(), audio_height), false); +} + + void AudioDisplay::OnMarkerMoved() { RefreshRect(wxRect(0, audio_top, GetClientSize().GetWidth(), audio_height), false); diff --git a/src/audio_display.h b/src/audio_display.h index 4c8e26a5f..272472d86 100644 --- a/src/audio_display.h +++ b/src/audio_display.h @@ -169,6 +169,11 @@ class AudioDisplay: public wxWindow { /// @param dir -1 for left, 1 for right void PaintFoot(wxDC &dc, int marker_x, int dir); + /// Draw an indicator for the tap marker + /// @param dc DC to paint to + /// @param marker_x Position of the tap marker + void PaintTapMarker(wxDC &dc, int marker_x); + /// Paint the labels in a time range /// @param dc DC to paint to /// @param updtime Time range to repaint @@ -205,6 +210,7 @@ class AudioDisplay: public wxWindow { void OnStyleRangesChanged(); void OnTimingController(); void OnMarkerMoved(); + void OnTapMarkerChanged(); public: AudioDisplay(wxWindow *parent, AudioController *controller, agi::Context *context); diff --git a/src/audio_karaoke.cpp b/src/audio_karaoke.cpp index 2abeb79d8..4643d500c 100644 --- a/src/audio_karaoke.cpp +++ b/src/audio_karaoke.cpp @@ -64,6 +64,7 @@ AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c) , file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this)) , audio_opened(c->project->AddAudioProviderListener(&AudioKaraoke::OnAudioOpened, this)) , active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this)) +, tap_to_time_toggled(OPT_SUB("Timing/Tap To Time", &AudioKaraoke::OnTapMarkerChanged, this)) , kara(agi::make_unique()) { using std::bind; @@ -134,6 +135,7 @@ void AudioKaraoke::SetEnabled(bool en) { if (enabled) { LoadFromLine(); c->audioController->SetTimingController(CreateKaraokeTimingController(c, kara.get(), file_changed)); + c->audioController->GetTimingController()->AddUpdatedTapMarkerListener(&AudioKaraoke::OnTapMarkerChanged, this); Refresh(false); } else { @@ -218,7 +220,15 @@ void AudioKaraoke::RenderText() { // Draw each character in the line int y = (bmp_size.GetHeight() - char_height) / 2; - for (size_t i = 0; i < spaced_text.size(); ++i) + for (size_t i = 0; i < spaced_text.size(); ++i) { + if (!(marked_syl_start <= i && i < marked_syl_end)) { + dc.DrawText(spaced_text[i], char_x[i], y); + } + } + + // Draw marked syllable + dc.SetTextForeground(*wxGREEN); + for (size_t i = marked_syl_start; i < marked_syl_end; ++i) dc.DrawText(spaced_text[i], char_x[i], y); // Draw the lines between each syllable @@ -332,6 +342,26 @@ void AudioKaraoke::OnScrollTimer(wxTimerEvent&) { OnScrollTimer(); } +void AudioKaraoke::OnTapMarkerChanged() { + marked_syl_start = 0; + marked_syl_end = 0; + + if (OPT_GET("Timing/Tap To Time")->GetBool() && kara->size() > 0) { + const AudioTimingController *tc = c->audioController->GetTimingController(); + const size_t marker_idx = tc->GetTapMarkerIndex(); + if (marker_idx > 0) { + marked_syl_start = syl_start_points[marker_idx - 1]; + marked_syl_end = + (marker_idx < syl_start_points.size() ? + syl_start_points[marker_idx] : + spaced_text.size()); + } + } + + RenderText(); + Refresh(false); +} + void AudioKaraoke::LoadFromLine() { scroll_x = 0; scroll_timer.Stop(); diff --git a/src/audio_karaoke.h b/src/audio_karaoke.h index 520f48682..c9a071d40 100644 --- a/src/audio_karaoke.h +++ b/src/audio_karaoke.h @@ -67,6 +67,7 @@ class AudioKaraoke final : public wxWindow { agi::signal::Connection audio_opened; ///< Audio opened connection agi::signal::Connection audio_closed; ///< Audio closed connection agi::signal::Connection active_line_changed; + agi::signal::Connection tap_to_time_toggled; /// Currently active dialogue line AssDialogue *active_line = nullptr; @@ -105,6 +106,9 @@ class AudioKaraoke final : public wxWindow { wxFont split_font; ///< Font used in the split/join interface + size_t marked_syl_start = 0; + size_t marked_syl_end = 0; + bool enabled = false; ///< Is karaoke mode enabled? wxButton *accept_button; ///< Accept pending splits button @@ -144,6 +148,7 @@ class AudioKaraoke final : public wxWindow { void OnAudioOpened(agi::AudioProvider *provider); void OnScrollTimer(); void OnScrollTimer(wxTimerEvent& event); + void OnTapMarkerChanged(); public: /// Constructor diff --git a/src/audio_timing.h b/src/audio_timing.h index 4c0ecf9cb..780a6c26a 100644 --- a/src/audio_timing.h +++ b/src/audio_timing.h @@ -57,6 +57,9 @@ protected: /// One or more rendering style ranges have changed in the timing controller. agi::signal::Signal<> AnnounceUpdatedStyleRanges; + /// The tap marker has changed in the timing controller. + agi::signal::Signal<> AnnounceUpdatedTapMarker; + public: /// @brief Get any warning message to show in the audio display /// @return The warning message to show, may be empty if there is none @@ -86,6 +89,12 @@ public: /// @param[out] ranges Rendering ranges will be added to this virtual void GetRenderingStyles(AudioRenderingStyleRanges &ranges) const = 0; + /// @brief Return the position of the tap marker + virtual int GetTapMarkerPosition() const = 0; + + /// @brief Return the index of the tap marker + virtual size_t GetTapMarkerIndex() const = 0; + enum NextMode { /// Advance to the next timing unit, whether it's a line or a sub-part /// of a line such as a karaoke syllable @@ -138,6 +147,15 @@ public: /// @param delta Amount to add in centiseconds virtual void ModifyStart(int delta) = 0; + /// Move tap marker position to given position + /// @param position to move marker to + virtual void MoveTapMarker(int ms) = 0; + + /// Go to next tap marker + /// @return True if moved to the next marker, False if tap marker is already + /// the last marker of the line + virtual bool NextTapMarker() = 0; + /// @brief Determine if a position is close to a draggable marker /// @param ms The time in milliseconds to test /// @param sensitivity Distance in milliseconds to consider markers as nearby @@ -178,6 +196,7 @@ public: DEFINE_SIGNAL_ADDERS(AnnounceUpdatedPrimaryRange, AddUpdatedPrimaryRangeListener) DEFINE_SIGNAL_ADDERS(AnnounceUpdatedStyleRanges, AddUpdatedStyleRangesListener) + DEFINE_SIGNAL_ADDERS(AnnounceUpdatedTapMarker, AddUpdatedTapMarkerListener) }; /// @brief Create a standard dialogue audio timing controller diff --git a/src/audio_timing_dialogue.cpp b/src/audio_timing_dialogue.cpp index 44e9c39b2..dce66b1ac 100644 --- a/src/audio_timing_dialogue.cpp +++ b/src/audio_timing_dialogue.cpp @@ -29,6 +29,7 @@ #include "ass_dialogue.h" #include "ass_file.h" +#include "audio_controller.h" #include "audio_marker.h" #include "audio_rendering_style.h" #include "audio_timing.h" @@ -327,6 +328,12 @@ class AudioTimingControllerDialogue final : public AudioTimingController { /// The time which was clicked on for alt-dragging mode, or INT_MIN if not in alt-draging mode int clicked_ms = INT_MIN; + /// Index of marker serving as tap marker + /// For AudioTimingControllerDialogue: + /// - 0 is left marker + /// - 1 is right marker + size_t tap_marker_idx = 0; + /// Autocommit option const agi::OptionValue *auto_commit = OPT_GET("Audio/Auto/Commit"); const agi::OptionValue *inactive_line_mode = OPT_GET("Audio/Inactive Lines Display Mode"); @@ -384,6 +391,8 @@ class AudioTimingControllerDialogue final : public AudioTimingController { public: // AudioMarkerProvider interface void GetMarkers(const TimeRange &range, AudioMarkerVector &out_markers) const override; + int GetTapMarkerPosition() const override; + size_t GetTapMarkerIndex() const override; // AudioTimingController interface void GetRenderingStyles(AudioRenderingStyleRanges &ranges) const override; @@ -395,9 +404,11 @@ public: void AddLeadOut() override; void ModifyLength(int delta, bool shift_following) override; void ModifyStart(int delta) override; + void MoveTapMarker(int ms) override; + bool NextTapMarker() override; bool IsNearbyMarker(int ms, int sensitivity, bool alt_down) const override; std::vector OnLeftClick(int ms, bool ctrl_down, bool alt_down, int sensitivity, int snap_range) override; - std::vector OnRightClick(int ms, bool, int sensitivity, int snap_range) override; + std::vector OnRightClick(int ms, bool ctrl_down, int sensitivity, int snap_range) override; void OnMarkerDrag(std::vector const& markers, int new_position, int snap_range) override; // We have no warning messages currently, maybe add the old "Modified" message back later? @@ -447,6 +458,23 @@ void AudioTimingControllerDialogue::GetMarkers(const TimeRange &range, AudioMark video_position_provider.GetMarkers(range, out_markers); } +int AudioTimingControllerDialogue::GetTapMarkerPosition() const +{ + assert(tap_marker_idx <= 1); + + if (tap_marker_idx == 0) { + return *active_line.GetLeftMarker(); + } else { + return *active_line.GetRightMarker(); + } +} + +size_t AudioTimingControllerDialogue::GetTapMarkerIndex() const +{ + assert(tap_marker_idx <= 1); + return tap_marker_idx; +} + void AudioTimingControllerDialogue::OnSelectedSetChanged() { RegenerateSelectedLines(); @@ -526,6 +554,7 @@ void AudioTimingControllerDialogue::DoCommit(bool user_triggered) void AudioTimingControllerDialogue::Revert() { commit_id = -1; + tap_marker_idx = 0; if (AssDialogue *line = context->selectionController->GetActiveLine()) { @@ -535,6 +564,7 @@ void AudioTimingControllerDialogue::Revert() AnnounceUpdatedPrimaryRange(); if (inactive_line_mode->GetInt() == 0) AnnounceUpdatedStyleRanges(); + AnnounceUpdatedTapMarker(); } else { @@ -570,6 +600,34 @@ void AudioTimingControllerDialogue::ModifyStart(int delta) { std::min(*m + delta * 10, *active_line.GetRightMarker()), 0); } +void AudioTimingControllerDialogue::MoveTapMarker(int ms) { + // Fix rounding error + ms = (ms + 5) / 10 * 10; + + DialogueTimingMarker *left = active_line.GetLeftMarker(); + DialogueTimingMarker *right = active_line.GetRightMarker(); + + clicked_ms = INT_MIN; + if (tap_marker_idx == 0) { + // Moving left marker (start time of the line) + if (ms > *right) SetMarkers({ right }, ms, 0); + SetMarkers({ left }, ms, 0); + } else { + // Moving right marker (end time of the line) + if (ms < *left) SetMarkers({ left }, ms, 0); + SetMarkers({ right }, ms, 0); + } +} + +bool AudioTimingControllerDialogue::NextTapMarker() { + if (tap_marker_idx == 0) { + tap_marker_idx = 1; + AnnounceUpdatedTapMarker(); + return true; + } + return false; +} + bool AudioTimingControllerDialogue::IsNearbyMarker(int ms, int sensitivity, bool alt_down) const { assert(sensitivity >= 0); @@ -609,6 +667,8 @@ std::vector AudioTimingControllerDialogue::OnLeftClick(int ms, boo ret = drag_timing->GetBool() ? GetRightMarkers() : jump; // Get ret before setting as setting may swap left/right SetMarkers(jump, ms, snap_range); + // Also change tap marker to left marker + tap_marker_idx = 0; return ret; } @@ -627,18 +687,34 @@ std::vector AudioTimingControllerDialogue::OnLeftClick(int ms, boo // Left-click within drag range should still move the left marker to the // clicked position, but not the right marker - if (clicked == left) + if (clicked == left) { SetMarkers(ret, ms, snap_range); + } + + // Also change tap marker + if (clicked == left) { + tap_marker_idx = 0; + } + else { + tap_marker_idx = 1; + } return ret; } -std::vector AudioTimingControllerDialogue::OnRightClick(int ms, bool, int sensitivity, int snap_range) +std::vector AudioTimingControllerDialogue::OnRightClick(int ms, bool ctrl_down, int sensitivity, int snap_range) { - clicked_ms = INT_MIN; - std::vector ret = GetRightMarkers(); - SetMarkers(ret, ms, snap_range); - return ret; + if (ctrl_down) { + context->audioController->PlayToEnd(ms); + return {}; + + } else { + clicked_ms = INT_MIN; + std::vector ret = GetRightMarkers(); + SetMarkers(ret, ms, snap_range); + tap_marker_idx = 1; + return ret; + } } void AudioTimingControllerDialogue::OnMarkerDrag(std::vector const& markers, int new_position, int snap_range) diff --git a/src/audio_timing_karaoke.cpp b/src/audio_timing_karaoke.cpp index 510c272c0..41f0e3b3c 100644 --- a/src/audio_timing_karaoke.cpp +++ b/src/audio_timing_karaoke.cpp @@ -81,6 +81,13 @@ class AudioTimingControllerKaraoke final : public AudioTimingController { size_t cur_syl = 0; ///< Index of currently selected syllable in the line + /// Index of marker serving as tap marker + /// For AudioControllerTimingKaraoke: + /// - 0 is start marker + /// - 1 to markers.size() is a regular syllable marker + /// - markers.size() + 1 is end marker + size_t tap_marker_idx = 0; + /// Pen used for the mid-syllable markers Pen separator_pen{"Colour/Audio Display/Syllable Boundaries", "Audio/Line Boundaries Thickness", wxPENSTYLE_DOT}; /// Pen used for the start-of-line marker @@ -112,11 +119,16 @@ class AudioTimingControllerKaraoke final : public AudioTimingController { void DoCommit(); void ApplyLead(bool announce_primary); int MoveMarker(KaraokeMarker *marker, int new_position); - void AnnounceChanges(int syl); + void MoveStartMarker(int new_position); + void MoveEndMarker(int new_position); + void CompressMarkers(size_t from, size_t to, int new_position); + void AnnounceChanges(bool announce_primary); public: // AudioTimingController implementation void GetMarkers(const TimeRange &range, AudioMarkerVector &out_markers) const override; + int GetTapMarkerPosition() const override; + size_t GetTapMarkerIndex() const override; wxString GetWarningMessage() const override { return ""; } TimeRange GetIdealVisibleTimeRange() const override; void GetRenderingStyles(AudioRenderingStyleRanges &ranges) const override; @@ -131,9 +143,11 @@ public: void AddLeadOut() override; void ModifyLength(int delta, bool shift_following) override; void ModifyStart(int delta) override; + void MoveTapMarker(int ms) override; + bool NextTapMarker() override; bool IsNearbyMarker(int ms, int sensitivity, bool) const override; std::vector OnLeftClick(int ms, bool, bool, int sensitivity, int) override; - std::vector OnRightClick(int ms, bool, int, int) override; + std::vector OnRightClick(int ms, bool ctrl_down, int, int) override; void OnMarkerDrag(std::vector const& marker, int new_position, int) override; AudioTimingControllerKaraoke(agi::Context *c, AssKaraoke *kara, agi::signal::Connection& file_changed); @@ -235,10 +249,29 @@ void AudioTimingControllerKaraoke::GetMarkers(TimeRange const& range, AudioMarke video_position_provider.GetMarkers(range, out); } +int AudioTimingControllerKaraoke::GetTapMarkerPosition() const { + assert(tap_marker_idx <= markers.size() + 1); + + if (tap_marker_idx == 0) { + return start_marker; + } + else if (tap_marker_idx < markers.size() + 1) { + return markers[tap_marker_idx-1]; + } + else { + return end_marker; + } +} + +size_t AudioTimingControllerKaraoke::GetTapMarkerIndex() const { + assert(tap_marker_idx <= markers.size() + 1); + return tap_marker_idx; +} + void AudioTimingControllerKaraoke::DoCommit() { active_line->Text = kara->GetText(); file_changed_slot.Block(); - commit_id = c->ass->Commit(_("karaoke timing"), AssFile::COMMIT_DIAG_TEXT, commit_id, active_line); + commit_id = c->ass->Commit(_("karaoke timing"), AssFile::COMMIT_DIAG_TEXT | AssFile::COMMIT_DIAG_TIME, commit_id, active_line); file_changed_slot.Unblock(); pending_changes = false; } @@ -254,6 +287,7 @@ void AudioTimingControllerKaraoke::Revert() { cur_syl = 0; commit_id = -1; pending_changes = false; + tap_marker_idx = 0; start_marker.Move(active_line->Start); end_marker.Move(active_line->End); @@ -273,6 +307,7 @@ void AudioTimingControllerKaraoke::Revert() { AnnounceUpdatedPrimaryRange(); AnnounceUpdatedStyleRanges(); AnnounceMarkerMoved(); + AnnounceUpdatedTapMarker(); } void AudioTimingControllerKaraoke::AddLeadIn() { @@ -293,7 +328,7 @@ void AudioTimingControllerKaraoke::ApplyLead(bool announce_primary) { kara->SetLineTimes(start_marker, end_marker); if (!announce_primary) AnnounceUpdatedStyleRanges(); - AnnounceChanges(announce_primary ? cur_syl : cur_syl + 2); + AnnounceChanges(announce_primary); } void AudioTimingControllerKaraoke::ModifyLength(int delta, bool shift_following) { @@ -314,13 +349,63 @@ void AudioTimingControllerKaraoke::ModifyLength(int delta, bool shift_following) for (; cur != end; cur += step) { MoveMarker(&markers[cur], markers[cur] + delta * 10); } - AnnounceChanges(cur_syl); + AnnounceChanges(true); } void AudioTimingControllerKaraoke::ModifyStart(int delta) { if (cur_syl == 0) return; MoveMarker(&markers[cur_syl - 1], markers[cur_syl - 1] + delta * 10); - AnnounceChanges(cur_syl); + AnnounceChanges(true); +} + +void AudioTimingControllerKaraoke::MoveTapMarker(int ms) { + // Fix rounding error + ms = (ms + 5) / 10 * 10; + + // Get syllable this time falls within + const size_t syl = distance(markers.begin(), lower_bound(markers.begin(), markers.end(), ms)); + + // Tapping automatically all of the necessary markers for the tap marker to + // land at the current audio position. Intuitively, it "pushes" or + // "compresses" all of the markers in front of the tap marker. The + // expectation is that the markers will reach their proper position once the + // user finishes tapping to the line + if (tap_marker_idx == 0) { + // Moving the start time of first syllable (i.e. start time of the line) + if (ms > end_marker) MoveEndMarker(ms); + if (syl > 0) CompressMarkers(syl-1, 0, ms); + MoveStartMarker(ms); + } + else if (tap_marker_idx < markers.size() + 1) { + // Moving the end time of a non-end syllable + if (ms < start_marker) MoveStartMarker(ms); + else if (ms > end_marker) MoveEndMarker(ms); + if (syl < tap_marker_idx) { + // Moving marker left + CompressMarkers(syl, tap_marker_idx-1, ms); + } + else { + // Moving marker right + CompressMarkers(syl-1, tap_marker_idx-1, ms); + } + } + else { + // Moving the end time of last syllable (i.e. end time of the line) + if (ms < start_marker) MoveStartMarker(ms); + if (syl < markers.size()) CompressMarkers(0, markers.size()-1, ms); + MoveEndMarker(ms); + } + + AnnounceChanges(true); +} + +bool AudioTimingControllerKaraoke::NextTapMarker() { + if (tap_marker_idx < markers.size() + 1) { + ++tap_marker_idx; + AnnounceUpdatedTapMarker(); + return true; + } + return false; } bool AudioTimingControllerKaraoke::IsNearbyMarker(int ms, int sensitivity, bool) const { @@ -350,18 +435,34 @@ std::vector AudioTimingControllerKaraoke::OnLeftClick(int ms, bool cur_syl = syl; + // Change tap marker + // Selecting a syllable moves the marker to the _end_ of that syllable, such + // that the next tap determines when that syllable ends. This behavior is + // more intuitive when coupled with AudioKaraoke's tap syllable highlight. + if (ms < start_marker.GetPosition()) { + tap_marker_idx = 0; + } + else { + tap_marker_idx = cur_syl + 1; + } + AnnounceUpdatedPrimaryRange(); AnnounceUpdatedStyleRanges(); + AnnounceUpdatedTapMarker(); return {}; } -std::vector AudioTimingControllerKaraoke::OnRightClick(int ms, bool, int, int) { - cur_syl = distance(markers.begin(), lower_bound(markers.begin(), markers.end(), ms)); +std::vector AudioTimingControllerKaraoke::OnRightClick(int ms, bool ctrl_down, int, int) { + if (ctrl_down) { + c->audioController->PlayToEnd(ms); - AnnounceUpdatedPrimaryRange(); - AnnounceUpdatedStyleRanges(); - c->audioController->PlayPrimaryRange(); + } else { + cur_syl = distance(markers.begin(), lower_bound(markers.begin(), markers.end(), ms)); + AnnounceUpdatedPrimaryRange(); + AnnounceUpdatedStyleRanges(); + c->audioController->PlayPrimaryRange(); + } return {}; } @@ -387,10 +488,57 @@ int AudioTimingControllerKaraoke::MoveMarker(KaraokeMarker *marker, int new_posi return syl; } -void AudioTimingControllerKaraoke::AnnounceChanges(int syl) { - if (syl < 0) return; +void AudioTimingControllerKaraoke::MoveStartMarker(int new_position) { + // No rearranging of syllables allowed + new_position = mid( + 0, + new_position, + markers[0].GetPosition()); - if (syl == cur_syl || syl == cur_syl + 1) { + if (new_position == start_marker.GetPosition()) + return; + + start_marker.Move(new_position); + + active_line->Start = (int)start_marker; + kara->SetLineTimes(start_marker, end_marker); + + labels.front().range = TimeRange(start_marker, labels.front().range.end()); +} + +void AudioTimingControllerKaraoke::MoveEndMarker(int new_position) { + // No rearranging of syllables allowed + new_position = mid( + markers.back().GetPosition(), + new_position, + INT_MAX); + + if (new_position == end_marker.GetPosition()) + return; + + end_marker.Move(new_position); + + active_line->End = (int)end_marker; + kara->SetLineTimes(start_marker, end_marker); + + labels.back().range = TimeRange(labels.back().range.begin(), end_marker); +} + +void AudioTimingControllerKaraoke::CompressMarkers(size_t from, size_t to, int new_position) { + int incr = (from < to ? 1 : -1); + size_t i = from; + for (;;) { + MoveMarker(&markers[i], new_position); + if (i == to) { + break; + } else { + i += incr; + } + } +} + +void AudioTimingControllerKaraoke::AnnounceChanges(bool announce_primary) { + if (announce_primary) { AnnounceUpdatedPrimaryRange(); AnnounceUpdatedStyleRanges(); } @@ -412,14 +560,15 @@ void AudioTimingControllerKaraoke::OnMarkerDrag(std::vector const& int syl = MoveMarker(static_cast(m[0]), new_position); if (syl < 0) return; + bool announce_primary = (syl == cur_syl || syl == cur_syl + 1); if (m.size() > 1) { int delta = m[0]->GetPosition() - old_position; for (AudioMarker *marker : m | boost::adaptors::sliced(1, m.size())) MoveMarker(static_cast(marker), marker->GetPosition() + delta); - syl = cur_syl; + announce_primary = true; } - AnnounceChanges(syl); + AnnounceChanges(announce_primary); } void AudioTimingControllerKaraoke::GetLabels(TimeRange const& range, std::vector &out) const { diff --git a/src/bitmaps/button/time_opt_tap_to_time_16.png b/src/bitmaps/button/time_opt_tap_to_time_16.png new file mode 100644 index 000000000..0e433cd22 Binary files /dev/null and b/src/bitmaps/button/time_opt_tap_to_time_16.png differ diff --git a/src/bitmaps/button/time_opt_tap_to_time_24.png b/src/bitmaps/button/time_opt_tap_to_time_24.png new file mode 100644 index 000000000..5056a5fa0 Binary files /dev/null and b/src/bitmaps/button/time_opt_tap_to_time_24.png differ diff --git a/src/bitmaps/button/time_opt_tap_to_time_32.png b/src/bitmaps/button/time_opt_tap_to_time_32.png new file mode 100644 index 000000000..ad1972d7d Binary files /dev/null and b/src/bitmaps/button/time_opt_tap_to_time_32.png differ diff --git a/src/bitmaps/button/time_opt_tap_to_time_48.png b/src/bitmaps/button/time_opt_tap_to_time_48.png new file mode 100644 index 000000000..2e5d4ae1f Binary files /dev/null and b/src/bitmaps/button/time_opt_tap_to_time_48.png differ diff --git a/src/bitmaps/button/time_opt_tap_to_time_64.png b/src/bitmaps/button/time_opt_tap_to_time_64.png new file mode 100644 index 000000000..2abe07f52 Binary files /dev/null and b/src/bitmaps/button/time_opt_tap_to_time_64.png differ diff --git a/src/bitmaps/button/time_tap_connect_16.png b/src/bitmaps/button/time_tap_connect_16.png new file mode 100644 index 000000000..da5b51afc Binary files /dev/null and b/src/bitmaps/button/time_tap_connect_16.png differ diff --git a/src/bitmaps/button/time_tap_connect_24.png b/src/bitmaps/button/time_tap_connect_24.png new file mode 100644 index 000000000..96ca89f3f Binary files /dev/null and b/src/bitmaps/button/time_tap_connect_24.png differ diff --git a/src/bitmaps/button/time_tap_connect_32.png b/src/bitmaps/button/time_tap_connect_32.png new file mode 100644 index 000000000..4b3c62f22 Binary files /dev/null and b/src/bitmaps/button/time_tap_connect_32.png differ diff --git a/src/bitmaps/button/time_tap_connect_48.png b/src/bitmaps/button/time_tap_connect_48.png new file mode 100644 index 000000000..22dd8eaa0 Binary files /dev/null and b/src/bitmaps/button/time_tap_connect_48.png differ diff --git a/src/bitmaps/button/time_tap_connect_64.png b/src/bitmaps/button/time_tap_connect_64.png new file mode 100644 index 000000000..e7801eda1 Binary files /dev/null and b/src/bitmaps/button/time_tap_connect_64.png differ diff --git a/src/bitmaps/button/time_tap_no_connect_16.png b/src/bitmaps/button/time_tap_no_connect_16.png new file mode 100644 index 000000000..e685f1093 Binary files /dev/null and b/src/bitmaps/button/time_tap_no_connect_16.png differ diff --git a/src/bitmaps/button/time_tap_no_connect_24.png b/src/bitmaps/button/time_tap_no_connect_24.png new file mode 100644 index 000000000..1b1ce0e04 Binary files /dev/null and b/src/bitmaps/button/time_tap_no_connect_24.png differ diff --git a/src/bitmaps/button/time_tap_no_connect_32.png b/src/bitmaps/button/time_tap_no_connect_32.png new file mode 100644 index 000000000..1145a5d16 Binary files /dev/null and b/src/bitmaps/button/time_tap_no_connect_32.png differ diff --git a/src/bitmaps/button/time_tap_no_connect_48.png b/src/bitmaps/button/time_tap_no_connect_48.png new file mode 100644 index 000000000..cfbd7c628 Binary files /dev/null and b/src/bitmaps/button/time_tap_no_connect_48.png differ diff --git a/src/bitmaps/button/time_tap_no_connect_64.png b/src/bitmaps/button/time_tap_no_connect_64.png new file mode 100644 index 000000000..a56b48142 Binary files /dev/null and b/src/bitmaps/button/time_tap_no_connect_64.png differ diff --git a/src/bitmaps/manifest.respack b/src/bitmaps/manifest.respack index 25e6fa33d..00e744edc 100644 --- a/src/bitmaps/manifest.respack +++ b/src/bitmaps/manifest.respack @@ -446,6 +446,21 @@ button/substart_to_video_24.png button/substart_to_video_32.png button/substart_to_video_48.png button/substart_to_video_64.png +button/time_tap_connect_16.png +button/time_tap_connect_24.png +button/time_tap_connect_32.png +button/time_tap_connect_48.png +button/time_tap_connect_64.png +button/time_tap_no_connect_16.png +button/time_tap_no_connect_24.png +button/time_tap_no_connect_32.png +button/time_tap_no_connect_48.png +button/time_tap_no_connect_64.png +button/time_opt_tap_to_time_16.png +button/time_opt_tap_to_time_24.png +button/time_opt_tap_to_time_32.png +button/time_opt_tap_to_time_48.png +button/time_opt_tap_to_time_64.png button/timing_processor_toolbutton_16.png button/timing_processor_toolbutton_24.png button/timing_processor_toolbutton_32.png diff --git a/src/command/time.cpp b/src/command/time.cpp index c44216db2..c7b2c8ecc 100644 --- a/src/command/time.cpp +++ b/src/command/time.cpp @@ -39,6 +39,7 @@ #include "../dialogs.h" #include "../include/aegisub/context.h" #include "../libresrc/libresrc.h" +#include "../options.h" #include "../project.h" #include "../selection_controller.h" #include "../video_controller.h" @@ -50,6 +51,10 @@ namespace { using cmd::Command; +static inline void toggle(const char *opt) { + OPT_SET(opt)->SetBool(!OPT_GET(opt)->GetBool()); +} + struct validate_video_loaded : public Command { CMD_TYPE(COMMAND_VALIDATE) bool Validate(const agi::Context *c) override { @@ -385,6 +390,93 @@ struct time_prev final : public Command { c->audioController->GetTimingController()->Prev(); } }; + +struct time_tap_connect final : public Command { + CMD_NAME("time/tap/connect") + CMD_ICON(time_tap_connect) + STR_MENU("Time tap connect") + STR_DISP("Time tap connect") + STR_HELP("Set tap marker to audio position, connect next line's start") + CMD_TYPE(COMMAND_VALIDATE) + + bool Validate(const agi::Context *c) override { + return + OPT_GET("Timing/Tap To Time")->GetBool() && + c->audioController->IsPlaying(); + } + + void operator()(agi::Context *c) override { + if (c->audioController->IsPlaying()) { + AudioTimingController *tc = c->audioController->GetTimingController(); + if (tc) { + int ms = c->audioController->GetPlaybackPosition(); + + tc->MoveTapMarker(ms); + bool moved_marker = tc->NextTapMarker(); + if (!moved_marker && + OPT_GET("Audio/Auto/Commit")->GetBool() && + OPT_GET("Audio/Next Line on Commit")->GetBool()) { + // go to next line, and then tap again to connect start to the same + // time + c->selectionController->NextLine(); + tc->MoveTapMarker(ms); + tc->NextTapMarker(); + } + } + } + } +}; + +struct time_tap_no_connect final : public Command { + CMD_NAME("time/tap/no_connect") + CMD_ICON(time_tap_no_connect) + STR_MENU("Tap marker no connect") + STR_DISP("Tap marker no connect") + STR_HELP("Set tap marker to audio position, do not connect next line's start") + CMD_TYPE(COMMAND_VALIDATE) + + bool Validate(const agi::Context *c) override { + return + OPT_GET("Timing/Tap To Time")->GetBool() && + c->audioController->IsPlaying(); + } + + void operator()(agi::Context *c) override { + if (c->audioController->IsPlaying()) { + AudioTimingController *tc = c->audioController->GetTimingController(); + if (tc) { + int ms = c->audioController->GetPlaybackPosition(); + + tc->MoveTapMarker(ms); + bool moved_marker = tc->NextTapMarker(); + if (!moved_marker && + OPT_GET("Audio/Auto/Commit")->GetBool() && + OPT_GET("Audio/Next Line on Commit")->GetBool()) { + // go to next line, but don't do anything more + c->selectionController->NextLine(); + } + } + } + } +}; + +struct time_opt_tap_to_time final : public Command { + CMD_NAME("time/opt/tap_to_time") + CMD_ICON(time_opt_tap_to_time) + STR_MENU("Enable tap-to-time UI") + STR_DISP("Enable tap-to-time UI") + STR_HELP("Enable tap-to-time UI") + CMD_TYPE(COMMAND_TOGGLE) + + bool IsActive(const agi::Context *) override { + return OPT_GET("Timing/Tap To Time")->GetBool(); + } + + void operator()(agi::Context *) override { + toggle("Timing/Tap To Time"); + } +}; + } namespace cmd { @@ -399,7 +491,10 @@ namespace cmd { reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); + reg(agi::make_unique()); + reg(agi::make_unique()); reg(agi::make_unique()); + reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); diff --git a/src/hotkey.cpp b/src/hotkey.cpp index deb624628..3283c1eb4 100644 --- a/src/hotkey.cpp +++ b/src/hotkey.cpp @@ -57,6 +57,12 @@ namespace { {nullptr} }; + const char *added_hotkeys_time_tap[][3] = { + {"time/tap/connect", "Audio", "I"}, + {"time/tap/no_connect", "Audio", "O"}, + {nullptr} + }; + void migrate_hotkeys(const char *added[][3]) { auto hk_map = hotkey::inst->GetHotkeyMap(); bool changed = false; @@ -131,6 +137,11 @@ void init() { } #endif + if (boost::find(migrations, "time/tap") == end(migrations)) { + migrate_hotkeys(added_hotkeys_time_tap); + migrations.emplace_back("time/tap"); + } + OPT_SET("App/Hotkey Migrations")->SetListString(std::move(migrations)); } diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 240998fdb..41b04d5b8 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -471,7 +471,8 @@ }, "Timing" : { - "Default Duration" : 3000 + "Default Duration" : 3000, + "Tap To Time" : false }, "Tool" : { diff --git a/src/libresrc/default_hotkey.json b/src/libresrc/default_hotkey.json index 789ab0d78..43ecca9c9 100644 --- a/src/libresrc/default_hotkey.json +++ b/src/libresrc/default_hotkey.json @@ -32,6 +32,12 @@ ], "time/start/increase" : [ "KP_6" + ], + "time/mark/connect" : [ + "I" + ], + "time/mark/no_connect" : [ + "O" ] }, "Audio" : { diff --git a/src/libresrc/default_toolbar.json b/src/libresrc/default_toolbar.json index ad52b2e5b..a47a0fb23 100644 --- a/src/libresrc/default_toolbar.json +++ b/src/libresrc/default_toolbar.json @@ -15,6 +15,9 @@ "time/lead/in", "time/lead/out", "", + "time/tap/connect", + "time/tap/no_connect", + "", "audio/commit", "audio/go_to", "", @@ -23,6 +26,7 @@ "audio/opt/autoscroll", "audio/opt/spectrum", "app/toggle/global_hotkeys", + "time/opt/tap_to_time", "", "audio/karaoke" ], diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 01acf37da..e39820c82 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -471,7 +471,8 @@ }, "Timing" : { - "Default Duration" : 3000 + "Default Duration" : 3000, + "Tap To Time" : false }, "Tool" : {