Audio/Timing: implement tap-to-time

Tap-to-time provides the user the ability to tap to the lyrics/syllables
of the song in order to time lines or karaoke. It consists of these
extra UI interactions:

- **Indicator**: tap marker: a designated marker that can be moved to
  the current audio position; indicated in:
  - the audio display by a green arrow underneath a marker
  - the karaoke display by a green-colored syllable
- **Control**: tap marker: the tap marker can be changed by selecting
  syllables on audio display in karaoke mode, or clicking the markers on
  audio display in dialogue mode
- **Control**: ctrl-right-click audio display: starts playing the audio
  from that exact position until the end of the file
- **Option**: Timing/Tap To Time: enables the tap marker indicator and
  commands
- **Button**: time_opt_tap_to_time: toggles the Timing/Tap To Time option
- **Button**: time_tap_connect (hotkey I): a command that:
  - moves the tap marker's position to the current playing audio
    position
  - sets the next marker to be the tap marker
  - if the tap marker is already the last marker AND BOTH autocommit AND
    next-line-on-commit is ON, will move onto the next line
  - if moved on to the next line, also sets the start marker to the current
    audio position, so the two lines are connected, and moves to the
    next tap marker (essentially reinvoking time_tap_connect once)
- **Button**: time_tap_no_connect (hotkey O): similar to
  time_tap_connect, except it will not set the next line's start
  position even if moved to the next line

Expected workflow:
1) User loads song lyrics
2) User splits each line into syllables
3) User turns on tap-to-time, autocommit, and next-line-on-commit
4) User plays audio from beginning, tapping time_tap_connect to each
syllable, occasionally tapping time_tap_no_connect when a break between
lines is desired
5) If user messes up a line, they can set the tap marker to where they
want to restart from, and ctrl-right-click to start the audio a few
seconds before it
6) Syllables can be split/merged at will, and adjustments to timing can
be done using normal karaoke timing controls
This commit is contained in:
Vincent Wong 2018-10-21 00:42:33 -07:00 committed by odrling
parent c48150008b
commit 4d12bbfb21
No known key found for this signature in database
GPG Key ID: E24CA7508C27AF5B
29 changed files with 467 additions and 26 deletions

View File

@ -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<AudioLabelProvider::AudioLabel> 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);

View File

@ -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);

View File

@ -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<AssKaraoke>())
{
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();

View File

@ -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

View File

@ -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

View File

@ -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<AudioMarker*> OnLeftClick(int ms, bool ctrl_down, bool alt_down, int sensitivity, int snap_range) override;
std::vector<AudioMarker*> OnRightClick(int ms, bool, int sensitivity, int snap_range) override;
std::vector<AudioMarker*> OnRightClick(int ms, bool ctrl_down, int sensitivity, int snap_range) override;
void OnMarkerDrag(std::vector<AudioMarker*> 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<int>(*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<AudioMarker*> 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<AudioMarker*> 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<AudioMarker*> AudioTimingControllerDialogue::OnRightClick(int ms, bool, int sensitivity, int snap_range)
std::vector<AudioMarker*> AudioTimingControllerDialogue::OnRightClick(int ms, bool ctrl_down, int sensitivity, int snap_range)
{
clicked_ms = INT_MIN;
std::vector<AudioMarker*> ret = GetRightMarkers();
SetMarkers(ret, ms, snap_range);
return ret;
if (ctrl_down) {
context->audioController->PlayToEnd(ms);
return {};
} else {
clicked_ms = INT_MIN;
std::vector<AudioMarker*> ret = GetRightMarkers();
SetMarkers(ret, ms, snap_range);
tap_marker_idx = 1;
return ret;
}
}
void AudioTimingControllerDialogue::OnMarkerDrag(std::vector<AudioMarker*> const& markers, int new_position, int snap_range)

View File

@ -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<AudioMarker*> OnLeftClick(int ms, bool, bool, int sensitivity, int) override;
std::vector<AudioMarker*> OnRightClick(int ms, bool, int, int) override;
std::vector<AudioMarker*> OnRightClick(int ms, bool ctrl_down, int, int) override;
void OnMarkerDrag(std::vector<AudioMarker*> 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<AudioMarker*> 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<AudioMarker*> AudioTimingControllerKaraoke::OnRightClick(int ms, bool, int, int) {
cur_syl = distance(markers.begin(), lower_bound(markers.begin(), markers.end(), ms));
std::vector<AudioMarker*> 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<AudioMarker*> const&
int syl = MoveMarker(static_cast<KaraokeMarker *>(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<KaraokeMarker *>(marker), marker->GetPosition() + delta);
syl = cur_syl;
announce_primary = true;
}
AnnounceChanges(syl);
AnnounceChanges(announce_primary);
}
void AudioTimingControllerKaraoke::GetLabels(TimeRange const& range, std::vector<AudioLabel> &out) const {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -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

View File

@ -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<time_length_decrease_shift>());
reg(agi::make_unique<time_length_increase>());
reg(agi::make_unique<time_length_increase_shift>());
reg(agi::make_unique<time_tap_connect>());
reg(agi::make_unique<time_tap_no_connect>());
reg(agi::make_unique<time_next>());
reg(agi::make_unique<time_opt_tap_to_time>());
reg(agi::make_unique<time_prev>());
reg(agi::make_unique<time_shift>());
reg(agi::make_unique<time_snap_end_video>());

View File

@ -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));
}

View File

@ -471,7 +471,8 @@
},
"Timing" : {
"Default Duration" : 3000
"Default Duration" : 3000,
"Tap To Time" : false
},
"Tool" : {

View File

@ -32,6 +32,12 @@
],
"time/start/increase" : [
"KP_6"
],
"time/mark/connect" : [
"I"
],
"time/mark/no_connect" : [
"O"
]
},
"Audio" : {

View File

@ -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"
],

View File

@ -471,7 +471,8 @@
},
"Timing" : {
"Default Duration" : 3000
"Default Duration" : 3000,
"Tap To Time" : false
},
"Tool" : {