2013-04-05 04:53:59 +02:00
|
|
|
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
|
2006-01-16 22:02:54 +01:00
|
|
|
//
|
2011-09-28 21:44:07 +02:00
|
|
|
// Permission to use, copy, modify, and distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice appear in all copies.
|
2006-01-16 22:02:54 +01:00
|
|
|
//
|
2011-09-28 21:44:07 +02:00
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
2006-01-16 22:02:54 +01:00
|
|
|
//
|
2009-07-29 07:43:02 +02:00
|
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
|
|
|
|
/// @file audio_karaoke.cpp
|
|
|
|
/// @brief Karaoke table UI in audio box (not in audio display)
|
|
|
|
/// @ingroup audio_ui
|
|
|
|
///
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
#include "audio_karaoke.h"
|
|
|
|
|
|
|
|
#include "include/aegisub/context.h"
|
2010-06-01 10:21:30 +02:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
#include "ass_dialogue.h"
|
|
|
|
#include "ass_file.h"
|
|
|
|
#include "ass_karaoke.h"
|
2011-09-29 07:33:10 +02:00
|
|
|
#include "audio_box.h"
|
2012-02-02 00:59:12 +01:00
|
|
|
#include "audio_controller.h"
|
2011-09-29 07:33:10 +02:00
|
|
|
#include "audio_timing.h"
|
2012-12-30 00:53:56 +01:00
|
|
|
#include "compat.h"
|
2011-09-28 21:44:07 +02:00
|
|
|
#include "libresrc/libresrc.h"
|
2013-01-07 02:50:09 +01:00
|
|
|
#include "options.h"
|
2014-05-22 01:23:28 +02:00
|
|
|
#include "project.h"
|
2011-07-15 06:04:13 +02:00
|
|
|
#include "selection_controller.h"
|
2012-10-01 19:25:48 +02:00
|
|
|
#include "utils.h"
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2014-04-23 22:53:24 +02:00
|
|
|
#include <libaegisub/make_unique.h>
|
2013-10-23 23:43:05 +02:00
|
|
|
|
2013-04-05 04:53:59 +02:00
|
|
|
#include <algorithm>
|
|
|
|
#include <boost/locale/boundary.hpp>
|
2012-12-30 00:53:56 +01:00
|
|
|
#include <wx/bmpbuttn.h>
|
|
|
|
#include <wx/button.h>
|
|
|
|
#include <wx/dcclient.h>
|
|
|
|
#include <wx/dcmemory.h>
|
|
|
|
#include <wx/menu.h>
|
|
|
|
#include <wx/panel.h>
|
|
|
|
#include <wx/settings.h>
|
|
|
|
#include <wx/sizer.h>
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
template<class Container, class Value>
|
|
|
|
static inline size_t last_lt_or_eq(Container const& c, Value const& v) {
|
2013-11-21 18:13:36 +01:00
|
|
|
auto it = lower_bound(c.begin(), c.end(), v);
|
2011-09-28 21:44:07 +02:00
|
|
|
// lower_bound gives first >=
|
|
|
|
if (it == c.end() || *it > v)
|
|
|
|
--it;
|
|
|
|
return distance(c.begin(), it);
|
2011-12-28 22:27:06 +01:00
|
|
|
}
|
2011-09-28 21:44:07 +02:00
|
|
|
|
|
|
|
AudioKaraoke::AudioKaraoke(wxWindow *parent, agi::Context *c)
|
|
|
|
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_SUNKEN)
|
|
|
|
, c(c)
|
|
|
|
, file_changed(c->ass->AddCommitListener(&AudioKaraoke::OnFileChanged, this))
|
2014-05-22 01:23:28 +02:00
|
|
|
, audio_opened(c->project->AddAudioProviderListener(&AudioKaraoke::OnAudioOpened, this))
|
2012-10-05 05:22:54 +02:00
|
|
|
, active_line_changed(c->selectionController->AddActiveLineListener(&AudioKaraoke::OnActiveLineChanged, this))
|
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
2018-10-21 09:42:33 +02:00
|
|
|
, tap_to_time_toggled(OPT_SUB("Timing/Tap To Time", &AudioKaraoke::OnTapMarkerChanged, this))
|
2014-04-23 22:53:24 +02:00
|
|
|
, kara(agi::make_unique<AssKaraoke>())
|
2007-06-23 02:21:20 +02:00
|
|
|
{
|
2012-09-25 01:35:27 +02:00
|
|
|
using std::bind;
|
2007-06-23 02:21:20 +02:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
cancel_button = new wxBitmapButton(this, -1, GETIMAGE(kara_split_cancel_16));
|
|
|
|
cancel_button->SetToolTip(_("Discard all uncommitted splits"));
|
2013-12-12 03:25:13 +01:00
|
|
|
cancel_button->Bind(wxEVT_BUTTON, bind(&AudioKaraoke::CancelSplit, this));
|
2006-02-23 22:56:46 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
accept_button = new wxBitmapButton(this, -1, GETIMAGE(kara_split_accept_16));
|
|
|
|
accept_button->SetToolTip(_("Commit splits"));
|
2013-12-12 03:25:13 +01:00
|
|
|
accept_button->Bind(wxEVT_BUTTON, bind(&AudioKaraoke::AcceptSplit, this));
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
split_area = new wxPanel(this);
|
2006-01-27 01:48:59 +01:00
|
|
|
|
2011-12-26 23:20:49 +01:00
|
|
|
wxSizer *main_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
main_sizer->Add(cancel_button);
|
|
|
|
main_sizer->Add(accept_button);
|
|
|
|
main_sizer->Add(split_area, wxSizerFlags(1).Expand());
|
2011-09-28 21:44:07 +02:00
|
|
|
SetSizerAndFit(main_sizer);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
/// @todo subscribe
|
2013-12-10 18:08:30 +01:00
|
|
|
split_font.SetFaceName(FontFace("Audio/Karaoke"));
|
2011-09-28 21:44:07 +02:00
|
|
|
split_font.SetPointSize(OPT_GET("Audio/Karaoke/Font Size")->GetInt());
|
|
|
|
|
2012-10-02 00:21:18 +02:00
|
|
|
split_area->Bind(wxEVT_SIZE, &AudioKaraoke::OnSize, this);
|
2011-09-28 21:44:07 +02:00
|
|
|
split_area->Bind(wxEVT_PAINT, &AudioKaraoke::OnPaint, this);
|
|
|
|
split_area->Bind(wxEVT_LEFT_DOWN, &AudioKaraoke::OnMouse, this);
|
|
|
|
split_area->Bind(wxEVT_LEFT_UP, &AudioKaraoke::OnMouse, this);
|
|
|
|
split_area->Bind(wxEVT_MOTION, &AudioKaraoke::OnMouse, this);
|
|
|
|
split_area->Bind(wxEVT_LEAVE_WINDOW, &AudioKaraoke::OnMouse, this);
|
|
|
|
split_area->Bind(wxEVT_CONTEXT_MENU, &AudioKaraoke::OnContextMenu, this);
|
2012-10-01 19:25:48 +02:00
|
|
|
scroll_timer.Bind(wxEVT_TIMER, &AudioKaraoke::OnScrollTimer, this);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-29 07:33:10 +02:00
|
|
|
accept_button->Enable(false);
|
|
|
|
cancel_button->Enable(false);
|
|
|
|
enabled = false;
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
AudioKaraoke::~AudioKaraoke() {
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
void AudioKaraoke::OnActiveLineChanged(AssDialogue *new_line) {
|
|
|
|
active_line = new_line;
|
|
|
|
if (enabled) {
|
|
|
|
LoadFromLine();
|
|
|
|
split_area->Refresh(false);
|
2008-01-19 01:48:47 +01:00
|
|
|
}
|
2011-09-28 21:44:07 +02:00
|
|
|
}
|
2008-01-19 01:48:47 +01:00
|
|
|
|
2014-12-28 21:06:15 +01:00
|
|
|
void AudioKaraoke::OnFileChanged(int type, const AssDialogue *changed) {
|
|
|
|
if (enabled && (type & AssFile::COMMIT_DIAG_FULL) && (!changed || changed == active_line)) {
|
2011-09-28 21:44:07 +02:00
|
|
|
LoadFromLine();
|
|
|
|
split_area->Refresh(false);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-09 16:22:49 +02:00
|
|
|
void AudioKaraoke::OnAudioOpened(agi::AudioProvider *provider) {
|
2014-05-22 01:23:28 +02:00
|
|
|
if (provider)
|
|
|
|
SetEnabled(enabled);
|
|
|
|
else
|
|
|
|
c->audioController->SetTimingController(nullptr);
|
2011-11-18 23:58:22 +01:00
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
void AudioKaraoke::SetEnabled(bool en) {
|
2011-09-29 20:17:37 +02:00
|
|
|
enabled = en;
|
|
|
|
|
|
|
|
c->audioBox->ShowKaraokeBar(enabled);
|
|
|
|
if (enabled) {
|
2011-09-28 21:44:07 +02:00
|
|
|
LoadFromLine();
|
|
|
|
c->audioController->SetTimingController(CreateKaraokeTimingController(c, kara.get(), file_changed));
|
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
2018-10-21 09:42:33 +02:00
|
|
|
c->audioController->GetTimingController()->AddUpdatedTapMarkerListener(&AudioKaraoke::OnTapMarkerChanged, this);
|
2011-09-29 20:17:37 +02:00
|
|
|
Refresh(false);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
2011-09-28 21:44:07 +02:00
|
|
|
else {
|
2011-09-28 21:44:44 +02:00
|
|
|
c->audioController->SetTimingController(CreateDialogueTimingController(c));
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-02 00:21:18 +02:00
|
|
|
void AudioKaraoke::OnSize(wxSizeEvent &evt) {
|
|
|
|
RenderText();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
2011-12-22 22:09:31 +01:00
|
|
|
void AudioKaraoke::OnPaint(wxPaintEvent &) {
|
2011-09-28 21:44:07 +02:00
|
|
|
int w, h;
|
|
|
|
split_area->GetClientSize(&w, &h);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
wxPaintDC dc(split_area);
|
2011-09-29 20:17:37 +02:00
|
|
|
wxMemoryDC bmp_dc(rendered_line);
|
|
|
|
|
|
|
|
// Draw the text and split lines
|
2012-10-01 19:25:48 +02:00
|
|
|
dc.Blit(-scroll_x, 0, rendered_line.GetWidth(), h, &bmp_dc, 0, 0);
|
2011-09-29 20:17:37 +02:00
|
|
|
|
|
|
|
// Draw the split line under the mouse
|
2012-10-05 17:04:46 +02:00
|
|
|
if (click_will_remove_split)
|
|
|
|
dc.SetPen(*wxRED);
|
|
|
|
else
|
|
|
|
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
|
2011-09-29 20:17:37 +02:00
|
|
|
dc.DrawLine(mouse_pos, 0, mouse_pos, h);
|
2012-10-01 19:25:48 +02:00
|
|
|
|
|
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
|
|
|
|
|
|
int width_past_bmp = w + scroll_x - rendered_line.GetWidth();
|
|
|
|
dc.SetBrush(*wxWHITE_BRUSH);
|
|
|
|
if (width_past_bmp > 0)
|
|
|
|
dc.DrawRectangle(w - width_past_bmp, 0, width_past_bmp, h);
|
|
|
|
|
|
|
|
// Draw scroll arrows if needed
|
|
|
|
if (scroll_x > 0) {
|
|
|
|
dc.DrawRectangle(0, 0, 20, h);
|
|
|
|
|
|
|
|
wxPoint triangle[] = {
|
|
|
|
wxPoint(10, h / 2 - 6),
|
|
|
|
wxPoint(4, h / 2),
|
|
|
|
wxPoint(10, h / 2 + 6)
|
|
|
|
};
|
|
|
|
dc.SetBrush(*wxBLACK_BRUSH);
|
|
|
|
dc.DrawPolygon(3, triangle);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rendered_line.GetWidth() - scroll_x > w) {
|
|
|
|
dc.SetBrush(*wxWHITE_BRUSH);
|
|
|
|
dc.DrawRectangle(w - 20, 0, 20, h);
|
|
|
|
|
|
|
|
wxPoint triangle[] = {
|
|
|
|
wxPoint(w - 10, h / 2 - 6),
|
|
|
|
wxPoint(w - 4, h / 2),
|
|
|
|
wxPoint(w - 10, h / 2 + 6)
|
|
|
|
};
|
|
|
|
dc.SetBrush(*wxBLACK_BRUSH);
|
|
|
|
dc.DrawPolygon(3, triangle);
|
|
|
|
}
|
2011-09-29 20:17:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AudioKaraoke::RenderText() {
|
2012-10-01 19:25:48 +02:00
|
|
|
wxSize bmp_size = split_area->GetClientSize();
|
|
|
|
int line_width = spaced_text.size() * char_width + 5;
|
|
|
|
if (line_width > bmp_size.GetWidth())
|
|
|
|
bmp_size.SetWidth(line_width);
|
2011-09-29 20:17:37 +02:00
|
|
|
|
2013-04-05 04:53:59 +02:00
|
|
|
if (!rendered_line.IsOk() || bmp_size != rendered_line.GetSize())
|
2012-10-01 19:25:48 +02:00
|
|
|
rendered_line = wxBitmap(bmp_size);
|
2011-09-29 20:17:37 +02:00
|
|
|
|
|
|
|
wxMemoryDC dc(rendered_line);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
|
|
|
// Draw background
|
2011-09-29 20:17:37 +02:00
|
|
|
dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)));
|
2006-01-16 22:02:54 +01:00
|
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
2012-10-01 19:25:48 +02:00
|
|
|
dc.DrawRectangle(wxPoint(), bmp_size);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
dc.SetFont(split_font);
|
|
|
|
dc.SetTextForeground(wxColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)));
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
// Draw each character in the line
|
2012-10-01 19:25:48 +02:00
|
|
|
int y = (bmp_size.GetHeight() - char_height) / 2;
|
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
2018-10-21 09:42:33 +02:00
|
|
|
for (size_t i = 0; i < spaced_text.size(); ++i) {
|
2018-10-28 20:01:13 +01:00
|
|
|
if (!(tap_syl_start <= i && i < tap_syl_end)) {
|
|
|
|
// Only draw with normal color if _not_ the tap syllable
|
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
2018-10-21 09:42:33 +02:00
|
|
|
dc.DrawText(spaced_text[i], char_x[i], y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw marked syllable
|
|
|
|
dc.SetTextForeground(*wxGREEN);
|
2018-10-28 20:01:13 +01:00
|
|
|
for (size_t i = tap_syl_start; i < tap_syl_end; ++i)
|
2011-09-28 21:44:07 +02:00
|
|
|
dc.DrawText(spaced_text[i], char_x[i], y);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
// Draw the lines between each syllable
|
|
|
|
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
|
2013-11-21 18:13:36 +01:00
|
|
|
for (auto syl_line : syl_lines)
|
|
|
|
dc.DrawLine(syl_line, 0, syl_line, bmp_size.GetHeight());
|
2011-09-28 21:44:07 +02:00
|
|
|
}
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-12-30 00:53:56 +01:00
|
|
|
void AudioKaraoke::AddMenuItem(wxMenu &menu, std::string const& tag, wxString const& help, std::string const& selected) {
|
|
|
|
wxMenuItem *item = menu.AppendCheckItem(-1, to_wx(tag), help);
|
2013-12-12 03:25:13 +01:00
|
|
|
menu.Bind(wxEVT_MENU, std::bind(&AudioKaraoke::SetTagType, this, tag), item->GetId());
|
2011-09-28 21:44:07 +02:00
|
|
|
item->Check(tag == selected);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
void AudioKaraoke::OnContextMenu(wxContextMenuEvent&) {
|
|
|
|
if (!enabled) return;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
wxMenu context_menu(_("Karaoke tag"));
|
2012-12-30 00:53:56 +01:00
|
|
|
std::string type = kara->GetTagType();
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
AddMenuItem(context_menu, "\\k", _("Change karaoke tag to \\k"), type);
|
|
|
|
AddMenuItem(context_menu, "\\kf", _("Change karaoke tag to \\kf"), type);
|
|
|
|
AddMenuItem(context_menu, "\\ko", _("Change karaoke tag to \\ko"), type);
|
2007-06-30 02:00:44 +02:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
PopupMenu(&context_menu);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
void AudioKaraoke::OnMouse(wxMouseEvent &event) {
|
|
|
|
if (!enabled) return;
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
mouse_pos = event.GetX();
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2012-10-01 19:25:48 +02:00
|
|
|
if (event.Leaving()) {
|
|
|
|
mouse_pos = -1;
|
|
|
|
split_area->Refresh(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scroll_timer.IsRunning() || split_area->HasCapture()) {
|
|
|
|
if (event.LeftUp()) {
|
|
|
|
scroll_timer.Stop();
|
|
|
|
split_area->ReleaseMouse();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the mouse is over a scroll arrow
|
|
|
|
int client_width = split_area->GetClientSize().GetWidth();
|
2013-04-05 04:53:59 +02:00
|
|
|
if (scroll_x > 0 && mouse_pos < 20)
|
2012-10-01 19:25:48 +02:00
|
|
|
scroll_dir = -1;
|
2013-04-05 04:53:59 +02:00
|
|
|
else if (scroll_x + client_width < rendered_line.GetWidth() && mouse_pos > client_width - 20)
|
2012-10-01 19:25:48 +02:00
|
|
|
scroll_dir = 1;
|
2013-04-05 04:53:59 +02:00
|
|
|
else
|
2012-10-01 19:25:48 +02:00
|
|
|
scroll_dir = 0;
|
|
|
|
|
|
|
|
if (scroll_dir) {
|
2011-09-28 21:44:07 +02:00
|
|
|
mouse_pos = -1;
|
2012-10-01 19:25:48 +02:00
|
|
|
if (event.LeftDown()) {
|
|
|
|
split_area->Refresh(false);
|
|
|
|
scroll_timer.Start(50);
|
|
|
|
split_area->CaptureMouse();
|
|
|
|
wxTimerEvent evt;
|
|
|
|
OnScrollTimer(evt);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int shifted_pos = mouse_pos + scroll_x;
|
2011-09-28 21:44:07 +02:00
|
|
|
|
|
|
|
// Character to insert the new split point before
|
2013-04-05 04:53:59 +02:00
|
|
|
int split_pos = std::min<int>((shifted_pos + char_width / 2) / char_width, spaced_text.size());
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
// Syllable this character is in
|
|
|
|
int syl = last_lt_or_eq(syl_start_points, split_pos);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
// If the click is sufficiently close to a line of a syllable split,
|
|
|
|
// remove that split rather than adding a new one
|
2012-10-05 17:21:29 +02:00
|
|
|
bool click_right = (syl > 0 && shifted_pos <= syl_lines[syl - 1] + 3);
|
|
|
|
bool click_left = (syl < (int)syl_lines.size() && shifted_pos >= syl_lines[syl] - 3);
|
|
|
|
click_will_remove_split = click_left || click_right;
|
2012-10-05 17:04:46 +02:00
|
|
|
|
|
|
|
if (!event.LeftDown()) {
|
|
|
|
// Erase the old line and draw the new one
|
|
|
|
split_area->Refresh(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-04-05 04:53:59 +02:00
|
|
|
if (click_will_remove_split)
|
2012-10-05 17:21:29 +02:00
|
|
|
kara->RemoveSplit(syl + (click_left && !click_right));
|
2013-04-05 04:53:59 +02:00
|
|
|
else
|
|
|
|
kara->AddSplit(syl, char_to_byte[split_pos] - 1);
|
2006-01-16 22:02:54 +01:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
SetDisplayText();
|
|
|
|
accept_button->Enable(true);
|
|
|
|
cancel_button->Enable(true);
|
|
|
|
split_area->Refresh(false);
|
2006-01-16 22:02:54 +01:00
|
|
|
}
|
|
|
|
|
2012-10-01 19:25:48 +02:00
|
|
|
void AudioKaraoke::OnScrollTimer(wxTimerEvent &) {
|
|
|
|
scroll_x += scroll_dir * char_width * 3;
|
|
|
|
|
|
|
|
int max_scroll = rendered_line.GetWidth() + 20 - split_area->GetClientSize().GetWidth();
|
|
|
|
if (scroll_x < 0 || scroll_x > max_scroll) {
|
|
|
|
scroll_x = mid(0, scroll_x, max_scroll);
|
|
|
|
scroll_timer.Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
split_area->Refresh(false);
|
|
|
|
}
|
|
|
|
|
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
2018-10-21 09:42:33 +02:00
|
|
|
void AudioKaraoke::OnTapMarkerChanged() {
|
2018-10-28 20:01:13 +01:00
|
|
|
tap_syl_start = 0;
|
|
|
|
tap_syl_end = 0;
|
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
2018-10-21 09:42:33 +02:00
|
|
|
|
|
|
|
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) {
|
2018-10-28 20:01:13 +01:00
|
|
|
tap_syl_start = syl_start_points[marker_idx - 1];
|
|
|
|
tap_syl_end =
|
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
2018-10-21 09:42:33 +02:00
|
|
|
(marker_idx < syl_start_points.size() ?
|
|
|
|
syl_start_points[marker_idx] :
|
|
|
|
spaced_text.size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RenderText();
|
|
|
|
Refresh(false);
|
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
void AudioKaraoke::LoadFromLine() {
|
2012-10-01 19:25:48 +02:00
|
|
|
scroll_x = 0;
|
|
|
|
scroll_timer.Stop();
|
2011-09-28 21:44:07 +02:00
|
|
|
kara->SetLine(active_line, true);
|
|
|
|
SetDisplayText();
|
2012-06-07 04:48:08 +02:00
|
|
|
accept_button->Enable(kara->GetText() != active_line->Text);
|
2011-09-28 21:44:07 +02:00
|
|
|
cancel_button->Enable(false);
|
2009-05-14 23:44:43 +02:00
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
void AudioKaraoke::SetDisplayText() {
|
2013-04-05 04:53:59 +02:00
|
|
|
using namespace boost::locale::boundary;
|
|
|
|
|
|
|
|
wxMemoryDC dc;
|
|
|
|
dc.SetFont(split_font);
|
|
|
|
|
|
|
|
auto get_char_width = [&](std::string const& character) -> int {
|
|
|
|
const auto it = char_widths.find(character);
|
|
|
|
if (it != end(char_widths))
|
|
|
|
return it->second;
|
|
|
|
|
|
|
|
const auto size = dc.GetTextExtent(to_wx(character));
|
|
|
|
char_height = std::max(char_height, size.GetHeight());
|
|
|
|
char_widths[character] = size.GetWidth();
|
|
|
|
return size.GetWidth();
|
|
|
|
};
|
|
|
|
|
|
|
|
char_width = get_char_width(" ");
|
|
|
|
|
|
|
|
// Width in pixels of each character in this string
|
|
|
|
std::vector<int> str_char_widths;
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
spaced_text.clear();
|
2013-04-05 04:53:59 +02:00
|
|
|
char_to_byte.clear();
|
2011-09-28 21:44:07 +02:00
|
|
|
syl_start_points.clear();
|
2012-11-04 04:53:03 +01:00
|
|
|
for (auto const& syl : *kara) {
|
2013-04-05 04:53:59 +02:00
|
|
|
// The last (and only the last) syllable needs the width of the final
|
|
|
|
// character in the syllable, so we unconditionally add it at the end
|
|
|
|
// of this loop, then remove the extra ones here
|
|
|
|
if (!char_to_byte.empty())
|
|
|
|
char_to_byte.pop_back();
|
2006-06-27 06:04:40 +02:00
|
|
|
|
2013-04-05 04:53:59 +02:00
|
|
|
syl_start_points.push_back(spaced_text.size());
|
2006-06-27 06:04:40 +02:00
|
|
|
|
2013-04-05 04:53:59 +02:00
|
|
|
// Add a space between each syllable to avoid crowding
|
|
|
|
spaced_text.emplace_back(wxS(" "));
|
|
|
|
str_char_widths.push_back(char_width);
|
|
|
|
char_to_byte.push_back(1);
|
|
|
|
|
|
|
|
size_t syl_idx = 1;
|
|
|
|
const ssegment_index characters(character, begin(syl.text), end(syl.text));
|
|
|
|
for (auto chr : characters) {
|
|
|
|
// Calculate the width in pixels of this character
|
|
|
|
const std::string character = chr.str();
|
|
|
|
const int width = get_char_width(character);
|
|
|
|
char_width = std::max(char_width, width);
|
|
|
|
str_char_widths.push_back(width);
|
|
|
|
|
|
|
|
spaced_text.emplace_back(to_wx(character));
|
|
|
|
char_to_byte.push_back(syl_idx);
|
|
|
|
syl_idx += character.size();
|
|
|
|
}
|
2006-06-27 06:04:40 +02:00
|
|
|
|
2013-04-05 04:53:59 +02:00
|
|
|
char_to_byte.push_back(syl_idx);
|
|
|
|
}
|
2011-09-28 21:44:07 +02:00
|
|
|
|
|
|
|
// Center each character within the space available to it
|
2013-04-05 04:53:59 +02:00
|
|
|
char_x.resize(str_char_widths.size());
|
|
|
|
for (size_t i = 0; i < str_char_widths.size(); ++i)
|
|
|
|
char_x[i] = i * char_width + (char_width - str_char_widths[i]) / 2;
|
2006-06-27 06:04:40 +02:00
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
// Calculate the positions of the syllable divider lines
|
|
|
|
syl_lines.resize(syl_start_points.size() - 1);
|
2013-04-05 04:53:59 +02:00
|
|
|
for (size_t i = 1; i < syl_start_points.size(); ++i)
|
2011-09-28 21:44:07 +02:00
|
|
|
syl_lines[i - 1] = syl_start_points[i] * char_width + char_width / 2;
|
2011-09-29 20:17:37 +02:00
|
|
|
|
|
|
|
RenderText();
|
2006-06-27 06:04:40 +02:00
|
|
|
}
|
|
|
|
|
2011-09-28 21:44:07 +02:00
|
|
|
void AudioKaraoke::CancelSplit() {
|
|
|
|
LoadFromLine();
|
|
|
|
split_area->Refresh(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioKaraoke::AcceptSplit() {
|
|
|
|
active_line->Text = kara->GetText();
|
|
|
|
file_changed.Block();
|
|
|
|
c->ass->Commit(_("karaoke split"), AssFile::COMMIT_DIAG_TEXT);
|
|
|
|
file_changed.Unblock();
|
|
|
|
|
|
|
|
accept_button->Enable(false);
|
|
|
|
cancel_button->Enable(false);
|
|
|
|
}
|
|
|
|
|
2012-12-30 00:53:56 +01:00
|
|
|
void AudioKaraoke::SetTagType(std::string const& new_tag) {
|
2011-09-28 21:44:07 +02:00
|
|
|
kara->SetTagType(new_tag);
|
|
|
|
AcceptSplit();
|
|
|
|
}
|